diff --git a/3rdparty/llvm/CMakeLists.txt b/3rdparty/llvm/CMakeLists.txt index 0e11aac59..7ead340eb 100644 --- a/3rdparty/llvm/CMakeLists.txt +++ b/3rdparty/llvm/CMakeLists.txt @@ -185,12 +185,13 @@ if(WITH_LLVM) message(FATAL_ERROR "Can't find LLVM libraries from the CMAKE_PREFIX_PATH path or LLVM_DIR. \ Enable BUILD_LLVM option to build LLVM from included as a git submodule.") endif() + if (LLVM_VERSION VERSION_LESS 18) message(FATAL_ERROR "Found LLVM version ${LLVM_VERSION}. Required version 18 or above.") endif() if (NOT MLIR_FOUND) - message(FATAL_ERROR "Can't find MLIR libraries from the CMAKE_PREFIX_PATH path or MLIR_DIR") + message(FATAL_ERROR "Can't find MLIR libraries from the CMAKE_PREFIX_PATH path or MLIR_DIR: ${MLIR_DIR}") endif() diff --git a/CMakeLists.txt b/CMakeLists.txt index 74b9b953d..dff353bec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -279,6 +279,7 @@ add_subdirectory(rpcsx) if (WITH_PS3) include(ConfigureCompiler) + add_subdirectory(kernel/cellos) add_subdirectory(rpcs3) add_subdirectory(ps3fw) endif() diff --git a/android/src/rpcsx-android.cpp b/android/src/rpcsx-android.cpp index 53f79aeda..49df82b3f 100644 --- a/android/src/rpcsx-android.cpp +++ b/android/src/rpcsx-android.cpp @@ -4,7 +4,7 @@ #include "Emu/Audio/Null/NullAudioBackend.h" #include "Emu/Cell/PPUAnalyser.h" #include "Emu/Cell/SPURecompiler.h" -#include "Emu/Cell/lv2/sys_sync.h" +#include "cellos/sys_sync.h" #include "Emu/IdManager.h" #include "Emu/Io/KeyboardHandler.h" #include "Emu/Io/Null/NullKeyboardHandler.h" diff --git a/kernel/cellos/CMakeLists.txt b/kernel/cellos/CMakeLists.txt new file mode 100644 index 000000000..5fb9c1a24 --- /dev/null +++ b/kernel/cellos/CMakeLists.txt @@ -0,0 +1,70 @@ +add_library(cellos-kernel STATIC + src/lv2.cpp + src/sys_bdemu.cpp + src/sys_btsetting.cpp + src/sys_cond.cpp + src/sys_console.cpp + src/sys_crypto_engine.cpp + src/sys_config.cpp + src/sys_dbg.cpp + src/sys_event.cpp + src/sys_event_flag.cpp + src/sys_fs.cpp + src/sys_game.cpp + src/sys_gamepad.cpp + src/sys_gpio.cpp + src/sys_hid.cpp + src/sys_interrupt.cpp + src/sys_io.cpp + src/sys_lwcond.cpp + src/sys_lwmutex.cpp + src/sys_memory.cpp + src/sys_mmapper.cpp + src/sys_mutex.cpp + src/sys_net.cpp + src/sys_net/lv2_socket.cpp + src/sys_net/lv2_socket_native.cpp + src/sys_net/lv2_socket_raw.cpp + src/sys_net/lv2_socket_p2p.cpp + src/sys_net/lv2_socket_p2ps.cpp + src/sys_net/network_context.cpp + src/sys_net/nt_p2p_port.cpp + src/sys_net/sys_net_helpers.cpp + src/sys_overlay.cpp + src/sys_ppu_thread.cpp + src/sys_process.cpp + src/sys_prx.cpp + src/sys_rsx.cpp + src/sys_rsxaudio.cpp + src/sys_rwlock.cpp + src/sys_semaphore.cpp + src/sys_spu.cpp + src/sys_sm.cpp + src/sys_ss.cpp + src/sys_storage.cpp + src/sys_time.cpp + src/sys_timer.cpp + src/sys_trace.cpp + src/sys_tty.cpp + src/sys_uart.cpp + src/sys_usbd.cpp + src/sys_vm.cpp +) + +target_include_directories(cellos-kernel +PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/include + +PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/include/cellos +) + +target_link_libraries(cellos-kernel PUBLIC + rpcs3_core # FIXME: remove + 3rdparty::soundtouch # FIXME: remove + 3rdparty::flatbuffers # FIXME: remove + 3rdparty::wolfssl # FIXME: remove + 3rdparty::miniupnpc # FIXME: remove + 3rdparty::libusb # FIXME: remove + 3rdparty::rtmidi # FIXME: remove +) diff --git a/rpcs3/Emu/Cell/lv2/sys_bdemu.h b/kernel/cellos/include/cellos/sys_bdemu.h similarity index 71% rename from rpcs3/Emu/Cell/lv2/sys_bdemu.h rename to kernel/cellos/include/cellos/sys_bdemu.h index 4093f8b54..f36eb1012 100644 --- a/rpcs3/Emu/Cell/lv2/sys_bdemu.h +++ b/kernel/cellos/include/cellos/sys_bdemu.h @@ -1,8 +1,9 @@ #pragma once -#include "Emu/Memory/vm_ptr.h" #include "Emu/Cell/ErrorCodes.h" +#include "Emu/Memory/vm_ptr.h" // SysCalls -error_code sys_bdemu_send_command(u64 cmd, u64 a2, u64 a3, vm::ptr buf, u64 buf_len); +error_code sys_bdemu_send_command(u64 cmd, u64 a2, u64 a3, vm::ptr buf, + u64 buf_len); diff --git a/rpcs3/Emu/Cell/lv2/sys_btsetting.h b/kernel/cellos/include/cellos/sys_btsetting.h similarity index 100% rename from rpcs3/Emu/Cell/lv2/sys_btsetting.h rename to kernel/cellos/include/cellos/sys_btsetting.h index 9224bf0e0..c19c066ad 100644 --- a/rpcs3/Emu/Cell/lv2/sys_btsetting.h +++ b/kernel/cellos/include/cellos/sys_btsetting.h @@ -1,7 +1,7 @@ #pragma once -#include "Emu/Memory/vm_ptr.h" #include "Emu/Cell/ErrorCodes.h" +#include "Emu/Memory/vm_ptr.h" // SysCalls diff --git a/kernel/cellos/include/cellos/sys_cond.h b/kernel/cellos/include/cellos/sys_cond.h new file mode 100644 index 000000000..6072b47a4 --- /dev/null +++ b/kernel/cellos/include/cellos/sys_cond.h @@ -0,0 +1,49 @@ +#pragma once + +#include "sys_mutex.h" +#include "sys_sync.h" + +struct lv2_mutex; + +struct sys_cond_attribute_t { + be_t pshared; + be_t flags; + be_t ipc_key; + + union { + nse_t name_u64; + char name[sizeof(u64)]; + }; +}; + +struct lv2_cond final : lv2_obj { + static const u32 id_base = 0x86000000; + + const u64 key; + const u64 name; + const u32 mtx_id; + + lv2_mutex *mutex; // Associated Mutex + shared_ptr _mutex; + ppu_thread *sq{}; + + lv2_cond(u64 key, u64 name, u32 mtx_id, shared_ptr mutex0) noexcept; + + lv2_cond(utils::serial &ar) noexcept; + static std::function load(utils::serial &ar); + void save(utils::serial &ar); + + CellError on_id_create(); +}; + +class ppu_thread; + +// Syscalls + +error_code sys_cond_create(ppu_thread &ppu, vm::ptr cond_id, u32 mutex_id, + vm::ptr attr); +error_code sys_cond_destroy(ppu_thread &ppu, u32 cond_id); +error_code sys_cond_wait(ppu_thread &ppu, u32 cond_id, u64 timeout); +error_code sys_cond_signal(ppu_thread &ppu, u32 cond_id); +error_code sys_cond_signal_all(ppu_thread &ppu, u32 cond_id); +error_code sys_cond_signal_to(ppu_thread &ppu, u32 cond_id, u32 thread_id); diff --git a/kernel/cellos/include/cellos/sys_config.h b/kernel/cellos/include/cellos/sys_config.h new file mode 100644 index 000000000..4a571c7c3 --- /dev/null +++ b/kernel/cellos/include/cellos/sys_config.h @@ -0,0 +1,444 @@ +#pragma once + +#include "Emu/Cell/timers.hpp" +#include "sys_event.h" +#include "util/atomic.hpp" +#include "util/mutex.h" +#include "util/shared_ptr.hpp" + +/* + * sys_config is a "subscription-based data storage API" + * + * It has the concept of services and listeners. Services provide data, + * listeners subscribe to registration/unregistration events from specific + * services. + * + * Services are divided into two classes: LV2 services (positive service IDs) + * and User services (negative service IDs). LV2 services seem to be implictly + * "available", probably constructed on-demand with internal LV2 code generating + * the data. An example is PadManager (service ID 0x11). User services may be + * registered through a syscall, and have negative IDs. An example is libPad + * (service ID 0x8000'0000'0000'0001). Note that user-mode *cannot* register + * positive service IDs. + * + * To start with, you have to get a sys_config handle by calling sys_config_open + * and providing an event queue. This event queue will be used for sys_config + * notifications if a subscribed config event is registered. + * + * With a sys_config handle, listeners can be added to specific services using + * sys_config_add_service_listener. This syscall returns a service listener + * handle, which can be used to close the listener and stop further + * notifications. Once subscribed, any matching past service registrations will + * be automatically sent to the supplied queue (thus the "data storage"). + * + * Services exist "implicitly", and data may be registered *onto* a service by + * calling sys_config_register_service. You can remove config events by calling + * sys_config_unregister_service and providing the handle returned when + * registering a service. + * + * If a service is registered (or unregistered) and matches any active listener, + * that listener will get an event sent to the event queue provided in the call + * to sys_config_open. + * + * This event will contain the type of config event ("service event" or "IO + * event", in event.source), the corresponding sys_config handle (event.data1), + * the config event ID (event.data2 & 0xffff'ffff), whether the service was + * registered or unregistered ('data2 >> 32'), and what buffer size will be + * needed to read the corresponding service event (event.data3). + * + * NOTE: if multiple listeners exist, each gets a separate event ID even though + * all events are the same! + * + * After receiving such an event from the event queue, the user should allocate + * enough buffer and call sys_config_get_service_event (or sys_config_io_event) + * with the given event ID, in order to obtain a sys_config_service_event_t (or + * sys_config_io_event_t) structure with the contents of the service that was + * (un)registered. + */ + +class lv2_config_handle; +class lv2_config_service; +class lv2_config_service_listener; +class lv2_config_service_event; + +// Known sys_config service IDs +enum sys_config_service_id : s64 { + SYS_CONFIG_SERVICE_PADMANAGER = 0x11, + SYS_CONFIG_SERVICE_PADMANAGER2 = + 0x12, // lv2 seems to send padmanager events to both 0x11 and 0x12 + SYS_CONFIG_SERVICE_0x20 = 0x20, + SYS_CONFIG_SERVICE_0x30 = 0x30, + + SYS_CONFIG_SERVICE_USER_BASE = + static_cast(UINT64_C(0x8000'0000'0000'0000)), + SYS_CONFIG_SERVICE_USER_LIBPAD = SYS_CONFIG_SERVICE_USER_BASE + 1, + SYS_CONFIG_SERVICE_USER_LIBKB = SYS_CONFIG_SERVICE_USER_BASE + 2, + SYS_CONFIG_SERVICE_USER_LIBMOUSE = SYS_CONFIG_SERVICE_USER_BASE + 3, + SYS_CONFIG_SERVICE_USER_0x1000 = SYS_CONFIG_SERVICE_USER_BASE + 0x1000, + SYS_CONFIG_SERVICE_USER_0x1010 = SYS_CONFIG_SERVICE_USER_BASE + 0x1010, + SYS_CONFIG_SERVICE_USER_0x1011 = SYS_CONFIG_SERVICE_USER_BASE + 0x1011, + SYS_CONFIG_SERVICE_USER_0x1013 = SYS_CONFIG_SERVICE_USER_BASE + 0x1013, + SYS_CONFIG_SERVICE_USER_0x1020 = SYS_CONFIG_SERVICE_USER_BASE + 0x1020, + SYS_CONFIG_SERVICE_USER_0x1030 = SYS_CONFIG_SERVICE_USER_BASE + 0x1030, +}; + +enum sys_config_service_listener_type : u32 { + SYS_CONFIG_SERVICE_LISTENER_ONCE = 0, + SYS_CONFIG_SERVICE_LISTENER_REPEATING = 1 +}; + +enum sys_config_event_source : u64 { + SYS_CONFIG_EVENT_SOURCE_SERVICE = 1, + SYS_CONFIG_EVENT_SOURCE_IO = 2 +}; + +/* + * Dynamic-sized struct to describe a sys_config_service_event + * We never allocate it - the guest does it for us and provides a pointer + */ +struct sys_config_service_event_t { + // Handle to the service listener for whom this event is destined + be_t service_listener_handle; + + // 1 if this service is currently registered or unregistered + be_t registered; + + // Service ID that triggered this event + be_t service_id; + + // Custom ID provided by the user, used to uniquely identify service events + // (provided to sys_config_register_event) When a service is unregistered, + // this is the only value available to distinguish which service event was + // unregistered. + be_t user_id; + + /* if added==0, the structure ends here */ + + // Verbosity of this service event (provided to sys_config_register_event) + be_t verbosity; + + // Size of 'data' + be_t data_size; + + // Ignored, seems to be simply 32-bits of padding + be_t padding; + + // Buffer containing event data (copy of the buffer supplied to + // sys_config_register_service) NOTE: This buffer size is dynamic, according + // to 'data_size', and can be 0. Here it is set to 1 since zero-sized buffers + // are not standards-compliant + u8 data[1]; +}; + +/* + * Event data structure for SYS_CONFIG_SERVICE_PADMANAGER + * This is a guess + */ +struct sys_config_padmanager_data_t { + be_t unk[5]; // hid device type ? + be_t vid; + be_t pid; + be_t unk2[6]; // bluetooth address? +}; +static_assert(sizeof(sys_config_padmanager_data_t) == 26); + +/* + * Global sys_config state + */ + +class lv2_config { + atomic_t m_state = 0; + + // LV2 Config mutex + shared_mutex m_mutex; + + // Map of LV2 Service Events + std::unordered_map> events; + +public: + void initialize(); + + // Service Events + void add_service_event(shared_ptr event); + void remove_service_event(u32 id); + + shared_ptr find_event(u32 id) { + reader_lock lock(m_mutex); + + const auto it = events.find(id); + + if (it == events.cend()) + return null_ptr; + + if (it->second) { + return it->second; + } + + return null_ptr; + } + + ~lv2_config() noexcept; +}; + +/* + * LV2 Config Handle object, managed by IDM + */ +class lv2_config_handle { +public: + static const u32 id_base = 0x41000000; + static const u32 id_step = 0x100; + static const u32 id_count = 2048; + SAVESTATE_INIT_POS(37); + +private: + u32 idm_id; + + // queue for service/io event notifications + const shared_ptr queue; + + bool send_queue_event(u64 source, u64 d1, u64 d2, u64 d3) const { + if (auto sptr = queue) { + return sptr->send(source, d1, d2, d3) == 0; + } + + return false; + } + +public: + // Constructors (should not be used directly) + lv2_config_handle(shared_ptr _queue) noexcept + : queue(std::move(_queue)) {} + + // Factory + template + static shared_ptr create(Args &&...args) { + if (auto cfg = + idm::make_ptr(std::forward(args)...)) { + cfg->idm_id = idm::last_id(); + return cfg; + } + return null_ptr; + } + + // Notify event queue for this handle + bool notify(u64 source, u64 data2, u64 data3) const { + return send_queue_event(source, idm_id, data2, data3); + } +}; + +/* + * LV2 Service object, managed by IDM + */ +class lv2_config_service { +public: + static const u32 id_base = 0x43000000; + static const u32 id_step = 0x100; + static const u32 id_count = 2048; + SAVESTATE_INIT_POS(38); + +private: + // IDM data + u32 idm_id; + + // Whether this service is currently registered or not + bool registered = true; + +public: + const u64 timestamp; + const sys_config_service_id id; + + const u64 user_id; + const u64 verbosity; + const u32 padding; // not used, but stored here just in case + const std::vector data; + + // Constructors (should not be used directly) + lv2_config_service(sys_config_service_id _id, u64 _user_id, u64 _verbosity, + u32 _padding, const u8 *_data, usz size) noexcept + : timestamp(get_system_time()), id(_id), user_id(_user_id), + verbosity(_verbosity), padding(_padding), + data(&_data[0], &_data[size]) {} + + // Factory + template + static shared_ptr create(Args &&...args) { + if (auto service = + idm::make_ptr(std::forward(args)...)) { + service->idm_id = idm::last_id(); + return service; + } + + return null_ptr; + } + + // Registration + bool is_registered() const { return registered; } + void unregister(); + + // Notify listeners + void notify() const; + + // Utilities + usz get_size() const { + return sizeof(sys_config_service_event_t) - 1 + data.size(); + } + shared_ptr get_shared_ptr() const { + return stx::make_shared_from_this(this); + } + u32 get_id() const { return idm_id; } +}; + +/* + * LV2 Service Event Listener object, managed by IDM + */ +class lv2_config_service_listener { +public: + static const u32 id_base = 0x42000000; + static const u32 id_step = 0x100; + static const u32 id_count = 2048; + SAVESTATE_INIT_POS(39); + +private: + // IDM data + u32 idm_id; + + // The service listener owns the service events - service events will not be + // freed as long as their corresponding listener exists This has been + // confirmed to be the case in realhw + std::vector> service_events; + shared_ptr handle; + + bool notify(const shared_ptr &event); + +public: + const sys_config_service_id service_id; + const u64 min_verbosity; + const sys_config_service_listener_type type; + + const std::vector data; + + // Constructors (should not be used directly) + lv2_config_service_listener(shared_ptr _handle, + sys_config_service_id _service_id, + u64 _min_verbosity, + sys_config_service_listener_type _type, + const u8 *_data, usz size) noexcept + : handle(std::move(_handle)), service_id(_service_id), + min_verbosity(_min_verbosity), type(_type), + data(&_data[0], &_data[size]) {} + + // Factory + template + static shared_ptr create(Args &&...args) { + if (auto listener = idm::make_ptr( + std::forward(args)...)) { + listener->idm_id = idm::last_id(); + return listener; + } + + return null_ptr; + } + + // Check whether service matches + bool check_service(const lv2_config_service &service) const; + + // Register new event, and notify queue + bool notify(const shared_ptr &service); + + // (Re-)notify about all still-registered past events + void notify_all(); + + // Utilities + u32 get_id() const { return idm_id; } + shared_ptr get_shared_ptr() const { + return stx::make_shared_from_this(this); + } +}; + +/* + * LV2 Service Event object (*not* managed by IDM) + */ +class lv2_config_service_event { + static u32 get_next_id() { + struct service_event_id { + atomic_t next_id = 0; + }; + + return g_fxo->get().next_id++; + } + + atomic_t m_destroyed = false; + + friend class lv2_config; + +public: + const u32 id; + + // Note: Events hold a shared_ptr to their corresponding service - services + // only get freed once there are no more pending service events This has been + // confirmed to be the case in realhw + const shared_ptr handle; + const shared_ptr service; + const lv2_config_service_listener &listener; + + // Constructors (should not be used directly) + lv2_config_service_event( + shared_ptr _handle, + shared_ptr _service, + const lv2_config_service_listener &_listener) noexcept + : id(get_next_id()), handle(std::move(_handle)), + service(std::move(_service)), listener(_listener) {} + + // Factory + template + static shared_ptr create(Args &&...args) { + auto ev = + make_shared(std::forward(args)...); + + g_fxo->get().add_service_event(ev); + + return ev; + } + + // Destructor + lv2_config_service_event &operator=(thread_state s) noexcept; + ~lv2_config_service_event() noexcept; + + // Notify queue that this event exists + bool notify() const; + + // Write event to buffer + void write(sys_config_service_event_t *dst) const; + + // Check if the buffer can fit the current event, return false otherwise + bool check_buffer_size(usz size) const { return service->get_size() <= size; } +}; + +/* + * Syscalls + */ +/*516*/ error_code sys_config_open(u32 equeue_hdl, vm::ptr out_config_hdl); +/*517*/ error_code sys_config_close(u32 config_hdl); +/*518*/ error_code +sys_config_get_service_event(u32 config_hdl, u32 event_id, + vm::ptr dst, u64 size); +/*519*/ error_code sys_config_add_service_listener( + u32 config_hdl, sys_config_service_id service_id, u64 min_verbosity, + vm::ptr in, u64 size, sys_config_service_listener_type type, + vm::ptr out_listener_hdl); +/*520*/ error_code sys_config_remove_service_listener(u32 config_hdl, + u32 listener_hdl); +/*521*/ error_code sys_config_register_service(u32 config_hdl, + sys_config_service_id service_id, + u64 user_id, u64 verbosity, + vm::ptr data_buf, u64 size, + vm::ptr out_service_hdl); +/*522*/ error_code sys_config_unregister_service(u32 config_hdl, + u32 service_hdl); + +// Following syscalls have not been REd yet +/*523*/ error_code sys_config_get_io_event(u32 config_hdl, u32 event_id /*?*/, + vm::ptr out_buf /*?*/, + u64 size /*?*/); +/*524*/ error_code sys_config_register_io_error_listener(u32 config_hdl); +/*525*/ error_code sys_config_unregister_io_error_listener(u32 config_hdl); diff --git a/rpcs3/Emu/Cell/lv2/sys_console.h b/kernel/cellos/include/cellos/sys_console.h similarity index 83% rename from rpcs3/Emu/Cell/lv2/sys_console.h rename to kernel/cellos/include/cellos/sys_console.h index 2ba3189e8..17a111cb7 100644 --- a/rpcs3/Emu/Cell/lv2/sys_console.h +++ b/kernel/cellos/include/cellos/sys_console.h @@ -1,5 +1,6 @@ #pragma once +#include "Emu/Cell/ErrorCodes.h" #include "Emu/Memory/vm_ptr.h" // SysCalls diff --git a/rpcs3/Emu/Cell/lv2/sys_crypto_engine.h b/kernel/cellos/include/cellos/sys_crypto_engine.h similarity index 79% rename from rpcs3/Emu/Cell/lv2/sys_crypto_engine.h rename to kernel/cellos/include/cellos/sys_crypto_engine.h index a9be56ee2..2701f8e9e 100644 --- a/rpcs3/Emu/Cell/lv2/sys_crypto_engine.h +++ b/kernel/cellos/include/cellos/sys_crypto_engine.h @@ -1,10 +1,11 @@ #pragma once -#include "Emu/Memory/vm_ptr.h" #include "Emu/Cell/ErrorCodes.h" +#include "Emu/Memory/vm_ptr.h" // SysCalls error_code sys_crypto_engine_create(vm::ptr id); error_code sys_crypto_engine_destroy(u32 id); -error_code sys_crypto_engine_random_generate(vm::ptr buffer, u64 buffer_size); +error_code sys_crypto_engine_random_generate(vm::ptr buffer, + u64 buffer_size); diff --git a/rpcs3/Emu/Cell/lv2/sys_dbg.h b/kernel/cellos/include/cellos/sys_dbg.h similarity index 61% rename from rpcs3/Emu/Cell/lv2/sys_dbg.h rename to kernel/cellos/include/cellos/sys_dbg.h index 0f3a740ee..b0b264203 100644 --- a/rpcs3/Emu/Cell/lv2/sys_dbg.h +++ b/kernel/cellos/include/cellos/sys_dbg.h @@ -1,9 +1,11 @@ #pragma once -#include "Emu/Memory/vm_ptr.h" #include "Emu/Cell/ErrorCodes.h" +#include "Emu/Memory/vm_ptr.h" // Syscalls -error_code sys_dbg_read_process_memory(s32 pid, u32 address, u32 size, vm::ptr data); -error_code sys_dbg_write_process_memory(s32 pid, u32 address, u32 size, vm::cptr data); +error_code sys_dbg_read_process_memory(s32 pid, u32 address, u32 size, + vm::ptr data); +error_code sys_dbg_write_process_memory(s32 pid, u32 address, u32 size, + vm::cptr data); diff --git a/kernel/cellos/include/cellos/sys_event.h b/kernel/cellos/include/cellos/sys_event.h new file mode 100644 index 000000000..eb743fcdc --- /dev/null +++ b/kernel/cellos/include/cellos/sys_event.h @@ -0,0 +1,153 @@ +#pragma once + +#include "sys_sync.h" + +#include "Emu/Memory/vm_ptr.h" + +#include + +class cpu_thread; +class spu_thrread; + +// Event Queue Type +enum : u32 { + SYS_PPU_QUEUE = 1, + SYS_SPU_QUEUE = 2, +}; + +// Event Queue Destroy Mode +enum : s32 { + SYS_EVENT_QUEUE_DESTROY_FORCE = 1, +}; + +// Event Queue Ipc Key +enum : u64 { + SYS_EVENT_QUEUE_LOCAL = 0, +}; + +// Event Port Type +enum : s32 { + SYS_EVENT_PORT_LOCAL = 1, + SYS_EVENT_PORT_IPC = 3, // Unofficial name +}; + +// Event Port Name +enum : u64 { + SYS_EVENT_PORT_NO_NAME = 0, +}; + +// Event Source Type +enum : u32 { + SYS_SPU_THREAD_EVENT_USER = 1, + SYS_SPU_THREAD_EVENT_DMA = 2, // not supported +}; + +// Event Source Key +enum : u64 { + SYS_SPU_THREAD_EVENT_USER_KEY = 0xFFFFFFFF53505501ull, + SYS_SPU_THREAD_EVENT_DMA_KEY = 0xFFFFFFFF53505502ull, + SYS_SPU_THREAD_EVENT_EXCEPTION_KEY = 0xFFFFFFFF53505503ull, +}; + +struct sys_event_queue_attribute_t { + be_t protocol; // SYS_SYNC_PRIORITY or SYS_SYNC_FIFO + be_t type; // SYS_PPU_QUEUE or SYS_SPU_QUEUE + + union { + nse_t name_u64; + char name[sizeof(u64)]; + }; +}; + +struct sys_event_t { + be_t source; + be_t data1; + be_t data2; + be_t data3; +}; + +// Source, data1, data2, data3 +using lv2_event = std::tuple; + +struct lv2_event_port; + +struct lv2_event_queue final : public lv2_obj { + static const u32 id_base = 0x8d000000; + + const u32 id; + const lv2_protocol protocol; + const u8 type; + const u8 size; + const u64 name; + const u64 key; + + shared_mutex mutex; + std::deque events; + spu_thread *sq{}; + ppu_thread *pq{}; + + lv2_event_queue(u32 protocol, s32 type, s32 size, u64 name, + u64 ipc_key) noexcept; + + lv2_event_queue(utils::serial &ar) noexcept; + static std::function load(utils::serial &ar); + void save(utils::serial &ar); + static void save_ptr(utils::serial &, lv2_event_queue *); + static shared_ptr + load_ptr(utils::serial &ar, shared_ptr &queue, + std::string_view msg = {}); + + CellError send(lv2_event event, bool *notified_thread = nullptr, + lv2_event_port *port = nullptr); + + CellError send(u64 source, u64 d1, u64 d2, u64 d3, + bool *notified_thread = nullptr, + lv2_event_port *port = nullptr) { + return send(std::make_tuple(source, d1, d2, d3), notified_thread, port); + } + + // Get event queue by its global key + static shared_ptr find(u64 ipc_key); +}; + +struct lv2_event_port final : lv2_obj { + static const u32 id_base = 0x0e000000; + + const s32 type; // Port type, either IPC or local + const u64 name; // Event source (generated from id and process id if not set) + + atomic_t is_busy = 0; // Counts threads waiting on event sending + shared_ptr queue; // Event queue this port is connected to + + lv2_event_port(s32 type, u64 name) : type(type), name(name) {} + + lv2_event_port(utils::serial &ar); + void save(utils::serial &ar); +}; + +class ppu_thread; + +// Syscalls + +error_code sys_event_queue_create(cpu_thread &cpu, vm::ptr equeue_id, + vm::ptr attr, + u64 event_queue_key, s32 size); +error_code sys_event_queue_destroy(ppu_thread &ppu, u32 equeue_id, s32 mode); +error_code sys_event_queue_receive(ppu_thread &ppu, u32 equeue_id, + vm::ptr dummy_event, + u64 timeout); +error_code sys_event_queue_tryreceive(ppu_thread &ppu, u32 equeue_id, + vm::ptr event_array, + s32 size, vm::ptr number); +error_code sys_event_queue_drain(ppu_thread &ppu, u32 event_queue_id); + +error_code sys_event_port_create(cpu_thread &cpu, vm::ptr eport_id, + s32 port_type, u64 name); +error_code sys_event_port_destroy(ppu_thread &ppu, u32 eport_id); +error_code sys_event_port_connect_local(cpu_thread &cpu, u32 event_port_id, + u32 event_queue_id); +error_code sys_event_port_connect_ipc(ppu_thread &ppu, u32 eport_id, + u64 ipc_key); +error_code sys_event_port_disconnect(ppu_thread &ppu, u32 eport_id); +error_code sys_event_port_send(u32 event_port_id, u64 data1, u64 data2, + u64 data3); diff --git a/kernel/cellos/include/cellos/sys_event_flag.h b/kernel/cellos/include/cellos/sys_event_flag.h new file mode 100644 index 000000000..45efc19e3 --- /dev/null +++ b/kernel/cellos/include/cellos/sys_event_flag.h @@ -0,0 +1,118 @@ +#pragma once + +#include "sys_sync.h" + +#include "Emu/Memory/vm_ptr.h" + +enum { + SYS_SYNC_WAITER_SINGLE = 0x10000, + SYS_SYNC_WAITER_MULTIPLE = 0x20000, + + SYS_EVENT_FLAG_WAIT_AND = 0x01, + SYS_EVENT_FLAG_WAIT_OR = 0x02, + + SYS_EVENT_FLAG_WAIT_CLEAR = 0x10, + SYS_EVENT_FLAG_WAIT_CLEAR_ALL = 0x20, +}; + +struct sys_event_flag_attribute_t { + be_t protocol; + be_t pshared; + be_t ipc_key; + be_t flags; + be_t type; + + union { + nse_t name_u64; + char name[sizeof(u64)]; + }; +}; + +struct lv2_event_flag final : lv2_obj { + static const u32 id_base = 0x98000000; + + const lv2_protocol protocol; + const u64 key; + const s32 type; + const u64 name; + + shared_mutex mutex; + atomic_t pattern; + ppu_thread *sq{}; + + lv2_event_flag(u32 protocol, u64 key, s32 type, u64 name, + u64 pattern) noexcept + : protocol{static_cast(protocol)}, key(key), type(type), name(name), + pattern(pattern) {} + + lv2_event_flag(utils::serial &ar); + static std::function load(utils::serial &ar); + void save(utils::serial &ar); + + // Check mode arg + static bool check_mode(u32 mode) { + switch (mode & 0xf) { + case SYS_EVENT_FLAG_WAIT_AND: + break; + case SYS_EVENT_FLAG_WAIT_OR: + break; + default: + return false; + } + + switch (mode & ~0xf) { + case 0: + break; + case SYS_EVENT_FLAG_WAIT_CLEAR: + break; + case SYS_EVENT_FLAG_WAIT_CLEAR_ALL: + break; + default: + return false; + } + + return true; + } + + // Check and clear pattern (must be atomic op) + static bool check_pattern(u64 &pattern, u64 bitptn, u64 mode, u64 *result) { + // Write pattern + if (result) { + *result = pattern; + } + + // Check pattern + if (((mode & 0xf) == SYS_EVENT_FLAG_WAIT_AND && + (pattern & bitptn) != bitptn) || + ((mode & 0xf) == SYS_EVENT_FLAG_WAIT_OR && (pattern & bitptn) == 0)) { + return false; + } + + // Clear pattern if necessary + if ((mode & ~0xf) == SYS_EVENT_FLAG_WAIT_CLEAR) { + pattern &= ~bitptn; + } else if ((mode & ~0xf) == SYS_EVENT_FLAG_WAIT_CLEAR_ALL) { + pattern = 0; + } + + return true; + } +}; + +// Aux +class ppu_thread; + +// Syscalls + +error_code sys_event_flag_create(ppu_thread &ppu, vm::ptr id, + vm::ptr attr, + u64 init); +error_code sys_event_flag_destroy(ppu_thread &ppu, u32 id); +error_code sys_event_flag_wait(ppu_thread &ppu, u32 id, u64 bitptn, u32 mode, + vm::ptr result, u64 timeout); +error_code sys_event_flag_trywait(ppu_thread &ppu, u32 id, u64 bitptn, u32 mode, + vm::ptr result); +error_code sys_event_flag_set(cpu_thread &cpu, u32 id, u64 bitptn); +error_code sys_event_flag_clear(ppu_thread &ppu, u32 id, u64 bitptn); +error_code sys_event_flag_cancel(ppu_thread &ppu, u32 id, vm::ptr num); +error_code sys_event_flag_get(ppu_thread &ppu, u32 id, vm::ptr flags); diff --git a/kernel/cellos/include/cellos/sys_fs.h b/kernel/cellos/include/cellos/sys_fs.h new file mode 100644 index 000000000..890c9db3f --- /dev/null +++ b/kernel/cellos/include/cellos/sys_fs.h @@ -0,0 +1,665 @@ +#pragma once + +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/Memory/vm_ptr.h" +#include "util/File.h" +#include "util/StrUtil.h" +#include "util/mutex.h" + +#include + +// Open Flags +enum : s32 { + CELL_FS_O_RDONLY = 000000, + CELL_FS_O_WRONLY = 000001, + CELL_FS_O_RDWR = 000002, + CELL_FS_O_ACCMODE = 000003, + CELL_FS_O_CREAT = 000100, + CELL_FS_O_EXCL = 000200, + CELL_FS_O_TRUNC = 001000, + CELL_FS_O_APPEND = 002000, + CELL_FS_O_MSELF = 010000, + CELL_FS_O_UNK = + 01000000, // Tests have shown this is independent of other flags. Only + // known to be called in Rockband games. +}; + +// Seek Mode +enum : s32 { + CELL_FS_SEEK_SET, + CELL_FS_SEEK_CUR, + CELL_FS_SEEK_END, +}; + +enum : s32 { + CELL_FS_MAX_FS_PATH_LENGTH = 1024, + CELL_FS_MAX_FS_FILE_NAME_LENGTH = 255, + CELL_FS_MAX_MP_LENGTH = 31, +}; + +enum : s32 { + CELL_FS_S_IFMT = 0170000, + CELL_FS_S_IFDIR = 0040000, // directory + CELL_FS_S_IFREG = 0100000, // regular + CELL_FS_S_IFLNK = 0120000, // symbolic link + CELL_FS_S_IFWHT = 0160000, // unknown + + CELL_FS_S_IRUSR = 0000400, // R for owner + CELL_FS_S_IWUSR = 0000200, // W for owner + CELL_FS_S_IXUSR = 0000100, // X for owner + + CELL_FS_S_IRGRP = 0000040, // R for group + CELL_FS_S_IWGRP = 0000020, // W for group + CELL_FS_S_IXGRP = 0000010, // X for group + + CELL_FS_S_IROTH = 0000004, // R for other + CELL_FS_S_IWOTH = 0000002, // W for other + CELL_FS_S_IXOTH = 0000001, // X for other +}; + +// CellFsDirent.d_type +enum : u8 { + CELL_FS_TYPE_UNKNOWN = 0, + CELL_FS_TYPE_DIRECTORY = 1, + CELL_FS_TYPE_REGULAR = 2, + CELL_FS_TYPE_SYMLINK = 3, +}; + +enum : u32 { + CELL_FS_IO_BUFFER_PAGE_SIZE_64KB = 0x0002, + CELL_FS_IO_BUFFER_PAGE_SIZE_1MB = 0x0004, +}; + +struct CellFsDirent { + u8 d_type; + u8 d_namlen; + char d_name[256]; +}; + +struct CellFsStat { + be_t mode; + be_t uid; + be_t gid; + be_t atime; + be_t mtime; + be_t ctime; + be_t size; + be_t blksize; +}; + +CHECK_SIZE_ALIGN(CellFsStat, 52, 4); + +struct CellFsDirectoryEntry { + CellFsStat attribute; + CellFsDirent entry_name; +}; + +struct CellFsUtimbuf { + be_t actime; + be_t modtime; +}; + +CHECK_SIZE_ALIGN(CellFsUtimbuf, 16, 4); + +// MSelf file structs +struct FsMselfHeader { + be_t m_magic; + be_t m_format_version; + be_t m_file_size; + be_t m_entry_num; + be_t m_entry_size; + u8 m_reserve[40]; +}; + +struct FsMselfEntry { + char m_name[32]; + be_t m_offset; + be_t m_size; + u8 m_reserve[16]; +}; + +enum class lv2_mp_flag { + read_only, + no_uid_gid, + strict_get_block_size, + cache, + + __bitset_enum_max +}; + +enum class lv2_file_type { + regular = 0, + sdata, + edata, +}; + +struct lv2_fs_mount_point { + const std::string_view root; + const std::string_view file_system; + const std::string_view device; + const u32 sector_size = 512; + const u64 sector_count = 256; + const u32 block_size = 4096; + const bs_t flags{}; + lv2_fs_mount_point *const next = nullptr; + + mutable shared_mutex mutex; +}; + +extern lv2_fs_mount_point g_mp_sys_dev_hdd0; +extern lv2_fs_mount_point g_mp_sys_no_device; + +struct lv2_fs_mount_info { + lv2_fs_mount_point *const mp; + const std::string device; + const std::string file_system; + const bool read_only; + + lv2_fs_mount_info(lv2_fs_mount_point *mp = nullptr, + std::string_view device = {}, + std::string_view file_system = {}, bool read_only = false) + : mp(mp ? mp : &g_mp_sys_no_device), + device(device.empty() ? this->mp->device : device), + file_system(file_system.empty() ? this->mp->file_system : file_system), + read_only( + (this->mp->flags & lv2_mp_flag::read_only) || + read_only) // respect the original flags of the mount point as well + {} + + constexpr bool operator==(const lv2_fs_mount_info &rhs) const noexcept { + return this == &rhs; + } + constexpr bool + operator==(const lv2_fs_mount_point *const &rhs) const noexcept { + return mp == rhs; + } + constexpr lv2_fs_mount_point *operator->() const noexcept { return mp; } +}; + +extern lv2_fs_mount_info g_mi_sys_not_found; + +struct CellFsMountInfo; // Forward Declaration + +struct lv2_fs_mount_info_map { +public: + SAVESTATE_INIT_POS(40); + + lv2_fs_mount_info_map(); + lv2_fs_mount_info_map(const lv2_fs_mount_info_map &) = delete; + lv2_fs_mount_info_map &operator=(const lv2_fs_mount_info_map &) = delete; + ~lv2_fs_mount_info_map(); + + // Forwarding arguments to map.try_emplace(): refer to the constructor of + // lv2_fs_mount_info + template bool add(Args &&...args) { + return map.try_emplace(std::forward(args)...).second; + } + bool remove(std::string_view path); + const lv2_fs_mount_info &lookup(std::string_view path, + bool no_cell_fs_path = false, + std::string *mount_path = nullptr) const; + u64 get_all(CellFsMountInfo *info = nullptr, u64 len = 0) const; + bool is_device_mounted(std::string_view device_name) const; + + static bool vfs_unmount(std::string_view vpath, bool remove_from_map = true); + +private: + std::unordered_map> + map; +}; + +struct lv2_fs_object { + static constexpr u32 id_base = 3; + static constexpr u32 id_step = 1; + static constexpr u32 id_count = 255 - id_base; + static constexpr bool id_lowest = true; + SAVESTATE_INIT_POS(49); + + // File Name (max 1055) + const std::array name; + + // Mount Info + const lv2_fs_mount_info ∓ + +protected: + lv2_fs_object(std::string_view filename); + lv2_fs_object(utils::serial &ar, bool dummy); + +public: + lv2_fs_object(const lv2_fs_object &) = delete; + + lv2_fs_object &operator=(const lv2_fs_object &) = delete; + + // Normalize a virtual path + static std::string get_normalized_path(std::string_view path); + + // Get the device's root path (e.g. "/dev_hdd0") from a given path + static std::string get_device_root(std::string_view filename); + + // Filename can be either a path starting with '/' or a CELL_FS device name + // This should be used only when handling devices that are not mounted + // Otherwise, use g_fxo->get().lookup() to look up + // mounted devices accurately + static lv2_fs_mount_point *get_mp(std::string_view filename, + std::string *vfs_path = nullptr); + + static std::array get_name(std::string_view filename) { + std::array name; + + if (filename.size() >= 0x420) { + filename = filename.substr(0, 0x420 - 1); + } + + filename.copy(name.data(), filename.size()); + name[filename.size()] = 0; + return name; + } + + void save(utils::serial &) {} +}; + +struct lv2_file final : lv2_fs_object { + static constexpr u32 id_type = 1; + + fs::file file; + const s32 mode; + const s32 flags; + std::string real_path; + const lv2_file_type type; + + // IO Container + u32 ct_id{}, ct_used{}; + + // Stream lock + atomic_t lock{0}; + + // Some variables for convenience of data restoration + struct save_restore_t { + u64 seek_pos; + u64 atime; + u64 mtime; + } restore_data{}; + + lv2_file(std::string_view filename, fs::file &&file, s32 mode, s32 flags, + const std::string &real_path, lv2_file_type type = {}) + : lv2_fs_object(filename), file(std::move(file)), mode(mode), + flags(flags), real_path(real_path), type(type) {} + + lv2_file(const lv2_file &host, fs::file &&file, s32 mode, s32 flags, + const std::string &real_path, lv2_file_type type = {}) + : lv2_fs_object(host.name.data()), file(std::move(file)), mode(mode), + flags(flags), real_path(real_path), type(type) {} + + lv2_file(utils::serial &ar); + void save(utils::serial &ar); + + struct open_raw_result_t { + CellError error; + fs::file file; + }; + + struct open_result_t { + CellError error; + std::string ppath; + std::string real_path; + fs::file file; + lv2_file_type type; + }; + + // Open a file with wrapped logic of sys_fs_open + static open_raw_result_t + open_raw(const std::string &path, s32 flags, s32 mode, + lv2_file_type type = lv2_file_type::regular, + const lv2_fs_mount_info &mp = g_mi_sys_not_found); + static open_result_t open(std::string_view vpath, s32 flags, s32 mode, + const void *arg = {}, u64 size = 0); + + // File reading with intermediate buffer + static u64 op_read(const fs::file &file, vm::ptr buf, u64 size, + u64 opt_pos = umax); + + u64 op_read(vm::ptr buf, u64 size, u64 opt_pos = umax) const { + return op_read(file, buf, size, opt_pos); + } + + // File writing with intermediate buffer + static u64 op_write(const fs::file &file, vm::cptr buf, u64 size); + + u64 op_write(vm::cptr buf, u64 size) const { + return op_write(file, buf, size); + } + + // For MSELF support + struct file_view; + + // Make file view from lv2_file object (for MSELF support) + static fs::file make_view(const shared_ptr &_file, u64 offset); +}; + +struct lv2_dir final : lv2_fs_object { + static constexpr u32 id_type = 2; + + const std::vector entries; + + // Current reading position + atomic_t pos{0}; + + lv2_dir(std::string_view filename, std::vector &&entries) + : lv2_fs_object(filename), entries(std::move(entries)) {} + + lv2_dir(utils::serial &ar); + void save(utils::serial &ar); + + // Read next + const fs::dir_entry *dir_read() { + const u64 old_pos = pos; + + if (const u64 cur = (old_pos < entries.size() ? pos++ : old_pos); + cur < entries.size()) { + return &entries[cur]; + } + + return nullptr; + } +}; + +// sys_fs_fcntl arg base class (left empty for PODness) +struct lv2_file_op {}; + +namespace vtable { +struct lv2_file_op { + // Speculation + vm::bptrb(vm::ptrb)> get_data; + vm::bptrb)> get_size; + vm::bptrb)> _dtor1; + vm::bptrb)> _dtor2; +}; +} // namespace vtable + +// sys_fs_fcntl: read with offset, write with offset +struct lv2_file_op_rw : lv2_file_op { + vm::bptrb _vtable; + + be_t op; + be_t _x8; // ??? + be_t _xc; // ??? + + be_t fd; // File descriptor (3..255) + vm::bptrb buf; // Buffer for data + be_t offset; // File offset + be_t size; // Access size + + be_t out_code; // Op result + be_t out_size; // Size processed +}; + +CHECK_SIZE(lv2_file_op_rw, 0x38); + +// sys_fs_fcntl: cellFsSdataOpenByFd +struct lv2_file_op_09 : lv2_file_op { + vm::bptrb _vtable; + + be_t op; + be_t _x8; + be_t _xc; + + be_t fd; + be_t offset; + be_t _vtabl2; + be_t arg1; // 0x180 + be_t arg2; // 0x10 + be_t arg_size; // 6th arg + be_t arg_ptr; // 5th arg + + be_t _x34; + be_t out_code; + be_t out_fd; +}; + +CHECK_SIZE(lv2_file_op_09, 0x40); + +struct lv2_file_e0000025 : lv2_file_op { + be_t size; // 0x30 + be_t _x4; // 0x10 + be_t _x8; // 0x28 - offset of out_code + be_t name_size; + vm::bcptr name; + be_t _x14; + be_t _x18; // 0 + be_t _x1c; // 0 + be_t _x20; // 16 + be_t _x24; // unk, seems to be memory location + be_t out_code; // out_code + be_t fd; // 0xffffffff - likely fd out +}; + +CHECK_SIZE(lv2_file_e0000025, 0x30); + +// sys_fs_fnctl: cellFsGetDirectoryEntries +struct lv2_file_op_dir : lv2_file_op { + struct dir_info : lv2_file_op { + be_t _code; // Op result + be_t _size; // Number of entries written + vm::bptrb ptr; + be_t max; + }; + + CHECK_SIZE(dir_info, 0x10); + + vm::bptrb _vtable; + + be_t op; + be_t _x8; + dir_info arg; +}; + +CHECK_SIZE(lv2_file_op_dir, 0x1c); + +// sys_fs_fcntl: cellFsGetFreeSize (for dev_hdd0) +struct lv2_file_c0000002 : lv2_file_op { + vm::bptrb _vtable; + + be_t op; + be_t _x8; + vm::bcptr path; + be_t _x10; // 0 + be_t _x14; + + be_t out_code; // CELL_ENOSYS + be_t out_block_size; + be_t out_block_count; +}; + +CHECK_SIZE(lv2_file_c0000002, 0x28); + +// sys_fs_fcntl: unknown (called before cellFsOpen, for example) +struct lv2_file_c0000006 : lv2_file_op { + be_t size; // 0x20 + be_t _x4; // 0x10 + be_t _x8; // 0x18 - offset of out_code + be_t name_size; + vm::bcptr name; + be_t _x14; // 0 + be_t out_code; // 0x80010003 + be_t out_id; // set to 0, may return 0x1b5 +}; + +CHECK_SIZE(lv2_file_c0000006, 0x20); + +// sys_fs_fcntl: cellFsArcadeHddSerialNumber +struct lv2_file_c0000007 : lv2_file_op { + be_t out_code; // set to 0 + vm::bcptr device; // CELL_FS_IOS:ATA_HDD + be_t device_size; // 0x14 + vm::bptr model; + be_t model_size; // 0x29 + vm::bptr serial; + be_t serial_size; // 0x15 +}; + +CHECK_SIZE(lv2_file_c0000007, 0x1c); + +struct lv2_file_c0000008 : lv2_file_op { + u8 _x0[4]; + be_t op; // 0xC0000008 + u8 _x8[8]; + be_t container_id; + be_t size; + be_t page_type; // 0x4000 for cellFsSetDefaultContainer + // 0x4000 | page_type given by user, valid values seem to + // be: CELL_FS_IO_BUFFER_PAGE_SIZE_64KB 0x0002 + // CELL_FS_IO_BUFFER_PAGE_SIZE_1MB 0x0004 + be_t out_code; + u8 _x24[4]; +}; + +CHECK_SIZE(lv2_file_c0000008, 0x28); + +struct lv2_file_c0000015 : lv2_file_op { + be_t size; // 0x20 + be_t _x4; // 0x10 + be_t _x8; // 0x18 - offset of out_code + be_t path_size; + vm::bcptr path; + be_t _x14; // + be_t vendorID; + be_t productID; + be_t out_code; // set to 0 +}; + +CHECK_SIZE(lv2_file_c0000015, 0x20); + +struct lv2_file_c000001a : lv2_file_op { + be_t + disc_retry_type; // CELL_FS_DISC_READ_RETRY_NONE results in a 0 here + // CELL_FS_DISC_READ_RETRY_DEFAULT results in a 0x63 here + be_t _x4; // 0 + be_t _x8; // 0x000186A0 + be_t _xC; // 0 + be_t _x10; // 0 + be_t _x14; // 0 +}; + +CHECK_SIZE(lv2_file_c000001a, 0x18); + +struct lv2_file_c000001c : lv2_file_op { + be_t size; // 0x60 + be_t _x4; // 0x10 + be_t _x8; // 0x18 - offset of out_code + be_t path_size; + vm::bcptr path; + be_t unk1; + be_t vendorID; + be_t productID; + be_t out_code; // set to 0 + be_t serial[32]; +}; + +CHECK_SIZE(lv2_file_c000001c, 0x60); + +// sys_fs_fcntl: cellFsAllocateFileAreaWithoutZeroFill +struct lv2_file_e0000017 : lv2_file_op { + be_t size; // 0x28 + be_t _x4; // 0x10, offset + be_t _x8; // 0x20, offset + be_t _xc; // - + vm::bcptr file_path; + be_t file_size; + be_t out_code; +}; + +CHECK_SIZE(lv2_file_e0000017, 0x28); + +struct CellFsMountInfo { + char mount_path[0x20]; // 0x0 + char filesystem[0x20]; // 0x20 + char dev_name[0x40]; // 0x40 + be_t unk[5]; // 0x80, probably attributes +}; + +CHECK_SIZE(CellFsMountInfo, 0x94); + +// Default IO container +struct default_sys_fs_container { + shared_mutex mutex; + u32 id = 0; + u32 cap = 0; + u32 used = 0; +}; + +// Syscalls + +error_code sys_fs_test(ppu_thread &ppu, u32 arg1, u32 arg2, vm::ptr arg3, + u32 arg4, vm::ptr buf, u32 buf_size); +error_code sys_fs_open(ppu_thread &ppu, vm::cptr path, s32 flags, + vm::ptr fd, s32 mode, vm::cptr arg, u64 size); +error_code sys_fs_read(ppu_thread &ppu, u32 fd, vm::ptr buf, u64 nbytes, + vm::ptr nread); +error_code sys_fs_write(ppu_thread &ppu, u32 fd, vm::cptr buf, u64 nbytes, + vm::ptr nwrite); +error_code sys_fs_close(ppu_thread &ppu, u32 fd); +error_code sys_fs_opendir(ppu_thread &ppu, vm::cptr path, + vm::ptr fd); +error_code sys_fs_readdir(ppu_thread &ppu, u32 fd, vm::ptr dir, + vm::ptr nread); +error_code sys_fs_closedir(ppu_thread &ppu, u32 fd); +error_code sys_fs_stat(ppu_thread &ppu, vm::cptr path, + vm::ptr sb); +error_code sys_fs_fstat(ppu_thread &ppu, u32 fd, vm::ptr sb); +error_code sys_fs_link(ppu_thread &ppu, vm::cptr from, vm::cptr to); +error_code sys_fs_mkdir(ppu_thread &ppu, vm::cptr path, s32 mode); +error_code sys_fs_rename(ppu_thread &ppu, vm::cptr from, + vm::cptr to); +error_code sys_fs_rmdir(ppu_thread &ppu, vm::cptr path); +error_code sys_fs_unlink(ppu_thread &ppu, vm::cptr path); +error_code sys_fs_access(ppu_thread &ppu, vm::cptr path, s32 mode); +error_code sys_fs_fcntl(ppu_thread &ppu, u32 fd, u32 op, vm::ptr arg, + u32 size); +error_code sys_fs_lseek(ppu_thread &ppu, u32 fd, s64 offset, s32 whence, + vm::ptr pos); +error_code sys_fs_fdatasync(ppu_thread &ppu, u32 fd); +error_code sys_fs_fsync(ppu_thread &ppu, u32 fd); +error_code sys_fs_fget_block_size(ppu_thread &ppu, u32 fd, + vm::ptr sector_size, + vm::ptr block_size, vm::ptr arg4, + vm::ptr out_flags); +error_code sys_fs_get_block_size(ppu_thread &ppu, vm::cptr path, + vm::ptr sector_size, + vm::ptr block_size, vm::ptr arg4); +error_code sys_fs_truncate(ppu_thread &ppu, vm::cptr path, u64 size); +error_code sys_fs_ftruncate(ppu_thread &ppu, u32 fd, u64 size); +error_code sys_fs_symbolic_link(ppu_thread &ppu, vm::cptr target, + vm::cptr linkpath); +error_code sys_fs_chmod(ppu_thread &ppu, vm::cptr path, s32 mode); +error_code sys_fs_chown(ppu_thread &ppu, vm::cptr path, s32 uid, s32 gid); +error_code sys_fs_disk_free(ppu_thread &ppu, vm::cptr path, + vm::ptr total_free, vm::ptr avail_free); +error_code sys_fs_utime(ppu_thread &ppu, vm::cptr path, + vm::cptr timep); +error_code sys_fs_acl_read(ppu_thread &ppu, vm::cptr path, vm::ptr); +error_code sys_fs_acl_write(ppu_thread &ppu, vm::cptr path, + vm::ptr); +error_code sys_fs_lsn_get_cda_size(ppu_thread &ppu, u32 fd, vm::ptr ptr); +error_code sys_fs_lsn_get_cda(ppu_thread &ppu, u32 fd, vm::ptr, u64, + vm::ptr); +error_code sys_fs_lsn_lock(ppu_thread &ppu, u32 fd); +error_code sys_fs_lsn_unlock(ppu_thread &ppu, u32 fd); +error_code sys_fs_lsn_read(ppu_thread &ppu, u32 fd, vm::cptr, u64); +error_code sys_fs_lsn_write(ppu_thread &ppu, u32 fd, vm::cptr, u64); +error_code sys_fs_mapped_allocate(ppu_thread &ppu, u32 fd, u64, + vm::pptr out_ptr); +error_code sys_fs_mapped_free(ppu_thread &ppu, u32 fd, vm::ptr ptr); +error_code sys_fs_truncate2(ppu_thread &ppu, u32 fd, u64 size); +error_code sys_fs_newfs(ppu_thread &ppu, vm::cptr dev_name, + vm::cptr file_system, s32 unk1, + vm::cptr str1); +error_code sys_fs_mount(ppu_thread &ppu, vm::cptr dev_name, + vm::cptr file_system, vm::cptr path, + s32 unk1, s32 prot, s32 unk2, vm::cptr str1, + u32 str_len); +error_code sys_fs_unmount(ppu_thread &ppu, vm::cptr path, s32 unk1, + s32 force); +error_code sys_fs_get_mount_info_size(ppu_thread &ppu, vm::ptr len); +error_code sys_fs_get_mount_info(ppu_thread &ppu, vm::ptr info, + u64 len, vm::ptr out_len); diff --git a/rpcs3/Emu/Cell/lv2/sys_game.h b/kernel/cellos/include/cellos/sys_game.h similarity index 65% rename from rpcs3/Emu/Cell/lv2/sys_game.h rename to kernel/cellos/include/cellos/sys_game.h index a3c023790..4529dc60e 100644 --- a/rpcs3/Emu/Cell/lv2/sys_game.h +++ b/kernel/cellos/include/cellos/sys_game.h @@ -1,5 +1,9 @@ #pragma once +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/Memory/vm_ptr.h" +#include "rx/types.hpp" + void abort_lv2_watchdog(); error_code _sys_game_watchdog_start(u32 timeout); @@ -8,5 +12,6 @@ error_code _sys_game_watchdog_clear(); error_code _sys_game_set_system_sw_version(u64 version); u64 _sys_game_get_system_sw_version(); error_code _sys_game_board_storage_read(vm::ptr buffer, vm::ptr status); -error_code _sys_game_board_storage_write(vm::ptr buffer, vm::ptr status); +error_code _sys_game_board_storage_write(vm::ptr buffer, + vm::ptr status); error_code _sys_game_get_rtc_status(vm::ptr status); diff --git a/rpcs3/Emu/Cell/lv2/sys_gamepad.h b/kernel/cellos/include/cellos/sys_gamepad.h similarity index 100% rename from rpcs3/Emu/Cell/lv2/sys_gamepad.h rename to kernel/cellos/include/cellos/sys_gamepad.h diff --git a/rpcs3/Emu/Cell/lv2/sys_gpio.h b/kernel/cellos/include/cellos/sys_gpio.h similarity index 62% rename from rpcs3/Emu/Cell/lv2/sys_gpio.h rename to kernel/cellos/include/cellos/sys_gpio.h index e2cd1ff23..f9e08d248 100644 --- a/rpcs3/Emu/Cell/lv2/sys_gpio.h +++ b/kernel/cellos/include/cellos/sys_gpio.h @@ -1,13 +1,12 @@ #pragma once -#include "Emu/Memory/vm_ptr.h" #include "Emu/Cell/ErrorCodes.h" +#include "Emu/Memory/vm_ptr.h" -enum : u64 -{ - SYS_GPIO_UNKNOWN_DEVICE_ID = 0x0, - SYS_GPIO_LED_DEVICE_ID = 0x1, - SYS_GPIO_DIP_SWITCH_DEVICE_ID = 0x2, +enum : u64 { + SYS_GPIO_UNKNOWN_DEVICE_ID = 0x0, + SYS_GPIO_LED_DEVICE_ID = 0x1, + SYS_GPIO_DIP_SWITCH_DEVICE_ID = 0x2, }; error_code sys_gpio_get(u64 device_id, vm::ptr value); diff --git a/kernel/cellos/include/cellos/sys_hid.h b/kernel/cellos/include/cellos/sys_hid.h new file mode 100644 index 000000000..9906b8af5 --- /dev/null +++ b/kernel/cellos/include/cellos/sys_hid.h @@ -0,0 +1,44 @@ +#pragma once + +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/Memory/vm_ptr.h" + +// set sensor mode? also getinfo? +struct sys_hid_info_5 { + le_t vid; + le_t pid; + u8 status; + // todo: more in this, not sure what tho +}; + +struct sys_hid_info_2 { + be_t vid; + be_t pid; + u8 unk[17]; +}; + +struct sys_hid_ioctl_68 { + u8 unk; + u8 unk2; +}; + +// unk +struct sys_hid_manager_514_pkg_d { + be_t unk1; + u8 unk2; +}; + +// SysCalls + +error_code sys_hid_manager_open(ppu_thread &ppu, u64 device_type, u64 port_no, + vm::ptr handle); +error_code sys_hid_manager_ioctl(u32 hid_handle, u32 pkg_id, vm::ptr buf, + u64 buf_size); +error_code sys_hid_manager_add_hot_key_observer(u32 event_queue, + vm::ptr unk); +error_code sys_hid_manager_check_focus(); +error_code sys_hid_manager_is_process_permission_root(u32 pid); +error_code sys_hid_manager_513(u64 a1, u64 a2, vm::ptr buf, u64 buf_size); +error_code sys_hid_manager_514(u32 pkg_id, vm::ptr buf, u64 buf_size); +error_code sys_hid_manager_read(u32 handle, u32 pkg_id, vm::ptr buf, + u64 buf_size); diff --git a/kernel/cellos/include/cellos/sys_interrupt.h b/kernel/cellos/include/cellos/sys_interrupt.h new file mode 100644 index 000000000..108ffc6a9 --- /dev/null +++ b/kernel/cellos/include/cellos/sys_interrupt.h @@ -0,0 +1,46 @@ +#pragma once + +#include "Emu/Cell/ErrorCodes.h" +#include "sys_sync.h" + +#include "Emu/Memory/vm_ptr.h" + +class ppu_thread; + +struct lv2_int_tag final : public lv2_obj { + static const u32 id_base = 0x0a000000; + + const u32 id; + shared_ptr handler; + + lv2_int_tag() noexcept; + lv2_int_tag(utils::serial &ar) noexcept; + void save(utils::serial &ar); +}; + +struct lv2_int_serv final : public lv2_obj { + static const u32 id_base = 0x0b000000; + + const u32 id; + const shared_ptr> thread; + const u64 arg1; + const u64 arg2; + + lv2_int_serv(shared_ptr> thread, u64 arg1, + u64 arg2) noexcept; + lv2_int_serv(utils::serial &ar) noexcept; + void save(utils::serial &ar); + + void exec() const; + void join() const; +}; + +// Syscalls + +error_code sys_interrupt_tag_destroy(ppu_thread &ppu, u32 intrtag); +error_code _sys_interrupt_thread_establish(ppu_thread &ppu, vm::ptr ih, + u32 intrtag, u32 intrthread, + u64 arg1, u64 arg2); +error_code _sys_interrupt_thread_disestablish(ppu_thread &ppu, u32 ih, + vm::ptr r13); +void sys_interrupt_thread_eoi(ppu_thread &ppu); diff --git a/kernel/cellos/include/cellos/sys_io.h b/kernel/cellos/include/cellos/sys_io.h new file mode 100644 index 000000000..993c121cf --- /dev/null +++ b/kernel/cellos/include/cellos/sys_io.h @@ -0,0 +1,28 @@ +#pragma once + +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/Memory/vm_ptr.h" + +struct lv2_io_buf { + static const u32 id_base = 0x44000000; + static const u32 id_step = 1; + static const u32 id_count = 2048; + SAVESTATE_INIT_POS(41); + + const u32 block_count; + const u32 block_size; + const u32 blocks; + const u32 unk1; + + lv2_io_buf(u32 block_count, u32 block_size, u32 blocks, u32 unk1) + : block_count(block_count), block_size(block_size), blocks(blocks), + unk1(unk1) {} +}; + +// SysCalls + +error_code sys_io_buffer_create(u32 block_count, u32 block_size, u32 blocks, + u32 unk1, vm::ptr handle); +error_code sys_io_buffer_destroy(u32 handle); +error_code sys_io_buffer_allocate(u32 handle, vm::ptr block); +error_code sys_io_buffer_free(u32 handle, u32 block); diff --git a/kernel/cellos/include/cellos/sys_lwcond.h b/kernel/cellos/include/cellos/sys_lwcond.h new file mode 100644 index 000000000..83af45708 --- /dev/null +++ b/kernel/cellos/include/cellos/sys_lwcond.h @@ -0,0 +1,57 @@ +#pragma once + +#include "sys_sync.h" + +#include "Emu/Memory/vm_ptr.h" + +struct sys_lwmutex_t; + +struct sys_lwcond_attribute_t { + union { + nse_t name_u64; + char name[sizeof(u64)]; + }; +}; + +struct sys_lwcond_t { + vm::bptr lwmutex; + be_t lwcond_queue; // lwcond pseudo-id +}; + +struct lv2_lwcond final : lv2_obj { + static const u32 id_base = 0x97000000; + + const be_t name; + const u32 lwid; + const lv2_protocol protocol; + vm::ptr control; + + shared_mutex mutex; + ppu_thread *sq{}; + + atomic_t lwmutex_waiters = 0; + + lv2_lwcond(u64 name, u32 lwid, u32 protocol, + vm::ptr control) noexcept + : name(std::bit_cast>(name)), lwid(lwid), + protocol{static_cast(protocol)}, control(control) {} + + lv2_lwcond(utils::serial &ar); + void save(utils::serial &ar); +}; + +// Aux +class ppu_thread; + +// Syscalls + +error_code _sys_lwcond_create(ppu_thread &ppu, vm::ptr lwcond_id, + u32 lwmutex_id, vm::ptr control, + u64 name); +error_code _sys_lwcond_destroy(ppu_thread &ppu, u32 lwcond_id); +error_code _sys_lwcond_signal(ppu_thread &ppu, u32 lwcond_id, u32 lwmutex_id, + u64 ppu_thread_id, u32 mode); +error_code _sys_lwcond_signal_all(ppu_thread &ppu, u32 lwcond_id, + u32 lwmutex_id, u32 mode); +error_code _sys_lwcond_queue_wait(ppu_thread &ppu, u32 lwcond_id, + u32 lwmutex_id, u64 timeout); diff --git a/kernel/cellos/include/cellos/sys_lwmutex.h b/kernel/cellos/include/cellos/sys_lwmutex.h new file mode 100644 index 000000000..6f912baab --- /dev/null +++ b/kernel/cellos/include/cellos/sys_lwmutex.h @@ -0,0 +1,180 @@ +#pragma once + +#include "sys_sync.h" + +#include "Emu/Memory/vm_ptr.h" + +struct sys_lwmutex_attribute_t { + be_t protocol; + be_t recursive; + + union { + nse_t name_u64; + char name[sizeof(u64)]; + }; +}; + +enum : u32 { + lwmutex_free = 0xffffffffu, + lwmutex_dead = 0xfffffffeu, + lwmutex_reserved = 0xfffffffdu, +}; + +struct sys_lwmutex_t { + struct alignas(8) sync_var_t { + be_t owner; + be_t waiter; + }; + + union { + atomic_t lock_var; + + struct { + atomic_be_t owner; + atomic_be_t waiter; + } vars; + + atomic_be_t all_info; + }; + + be_t attribute; + be_t recursive_count; + be_t sleep_queue; // lwmutex pseudo-id + be_t pad; +}; + +struct lv2_lwmutex final : lv2_obj { + static const u32 id_base = 0x95000000; + + const lv2_protocol protocol; + const vm::ptr control; + const be_t name; + + shared_mutex mutex; + atomic_t lwcond_waiters{0}; + + struct alignas(16) control_data_t { + s32 signaled{0}; + u32 reserved{}; + ppu_thread *sq{}; + }; + + atomic_t lv2_control{}; + + lv2_lwmutex(u32 protocol, vm::ptr control, u64 name) noexcept + : protocol{static_cast(protocol)}, control(control), + name(std::bit_cast>(name)) {} + + lv2_lwmutex(utils::serial &ar); + void save(utils::serial &ar); + + ppu_thread *load_sq() const { + return atomic_storage::load(lv2_control.raw().sq); + } + + template s32 try_own(T *cpu, bool wait_only = false) { + const s32 signal = + lv2_control + .fetch_op([&](control_data_t &data) { + if (!data.signaled) { + cpu->prio.atomic_op( + [tag = ++g_priority_order_tag]( + std::common_type_t &prio) { + prio.order = tag; + }); + + cpu->next_cpu = data.sq; + data.sq = cpu; + } else { + ensure(!wait_only); + data.signaled = 0; + } + }) + .signaled; + + if (signal) { + cpu->next_cpu = nullptr; + } else { + const bool notify = lwcond_waiters + .fetch_op([](s32 &val) { + if (val + 0u <= 1u << 31) { + // Value was either positive or INT32_MIN + return false; + } + + // lwmutex was set to be destroyed, but there + // are lwcond waiters + // Turn off the "lwcond_waiters notification" + // bit as we are adding an lwmutex waiter + val &= 0x7fff'ffff; + return true; + }) + .second; + + if (notify) { + // Notify lwmutex destroyer (may cause EBUSY to be returned for it) + lwcond_waiters.notify_all(); + } + } + + return signal; + } + + bool try_unlock(bool unlock2) { + if (!load_sq()) { + control_data_t old{}; + old.signaled = atomic_storage::load(lv2_control.raw().signaled); + control_data_t store = old; + store.signaled |= (unlock2 ? s32{smin} : 1); + + if (lv2_control.compare_exchange(old, store)) { + return true; + } + } + + return false; + } + + template T *reown(bool unlock2 = false) { + T *res = nullptr; + + lv2_control.fetch_op([&](control_data_t &data) { + res = nullptr; + + if (auto sq = static_cast(data.sq)) { + res = schedule(data.sq, protocol, false); + + if (sq == data.sq) { + return false; + } + + return true; + } else { + data.signaled |= (unlock2 ? s32{smin} : 1); + return true; + } + }); + + if (res && cpu_flag::again - res->state) { + // Detach manually (fetch_op can fail, so avoid side-effects on the first + // node in this case) + res->next_cpu = nullptr; + } + + return res; + } +}; + +// Aux +class ppu_thread; + +// Syscalls + +error_code _sys_lwmutex_create(ppu_thread &ppu, vm::ptr lwmutex_id, + u32 protocol, vm::ptr control, + s32 has_name, u64 name); +error_code _sys_lwmutex_destroy(ppu_thread &ppu, u32 lwmutex_id); +error_code _sys_lwmutex_lock(ppu_thread &ppu, u32 lwmutex_id, u64 timeout); +error_code _sys_lwmutex_trylock(ppu_thread &ppu, u32 lwmutex_id); +error_code _sys_lwmutex_unlock(ppu_thread &ppu, u32 lwmutex_id); +error_code _sys_lwmutex_unlock2(ppu_thread &ppu, u32 lwmutex_id); diff --git a/kernel/cellos/include/cellos/sys_memory.h b/kernel/cellos/include/cellos/sys_memory.h new file mode 100644 index 000000000..ff273e913 --- /dev/null +++ b/kernel/cellos/include/cellos/sys_memory.h @@ -0,0 +1,135 @@ +#pragma once + +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/Memory/vm_ptr.h" + +class cpu_thread; + +enum lv2_mem_container_id : u32 { + SYS_MEMORY_CONTAINER_ID_INVALID = 0xFFFFFFFF, +}; + +enum : u64 { + SYS_MEMORY_ACCESS_RIGHT_NONE = 0x00000000000000F0ULL, + SYS_MEMORY_ACCESS_RIGHT_ANY = 0x000000000000000FULL, + SYS_MEMORY_ACCESS_RIGHT_PPU_THR = 0x0000000000000008ULL, + SYS_MEMORY_ACCESS_RIGHT_HANDLER = 0x0000000000000004ULL, + SYS_MEMORY_ACCESS_RIGHT_SPU_THR = 0x0000000000000002ULL, + SYS_MEMORY_ACCESS_RIGHT_RAW_SPU = 0x0000000000000001ULL, + + SYS_MEMORY_ATTR_READ_ONLY = 0x0000000000080000ULL, + SYS_MEMORY_ATTR_READ_WRITE = 0x0000000000040000ULL, +}; + +enum : u64 { + SYS_MEMORY_PAGE_SIZE_4K = 0x100ull, + SYS_MEMORY_PAGE_SIZE_64K = 0x200ull, + SYS_MEMORY_PAGE_SIZE_1M = 0x400ull, + SYS_MEMORY_PAGE_SIZE_MASK = 0xf00ull, +}; + +enum : u64 { + SYS_MEMORY_GRANULARITY_64K = 0x0000000000000200, + SYS_MEMORY_GRANULARITY_1M = 0x0000000000000400, + SYS_MEMORY_GRANULARITY_MASK = 0x0000000000000f00, +}; + +enum : u64 { + SYS_MEMORY_PROT_READ_WRITE = 0x0000000000040000, + SYS_MEMORY_PROT_READ_ONLY = 0x0000000000080000, + SYS_MEMORY_PROT_MASK = 0x00000000000f0000, +}; + +struct sys_memory_info_t { + be_t total_user_memory; + be_t available_user_memory; +}; + +struct sys_page_attr_t { + be_t attribute; + be_t access_right; + be_t page_size; + be_t pad; +}; + +struct lv2_memory_container { + static const u32 id_base = 0x3F000000; + static const u32 id_step = 0x1; + static const u32 id_count = 16; + + const u32 size; // Amount of "physical" memory in this container + const lv2_mem_container_id id; // ID of the container in if placed at IDM, + // otherwise SYS_MEMORY_CONTAINER_ID_INVALID + atomic_t used{}; // Amount of "physical" memory currently used + + SAVESTATE_INIT_POS(1); + + lv2_memory_container(u32 size, bool from_idm = false) noexcept; + lv2_memory_container(utils::serial &ar, bool from_idm = false) noexcept; + static std::function load(utils::serial &ar); + void save(utils::serial &ar); + static lv2_memory_container *search(u32 id); + + // Try to get specified amount of "physical" memory + // Values greater than UINT32_MAX will fail + u32 take(u64 amount) { + auto [_, result] = used.fetch_op([&](u32 &value) -> u32 { + if (size - value >= amount) { + value += static_cast(amount); + return static_cast(amount); + } + + return 0; + }); + + return result; + } + + u32 free(u64 amount) { + auto [_, result] = used.fetch_op([&](u32 &value) -> u32 { + if (value >= amount) { + value -= static_cast(amount); + return static_cast(amount); + } + + return 0; + }); + + // Sanity check + ensure(result == amount); + + return result; + } +}; + +struct sys_memory_user_memory_stat_t { + be_t a; // 0x0 + be_t b; // 0x4 + be_t c; // 0x8 + be_t d; // 0xc + be_t e; // 0x10 + be_t f; // 0x14 + be_t g; // 0x18 +}; + +// SysCalls +error_code sys_memory_allocate(cpu_thread &cpu, u64 size, u64 flags, + vm::ptr alloc_addr); +error_code sys_memory_allocate_from_container(cpu_thread &cpu, u64 size, + u32 cid, u64 flags, + vm::ptr alloc_addr); +error_code sys_memory_free(cpu_thread &cpu, u32 start_addr); +error_code sys_memory_get_page_attribute(cpu_thread &cpu, u32 addr, + vm::ptr attr); +error_code sys_memory_get_user_memory_size(cpu_thread &cpu, + vm::ptr mem_info); +error_code sys_memory_get_user_memory_stat( + cpu_thread &cpu, vm::ptr mem_stat); +error_code sys_memory_container_create(cpu_thread &cpu, vm::ptr cid, + u64 size); +error_code sys_memory_container_destroy(cpu_thread &cpu, u32 cid); +error_code sys_memory_container_get_size(cpu_thread &cpu, + vm::ptr mem_info, + u32 cid); +error_code sys_memory_container_destroy_parent_with_childs( + cpu_thread &cpu, u32 cid, u32 must_0, vm::ptr mc_child); diff --git a/kernel/cellos/include/cellos/sys_mmapper.h b/kernel/cellos/include/cellos/sys_mmapper.h new file mode 100644 index 000000000..cb1312ef4 --- /dev/null +++ b/kernel/cellos/include/cellos/sys_mmapper.h @@ -0,0 +1,125 @@ +#pragma once + +#include "sys_sync.h" + +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/Memory/vm_ptr.h" + +#include + +struct lv2_memory_container; + +namespace utils { +class shm; +} + +struct lv2_memory : lv2_obj { + static const u32 id_base = 0x08000000; + + const u32 size; // Memory size + const u32 align; // Alignment required + const u64 flags; + const u64 key; // IPC key + const bool pshared; // Process shared flag + lv2_memory_container *const ct; // Associated memory container + const std::shared_ptr shm; + + atomic_t counter{0}; + + lv2_memory(u32 size, u32 align, u64 flags, u64 key, bool pshared, + lv2_memory_container *ct); + + lv2_memory(utils::serial &ar); + static std::function load(utils::serial &ar); + void save(utils::serial &ar); + + CellError on_id_create(); +}; + +enum : u64 { + SYS_MEMORY_PAGE_FAULT_EVENT_KEY = 0xfffe000000000000ULL, +}; + +enum : u64 { + SYS_MMAPPER_NO_SHM_KEY = 0xffff000000000000ull, // Unofficial name +}; + +enum : u64 { + SYS_MEMORY_PAGE_FAULT_CAUSE_NON_MAPPED = 0x2ULL, + SYS_MEMORY_PAGE_FAULT_CAUSE_READ_ONLY = 0x1ULL, + SYS_MEMORY_PAGE_FAULT_TYPE_PPU_THREAD = 0x0ULL, + SYS_MEMORY_PAGE_FAULT_TYPE_SPU_THREAD = 0x1ULL, + SYS_MEMORY_PAGE_FAULT_TYPE_RAW_SPU = 0x2ULL, +}; + +struct page_fault_notification_entry { + ENABLE_BITWISE_SERIALIZATION; + + u32 start_addr; // Starting address of region to monitor. + u32 event_queue_id; // Queue to be notified. + u32 port_id; // Port used to notify the queue. +}; + +// Used to hold list of queues to be notified on page fault event. +struct page_fault_notification_entries { + std::vector entries; + shared_mutex mutex; + + SAVESTATE_INIT_POS(44); + + page_fault_notification_entries() = default; + page_fault_notification_entries(utils::serial &ar); + void save(utils::serial &ar); +}; + +struct page_fault_event_entries { + // First = thread, second = addr + std::unordered_map events; + shared_mutex pf_mutex; +}; + +struct mmapper_unk_entry_struct0 { + be_t a; // 0x0 + be_t b; // 0x4 + be_t c; // 0x8 + be_t d; // 0xc + be_t type; // 0x10 +}; + +// Aux +class ppu_thread; + +error_code mmapper_thread_recover_page_fault(cpu_thread *cpu); + +// SysCalls +error_code sys_mmapper_allocate_address(ppu_thread &, u64 size, u64 flags, + u64 alignment, vm::ptr alloc_addr); +error_code sys_mmapper_allocate_fixed_address(ppu_thread &); +error_code sys_mmapper_allocate_shared_memory(ppu_thread &, u64 ipc_key, + u64 size, u64 flags, + vm::ptr mem_id); +error_code +sys_mmapper_allocate_shared_memory_from_container(ppu_thread &, u64 ipc_key, + u64 size, u32 cid, u64 flags, + vm::ptr mem_id); +error_code sys_mmapper_allocate_shared_memory_ext( + ppu_thread &, u64 ipc_key, u64 size, u32 flags, + vm::ptr entries, s32 entry_count, + vm::ptr mem_id); +error_code sys_mmapper_allocate_shared_memory_from_container_ext( + ppu_thread &, u64 ipc_key, u64 size, u64 flags, u32 cid, + vm::ptr entries, s32 entry_count, + vm::ptr mem_id); +error_code sys_mmapper_change_address_access_right(ppu_thread &, u32 addr, + u64 flags); +error_code sys_mmapper_free_address(ppu_thread &, u32 addr); +error_code sys_mmapper_free_shared_memory(ppu_thread &, u32 mem_id); +error_code sys_mmapper_map_shared_memory(ppu_thread &, u32 addr, u32 mem_id, + u64 flags); +error_code sys_mmapper_search_and_map(ppu_thread &, u32 start_addr, u32 mem_id, + u64 flags, vm::ptr alloc_addr); +error_code sys_mmapper_unmap_shared_memory(ppu_thread &, u32 addr, + vm::ptr mem_id); +error_code sys_mmapper_enable_page_fault_notification(ppu_thread &, + u32 start_addr, + u32 event_queue_id); diff --git a/kernel/cellos/include/cellos/sys_mutex.h b/kernel/cellos/include/cellos/sys_mutex.h new file mode 100644 index 000000000..5191d54ad --- /dev/null +++ b/kernel/cellos/include/cellos/sys_mutex.h @@ -0,0 +1,174 @@ +#pragma once + +#include "sys_sync.h" + +#include "Emu/Memory/vm_ptr.h" + +#include "Emu/Cell/PPUThread.h" + +struct sys_mutex_attribute_t { + be_t + protocol; // SYS_SYNC_FIFO, SYS_SYNC_PRIORITY or SYS_SYNC_PRIORITY_INHERIT + be_t recursive; // SYS_SYNC_RECURSIVE or SYS_SYNC_NOT_RECURSIVE + be_t pshared; + be_t adaptive; + be_t ipc_key; + be_t flags; + be_t pad; + + union { + nse_t name_u64; + char name[sizeof(u64)]; + }; +}; + +class ppu_thread; + +struct lv2_mutex final : lv2_obj { + static const u32 id_base = 0x85000000; + + const lv2_protocol protocol; + const u32 recursive; + const u32 adaptive; + const u64 key; + const u64 name; + + u32 cond_count = 0; // Condition Variables + shared_mutex mutex; + atomic_t lock_count{0}; // Recursive Locks + + struct alignas(16) control_data_t { + u32 owner{}; + u32 reserved{}; + ppu_thread *sq{}; + }; + + atomic_t control{}; + + lv2_mutex(u32 protocol, u32 recursive, u32 adaptive, u64 key, + u64 name) noexcept + : protocol{static_cast(protocol)}, recursive(recursive), + adaptive(adaptive), key(key), name(name) {} + + lv2_mutex(utils::serial &ar); + static std::function load(utils::serial &ar); + void save(utils::serial &ar); + + template CellError try_lock(T &cpu) { + auto it = control.load(); + + if (!it.owner) { + auto store = it; + store.owner = cpu.id; + if (!control.compare_and_swap_test(it, store)) { + return CELL_EBUSY; + } + + return {}; + } + + if (it.owner == cpu.id) { + // Recursive locking + if (recursive == SYS_SYNC_RECURSIVE) { + if (lock_count == 0xffffffffu) { + return CELL_EKRESOURCE; + } + + lock_count++; + return {}; + } + + return CELL_EDEADLK; + } + + return CELL_EBUSY; + } + + template bool try_own(T &cpu) { + if (control.atomic_op([&](control_data_t &data) { + if (data.owner) { + cpu.prio.atomic_op( + [tag = ++g_priority_order_tag]( + std::common_type_t &prio) { + prio.order = tag; + }); + + cpu.next_cpu = data.sq; + data.sq = &cpu; + return false; + } else { + data.owner = cpu.id; + return true; + } + })) { + cpu.next_cpu = nullptr; + return true; + } + + return false; + } + + template CellError try_unlock(T &cpu) { + auto it = control.load(); + + if (it.owner != cpu.id) { + return CELL_EPERM; + } + + if (lock_count) { + lock_count--; + return {}; + } + + if (!it.sq) { + auto store = it; + store.owner = 0; + + if (control.compare_and_swap_test(it, store)) { + return {}; + } + } + + return CELL_EBUSY; + } + + template T *reown() { + T *res{}; + + control.fetch_op([&](control_data_t &data) { + res = nullptr; + + if (auto sq = static_cast(data.sq)) { + res = schedule(data.sq, protocol, false); + + if (sq == data.sq) { + atomic_storage::release(control.raw().owner, res->id); + return false; + } + + data.owner = res->id; + return true; + } else { + data.owner = 0; + return true; + } + }); + + if (res && cpu_flag::again - res->state) { + // Detach manually (fetch_op can fail, so avoid side-effects on the first + // node in this case) + res->next_cpu = nullptr; + } + + return res; + } +}; + +// Syscalls + +error_code sys_mutex_create(ppu_thread &ppu, vm::ptr mutex_id, + vm::ptr attr); +error_code sys_mutex_destroy(ppu_thread &ppu, u32 mutex_id); +error_code sys_mutex_lock(ppu_thread &ppu, u32 mutex_id, u64 timeout); +error_code sys_mutex_trylock(ppu_thread &ppu, u32 mutex_id); +error_code sys_mutex_unlock(ppu_thread &ppu, u32 mutex_id); diff --git a/kernel/cellos/include/cellos/sys_net.h b/kernel/cellos/include/cellos/sys_net.h new file mode 100644 index 000000000..132fea706 --- /dev/null +++ b/kernel/cellos/include/cellos/sys_net.h @@ -0,0 +1,365 @@ +#pragma once + +#include "util/bit_set.h" +#include "util/mutex.h" + +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/Memory/vm_ptr.h" + +#include +#include +#include +#include + +// Error codes +enum sys_net_error : s32 { + SYS_NET_ENOENT = 2, + SYS_NET_EINTR = 4, + SYS_NET_EBADF = 9, + SYS_NET_ENOMEM = 12, + SYS_NET_EACCES = 13, + SYS_NET_EFAULT = 14, + SYS_NET_EBUSY = 16, + SYS_NET_EINVAL = 22, + SYS_NET_EMFILE = 24, + SYS_NET_ENOSPC = 28, + SYS_NET_EPIPE = 32, + SYS_NET_EAGAIN = 35, + SYS_NET_EWOULDBLOCK = SYS_NET_EAGAIN, + SYS_NET_EINPROGRESS = 36, + SYS_NET_EALREADY = 37, + SYS_NET_EDESTADDRREQ = 39, + SYS_NET_EMSGSIZE = 40, + SYS_NET_EPROTOTYPE = 41, + SYS_NET_ENOPROTOOPT = 42, + SYS_NET_EPROTONOSUPPORT = 43, + SYS_NET_EOPNOTSUPP = 45, + SYS_NET_EPFNOSUPPORT = 46, + SYS_NET_EAFNOSUPPORT = 47, + SYS_NET_EADDRINUSE = 48, + SYS_NET_EADDRNOTAVAIL = 49, + SYS_NET_ENETDOWN = 50, + SYS_NET_ENETUNREACH = 51, + SYS_NET_ECONNABORTED = 53, + SYS_NET_ECONNRESET = 54, + SYS_NET_ENOBUFS = 55, + SYS_NET_EISCONN = 56, + SYS_NET_ENOTCONN = 57, + SYS_NET_ESHUTDOWN = 58, + SYS_NET_ETOOMANYREFS = 59, + SYS_NET_ETIMEDOUT = 60, + SYS_NET_ECONNREFUSED = 61, + SYS_NET_EHOSTDOWN = 64, + SYS_NET_EHOSTUNREACH = 65, +}; + +static constexpr sys_net_error operator-(sys_net_error v) { + return sys_net_error{-+v}; +} + +// Socket types (prefixed with SYS_NET_) +enum lv2_socket_type : s32 { + SYS_NET_SOCK_STREAM = 1, + SYS_NET_SOCK_DGRAM = 2, + SYS_NET_SOCK_RAW = 3, + SYS_NET_SOCK_DGRAM_P2P = 6, + SYS_NET_SOCK_STREAM_P2P = 10, +}; + +// Socket options (prefixed with SYS_NET_) +enum lv2_socket_option : s32 { + SYS_NET_SO_SNDBUF = 0x1001, + SYS_NET_SO_RCVBUF = 0x1002, + SYS_NET_SO_SNDLOWAT = 0x1003, + SYS_NET_SO_RCVLOWAT = 0x1004, + SYS_NET_SO_SNDTIMEO = 0x1005, + SYS_NET_SO_RCVTIMEO = 0x1006, + SYS_NET_SO_ERROR = 0x1007, + SYS_NET_SO_TYPE = 0x1008, + SYS_NET_SO_NBIO = 0x1100, // Non-blocking IO + SYS_NET_SO_TPPOLICY = 0x1101, + + SYS_NET_SO_REUSEADDR = 0x0004, + SYS_NET_SO_KEEPALIVE = 0x0008, + SYS_NET_SO_BROADCAST = 0x0020, + SYS_NET_SO_LINGER = 0x0080, + SYS_NET_SO_OOBINLINE = 0x0100, + SYS_NET_SO_REUSEPORT = 0x0200, + SYS_NET_SO_ONESBCAST = 0x0800, + SYS_NET_SO_USECRYPTO = 0x1000, + SYS_NET_SO_USESIGNATURE = 0x2000, + + SYS_NET_SOL_SOCKET = 0xffff, +}; + +// IP options (prefixed with SYS_NET_) +enum lv2_ip_option : s32 { + SYS_NET_IP_HDRINCL = 2, + SYS_NET_IP_TOS = 3, + SYS_NET_IP_TTL = 4, + SYS_NET_IP_MULTICAST_IF = 9, + SYS_NET_IP_MULTICAST_TTL = 10, + SYS_NET_IP_MULTICAST_LOOP = 11, + SYS_NET_IP_ADD_MEMBERSHIP = 12, + SYS_NET_IP_DROP_MEMBERSHIP = 13, + SYS_NET_IP_TTLCHK = 23, + SYS_NET_IP_MAXTTL = 24, + SYS_NET_IP_DONTFRAG = 26 +}; + +// Family (prefixed with SYS_NET_) +enum lv2_socket_family : s32 { + SYS_NET_AF_UNSPEC = 0, + SYS_NET_AF_LOCAL = 1, + SYS_NET_AF_UNIX = SYS_NET_AF_LOCAL, + SYS_NET_AF_INET = 2, + SYS_NET_AF_INET6 = 24, +}; + +// Flags (prefixed with SYS_NET_) +enum { + SYS_NET_MSG_OOB = 0x1, + SYS_NET_MSG_PEEK = 0x2, + SYS_NET_MSG_DONTROUTE = 0x4, + SYS_NET_MSG_EOR = 0x8, + SYS_NET_MSG_TRUNC = 0x10, + SYS_NET_MSG_CTRUNC = 0x20, + SYS_NET_MSG_WAITALL = 0x40, + SYS_NET_MSG_DONTWAIT = 0x80, + SYS_NET_MSG_BCAST = 0x100, + SYS_NET_MSG_MCAST = 0x200, + SYS_NET_MSG_USECRYPTO = 0x400, + SYS_NET_MSG_USESIGNATURE = 0x800, +}; + +// Shutdown types (prefixed with SYS_NET_) +enum { + SYS_NET_SHUT_RD = 0, + SYS_NET_SHUT_WR = 1, + SYS_NET_SHUT_RDWR = 2, +}; + +// TCP options (prefixed with SYS_NET_) +enum lv2_tcp_option : s32 { + SYS_NET_TCP_NODELAY = 1, + SYS_NET_TCP_MAXSEG = 2, + SYS_NET_TCP_MSS_TO_ADVERTISE = 3, +}; + +// IP protocols (prefixed with SYS_NET_) +enum lv2_ip_protocol : s32 { + SYS_NET_IPPROTO_IP = 0, + SYS_NET_IPPROTO_ICMP = 1, + SYS_NET_IPPROTO_IGMP = 2, + SYS_NET_IPPROTO_TCP = 6, + SYS_NET_IPPROTO_UDP = 17, + SYS_NET_IPPROTO_ICMPV6 = 58, +}; + +// Poll events (prefixed with SYS_NET_) +enum { + SYS_NET_POLLIN = 0x0001, + SYS_NET_POLLPRI = 0x0002, + SYS_NET_POLLOUT = 0x0004, + SYS_NET_POLLERR = 0x0008, /* revent only */ + SYS_NET_POLLHUP = 0x0010, /* revent only */ + SYS_NET_POLLNVAL = 0x0020, /* revent only */ + SYS_NET_POLLRDNORM = 0x0040, + SYS_NET_POLLWRNORM = SYS_NET_POLLOUT, + SYS_NET_POLLRDBAND = 0x0080, + SYS_NET_POLLWRBAND = 0x0100, +}; + +enum lv2_socket_abort_flags : s32 { + SYS_NET_ABORT_STRICT_CHECK = 1, +}; + +// in_addr_t type prefixed with sys_net_ +using sys_net_in_addr_t = u32; + +// in_port_t type prefixed with sys_net_ +using sys_net_in_port_t = u16; + +// sa_family_t type prefixed with sys_net_ +using sys_net_sa_family_t = u8; + +// socklen_t type prefixed with sys_net_ +using sys_net_socklen_t = u32; + +// fd_set prefixed with sys_net_ +struct sys_net_fd_set { + be_t fds_bits[32]; + + u32 bit(s32 s) const { return (fds_bits[(s >> 5) & 31] >> (s & 31)) & 1u; } + + void set(s32 s) { fds_bits[(s >> 5) & 31] |= (1u << (s & 31)); } +}; + +// hostent prefixed with sys_net_ +struct sys_net_hostent { + vm::bptr h_name; + vm::bpptr h_aliases; + be_t h_addrtype; + be_t h_length; + vm::bpptr h_addr_list; +}; + +// in_addr prefixed with sys_net_ +struct sys_net_in_addr { + be_t _s_addr; +}; + +// iovec prefixed with sys_net_ +struct sys_net_iovec { + be_t zero1; + vm::bptr iov_base; + be_t zero2; + be_t iov_len; +}; + +// ip_mreq prefixed with sys_net_ +struct sys_net_ip_mreq { + be_t imr_multiaddr; + be_t imr_interface; +}; + +// msghdr prefixed with sys_net_ +struct sys_net_msghdr { + be_t zero1; + vm::bptr msg_name; + be_t msg_namelen; + be_t pad1; + be_t zero2; + vm::bptr msg_iov; + be_t msg_iovlen; + be_t pad2; + be_t zero3; + vm::bptr msg_control; + be_t msg_controllen; + be_t msg_flags; +}; + +// pollfd prefixed with sys_net_ +struct sys_net_pollfd { + be_t fd; + be_t events; + be_t revents; +}; + +// sockaddr prefixed with sys_net_ +struct sys_net_sockaddr { + ENABLE_BITWISE_SERIALIZATION; + + u8 sa_len; + u8 sa_family; + char sa_data[14]; +}; + +// sockaddr_dl prefixed with sys_net_ +struct sys_net_sockaddr_dl { + ENABLE_BITWISE_SERIALIZATION; + + u8 sdl_len; + u8 sdl_family; + be_t sdl_index; + u8 sdl_type; + u8 sdl_nlen; + u8 sdl_alen; + u8 sdl_slen; + char sdl_data[12]; +}; + +// sockaddr_in prefixed with sys_net_ +struct sys_net_sockaddr_in { + ENABLE_BITWISE_SERIALIZATION; + + u8 sin_len; + u8 sin_family; + be_t sin_port; + be_t sin_addr; + be_t sin_zero; +}; + +// sockaddr_in_p2p prefixed with sys_net_ +struct sys_net_sockaddr_in_p2p { + ENABLE_BITWISE_SERIALIZATION; + + u8 sin_len; + u8 sin_family; + be_t sin_port; + be_t sin_addr; + be_t sin_vport; + char sin_zero[6]; +}; + +// timeval prefixed with sys_net_ +struct sys_net_timeval { + be_t tv_sec; + be_t tv_usec; +}; + +// linger prefixed with sys_net_ +struct sys_net_linger { + be_t l_onoff; + be_t l_linger; +}; + +class ppu_thread; + +// Syscalls + +error_code sys_net_bnet_accept(ppu_thread &, s32 s, + vm::ptr addr, + vm::ptr paddrlen); +error_code sys_net_bnet_bind(ppu_thread &, s32 s, + vm::cptr addr, u32 addrlen); +error_code sys_net_bnet_connect(ppu_thread &, s32 s, + vm::ptr addr, u32 addrlen); +error_code sys_net_bnet_getpeername(ppu_thread &, s32 s, + vm::ptr addr, + vm::ptr paddrlen); +error_code sys_net_bnet_getsockname(ppu_thread &, s32 s, + vm::ptr addr, + vm::ptr paddrlen); +error_code sys_net_bnet_getsockopt(ppu_thread &, s32 s, s32 level, s32 optname, + vm::ptr optval, vm::ptr optlen); +error_code sys_net_bnet_listen(ppu_thread &, s32 s, s32 backlog); +error_code sys_net_bnet_recvfrom(ppu_thread &, s32 s, vm::ptr buf, + u32 len, s32 flags, + vm::ptr addr, + vm::ptr paddrlen); +error_code sys_net_bnet_recvmsg(ppu_thread &, s32 s, + vm::ptr msg, s32 flags); +error_code sys_net_bnet_sendmsg(ppu_thread &, s32 s, + vm::cptr msg, s32 flags); +error_code sys_net_bnet_sendto(ppu_thread &, s32 s, vm::cptr buf, u32 len, + s32 flags, vm::cptr addr, + u32 addrlen); +error_code sys_net_bnet_setsockopt(ppu_thread &, s32 s, s32 level, s32 optname, + vm::cptr optval, u32 optlen); +error_code sys_net_bnet_shutdown(ppu_thread &, s32 s, s32 how); +error_code sys_net_bnet_socket(ppu_thread &, lv2_socket_family family, + lv2_socket_type type, lv2_ip_protocol protocol); +error_code sys_net_bnet_close(ppu_thread &, s32 s); +error_code sys_net_bnet_poll(ppu_thread &, vm::ptr fds, + s32 nfds, s32 ms); +error_code sys_net_bnet_select(ppu_thread &, s32 nfds, + vm::ptr readfds, + vm::ptr writefds, + vm::ptr exceptfds, + vm::ptr timeout); +error_code _sys_net_open_dump(ppu_thread &, s32 len, s32 flags); +error_code _sys_net_read_dump(ppu_thread &, s32 id, vm::ptr buf, s32 len, + vm::ptr pflags); +error_code _sys_net_close_dump(ppu_thread &, s32 id, vm::ptr pflags); +error_code _sys_net_write_dump(ppu_thread &, s32 id, vm::cptr buf, + s32 len, u32 unknown); +error_code sys_net_abort(ppu_thread &, s32 type, u64 arg, s32 flags); +error_code sys_net_infoctl(ppu_thread &, s32 cmd, vm::ptr arg); +error_code sys_net_control(ppu_thread &, u32 arg1, s32 arg2, vm::ptr arg3, + s32 arg4); +error_code sys_net_bnet_ioctl(ppu_thread &, s32 arg1, u32 arg2, u32 arg3); +error_code sys_net_bnet_sysctl(ppu_thread &, u32 arg1, u32 arg2, u32 arg3, + vm::ptr arg4, u32 arg5, u32 arg6); +error_code sys_net_eurus_post_command(ppu_thread &, s32 arg1, u32 arg2, + u32 arg3); diff --git a/kernel/cellos/include/cellos/sys_net/lv2_socket.h b/kernel/cellos/include/cellos/sys_net/lv2_socket.h new file mode 100644 index 000000000..cd426dc30 --- /dev/null +++ b/kernel/cellos/include/cellos/sys_net/lv2_socket.h @@ -0,0 +1,164 @@ +#pragma once + +#include +#include + +#include "Emu/IdManager.h" +#include "Emu/NP/ip_address.h" +#include "cellos/sys_net.h" +#include "util/mutex.h" + +#ifdef _WIN32 +#include +#include +#else +#ifdef __clang__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wold-style-cast" +#endif +#include +#ifdef __clang__ +#pragma GCC diagnostic pop +#endif +#endif + +enum class thread_state : u32; + +class lv2_socket { +public: + // Poll events + enum class poll_t { + read, + write, + error, + + __bitset_enum_max + }; + + union sockopt_data { + char ch[128]; + be_t _int = 0; + sys_net_timeval timeo; + sys_net_linger linger; + }; + + struct sockopt_cache { + sockopt_data data{}; + s32 len = 0; + }; + +public: + SAVESTATE_INIT_POS(7); // Dependency on RPCN + + lv2_socket(lv2_socket_family family, lv2_socket_type type, + lv2_ip_protocol protocol); + lv2_socket(utils::serial &) {} + lv2_socket(utils::serial &, lv2_socket_type type); + static std::function load(utils::serial &ar); + void save(utils::serial &, bool save_only_this_class = false); + virtual ~lv2_socket() noexcept; + lv2_socket &operator=(thread_state s) noexcept; + + std::unique_lock lock(); + + void set_lv2_id(u32 id); + bs_t get_events() const; + void set_poll_event(bs_t event); + void poll_queue(shared_ptr ppu, bs_t event, + std::function)> poll_cb); + u32 clear_queue(ppu_thread *); + void handle_events(const pollfd &native_fd, bool unset_connecting = false); + void queue_wake(ppu_thread *ppu); + + lv2_socket_family get_family() const; + lv2_socket_type get_type() const; + lv2_ip_protocol get_protocol() const; + std::size_t get_queue_size() const; + socket_type get_socket() const; +#ifdef _WIN32 + bool is_connecting() const; + void set_connecting(bool is_connecting); +#endif + +public: + virtual std::tuple, sys_net_sockaddr> + accept(bool is_lock = true) = 0; + virtual s32 bind(const sys_net_sockaddr &addr) = 0; + + virtual std::optional connect(const sys_net_sockaddr &addr) = 0; + virtual s32 connect_followup() = 0; + + virtual std::pair getpeername() = 0; + virtual std::pair getsockname() = 0; + + virtual std::tuple getsockopt(s32 level, s32 optname, + u32 len) = 0; + virtual s32 setsockopt(s32 level, s32 optname, + const std::vector &optval) = 0; + + virtual s32 listen(s32 backlog) = 0; + + virtual std::optional, sys_net_sockaddr>> + recvfrom(s32 flags, u32 len, bool is_lock = true) = 0; + virtual std::optional sendto(s32 flags, const std::vector &buf, + std::optional opt_sn_addr, + bool is_lock = true) = 0; + virtual std::optional sendmsg(s32 flags, const sys_net_msghdr &msg, + bool is_lock = true) = 0; + + virtual void close() = 0; + virtual s32 shutdown(s32 how) = 0; + + virtual s32 poll(sys_net_pollfd &sn_pfd, pollfd &native_pfd) = 0; + virtual std::tuple select(bs_t selected, + pollfd &native_pfd) = 0; + + error_code abort_socket(s32 flags); + +public: + // IDM data + static const u32 id_base = 24; + static const u32 id_step = 1; + static const u32 id_count = 1000; + +protected: + lv2_socket(utils::serial &, bool); + + shared_mutex mutex; + s32 lv2_id = 0; + + socket_type native_socket = 0; + + lv2_socket_family family{}; + lv2_socket_type type{}; + lv2_ip_protocol protocol{}; + + // Events selected for polling + atomic_bs_t events{}; + + // Event processing workload (pair of thread id and the processing function) + std::vector< + std::pair, std::function)>>> + queue; + + // Socket options value keepers + // Non-blocking IO option + s32 so_nbio = 0; + // Error, only used for connection result for non blocking stream sockets + s32 so_error = 0; + // Unsupported option + s32 so_tcp_maxseg = 1500; +#ifdef _WIN32 + s32 so_reuseaddr = 0; + s32 so_reuseport = 0; + + // Tracks connect for WSAPoll workaround + bool connecting = false; +#endif + + sys_net_sockaddr last_bound_addr{}; + +public: + u64 so_rcvtimeo = 0; + u64 so_sendtimeo = 0; +}; diff --git a/kernel/cellos/include/cellos/sys_net/lv2_socket_native.h b/kernel/cellos/include/cellos/sys_net/lv2_socket_native.h new file mode 100644 index 000000000..b8d7f2e74 --- /dev/null +++ b/kernel/cellos/include/cellos/sys_net/lv2_socket_native.h @@ -0,0 +1,84 @@ + +#pragma once + +#ifdef _WIN32 +#include +#include +#else +#ifdef __clang__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wold-style-cast" +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef __clang__ +#pragma GCC diagnostic pop +#endif +#endif + +#include "lv2_socket.h" + +class lv2_socket_native final : public lv2_socket { +public: + static constexpr u32 id_type = 1; + + lv2_socket_native(lv2_socket_family family, lv2_socket_type type, + lv2_ip_protocol protocol); + lv2_socket_native(utils::serial &ar, lv2_socket_type type); + ~lv2_socket_native() noexcept override; + void save(utils::serial &ar); + s32 create_socket(); + + std::tuple, sys_net_sockaddr> + accept(bool is_lock = true) override; + s32 bind(const sys_net_sockaddr &addr) override; + + std::optional connect(const sys_net_sockaddr &addr) override; + s32 connect_followup() override; + + std::pair getpeername() override; + std::pair getsockname() override; + std::tuple getsockopt(s32 level, s32 optname, + u32 len) override; + s32 setsockopt(s32 level, s32 optname, + const std::vector &optval) override; + std::optional, sys_net_sockaddr>> + recvfrom(s32 flags, u32 len, bool is_lock = true) override; + std::optional sendto(s32 flags, const std::vector &buf, + std::optional opt_sn_addr, + bool is_lock = true) override; + std::optional sendmsg(s32 flags, const sys_net_msghdr &msg, + bool is_lock = true) override; + + s32 poll(sys_net_pollfd &sn_pfd, pollfd &native_pfd) override; + std::tuple select(bs_t selected, + pollfd &native_pfd) override; + + bool is_socket_connected(); + + s32 listen(s32 backlog) override; + void close() override; + s32 shutdown(s32 how) override; + +private: + void set_socket(socket_type native_socket, lv2_socket_family family, + lv2_socket_type type, lv2_ip_protocol protocol); + void set_default_buffers(); + void set_non_blocking(); + +private: + // Value keepers +#ifdef _WIN32 + s32 so_reuseaddr = 0; + s32 so_reuseport = 0; +#endif + u16 bound_port = 0; + bool feign_tcp_conn_failure = false; // Savestate load related +}; diff --git a/kernel/cellos/include/cellos/sys_net/lv2_socket_p2p.h b/kernel/cellos/include/cellos/sys_net/lv2_socket_p2p.h new file mode 100644 index 000000000..dc98edb50 --- /dev/null +++ b/kernel/cellos/include/cellos/sys_net/lv2_socket_p2p.h @@ -0,0 +1,57 @@ +#pragma once + +#include "lv2_socket.h" + +class lv2_socket_p2p : public lv2_socket { +public: + lv2_socket_p2p(lv2_socket_family family, lv2_socket_type type, + lv2_ip_protocol protocol); + lv2_socket_p2p(utils::serial &ar, lv2_socket_type type); + void save(utils::serial &ar); + + std::tuple, sys_net_sockaddr> + accept(bool is_lock = true) override; + s32 bind(const sys_net_sockaddr &addr) override; + + std::optional connect(const sys_net_sockaddr &addr) override; + s32 connect_followup() override; + + std::pair getpeername() override; + std::pair getsockname() override; + + std::tuple getsockopt(s32 level, s32 optname, + u32 len) override; + s32 setsockopt(s32 level, s32 optname, + const std::vector &optval) override; + + s32 listen(s32 backlog) override; + + std::optional, sys_net_sockaddr>> + recvfrom(s32 flags, u32 len, bool is_lock = true) override; + std::optional sendto(s32 flags, const std::vector &buf, + std::optional opt_sn_addr, + bool is_lock = true) override; + std::optional sendmsg(s32 flags, const sys_net_msghdr &msg, + bool is_lock = true) override; + + void close() override; + s32 shutdown(s32 how) override; + + s32 poll(sys_net_pollfd &sn_pfd, pollfd &native_pfd) override; + std::tuple select(bs_t selected, + pollfd &native_pfd) override; + + void handle_new_data(sys_net_sockaddr_in_p2p p2p_addr, + std::vector p2p_data); + +protected: + // Port(actual bound port) and Virtual Port(indicated by u16 at the start of + // the packet) + u16 port = 3658, vport = 0; + u32 bound_addr = 0; + // Queue containing received packets from network_thread for + // SYS_NET_SOCK_DGRAM_P2P sockets + std::queue>> data{}; + // List of sock options + std::map sockopts; +}; diff --git a/kernel/cellos/include/cellos/sys_net/lv2_socket_p2ps.h b/kernel/cellos/include/cellos/sys_net/lv2_socket_p2ps.h new file mode 100644 index 000000000..1cb1e5e2e --- /dev/null +++ b/kernel/cellos/include/cellos/sys_net/lv2_socket_p2ps.h @@ -0,0 +1,128 @@ +#pragma once + +#ifdef _WIN32 +#include +#include +#else +#ifdef __clang__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wold-style-cast" +#endif +#include +#ifdef __clang__ +#pragma GCC diagnostic pop +#endif +#endif + +#include "lv2_socket_p2p.h" + +struct nt_p2p_port; + +constexpr be_t P2PS_U2S_SIG = + (static_cast('U') << 24 | static_cast('2') << 16 | + static_cast('S') << 8 | static_cast('0')); + +struct p2ps_encapsulated_tcp { + be_t signature = + P2PS_U2S_SIG; // Signature to verify it's P2P Stream data + be_t length = 0; // Length of data + be_t seq = 0; // This should be u32 but changed to u64 for simplicity + be_t ack = 0; + be_t src_port = 0; // fake source tcp port + be_t dst_port = 0; // fake dest tcp port(should be == vport) + be_t checksum = 0; + u8 flags = 0; +}; + +enum p2ps_stream_status { + stream_closed, // Default when port is not listening nor connected + stream_listening, // Stream is listening, accepting SYN packets + stream_handshaking, // Currently handshaking + stream_connected, // This is an established connection(after tcp handshake) +}; + +enum p2ps_tcp_flags : u8 { + FIN = (1 << 0), + SYN = (1 << 1), + RST = (1 << 2), + PSH = (1 << 3), + ACK = (1 << 4), + URG = (1 << 5), + ECE = (1 << 6), + CWR = (1 << 7), +}; + +u16 u2s_tcp_checksum(const le_t *buffer, usz size); +std::vector generate_u2s_packet(const p2ps_encapsulated_tcp &header, + const u8 *data, const u32 datasize); + +class lv2_socket_p2ps final : public lv2_socket_p2p { +public: + static constexpr u32 id_type = 2; + + lv2_socket_p2ps(lv2_socket_family family, lv2_socket_type type, + lv2_ip_protocol protocol); + lv2_socket_p2ps(socket_type socket, u16 port, u16 vport, u32 op_addr, + u16 op_port, u16 op_vport, u64 cur_seq, u64 data_beg_seq, + s32 so_nbio); + lv2_socket_p2ps(utils::serial &ar, lv2_socket_type type); + void save(utils::serial &ar); + + p2ps_stream_status get_status() const; + void set_status(p2ps_stream_status new_status); + bool handle_connected(p2ps_encapsulated_tcp *tcp_header, u8 *data, + ::sockaddr_storage *op_addr, nt_p2p_port *p2p_port); + bool handle_listening(p2ps_encapsulated_tcp *tcp_header, u8 *data, + ::sockaddr_storage *op_addr); + void send_u2s_packet(std::vector data, const ::sockaddr_in *dst, u64 seq, + bool require_ack); + void close_stream(); + + std::tuple, sys_net_sockaddr> + accept(bool is_lock = true) override; + s32 bind(const sys_net_sockaddr &addr) override; + + std::optional connect(const sys_net_sockaddr &addr) override; + + std::pair getpeername() override; + std::pair getsockname() override; + + s32 listen(s32 backlog) override; + + std::optional, sys_net_sockaddr>> + recvfrom(s32 flags, u32 len, bool is_lock = true) override; + std::optional sendto(s32 flags, const std::vector &buf, + std::optional opt_sn_addr, + bool is_lock = true) override; + std::optional sendmsg(s32 flags, const sys_net_msghdr &msg, + bool is_lock = true) override; + + void close() override; + s32 shutdown(s32 how) override; + + s32 poll(sys_net_pollfd &sn_pfd, pollfd &native_pfd) override; + std::tuple select(bs_t selected, + pollfd &native_pfd) override; + +private: + void close_stream_nl(nt_p2p_port *p2p_port); + +private: + static constexpr usz MAX_RECEIVED_BUFFER = (1024 * 1024 * 10); + + p2ps_stream_status status = p2ps_stream_status::stream_closed; + + usz max_backlog = 0; // set on listen + std::deque backlog; + + u16 op_port = 0, op_vport = 0; + u32 op_addr = 0; + + u64 data_beg_seq = 0; // Seq of first byte of received_data + u64 data_available = + 0; // Amount of continuous data available(calculated on ACK send) + std::map> + received_data; // holds seq/data of data received + + u64 cur_seq = 0; // SEQ of next packet to be sent +}; diff --git a/kernel/cellos/include/cellos/sys_net/lv2_socket_raw.h b/kernel/cellos/include/cellos/sys_net/lv2_socket_raw.h new file mode 100644 index 000000000..65cf35348 --- /dev/null +++ b/kernel/cellos/include/cellos/sys_net/lv2_socket_raw.h @@ -0,0 +1,45 @@ +#pragma once + +#include "lv2_socket.h" + +class lv2_socket_raw final : public lv2_socket { +public: + static constexpr u32 id_type = 1; + + lv2_socket_raw(lv2_socket_family family, lv2_socket_type type, + lv2_ip_protocol protocol); + lv2_socket_raw(utils::serial &ar, lv2_socket_type type); + void save(utils::serial &ar); + + std::tuple, sys_net_sockaddr> + accept(bool is_lock = true) override; + s32 bind(const sys_net_sockaddr &addr) override; + + std::optional connect(const sys_net_sockaddr &addr) override; + s32 connect_followup() override; + + std::pair getpeername() override; + std::pair getsockname() override; + + std::tuple getsockopt(s32 level, s32 optname, + u32 len) override; + s32 setsockopt(s32 level, s32 optname, + const std::vector &optval) override; + + s32 listen(s32 backlog) override; + + std::optional, sys_net_sockaddr>> + recvfrom(s32 flags, u32 len, bool is_lock = true) override; + std::optional sendto(s32 flags, const std::vector &buf, + std::optional opt_sn_addr, + bool is_lock = true) override; + std::optional sendmsg(s32 flags, const sys_net_msghdr &msg, + bool is_lock = true) override; + + void close() override; + s32 shutdown(s32 how) override; + + s32 poll(sys_net_pollfd &sn_pfd, pollfd &native_pfd) override; + std::tuple select(bs_t selected, + pollfd &native_pfd) override; +}; diff --git a/kernel/cellos/include/cellos/sys_net/network_context.h b/kernel/cellos/include/cellos/sys_net/network_context.h new file mode 100644 index 000000000..363bc2e02 --- /dev/null +++ b/kernel/cellos/include/cellos/sys_net/network_context.h @@ -0,0 +1,45 @@ +#pragma once + +#include "Emu/Cell/PPUThread.h" +#include "util/mutex.h" +#include +#include + +#include "nt_p2p_port.h" + +struct base_network_thread { + void add_ppu_to_awake(ppu_thread *ppu); + void del_ppu_to_awake(ppu_thread *ppu); + + shared_mutex mutex_ppu_to_awake; + std::vector ppu_to_awake; + + void wake_threads(); +}; + +struct network_thread : base_network_thread { + shared_mutex mutex_thread_loop; + atomic_t num_polls = 0; + + static constexpr auto thread_name = "Network Thread"; + + void operator()(); +}; + +struct p2p_thread : base_network_thread { + shared_mutex list_p2p_ports_mutex; + std::map list_p2p_ports; + atomic_t num_p2p_ports = 0; + + static constexpr auto thread_name = "Network P2P Thread"; + + p2p_thread(); + + void create_p2p_port(u16 p2p_port); + + void bind_sce_np_port(); + void operator()(); +}; + +using network_context = named_thread; +using p2p_context = named_thread; diff --git a/kernel/cellos/include/cellos/sys_net/nt_p2p_port.h b/kernel/cellos/include/cellos/sys_net/nt_p2p_port.h new file mode 100644 index 000000000..a723c4ef7 --- /dev/null +++ b/kernel/cellos/include/cellos/sys_net/nt_p2p_port.h @@ -0,0 +1,82 @@ +#pragma once + +#include + +#include "lv2_socket_p2ps.h" + +#ifdef _WIN32 +#include +#include +#else +#ifdef __clang__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wold-style-cast" +#endif +#include +#include +#include +#ifdef __clang__ +#pragma GCC diagnostic pop +#endif +#endif + +// dst_vport src_vport flags +constexpr s32 VPORT_P2P_HEADER_SIZE = sizeof(u16) + sizeof(u16) + sizeof(u16); + +enum VPORT_P2P_FLAGS { + P2P_FLAG_P2P = 1, + P2P_FLAG_P2PS = 1 << 1, +}; + +struct signaling_message { + u32 src_addr = 0; + u16 src_port = 0; + + std::vector data; +}; + +namespace sys_net_helpers { +bool all_reusable(const std::set &sock_ids); +} + +struct nt_p2p_port { + // Real socket where P2P packets are received/sent + socket_type p2p_socket = 0; + u16 port = 0; + + bool is_ipv6 = false; + + shared_mutex bound_p2p_vports_mutex; + // For DGRAM_P2P sockets (vport, sock_ids) + std::map> bound_p2p_vports{}; + // For STREAM_P2P sockets (vport, sock_ids) + std::map> bound_p2ps_vports{}; + // List of active(either from a connect or an accept) P2PS sockets (key, + // sock_id) key is ( (src_vport) << 48 | (dst_vport) << 32 | addr ) with + // src_vport and addr being 0 for listening sockets + std::map bound_p2p_streams{}; + // Current free port index + u16 binding_port = 30000; + + // Queued messages from RPCN + shared_mutex s_rpcn_mutex; + std::vector> rpcn_msgs{}; + // Queued signaling messages + shared_mutex s_sign_mutex; + std::vector sign_msgs{}; + + std::array p2p_recv_data{}; + + nt_p2p_port(u16 port); + ~nt_p2p_port(); + + static void dump_packet(p2ps_encapsulated_tcp *tcph); + + u16 get_port(); + + bool handle_connected(s32 sock_id, p2ps_encapsulated_tcp *tcp_header, + u8 *data, ::sockaddr_storage *op_addr); + bool handle_listening(s32 sock_id, p2ps_encapsulated_tcp *tcp_header, + u8 *data, ::sockaddr_storage *op_addr); + bool recv_data(); +}; diff --git a/kernel/cellos/include/cellos/sys_net/sys_net_helpers.h b/kernel/cellos/include/cellos/sys_net/sys_net_helpers.h new file mode 100644 index 000000000..7a09f1f22 --- /dev/null +++ b/kernel/cellos/include/cellos/sys_net/sys_net_helpers.h @@ -0,0 +1,34 @@ +#pragma once + +#ifdef _WIN32 +#include +#include +#else +#ifdef __clang__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wold-style-cast" +#endif +#include +#include +#ifdef __clang__ +#pragma GCC diagnostic pop +#endif +#endif + +#include "cellos/sys_net.h" + +int get_native_error(); +sys_net_error convert_error(bool is_blocking, int native_error, + bool is_connecting = false); +sys_net_error get_last_error(bool is_blocking, bool is_connecting = false); +sys_net_sockaddr +native_addr_to_sys_net_addr(const ::sockaddr_storage &native_addr); +::sockaddr_in sys_net_addr_to_native_addr(const sys_net_sockaddr &sn_addr); +bool is_ip_public_address(const ::sockaddr_in &addr); +u32 network_clear_queue(ppu_thread &ppu); +void clear_ppu_to_awake(ppu_thread &ppu); + +#ifdef _WIN32 +void windows_poll(std::vector &fds, unsigned long nfds, int timeout, + std::vector &connecting); +#endif diff --git a/rpcs3/Emu/Cell/lv2/sys_overlay.h b/kernel/cellos/include/cellos/sys_overlay.h similarity index 62% rename from rpcs3/Emu/Cell/lv2/sys_overlay.h rename to kernel/cellos/include/cellos/sys_overlay.h index 780794c8f..539112266 100644 --- a/rpcs3/Emu/Cell/lv2/sys_overlay.h +++ b/kernel/cellos/include/cellos/sys_overlay.h @@ -4,22 +4,26 @@ #include "Emu/Memory/vm_ptr.h" #include "sys_sync.h" -struct lv2_overlay final : ppu_module -{ - static const u32 id_base = 0x25000000; +struct lv2_overlay final : ppu_module { + static const u32 id_base = 0x25000000; - u32 entry{}; - u32 seg0_code_end{}; + u32 entry{}; + u32 seg0_code_end{}; - lv2_overlay() = default; - lv2_overlay(utils::serial&) {} - static std::function load(utils::serial& ar); - void save(utils::serial& ar); + lv2_overlay() = default; + lv2_overlay(utils::serial &) {} + static std::function load(utils::serial &ar); + void save(utils::serial &ar); }; -error_code sys_overlay_load_module(vm::ptr ovlmid, vm::cptr path, u64 flags, vm::ptr entry); -error_code sys_overlay_load_module_by_fd(vm::ptr ovlmid, u32 fd, u64 offset, u64 flags, vm::ptr entry); +error_code sys_overlay_load_module(vm::ptr ovlmid, vm::cptr path, + u64 flags, vm::ptr entry); +error_code sys_overlay_load_module_by_fd(vm::ptr ovlmid, u32 fd, + u64 offset, u64 flags, + vm::ptr entry); error_code sys_overlay_unload_module(u32 ovlmid); + +// clang-format off // error_code sys_overlay_get_module_list(sys_pid_t pid, usz ovlmids_num, sys_overlay_t * ovlmids, usz * num_of_modules); // error_code sys_overlay_get_module_info(sys_pid_t pid, sys_overlay_t ovlmid, sys_overlay_module_info_t * info); // error_code sys_overlay_get_module_info2(sys_pid_t pid, sys_overlay_t ovlmid, sys_overlay_module_info2_t * info);// @@ -27,3 +31,4 @@ error_code sys_overlay_unload_module(u32 ovlmid); // error_code sys_overlay_get_module_dbg_info(); //3 params? // error_code _sys_prx_load_module(vm::ps3::cptr path, u64 flags, vm::ps3::ptr pOpt); +// clang-format on diff --git a/kernel/cellos/include/cellos/sys_ppu_thread.h b/kernel/cellos/include/cellos/sys_ppu_thread.h new file mode 100644 index 000000000..6258db0dd --- /dev/null +++ b/kernel/cellos/include/cellos/sys_ppu_thread.h @@ -0,0 +1,69 @@ +#pragma once + +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/Memory/vm_ptr.h" + +class ppu_thread; + +enum : s32 { + SYS_PPU_THREAD_ONCE_INIT = 0, + SYS_PPU_THREAD_DONE_INIT = 1, +}; + +// PPU Thread Flags +enum : u64 { + SYS_PPU_THREAD_CREATE_JOINABLE = 0x1, + SYS_PPU_THREAD_CREATE_INTERRUPT = 0x2, +}; + +struct sys_ppu_thread_stack_t { + be_t pst_addr; + be_t pst_size; +}; + +struct ppu_thread_param_t { + vm::bptr entry; + be_t tls; // vm::bptr +}; + +struct sys_ppu_thread_icontext_t { + be_t gpr[32]; + be_t cr; + be_t rsv1; + be_t xer; + be_t lr; + be_t ctr; + be_t pc; +}; + +// Syscalls + +void _sys_ppu_thread_exit(ppu_thread &ppu, u64 errorcode); +s32 sys_ppu_thread_yield( + ppu_thread &ppu); // Return value is ignored by the library +error_code sys_ppu_thread_join(ppu_thread &ppu, u32 thread_id, + vm::ptr vptr); +error_code sys_ppu_thread_detach(ppu_thread &ppu, u32 thread_id); +error_code sys_ppu_thread_get_join_state( + ppu_thread &ppu, + vm::ptr isjoinable); // Error code is ignored by the library +error_code sys_ppu_thread_set_priority(ppu_thread &ppu, u32 thread_id, + s32 prio); +error_code sys_ppu_thread_get_priority(ppu_thread &ppu, u32 thread_id, + vm::ptr priop); +error_code +sys_ppu_thread_get_stack_information(ppu_thread &ppu, + vm::ptr sp); +error_code sys_ppu_thread_stop(ppu_thread &ppu, u32 thread_id); +error_code sys_ppu_thread_restart(ppu_thread &ppu); +error_code _sys_ppu_thread_create(ppu_thread &ppu, vm::ptr thread_id, + vm::ptr param, u64 arg, + u64 arg4, s32 prio, u32 stacksize, u64 flags, + vm::cptr threadname); +error_code sys_ppu_thread_start(ppu_thread &ppu, u32 thread_id); +error_code sys_ppu_thread_rename(ppu_thread &ppu, u32 thread_id, + vm::cptr name); +error_code sys_ppu_thread_recover_page_fault(ppu_thread &ppu, u32 thread_id); +error_code +sys_ppu_thread_get_page_fault_context(ppu_thread &ppu, u32 thread_id, + vm::ptr ctxt); diff --git a/kernel/cellos/include/cellos/sys_process.h b/kernel/cellos/include/cellos/sys_process.h new file mode 100644 index 000000000..5b42e904d --- /dev/null +++ b/kernel/cellos/include/cellos/sys_process.h @@ -0,0 +1,130 @@ +#pragma once + +#include "Crypto/unself.h" +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/Memory/vm_ptr.h" + +// Process Local Object Type +enum : u32 { + SYS_MEM_OBJECT = 0x08, + SYS_MUTEX_OBJECT = 0x85, + SYS_COND_OBJECT = 0x86, + SYS_RWLOCK_OBJECT = 0x88, + SYS_INTR_TAG_OBJECT = 0x0A, + SYS_INTR_SERVICE_HANDLE_OBJECT = 0x0B, + SYS_EVENT_QUEUE_OBJECT = 0x8D, + SYS_EVENT_PORT_OBJECT = 0x0E, + SYS_TRACE_OBJECT = 0x21, + SYS_SPUIMAGE_OBJECT = 0x22, + SYS_PRX_OBJECT = 0x23, + SYS_SPUPORT_OBJECT = 0x24, + SYS_OVERLAY_OBJECT = 0x25, + SYS_LWMUTEX_OBJECT = 0x95, + SYS_TIMER_OBJECT = 0x11, + SYS_SEMAPHORE_OBJECT = 0x96, + SYS_FS_FD_OBJECT = 0x73, + SYS_LWCOND_OBJECT = 0x97, + SYS_EVENT_FLAG_OBJECT = 0x98, + SYS_RSXAUDIO_OBJECT = 0x60, +}; + +enum : u64 { + SYS_PROCESS_PRIMARY_STACK_SIZE_32K = 0x0000000000000010, + SYS_PROCESS_PRIMARY_STACK_SIZE_64K = 0x0000000000000020, + SYS_PROCESS_PRIMARY_STACK_SIZE_96K = 0x0000000000000030, + SYS_PROCESS_PRIMARY_STACK_SIZE_128K = 0x0000000000000040, + SYS_PROCESS_PRIMARY_STACK_SIZE_256K = 0x0000000000000050, + SYS_PROCESS_PRIMARY_STACK_SIZE_512K = 0x0000000000000060, + SYS_PROCESS_PRIMARY_STACK_SIZE_1M = 0x0000000000000070, +}; + +constexpr auto SYS_PROCESS_PARAM_SECTION_NAME = ".sys_proc_param"; + +enum { + SYS_PROCESS_PARAM_INVALID_PRIO = -32768, +}; + +enum : u32 { + SYS_PROCESS_PARAM_INVALID_STACK_SIZE = 0xffffffff, + + SYS_PROCESS_PARAM_STACK_SIZE_MIN = 0x1000, // 4KB + SYS_PROCESS_PARAM_STACK_SIZE_MAX = 0x100000, // 1MB + + SYS_PROCESS_PARAM_VERSION_INVALID = 0xffffffff, + SYS_PROCESS_PARAM_VERSION_1 = 0x00000001, // for SDK 08X + SYS_PROCESS_PARAM_VERSION_084_0 = 0x00008400, + SYS_PROCESS_PARAM_VERSION_090_0 = 0x00009000, + SYS_PROCESS_PARAM_VERSION_330_0 = 0x00330000, + + SYS_PROCESS_PARAM_MAGIC = 0x13bcc5f6, + + SYS_PROCESS_PARAM_MALLOC_PAGE_SIZE_NONE = 0x00000000, + SYS_PROCESS_PARAM_MALLOC_PAGE_SIZE_64K = 0x00010000, + SYS_PROCESS_PARAM_MALLOC_PAGE_SIZE_1M = 0x00100000, + + SYS_PROCESS_PARAM_PPC_SEG_DEFAULT = 0x00000000, + SYS_PROCESS_PARAM_PPC_SEG_OVLM = 0x00000001, + SYS_PROCESS_PARAM_PPC_SEG_FIXEDADDR_PRX = 0x00000002, + + SYS_PROCESS_PARAM_SDK_VERSION_UNKNOWN = 0xffffffff, +}; + +struct sys_exit2_param { + be_t x0; // 0x85 + be_t this_size; // 0x30 + be_t next_size; + be_t prio; + be_t flags; + vm::bpptr args; +}; + +struct ps3_process_info_t { + u32 sdk_ver; + u32 ppc_seg; + SelfAdditionalInfo self_info; + u32 ctrl_flags1 = 0; + + bool has_root_perm() const; + bool has_debug_perm() const; + bool debug_or_root() const; + std::string_view get_cellos_appname() const; +}; + +extern ps3_process_info_t g_ps3_process_info; + +// Auxiliary functions +s32 process_getpid(); +s32 process_get_sdk_version(u32 pid, s32 &ver); +void lv2_exitspawn(ppu_thread &ppu, std::vector &argv, + std::vector &envp, std::vector &data); + +enum CellError : u32; +CellError process_is_spu_lock_line_reservation_address(u32 addr, u64 flags); + +// SysCalls +s32 sys_process_getpid(); +s32 sys_process_getppid(); +error_code sys_process_get_number_of_object(u32 object, vm::ptr nump); +error_code sys_process_get_id(u32 object, vm::ptr buffer, u32 size, + vm::ptr set_size); +error_code sys_process_get_id2(u32 object, vm::ptr buffer, u32 size, + vm::ptr set_size); +error_code _sys_process_get_paramsfo(vm::ptr buffer); +error_code sys_process_get_sdk_version(u32 pid, vm::ptr version); +error_code sys_process_get_status(u64 unk); +error_code sys_process_is_spu_lock_line_reservation_address(u32 addr, + u64 flags); +error_code sys_process_kill(u32 pid); +error_code sys_process_wait_for_child(u32 pid, vm::ptr status, u64 unk); +error_code sys_process_wait_for_child2(u64 unk1, u64 unk2, u64 unk3, u64 unk4, + u64 unk5, u64 unk6); +error_code sys_process_detach_child(u64 unk); +void _sys_process_exit(ppu_thread &ppu, s32 status, u32 arg2, u32 arg3); +void _sys_process_exit2(ppu_thread &ppu, s32 status, + vm::ptr arg, u32 arg_size, u32 arg4); +void sys_process_exit3(ppu_thread &ppu, s32 status); +error_code sys_process_spawns_a_self2(vm::ptr pid, u32 primary_prio, + u64 flags, vm::ptr stack, + u32 stack_size, u32 mem_id, + vm::ptr param_sfo, + vm::ptr dbg_data); diff --git a/kernel/cellos/include/cellos/sys_prx.h b/kernel/cellos/include/cellos/sys_prx.h new file mode 100644 index 000000000..69de96149 --- /dev/null +++ b/kernel/cellos/include/cellos/sys_prx.h @@ -0,0 +1,293 @@ +#pragma once + +#include "sys_sync.h" + +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/Cell/PPUAnalyser.h" +#include "Emu/Memory/vm_ptr.h" + +// Return codes +enum CellPrxError : u32 { + CELL_PRX_ERROR_ERROR = 0x80011001, // Error state + CELL_PRX_ERROR_ILLEGAL_PERM = 0x800110d1, // No permission to execute API + CELL_PRX_ERROR_UNKNOWN_MODULE = + 0x8001112e, // Specified PRX could not be found + CELL_PRX_ERROR_ALREADY_STARTED = + 0x80011133, // Specified PRX is already started + CELL_PRX_ERROR_NOT_STARTED = 0x80011134, // Specified PRX is not started + CELL_PRX_ERROR_ALREADY_STOPPED = + 0x80011135, // Specified PRX is already stopped + CELL_PRX_ERROR_CAN_NOT_STOP = 0x80011136, // Specified PRX must not be stopped + CELL_PRX_ERROR_NOT_REMOVABLE = + 0x80011138, // Specified PRX must not be deleted + CELL_PRX_ERROR_LIBRARY_NOT_YET_LINKED = + 0x8001113a, // Called unlinked function + CELL_PRX_ERROR_LIBRARY_FOUND = + 0x8001113b, // Specified library is already registered + CELL_PRX_ERROR_LIBRARY_NOTFOUND = + 0x8001113c, // Specified library is not registered + CELL_PRX_ERROR_ILLEGAL_LIBRARY = 0x8001113d, // Library structure is invalid + CELL_PRX_ERROR_LIBRARY_INUSE = + 0x8001113e, // Library cannot be deleted because it is linked + CELL_PRX_ERROR_ALREADY_STOPPING = + 0x8001113f, // Specified PRX is in the process of stopping + CELL_PRX_ERROR_UNSUPPORTED_PRX_TYPE = + 0x80011148, // Specified PRX format is invalid and cannot be loaded + CELL_PRX_ERROR_INVAL = 0x80011324, // Argument value is invalid + CELL_PRX_ERROR_ILLEGAL_PROCESS = + 0x80011801, // Specified process does not exist + CELL_PRX_ERROR_NO_LIBLV2 = 0x80011881, // liblv2.sprx does not exist + CELL_PRX_ERROR_UNSUPPORTED_ELF_TYPE = + 0x80011901, // ELF type of specified file is not supported + CELL_PRX_ERROR_UNSUPPORTED_ELF_CLASS = + 0x80011902, // ELF class of specified file is not supported + CELL_PRX_ERROR_UNDEFINED_SYMBOL = 0x80011904, // References undefined symbols + CELL_PRX_ERROR_UNSUPPORTED_RELOCATION_TYPE = + 0x80011905, // Uses unsupported relocation type + CELL_PRX_ERROR_ELF_IS_REGISTERED = + 0x80011910, // Fixed ELF is already registered + CELL_PRX_ERROR_NO_EXIT_ENTRY = 0x80011911, +}; + +enum { SYS_PRX_MODULE_FILENAME_SIZE = 512 }; + +struct sys_prx_get_module_id_by_name_option_t { + be_t size; + vm::ptr base; +}; + +struct sys_prx_load_module_option_t { + be_t size; + vm::bptr base_addr; +}; + +struct sys_prx_segment_info_t { + be_t base; + be_t filesz; + be_t memsz; + be_t index; + be_t type; +}; + +struct sys_prx_module_info_t { + be_t size; // 0 + char name[30]; // 8 + char version[2]; // 0x26 + be_t modattribute; // 0x28 + be_t start_entry; // 0x2c + be_t stop_entry; // 0x30 + be_t all_segments_num; // 0x34 + vm::bptr filename; // 0x38 + be_t filename_size; // 0x3c + vm::bptr segments; // 0x40 + be_t segments_num; // 0x44 +}; + +struct sys_prx_module_info_v2_t : sys_prx_module_info_t { + be_t exports_addr; // 0x48 + be_t exports_size; // 0x4C + be_t imports_addr; // 0x50 + be_t imports_size; // 0x54 +}; + +struct sys_prx_module_info_option_t { + be_t size; // 0x10 + union { + vm::bptr info; + vm::bptr info_v2; + }; +}; + +struct sys_prx_start_module_option_t { + be_t size; +}; + +struct sys_prx_stop_module_option_t { + be_t size; +}; + +struct sys_prx_start_stop_module_option_t { + be_t size; + be_t cmd; + vm::bptr argv), u64> entry; + be_t res; + vm::bptr), u64>, u32 argc, + vm::ptr argv), + u64> + entry2; +}; + +struct sys_prx_unload_module_option_t { + be_t size; +}; + +struct sys_prx_get_module_list_t { + be_t size; + be_t max; + be_t count; + vm::bptr idlist; +}; + +struct sys_prx_get_module_list_option_t { + be_t size; // 0x20 + be_t pad; + be_t max; + be_t count; + vm::bptr idlist; + be_t unk; // 0 +}; + +struct sys_prx_register_module_0x20_t { + be_t size; // 0x0 + be_t toc; // 0x8 + be_t toc_size; // 0xC + vm::bptr stubs_ea; // 0x10 + be_t stubs_size; // 0x14 + vm::bptr error_handler; // 0x18 + char pad[4]; // 0x1C +}; + +struct sys_prx_register_module_0x30_type_1_t { + be_t size; // 0x0 + be_t type; // 0x8 + be_t unk3; // 0x10 + be_t unk4; // 0x14 + vm::bptr lib_entries_ea; // 0x18 + be_t lib_entries_size; // 0x1C + vm::bptr lib_stub_ea; // 0x20 + be_t lib_stub_size; // 0x24 + vm::bptr error_handler; // 0x28 + char pad[4]; // 0x2C +}; + +enum : u32 { + SYS_PRX_RESIDENT = 0, + SYS_PRX_NO_RESIDENT = 1, + + SYS_PRX_START_OK = 0, + + SYS_PRX_STOP_SUCCESS = 0, + SYS_PRX_STOP_OK = 0, + SYS_PRX_STOP_FAILED = 1 +}; + +// Unofficial names for PRX state +enum : u32 { + PRX_STATE_INITIALIZED, + PRX_STATE_STARTING, // In-between state between initialized and started + // (internal) + PRX_STATE_STARTED, + PRX_STATE_STOPPING, // In-between state between started and stopped (internal) + PRX_STATE_STOPPED, // Last state, the module cannot be restarted + PRX_STATE_DESTROYED, // Last state, the module cannot be restarted +}; + +struct lv2_prx final : ppu_module { + static const u32 id_base = 0x23000000; + + atomic_t state = PRX_STATE_INITIALIZED; + shared_mutex mutex; + + std::unordered_map specials; + + vm::ptr argv)> start = vm::null; + vm::ptr argv)> stop = vm::null; + vm::ptr argv)> prologue = + vm::null; + vm::ptr argv)> epilogue = + vm::null; + vm::ptr exit = vm::null; + + char module_info_name[28]{}; + u8 module_info_version[2]{}; + be_t module_info_attributes{}; + + u32 imports_start = umax; + u32 imports_end = 0; + + u32 exports_start = umax; + u32 exports_end = 0; + + std::basic_string m_loaded_flags; + std::basic_string m_external_loaded_flags; + + void load_exports(); // (Re)load exports + void restore_exports(); // For savestates + void unload_exports(); + + lv2_prx() noexcept = default; + lv2_prx(utils::serial &) {} + static std::function load(utils::serial &); + void save(utils::serial &ar); +}; + +enum : u64 { + SYS_PRX_LOAD_MODULE_FLAGS_FIXEDADDR = 0x1ull, + SYS_PRX_LOAD_MODULE_FLAGS_INVALIDMASK = ~SYS_PRX_LOAD_MODULE_FLAGS_FIXEDADDR, +}; + +// PPC +enum { + SYS_PRX_R_PPC_ADDR32 = 1, + SYS_PRX_R_PPC_ADDR16_LO = 4, + SYS_PRX_R_PPC_ADDR16_HI = 5, + SYS_PRX_R_PPC_ADDR16_HA = 6, + + SYS_PRX_R_PPC64_ADDR32 = SYS_PRX_R_PPC_ADDR32, + SYS_PRX_R_PPC64_ADDR16_LO = SYS_PRX_R_PPC_ADDR16_LO, + SYS_PRX_R_PPC64_ADDR16_HI = SYS_PRX_R_PPC_ADDR16_HI, + SYS_PRX_R_PPC64_ADDR16_HA = SYS_PRX_R_PPC_ADDR16_HA, + SYS_PRX_R_PPC64_ADDR64 = 38, + SYS_PRX_VARLINK_TERMINATE32 = 0x00000000 +}; + +// SysCalls + +error_code sys_prx_get_ppu_guid(ppu_thread &ppu); +error_code +_sys_prx_load_module_by_fd(ppu_thread &ppu, s32 fd, u64 offset, u64 flags, + vm::ptr pOpt); +error_code _sys_prx_load_module_on_memcontainer_by_fd( + ppu_thread &ppu, s32 fd, u64 offset, u32 mem_ct, u64 flags, + vm::ptr pOpt); +error_code _sys_prx_load_module_list(ppu_thread &ppu, s32 count, + vm::cpptr path_list, + u64 flags, + vm::ptr pOpt, + vm::ptr id_list); +error_code _sys_prx_load_module_list_on_memcontainer( + ppu_thread &ppu, s32 count, vm::cpptr path_list, u32 mem_ct, + u64 flags, vm::ptr pOpt, + vm::ptr id_list); +error_code _sys_prx_load_module_on_memcontainer( + ppu_thread &ppu, vm::cptr path, u32 mem_ct, u64 flags, + vm::ptr pOpt); +error_code _sys_prx_load_module(ppu_thread &ppu, vm::cptr path, u64 flags, + vm::ptr pOpt); +error_code +_sys_prx_start_module(ppu_thread &ppu, u32 id, u64 flags, + vm::ptr pOpt); +error_code +_sys_prx_stop_module(ppu_thread &ppu, u32 id, u64 flags, + vm::ptr pOpt); +error_code _sys_prx_unload_module(ppu_thread &ppu, u32 id, u64 flags, + vm::ptr pOpt); +error_code _sys_prx_register_module(ppu_thread &ppu, vm::cptr name, + vm::ptr opt); +error_code _sys_prx_query_module(ppu_thread &ppu); +error_code _sys_prx_register_library(ppu_thread &ppu, vm::ptr library); +error_code _sys_prx_unregister_library(ppu_thread &ppu, vm::ptr library); +error_code _sys_prx_link_library(ppu_thread &ppu); +error_code _sys_prx_unlink_library(ppu_thread &ppu); +error_code _sys_prx_query_library(ppu_thread &ppu); +error_code +_sys_prx_get_module_list(ppu_thread &ppu, u64 flags, + vm::ptr pInfo); +error_code _sys_prx_get_module_info(ppu_thread &ppu, u32 id, u64 flags, + vm::ptr pOpt); +error_code _sys_prx_get_module_id_by_name( + ppu_thread &ppu, vm::cptr name, u64 flags, + vm::ptr pOpt); +error_code _sys_prx_get_module_id_by_address(ppu_thread &ppu, u32 addr); +error_code _sys_prx_start(ppu_thread &ppu); +error_code _sys_prx_stop(ppu_thread &ppu); diff --git a/kernel/cellos/include/cellos/sys_rsx.h b/kernel/cellos/include/cellos/sys_rsx.h new file mode 100644 index 000000000..9783c20a6 --- /dev/null +++ b/kernel/cellos/include/cellos/sys_rsx.h @@ -0,0 +1,144 @@ +#pragma once + +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/Memory/vm_ptr.h" + +class cpu_thread; + +struct RsxDriverInfo { + be_t version_driver; // 0x0 + be_t version_gpu; // 0x4 + be_t memory_size; // 0x8 + be_t hardware_channel; // 0xC + be_t nvcore_frequency; // 0x10 + be_t memory_frequency; // 0x14 + be_t unk1[4]; // 0x18 - 0x24 + be_t unk2; // 0x28 -- pgraph stuff + be_t reportsNotifyOffset; // 0x2C offset to notify memory + be_t reportsOffset; // 0x30 offset to reports memory + be_t reportsReportOffset; // 0x34 offset to reports in reports memory + be_t unk3[6]; // 0x38-0x54 + be_t systemModeFlags; // 0x54 + u8 unk4[0x1064]; // 0x10B8 + + struct Head { + be_t lastFlipTime; // 0x0 last flip time + atomic_be_t flipFlags; // 0x8 flags to handle flip/queue + be_t offset; // 0xC + be_t flipBufferId; // 0x10 + be_t lastQueuedBufferId; // 0x14 todo: this is definately not this + // variable but its 'unused' so im using it + // for queueId to pass to flip handler + be_t unk3; // 0x18 + be_t + lastVTimeLow; // 0x1C last time for first vhandler freq (low 32-bits) + atomic_be_t lastSecondVTime; // 0x20 last time for second vhandler freq + be_t unk4; // 0x28 + atomic_be_t vBlankCount; // 0x30 + be_t unk; // 0x38 possible u32, 'flip field', top/bottom for interlaced + be_t + lastVTimeHigh; // 0x3C last time for first vhandler freq (high 32-bits) + } head[8]; // size = 0x40, 0x200 + + be_t unk7; // 0x12B8 + be_t unk8; // 0x12BC + atomic_be_t handlers; // 0x12C0 -- flags showing which handlers are set + be_t unk9; // 0x12C4 + be_t unk10; // 0x12C8 + be_t userCmdParam; // 0x12CC + be_t handler_queue; // 0x12D0 + be_t unk11; // 0x12D4 + be_t unk12; // 0x12D8 + be_t unk13; // 0x12DC + be_t unk14; // 0x12E0 + be_t unk15; // 0x12E4 + be_t unk16; // 0x12E8 + be_t unk17; // 0x12F0 + be_t lastError; // 0x12F4 error param for cellGcmSetGraphicsHandler + // todo: theres more to this +}; + +static_assert(sizeof(RsxDriverInfo) == 0x12F8, "rsxSizeTest"); +static_assert(sizeof(RsxDriverInfo::Head) == 0x40, "rsxHeadSizeTest"); + +enum : u64 { + // Unused + SYS_RSX_IO_MAP_IS_STRICT = 1ull << 60 +}; + +// Unofficial event names +enum : u64 { + // SYS_RSX_EVENT_GRAPHICS_ERROR = 1 << 0, + SYS_RSX_EVENT_VBLANK = 1 << 1, + SYS_RSX_EVENT_FLIP_BASE = 1 << 3, + SYS_RSX_EVENT_QUEUE_BASE = 1 << 5, + SYS_RSX_EVENT_USER_CMD = 1 << 7, + SYS_RSX_EVENT_SECOND_VBLANK_BASE = 1 << 10, + SYS_RSX_EVENT_UNMAPPED_BASE = 1ull << 32, +}; + +struct RsxDmaControl { + u8 resv[0x40]; + atomic_be_t put; + atomic_be_t get; + atomic_be_t ref; + be_t unk[2]; + be_t unk1; +}; + +struct RsxSemaphore { + atomic_be_t val; +}; + +struct alignas(16) RsxNotify { + be_t timestamp; + be_t zero; +}; + +struct alignas(16) RsxReport { + be_t timestamp; + be_t val; + be_t pad; +}; + +struct RsxReports { + RsxSemaphore semaphore[1024]; + RsxNotify notify[64]; + RsxReport report[2048]; +}; + +struct RsxDisplayInfo { + be_t offset{0}; + be_t pitch{0}; + be_t width{0}; + be_t height{0}; + + ENABLE_BITWISE_SERIALIZATION; + + bool valid() const { return height != 0u && width != 0u; } +}; + +// SysCalls +error_code sys_rsx_device_open(cpu_thread &cpu); +error_code sys_rsx_device_close(cpu_thread &cpu); +error_code sys_rsx_memory_allocate(cpu_thread &cpu, vm::ptr mem_handle, + vm::ptr mem_addr, u32 size, u64 flags, + u64 a5, u64 a6, u64 a7); +error_code sys_rsx_memory_free(cpu_thread &cpu, u32 mem_handle); +error_code sys_rsx_context_allocate(cpu_thread &cpu, vm::ptr context_id, + vm::ptr lpar_dma_control, + vm::ptr lpar_driver_info, + vm::ptr lpar_reports, u64 mem_ctx, + u64 system_mode); +error_code sys_rsx_context_free(ppu_thread &ppu, u32 context_id); +error_code sys_rsx_context_iomap(cpu_thread &cpu, u32 context_id, u32 io, + u32 ea, u32 size, u64 flags); +error_code sys_rsx_context_iounmap(cpu_thread &cpu, u32 context_id, u32 io, + u32 size); +error_code sys_rsx_context_attribute(u32 context_id, u32 package_id, u64 a3, + u64 a4, u64 a5, u64 a6); +error_code sys_rsx_device_map(cpu_thread &cpu, vm::ptr dev_addr, + vm::ptr a2, u32 dev_id); +error_code sys_rsx_device_unmap(cpu_thread &cpu, u32 dev_id); +error_code sys_rsx_attribute(cpu_thread &cpu, u32 packageId, u32 a2, u32 a3, + u32 a4, u32 a5); diff --git a/kernel/cellos/include/cellos/sys_rsxaudio.h b/kernel/cellos/include/cellos/sys_rsxaudio.h new file mode 100644 index 000000000..252549cb2 --- /dev/null +++ b/kernel/cellos/include/cellos/sys_rsxaudio.h @@ -0,0 +1,629 @@ +#pragma once + +#include "Emu/Audio/AudioBackend.h" +#include "Emu/Audio/AudioDumper.h" +#include "Emu/Audio/audio_resampler.h" +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/Memory/vm_ptr.h" +#include "Emu/system_config_types.h" +#include "sys_event.h" +#include "sys_sync.h" +#include "util/cond.h" +#include "util/simple_ringbuf.h" +#include "util/transactional_storage.h" + +#if defined(unix) || defined(__unix) || defined(__unix__) +// For BSD detection +#include +#endif + +#ifdef _WIN32 +#include +#elif defined(BSD) || defined(__APPLE__) +#include +#endif + +enum : u32 { + SYS_RSXAUDIO_SERIAL_STREAM_CNT = 4, + SYS_RSXAUDIO_STREAM_DATA_BLK_CNT = 4, + SYS_RSXAUDIO_DATA_BLK_SIZE = 256, + SYS_RSXAUDIO_STREAM_SIZE = + SYS_RSXAUDIO_DATA_BLK_SIZE * SYS_RSXAUDIO_STREAM_DATA_BLK_CNT, + SYS_RSXAUDIO_CH_PER_STREAM = 2, + SYS_RSXAUDIO_SERIAL_MAX_CH = 8, + SYS_RSXAUDIO_SPDIF_MAX_CH = 2, + SYS_RSXAUDIO_STREAM_SAMPLE_CNT = + SYS_RSXAUDIO_STREAM_SIZE / SYS_RSXAUDIO_CH_PER_STREAM / sizeof(f32), + + SYS_RSXAUDIO_RINGBUF_BLK_SZ_SERIAL = + SYS_RSXAUDIO_STREAM_SIZE * SYS_RSXAUDIO_SERIAL_STREAM_CNT, + SYS_RSXAUDIO_RINGBUF_BLK_SZ_SPDIF = SYS_RSXAUDIO_STREAM_SIZE, + + SYS_RSXAUDIO_RINGBUF_SZ = 16, + + SYS_RSXAUDIO_AVPORT_CNT = 5, + + SYS_RSXAUDIO_FREQ_BASE_384K = 384000, + SYS_RSXAUDIO_FREQ_BASE_352K = 352800, + + SYS_RSXAUDIO_PORT_CNT = 3, + + SYS_RSXAUDIO_SPDIF_CNT = 2, +}; + +enum class RsxaudioAvportIdx : u8 { + HDMI_0 = 0, + HDMI_1 = 1, + AVMULTI = 2, + SPDIF_0 = 3, + SPDIF_1 = 4, +}; + +enum class RsxaudioPort : u8 { + SERIAL = 0, + SPDIF_0 = 1, + SPDIF_1 = 2, + INVALID = 0xFF, +}; + +enum class RsxaudioSampleSize : u8 { + _16BIT = 2, + _32BIT = 4, +}; + +struct rsxaudio_shmem { + struct ringbuf_t { + struct entry_t { + be_t valid{}; + be_t unk1{}; + be_t audio_blk_idx{}; + be_t timestamp{}; + be_t buf_addr{}; + be_t dma_addr{}; + }; + + be_t active{}; + be_t unk2{}; + be_t read_idx{}; + be_t write_idx{}; + be_t rw_max_idx{}; + be_t queue_notify_idx{}; + be_t queue_notify_step{}; + be_t unk6{}; + be_t dma_silence_addr{}; + be_t unk7{}; + be_t next_blk_idx{}; + + entry_t entries[16]{}; + }; + + struct uf_event_t { + be_t unk1{}; + be_t uf_event_cnt{}; + u8 unk2[244]{}; + }; + + struct ctrl_t { + ringbuf_t ringbuf[SYS_RSXAUDIO_PORT_CNT]{}; + + be_t unk1{}; + be_t event_queue_1_id{}; + u8 unk2[16]{}; + be_t event_queue_2_id{}; + be_t spdif_ch0_channel_data_lo{}; + be_t spdif_ch0_channel_data_hi{}; + be_t spdif_ch0_channel_data_tx_cycles{}; + be_t unk3{}; + be_t event_queue_3_id{}; + be_t spdif_ch1_channel_data_lo{}; + be_t spdif_ch1_channel_data_hi{}; + be_t spdif_ch1_channel_data_tx_cycles{}; + be_t unk4{}; + be_t intr_thread_prio{}; + be_t unk5{}; + u8 unk6[248]{}; + uf_event_t channel_uf[SYS_RSXAUDIO_PORT_CNT]{}; + u8 pad[0x3530]{}; + }; + + u8 dma_serial_region[0x10000]{}; + u8 dma_spdif_0_region[0x4000]{}; + u8 dma_spdif_1_region[0x4000]{}; + u8 dma_silence_region[0x4000]{}; + ctrl_t ctrl{}; +}; + +static_assert(sizeof(rsxaudio_shmem::ringbuf_t) == 0x230U, + "rsxAudioRingBufSizeTest"); +static_assert(sizeof(rsxaudio_shmem::uf_event_t) == 0x100U, + "rsxAudioUfEventTest"); +static_assert(sizeof(rsxaudio_shmem::ctrl_t) == 0x4000U, + "rsxAudioCtrlSizeTest"); +static_assert(sizeof(rsxaudio_shmem) == 0x20000U, "rsxAudioShmemSizeTest"); + +enum rsxaudio_dma_flag : u32 { IO_BASE = 0, IO_ID = 1 }; + +struct lv2_rsxaudio final : lv2_obj { + static constexpr u32 id_base = 0x60000000; + static constexpr u64 dma_io_id = 1; + static constexpr u32 dma_io_base = 0x30000000; + + shared_mutex mutex{}; + bool init = false; + + vm::addr_t shmem{}; + + std::array, SYS_RSXAUDIO_PORT_CNT> event_queue{}; + + // lv2 uses port memory addresses for their names + static constexpr std::array event_port_name{ + 0x8000000000400100, 0x8000000000400200, 0x8000000000400300}; + + lv2_rsxaudio() noexcept = default; + lv2_rsxaudio(utils::serial &ar) noexcept; + void save(utils::serial &ar); + + void page_lock() { + ensure(shmem && vm::page_protect(shmem, sizeof(rsxaudio_shmem), 0, 0, + vm::page_readable | vm::page_writable | + vm::page_executable)); + } + + void page_unlock() { + ensure(shmem && vm::page_protect(shmem, sizeof(rsxaudio_shmem), 0, + vm::page_readable | vm::page_writable)); + } + + rsxaudio_shmem *get_rw_shared_page() const { + return reinterpret_cast(vm::g_sudo_addr + u32{shmem}); + } +}; + +class rsxaudio_periodic_tmr { +public: + enum class wait_result { + SUCCESS, + INVALID_PARAM, + TIMEOUT, + TIMER_ERROR, + TIMER_CANCELED, + }; + + rsxaudio_periodic_tmr(); + ~rsxaudio_periodic_tmr(); + + rsxaudio_periodic_tmr(const rsxaudio_periodic_tmr &) = delete; + rsxaudio_periodic_tmr &operator=(const rsxaudio_periodic_tmr &) = delete; + + // Wait until timer fires and calls callback. + wait_result wait(const std::function &callback); + + // Cancel wait() call + void cancel_wait(); + + // VTimer funtions + + void vtimer_access_sec(std::invocable<> auto func) { + std::lock_guard lock(mutex); + std::invoke(func); + + // Adjust timer expiration + cancel_timer_unlocked(); + sched_timer(); + } + + void enable_vtimer(u32 vtimer_id, u32 rate, u64 crnt_time); + + void disable_vtimer(u32 vtimer_id); + + bool is_vtimer_behind(u32 vtimer_id, u64 crnt_time) const; + + void vtimer_skip_periods(u32 vtimer_id, u64 crnt_time); + + void vtimer_incr(u32 vtimer_id, u64 crnt_time); + + bool is_vtimer_active(u32 vtimer_id) const; + + u64 vtimer_get_sched_time(u32 vtimer_id) const; + +private: + static constexpr u64 MAX_BURST_PERIODS = SYS_RSXAUDIO_RINGBUF_SZ; + static constexpr u32 VTIMER_MAX = 4; + + struct vtimer { + u64 blk_cnt = 0; + f64 blk_time = 0.0; + bool active = false; + }; + + std::array vtmr_pool{}; + + shared_mutex mutex{}; + bool in_wait = false; + bool zero_period = false; + +#if defined(_WIN32) + HANDLE cancel_event{}; + HANDLE timer_handle{}; +#elif defined(__linux__) + int cancel_event{}; + int timer_handle{}; + int epoll_fd{}; +#elif defined(BSD) || defined(__APPLE__) + static constexpr u64 TIMER_ID = 0; + static constexpr u64 CANCEL_ID = 1; + + int kq{}; + struct kevent handle[2]{}; +#else +#error "Implement" +#endif + + void sched_timer(); + void cancel_timer_unlocked(); + void reset_cancel_flag(); + + bool is_vtimer_behind(const vtimer &vtimer, u64 crnt_time) const; + + u64 get_crnt_blk(u64 crnt_time, f64 blk_time) const; + f64 get_blk_time(u32 data_rate) const; + + u64 get_rel_next_time(); +}; + +struct rsxaudio_hw_param_t { + struct serial_param_t { + bool dma_en = false; + bool buf_empty_en = false; + bool muted = true; + bool en = false; + u8 freq_div = 8; + RsxaudioSampleSize depth = RsxaudioSampleSize::_16BIT; + }; + + struct spdif_param_t { + bool dma_en = false; + bool buf_empty_en = false; + bool muted = true; + bool en = false; + bool use_serial_buf = true; + u8 freq_div = 8; + RsxaudioSampleSize depth = RsxaudioSampleSize::_16BIT; + std::array cs_data = { + 0x00, 0x90, 0x00, + 0x40, 0x80, 0x00}; // HW supports only 6 bytes (uart pkt has 8) + }; + + struct hdmi_param_t { + struct hdmi_ch_cfg_t { + std::array map{}; + AudioChannelCnt total_ch_cnt = AudioChannelCnt::STEREO; + }; + + static constexpr u8 MAP_SILENT_CH = umax; + + bool init = false; + hdmi_ch_cfg_t ch_cfg{}; + std::array info_frame{}; // TODO: check chstat and info_frame for + // info on audio layout, add default values + std::array chstat{}; + + bool muted = true; + bool force_mute = true; + bool use_spdif_1 = false; // TODO: unused for now + }; + + u32 serial_freq_base = SYS_RSXAUDIO_FREQ_BASE_384K; + u32 spdif_freq_base = SYS_RSXAUDIO_FREQ_BASE_352K; + + bool avmulti_av_muted = true; + + serial_param_t serial{}; + spdif_param_t spdif[2]{}; + hdmi_param_t hdmi[2]{}; + + std::array avport_src = { + RsxaudioPort::INVALID, RsxaudioPort::INVALID, RsxaudioPort::INVALID, + RsxaudioPort::INVALID, RsxaudioPort::INVALID}; +}; + +// 16-bit PCM converted into float, so buffer must be twice as big +using ra_stream_blk_t = std::array; + +class rsxaudio_data_container { +public: + struct buf_t { + std::array serial{}; + std::array + spdif[SYS_RSXAUDIO_SPDIF_CNT]{}; + }; + + using data_blk_t = std::array; + + rsxaudio_data_container(const rsxaudio_hw_param_t &hw_param, const buf_t &buf, + bool serial_rdy, bool spdif_0_rdy, bool spdif_1_rdy); + u32 get_data_size(RsxaudioAvportIdx avport); + void get_data(RsxaudioAvportIdx avport, data_blk_t &data_out); + bool data_was_used(); + +private: + const rsxaudio_hw_param_t &hwp; + const buf_t &out_buf; + + std::array avport_data_avail{}; + u8 hdmi_stream_cnt[2]{}; + bool data_was_written = false; + + rsxaudio_data_container(const rsxaudio_data_container &) = delete; + rsxaudio_data_container &operator=(const rsxaudio_data_container &) = delete; + + rsxaudio_data_container(rsxaudio_data_container &&) = delete; + rsxaudio_data_container &operator=(rsxaudio_data_container &&) = delete; + + // Mix individual channels into final PCM stream. Channels in channel map that + // are > input_ch_cnt treated as silent. + template + requires(output_ch_cnt > 0 && output_ch_cnt <= 8 && input_ch_cnt > 0) + constexpr void + mix(const std::array &ch_map, RsxaudioSampleSize sample_size, + const std::array &input_channels, + data_blk_t &data_out) { + const ra_stream_blk_t silent_channel{}; + + // Build final map + std::array real_input_ch = {}; + for (u64 ch_idx = 0; ch_idx < output_ch_cnt; ch_idx++) { + if (ch_map[ch_idx] >= input_ch_cnt) { + real_input_ch[ch_idx] = &silent_channel; + } else { + real_input_ch[ch_idx] = &input_channels[ch_map[ch_idx]]; + } + } + + const u32 samples_in_buf = sample_size == RsxaudioSampleSize::_16BIT + ? SYS_RSXAUDIO_STREAM_SAMPLE_CNT * 2 + : SYS_RSXAUDIO_STREAM_SAMPLE_CNT; + + for (u32 sample_idx = 0; sample_idx < samples_in_buf * output_ch_cnt; + sample_idx += output_ch_cnt) { + const u32 src_sample_idx = sample_idx / output_ch_cnt; + + if constexpr (output_ch_cnt >= 1) + data_out[sample_idx + 0] = (*real_input_ch[0])[src_sample_idx]; + if constexpr (output_ch_cnt >= 2) + data_out[sample_idx + 1] = (*real_input_ch[1])[src_sample_idx]; + if constexpr (output_ch_cnt >= 3) + data_out[sample_idx + 2] = (*real_input_ch[2])[src_sample_idx]; + if constexpr (output_ch_cnt >= 4) + data_out[sample_idx + 3] = (*real_input_ch[3])[src_sample_idx]; + if constexpr (output_ch_cnt >= 5) + data_out[sample_idx + 4] = (*real_input_ch[4])[src_sample_idx]; + if constexpr (output_ch_cnt >= 6) + data_out[sample_idx + 5] = (*real_input_ch[5])[src_sample_idx]; + if constexpr (output_ch_cnt >= 7) + data_out[sample_idx + 6] = (*real_input_ch[6])[src_sample_idx]; + if constexpr (output_ch_cnt >= 8) + data_out[sample_idx + 7] = (*real_input_ch[7])[src_sample_idx]; + } + } +}; + +namespace audio { +void configure_rsxaudio(); +} + +class rsxaudio_backend_thread { +public: + struct port_config { + AudioFreq freq = AudioFreq::FREQ_48K; + AudioChannelCnt ch_cnt = AudioChannelCnt::STEREO; + + auto operator<=>(const port_config &) const = default; + }; + + struct avport_bit { + bool hdmi_0 : 1; + bool hdmi_1 : 1; + bool avmulti : 1; + bool spdif_0 : 1; + bool spdif_1 : 1; + }; + + rsxaudio_backend_thread(); + ~rsxaudio_backend_thread(); + + void operator()(); + rsxaudio_backend_thread &operator=(thread_state state); + + void set_new_stream_param( + const std::array &cfg, + avport_bit muted_avports); + void set_mute_state(avport_bit muted_avports); + void add_data(rsxaudio_data_container &cont); + + void update_emu_cfg(); + + u32 get_sample_rate() const; + u8 get_channel_count() const; + + static constexpr auto thread_name = "RsxAudio Backend Thread"sv; + + SAVESTATE_INIT_POS(8.91); // Depends on audio_out_configuration + +private: + struct emu_audio_cfg { + std::string audio_device{}; + s64 desired_buffer_duration = 0; + f64 time_stretching_threshold = 0; + bool buffering_enabled = false; + bool convert_to_s16 = false; + bool enable_time_stretching = false; + bool dump_to_file = false; + AudioChannelCnt channels = AudioChannelCnt::STEREO; + audio_channel_layout channel_layout = audio_channel_layout::automatic; + audio_renderer renderer = audio_renderer::null; + audio_provider provider = audio_provider::none; + RsxaudioAvportIdx avport = RsxaudioAvportIdx::HDMI_0; + + auto operator<=>(const emu_audio_cfg &) const = default; + }; + + struct rsxaudio_state { + std::array port{}; + }; + + struct alignas(16) callback_config { + static constexpr u16 VOL_NOMINAL = 10000; + static constexpr f32 VOL_NOMINAL_INV = 1.0f / VOL_NOMINAL; + + u32 freq : 20 = 48000; + + u16 target_volume = 10000; + u16 initial_volume = 10000; + u16 current_volume = 10000; + + RsxaudioAvportIdx avport_idx = RsxaudioAvportIdx::HDMI_0; + u8 mute_state : SYS_RSXAUDIO_AVPORT_CNT = 0b11111; + + u8 input_ch_cnt : 4 = 2; + u8 output_channel_layout : 4 = + static_cast(audio_channel_layout::stereo); + + bool ready : 1 = false; + bool convert_to_s16 : 1 = false; + bool cfg_changed : 1 = false; + bool callback_active : 1 = false; + }; + + static_assert(sizeof(callback_config) <= 16); + + struct backend_config { + port_config cfg{}; + RsxaudioAvportIdx avport = RsxaudioAvportIdx::HDMI_0; + }; + + static constexpr u64 ERROR_SERVICE_PERIOD = 500'000; + static constexpr u64 SERVICE_PERIOD = 10'000; + static constexpr f64 SERVICE_PERIOD_SEC = SERVICE_PERIOD / 1'000'000.0; + static constexpr u64 SERVICE_THRESHOLD = 1'500; + + static constexpr f64 TIME_STRETCHING_STEP = 0.1f; + + u64 start_time = get_system_time(); + u64 time_period_idx = 1; + + emu_audio_cfg new_emu_cfg{}; + bool emu_cfg_changed = true; + + rsxaudio_state new_ra_state{}; + bool ra_state_changed = true; + + shared_mutex state_update_m{}; + cond_variable state_update_c{}; + + simple_ringbuf ringbuf{}; + simple_ringbuf aux_ringbuf{}; + std::vector thread_tmp_buf{}; + std::vector callback_tmp_buf{}; + bool use_aux_ringbuf = false; + shared_mutex ringbuf_mutex{}; + + std::shared_ptr backend{}; + backend_config backend_current_cfg{{}, new_emu_cfg.avport}; + atomic_t callback_cfg{}; + bool backend_error_occured = false; + bool backend_device_changed = false; + + AudioDumper dumper{}; + audio_resampler resampler{}; + + // Backend + void backend_init(const rsxaudio_state &ra_state, + const emu_audio_cfg &emu_cfg, bool reset_backend = true); + void backend_start(); + void backend_stop(); + bool backend_playing(); + u32 write_data_callback(u32 bytes, void *buf); + void state_changed_callback(AudioStateEvent event); + + // Time management + u64 get_time_until_service(); + void update_service_time(); + void reset_service_time(); + + // Helpers + static emu_audio_cfg get_emu_cfg(); + static u8 gen_mute_state(avport_bit avports); + static RsxaudioAvportIdx convert_avport(audio_avport avport); +}; + +class rsxaudio_data_thread { +public: + // Prevent creation of multiple rsxaudio contexts + atomic_t rsxaudio_ctx_allocated = false; + + shared_mutex rsxaudio_obj_upd_m{}; + shared_ptr rsxaudio_obj_ptr{}; + + void operator()(); + rsxaudio_data_thread &operator=(thread_state state); + + rsxaudio_data_thread(); + + void + update_hw_param(std::function update_callback); + void update_mute_state(RsxaudioPort port, bool muted); + void update_av_mute_state(RsxaudioAvportIdx avport, bool muted, + bool force_mute, bool set = true); + void reset_hw(); + + static constexpr auto thread_name = "RsxAudioData Thread"sv; + +private: + rsxaudio_data_container::buf_t output_buf{}; + + transactional_storage hw_param_ts{ + std::make_shared(), + std::make_shared()}; + rsxaudio_periodic_tmr timer{}; + + void advance_all_timers(); + void extract_audio_data(); + static std::pair + get_ringbuf_addr(RsxaudioPort dst, const lv2_rsxaudio &rsxaudio_obj); + + static f32 pcm_to_float(s32 sample); + static f32 pcm_to_float(s16 sample); + static void pcm_serial_process_channel(RsxaudioSampleSize word_bits, + ra_stream_blk_t &buf_out_l, + ra_stream_blk_t &buf_out_r, + const void *buf_in, u8 src_stream); + static void pcm_spdif_process_channel(RsxaudioSampleSize word_bits, + ra_stream_blk_t &buf_out_l, + ra_stream_blk_t &buf_out_r, + const void *buf_in); + bool enqueue_data(RsxaudioPort dst, bool silence, const void *src_addr, + const rsxaudio_hw_param_t &hwp); + + static rsxaudio_backend_thread::avport_bit + calc_avport_mute_state(const rsxaudio_hw_param_t &hwp); + static bool calc_port_active_state(RsxaudioPort port, + const rsxaudio_hw_param_t &hwp); +}; + +using rsx_audio_backend = named_thread; +using rsx_audio_data = named_thread; + +// SysCalls + +error_code sys_rsxaudio_initialize(vm::ptr handle); +error_code sys_rsxaudio_finalize(u32 handle); +error_code sys_rsxaudio_import_shared_memory(u32 handle, vm::ptr addr); +error_code sys_rsxaudio_unimport_shared_memory(u32 handle, vm::ptr addr); +error_code sys_rsxaudio_create_connection(u32 handle); +error_code sys_rsxaudio_close_connection(u32 handle); +error_code sys_rsxaudio_prepare_process(u32 handle); +error_code sys_rsxaudio_start_process(u32 handle); +error_code sys_rsxaudio_stop_process(u32 handle); +error_code sys_rsxaudio_get_dma_param(u32 handle, u32 flag, vm::ptr out); diff --git a/kernel/cellos/include/cellos/sys_rwlock.h b/kernel/cellos/include/cellos/sys_rwlock.h new file mode 100644 index 000000000..8224b52e3 --- /dev/null +++ b/kernel/cellos/include/cellos/sys_rwlock.h @@ -0,0 +1,55 @@ +#pragma once + +#include "sys_sync.h" + +#include "Emu/Memory/vm_ptr.h" + +struct sys_rwlock_attribute_t { + be_t protocol; + be_t pshared; + be_t ipc_key; + be_t flags; + be_t pad; + + union { + nse_t name_u64; + char name[sizeof(u64)]; + }; +}; + +struct lv2_rwlock final : lv2_obj { + static const u32 id_base = 0x88000000; + + const lv2_protocol protocol; + const u64 key; + const u64 name; + + shared_mutex mutex; + atomic_t owner{0}; + ppu_thread *rq{}; + ppu_thread *wq{}; + + lv2_rwlock(u32 protocol, u64 key, u64 name) noexcept + : protocol{static_cast(protocol)}, key(key), name(name) {} + + lv2_rwlock(utils::serial &ar); + static std::function load(utils::serial &ar); + void save(utils::serial &ar); +}; + +// Aux +class ppu_thread; + +// Syscalls + +error_code sys_rwlock_create(ppu_thread &ppu, vm::ptr rw_lock_id, + vm::ptr attr); +error_code sys_rwlock_destroy(ppu_thread &ppu, u32 rw_lock_id); +error_code sys_rwlock_rlock(ppu_thread &ppu, u32 rw_lock_id, u64 timeout); +error_code sys_rwlock_tryrlock(ppu_thread &ppu, u32 rw_lock_id); +error_code sys_rwlock_runlock(ppu_thread &ppu, u32 rw_lock_id); +error_code sys_rwlock_wlock(ppu_thread &ppu, u32 rw_lock_id, u64 timeout); +error_code sys_rwlock_trywlock(ppu_thread &ppu, u32 rw_lock_id); +error_code sys_rwlock_wunlock(ppu_thread &ppu, u32 rw_lock_id); + +constexpr auto _sys_rwlock_trywlock = sys_rwlock_trywlock; diff --git a/kernel/cellos/include/cellos/sys_semaphore.h b/kernel/cellos/include/cellos/sys_semaphore.h new file mode 100644 index 000000000..d66b94009 --- /dev/null +++ b/kernel/cellos/include/cellos/sys_semaphore.h @@ -0,0 +1,54 @@ +#pragma once + +#include "sys_sync.h" + +#include "Emu/Memory/vm_ptr.h" + +struct sys_semaphore_attribute_t { + be_t protocol; + be_t pshared; + be_t ipc_key; + be_t flags; + be_t pad; + + union { + nse_t name_u64; + char name[sizeof(u64)]; + }; +}; + +struct lv2_sema final : lv2_obj { + static const u32 id_base = 0x96000000; + + const lv2_protocol protocol; + const u64 key; + const u64 name; + const s32 max; + + shared_mutex mutex; + atomic_t val; + ppu_thread *sq{}; + + lv2_sema(u32 protocol, u64 key, u64 name, s32 max, s32 value) noexcept + : protocol{static_cast(protocol)}, key(key), name(name), max(max), + val(value) {} + + lv2_sema(utils::serial &ar); + static std::function load(utils::serial &ar); + void save(utils::serial &ar); +}; + +// Aux +class ppu_thread; + +// Syscalls + +error_code sys_semaphore_create(ppu_thread &ppu, vm::ptr sem_id, + vm::ptr attr, + s32 initial_val, s32 max_val); +error_code sys_semaphore_destroy(ppu_thread &ppu, u32 sem_id); +error_code sys_semaphore_wait(ppu_thread &ppu, u32 sem_id, u64 timeout); +error_code sys_semaphore_trywait(ppu_thread &ppu, u32 sem_id); +error_code sys_semaphore_post(ppu_thread &ppu, u32 sem_id, s32 count); +error_code sys_semaphore_get_value(ppu_thread &ppu, u32 sem_id, + vm::ptr count); diff --git a/rpcs3/Emu/Cell/lv2/sys_sm.h b/kernel/cellos/include/cellos/sys_sm.h similarity index 65% rename from rpcs3/Emu/Cell/lv2/sys_sm.h rename to kernel/cellos/include/cellos/sys_sm.h index 9e42e8f37..1f8091fe0 100644 --- a/rpcs3/Emu/Cell/lv2/sys_sm.h +++ b/kernel/cellos/include/cellos/sys_sm.h @@ -1,13 +1,16 @@ #pragma once -#include "Emu/Memory/vm_ptr.h" #include "Emu/Cell/ErrorCodes.h" +#include "Emu/Memory/vm_ptr.h" // SysCalls -error_code sys_sm_get_ext_event2(vm::ptr a1, vm::ptr a2, vm::ptr a3, u64 a4); -error_code sys_sm_shutdown(ppu_thread& ppu, u16 op, vm::ptr param, u64 size); -error_code sys_sm_get_params(vm::ptr a, vm::ptr b, vm::ptr c, vm::ptr d); +error_code sys_sm_get_ext_event2(vm::ptr a1, vm::ptr a2, + vm::ptr a3, u64 a4); +error_code sys_sm_shutdown(ppu_thread &ppu, u16 op, vm::ptr param, + u64 size); +error_code sys_sm_get_params(vm::ptr a, vm::ptr b, vm::ptr c, + vm::ptr d); error_code sys_sm_set_shop_mode(s32 mode); error_code sys_sm_control_led(u8 led, u8 action); error_code sys_sm_ring_buzzer(u64 packet, u64 a1, u64 a2); diff --git a/kernel/cellos/include/cellos/sys_spu.h b/kernel/cellos/include/cellos/sys_spu.h new file mode 100644 index 000000000..04ec71113 --- /dev/null +++ b/kernel/cellos/include/cellos/sys_spu.h @@ -0,0 +1,426 @@ +#pragma once + +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/Cell/SPUThread.h" +#include "sys_event.h" +#include "sys_sync.h" + +#include "Emu/Memory/vm_ptr.h" +#include "util/File.h" + +#include + +struct lv2_memory_container; + +enum : s32 { + SYS_SPU_THREAD_GROUP_TYPE_NORMAL = 0x00, + // SYS_SPU_THREAD_GROUP_TYPE_SEQUENTIAL = 0x01, doesn't exist + SYS_SPU_THREAD_GROUP_TYPE_SYSTEM = 0x02, + SYS_SPU_THREAD_GROUP_TYPE_MEMORY_FROM_CONTAINER = 0x04, + SYS_SPU_THREAD_GROUP_TYPE_NON_CONTEXT = 0x08, + SYS_SPU_THREAD_GROUP_TYPE_EXCLUSIVE_NON_CONTEXT = 0x18, + SYS_SPU_THREAD_GROUP_TYPE_COOPERATE_WITH_SYSTEM = 0x20, +}; + +enum { + SYS_SPU_THREAD_GROUP_JOIN_GROUP_EXIT = 0x0001, + SYS_SPU_THREAD_GROUP_JOIN_ALL_THREADS_EXIT = 0x0002, + SYS_SPU_THREAD_GROUP_JOIN_TERMINATED = 0x0004 +}; + +enum { + SYS_SPU_THREAD_GROUP_EVENT_RUN = 1, + SYS_SPU_THREAD_GROUP_EVENT_EXCEPTION = 2, + SYS_SPU_THREAD_GROUP_EVENT_SYSTEM_MODULE = 4, +}; + +enum : u64 { + SYS_SPU_THREAD_GROUP_EVENT_RUN_KEY = 0xFFFFFFFF53505500ull, + SYS_SPU_THREAD_GROUP_EVENT_EXCEPTION_KEY = 0xFFFFFFFF53505503ull, + SYS_SPU_THREAD_GROUP_EVENT_SYSTEM_MODULE_KEY = 0xFFFFFFFF53505504ull, +}; + +enum { + SYS_SPU_THREAD_GROUP_LOG_ON = 0x0, + SYS_SPU_THREAD_GROUP_LOG_OFF = 0x1, + SYS_SPU_THREAD_GROUP_LOG_GET_STATUS = 0x2, +}; + +enum spu_group_status : u32 { + SPU_THREAD_GROUP_STATUS_NOT_INITIALIZED, + SPU_THREAD_GROUP_STATUS_INITIALIZED, + SPU_THREAD_GROUP_STATUS_READY, + SPU_THREAD_GROUP_STATUS_WAITING, + SPU_THREAD_GROUP_STATUS_SUSPENDED, + SPU_THREAD_GROUP_STATUS_WAITING_AND_SUSPENDED, + SPU_THREAD_GROUP_STATUS_RUNNING, + SPU_THREAD_GROUP_STATUS_STOPPED, + SPU_THREAD_GROUP_STATUS_DESTROYED, // Internal state + SPU_THREAD_GROUP_STATUS_UNKNOWN, +}; + +enum : s32 { + SYS_SPU_SEGMENT_TYPE_COPY = 1, + SYS_SPU_SEGMENT_TYPE_FILL = 2, + SYS_SPU_SEGMENT_TYPE_INFO = 4, +}; + +enum spu_stop_syscall : u32 { + SYS_SPU_THREAD_STOP_YIELD = 0x0100, + SYS_SPU_THREAD_STOP_GROUP_EXIT = 0x0101, + SYS_SPU_THREAD_STOP_THREAD_EXIT = 0x0102, + SYS_SPU_THREAD_STOP_RECEIVE_EVENT = 0x0110, + SYS_SPU_THREAD_STOP_TRY_RECEIVE_EVENT = 0x0111, + SYS_SPU_THREAD_STOP_SWITCH_SYSTEM_MODULE = 0x0120, +}; + +struct sys_spu_thread_group_attribute { + be_t nsize; // name length including NULL terminator + vm::bcptr name; + be_t type; + be_t ct; // memory container id +}; + +enum : u32 { + SYS_SPU_THREAD_OPTION_NONE = 0, + SYS_SPU_THREAD_OPTION_ASYNC_INTR_ENABLE = 1, + SYS_SPU_THREAD_OPTION_DEC_SYNC_TB_ENABLE = 2, +}; + +struct sys_spu_thread_attribute { + vm::bcptr name; + be_t name_len; + be_t option; +}; + +struct sys_spu_thread_argument { + be_t arg1; + be_t arg2; + be_t arg3; + be_t arg4; +}; + +struct sys_spu_segment { + ENABLE_BITWISE_SERIALIZATION; + + be_t type; // copy, fill, info + be_t ls; // local storage address + be_t size; + + union { + be_t addr; // address or fill value + u64 pad; + }; +}; + +CHECK_SIZE(sys_spu_segment, 0x18); + +enum : u32 { + SYS_SPU_IMAGE_TYPE_USER = 0, + SYS_SPU_IMAGE_TYPE_KERNEL = 1, +}; + +struct sys_spu_image { + be_t type; // user, kernel + be_t entry_point; // Note: in kernel mode it's used to store id + vm::bptr segs; + be_t nsegs; + + template + static s32 get_nsegs(const Phdrs &phdrs) { + s32 num_segs = 0; + + for (const auto &phdr : phdrs) { + if (phdr.p_type != 1u && phdr.p_type != 4u) { + return -1; + } + + if (phdr.p_type == 1u && phdr.p_filesz != phdr.p_memsz && phdr.p_filesz) { + num_segs += 2; + } else if (phdr.p_type == 1u || CountInfo) { + num_segs += 1; + } + } + + return num_segs; + } + + template + static s32 fill(vm::ptr segs, s32 nsegs, const Phdrs &phdrs, + u32 src) { + s32 num_segs = 0; + + for (const auto &phdr : phdrs) { + if (phdr.p_type == 1u) { + if (phdr.p_filesz) { + if (num_segs >= nsegs) { + return -2; + } + + auto *seg = &segs[num_segs++]; + seg->type = SYS_SPU_SEGMENT_TYPE_COPY; + seg->ls = static_cast(phdr.p_vaddr); + seg->size = static_cast(phdr.p_filesz); + seg->addr = static_cast(phdr.p_offset + src); + } + + if (phdr.p_memsz > phdr.p_filesz) { + if (num_segs >= nsegs) { + return -2; + } + + auto *seg = &segs[num_segs++]; + seg->type = SYS_SPU_SEGMENT_TYPE_FILL; + seg->ls = static_cast(phdr.p_vaddr + phdr.p_filesz); + seg->size = static_cast(phdr.p_memsz - phdr.p_filesz); + seg->addr = 0; + } + } else if (WriteInfo && phdr.p_type == 4u) { + if (num_segs >= nsegs) { + return -2; + } + + auto *seg = &segs[num_segs++]; + seg->type = SYS_SPU_SEGMENT_TYPE_INFO; + seg->size = 0x20; + seg->addr = static_cast(phdr.p_offset + 0x14 + src); + } else if (phdr.p_type != 4u) { + return -1; + } + } + + return num_segs; + } + + void load(const fs::file &stream); + void free() const; + static void deploy(u8 *loc, std::span segs, + bool is_verbose = true); +}; + +enum : u32 { + SYS_SPU_IMAGE_PROTECT = 0, + SYS_SPU_IMAGE_DIRECT = 1, +}; + +struct lv2_spu_image : lv2_obj { + static const u32 id_base = 0x22000000; + + const u32 e_entry; + const vm::ptr segs; + const s32 nsegs; + + lv2_spu_image(u32 entry, vm::ptr segs, s32 nsegs) + : e_entry(entry), segs(segs), nsegs(nsegs) {} + + lv2_spu_image(utils::serial &ar); + void save(utils::serial &ar); +}; + +struct sys_spu_thread_group_syscall_253_info { + be_t deadlineMeetCounter; // From cellSpursGetInfo + be_t deadlineMissCounter; // Same + be_t timestamp; + be_t _x10[6]; +}; + +struct lv2_spu_group { + static const u32 id_base = 0x04000100; + static const u32 id_step = 0x100; + static const u32 id_count = 255; + static constexpr std::pair id_invl_range = {0, 8}; + + static_assert(spu_thread::id_count == id_count * 6 + 5); + + const std::string name; + const u32 id; + const u32 max_num; + const u32 mem_size; + const s32 type; // SPU Thread Group Type + lv2_memory_container *const ct; // Memory Container + const bool has_scheduler_context; + u32 max_run; + + shared_mutex mutex; + + atomic_t init; // Initialization Counter + atomic_t prio{}; // SPU Thread Group Priority + atomic_t run_state; // SPU Thread Group State + atomic_t exit_status; // SPU Thread Group Exit Status + atomic_t join_state; // flags used to detect exit cause and signal + atomic_t running = 0; // Number of running threads + atomic_t spurs_running = 0; + atomic_t stop_count = 0; + atomic_t wait_term_count = 0; + u32 waiter_spu_index = -1; // Index of SPU executing a waiting syscall + class ppu_thread *waiter = nullptr; + bool set_terminate = false; + + std::array>, 8> threads; // SPU Threads + std::array threads_map; // SPU Threads map based number + std::array>, 8> + imgs; // Entry points, SPU image segments + std::array, 8> args; // SPU Thread Arguments + + shared_ptr + ep_run; // port for SYS_SPU_THREAD_GROUP_EVENT_RUN events + shared_ptr + ep_exception; // TODO: SYS_SPU_THREAD_GROUP_EVENT_EXCEPTION + shared_ptr + ep_sysmodule; // TODO: SYS_SPU_THREAD_GROUP_EVENT_SYSTEM_MODULE + + lv2_spu_group(std::string name, u32 num, s32 _prio, s32 type, + lv2_memory_container *ct, bool uses_scheduler, + u32 mem_size) noexcept + : name(std::move(name)), id(idm::last_id()), max_num(num), + mem_size(mem_size), type(type), ct(ct), + has_scheduler_context(uses_scheduler), max_run(num), init(0), + run_state(SPU_THREAD_GROUP_STATUS_NOT_INITIALIZED), exit_status(0), + join_state(0), args({}) { + threads_map.fill(-1); + prio.raw().prio = _prio; + } + + SAVESTATE_INIT_POS(8); // Dependency on SPUs + + lv2_spu_group(utils::serial &ar) noexcept; + void save(utils::serial &ar); + + CellError send_run_event(u64 data1, u64 data2, u64 data3) const { + return ep_run ? ep_run->send(SYS_SPU_THREAD_GROUP_EVENT_RUN_KEY, data1, + data2, data3) + : CELL_ENOTCONN; + } + + CellError send_exception_event(u64 data1, u64 data2, u64 data3) const { + return ep_exception + ? ep_exception->send(SYS_SPU_THREAD_GROUP_EVENT_EXCEPTION_KEY, + data1, data2, data3) + : CELL_ENOTCONN; + } + + CellError send_sysmodule_event(u64 data1, u64 data2, u64 data3) const { + return ep_sysmodule ? ep_sysmodule->send( + SYS_SPU_THREAD_GROUP_EVENT_SYSTEM_MODULE_KEY, + data1, data2, data3) + : CELL_ENOTCONN; + } + + static std::pair *, shared_ptr> + get_thread(u32 id); +}; + +class ppu_thread; + +// Syscalls + +error_code sys_spu_initialize(ppu_thread &, u32 max_usable_spu, + u32 max_raw_spu); +error_code _sys_spu_image_get_information(ppu_thread &, + vm::ptr img, + vm::ptr entry_point, + vm::ptr nsegs); +error_code sys_spu_image_open(ppu_thread &, vm::ptr img, + vm::cptr path); +error_code _sys_spu_image_import(ppu_thread &, vm::ptr img, + u32 src, u32 size, u32 arg4); +error_code _sys_spu_image_close(ppu_thread &, vm::ptr img); +error_code _sys_spu_image_get_segments(ppu_thread &, vm::ptr img, + vm::ptr segments, + s32 nseg); +error_code sys_spu_thread_initialize(ppu_thread &, vm::ptr thread, + u32 group, u32 spu_num, + vm::ptr, + vm::ptr, + vm::ptr); +error_code sys_spu_thread_set_argument(ppu_thread &, u32 id, + vm::ptr arg); +error_code +sys_spu_thread_group_create(ppu_thread &, vm::ptr id, u32 num, s32 prio, + vm::ptr attr); +error_code sys_spu_thread_group_destroy(ppu_thread &, u32 id); +error_code sys_spu_thread_group_start(ppu_thread &, u32 id); +error_code sys_spu_thread_group_suspend(ppu_thread &, u32 id); +error_code sys_spu_thread_group_resume(ppu_thread &, u32 id); +error_code sys_spu_thread_group_yield(ppu_thread &, u32 id); +error_code sys_spu_thread_group_terminate(ppu_thread &, u32 id, s32 value); +error_code sys_spu_thread_group_join(ppu_thread &, u32 id, vm::ptr cause, + vm::ptr status); +error_code sys_spu_thread_group_set_priority(ppu_thread &, u32 id, + s32 priority); +error_code sys_spu_thread_group_get_priority(ppu_thread &, u32 id, + vm::ptr priority); +error_code sys_spu_thread_group_connect_event(ppu_thread &, u32 id, u32 eq, + u32 et); +error_code sys_spu_thread_group_disconnect_event(ppu_thread &, u32 id, u32 et); +error_code sys_spu_thread_group_connect_event_all_threads(ppu_thread &, u32 id, + u32 eq_id, u64 req, + vm::ptr spup); +error_code sys_spu_thread_group_disconnect_event_all_threads(ppu_thread &, + u32 id, u32 spup); +error_code sys_spu_thread_group_set_cooperative_victims(ppu_thread &, u32 id, + u32 threads_mask); +error_code sys_spu_thread_group_syscall_253( + ppu_thread &ppu, u32 id, + vm::ptr info); +error_code sys_spu_thread_group_log(ppu_thread &, s32 command, + vm::ptr stat); +error_code sys_spu_thread_write_ls(ppu_thread &, u32 id, u32 lsa, u64 value, + u32 type); +error_code sys_spu_thread_read_ls(ppu_thread &, u32 id, u32 lsa, + vm::ptr value, u32 type); +error_code sys_spu_thread_write_spu_mb(ppu_thread &, u32 id, u32 value); +error_code sys_spu_thread_set_spu_cfg(ppu_thread &, u32 id, u64 value); +error_code sys_spu_thread_get_spu_cfg(ppu_thread &, u32 id, vm::ptr value); +error_code sys_spu_thread_write_snr(ppu_thread &, u32 id, u32 number, + u32 value); +error_code sys_spu_thread_connect_event(ppu_thread &, u32 id, u32 eq, u32 et, + u32 spup); +error_code sys_spu_thread_disconnect_event(ppu_thread &, u32 id, u32 et, + u32 spup); +error_code sys_spu_thread_bind_queue(ppu_thread &, u32 id, u32 spuq, + u32 spuq_num); +error_code sys_spu_thread_unbind_queue(ppu_thread &, u32 id, u32 spuq_num); +error_code sys_spu_thread_get_exit_status(ppu_thread &, u32 id, + vm::ptr status); +error_code sys_spu_thread_recover_page_fault(ppu_thread &, u32 id); + +error_code sys_raw_spu_create(ppu_thread &, vm::ptr id, + vm::ptr attr); +error_code sys_raw_spu_destroy(ppu_thread &ppu, u32 id); +error_code sys_raw_spu_create_interrupt_tag(ppu_thread &, u32 id, u32 class_id, + u32 hwthread, vm::ptr intrtag); +error_code sys_raw_spu_set_int_mask(ppu_thread &, u32 id, u32 class_id, + u64 mask); +error_code sys_raw_spu_get_int_mask(ppu_thread &, u32 id, u32 class_id, + vm::ptr mask); +error_code sys_raw_spu_set_int_stat(ppu_thread &, u32 id, u32 class_id, + u64 stat); +error_code sys_raw_spu_get_int_stat(ppu_thread &, u32 id, u32 class_id, + vm::ptr stat); +error_code sys_raw_spu_read_puint_mb(ppu_thread &, u32 id, vm::ptr value); +error_code sys_raw_spu_set_spu_cfg(ppu_thread &, u32 id, u32 value); +error_code sys_raw_spu_get_spu_cfg(ppu_thread &, u32 id, vm::ptr value); +error_code sys_raw_spu_recover_page_fault(ppu_thread &, u32 id); + +error_code sys_isolated_spu_create(ppu_thread &, vm::ptr id, + vm::ptr image, u64 arg1, u64 arg2, + u64 arg3, u64 arg4); +error_code sys_isolated_spu_start(ppu_thread &, u32 id); +error_code sys_isolated_spu_destroy(ppu_thread &ppu, u32 id); +error_code sys_isolated_spu_create_interrupt_tag(ppu_thread &, u32 id, + u32 class_id, u32 hwthread, + vm::ptr intrtag); +error_code sys_isolated_spu_set_int_mask(ppu_thread &, u32 id, u32 class_id, + u64 mask); +error_code sys_isolated_spu_get_int_mask(ppu_thread &, u32 id, u32 class_id, + vm::ptr mask); +error_code sys_isolated_spu_set_int_stat(ppu_thread &, u32 id, u32 class_id, + u64 stat); +error_code sys_isolated_spu_get_int_stat(ppu_thread &, u32 id, u32 class_id, + vm::ptr stat); +error_code sys_isolated_spu_read_puint_mb(ppu_thread &, u32 id, + vm::ptr value); +error_code sys_isolated_spu_set_spu_cfg(ppu_thread &, u32 id, u32 value); +error_code sys_isolated_spu_get_spu_cfg(ppu_thread &, u32 id, + vm::ptr value); diff --git a/rpcs3/Emu/Cell/lv2/sys_ss.h b/kernel/cellos/include/cellos/sys_ss.h similarity index 54% rename from rpcs3/Emu/Cell/lv2/sys_ss.h rename to kernel/cellos/include/cellos/sys_ss.h index 0925cacc0..7c22c6f57 100644 --- a/rpcs3/Emu/Cell/lv2/sys_ss.h +++ b/kernel/cellos/include/cellos/sys_ss.h @@ -1,25 +1,24 @@ #pragma once -#include "Emu/Memory/vm_ptr.h" #include "Emu/Cell/ErrorCodes.h" +#include "Emu/Memory/vm_ptr.h" // Unofficial error code names -enum sys_ss_rng_error : u32 -{ - SYS_SS_RNG_ERROR_INVALID_PKG = 0x80010500, - SYS_SS_RNG_ERROR_ENOMEM = 0x80010501, - SYS_SS_RNG_ERROR_EAGAIN = 0x80010503, - SYS_SS_RNG_ERROR_EFAULT = 0x80010509, - SYS_SS_RTC_ERROR_UNK = 0x8001050f, +enum sys_ss_rng_error : u32 { + SYS_SS_RNG_ERROR_INVALID_PKG = 0x80010500, + SYS_SS_RNG_ERROR_ENOMEM = 0x80010501, + SYS_SS_RNG_ERROR_EAGAIN = 0x80010503, + SYS_SS_RNG_ERROR_EFAULT = 0x80010509, + SYS_SS_RTC_ERROR_UNK = 0x8001050f, }; -struct CellSsOpenPSID -{ - be_t high; - be_t low; +struct CellSsOpenPSID { + be_t high; + be_t low; }; -error_code sys_ss_random_number_generator(u64 pkg_id, vm::ptr buf, u64 size); +error_code sys_ss_random_number_generator(u64 pkg_id, vm::ptr buf, + u64 size); error_code sys_ss_access_control_engine(u64 pkg_id, u64 a2, u64 a3); error_code sys_ss_get_console_id(vm::ptr buf); error_code sys_ss_get_open_psid(vm::ptr psid); @@ -28,6 +27,10 @@ error_code sys_ss_get_cache_of_product_mode(vm::ptr ptr); error_code sys_ss_secure_rtc(u64 cmd, u64 a2, u64 a3, u64 a4); error_code sys_ss_get_cache_of_flash_ext_flag(vm::ptr flag); error_code sys_ss_get_boot_device(vm::ptr dev); -error_code sys_ss_update_manager(u64 pkg_id, u64 a1, u64 a2, u64 a3, u64 a4, u64 a5, u64 a6); -error_code sys_ss_virtual_trm_manager(u64 pkg_id, u64 a1, u64 a2, u64 a3, u64 a4); -error_code sys_ss_individual_info_manager(u64 pkg_id, u64 a2, vm::ptr out_size, u64 a4, u64 a5, u64 a6); +error_code sys_ss_update_manager(u64 pkg_id, u64 a1, u64 a2, u64 a3, u64 a4, + u64 a5, u64 a6); +error_code sys_ss_virtual_trm_manager(u64 pkg_id, u64 a1, u64 a2, u64 a3, + u64 a4); +error_code sys_ss_individual_info_manager(u64 pkg_id, u64 a2, + vm::ptr out_size, u64 a4, u64 a5, + u64 a6); diff --git a/kernel/cellos/include/cellos/sys_storage.h b/kernel/cellos/include/cellos/sys_storage.h new file mode 100644 index 000000000..50f463ea2 --- /dev/null +++ b/kernel/cellos/include/cellos/sys_storage.h @@ -0,0 +1,91 @@ +#pragma once + +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/Memory/vm_ptr.h" +#include "util/File.h" + +enum Devices : u64 { + ATA_HDD = 0x101000000000007, + BDVD_DRIVE = 0x101000000000006, + PATA0_HDD_DRIVE = 0x101000000000008, + PATA0_BDVD_DRIVE = BDVD_DRIVE, + PATA1_HDD_DRIVE = ATA_HDD, + BUILTIN_FLASH = 0x100000000000001, + NAND_FLASH = BUILTIN_FLASH, + NAND_UNK = 0x100000000000003, + NOR_FLASH = 0x100000000000004, + MEMORY_STICK = 0x103000000000010, + SD_CARD = 0x103000100000010, + COMPACT_FLASH = 0x103000200000010, + USB_MASS_STORAGE_1_BASE = 0x10300000000000A, + USB_MASS_STORAGE_2_BASE = 0x10300000000001F, +}; + +struct lv2_storage { + static const u32 id_base = 0x45000000; + static const u32 id_step = 1; + static const u32 id_count = 2048; + SAVESTATE_INIT_POS(45); + + const u64 device_id; + const fs::file file; + const u64 mode; + const u64 flags; + + lv2_storage(u64 device_id, fs::file &&file, u64 mode, u64 flags) + : device_id(device_id), file(std::move(file)), mode(mode), flags(flags) {} +}; + +struct StorageDeviceInfo { + u8 name[0x20]; // 0x0 + be_t zero; // 0x20 + be_t zero2; // 0x24 + be_t sector_count; // 0x28 + be_t sector_size; // 0x30 + be_t one; // 0x34 + u8 flags[8]; // 0x38 +}; + +#define USB_MASS_STORAGE_1(n) (USB_MASS_STORAGE_1_BASE + n) /* For 0-5 */ +#define USB_MASS_STORAGE_2(n) \ + (USB_MASS_STORAGE_2_BASE + (n - 6)) /* For 6-127 */ + +// SysCalls + +error_code sys_storage_open(u64 device, u64 mode, vm::ptr fd, u64 flags); +error_code sys_storage_close(u32 fd); +error_code sys_storage_read(u32 fd, u32 mode, u32 start_sector, u32 num_sectors, + vm::ptr bounce_buf, vm::ptr sectors_read, + u64 flags); +error_code sys_storage_write(u32 fd, u32 mode, u32 start_sector, + u32 num_sectors, vm::ptr data, + vm::ptr sectors_wrote, u64 flags); +error_code sys_storage_send_device_command(u32 dev_handle, u64 cmd, + vm::ptr in, u64 inlen, + vm::ptr out, u64 outlen); +error_code sys_storage_async_configure(u32 fd, u32 io_buf, u32 equeue_id, + u32 unk); +error_code sys_storage_async_read(); +error_code sys_storage_async_write(); +error_code sys_storage_async_cancel(); +error_code sys_storage_get_device_info(u64 device, + vm::ptr buffer); +error_code sys_storage_get_device_config(vm::ptr storages, + vm::ptr devices); +error_code sys_storage_report_devices(u32 storages, u32 start, u32 devices, + vm::ptr device_ids); +error_code sys_storage_configure_medium_event(u32 fd, u32 equeue_id, u32 c); +error_code sys_storage_set_medium_polling_interval(); +error_code sys_storage_create_region(); +error_code sys_storage_delete_region(); +error_code sys_storage_execute_device_command( + u32 fd, u64 cmd, vm::ptr cmdbuf, u64 cmdbuf_size, + vm::ptr databuf, u64 databuf_size, vm::ptr driver_status); +error_code sys_storage_check_region_acl(); +error_code sys_storage_set_region_acl(); +error_code sys_storage_async_send_device_command(u32 dev_handle, u64 cmd, + vm::ptr in, u64 inlen, + vm::ptr out, u64 outlen, + u64 unk); +error_code sys_storage_get_region_offset(); +error_code sys_storage_set_emulated_speed(); diff --git a/kernel/cellos/include/cellos/sys_sync.h b/kernel/cellos/include/cellos/sys_sync.h new file mode 100644 index 000000000..409c9860e --- /dev/null +++ b/kernel/cellos/include/cellos/sys_sync.h @@ -0,0 +1,469 @@ +#pragma once + +#include "util/mutex.h" + +#include "Emu/CPU/CPUThread.h" +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/IPC.h" +#include "Emu/IdManager.h" + +#include "util/shared_ptr.hpp" + +// attr_protocol (waiting scheduling policy) +enum lv2_protocol : u8 { + SYS_SYNC_FIFO = 0x1, // First In, First Out Order + SYS_SYNC_PRIORITY = 0x2, // Priority Order + SYS_SYNC_PRIORITY_INHERIT = 0x3, // Basic Priority Inheritance Protocol + SYS_SYNC_RETRY = 0x4, // Not selected while unlocking +}; + +enum : u32 { + SYS_SYNC_ATTR_PROTOCOL_MASK = 0xf, +}; + +// attr_recursive (recursive locks policy) +enum { + SYS_SYNC_RECURSIVE = 0x10, + SYS_SYNC_NOT_RECURSIVE = 0x20, + SYS_SYNC_ATTR_RECURSIVE_MASK = 0xf0, +}; + +// attr_pshared (sharing among processes policy) +enum { + SYS_SYNC_PROCESS_SHARED = 0x100, + SYS_SYNC_NOT_PROCESS_SHARED = 0x200, + SYS_SYNC_ATTR_PSHARED_MASK = 0xf00, +}; + +// attr_flags (creation policy) +enum { + SYS_SYNC_NEWLY_CREATED = + 0x1, // Create new object, fails if specified IPC key exists + SYS_SYNC_NOT_CREATE = + 0x2, // Reference existing object, fails if IPC key not found + SYS_SYNC_NOT_CARE = + 0x3, // Reference existing object, create new one if IPC key not found + SYS_SYNC_ATTR_FLAGS_MASK = 0xf, +}; + +// attr_adaptive +enum { + SYS_SYNC_ADAPTIVE = 0x1000, + SYS_SYNC_NOT_ADAPTIVE = 0x2000, + SYS_SYNC_ATTR_ADAPTIVE_MASK = 0xf000, +}; + +enum ppu_thread_status : u32; + +struct ppu_non_sleeping_count_t { + bool has_running; // no actual count for optimization sake + u32 onproc_count; +}; + +// Base class for some kernel objects (shared set of 8192 objects). +struct lv2_obj { + static const u32 id_step = 0x100; + static const u32 id_count = 8192; + static constexpr std::pair id_invl_range = {0, 8}; + +private: + enum thread_cmd : s32 { + yield_cmd = smin, + enqueue_cmd, + }; + + // Function executed under IDM mutex, error will make the object creation fail + // and the error will be returned + CellError on_id_create() { + exists++; + return {}; + } + +public: + SAVESTATE_INIT_POS(4); // Dependency on PPUs + + lv2_obj() noexcept = default; + lv2_obj(u32 i) noexcept : exists{i} {} + lv2_obj(lv2_obj &&rhs) noexcept : exists{+rhs.exists} {} + lv2_obj(utils::serial &) noexcept {} + lv2_obj &operator=(lv2_obj &&rhs) noexcept { + exists = +rhs.exists; + return *this; + } + void save(utils::serial &) {} + + // Existence validation (workaround for shared-ptr ref-counting) + atomic_t exists = 0; + + template static bool check(Ptr &&ptr) { + return ptr && ptr->exists; + } + + // wrapper for name64 string formatting + struct name_64 { + u64 data; + }; + + static std::string name64(u64 name_u64); + + // Find and remove the object from the linked list + template + static T *unqueue(T *&first, T *object, T *T::*mem_ptr = &T::next_cpu) { + auto it = +first; + + if (it == object) { + atomic_storage::release(first, it->*mem_ptr); + + if constexpr (ModifyNode) { + atomic_storage::release(it->*mem_ptr, nullptr); + } + + return it; + } + + for (; it;) { + const auto next = it->*mem_ptr + 0; + + if (next == object) { + atomic_storage::release(it->*mem_ptr, next->*mem_ptr); + + if constexpr (ModifyNode) { + atomic_storage::release(next->*mem_ptr, nullptr); + } + + return next; + } + + it = next; + } + + return {}; + } + + // Remove an object from the linked set according to the protocol + template + static E *schedule(T &first, u32 protocol, bool modify_node = true) { + auto it = static_cast(first); + + if (!it) { + return it; + } + + auto parent_found = &first; + + if (protocol == SYS_SYNC_FIFO) { + while (true) { + const auto next = +it->next_cpu; + + if (next) { + parent_found = &it->next_cpu; + it = next; + continue; + } + + if (cpu_flag::again - it->state) { + atomic_storage::release(*parent_found, nullptr); + } + + return it; + } + } + + auto prio = it->prio.load(); + auto found = it; + + while (true) { + auto &node = it->next_cpu; + const auto next = static_cast(node); + + if (!next) { + break; + } + + const auto _prio = static_cast(next)->prio.load(); + + // This condition tests for equality as well so the earliest element to be + // pushed is popped + if (_prio.prio < prio.prio || + (_prio.prio == prio.prio && _prio.order < prio.order)) { + found = next; + parent_found = &node; + prio = _prio; + } + + it = next; + } + + if (cpu_flag::again - found->state) { + atomic_storage::release(*parent_found, found->next_cpu); + + if (modify_node) { + atomic_storage::release(found->next_cpu, nullptr); + } + } + + return found; + } + + template static void emplace(T &first, T object) { + atomic_storage::release(object->next_cpu, first); + atomic_storage::release(first, object); + + object->prio.atomic_op( + [order = ++g_priority_order_tag]( + std::common_type_t()->prio.load())> + &prio) { + if constexpr (requires { + +std::declval().preserve_bit; + }) { + if (prio.preserve_bit) { + // Restoring state on load + prio.preserve_bit = 0; + return; + } + } + + prio.order = order; + }); + } + +private: + // Remove the current thread from the scheduling queue, register timeout + static bool sleep_unlocked(cpu_thread &, u64 timeout, u64 current_time); + + // Schedule the thread + static bool awake_unlocked(cpu_thread *, s32 prio = enqueue_cmd); + +public: + static constexpr u64 max_timeout = u64{umax} / 1000; + + static bool sleep(cpu_thread &cpu, const u64 timeout = 0); + + static bool awake(cpu_thread *thread, s32 prio = enqueue_cmd); + + // Returns true on successful context switch, false otherwise + static bool yield(cpu_thread &thread); + + static void set_priority(cpu_thread &thread, s32 prio) { + ensure(prio + 512u < 3712); + awake(&thread, prio); + } + + static inline void awake_all() { + awake({}); + g_to_awake.clear(); + } + + static void make_scheduler_ready(); + + static std::pair + ppu_state(ppu_thread *ppu, bool lock_idm = true, bool lock_lv2 = true); + + static inline void append(cpu_thread *const thread) { + g_to_awake.emplace_back(thread); + } + + // Serialization related + static void set_future_sleep(cpu_thread *cpu); + static bool is_scheduler_ready(); + + // Must be called under IDM lock + static ppu_non_sleeping_count_t count_non_sleeping_threads(); + + static inline bool has_ppus_in_running_state() noexcept { + return count_non_sleeping_threads().has_running != 0; + } + + static void set_yield_frequency(u64 freq, u64 max_allowed_tsx); + + static void cleanup(); + + template static inline u64 get_key(const T &attr) { + return (attr.pshared == SYS_SYNC_PROCESS_SHARED ? +attr.ipc_key : 0); + } + + template + static error_code create(u32 pshared, u64 ipc_key, s32 flags, F &&make, + bool key_not_zero = true) { + switch (pshared) { + case SYS_SYNC_PROCESS_SHARED: { + if (key_not_zero && ipc_key == 0) { + return CELL_EINVAL; + } + + switch (flags) { + case SYS_SYNC_NEWLY_CREATED: + case SYS_SYNC_NOT_CARE: + case SYS_SYNC_NOT_CREATE: { + break; + } + default: + return CELL_EINVAL; + } + + break; + } + case SYS_SYNC_NOT_PROCESS_SHARED: { + break; + } + default: + return CELL_EINVAL; + } + + // EAGAIN for IDM IDs shortage + CellError error = CELL_EAGAIN; + + if (!idm::import ([&]() -> shared_ptr { + shared_ptr result = make(); + + auto finalize_construct = [&]() -> shared_ptr { + if ((error = result->on_id_create())) { + result.reset(); + } + + return std::move(result); + }; + + if (pshared != SYS_SYNC_PROCESS_SHARED) { + // Creation of unique (non-shared) object handle + return finalize_construct(); + } + + auto &ipc_container = g_fxo->get>(); + + if (flags == SYS_SYNC_NOT_CREATE) { + result = ipc_container.get(ipc_key); + + if (!result) { + error = CELL_ESRCH; + return result; + } + + // Run on_id_create() on existing object + return finalize_construct(); + } + + bool added = false; + std::tie(added, result) = ipc_container.add( + ipc_key, finalize_construct, flags != SYS_SYNC_NEWLY_CREATED); + + if (!added) { + if (flags == SYS_SYNC_NEWLY_CREATED) { + // Object already exists but flags does not allow it + error = CELL_EEXIST; + + // We specified we do not want to peek pointer's value, result + // must be empty + AUDIT(!result); + return result; + } + + // Run on_id_create() on existing object + return finalize_construct(); + } + + return result; + })) { + return error; + } + + return CELL_OK; + } + + template + static void on_id_destroy(T &obj, u64 ipc_key, u64 pshared = umax) { + if (pshared == umax) { + // Default is to check key + pshared = ipc_key != 0; + } + + if (obj.exists-- == 1u && pshared) { + g_fxo->get>().remove(ipc_key); + } + } + + template + static shared_ptr load(u64 ipc_key, shared_ptr make, + u64 pshared = umax) { + if (pshared == umax ? ipc_key != 0 : pshared != 0) { + g_fxo->need>(); + + g_fxo->get>().add(ipc_key, [&]() { return make; }); + } + + // Ensure no error + ensure(!make->on_id_create()); + return make; + } + + template + static std::function load_func(shared_ptr make, + u64 pshared = umax) { + const u64 key = make->key; + return [ptr = load(key, make, pshared)](void *storage) { + *static_cast *>(storage) = ptr; + }; + } + + static bool wait_timeout(u64 usec, ppu_thread *cpu = {}, bool scale = true, + bool is_usleep = false); + + static void notify_all() noexcept; + + // Can be called before the actual sleep call in order to move it out of mutex + // scope + static void prepare_for_sleep(cpu_thread &cpu); + + struct notify_all_t { + notify_all_t() noexcept { g_postpone_notify_barrier = true; } + + notify_all_t(const notify_all_t &) = delete; + + static void cleanup() { + for (auto &cpu : g_to_notify) { + if (!cpu) { + return; + } + + // While IDM mutex is still locked (this function assumes so) check if + // the notification is still needed Pending flag is meant for forced + // notification (if the CPU really has pending work it can restore the + // flag in theory) Disabled to allow reservation notifications from here + if (false && cpu != &g_to_notify && + static_cast(cpu)->none_of( + cpu_flag::signal + cpu_flag::pending)) { + // Omit it (this is a void pointer, it can hold anything) + cpu = &g_to_notify; + } + } + } + + ~notify_all_t() noexcept { lv2_obj::notify_all(); } + }; + + // Scheduler mutex + static shared_mutex g_mutex; + + // Proirity tags + static atomic_t g_priority_order_tag; + +private: + // Pending list of threads to run + static thread_local std::vector g_to_awake; + + // Scheduler queue for active PPU threads + static class ppu_thread *g_ppu; + + // Waiting for the response from + static u32 g_pending; + + // Pending list of threads to notify (cpu_thread::state ptr) + static thread_local std::add_pointer_t g_to_notify[4]; + + // If a notify_all_t object exists locally, postpone notifications to the + // destructor of it (not recursive, notifies on the first destructor for + // safety) + static thread_local bool g_postpone_notify_barrier; + + static void schedule_all(u64 current_time = 0); +}; diff --git a/rpcs3/Emu/Cell/lv2/sys_time.h b/kernel/cellos/include/cellos/sys_time.h similarity index 77% rename from rpcs3/Emu/Cell/lv2/sys_time.h rename to kernel/cellos/include/cellos/sys_time.h index fd8c729ab..ead6f8bbf 100644 --- a/rpcs3/Emu/Cell/lv2/sys_time.h +++ b/kernel/cellos/include/cellos/sys_time.h @@ -1,12 +1,13 @@ #pragma once -#include "Emu/Memory/vm_ptr.h" #include "Emu/Cell/ErrorCodes.h" +#include "Emu/Memory/vm_ptr.h" // SysCalls error_code sys_time_set_timezone(s32 timezone, s32 summertime); -error_code sys_time_get_timezone(vm::ptr timezone, vm::ptr summertime); +error_code sys_time_get_timezone(vm::ptr timezone, + vm::ptr summertime); error_code sys_time_get_current_time(vm::ptr sec, vm::ptr nsec); error_code sys_time_set_current_time(s64 sec, s64 nsec); u64 sys_time_get_timebase_frequency(); diff --git a/rpcs3/Emu/Cell/lv2/sys_timer.h b/kernel/cellos/include/cellos/sys_timer.h similarity index 100% rename from rpcs3/Emu/Cell/lv2/sys_timer.h rename to kernel/cellos/include/cellos/sys_timer.h diff --git a/rpcs3/Emu/Cell/lv2/sys_trace.h b/kernel/cellos/include/cellos/sys_trace.h similarity index 100% rename from rpcs3/Emu/Cell/lv2/sys_trace.h rename to kernel/cellos/include/cellos/sys_trace.h diff --git a/kernel/cellos/include/cellos/sys_tty.h b/kernel/cellos/include/cellos/sys_tty.h new file mode 100644 index 000000000..4a703222a --- /dev/null +++ b/kernel/cellos/include/cellos/sys_tty.h @@ -0,0 +1,33 @@ +#pragma once + +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/Memory/vm_ptr.h" + +// TTY channels +enum { + SYS_TTYP_PPU_STDIN = 0, + SYS_TTYP_PPU_STDOUT = 0, + SYS_TTYP_PPU_STDERR = 1, + SYS_TTYP_SPU_STDOUT = 2, + SYS_TTYP_USER1 = 3, + SYS_TTYP_USER2 = 4, + SYS_TTYP_USER3 = 5, + SYS_TTYP_USER4 = 6, + SYS_TTYP_USER5 = 7, + SYS_TTYP_USER6 = 8, + SYS_TTYP_USER7 = 9, + SYS_TTYP_USER8 = 10, + SYS_TTYP_USER9 = 11, + SYS_TTYP_USER10 = 12, + SYS_TTYP_USER11 = 13, + SYS_TTYP_USER12 = 14, + SYS_TTYP_USER13 = 15, +}; + +class ppu_thread; + +// SysCalls +error_code sys_tty_read(s32 ch, vm::ptr buf, u32 len, + vm::ptr preadlen); +error_code sys_tty_write(ppu_thread &ppu, s32 ch, vm::cptr buf, u32 len, + vm::ptr pwritelen); diff --git a/kernel/cellos/include/cellos/sys_uart.h b/kernel/cellos/include/cellos/sys_uart.h new file mode 100644 index 000000000..766631b96 --- /dev/null +++ b/kernel/cellos/include/cellos/sys_uart.h @@ -0,0 +1,713 @@ +#pragma once + +#include "Emu/Memory/vm_ptr.h" +#include "sys_rsxaudio.h" +#include "util/Thread.h" +#include "util/cond.h" +#include "util/mutex.h" +#include "util/simple_ringbuf.h" + +enum : u32 { + PS3AV_RX_BUF_SIZE = 0x800, + PS3AV_TX_BUF_SIZE = 0x800, + + PS3AV_VERSION = 0x205, + + PS3AV_CID_AV_INIT = 0x00000001, + PS3AV_CID_AV_FIN = 0x00000002, + PS3AV_CID_AV_GET_HW_CONF = 0x00000003, + PS3AV_CID_AV_GET_MONITOR_INFO = 0x00000004, + PS3AV_CID_AV_GET_BKSV_LIST = 0x00000005, + PS3AV_CID_AV_ENABLE_EVENT = 0x00000006, + PS3AV_CID_AV_DISABLE_EVENT = 0x00000007, + PS3AV_CID_AV_GET_PORT_STATE = 0x00000009, + PS3AV_CID_AV_TV_MUTE = 0x0000000A, + PS3AV_CID_AV_NULL_CMD = 0x0000000B, + PS3AV_CID_AV_GET_AKSV = 0x0000000C, + PS3AV_CID_AV_UNK4 = 0x0000000D, + PS3AV_CID_AV_UNK5 = 0x0000000E, + + PS3AV_CID_AV_VIDEO_MUTE = 0x00010002, + PS3AV_CID_AV_VIDEO_DISABLE_SIG = 0x00010003, + PS3AV_CID_AV_VIDEO_YTRAPCONTROL = 0x00010004, + PS3AV_CID_AV_VIDEO_UNK5 = 0x00010005, + PS3AV_CID_AV_VIDEO_UNK6 = 0x00010006, + PS3AV_CID_AV_AUDIO_MUTE = 0x00020002, + PS3AV_CID_AV_ACP_CTRL = 0x00020003, + PS3AV_CID_AV_SET_ACP_PACKET = 0x00020004, + PS3AV_CID_AV_ADD_SIGNAL_CTL = 0x00030001, + PS3AV_CID_AV_SET_CC_CODE = 0x00030002, + PS3AV_CID_AV_SET_CGMS_WSS = 0x00030003, + PS3AV_CID_AV_SET_MACROVISION = 0x00030004, + PS3AV_CID_AV_UNK7 = 0x00030005, + PS3AV_CID_AV_UNK8 = 0x00030006, + PS3AV_CID_AV_UNK9 = 0x00030007, + PS3AV_CID_AV_HDMI_MODE = 0x00040001, + PS3AV_CID_AV_UNK15 = 0x00050001, + + PS3AV_CID_AV_CEC_MESSAGE = 0x000A0001, + PS3AV_CID_AV_GET_CEC_CONFIG = 0x000A0002, + PS3AV_CID_AV_UNK11 = 0x000A0003, + PS3AV_CID_AV_UNK12 = 0x000A0004, + PS3AV_CID_AV_UNK13 = 0x000A0005, + PS3AV_CID_AV_UNK14 = 0x000A0006, + + PS3AV_CID_VIDEO_INIT = 0x01000001, + PS3AV_CID_VIDEO_MODE = 0x01000002, + PS3AV_CID_VIDEO_ROUTE = 0x01000003, + PS3AV_CID_VIDEO_FORMAT = 0x01000004, + PS3AV_CID_VIDEO_PITCH = 0x01000005, + PS3AV_CID_VIDEO_GET_HW_CONF = 0x01000006, + PS3AV_CID_VIDEO_GET_REG = 0x01000008, + PS3AV_CID_VIDEO_UNK = 0x01000009, + PS3AV_CID_VIDEO_UNK1 = 0x0100000A, + PS3AV_CID_VIDEO_UNK2 = 0x0100000B, + PS3AV_CID_VIDEO_UNK3 = 0x0100000C, + + PS3AV_CID_AUDIO_INIT = 0x02000001, + PS3AV_CID_AUDIO_MODE = 0x02000002, + PS3AV_CID_AUDIO_MUTE = 0x02000003, + PS3AV_CID_AUDIO_ACTIVE = 0x02000004, + PS3AV_CID_AUDIO_INACTIVE = 0x02000005, + PS3AV_CID_AUDIO_SPDIF_BIT = 0x02000006, + PS3AV_CID_AUDIO_CTRL = 0x02000007, + + PS3AV_CID_AVB_PARAM = 0x04000001, + + PS3AV_CID_EVENT_UNPLUGGED = 0x10000001, + PS3AV_CID_EVENT_PLUGGED = 0x10000002, + PS3AV_CID_EVENT_HDCP_DONE = 0x10000003, + PS3AV_CID_EVENT_HDCP_FAIL = 0x10000004, + PS3AV_CID_EVENT_HDCP_REAUTH = 0x10000005, + PS3AV_CID_EVENT_HDCP_ERROR = 0x10000006, + + PS3AV_REPLY_BIT = 0x80000000, + + PS3AV_RESBIT_720x480P = 0x0003, /* 0x0001 | 0x0002 */ + PS3AV_RESBIT_720x576P = 0x0003, /* 0x0001 | 0x0002 */ + PS3AV_RESBIT_1280x720P = 0x0004, + PS3AV_RESBIT_1920x1080I = 0x0008, + PS3AV_RESBIT_1920x1080P = 0x4000, + + PS3AV_MONITOR_TYPE_NONE = 0, + PS3AV_MONITOR_TYPE_HDMI = 1, + PS3AV_MONITOR_TYPE_DVI = 2, + PS3AV_MONITOR_TYPE_AVMULTI = 3, + + PS3AV_COLORIMETRY_xvYCC_601 = 1, + PS3AV_COLORIMETRY_xvYCC_709 = 2, + PS3AV_COLORIMETRY_MD0 = 1 << 4, + PS3AV_COLORIMETRY_MD1 = 1 << 5, + PS3AV_COLORIMETRY_MD2 = 1 << 6, + + PS3AV_CS_SUPPORTED = 1, + PS3AV_RGB_SELECTABLE_QAUNTIZATION_RANGE = 8, + PS3AV_12BIT_COLOR = 16, + + PS3AV_MON_INFO_AUDIO_BLK_MAX = 16, + + PS3AV_MON_INFO_AUDIO_TYPE_LPCM = 1, + PS3AV_MON_INFO_AUDIO_TYPE_AC3 = 2, + PS3AV_MON_INFO_AUDIO_TYPE_AAC = 6, + PS3AV_MON_INFO_AUDIO_TYPE_DTS = 7, + PS3AV_MON_INFO_AUDIO_TYPE_DDP = 10, + PS3AV_MON_INFO_AUDIO_TYPE_DTS_HD = 11, + PS3AV_MON_INFO_AUDIO_TYPE_DOLBY_THD = 12, + + PS3AV_HDMI_BEHAVIOR_HDCP_OFF = 0x01, + PS3AV_HDMI_BEHAVIOR_DVI = 0x40, + PS3AV_HDMI_BEHAVIOR_EDID_PASS = 0x80, + PS3AV_HDMI_BEHAVIOR_NORMAL = 0xFF, + + PS3AV_EVENT_BIT_UNPLUGGED = 0x01, + PS3AV_EVENT_BIT_PLUGGED = 0x02, + PS3AV_EVENT_BIT_HDCP_DONE = 0x04, + PS3AV_EVENT_BIT_HDCP_FAIL = 0x08, + PS3AV_EVENT_BIT_HDCP_REAUTH = 0x10, + PS3AV_EVENT_BIT_HDCP_TOPOLOGY = 0x20, + PS3AV_EVENT_BIT_UNK = 0x80000000, + + PS3AV_HEAD_A_HDMI = 0, + PS3AV_HEAD_B_ANALOG = 1, + + PS3AV_AUDIO_PORT_HDMI_0 = 1 << 0, + PS3AV_AUDIO_PORT_HDMI_1 = 1 << 1, + PS3AV_AUDIO_PORT_AVMULTI = 1 << 10, + PS3AV_AUDIO_PORT_SPDIF_0 = 1 << 20, + PS3AV_AUDIO_PORT_SPDIF_1 = 1 << 21, + + PS3AV_STATUS_SUCCESS = 0x00, + PS3AV_STATUS_RECEIVE_VUART_ERROR = 0x01, + PS3AV_STATUS_SYSCON_COMMUNICATE_FAIL = 0x02, + PS3AV_STATUS_INVALID_COMMAND = 0x03, + PS3AV_STATUS_INVALID_PORT = 0x04, + PS3AV_STATUS_INVALID_VID = 0x05, + PS3AV_STATUS_INVALID_COLOR_SPACE = 0x06, + PS3AV_STATUS_INVALID_FS = 0x07, + PS3AV_STATUS_INVALID_AUDIO_CH = 0x08, + PS3AV_STATUS_UNSUPPORTED_VERSION = 0x09, + PS3AV_STATUS_INVALID_SAMPLE_SIZE = 0x0A, + PS3AV_STATUS_FAILURE = 0x0B, + PS3AV_STATUS_UNSUPPORTED_COMMAND = 0x0C, + PS3AV_STATUS_BUFFER_OVERFLOW = 0x0D, + PS3AV_STATUS_INVALID_VIDEO_PARAM = 0x0E, + PS3AV_STATUS_NO_SEL = 0x0F, + PS3AV_STATUS_INVALID_AV_PARAM = 0x10, + PS3AV_STATUS_INVALID_AUDIO_PARAM = 0x11, + PS3AV_STATUS_UNSUPPORTED_HDMI_MODE = 0x12, + PS3AV_STATUS_NO_SYNC_HEAD = 0x13, + PS3AV_STATUS_UNK_0x14 = 0x14, +}; + +const u8 PS3AV_AKSV_VALUE[5] = {0x00, 0x00, 0x0F, 0xFF, 0xFF}; +const u8 PS3AV_BKSV_VALUE[5] = {0xFF, 0xFF, 0xF0, 0x00, 0x00}; + +enum PS3_AV_OP_MODE : u32 { + // BIG operation modes could send more then 4096 bytes + + NOT_BLOCKING_BIG_OP = 0, + BLOCKING_BIG_OP = 1, + NOT_BLOCKING_OP = 2, +}; + +enum class UartHdmiEvent : u8 { + NONE = 0, + UNPLUGGED = 1, + PLUGGED = 2, + HDCP_DONE = 3, +}; + +enum class UartAudioCtrlID : u32 { + DAC_RESET = 0, + DAC_DE_EMPHASIS = 1, + AVCLK = 2, +}; + +enum class UartAudioAvport : u8 { + HDMI_0 = 0x0, + HDMI_1 = 0x1, + AVMULTI_0 = 0x10, + AVMULTI_1 = 0x11, + SPDIF_0 = 0x20, + SPDIF_1 = 0x21, +}; + +enum class UartAudioSource : u32 { + SERIAL = 0, + SPDIF = 1, +}; + +enum class UartAudioFreq : u32 { + _32K = 1, + _44K = 2, + _48K = 3, + _88K = 4, + _96K = 5, + _176K = 6, + _192K = 7, +}; + +enum class UartAudioFormat : u32 { + PCM = 1, + BITSTREAM = 0xFF, +}; + +enum class UartAudioSampleSize : u32 { + _16BIT = 1, + _20BIT = 2, + _24BIT = 3, +}; + +class vuart_hdmi_event_handler { +public: + vuart_hdmi_event_handler(u64 time_offset = 0); + + void set_target_state(UartHdmiEvent start_state, UartHdmiEvent end_state); + bool events_available(); + + u64 time_until_next(); + UartHdmiEvent get_occured_event(); + +private: + static constexpr u64 EVENT_TIME_DURATION = 20000; + static constexpr u64 EVENT_TIME_THRESHOLD = 1000; + + u64 time_of_next_event = 0; + const u64 time_offset = 0; + + // Assume that syscon initialized hdmi to plugged state + UartHdmiEvent current_state = UartHdmiEvent::PLUGGED; + UartHdmiEvent current_to_state = UartHdmiEvent::PLUGGED; + + UartHdmiEvent base_state = UartHdmiEvent::NONE; + UartHdmiEvent target_state = UartHdmiEvent::NONE; + + void schedule_next(); + void advance_state(); +}; + +class vuart_av_thread; + +struct ps3av_cmd { + virtual u16 get_size(vuart_av_thread &vuart, const void *pkt_buf) = 0; + virtual void execute(vuart_av_thread &vuart, const void *pkt_buf) = 0; + virtual ~ps3av_cmd() {}; +}; + +class vuart_av_thread { +public: + atomic_t initialized{}; + + shared_mutex rx_mutex{}; + shared_mutex tx_mutex{}; + + shared_mutex tx_wake_m{}; + cond_variable tx_wake_c{}; + shared_mutex tx_rdy_m{}; + cond_variable tx_rdy_c{}; + shared_mutex rx_wake_m{}; + cond_variable rx_wake_c{}; + + bool head_b_initialized = false; + u8 hdmi_behavior_mode = PS3AV_HDMI_BEHAVIOR_NORMAL; + u16 av_cmd_ver = 0; + u32 hdmi_events_bitmask = 0; + bool hdmi_res_set[2]{false, false}; + + void operator()(); + void parse_tx_buffer(u32 buf_size); + vuart_av_thread &operator=(thread_state); + + u32 enque_tx_data(const void *data, u32 data_sz); + u32 get_tx_bytes(); + u32 read_rx_data(void *data, u32 data_sz); + + u32 get_reply_buf_free_size(); + + template + void write_resp(u32 cid, u32 status, const void *data = nullptr, + u16 data_size = 0); + + void add_hdmi_events(UartHdmiEvent first_event, UartHdmiEvent last_event, + bool hdmi_0, bool hdmi_1); + void add_hdmi_events(UartHdmiEvent last_event, bool hdmi_0, bool hdmi_1); + + static RsxaudioAvportIdx avport_to_idx(UartAudioAvport avport); + + static constexpr auto thread_name = "VUART AV Thread"sv; + +private: + struct temp_buf { + u32 crnt_size = 0; + u8 buf[PS3AV_RX_BUF_SIZE]{}; + }; + + simple_ringbuf tx_buf{PS3AV_TX_BUF_SIZE}; + simple_ringbuf rx_buf{PS3AV_RX_BUF_SIZE}; + + // uart_mngr could sometimes read past the tx_buffer due to weird size checks + // in FW, but no further than size of largest packet + u8 temp_tx_buf[PS3AV_TX_BUF_SIZE * 2]{}; + temp_buf temp_rx_buf{}; + temp_buf temp_rx_sc_buf{}; + + vuart_hdmi_event_handler hdmi_event_handler[2]{0, 5000}; + bool hdcp_first_auth[2]{true, true}; + + u32 read_tx_data(void *data, u32 data_sz); + std::shared_ptr get_cmd(u32 cid); + void commit_rx_buf(bool syscon_buf); + + void add_unplug_event(bool hdmi_0, bool hdmi_1); + void add_plug_event(bool hdmi_0, bool hdmi_1); + void add_hdcp_done_event(bool hdmi_0, bool hdmi_1); + void commit_event_data(const void *data, u16 data_size); + void dispatch_hdmi_event(UartHdmiEvent event, UartAudioAvport hdmi); +}; + +using vuart_av = named_thread; + +struct vuart_params { + be_t rx_buf_size; + be_t tx_buf_size; +}; + +static_assert(sizeof(vuart_params) == 16); + +struct ps3av_pkt_reply_hdr { + be_t version; + be_t length; + be_t cid; + be_t status; +}; + +static_assert(sizeof(ps3av_pkt_reply_hdr) == 12); + +struct ps3av_header { + be_t version; + be_t length; + be_t cid; +}; + +static_assert(sizeof(ps3av_header) == 8); + +struct ps3av_info_resolution { + be_t res_bits; + be_t native; +}; + +struct ps3av_info_cs { + u8 rgb; + u8 yuv444; + u8 yuv422; + u8 colorimetry_data; +}; + +struct ps3av_info_color { + be_t red_x; + be_t red_y; + be_t green_x; + be_t green_y; + be_t blue_x; + be_t blue_y; + be_t white_x; + be_t white_y; + be_t gamma; +}; + +struct ps3av_info_audio { + u8 type; + u8 max_num_of_ch; + u8 fs; + u8 sbit; +}; + +struct ps3av_get_monitor_info_reply { + u8 avport; + u8 monitor_id[10]; + u8 monitor_type; + u8 monitor_name[16]; + ps3av_info_resolution res_60; + ps3av_info_resolution res_50; + ps3av_info_resolution res_other; + ps3av_info_resolution res_vesa; + ps3av_info_cs cs; + ps3av_info_color color; + u8 supported_ai; + u8 speaker_info; + be_t num_of_audio_block; + ps3av_info_audio audio_info[PS3AV_MON_INFO_AUDIO_BLK_MAX]; + be_t hor_screen_size; + be_t ver_screen_size; + u8 supported_content_types; + u8 reserved_1[3]; + ps3av_info_resolution res_60_packed_3D; + ps3av_info_resolution res_50_packed_3D; + ps3av_info_resolution res_other_3D; + ps3av_info_resolution res_60_sbs_3D; + ps3av_info_resolution res_50_sbs_3D; + u8 vendor_specific_flags; + u8 reserved_2[7]; +}; + +static_assert(sizeof(ps3av_get_monitor_info_reply) == 208); + +struct ps3av_get_monitor_info { + ps3av_header hdr; + be_t avport; + be_t reserved; +}; + +static_assert(sizeof(ps3av_get_monitor_info) == 12); + +struct ps3av_get_hw_info_reply { + be_t num_of_hdmi; + be_t num_of_avmulti; + be_t num_of_spdif; + be_t extra_bistream_support; +}; + +static_assert(sizeof(ps3av_get_hw_info_reply) == 8); + +struct ps3av_pkt_set_hdmi_mode { + ps3av_header hdr; + u8 mode; + u8 resv[3]; +}; + +static_assert(sizeof(ps3av_pkt_set_hdmi_mode) == 12); + +struct ps3av_pkt_audio_mode { + ps3av_header hdr; + UartAudioAvport avport; + u8 reserved0[3]; + be_t mask; + be_t audio_num_of_ch; + be_t audio_fs; + be_t audio_word_bits; + be_t audio_format; + be_t audio_source; + u8 audio_enable[4]; + u8 audio_swap[4]; + u8 audio_map[4]; + be_t audio_layout; + be_t audio_downmix; + be_t audio_downmix_level; + u8 audio_cs_info[8]; +}; + +static_assert(sizeof(ps3av_pkt_audio_mode) == 68); + +struct ps3av_pkt_audio_mute { + ps3av_header hdr; + UartAudioAvport avport; + u8 reserved0[3]; + u8 mute; +}; + +static_assert(sizeof(ps3av_pkt_audio_mute) == 13); + +struct ps3av_pkt_audio_set_active { + ps3av_header hdr; + be_t audio_port; +}; + +static_assert(sizeof(ps3av_pkt_audio_set_active) == 12); + +struct ps3av_pkt_audio_spdif_bit { + ps3av_header hdr; + UartAudioAvport avport; + u8 reserved0[3]; + be_t audio_port; + be_t spdif_bit_data[12]; +}; + +static_assert(sizeof(ps3av_pkt_audio_spdif_bit) == 64); + +struct ps3av_pkt_audio_ctrl { + ps3av_header hdr; + be_t audio_ctrl_id; + be_t audio_ctrl_data[4]; +}; + +static_assert(sizeof(ps3av_pkt_audio_ctrl) == 28); + +struct ps3av_pkt_hdmi_plugged_event { + ps3av_header hdr; + ps3av_get_monitor_info_reply minfo; +}; + +static_assert(sizeof(ps3av_pkt_hdmi_plugged_event) == 216); + +struct ps3av_pkt_hdmi_hdcp_done_event { + ps3av_header hdr; + be_t ksv_cnt; + u8 ksv_arr[20][5]; +}; + +static_assert(sizeof(ps3av_pkt_hdmi_hdcp_done_event) == 112); + +struct ps3av_pkt_av_init { + ps3av_header hdr; + be_t event_bit; +}; + +static_assert(sizeof(ps3av_pkt_av_init) == 12); + +struct ps3av_pkt_av_init_reply { + be_t unk; +}; + +static_assert(sizeof(ps3av_pkt_av_init_reply) == 4); + +struct ps3av_pkt_enable_event { + ps3av_header hdr; + be_t event_bit; +}; + +static_assert(sizeof(ps3av_pkt_enable_event) == 12); + +struct ps3av_pkt_get_bksv { + ps3av_header hdr; + be_t avport; + u8 resv[2]; +}; + +static_assert(sizeof(ps3av_pkt_get_bksv) == 12); + +struct ps3av_pkt_get_bksv_reply { + be_t avport; + u8 resv[2]; + be_t ksv_cnt; + u8 ksv_arr[20][5]; +}; + +static_assert(sizeof(ps3av_pkt_get_bksv_reply) == 108); + +struct ps3av_pkt_video_get_hw_cfg_reply { + be_t gx_available; +}; + +static_assert(sizeof(ps3av_pkt_video_get_hw_cfg_reply) == 4); + +struct ps3av_pkt_video_set_pitch { + ps3av_header hdr; + be_t video_head; + be_t pitch; +}; + +static_assert(sizeof(ps3av_pkt_video_set_pitch) == 16); + +struct ps3av_pkt_get_aksv_reply { + be_t ksv_size; + u8 ksv_arr[2][5]; + u8 resv[2]; +}; + +static_assert(sizeof(ps3av_pkt_get_aksv_reply) == 16); + +struct ps3av_pkt_inc_avset { + ps3av_header hdr; + be_t num_of_video_pkt; + be_t num_of_audio_pkt; + be_t num_of_av_video_pkt; + be_t num_of_av_audio_pkt; +}; + +static_assert(sizeof(ps3av_pkt_inc_avset) == 16); + +struct ps3av_pkt_av_audio_param { + ps3av_header hdr; + be_t avport; + be_t resv; + u8 mclk; + u8 ns[3]; + u8 enable; + u8 swaplr; + u8 fifomap; + u8 inputctrl; + u8 inputlen; + u8 layout; + u8 info[5]; + u8 chstat[5]; +}; + +static_assert(sizeof(ps3av_pkt_av_audio_param) == 32); + +struct ps3av_pkt_av_video_cs { + ps3av_header hdr; + be_t avport; + be_t av_vid; + be_t av_cs_out; + be_t av_cs_in; + u8 dither; + u8 bitlen_out; + u8 super_white; + u8 aspect; + u8 unk1; + u8 unk2; + u8 resv[2]; +}; + +static_assert(sizeof(ps3av_pkt_av_video_cs) == 24); + +struct ps3av_pkt_video_mode { + ps3av_header hdr; + be_t video_head; + be_t unk1; + be_t unk2; + be_t video_vid; + be_t width; + be_t height; + be_t pitch; + be_t video_out_format; + be_t video_format; + be_t unk3; + be_t video_order; + be_t unk4; +}; + +static_assert(sizeof(ps3av_pkt_video_mode) == 48); + +struct ps3av_pkt_av_video_ytrapcontrol { + ps3av_header hdr; + be_t unk1; + be_t unk2; +}; + +static_assert(sizeof(ps3av_pkt_av_video_ytrapcontrol) == 12); + +struct ps3av_pkt_av_get_cec_config_reply { + be_t cec_present; +}; + +struct ps3av_pkt_video_format { + ps3av_header hdr; + be_t video_head; + be_t video_format; + be_t unk; + be_t video_order; +}; + +static_assert(sizeof(ps3av_pkt_video_format) == 20); + +struct ps3av_pkt_av_set_cgms_wss { + ps3av_header hdr; + be_t avport; + u8 resv[2]; + be_t cgms_wss; +}; + +static_assert(sizeof(ps3av_pkt_av_set_cgms_wss) == 16); + +struct ps3av_pkt_set_acp_packet { + ps3av_header hdr; + u8 avport; + u8 pkt_type; + u8 resv[2]; + u8 pkt_data[32]; +}; + +static_assert(sizeof(ps3av_pkt_set_acp_packet) == 44); + +struct ps3av_pkt_acp_ctrl { + ps3av_header hdr; + u8 avport; + u8 packetctl; + u8 resv[2]; +}; + +static_assert(sizeof(ps3av_pkt_acp_ctrl) == 12); + +struct ps3av_pkt_add_signal_ctl { + ps3av_header hdr; + be_t avport; + be_t signal_ctl; +}; + +static_assert(sizeof(ps3av_pkt_add_signal_ctl) == 12); + +struct ps3av_pkt_av_audio_mute { + ps3av_header hdr; + be_t avport; + be_t mute; +}; + +static_assert(sizeof(ps3av_pkt_av_audio_mute) == 12); + +struct ps3av_pkt_video_disable_sig { + ps3av_header hdr; + be_t avport; + be_t resv; +}; + +static_assert(sizeof(ps3av_pkt_video_disable_sig) == 12); + +// SysCalls + +error_code sys_uart_initialize(ppu_thread &ppu); +error_code sys_uart_receive(ppu_thread &ppu, vm::ptr buffer, u64 size, + u32 mode); +error_code sys_uart_send(ppu_thread &ppu, vm::cptr buffer, u64 size, + u32 mode); +error_code sys_uart_get_params(vm::ptr buffer); diff --git a/kernel/cellos/include/cellos/sys_usbd.h b/kernel/cellos/include/cellos/sys_usbd.h new file mode 100644 index 000000000..ae3472076 --- /dev/null +++ b/kernel/cellos/include/cellos/sys_usbd.h @@ -0,0 +1,118 @@ +#pragma once + +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/Memory/vm_ptr.h" +#include "Input/product_info.h" + +class ppu_thread; + +#define MAX_SYS_USBD_TRANSFERS 0x44 + +// PS3 internal codes +enum PS3StandardUsbErrors : u32 { + HC_CC_NOERR = 0x00, + EHCI_CC_MISSMF = 0x10, + EHCI_CC_XACT = 0x20, + EHCI_CC_BABBLE = 0x30, + EHCI_CC_DATABUF = 0x40, + EHCI_CC_HALTED = 0x50, +}; + +enum PS3IsochronousUsbErrors : u8 { + USBD_HC_CC_NOERR = 0x00, + USBD_HC_CC_MISSMF = 0x01, + USBD_HC_CC_XACT = 0x02, + USBD_HC_CC_BABBLE = 0x04, + USBD_HC_CC_DATABUF = 0x08, +}; + +enum SysUsbdEvents : u32 { + SYS_USBD_ATTACH = 0x01, + SYS_USBD_DETACH = 0x02, + SYS_USBD_TRANSFER_COMPLETE = 0x03, + SYS_USBD_TERMINATE = 0x04, +}; + +// PS3 internal structures +struct UsbInternalDevice { + u8 device_high; // System flag maybe (used in generating actual device number) + u8 device_low; // Just a number identifying the device (used in generating + // actual device number) + u8 unk3; // ? Seems to always be 2? + u8 unk4; // ? +}; + +struct UsbDeviceRequest { + u8 bmRequestType; + u8 bRequest; + be_t wValue; + be_t wIndex; + be_t wLength; +}; + +struct UsbDeviceIsoRequest { + vm::ptr buf; + be_t start_frame; + be_t num_packets; + be_t packets[8]; +}; + +error_code sys_usbd_initialize(ppu_thread &ppu, vm::ptr handle); +error_code sys_usbd_finalize(ppu_thread &ppu, u32 handle); +error_code sys_usbd_get_device_list(ppu_thread &ppu, u32 handle, + vm::ptr device_list, + u32 max_devices); +error_code sys_usbd_get_descriptor_size(ppu_thread &ppu, u32 handle, + u32 device_handle); +error_code sys_usbd_get_descriptor(ppu_thread &ppu, u32 handle, + u32 device_handle, vm::ptr descriptor, + u32 desc_size); +error_code sys_usbd_register_ldd(ppu_thread &ppu, u32 handle, + vm::cptr s_product, u16 slen_product); +error_code sys_usbd_unregister_ldd(ppu_thread &ppu, u32 handle, + vm::cptr s_product, u16 slen_product); +error_code sys_usbd_open_pipe(ppu_thread &ppu, u32 handle, u32 device_handle, + u32 unk1, u64 unk2, u64 unk3, u32 endpoint, + u64 unk4); +error_code sys_usbd_open_default_pipe(ppu_thread &ppu, u32 handle, + u32 device_handle); +error_code sys_usbd_close_pipe(ppu_thread &ppu, u32 handle, u32 pipe_handle); +error_code sys_usbd_receive_event(ppu_thread &ppu, u32 handle, + vm::ptr arg1, vm::ptr arg2, + vm::ptr arg3); +error_code sys_usbd_detect_event(ppu_thread &ppu); +error_code sys_usbd_attach(ppu_thread &ppu, u32 handle, u32 unk1, u32 unk2, + u32 device_handle); +error_code sys_usbd_transfer_data(ppu_thread &ppu, u32 handle, u32 id_pipe, + vm::ptr buf, u32 buf_size, + vm::ptr request, + u32 type_transfer); +error_code +sys_usbd_isochronous_transfer_data(ppu_thread &ppu, u32 handle, u32 id_pipe, + vm::ptr iso_request); +error_code sys_usbd_get_transfer_status(ppu_thread &ppu, u32 handle, + u32 id_transfer, u32 unk1, + vm::ptr result, + vm::ptr count); +error_code sys_usbd_get_isochronous_transfer_status( + ppu_thread &ppu, u32 handle, u32 id_transfer, u32 unk1, + vm::ptr request, vm::ptr result); +error_code sys_usbd_get_device_location(ppu_thread &ppu, u32 handle, + u32 device_handle, + vm::ptr location); +error_code sys_usbd_send_event(ppu_thread &ppu); +error_code sys_usbd_event_port_send(ppu_thread &ppu, u32 handle, u64 arg1, + u64 arg2, u64 arg3); +error_code sys_usbd_allocate_memory(ppu_thread &ppu); +error_code sys_usbd_free_memory(ppu_thread &ppu); +error_code sys_usbd_get_device_speed(ppu_thread &ppu); +error_code sys_usbd_register_extra_ldd(ppu_thread &ppu, u32 handle, + vm::cptr s_product, + u16 slen_product, u16 id_vendor, + u16 id_product_min, u16 id_product_max); +error_code sys_usbd_unregister_extra_ldd(ppu_thread &ppu, u32 handle, + vm::cptr s_product, + u16 slen_product); + +void connect_usb_controller(u8 index, input::product_type); +void handle_hotplug_event(bool connected); diff --git a/kernel/cellos/include/cellos/sys_vm.h b/kernel/cellos/include/cellos/sys_vm.h new file mode 100644 index 000000000..499709daf --- /dev/null +++ b/kernel/cellos/include/cellos/sys_vm.h @@ -0,0 +1,76 @@ +#pragma once + +#include "Emu/Memory/vm_ptr.h" +#include "sys_memory.h" + +#include + +enum : u64 { + SYS_VM_STATE_INVALID = 0ull, + SYS_VM_STATE_UNUSED = 1ull, + SYS_VM_STATE_ON_MEMORY = 2ull, + SYS_VM_STATE_STORED = 4ull, + + SYS_VM_POLICY_AUTO_RECOMMENDED = 1ull, +}; + +struct sys_vm_statistics_t { + be_t page_fault_ppu; // Number of bad virtual memory accesses from a PPU + // thread. + be_t page_fault_spu; // Number of bad virtual memory accesses from a SPU + // thread. + be_t page_in; // Number of virtual memory backup reading operations. + be_t page_out; // Number of virtual memory backup writing operations. + be_t pmem_total; // Total physical memory allocated for the virtual + // memory area. + be_t pmem_used; // Physical memory in use by the virtual memory area. + be_t timestamp; +}; + +// Block info +struct sys_vm_t { + static const u32 id_base = 0x1; + static const u32 id_step = 0x1; + static const u32 id_count = 16; + + lv2_memory_container *const ct; + const u32 addr; + const u32 size; + atomic_t psize; + + sys_vm_t(u32 addr, u32 vsize, lv2_memory_container *ct, u32 psize); + ~sys_vm_t(); + + SAVESTATE_INIT_POS(10); + + sys_vm_t(utils::serial &ar); + void save(utils::serial &ar); + + static std::array, id_count> g_ids; + + static u32 find_id(u32 addr) { return g_ids[addr >> 28].load(); } +}; + +// Aux +class ppu_thread; + +// SysCalls +error_code sys_vm_memory_map(ppu_thread &ppu, u64 vsize, u64 psize, u32 cid, + u64 flag, u64 policy, vm::ptr addr); +error_code sys_vm_memory_map_different(ppu_thread &ppu, u64 vsize, u64 psize, + u32 cid, u64 flag, u64 policy, + vm::ptr addr); +error_code sys_vm_unmap(ppu_thread &ppu, u32 addr); +error_code sys_vm_append_memory(ppu_thread &ppu, u32 addr, u64 size); +error_code sys_vm_return_memory(ppu_thread &ppu, u32 addr, u64 size); +error_code sys_vm_lock(ppu_thread &ppu, u32 addr, u32 size); +error_code sys_vm_unlock(ppu_thread &ppu, u32 addr, u32 size); +error_code sys_vm_touch(ppu_thread &ppu, u32 addr, u32 size); +error_code sys_vm_flush(ppu_thread &ppu, u32 addr, u32 size); +error_code sys_vm_invalidate(ppu_thread &ppu, u32 addr, u32 size); +error_code sys_vm_store(ppu_thread &ppu, u32 addr, u32 size); +error_code sys_vm_sync(ppu_thread &ppu, u32 addr, u32 size); +error_code sys_vm_test(ppu_thread &ppu, u32 addr, u32 size, + vm::ptr result); +error_code sys_vm_get_statistics(ppu_thread &ppu, u32 addr, + vm::ptr stat); diff --git a/kernel/cellos/src/lv2.cpp b/kernel/cellos/src/lv2.cpp new file mode 100644 index 000000000..68dec048f --- /dev/null +++ b/kernel/cellos/src/lv2.cpp @@ -0,0 +1,2523 @@ +#include "stdafx.h" + +#include "Emu/Memory/vm_locking.h" +#include "Emu/Memory/vm_ptr.h" +#include "Emu/Memory/vm_reservation.h" +#include "Emu/System.h" +#include "Emu/system_config.h" + +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/Cell/PPUFunction.h" +#include "Emu/Cell/PPUThread.h" +#include "Emu/Cell/SPUThread.h" +#include "sys_bdemu.h" +#include "sys_btsetting.h" +#include "sys_cond.h" +#include "sys_config.h" +#include "sys_console.h" +#include "sys_crypto_engine.h" +#include "sys_dbg.h" +#include "sys_event.h" +#include "sys_event_flag.h" +#include "sys_fs.h" +#include "sys_game.h" +#include "sys_gamepad.h" +#include "sys_gpio.h" +#include "sys_hid.h" +#include "sys_interrupt.h" +#include "sys_io.h" +#include "sys_lwcond.h" +#include "sys_lwmutex.h" +#include "sys_memory.h" +#include "sys_mmapper.h" +#include "sys_mutex.h" +#include "sys_net.h" +#include "sys_overlay.h" +#include "sys_ppu_thread.h" +#include "sys_process.h" +#include "sys_prx.h" +#include "sys_rsx.h" +#include "sys_rsxaudio.h" +#include "sys_rwlock.h" +#include "sys_semaphore.h" +#include "sys_sm.h" +#include "sys_spu.h" +#include "sys_ss.h" +#include "sys_storage.h" +#include "sys_sync.h" +#include "sys_time.h" +#include "sys_timer.h" +#include "sys_trace.h" +#include "sys_tty.h" +#include "sys_uart.h" +#include "sys_usbd.h" +#include "sys_vm.h" + +#include "util/init_mutex.hpp" +#include "util/sysinfo.hpp" +#include "util/tsc.hpp" +#include +#include +#include +#include + +#if defined(ARCH_X64) +#ifdef _MSC_VER +#include +#include +#else +#include +#endif +#endif + +extern std::string ppu_get_syscall_name(u64 code); + +namespace rsx { +void set_rsx_yield_flag() noexcept; +} + +using spu_rdata_t = decltype(spu_thread::rdata); +extern u32 compute_rdata_hash32(const spu_rdata_t &_src); + +template <> +void fmt_class_string::format(std::string &out, u64 arg) { + out += ppu_get_syscall_name(arg); +} + +template <> +void fmt_class_string::format(std::string &out, u64 arg) { + format_enum(out, arg, [](auto value) { + switch (value) { + case SYS_SYNC_FIFO: + return "FIFO"; + case SYS_SYNC_PRIORITY: + return "PRIO"; + case SYS_SYNC_PRIORITY_INHERIT: + return "PRIO-INHER"; + case SYS_SYNC_RETRY: + return "RETRY"; + } + + return unknown; + }); +} + +template <> +void fmt_class_string::format(std::string &out, u64 arg) { + out += lv2_obj::name64(get_object(arg).data); +} + +static void null_func_(ppu_thread &ppu, ppu_opcode_t, be_t *this_op, + ppu_intrp_func *) { + ppu_log.todo( + "Unimplemented syscall %s -> CELL_OK (r3=0x%llx, r4=0x%llx, r5=0x%llx, " + "r6=0x%llx, r7=0x%llx, r8=0x%llx, r9=0x%llx, r10=0x%llx)", + ppu_syscall_code(ppu.gpr[11]), ppu.gpr[3], ppu.gpr[4], ppu.gpr[5], + ppu.gpr[6], ppu.gpr[7], ppu.gpr[8], ppu.gpr[9], ppu.gpr[10]); + + ppu.gpr[3] = 0; + ppu.cia = vm::get_addr(this_op) + 4; +} + +static void uns_func_(ppu_thread &ppu, ppu_opcode_t, be_t *this_op, + ppu_intrp_func *) { + ppu_log.trace("Unused syscall %d -> ENOSYS", ppu.gpr[11]); + ppu.gpr[3] = CELL_ENOSYS; + ppu.cia = vm::get_addr(this_op) + 4; +} + +// Bind Syscall +#define BIND_SYSC(func) {BIND_FUNC(func), #func} +#define NULL_FUNC(name) {null_func_, #name} + +constexpr std::pair null_func{null_func_, + ""}; +constexpr std::pair uns_func{uns_func_, ""}; + +// UNS = Unused +// ROOT = Root +// DBG = Debug +// DEX..DECR = Unavailable on retail consoles +// PM = Product Mode +// AuthID = Authentication ID +const std::array, 1024> + g_ppu_syscall_table{ + null_func, + BIND_SYSC(sys_process_getpid), // 1 (0x001) + BIND_SYSC(sys_process_wait_for_child), // 2 (0x002) ROOT + BIND_SYSC(sys_process_exit3), // 3 (0x003) + BIND_SYSC(sys_process_get_status), // 4 (0x004) DBG + BIND_SYSC(sys_process_detach_child), // 5 (0x005) DBG + + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, // 6-11 UNS + + BIND_SYSC(sys_process_get_number_of_object), // 12 (0x00C) + BIND_SYSC(sys_process_get_id), // 13 (0x00D) + BIND_SYSC( + sys_process_is_spu_lock_line_reservation_address), // 14 (0x00E) + + uns_func, + uns_func, + uns_func, // 15-17 UNS + + BIND_SYSC(sys_process_getppid), // 18 (0x012) + BIND_SYSC(sys_process_kill), // 19 (0x013) + uns_func, // 20 (0x014) UNS + NULL_FUNC(_sys_process_spawn), // 21 (0x015) DBG + BIND_SYSC(_sys_process_exit), // 22 (0x016) + BIND_SYSC(sys_process_wait_for_child2), // 23 (0x017) DBG + null_func, // BIND_SYSC(), //24 (0x018) + // DBG + BIND_SYSC(sys_process_get_sdk_version), // 25 (0x019) + BIND_SYSC(_sys_process_exit2), // 26 (0x01A) + BIND_SYSC(sys_process_spawns_a_self2), // 27 (0x01B) DBG + NULL_FUNC(_sys_process_get_number_of_object), // 28 (0x01C) ROOT + BIND_SYSC(sys_process_get_id2), // 29 (0x01D) ROOT + BIND_SYSC(_sys_process_get_paramsfo), // 30 (0x01E) + NULL_FUNC(sys_process_get_ppu_guid), // 31 (0x01F) + + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, // 32-40 UNS + + BIND_SYSC(_sys_ppu_thread_exit), // 41 (0x029) + uns_func, // 42 (0x02A) UNS + BIND_SYSC(sys_ppu_thread_yield), // 43 (0x02B) + BIND_SYSC(sys_ppu_thread_join), // 44 (0x02C) + BIND_SYSC(sys_ppu_thread_detach), // 45 (0x02D) + BIND_SYSC(sys_ppu_thread_get_join_state), // 46 (0x02E) + BIND_SYSC(sys_ppu_thread_set_priority), // 47 (0x02F) DBG + BIND_SYSC(sys_ppu_thread_get_priority), // 48 (0x030) + BIND_SYSC(sys_ppu_thread_get_stack_information), // 49 (0x031) + BIND_SYSC(sys_ppu_thread_stop), // 50 (0x032) ROOT + BIND_SYSC(sys_ppu_thread_restart), // 51 (0x033) ROOT + BIND_SYSC(_sys_ppu_thread_create), // 52 (0x034) DBG + BIND_SYSC(sys_ppu_thread_start), // 53 (0x035) + null_func, // BIND_SYSC(sys_ppu_...), //54 (0x036) + // ROOT + null_func, // BIND_SYSC(sys_ppu_...), //55 (0x037) + // ROOT + BIND_SYSC(sys_ppu_thread_rename), // 56 (0x038) + BIND_SYSC(sys_ppu_thread_recover_page_fault), // 57 (0x039) + BIND_SYSC(sys_ppu_thread_get_page_fault_context), // 58 (0x03A) + uns_func, // 59 (0x03B) UNS + BIND_SYSC(sys_trace_create), // 60 (0x03C) + BIND_SYSC(sys_trace_start), // 61 (0x03D) + BIND_SYSC(sys_trace_stop), // 62 (0x03E) + BIND_SYSC(sys_trace_update_top_index), // 63 (0x03F) + BIND_SYSC(sys_trace_destroy), // 64 (0x040) + BIND_SYSC(sys_trace_drain), // 65 (0x041) + BIND_SYSC(sys_trace_attach_process), // 66 (0x042) + BIND_SYSC(sys_trace_allocate_buffer), // 67 (0x043) + BIND_SYSC(sys_trace_free_buffer), // 68 (0x044) + BIND_SYSC(sys_trace_create2), // 69 (0x045) + BIND_SYSC(sys_timer_create), // 70 (0x046) + BIND_SYSC(sys_timer_destroy), // 71 (0x047) + BIND_SYSC(sys_timer_get_information), // 72 (0x048) + BIND_SYSC(_sys_timer_start), // 73 (0x049) + BIND_SYSC(sys_timer_stop), // 74 (0x04A) + BIND_SYSC(sys_timer_connect_event_queue), // 75 (0x04B) + BIND_SYSC(sys_timer_disconnect_event_queue), // 76 (0x04C) + NULL_FUNC(sys_trace_create2_in_cbepm), // 77 (0x04D) + null_func, // BIND_SYSC(sys_trace_...), //78 (0x04E) + uns_func, // 79 (0x04F) UNS + NULL_FUNC(sys_interrupt_tag_create), // 80 (0x050) + BIND_SYSC(sys_interrupt_tag_destroy), // 81 (0x051) + BIND_SYSC(sys_event_flag_create), // 82 (0x052) + BIND_SYSC(sys_event_flag_destroy), // 83 (0x053) + BIND_SYSC(_sys_interrupt_thread_establish), // 84 (0x054) + BIND_SYSC(sys_event_flag_wait), // 85 (0x055) + BIND_SYSC(sys_event_flag_trywait), // 86 (0x056) + BIND_SYSC(sys_event_flag_set), // 87 (0x057) + BIND_SYSC(sys_interrupt_thread_eoi), // 88 (0x058) + BIND_SYSC(_sys_interrupt_thread_disestablish), // 89 (0x059) + BIND_SYSC(sys_semaphore_create), // 90 (0x05A) + BIND_SYSC(sys_semaphore_destroy), // 91 (0x05B) + BIND_SYSC(sys_semaphore_wait), // 92 (0x05C) + BIND_SYSC(sys_semaphore_trywait), // 93 (0x05D) + BIND_SYSC(sys_semaphore_post), // 94 (0x05E) + BIND_SYSC(_sys_lwmutex_create), // 95 (0x05F) + BIND_SYSC(_sys_lwmutex_destroy), // 96 (0x060) + BIND_SYSC(_sys_lwmutex_lock), // 97 (0x061) + BIND_SYSC(_sys_lwmutex_unlock), // 98 (0x062) + BIND_SYSC(_sys_lwmutex_trylock), // 99 (0x063) + BIND_SYSC(sys_mutex_create), // 100 (0x064) + BIND_SYSC(sys_mutex_destroy), // 101 (0x065) + BIND_SYSC(sys_mutex_lock), // 102 (0x066) + BIND_SYSC(sys_mutex_trylock), // 103 (0x067) + BIND_SYSC(sys_mutex_unlock), // 104 (0x068) + BIND_SYSC(sys_cond_create), // 105 (0x069) + BIND_SYSC(sys_cond_destroy), // 106 (0x06A) + BIND_SYSC(sys_cond_wait), // 107 (0x06B) + BIND_SYSC(sys_cond_signal), // 108 (0x06C) + BIND_SYSC(sys_cond_signal_all), // 109 (0x06D) + BIND_SYSC(sys_cond_signal_to), // 110 (0x06E) + BIND_SYSC(_sys_lwcond_create), // 111 (0x06F) + BIND_SYSC(_sys_lwcond_destroy), // 112 (0x070) + BIND_SYSC(_sys_lwcond_queue_wait), // 113 (0x071) + BIND_SYSC(sys_semaphore_get_value), // 114 (0x072) + BIND_SYSC(_sys_lwcond_signal), // 115 (0x073) + BIND_SYSC(_sys_lwcond_signal_all), // 116 (0x074) + BIND_SYSC(_sys_lwmutex_unlock2), // 117 (0x075) + BIND_SYSC(sys_event_flag_clear), // 118 (0x076) + BIND_SYSC(sys_time_get_rtc), // 119 (0x077) ROOT + BIND_SYSC(sys_rwlock_create), // 120 (0x078) + BIND_SYSC(sys_rwlock_destroy), // 121 (0x079) + BIND_SYSC(sys_rwlock_rlock), // 122 (0x07A) + BIND_SYSC(sys_rwlock_tryrlock), // 123 (0x07B) + BIND_SYSC(sys_rwlock_runlock), // 124 (0x07C) + BIND_SYSC(sys_rwlock_wlock), // 125 (0x07D) + BIND_SYSC(sys_rwlock_trywlock), // 126 (0x07E) + BIND_SYSC(sys_rwlock_wunlock), // 127 (0x07F) + BIND_SYSC(sys_event_queue_create), // 128 (0x080) + BIND_SYSC(sys_event_queue_destroy), // 129 (0x081) + BIND_SYSC(sys_event_queue_receive), // 130 (0x082) + BIND_SYSC(sys_event_queue_tryreceive), // 131 (0x083) + BIND_SYSC(sys_event_flag_cancel), // 132 (0x084) + BIND_SYSC(sys_event_queue_drain), // 133 (0x085) + BIND_SYSC(sys_event_port_create), // 134 (0x086) + BIND_SYSC(sys_event_port_destroy), // 135 (0x087) + BIND_SYSC(sys_event_port_connect_local), // 136 (0x088) + BIND_SYSC(sys_event_port_disconnect), // 137 (0x089) + BIND_SYSC(sys_event_port_send), // 138 (0x08A) + BIND_SYSC(sys_event_flag_get), // 139 (0x08B) + BIND_SYSC(sys_event_port_connect_ipc), // 140 (0x08C) + BIND_SYSC(sys_timer_usleep), // 141 (0x08D) + BIND_SYSC(sys_timer_sleep), // 142 (0x08E) + BIND_SYSC(sys_time_set_timezone), // 143 (0x08F) ROOT + BIND_SYSC(sys_time_get_timezone), // 144 (0x090) + BIND_SYSC(sys_time_get_current_time), // 145 (0x091) + BIND_SYSC(sys_time_set_current_time), // 146 (0x092) ROOT + BIND_SYSC(sys_time_get_timebase_frequency), // 147 (0x093) + BIND_SYSC(_sys_rwlock_trywlock), // 148 (0x094) + NULL_FUNC(sys_time_get_system_time), // 149 (0x095) + BIND_SYSC(sys_raw_spu_create_interrupt_tag), // 150 (0x096) + BIND_SYSC(sys_raw_spu_set_int_mask), // 151 (0x097) + BIND_SYSC(sys_raw_spu_get_int_mask), // 152 (0x098) + BIND_SYSC(sys_raw_spu_set_int_stat), // 153 (0x099) + BIND_SYSC(sys_raw_spu_get_int_stat), // 154 (0x09A) + BIND_SYSC(_sys_spu_image_get_information), // 155 (0x09B) + BIND_SYSC(sys_spu_image_open), // 156 (0x09C) + BIND_SYSC(_sys_spu_image_import), // 157 (0x09D) + BIND_SYSC(_sys_spu_image_close), // 158 (0x09E) + BIND_SYSC(_sys_spu_image_get_segments), // 159 (0x09F) + BIND_SYSC(sys_raw_spu_create), // 160 (0x0A0) + BIND_SYSC(sys_raw_spu_destroy), // 161 (0x0A1) + uns_func, // 162 (0x0A2) UNS + BIND_SYSC(sys_raw_spu_read_puint_mb), // 163 (0x0A3) + uns_func, // 164 (0x0A4) UNS + BIND_SYSC(sys_spu_thread_get_exit_status), // 165 (0x0A5) + BIND_SYSC(sys_spu_thread_set_argument), // 166 (0x0A6) + NULL_FUNC(sys_spu_thread_group_start_on_exit), // 167 (0x0A7) + uns_func, // 168 (0x0A8) UNS + BIND_SYSC(sys_spu_initialize), // 169 (0x0A9) + BIND_SYSC(sys_spu_thread_group_create), // 170 (0x0AA) + BIND_SYSC(sys_spu_thread_group_destroy), // 171 (0x0AB) + BIND_SYSC(sys_spu_thread_initialize), // 172 (0x0AC) + BIND_SYSC(sys_spu_thread_group_start), // 173 (0x0AD) + BIND_SYSC(sys_spu_thread_group_suspend), // 174 (0x0AE) + BIND_SYSC(sys_spu_thread_group_resume), // 175 (0x0AF) + BIND_SYSC(sys_spu_thread_group_yield), // 176 (0x0B0) + BIND_SYSC(sys_spu_thread_group_terminate), // 177 (0x0B1) + BIND_SYSC(sys_spu_thread_group_join), // 178 (0x0B2) + BIND_SYSC(sys_spu_thread_group_set_priority), // 179 (0x0B3) + BIND_SYSC(sys_spu_thread_group_get_priority), // 180 (0x0B4) + BIND_SYSC(sys_spu_thread_write_ls), // 181 (0x0B5) + BIND_SYSC(sys_spu_thread_read_ls), // 182 (0x0B6) + uns_func, // 183 (0x0B7) UNS + BIND_SYSC(sys_spu_thread_write_snr), // 184 (0x0B8) + BIND_SYSC(sys_spu_thread_group_connect_event), // 185 (0x0B9) + BIND_SYSC(sys_spu_thread_group_disconnect_event), // 186 (0x0BA) + BIND_SYSC(sys_spu_thread_set_spu_cfg), // 187 (0x0BB) + BIND_SYSC(sys_spu_thread_get_spu_cfg), // 188 (0x0BC) + uns_func, // 189 (0x0BD) UNS + BIND_SYSC(sys_spu_thread_write_spu_mb), // 190 (0x0BE) + BIND_SYSC(sys_spu_thread_connect_event), // 191 (0x0BF) + BIND_SYSC(sys_spu_thread_disconnect_event), // 192 (0x0C0) + BIND_SYSC(sys_spu_thread_bind_queue), // 193 (0x0C1) + BIND_SYSC(sys_spu_thread_unbind_queue), // 194 (0x0C2) + uns_func, // 195 (0x0C3) UNS + BIND_SYSC(sys_raw_spu_set_spu_cfg), // 196 (0x0C4) + BIND_SYSC(sys_raw_spu_get_spu_cfg), // 197 (0x0C5) + BIND_SYSC(sys_spu_thread_recover_page_fault), // 198 (0x0C6) + BIND_SYSC(sys_raw_spu_recover_page_fault), // 199 (0x0C7) + + null_func, + null_func, + null_func, + null_func, + null_func, // 204 UNS? + null_func, + null_func, + null_func, + null_func, + null_func, // 209 UNS? + null_func, + null_func, + null_func, // 212 UNS? + BIND_SYSC(sys_console_write2), // 213 (0x0D5) + null_func, // 214 UNS? + + NULL_FUNC(sys_dbg_mat_set_condition), // 215 (0x0D7) + NULL_FUNC(sys_dbg_mat_get_condition), // 216 (0x0D8) + uns_func, // BIND_SYSC(sys_dbg_...), //217 (0x0D9) + // DBG UNS? + uns_func, // BIND_SYSC(sys_dbg_...), //218 (0x0DA) + // DBG UNS? + uns_func, // BIND_SYSC(sys_dbg_...), //219 (0x0DB) + // DBG UNS? + + null_func, + null_func, + null_func, + null_func, + null_func, // 224 UNS + null_func, + null_func, + null_func, + null_func, + null_func, // 229 UNS? + + BIND_SYSC(sys_isolated_spu_create), // 230 (0x0E6) ROOT + BIND_SYSC(sys_isolated_spu_destroy), // 231 (0x0E7) ROOT + BIND_SYSC(sys_isolated_spu_start), // 232 (0x0E8) ROOT + BIND_SYSC(sys_isolated_spu_create_interrupt_tag), // 233 (0x0E9) ROOT + BIND_SYSC(sys_isolated_spu_set_int_mask), // 234 (0x0EA) ROOT + BIND_SYSC(sys_isolated_spu_get_int_mask), // 235 (0x0EB) ROOT + BIND_SYSC(sys_isolated_spu_set_int_stat), // 236 (0x0EC) ROOT + BIND_SYSC(sys_isolated_spu_get_int_stat), // 237 (0x0ED) ROOT + BIND_SYSC(sys_isolated_spu_set_spu_cfg), // 238 (0x0EE) ROOT + BIND_SYSC(sys_isolated_spu_get_spu_cfg), // 239 (0x0EF) ROOT + BIND_SYSC(sys_isolated_spu_read_puint_mb), // 240 (0x0F0) ROOT + uns_func, + uns_func, + uns_func, // 241-243 ROOT UNS + NULL_FUNC( + sys_spu_thread_group_system_set_next_group), // 244 (0x0F4) ROOT + NULL_FUNC( + sys_spu_thread_group_system_unset_next_group), // 245 (0x0F5) ROOT + NULL_FUNC( + sys_spu_thread_group_system_set_switch_group), // 246 (0x0F6) ROOT + NULL_FUNC( + sys_spu_thread_group_system_unset_switch_group), // 247 (0x0F7) ROOT + null_func, // BIND_SYSC(sys_spu_thread_group...), //248 (0x0F8) + // ROOT + null_func, // BIND_SYSC(sys_spu_thread_group...), //249 (0x0F9) + // ROOT + BIND_SYSC(sys_spu_thread_group_set_cooperative_victims), // 250 (0x0FA) + BIND_SYSC( + sys_spu_thread_group_connect_event_all_threads), // 251 (0x0FB) + BIND_SYSC( + sys_spu_thread_group_disconnect_event_all_threads), // 252 (0x0FC) + BIND_SYSC(sys_spu_thread_group_syscall_253), // 253 (0x0FD) + BIND_SYSC(sys_spu_thread_group_log), // 254 (0x0FE) + + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, // 255-259 UNS + + NULL_FUNC(sys_spu_image_open_by_fd), // 260 (0x104) + + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, // 261-269 UNS + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, // 270-279 UNS + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, // 280-289 UNS + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, // 290-299 UNS + + BIND_SYSC(sys_vm_memory_map), // 300 (0x12C) + BIND_SYSC(sys_vm_unmap), // 301 (0x12D) + BIND_SYSC(sys_vm_append_memory), // 302 (0x12E) + BIND_SYSC(sys_vm_return_memory), // 303 (0x12F) + BIND_SYSC(sys_vm_lock), // 304 (0x130) + BIND_SYSC(sys_vm_unlock), // 305 (0x131) + BIND_SYSC(sys_vm_touch), // 306 (0x132) + BIND_SYSC(sys_vm_flush), // 307 (0x133) + BIND_SYSC(sys_vm_invalidate), // 308 (0x134) + BIND_SYSC(sys_vm_store), // 309 (0x135) + BIND_SYSC(sys_vm_sync), // 310 (0x136) + BIND_SYSC(sys_vm_test), // 311 (0x137) + BIND_SYSC(sys_vm_get_statistics), // 312 (0x138) + BIND_SYSC(sys_vm_memory_map_different), // 313 (0x139) + null_func, // BIND_SYSC(sys_...), //314 (0x13A) + null_func, // BIND_SYSC(sys_...), //315 (0x13B) + + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, // 316-323 UNS + + BIND_SYSC(sys_memory_container_create), // 324 (0x144) DBG + BIND_SYSC(sys_memory_container_destroy), // 325 (0x145) DBG + BIND_SYSC(sys_mmapper_allocate_fixed_address), // 326 (0x146) + BIND_SYSC(sys_mmapper_enable_page_fault_notification), // 327 (0x147) + BIND_SYSC( + sys_mmapper_allocate_shared_memory_from_container_ext), // 328 + // (0x148) + BIND_SYSC(sys_mmapper_free_shared_memory), // 329 (0x149) + BIND_SYSC(sys_mmapper_allocate_address), // 330 (0x14A) + BIND_SYSC(sys_mmapper_free_address), // 331 (0x14B) + BIND_SYSC(sys_mmapper_allocate_shared_memory), // 332 (0x14C) + NULL_FUNC(sys_mmapper_set_shared_memory_flag), // 333(0x14D) + BIND_SYSC(sys_mmapper_map_shared_memory), // 334 (0x14E) + BIND_SYSC(sys_mmapper_unmap_shared_memory), // 335 (0x14F) + BIND_SYSC(sys_mmapper_change_address_access_right), // 336 (0x150) + BIND_SYSC(sys_mmapper_search_and_map), // 337 (0x151) + NULL_FUNC(sys_mmapper_get_shared_memory_attribute), // 338 (0x152) + BIND_SYSC(sys_mmapper_allocate_shared_memory_ext), // 339 (0x153) + null_func, // BIND_SYSC(sys_...), //340 (0x154) + BIND_SYSC(sys_memory_container_create), // 341 (0x155) + BIND_SYSC(sys_memory_container_destroy), // 342 (0x156) + BIND_SYSC(sys_memory_container_get_size), // 343 (0x157) + NULL_FUNC(sys_memory_budget_set), // 344 (0x158) + BIND_SYSC( + sys_memory_container_destroy_parent_with_childs), // 345 (0x159) + null_func, // BIND_SYSC(sys_memory_...), //346 (0x15A) + uns_func, // 347 (0x15B) UNS + BIND_SYSC(sys_memory_allocate), // 348 (0x15C) + BIND_SYSC(sys_memory_free), // 349 (0x15D) + BIND_SYSC(sys_memory_allocate_from_container), // 350 (0x15E) + BIND_SYSC(sys_memory_get_page_attribute), // 351 (0x15F) + BIND_SYSC(sys_memory_get_user_memory_size), // 352 (0x160) + BIND_SYSC(sys_memory_get_user_memory_stat), // 353 (0x161) + null_func, // BIND_SYSC(sys_memory_...), //354 (0x162) + null_func, // BIND_SYSC(sys_memory_...), //355 (0x163) + NULL_FUNC(sys_memory_allocate_colored), // 356 (0x164) + null_func, // BIND_SYSC(sys_memory_...), //357 (0x165) + null_func, // BIND_SYSC(sys_memory_...), //358 (0x166) + null_func, // BIND_SYSC(sys_memory_...), //359 (0x167) + null_func, // BIND_SYSC(sys_memory_...), //360 (0x168) + NULL_FUNC(sys_memory_allocate_from_container_colored), // 361 (0x169) + BIND_SYSC( + sys_mmapper_allocate_shared_memory_from_container), // 362 (0x16A) + null_func, // BIND_SYSC(sys_mmapper_...), //363 (0x16B) + null_func, // BIND_SYSC(sys_mmapper_...), //364 (0x16C) + uns_func, + uns_func, // 366 (0x16E) UNS + BIND_SYSC(sys_uart_initialize), // 367 (0x16F) ROOT + BIND_SYSC(sys_uart_receive), // 368 (0x170) ROOT + BIND_SYSC(sys_uart_send), // 369 (0x171) ROOT + BIND_SYSC(sys_uart_get_params), // 370 (0x172) ROOT + uns_func, // 371 (0x173) UNS + BIND_SYSC(_sys_game_watchdog_start), // 372 (0x174) + BIND_SYSC(_sys_game_watchdog_stop), // 373 (0x175) + BIND_SYSC(_sys_game_watchdog_clear), // 374 (0x176) + BIND_SYSC(_sys_game_set_system_sw_version), // 375 (0x177) ROOT + BIND_SYSC(_sys_game_get_system_sw_version), // 376 (0x178) ROOT + BIND_SYSC(sys_sm_set_shop_mode), // 377 (0x179) ROOT + BIND_SYSC(sys_sm_get_ext_event2), // 378 (0x17A) ROOT + BIND_SYSC(sys_sm_shutdown), // 379 (0x17B) ROOT + BIND_SYSC(sys_sm_get_params), // 380 (0x17C) DBG + NULL_FUNC(sys_sm_get_inter_lpar_parameter), // 381 (0x17D) ROOT + NULL_FUNC(sys_sm_initialize), // 382 (0x17E) ROOT + NULL_FUNC(sys_game_get_temperature), // 383 (0x17F) ROOT + NULL_FUNC(sys_sm_get_tzpb), // 384 (0x180) ROOT + NULL_FUNC(sys_sm_request_led), // 385 (0x181) ROOT + BIND_SYSC(sys_sm_control_led), // 386 (0x182) ROOT + NULL_FUNC(sys_sm_get_system_info), // 387 (0x183) DBG + BIND_SYSC(sys_sm_ring_buzzer2), // 388 (0x184) ROOT + NULL_FUNC(sys_sm_set_fan_policy), // 389 (0x185) PM + NULL_FUNC(sys_sm_request_error_log), // 390 (0x186) ROOT + NULL_FUNC(sys_sm_request_be_count), // 391 (0x187) ROOT + BIND_SYSC(sys_sm_ring_buzzer), // 392 (0x188) ROOT + NULL_FUNC(sys_sm_get_hw_config), // 393 (0x189) ROOT + NULL_FUNC(sys_sm_request_scversion), // 394 (0x18A) ROOT + NULL_FUNC(sys_sm_request_system_event_log), // 395 (0x18B) PM + NULL_FUNC(sys_sm_set_rtc_alarm), // 396 (0x18C) ROOT + NULL_FUNC(sys_sm_get_rtc_alarm), // 397 (0x18D) ROOT + BIND_SYSC(sys_console_write), // 398 (0x18E) ROOT + uns_func, // 399 (0x18F) UNS + null_func, // BIND_SYSC(sys_sm_...), //400 (0x190) + // PM + null_func, // BIND_SYSC(sys_sm_...), //401 (0x191) + // ROOT + BIND_SYSC(sys_tty_read), // 402 (0x192) + BIND_SYSC(sys_tty_write), // 403 (0x193) + null_func, // BIND_SYSC(sys_...), //404 (0x194) + // ROOT + null_func, // BIND_SYSC(sys_...), //405 (0x195) + // PM + null_func, // BIND_SYSC(sys_...), //406 (0x196) + // PM + null_func, // BIND_SYSC(sys_...), //407 (0x197) + // PM + NULL_FUNC(sys_sm_get_tzpb), // 408 (0x198) PM + NULL_FUNC(sys_sm_get_fan_policy), // 409 (0x199) PM + BIND_SYSC(_sys_game_board_storage_read), // 410 (0x19A) + BIND_SYSC(_sys_game_board_storage_write), // 411 (0x19B) + BIND_SYSC(_sys_game_get_rtc_status), // 412 (0x19C) + null_func, // BIND_SYSC(sys_...), //413 (0x19D) + // ROOT + null_func, // BIND_SYSC(sys_...), //414 (0x19E) + // ROOT + null_func, // BIND_SYSC(sys_...), //415 (0x19F) + // ROOT + + uns_func, + uns_func, + uns_func, + uns_func, // 416-419 UNS + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, // 420-429 UNS + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, // 430-439 UNS + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, // 440-449 UNS + + BIND_SYSC(sys_overlay_load_module), // 450 (0x1C2) + BIND_SYSC(sys_overlay_unload_module), // 451 (0x1C3) + NULL_FUNC(sys_overlay_get_module_list), // 452 (0x1C4) + NULL_FUNC(sys_overlay_get_module_info), // 453 (0x1C5) + BIND_SYSC(sys_overlay_load_module_by_fd), // 454 (0x1C6) + NULL_FUNC(sys_overlay_get_module_info2), // 455 (0x1C7) + NULL_FUNC(sys_overlay_get_sdk_version), // 456 (0x1C8) + NULL_FUNC(sys_overlay_get_module_dbg_info), // 457 (0x1C9) + NULL_FUNC(sys_overlay_get_module_dbg_info), // 458 (0x1CA) + uns_func, // 459 (0x1CB) UNS + NULL_FUNC(sys_prx_dbg_get_module_id_list), // 460 (0x1CC) ROOT + BIND_SYSC(_sys_prx_get_module_id_by_address), // 461 (0x1CD) + uns_func, // 462 (0x1CE) DEX + BIND_SYSC(_sys_prx_load_module_by_fd), // 463 (0x1CF) + BIND_SYSC(_sys_prx_load_module_on_memcontainer_by_fd), // 464 (0x1D0) + BIND_SYSC(_sys_prx_load_module_list), // 465 (0x1D1) + BIND_SYSC(_sys_prx_load_module_list_on_memcontainer), // 466 (0x1D2) + BIND_SYSC(sys_prx_get_ppu_guid), // 467 (0x1D3) + null_func, // BIND_SYSC(sys_...), //468 (0x1D4) + // ROOT + uns_func, // 469 (0x1D5) UNS + NULL_FUNC(sys_npdrm_check_ekc), // 470 (0x1D6) ROOT + NULL_FUNC(sys_npdrm_regist_ekc), // 471 (0x1D7) ROOT + null_func, // BIND_SYSC(sys_...), //472 (0x1D8) + // ROOT + null_func, // BIND_SYSC(sys_...), //473 (0x1D9) + null_func, // BIND_SYSC(sys_...), //474 (0x1DA) + null_func, // BIND_SYSC(sys_...), //475 (0x1DB) + // ROOT + null_func, // BIND_SYSC(sys_...), //476 (0x1DC) + // ROOT + + uns_func, + uns_func, + uns_func, // 477-479 UNS + + BIND_SYSC(_sys_prx_load_module), // 480 (0x1E0) + BIND_SYSC(_sys_prx_start_module), // 481 (0x1E1) + BIND_SYSC(_sys_prx_stop_module), // 482 (0x1E2) + BIND_SYSC(_sys_prx_unload_module), // 483 (0x1E3) + BIND_SYSC(_sys_prx_register_module), // 484 (0x1E4) + BIND_SYSC(_sys_prx_query_module), // 485 (0x1E5) + BIND_SYSC(_sys_prx_register_library), // 486 (0x1E6) + BIND_SYSC(_sys_prx_unregister_library), // 487 (0x1E7) + BIND_SYSC(_sys_prx_link_library), // 488 (0x1E8) + BIND_SYSC(_sys_prx_unlink_library), // 489 (0x1E9) + BIND_SYSC(_sys_prx_query_library), // 490 (0x1EA) + uns_func, // 491 (0x1EB) UNS + NULL_FUNC(sys_prx_dbg_get_module_list), // 492 (0x1EC) DBG + NULL_FUNC(sys_prx_dbg_get_module_info), // 493 (0x1ED) DBG + BIND_SYSC(_sys_prx_get_module_list), // 494 (0x1EE) + BIND_SYSC(_sys_prx_get_module_info), // 495 (0x1EF) + BIND_SYSC(_sys_prx_get_module_id_by_name), // 496 (0x1F0) + BIND_SYSC(_sys_prx_load_module_on_memcontainer), // 497 (0x1F1) + BIND_SYSC(_sys_prx_start), // 498 (0x1F2) + BIND_SYSC(_sys_prx_stop), // 499 (0x1F3) + BIND_SYSC(sys_hid_manager_open), // 500 (0x1F4) + NULL_FUNC(sys_hid_manager_close), // 501 (0x1F5) + BIND_SYSC(sys_hid_manager_read), // 502 (0x1F6) ROOT + BIND_SYSC(sys_hid_manager_ioctl), // 503 (0x1F7) + NULL_FUNC( + sys_hid_manager_map_logical_id_to_port_id), // 504 (0x1F8) ROOT + NULL_FUNC( + sys_hid_manager_unmap_logical_id_to_port_id), // 505 (0x1F9) ROOT + BIND_SYSC(sys_hid_manager_add_hot_key_observer), // 506 (0x1FA) ROOT + NULL_FUNC(sys_hid_manager_remove_hot_key_observer), // 507 (0x1FB) ROOT + NULL_FUNC(sys_hid_manager_grab_focus), // 508 (0x1FC) ROOT + NULL_FUNC(sys_hid_manager_release_focus), // 509 (0x1FD) ROOT + BIND_SYSC(sys_hid_manager_check_focus), // 510 (0x1FE) + NULL_FUNC(sys_hid_manager_set_master_process), // 511 (0x1FF) ROOT + BIND_SYSC( + sys_hid_manager_is_process_permission_root), // 512 (0x200) ROOT + BIND_SYSC(sys_hid_manager_513), // 513 (0x201) + BIND_SYSC(sys_hid_manager_514), // 514 (0x202) + uns_func, // 515 (0x203) UNS + BIND_SYSC(sys_config_open), // 516 (0x204) + BIND_SYSC(sys_config_close), // 517 (0x205) + BIND_SYSC(sys_config_get_service_event), // 518 (0x206) + BIND_SYSC(sys_config_add_service_listener), // 519 (0x207) + BIND_SYSC(sys_config_remove_service_listener), // 520 (0x208) + BIND_SYSC(sys_config_register_service), // 521 (0x209) + BIND_SYSC(sys_config_unregister_service), // 522 (0x20A) + BIND_SYSC(sys_config_get_io_event), // 523 (0x20B) + BIND_SYSC(sys_config_register_io_error_listener), // 524 (0x20C) + BIND_SYSC(sys_config_unregister_io_error_listener), // 525 (0x20D) + uns_func, + uns_func, + uns_func, + uns_func, // 526-529 UNS + BIND_SYSC(sys_usbd_initialize), // 530 (0x212) + BIND_SYSC(sys_usbd_finalize), // 531 (0x213) + BIND_SYSC(sys_usbd_get_device_list), // 532 (0x214) + BIND_SYSC(sys_usbd_get_descriptor_size), // 533 (0x215) + BIND_SYSC(sys_usbd_get_descriptor), // 534 (0x216) + BIND_SYSC(sys_usbd_register_ldd), // 535 (0x217) + BIND_SYSC(sys_usbd_unregister_ldd), // 536 (0x218) + BIND_SYSC(sys_usbd_open_pipe), // 537 (0x219) + BIND_SYSC(sys_usbd_open_default_pipe), // 538 (0x21A) + BIND_SYSC(sys_usbd_close_pipe), // 539 (0x21B) + BIND_SYSC(sys_usbd_receive_event), // 540 (0x21C) + BIND_SYSC(sys_usbd_detect_event), // 541 (0x21D) + BIND_SYSC(sys_usbd_attach), // 542 (0x21E) + BIND_SYSC(sys_usbd_transfer_data), // 543 (0x21F) + BIND_SYSC(sys_usbd_isochronous_transfer_data), // 544 (0x220) + BIND_SYSC(sys_usbd_get_transfer_status), // 545 (0x221) + BIND_SYSC(sys_usbd_get_isochronous_transfer_status), // 546 (0x222) + BIND_SYSC(sys_usbd_get_device_location), // 547 (0x223) + BIND_SYSC(sys_usbd_send_event), // 548 (0x224) + BIND_SYSC(sys_usbd_event_port_send), // 549 (0x225) + BIND_SYSC(sys_usbd_allocate_memory), // 550 (0x226) + BIND_SYSC(sys_usbd_free_memory), // 551 (0x227) + null_func, // BIND_SYSC(sys_usbd_...), //552 (0x228) + null_func, // BIND_SYSC(sys_usbd_...), //553 (0x229) + null_func, // BIND_SYSC(sys_usbd_...), //554 (0x22A) + null_func, // BIND_SYSC(sys_usbd_...), //555 (0x22B) + BIND_SYSC(sys_usbd_get_device_speed), // 556 (0x22C) + null_func, // BIND_SYSC(sys_usbd_...), //557 (0x22D) + BIND_SYSC(sys_usbd_unregister_extra_ldd), // 558 (0x22E) + BIND_SYSC(sys_usbd_register_extra_ldd), // 559 (0x22F) + null_func, // BIND_SYSC(sys_...), //560 (0x230) + // ROOT + null_func, // BIND_SYSC(sys_...), //561 (0x231) + // ROOT + null_func, // BIND_SYSC(sys_...), //562 (0x232) + // ROOT + null_func, // BIND_SYSC(sys_...), //563 (0x233) + null_func, // BIND_SYSC(sys_...), //564 (0x234) + null_func, // BIND_SYSC(sys_...), //565 (0x235) + null_func, // BIND_SYSC(sys_...), //566 (0x236) + null_func, // BIND_SYSC(sys_...), //567 (0x237) + null_func, // BIND_SYSC(sys_...), //568 (0x238) + null_func, // BIND_SYSC(sys_...), //569 (0x239) + NULL_FUNC(sys_pad_ldd_register_controller), // 570 (0x23A) + NULL_FUNC(sys_pad_ldd_unregister_controller), // 571 (0x23B) + NULL_FUNC(sys_pad_ldd_data_insert), // 572 (0x23C) + NULL_FUNC(sys_pad_dbg_ldd_set_data_insert_mode), // 573 (0x23D) + NULL_FUNC(sys_pad_ldd_register_controller), // 574 (0x23E) + NULL_FUNC(sys_pad_ldd_get_port_no), // 575 (0x23F) + uns_func, // 576 (0x240) UNS + null_func, // BIND_SYSC(sys_pad_manager_...), //577 (0x241) + // ROOT PM + null_func, // BIND_SYSC(sys_bluetooth_...), //578 (0x242) + null_func, // BIND_SYSC(sys_bluetooth_aud_serial_...), //579 (0x243) + null_func, // BIND_SYSC(sys_bluetooth_...), //580 (0x244) + // ROOT + null_func, // BIND_SYSC(sys_bluetooth_...), //581 (0x245) + // ROOT + null_func, // BIND_SYSC(sys_bluetooth_...), //582 (0x246) + // ROOT + NULL_FUNC(sys_bt_read_firmware_version), // 583 (0x247) ROOT + NULL_FUNC(sys_bt_complete_wake_on_host), // 584 (0x248) ROOT + NULL_FUNC(sys_bt_disable_bluetooth), // 585 (0x249) + NULL_FUNC(sys_bt_enable_bluetooth), // 586 (0x24A) + NULL_FUNC(sys_bt_bccmd), // 587 (0x24B) ROOT + NULL_FUNC(sys_bt_read_hq), // 588 (0x24C) + NULL_FUNC(sys_bt_hid_get_remote_status), // 589 (0x24D) + NULL_FUNC(sys_bt_register_controller), // 590 (0x24E) ROOT + NULL_FUNC(sys_bt_clear_registered_contoller), // 591 (0x24F) + NULL_FUNC(sys_bt_connect_accept_controller), // 592 (0x250) + NULL_FUNC(sys_bt_get_local_bdaddress), // 593 (0x251) ROOT + NULL_FUNC(sys_bt_hid_get_data), // 594 (0x252) + NULL_FUNC(sys_bt_hid_set_report), // 595 (0x253) + NULL_FUNC(sys_bt_sched_log), // 596 (0x254) + NULL_FUNC(sys_bt_cancel_connect_accept_controller), // 597 (0x255) + null_func, // BIND_SYSC(sys_bluetooth_...), //598 (0x256) + // ROOT + null_func, // BIND_SYSC(sys_bluetooth_...), //599 (0x257) + // ROOT + BIND_SYSC(sys_storage_open), // 600 (0x258) ROOT + BIND_SYSC(sys_storage_close), // 601 (0x259) + BIND_SYSC(sys_storage_read), // 602 (0x25A) + BIND_SYSC(sys_storage_write), // 603 (0x25B) + BIND_SYSC(sys_storage_send_device_command), // 604 (0x25C) + BIND_SYSC(sys_storage_async_configure), // 605 (0x25D) + BIND_SYSC(sys_storage_async_read), // 606 (0x25E) + BIND_SYSC(sys_storage_async_write), // 607 (0x25F) + BIND_SYSC(sys_storage_async_cancel), // 608 (0x260) + BIND_SYSC(sys_storage_get_device_info), // 609 (0x261) ROOT + BIND_SYSC(sys_storage_get_device_config), // 610 (0x262) ROOT + BIND_SYSC(sys_storage_report_devices), // 611 (0x263) ROOT + BIND_SYSC(sys_storage_configure_medium_event), // 612 (0x264) ROOT + BIND_SYSC(sys_storage_set_medium_polling_interval), // 613 (0x265) + BIND_SYSC(sys_storage_create_region), // 614 (0x266) + BIND_SYSC(sys_storage_delete_region), // 615 (0x267) + BIND_SYSC(sys_storage_execute_device_command), // 616 (0x268) + BIND_SYSC(sys_storage_check_region_acl), // 617 (0x269) + BIND_SYSC(sys_storage_set_region_acl), // 618 (0x26A) + BIND_SYSC(sys_storage_async_send_device_command), // 619 (0x26B) + null_func, // BIND_SYSC(sys_...), //620 (0x26C) + // ROOT + BIND_SYSC(sys_gamepad_ycon_if), // 621 (0x26D) + BIND_SYSC(sys_storage_get_region_offset), // 622 (0x26E) + BIND_SYSC(sys_storage_set_emulated_speed), // 623 (0x26F) + BIND_SYSC(sys_io_buffer_create), // 624 (0x270) + BIND_SYSC(sys_io_buffer_destroy), // 625 (0x271) + BIND_SYSC(sys_io_buffer_allocate), // 626 (0x272) + BIND_SYSC(sys_io_buffer_free), // 627 (0x273) + uns_func, + uns_func, // 629 (0x275) UNS + BIND_SYSC(sys_gpio_set), // 630 (0x276) + BIND_SYSC(sys_gpio_get), // 631 (0x277) + uns_func, // 632 (0x278) UNS + NULL_FUNC(sys_fsw_connect_event), // 633 (0x279) + NULL_FUNC(sys_fsw_disconnect_event), // 634 (0x27A) + BIND_SYSC(sys_btsetting_if), // 635 (0x27B) + null_func, // BIND_SYSC(sys_...), //636 (0x27C) + null_func, // BIND_SYSC(sys_...), //637 (0x27D) + null_func, // BIND_SYSC(sys_...), //638 (0x27E) + + null_func, // BIND_SYSC(sys...), //639 + // DEPRECATED + NULL_FUNC(sys_usbbtaudio_initialize), // 640 DEPRECATED + NULL_FUNC(sys_usbbtaudio_finalize), // 641 DEPRECATED + NULL_FUNC(sys_usbbtaudio_discovery), // 642 DEPRECATED + NULL_FUNC(sys_usbbtaudio_cancel_discovery), // 643 DEPRECATED + NULL_FUNC(sys_usbbtaudio_pairing), // 644 DEPRECATED + NULL_FUNC(sys_usbbtaudio_set_passkey), // 645 DEPRECATED + NULL_FUNC(sys_usbbtaudio_connect), // 646 DEPRECATED + NULL_FUNC(sys_usbbtaudio_disconnect), // 647 DEPRECATED + null_func, // BIND_SYSC(sys_...), //648 + // DEPRECATED + null_func, // BIND_SYSC(sys_...), //649 + // DEPRECATED + + BIND_SYSC(sys_rsxaudio_initialize), // 650 (0x28A) + BIND_SYSC(sys_rsxaudio_finalize), // 651 (0x28B) + BIND_SYSC(sys_rsxaudio_import_shared_memory), // 652 (0x28C) + BIND_SYSC(sys_rsxaudio_unimport_shared_memory), // 653 (0x28D) + BIND_SYSC(sys_rsxaudio_create_connection), // 654 (0x28E) + BIND_SYSC(sys_rsxaudio_close_connection), // 655 (0x28F) + BIND_SYSC(sys_rsxaudio_prepare_process), // 656 (0x290) + BIND_SYSC(sys_rsxaudio_start_process), // 657 (0x291) + BIND_SYSC(sys_rsxaudio_stop_process), // 658 (0x292) + BIND_SYSC(sys_rsxaudio_get_dma_param), // 659 (0x293) + + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, // 660-665 UNS + + BIND_SYSC(sys_rsx_device_open), // 666 (0x29A) + BIND_SYSC(sys_rsx_device_close), // 667 (0x29B) + BIND_SYSC(sys_rsx_memory_allocate), // 668 (0x29C) + BIND_SYSC(sys_rsx_memory_free), // 669 (0x29D) + BIND_SYSC(sys_rsx_context_allocate), // 670 (0x29E) + BIND_SYSC(sys_rsx_context_free), // 671 (0x29F) + BIND_SYSC(sys_rsx_context_iomap), // 672 (0x2A0) + BIND_SYSC(sys_rsx_context_iounmap), // 673 (0x2A1) + BIND_SYSC(sys_rsx_context_attribute), // 674 (0x2A2) + BIND_SYSC(sys_rsx_device_map), // 675 (0x2A3) + BIND_SYSC(sys_rsx_device_unmap), // 676 (0x2A4) + BIND_SYSC(sys_rsx_attribute), // 677 (0x2A5) + null_func, // BIND_SYSC(sys_...), //678 (0x2A6) + null_func, // BIND_SYSC(sys_...), //679 (0x2A7) + // ROOT + null_func, // BIND_SYSC(sys_...), //680 (0x2A8) + // ROOT + null_func, // BIND_SYSC(sys_...), //681 (0x2A9) + // ROOT + null_func, // BIND_SYSC(sys_...), //682 (0x2AA) + // ROOT + null_func, // BIND_SYSC(sys_...), //683 (0x2AB) + // ROOT + null_func, // BIND_SYSC(sys_...), //684 (0x2AC) + // ROOT + null_func, // BIND_SYSC(sys_...), //685 (0x2AD) + // ROOT + null_func, // BIND_SYSC(sys_...), //686 (0x2AE) + // ROOT + null_func, // BIND_SYSC(sys_...), //687 (0x2AF) + // ROOT + null_func, // BIND_SYSC(sys_...), //688 (0x2B0) + // ROOT + null_func, // BIND_SYSC(sys_...), //689 (0x2B1) + // ROOT + null_func, // BIND_SYSC(sys_...), //690 (0x2B2) + // ROOT + null_func, // BIND_SYSC(sys_...), //691 (0x2B3) + // ROOT + null_func, // BIND_SYSC(sys_...), //692 (0x2B4) + // ROOT + null_func, // BIND_SYSC(sys_...), //693 (0x2B5) + // ROOT + null_func, // BIND_SYSC(sys_...), //694 (0x2B6) + // DEPRECATED + null_func, // BIND_SYSC(sys_...), //695 (0x2B7) + // DEPRECATED + null_func, // BIND_SYSC(sys_...), //696 (0x2B8) + // ROOT + uns_func, // BIND_SYSC(sys_...), //697 (0x2B9) + // UNS + uns_func, // BIND_SYSC(sys_...), //698 (0x2BA) + // UNS + BIND_SYSC(sys_bdemu_send_command), // 699 (0x2BB) + BIND_SYSC(sys_net_bnet_accept), // 700 (0x2BC) + BIND_SYSC(sys_net_bnet_bind), // 701 (0x2BD) + BIND_SYSC(sys_net_bnet_connect), // 702 (0x2BE) + BIND_SYSC(sys_net_bnet_getpeername), // 703 (0x2BF) + BIND_SYSC(sys_net_bnet_getsockname), // 704 (0x2C0) + BIND_SYSC(sys_net_bnet_getsockopt), // 705 (0x2C1) + BIND_SYSC(sys_net_bnet_listen), // 706 (0x2C2) + BIND_SYSC(sys_net_bnet_recvfrom), // 707 (0x2C3) + BIND_SYSC(sys_net_bnet_recvmsg), // 708 (0x2C4) + BIND_SYSC(sys_net_bnet_sendmsg), // 709 (0x2C5) + BIND_SYSC(sys_net_bnet_sendto), // 710 (0x2C6) + BIND_SYSC(sys_net_bnet_setsockopt), // 711 (0x2C7) + BIND_SYSC(sys_net_bnet_shutdown), // 712 (0x2C8) + BIND_SYSC(sys_net_bnet_socket), // 713 (0x2C9) + BIND_SYSC(sys_net_bnet_close), // 714 (0x2CA) + BIND_SYSC(sys_net_bnet_poll), // 715 (0x2CB) + BIND_SYSC(sys_net_bnet_select), // 716 (0x2CC) + BIND_SYSC(_sys_net_open_dump), // 717 (0x2CD) + BIND_SYSC(_sys_net_read_dump), // 718 (0x2CE) + BIND_SYSC(_sys_net_close_dump), // 719 (0x2CF) + BIND_SYSC(_sys_net_write_dump), // 720 (0x2D0) + BIND_SYSC(sys_net_abort), // 721 (0x2D1) + BIND_SYSC(sys_net_infoctl), // 722 (0x2D2) + BIND_SYSC(sys_net_control), // 723 (0x2D3) + BIND_SYSC(sys_net_bnet_ioctl), // 724 (0x2D4) + BIND_SYSC(sys_net_bnet_sysctl), // 725 (0x2D5) + BIND_SYSC(sys_net_eurus_post_command), // 726 (0x2D6) + + uns_func, + uns_func, + uns_func, // 727-729 UNS + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, // 730-739 UNS + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, // 740-749 UNS + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, // 750-759 UNS + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, // 760-769 UNS + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, // 770-779 UNS + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, // 780-789 UNS + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, // 790-799 UNS + + BIND_SYSC(sys_fs_test), // 800 (0x320) + BIND_SYSC(sys_fs_open), // 801 (0x321) + BIND_SYSC(sys_fs_read), // 802 (0x322) + BIND_SYSC(sys_fs_write), // 803 (0x323) + BIND_SYSC(sys_fs_close), // 804 (0x324) + BIND_SYSC(sys_fs_opendir), // 805 (0x325) + BIND_SYSC(sys_fs_readdir), // 806 (0x326) + BIND_SYSC(sys_fs_closedir), // 807 (0x327) + BIND_SYSC(sys_fs_stat), // 808 (0x328) + BIND_SYSC(sys_fs_fstat), // 809 (0x329) + BIND_SYSC(sys_fs_link), // 810 (0x32A) + BIND_SYSC(sys_fs_mkdir), // 811 (0x32B) + BIND_SYSC(sys_fs_rename), // 812 (0x32C) + BIND_SYSC(sys_fs_rmdir), // 813 (0x32D) + BIND_SYSC(sys_fs_unlink), // 814 (0x32E) + BIND_SYSC(sys_fs_utime), // 815 (0x32F) + BIND_SYSC(sys_fs_access), // 816 (0x330) + BIND_SYSC(sys_fs_fcntl), // 817 (0x331) + BIND_SYSC(sys_fs_lseek), // 818 (0x332) + BIND_SYSC(sys_fs_fdatasync), // 819 (0x333) + BIND_SYSC(sys_fs_fsync), // 820 (0x334) + BIND_SYSC(sys_fs_fget_block_size), // 821 (0x335) + BIND_SYSC(sys_fs_get_block_size), // 822 (0x336) + BIND_SYSC(sys_fs_acl_read), // 823 (0x337) + BIND_SYSC(sys_fs_acl_write), // 824 (0x338) + BIND_SYSC(sys_fs_lsn_get_cda_size), // 825 (0x339) + BIND_SYSC(sys_fs_lsn_get_cda), // 826 (0x33A) + BIND_SYSC(sys_fs_lsn_lock), // 827 (0x33B) + BIND_SYSC(sys_fs_lsn_unlock), // 828 (0x33C) + BIND_SYSC(sys_fs_lsn_read), // 829 (0x33D) + BIND_SYSC(sys_fs_lsn_write), // 830 (0x33E) + BIND_SYSC(sys_fs_truncate), // 831 (0x33F) + BIND_SYSC(sys_fs_ftruncate), // 832 (0x340) + BIND_SYSC(sys_fs_symbolic_link), // 833 (0x341) + BIND_SYSC(sys_fs_chmod), // 834 (0x342) + BIND_SYSC(sys_fs_chown), // 835 (0x343) + BIND_SYSC(sys_fs_newfs), // 836 (0x344) + BIND_SYSC(sys_fs_mount), // 837 (0x345) + BIND_SYSC(sys_fs_unmount), // 838 (0x346) + NULL_FUNC(sys_fs_sync), // 839 (0x347) + BIND_SYSC(sys_fs_disk_free), // 840 (0x348) + BIND_SYSC(sys_fs_get_mount_info_size), // 841 (0x349) + BIND_SYSC(sys_fs_get_mount_info), // 842 (0x34A) + NULL_FUNC(sys_fs_get_fs_info_size), // 843 (0x34B) + NULL_FUNC(sys_fs_get_fs_info), // 844 (0x34C) + BIND_SYSC(sys_fs_mapped_allocate), // 845 (0x34D) + BIND_SYSC(sys_fs_mapped_free), // 846 (0x34E) + BIND_SYSC(sys_fs_truncate2), // 847 (0x34F) + + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, // 848-853 UNS + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, // 854-859 UNS + + NULL_FUNC(sys_ss_get_cache_of_analog_sunset_flag), // 860 (0x35C) AUTHID + NULL_FUNC(sys_ss_protected_file_db), // 861 ROOT + BIND_SYSC(sys_ss_virtual_trm_manager), // 862 ROOT + BIND_SYSC(sys_ss_update_manager), // 863 ROOT + NULL_FUNC(sys_ss_sec_hw_framework), // 864 DBG + BIND_SYSC(sys_ss_random_number_generator), // 865 (0x361) + BIND_SYSC(sys_ss_secure_rtc), // 866 ROOT + BIND_SYSC(sys_ss_appliance_info_manager), // 867 ROOT + BIND_SYSC(sys_ss_individual_info_manager), // 868 ROOT / DBG AUTHID + NULL_FUNC(sys_ss_factory_data_manager), // 869 ROOT + BIND_SYSC(sys_ss_get_console_id), // 870 (0x366) + BIND_SYSC(sys_ss_access_control_engine), // 871 (0x367) DBG + BIND_SYSC(sys_ss_get_open_psid), // 872 (0x368) + BIND_SYSC(sys_ss_get_cache_of_product_mode), // 873 (0x369) + BIND_SYSC(sys_ss_get_cache_of_flash_ext_flag), // 874 (0x36A) + BIND_SYSC(sys_ss_get_boot_device), // 875 (0x36B) + NULL_FUNC(sys_ss_disc_access_control), // 876 (0x36C) + null_func, // BIND_SYSC(sys_ss_~utoken_if), //877 (0x36D) + // ROOT + NULL_FUNC(sys_ss_ad_sign), // 878 (0x36E) + NULL_FUNC(sys_ss_media_id), // 879 (0x36F) + NULL_FUNC(sys_deci3_open), // 880 (0x370) + NULL_FUNC(sys_deci3_create_event_path), // 881 (0x371) + NULL_FUNC(sys_deci3_close), // 882 (0x372) + NULL_FUNC(sys_deci3_send), // 883 (0x373) + NULL_FUNC(sys_deci3_receive), // 884 (0x374) + NULL_FUNC(sys_deci3_open2), // 885 (0x375) + uns_func, + uns_func, + uns_func, // 886-888 UNS + null_func, // BIND_SYSC(sys_...), //889 (0x379) + // ROOT + NULL_FUNC(sys_deci3_initialize), // 890 (0x37A) + NULL_FUNC(sys_deci3_terminate), // 891 (0x37B) + NULL_FUNC(sys_deci3_debug_mode), // 892 (0x37C) + NULL_FUNC(sys_deci3_show_status), // 893 (0x37D) + NULL_FUNC(sys_deci3_echo_test), // 894 (0x37E) + NULL_FUNC(sys_deci3_send_dcmp_packet), // 895 (0x37F) + NULL_FUNC(sys_deci3_dump_cp_register), // 896 (0x380) + NULL_FUNC(sys_deci3_dump_cp_buffer), // 897 (0x381) + uns_func, // 898 (0x382) UNS + NULL_FUNC(sys_deci3_test), // 899 (0x383) + NULL_FUNC(sys_dbg_stop_processes), // 900 (0x384) + NULL_FUNC(sys_dbg_continue_processes), // 901 (0x385) + NULL_FUNC(sys_dbg_stop_threads), // 902 (0x386) + NULL_FUNC(sys_dbg_continue_threads), // 903 (0x387) + BIND_SYSC(sys_dbg_read_process_memory), // 904 (0x388) + BIND_SYSC(sys_dbg_write_process_memory), // 905 (0x389) + NULL_FUNC(sys_dbg_read_thread_register), // 906 (0x38A) + NULL_FUNC(sys_dbg_write_thread_register), // 907 (0x38B) + NULL_FUNC(sys_dbg_get_process_list), // 908 (0x38C) + NULL_FUNC(sys_dbg_get_thread_list), // 909 (0x38D) + NULL_FUNC(sys_dbg_get_thread_info), // 910 (0x38E) + NULL_FUNC(sys_dbg_spu_thread_read_from_ls), // 911 (0x38F) + NULL_FUNC(sys_dbg_spu_thread_write_to_ls), // 912 (0x390) + NULL_FUNC(sys_dbg_kill_process), // 913 (0x391) + NULL_FUNC(sys_dbg_get_process_info), // 914 (0x392) + NULL_FUNC(sys_dbg_set_run_control_bit_to_spu), // 915 (0x393) + NULL_FUNC(sys_dbg_spu_thread_get_exception_cause), // 916 (0x394) + NULL_FUNC(sys_dbg_create_kernel_event_queue), // 917 (0x395) + NULL_FUNC(sys_dbg_read_kernel_event_queue), // 918 (0x396) + NULL_FUNC(sys_dbg_destroy_kernel_event_queue), // 919 (0x397) + NULL_FUNC(sys_dbg_get_process_event_ctrl_flag), // 920 (0x398) + NULL_FUNC(sys_dbg_set_process_event_cntl_flag), // 921 (0x399) + NULL_FUNC(sys_dbg_get_spu_thread_group_event_cntl_flag), // 922 (0x39A) + NULL_FUNC(sys_dbg_set_spu_thread_group_event_cntl_flag), // 923 (0x39B) + NULL_FUNC(sys_dbg_get_module_list), // 924 (0x39C) + NULL_FUNC(sys_dbg_get_raw_spu_list), // 925 (0x39D) + NULL_FUNC(sys_dbg_initialize_scratch_executable_area), // 926 (0x39E) + NULL_FUNC(sys_dbg_terminate_scratch_executable_area), // 927 (0x3A0) + NULL_FUNC(sys_dbg_initialize_scratch_data_area), // 928 (0x3A1) + NULL_FUNC(sys_dbg_terminate_scratch_data_area), // 929 (0x3A2) + NULL_FUNC(sys_dbg_get_user_memory_stat), // 930 (0x3A3) + NULL_FUNC(sys_dbg_get_shared_memory_attribute_list), // 931 (0x3A4) + NULL_FUNC(sys_dbg_get_mutex_list), // 932 (0x3A4) + NULL_FUNC(sys_dbg_get_mutex_information), // 933 (0x3A5) + NULL_FUNC(sys_dbg_get_cond_list), // 934 (0x3A6) + NULL_FUNC(sys_dbg_get_cond_information), // 935 (0x3A7) + NULL_FUNC(sys_dbg_get_rwlock_list), // 936 (0x3A8) + NULL_FUNC(sys_dbg_get_rwlock_information), // 937 (0x3A9) + NULL_FUNC(sys_dbg_get_lwmutex_list), // 938 (0x3AA) + NULL_FUNC(sys_dbg_get_address_from_dabr), // 939 (0x3AB) + NULL_FUNC(sys_dbg_set_address_to_dabr), // 940 (0x3AC) + NULL_FUNC(sys_dbg_get_lwmutex_information), // 941 (0x3AD) + NULL_FUNC(sys_dbg_get_event_queue_list), // 942 (0x3AE) + NULL_FUNC(sys_dbg_get_event_queue_information), // 943 (0x3AF) + NULL_FUNC(sys_dbg_initialize_ppu_exception_handler), // 944 (0x3B0) + NULL_FUNC(sys_dbg_finalize_ppu_exception_handler), // 945 (0x3B1) DBG + NULL_FUNC(sys_dbg_get_semaphore_list), // 946 (0x3B2) + NULL_FUNC(sys_dbg_get_semaphore_information), // 947 (0x3B3) + NULL_FUNC(sys_dbg_get_kernel_thread_list), // 948 (0x3B4) + NULL_FUNC(sys_dbg_get_kernel_thread_info), // 949 (0x3B5) + NULL_FUNC(sys_dbg_get_lwcond_list), // 950 (0x3B6) + NULL_FUNC(sys_dbg_get_lwcond_information), // 951 (0x3B7) + NULL_FUNC(sys_dbg_create_scratch_data_area_ext), // 952 (0x3B8) + NULL_FUNC(sys_dbg_vm_get_page_information), // 953 (0x3B9) + NULL_FUNC(sys_dbg_vm_get_info), // 954 (0x3BA) + NULL_FUNC( + sys_dbg_enable_floating_point_enabled_exception), // 955 (0x3BB) + NULL_FUNC( + sys_dbg_disable_floating_point_enabled_exception), // 956 (0x3BC) + NULL_FUNC( + sys_dbg_get_process_memory_container_information), // 957 (0x3BD) + uns_func, // 958 (0x3BE) UNS + null_func, // BIND_SYSC(sys_dbg_...), //959 (0x3BF) + NULL_FUNC(sys_control_performance_monitor), // 960 (0x3C0) + NULL_FUNC(sys_performance_monitor_hidden), // 961 (0x3C1) + NULL_FUNC(sys_performance_monitor_bookmark), // 962 (0x3C2) + NULL_FUNC(sys_lv1_pc_trace_create), // 963 (0x3C3) + NULL_FUNC(sys_lv1_pc_trace_start), // 964 (0x3C4) + NULL_FUNC(sys_lv1_pc_trace_stop), // 965 (0x3C5) + NULL_FUNC(sys_lv1_pc_trace_get_status), // 966 (0x3C6) + NULL_FUNC(sys_lv1_pc_trace_destroy), // 967 (0x3C7) + NULL_FUNC(sys_rsx_trace_ioctl), // 968 (0x3C8) + null_func, // BIND_SYSC(sys_dbg_...), //969 (0x3C9) + NULL_FUNC(sys_dbg_get_event_flag_list), // 970 (0x3CA) + NULL_FUNC(sys_dbg_get_event_flag_information), // 971 (0x3CB) + null_func, // BIND_SYSC(sys_dbg_...), //972 (0x3CC) + uns_func, // BIND_SYSC(sys_dbg_...), //973 (0x3CD) + null_func, // BIND_SYSC(sys_dbg_...), //974 (0x3CE) + NULL_FUNC(sys_dbg_read_spu_thread_context2), // 975 (0x3CF) + BIND_SYSC(sys_crypto_engine_create), // 976 (0x3D0) + BIND_SYSC(sys_crypto_engine_destroy), // 977 (0x3D1) + NULL_FUNC(sys_crypto_engine_hasher_prepare), // 978 (0x3D2) ROOT + NULL_FUNC(sys_crypto_engine_hasher_run), // 979 (0x3D3) + NULL_FUNC(sys_crypto_engine_hasher_get_hash), // 980 (0x3D4) + NULL_FUNC(sys_crypto_engine_cipher_prepare), // 981 (0x3D5) ROOT + NULL_FUNC(sys_crypto_engine_cipher_run), // 982 (0x3D6) + NULL_FUNC(sys_crypto_engine_cipher_get_hash), // 983 (0x3D7) + BIND_SYSC(sys_crypto_engine_random_generate), // 984 (0x3D8) + NULL_FUNC(sys_dbg_get_console_type), // 985 (0x3D9) ROOT + null_func, // BIND_SYSC(sys_dbg_...), //986 (0x3DA) + // ROOT DBG + null_func, // BIND_SYSC(sys_dbg_...), //987 (0x3DB) + // ROOT + null_func, // BIND_SYSC(sys_dbg_..._ppu_exception_handler) //988 (0x3DC) + null_func, // BIND_SYSC(sys_dbg_...), //989 (0x3DD) + + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, // 990-998 UNS + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, // 999-1007 UNS + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, // 1008-1016 UNS + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, + uns_func, // 1020-1023 UNS + }; + +#undef BIND_SYSC +#undef NULL_FUNC + +// TODO: more enums +enum CellAdecError : u32; +enum CellAtracError : u32; +enum CellAtracMultiError : u32; +enum CellAudioError : u32; +enum CellAudioOutError : u32; +enum CellAudioInError : u32; + +enum CellVideoOutError : u32; + +enum CellSpursCoreError : u32; +enum CellSpursPolicyModuleError : u32; +enum CellSpursTaskError : u32; +enum CellSpursJobError : u32; +enum CellSyncError : u32; + +enum CellGameError : u32; +enum CellGameDataError : u32; +enum CellDiscGameError : u32; +enum CellHddGameError : u32; + +enum SceNpTrophyError : u32; +enum SceNpError : u32; + +template +constexpr auto formatter_of = + std::make_pair(EnumMin, &fmt_class_string::format); + +const std::map + s_error_codes_formatting_by_type{ + formatter_of<0x80610000, CellAdecError>, + formatter_of<0x80612100, CellAdecError>, + formatter_of<0x80610300, CellAtracError>, + formatter_of<0x80610b00, CellAtracMultiError>, + formatter_of<0x80310700, CellAudioError>, + formatter_of<0x8002b240, CellAudioOutError>, + formatter_of<0x8002b260, CellAudioInError>, + formatter_of<0x8002b220, CellVideoOutError>, + + formatter_of<0x80410100, CellSyncError>, + formatter_of<0x80410700, CellSpursCoreError>, + formatter_of<0x80410800, CellSpursPolicyModuleError>, + formatter_of<0x80410900, CellSpursTaskError>, + formatter_of<0x80410A00, CellSpursJobError>, + + formatter_of<0x8002cb00, CellGameError>, + formatter_of<0x8002b600, CellGameDataError>, + formatter_of<0x8002bd00, CellDiscGameError>, + formatter_of<0x8002ba00, CellHddGameError>, + + formatter_of<0x80022900, SceNpTrophyError>, + formatter_of<0x80029500, SceNpError>, + }; + +template <> +void fmt_class_string::format(std::string &out, u64 arg) { + // Test if can be formatted by this formatter + const bool lv2_cell_error = (arg >> 8) == 0x800100u; + + if (!lv2_cell_error) { + // Format by external enum formatters + auto upper = s_error_codes_formatting_by_type.upper_bound(arg); + + if (upper == s_error_codes_formatting_by_type.begin()) { + // Format as unknown by another enum formatter + upper->second(out, arg); + return; + } + + // Find the formatter whose base is the highest that is not more than arg + const auto found = std::prev(upper); + found->second(out, arg); + return; + } + + format_enum(out, arg, [](auto error) { + switch (error) { + STR_CASE(CELL_EAGAIN); + STR_CASE(CELL_EINVAL); + STR_CASE(CELL_ENOSYS); + STR_CASE(CELL_ENOMEM); + STR_CASE(CELL_ESRCH); + STR_CASE(CELL_ENOENT); + STR_CASE(CELL_ENOEXEC); + STR_CASE(CELL_EDEADLK); + STR_CASE(CELL_EPERM); + STR_CASE(CELL_EBUSY); + STR_CASE(CELL_ETIMEDOUT); + STR_CASE(CELL_EABORT); + STR_CASE(CELL_EFAULT); + STR_CASE(CELL_ENOCHILD); + STR_CASE(CELL_ESTAT); + STR_CASE(CELL_EALIGN); + STR_CASE(CELL_EKRESOURCE); + STR_CASE(CELL_EISDIR); + STR_CASE(CELL_ECANCELED); + STR_CASE(CELL_EEXIST); + STR_CASE(CELL_EISCONN); + STR_CASE(CELL_ENOTCONN); + STR_CASE(CELL_EAUTHFAIL); + STR_CASE(CELL_ENOTMSELF); + STR_CASE(CELL_ESYSVER); + STR_CASE(CELL_EAUTHFATAL); + STR_CASE(CELL_EDOM); + STR_CASE(CELL_ERANGE); + STR_CASE(CELL_EILSEQ); + STR_CASE(CELL_EFPOS); + STR_CASE(CELL_EINTR); + STR_CASE(CELL_EFBIG); + STR_CASE(CELL_EMLINK); + STR_CASE(CELL_ENFILE); + STR_CASE(CELL_ENOSPC); + STR_CASE(CELL_ENOTTY); + STR_CASE(CELL_EPIPE); + STR_CASE(CELL_EROFS); + STR_CASE(CELL_ESPIPE); + STR_CASE(CELL_E2BIG); + STR_CASE(CELL_EACCES); + STR_CASE(CELL_EBADF); + STR_CASE(CELL_EIO); + STR_CASE(CELL_EMFILE); + STR_CASE(CELL_ENODEV); + STR_CASE(CELL_ENOTDIR); + STR_CASE(CELL_ENXIO); + STR_CASE(CELL_EXDEV); + STR_CASE(CELL_EBADMSG); + STR_CASE(CELL_EINPROGRESS); + STR_CASE(CELL_EMSGSIZE); + STR_CASE(CELL_ENAMETOOLONG); + STR_CASE(CELL_ENOLCK); + STR_CASE(CELL_ENOTEMPTY); + STR_CASE(CELL_ENOTSUP); + STR_CASE(CELL_EFSSPECIFIC); + STR_CASE(CELL_EOVERFLOW); + STR_CASE(CELL_ENOTMOUNTED); + STR_CASE(CELL_ENOTSDATA); + STR_CASE(CELL_ESDKVER); + STR_CASE(CELL_ENOLICDISC); + STR_CASE(CELL_ENOLICENT); + } + + return unknown; + }); +} + +stx::init_lock acquire_lock(stx::init_mutex &mtx, ppu_thread *ppu) { + if (!ppu) { + ppu = ensure(cpu_thread::get_current()); + } + + return mtx.init( + [](int invoke_count, const stx::init_lock &, ppu_thread *ppu) { + if (!invoke_count) { + // Sleep before waiting on lock + lv2_obj::sleep(*ppu); + } else { + // Wake up after acquistion or failure to acquire + ppu->check_state(); + } + }, + ppu); +} + +stx::access_lock acquire_access_lock(stx::init_mutex &mtx, ppu_thread *ppu) { + if (!ppu) { + ppu = ensure(cpu_thread::get_current()); + } + + // TODO: Check if needs to wait + return mtx.access(); +} + +stx::reset_lock acquire_reset_lock(stx::init_mutex &mtx, ppu_thread *ppu) { + if (!ppu) { + ppu = ensure(cpu_thread::get_current()); + } + + return mtx.reset( + [](int invoke_count, const stx::init_lock &, ppu_thread *ppu) { + if (!invoke_count) { + // Sleep before waiting on lock + lv2_obj::sleep(*ppu); + } else { + // Wake up after acquistion or failure to acquire + ppu->check_state(); + } + }, + ppu); +} + +class ppu_syscall_usage { + // Internal buffer + std::string m_stats; + u64 m_old_stat[1024]{}; + +public: + // Public info collection buffers + atomic_t stat[1024]{}; + + void print_stats(bool force_print) noexcept { + std::multimap> usage; + + for (u32 i = 0; i < 1024; i++) { + if (u64 v = stat[i]; m_old_stat[i] != v || (force_print && v)) { + // Only add syscalls with non-zero usage counter and only if caught new + // calls since last print + usage.emplace(v, i); + m_old_stat[i] = v; + } + } + + m_stats.clear(); + + for (auto &&pair : usage) { + fmt::append(m_stats, u8"\n\t⁂ %s [%u]", ppu_get_syscall_name(pair.second), + pair.first); + } + + if (!m_stats.empty()) { + ppu_log.notice("PPU Syscall Usage Stats:%s", m_stats); + } + } + + void operator()() { + bool was_paused = false; + u64 sleep_until = get_system_time(); + + for (u32 i = 1; thread_ctrl::state() != thread_state::aborting; i++) { + thread_ctrl::wait_until(&sleep_until, 1'000'000); + + const bool is_paused = Emu.IsPaused(); + + // Force-print all if paused + const bool force_print = is_paused && !was_paused; + + if (force_print || i % 10 == 0) { + was_paused = is_paused; + print_stats(force_print); + } + } + } + + ~ppu_syscall_usage() { print_stats(true); } + + static constexpr auto thread_name = "PPU Syscall Usage Thread"sv; +}; + +extern void ppu_execute_syscall(ppu_thread &ppu, u64 code) { + if (g_cfg.core.ppu_decoder == ppu_decoder_type::llvm_legacy) { + code = ppu.gpr[11]; + } + + if (code < g_ppu_syscall_table.size()) { + g_fxo->get>().stat[code]++; + + if (const auto func = g_ppu_syscall_table[code].first) { +#ifdef __APPLE__ + pthread_jit_write_protect_np(false); +#endif + func(ppu, {}, vm::_ptr(ppu.cia), nullptr); + ppu_log.trace("Syscall '%s' (%llu) finished, r3=0x%llx", + ppu_syscall_code(code), code, ppu.gpr[3]); + +#ifdef __APPLE__ + pthread_jit_write_protect_np(true); + // No need to flush cache lines after a syscall, since we didn't generate + // any code. +#endif + return; + } + } + + fmt::throw_exception("Invalid syscall number (%llu)", code); +} + +extern ppu_intrp_func_t ppu_get_syscall(u64 code) { + if (code < g_ppu_syscall_table.size()) { + return g_ppu_syscall_table[code].first; + } + + return nullptr; +} + +std::string ppu_get_syscall_name(u64 code) { + if (code < g_ppu_syscall_table.size() && + !g_ppu_syscall_table[code].second.empty()) { + return std::string(g_ppu_syscall_table[code].second); + } + + return fmt::format("syscall_%u", code); +} + +DECLARE(lv2_obj::g_mutex); +DECLARE(lv2_obj::g_ppu){}; +DECLARE(lv2_obj::g_pending){}; +DECLARE(lv2_obj::g_priority_order_tag){}; + +thread_local DECLARE(lv2_obj::g_to_notify){}; +thread_local DECLARE(lv2_obj::g_postpone_notify_barrier){}; +thread_local DECLARE(lv2_obj::g_to_awake); + +// Scheduler queue for timeouts (wait until -> thread) +static std::deque> g_waiting; + +// Threads which must call lv2_obj::sleep before the scheduler starts +static std::deque g_to_sleep; +static atomic_t g_scheduler_ready = false; +static atomic_t s_yield_frequency = 0; +static atomic_t s_max_allowed_yield_tsc = 0; +static u64 s_last_yield_tsc = 0; +atomic_t g_lv2_preempts_taken = 0; + +namespace cpu_counter { +void remove(cpu_thread *) noexcept; +} + +std::string lv2_obj::name64(u64 name_u64) { + const auto ptr = reinterpret_cast(&name_u64); + + // NTS string, ignore invalid/newline characters + // Example: "lv2\n\0tx" will be printed as "lv2" + std::string str{ptr, std::find(ptr, ptr + 7, '\0')}; + str.erase(std::remove_if(str.begin(), str.end(), + [](uchar c) { return !std::isprint(c); }), + str.end()); + + return str; +} + +bool lv2_obj::sleep(cpu_thread &cpu, const u64 timeout) { + // Should already be performed when using this flag + if (!g_postpone_notify_barrier) { + prepare_for_sleep(cpu); + } + + if (cpu.get_class() == thread_class::ppu) { + if (u32 addr = static_cast(cpu).res_notify) { + static_cast(cpu).res_notify = 0; + + if (static_cast(cpu).res_notify_time != + vm::reservation_notifier_count_index(addr).second) { + // Ignore outdated notification request + } else if (auto it = std::find(g_to_notify, std::end(g_to_notify), + std::add_pointer_t{}); + it != std::end(g_to_notify)) { + *it++ = vm::reservation_notifier_notify(addr, true); + + if (it < std::end(g_to_notify)) { + // Null-terminate the list if it ends before last slot + *it = nullptr; + } + } else { + vm::reservation_notifier_notify(addr); + } + } + } + + bool result = false; + const u64 current_time = get_guest_system_time(); + { + std::lock_guard lock{g_mutex}; + result = sleep_unlocked(cpu, timeout, current_time); + + if (!g_to_awake.empty()) { + // Schedule pending entries + awake_unlocked({}); + } + + schedule_all(current_time); + } + + if (!g_postpone_notify_barrier) { + notify_all(); + } + + g_to_awake.clear(); + return result; +} + +bool lv2_obj::awake(cpu_thread *thread, s32 prio) { + if (ppu_thread *ppu = cpu_thread::get_current()) { + if (u32 addr = ppu->res_notify) { + ppu->res_notify = 0; + + if (ppu->res_notify_time != + vm::reservation_notifier_count_index(addr).second) { + // Ignore outdated notification request + } else if (auto it = std::find(g_to_notify, std::end(g_to_notify), + std::add_pointer_t{}); + it != std::end(g_to_notify)) { + *it++ = vm::reservation_notifier_notify(addr, true); + + if (it < std::end(g_to_notify)) { + // Null-terminate the list if it ends before last slot + *it = nullptr; + } + } else { + vm::reservation_notifier_notify(addr); + } + } + } + + bool result = false; + { + std::lock_guard lock(g_mutex); + result = awake_unlocked(thread, prio); + schedule_all(); + } + + if (result) { + if (auto cpu = cpu_thread::get_current(); cpu && cpu->is_paused()) { + vm::temporary_unlock(); + } + } + + if (!g_postpone_notify_barrier) { + notify_all(); + } + + return result; +} + +bool lv2_obj::yield(cpu_thread &thread) { + if (auto ppu = thread.try_get()) { + ppu->raddr = 0; // Clear reservation + + if (!atomic_storage::load(ppu->next_ppu)) { + // Nothing to do + return false; + } + } + + return awake(&thread, yield_cmd); +} + +bool lv2_obj::sleep_unlocked(cpu_thread &thread, u64 timeout, + u64 current_time) { + const u64 start_time = current_time; + + auto on_to_sleep_update = [&]() { + if (g_to_sleep.size() > 5u) { + ppu_log.warning("Threads (%d)", g_to_sleep.size()); + } else if (!g_to_sleep.empty()) { + // In case there is a deadlock (PPU threads not sleeping) + // Print-out their IDs for further inspection (focus at 5 at max for now + // to avoid log spam) + std::string out = fmt::format("Threads (%d):", g_to_sleep.size()); + for (auto thread : g_to_sleep) { + fmt::append(out, " 0x%x,", thread->id); + } + + out.resize(out.size() - 1); + + ppu_log.warning("%s", out); + } else { + ppu_log.warning("Final Thread"); + + // All threads are ready, wake threads + Emu.CallFromMainThread([] { + if (Emu.IsStarting()) { + // It uses lv2_obj::g_mutex, run it on main thread + Emu.FinalizeRunRequest(); + } + }); + } + }; + + bool return_val = true; + + if (auto ppu = thread.try_get()) { + ppu_log.trace("sleep() - waiting (%zu)", g_pending); + + if (ppu->ack_suspend) { + ppu->ack_suspend = false; + g_pending--; + } + + if (std::exchange(ppu->cancel_sleep, 0) == 2) { + // Signal that the underlying LV2 operation has been cancelled and + // replaced with a short yield + return_val = false; + } + + const auto [_, ok] = ppu->state.fetch_op([&](bs_t &val) { + if (!(val & cpu_flag::signal)) { + val += cpu_flag::suspend; + + // Flag used for forced timeout notification + ensure(!timeout || !(val & cpu_flag::notify)); + return true; + } + + return false; + }); + + if (!ok) { + ppu_log.fatal("sleep() failed (signaled) (%s)", ppu->current_function); + return false; + } + + // Find and remove the thread + if (!unqueue(g_ppu, ppu, &ppu_thread::next_ppu)) { + if (auto it = std::find(g_to_sleep.begin(), g_to_sleep.end(), ppu); + it != g_to_sleep.end()) { + g_to_sleep.erase(it); + ppu->start_time = start_time; + on_to_sleep_update(); + return true; + } + + // Already sleeping + ppu_log.trace("sleep(): called on already sleeping thread."); + return false; + } + + ppu->raddr = 0; // Clear reservation + ppu->start_time = start_time; + ppu->end_time = + timeout ? start_time + std::min(timeout, ~start_time) : u64{umax}; + } else if (auto spu = thread.try_get()) { + if (auto it = std::find(g_to_sleep.begin(), g_to_sleep.end(), spu); + it != g_to_sleep.end()) { + g_to_sleep.erase(it); + on_to_sleep_update(); + return true; + } + + return false; + } + + if (timeout) { + const u64 wait_until = start_time + std::min(timeout, ~start_time); + + // Register timeout if necessary + for (auto it = g_waiting.cbegin(), end = g_waiting.cend();; it++) { + if (it == end || it->first > wait_until) { + g_waiting.emplace(it, wait_until, &thread); + break; + } + } + } + + return return_val; +} + +bool lv2_obj::awake_unlocked(cpu_thread *cpu, s32 prio) { + // Check thread type + AUDIT(!cpu || cpu->get_class() == thread_class::ppu); + + bool push_first = false; + + switch (prio) { + default: { + // Priority set + auto set_prio = [](atomic_t &prio, s32 value, + bool increment_order_last, bool increment_order_first) { + s64 tag = 0; + + if (increment_order_first || increment_order_last) { + tag = ++g_priority_order_tag; + } + + prio.atomic_op([&](ppu_thread::ppu_prio_t &prio) { + prio.prio = value; + + if (increment_order_first) { + prio.order = ~tag; + } else if (increment_order_last) { + prio.order = tag; + } + }); + }; + + const s64 old_prio = static_cast(cpu)->prio.load().prio; + + // If priority is the same, push ONPROC/RUNNABLE thread to the back of the + // priority list if it is not the current thread + if (old_prio == prio && cpu == cpu_thread::get_current()) { + set_prio(static_cast(cpu)->prio, prio, false, false); + return true; + } + + if (!unqueue(g_ppu, static_cast(cpu), + &ppu_thread::next_ppu)) { + set_prio(static_cast(cpu)->prio, prio, old_prio > prio, + old_prio < prio); + return true; + } + + set_prio(static_cast(cpu)->prio, prio, false, false); + break; + } + case yield_cmd: { + usz i = 0; + + // Yield command + for (auto ppu_next = &g_ppu;; i++) { + const auto ppu = +*ppu_next; + + if (!ppu) { + return false; + } + + if (ppu == cpu) { + auto ppu2 = ppu->next_ppu; + + if (!ppu2 || ppu2->prio.load().prio != ppu->prio.load().prio) { + // Empty 'same prio' threads list + return false; + } + + for (i++;; i++) { + const auto next = ppu2->next_ppu; + + if (!next || next->prio.load().prio != ppu->prio.load().prio) { + break; + } + + ppu2 = next; + } + + // Rotate current thread to the last position of the 'same prio' threads + // list Exchange forward pointers + *ppu_next = + std::exchange(ppu->next_ppu, std::exchange(ppu2->next_ppu, ppu)); + + if (i < g_cfg.core.ppu_threads + 0u) { + // Threads were rotated, but no context switch was made + return false; + } + + ppu->start_time = get_guest_system_time(); + break; + } + + ppu_next = &ppu->next_ppu; + } + + break; + } + case enqueue_cmd: { + break; + } + } + + const auto emplace_thread = [push_first](cpu_thread *const cpu) { + for (auto it = &g_ppu;;) { + const auto next = +*it; + + if (next == cpu) { + ppu_log.trace("sleep() - suspended (p=%zu)", g_pending); + + if (static_cast(cpu)->cancel_sleep == 1) { + // The next sleep call of the thread is cancelled + static_cast(cpu)->cancel_sleep = 2; + } + + return false; + } + + // Use priority, also preserve FIFO order + if (!next || + (push_first ? next->prio.load().prio >= + static_cast(cpu)->prio.load().prio + : next->prio.load().prio > + static_cast(cpu)->prio.load().prio)) { + atomic_storage::release( + static_cast(cpu)->next_ppu, next); + atomic_storage::release(*it, + static_cast(cpu)); + break; + } + + it = &next->next_ppu; + } + + // Unregister timeout if necessary + for (auto it = g_waiting.cbegin(), end = g_waiting.cend(); it != end; + it++) { + if (it->second == cpu) { + g_waiting.erase(it); + break; + } + } + + ppu_log.trace("awake(): %s", cpu->id); + return true; + }; + + // Yield changed the queue before + bool changed_queue = prio == yield_cmd; + + s32 lowest_new_priority = smax; + const bool has_free_hw_thread_space = + count_non_sleeping_threads().onproc_count < g_cfg.core.ppu_threads + 0u; + + if (cpu && prio != yield_cmd) { + // Emplace current thread + if (emplace_thread(cpu)) { + changed_queue = true; + lowest_new_priority = + std::min(static_cast(cpu)->prio.load().prio, + lowest_new_priority); + } + } else + for (const auto _cpu : g_to_awake) { + // Emplace threads from list + if (emplace_thread(_cpu)) { + changed_queue = true; + lowest_new_priority = + std::min(static_cast(_cpu)->prio.load().prio, + lowest_new_priority); + } + } + + auto target = +g_ppu; + usz i = 0; + + // Suspend threads if necessary + for (usz thread_count = g_cfg.core.ppu_threads; target; + target = target->next_ppu, i++) { + if (i >= thread_count && cpu_flag::suspend - target->state) { + ppu_log.trace("suspend(): %s", target->id); + target->ack_suspend = true; + g_pending++; + ensure(!target->state.test_and_set(cpu_flag::suspend)); + + if (is_paused(target->state - cpu_flag::suspend)) { + target->state.notify_one(); + } + } + } + + const auto current_ppu = cpu_thread::get_current(); + + // Remove pending if necessary + if (current_ppu) { + if (std::exchange(current_ppu->ack_suspend, false)) { + ensure(g_pending)--; + } + } + + // In real PS3 (it seems), when a thread with a higher priority than the + // caller is signaled and - + // - that there is available space on the running queue for the other hardware + // thread to start It prioritizes signaled thread - caller's hardware thread + // switches instantly to the new thread code While signaling to the other + // hardware thread to execute the caller's code. Resulting in a delay to the + // caller after such thread is signaled + + if (current_ppu && changed_queue && has_free_hw_thread_space) { + if (current_ppu->prio.load().prio > lowest_new_priority) { + const bool is_create_thread = current_ppu->gpr[11] == 0x35; + + // When not being set to All timers - activate only for + // sys_ppu_thread_start + if (is_create_thread || g_cfg.core.sleep_timers_accuracy == + sleep_timers_accuracy_level::_all_timers) { + if (!current_ppu->state.test_and_set(cpu_flag::yield) || + current_ppu->hw_sleep_time != 0) { + current_ppu->hw_sleep_time += (is_create_thread ? 51 : 35); + } else { + current_ppu->hw_sleep_time = + 30000; // In addition to another flag's use (TODO: Refactor and + // clean this) + } + } + } + } + + return changed_queue; +} + +void lv2_obj::cleanup() { + g_ppu = nullptr; + g_scheduler_ready = false; + g_to_sleep.clear(); + g_waiting.clear(); + g_pending = 0; + s_yield_frequency = 0; +} + +void lv2_obj::schedule_all(u64 current_time) { + auto it = std::find(g_to_notify, std::end(g_to_notify), + std::add_pointer_t{}); + + if (!g_pending && g_scheduler_ready) { + auto target = +g_ppu; + + // Wake up threads + for (usz x = g_cfg.core.ppu_threads; target && x; + target = target->next_ppu, x--) { + if (target->state & cpu_flag::suspend) { + ppu_log.trace("schedule(): %s", target->id); + + // Remove yield if it was sleeping until now + const bs_t remove_yield = + target->start_time == 0 ? +cpu_flag::suspend + : (cpu_flag::yield + cpu_flag::preempt); + + target->start_time = 0; + + if ((target->state.fetch_op(AOFN(x += cpu_flag::signal, + x -= cpu_flag::suspend, + x -= remove_yield, void())) & + (cpu_flag::wait + cpu_flag::signal)) != cpu_flag::wait) { + continue; + } + + if (it == std::end(g_to_notify)) { + // Out of notification slots, notify locally (resizable container is + // not worth it) + target->state.notify_one(); + } else { + *it++ = &target->state; + } + } + } + } + + // Check registered timeouts + while (!g_waiting.empty()) { + const auto pair = &g_waiting.front(); + + if (!current_time) { + current_time = get_guest_system_time(); + } + + if (pair->first <= current_time) { + const auto target = pair->second; + g_waiting.pop_front(); + + if (target != cpu_thread::get_current()) { + // Change cpu_thread::state for the lightweight notification to work + ensure(!target->state.test_and_set(cpu_flag::notify)); + + // Otherwise notify it to wake itself + if (it == std::end(g_to_notify)) { + // Out of notification slots, notify locally (resizable container is + // not worth it) + target->state.notify_one(); + } else { + *it++ = &target->state; + } + } + } else { + // The list is sorted so assume no more timeouts + break; + } + } + + if (it < std::end(g_to_notify)) { + // Null-terminate the list if it ends before last slot + *it = nullptr; + } + + if (const u64 freq = s_yield_frequency) { + const u64 tsc = utils::get_tsc(); + const u64 last_tsc = s_last_yield_tsc; + + if (tsc >= last_tsc && tsc <= s_max_allowed_yield_tsc && + tsc - last_tsc >= freq) { + auto target = +g_ppu; + cpu_thread *cpu = nullptr; + + for (usz x = g_cfg.core.ppu_threads;; target = target->next_ppu, x--) { + if (!target || !x) { + if (g_ppu && cpu_flag::preempt - g_ppu->state) { + // Don't be picky, pick up any running PPU thread even it has a wait + // flag + cpu = g_ppu; + } + // TODO: If this case is common enough it may be valuable to iterate + // over all CPU threads to find a perfect candidate (one without a + // wait or suspend flag) + else if (auto current = cpu_thread::get_current(); + current && cpu_flag::suspend - current->state) { + // May be an SPU or RSX thread, use them as a last resort + cpu = current; + } + + break; + } + + if (target->state.none_of(cpu_flag::preempt + cpu_flag::wait)) { + cpu = target; + break; + } + } + + if (cpu && cpu_flag::preempt - cpu->state && + !cpu->state.test_and_set(cpu_flag::preempt)) { + s_last_yield_tsc = tsc; + g_lv2_preempts_taken.release( + g_lv2_preempts_taken.load() + + 1); // Has a minor race but performance is more important + rsx::set_rsx_yield_flag(); + } + } + } +} + +void lv2_obj::make_scheduler_ready() { + g_scheduler_ready.release(true); + lv2_obj::awake_all(); +} + +std::pair +lv2_obj::ppu_state(ppu_thread *ppu, bool lock_idm, bool lock_lv2) { + std::optional opt_lock[2]; + + if (lock_idm) { + opt_lock[0].emplace(id_manager::g_mutex); + } + + if (!Emu.IsReady() ? ppu->state.all_of(cpu_flag::stop) + : ppu->stop_flag_removal_protection) { + return {PPU_THREAD_STATUS_IDLE, 0}; + } + + switch (ppu->joiner) { + case ppu_join_status::zombie: + return {PPU_THREAD_STATUS_ZOMBIE, 0}; + case ppu_join_status::exited: + return {PPU_THREAD_STATUS_DELETED, 0}; + default: + break; + } + + if (lock_lv2) { + opt_lock[1].emplace(lv2_obj::g_mutex); + } + + u32 pos = umax; + u32 i = 0; + + for (auto target = +g_ppu; target; target = target->next_ppu, i++) { + if (target == ppu) { + pos = i; + break; + } + } + + if (pos == umax) { + if (!ppu->interrupt_thread_executing) { + return {PPU_THREAD_STATUS_STOP, 0}; + } + + return {PPU_THREAD_STATUS_SLEEP, 0}; + } + + if (pos >= g_cfg.core.ppu_threads + 0u) { + return {PPU_THREAD_STATUS_RUNNABLE, pos}; + } + + return {PPU_THREAD_STATUS_ONPROC, pos}; +} + +void lv2_obj::set_future_sleep(cpu_thread *cpu) { + g_to_sleep.emplace_back(cpu); +} + +bool lv2_obj::is_scheduler_ready() { + reader_lock lock(g_mutex); + return g_to_sleep.empty(); +} + +ppu_non_sleeping_count_t lv2_obj::count_non_sleeping_threads() { + ppu_non_sleeping_count_t total{}; + + auto target = atomic_storage::load(g_ppu); + + for (usz thread_count = g_cfg.core.ppu_threads; target; + target = atomic_storage::load(target->next_ppu)) { + if (total.onproc_count == thread_count) { + total.has_running = true; + break; + } + + total.onproc_count++; + } + + return total; +} + +void lv2_obj::set_yield_frequency(u64 freq, u64 max_allowed_tsc) { + s_yield_frequency.release(freq); + s_max_allowed_yield_tsc.release(max_allowed_tsc); + g_lv2_preempts_taken.release(0); +} + +#if defined(_MSC_VER) +#define mwaitx_func +#define waitpkg_func +#else +#define mwaitx_func __attribute__((__target__("mwaitx"))) +#define waitpkg_func __attribute__((__target__("waitpkg"))) +#endif + +#if defined(ARCH_X64) +// Waits for a number of TSC clock cycles in power optimized state +// Cstate is represented in bits [7:4]+1 cstate. So C0 requires bits [7:4] to be +// set to 0xf, C1 requires bits [7:4] to be set to 0. +mwaitx_func static void __mwaitx(u32 cycles, u32 cstate) { + constexpr u32 timer_enable = 0x2; + + // monitorx will wake if the cache line is written to. We don't want this, so + // place the monitor value on it's own cache line. + alignas(64) u64 monitor_var{}; + _mm_monitorx(&monitor_var, 0, 0); + _mm_mwaitx(timer_enable, cstate, cycles); +} + +// First bit indicates cstate, 0x0 for C.02 state (lower power) or 0x1 for C.01 +// state (higher power) +waitpkg_func static void __tpause(u32 cycles, u32 cstate) { + const u64 tsc = utils::get_tsc() + cycles; + _tpause(cstate, tsc); +} +#endif + +bool lv2_obj::wait_timeout(u64 usec, ppu_thread *cpu, bool scale, + bool is_usleep) { + static_assert(u64{umax} / max_timeout >= 100, + "max timeout is not valid for scaling"); + + const u64 start_time = get_system_time(); + + if (cpu) { + if (u64 end_time = cpu->end_time; end_time != umax) { + const u64 guest_start = get_guest_system_time(start_time); + + if (end_time <= guest_start) { + return true; + } + + usec = end_time - guest_start; + scale = true; + } + } + + if (scale) { + // Scale time + usec = std::min(usec, u64{umax} / 100) * 100 / g_cfg.core.clocks_scale; + } + + // Clamp + usec = std::min(usec, max_timeout); + + u64 passed = 0; + + atomic_bs_t dummy{}; + const auto &state = cpu ? cpu->state : dummy; + auto old_state = +state; + + auto wait_for = [&](u64 timeout) { + thread_ctrl::wait_on(state, old_state, timeout); + }; + + for (;; old_state = state) { + if (old_state & cpu_flag::notify) { + // Timeout notification has been forced + break; + } + + if (old_state & cpu_flag::signal) { + return false; + } + + if (::is_stopped(old_state) || + thread_ctrl::state() == thread_state::aborting) { + return passed >= usec; + } + + if (passed >= usec) { + break; + } + + u64 remaining = usec - passed; +#ifdef __linux__ + // NOTE: Assumption that timer initialization has succeeded + constexpr u64 host_min_quantum = 10; +#else + // Host scheduler quantum for windows (worst case) + // NOTE: On ps3 this function has very high accuracy + constexpr u64 host_min_quantum = 500; +#endif + // TODO: Tune for other non windows operating sytems + + if (g_cfg.core.sleep_timers_accuracy < + (is_usleep ? sleep_timers_accuracy_level::_usleep + : sleep_timers_accuracy_level::_all_timers)) { + wait_for(remaining); + } else { + if (remaining > host_min_quantum) { +#ifdef __linux__ + // With timerslack set low, Linux is precise for all values above + wait_for(remaining); +#else + // Wait on multiple of min quantum for large durations to avoid + // overloading low thread cpus + wait_for(remaining - (remaining % host_min_quantum)); +#endif + } + // TODO: Determine best value for yield delay +#if defined(ARCH_X64) + else if (utils::has_appropriate_um_wait()) { + const u32 us_in_tsc_clocks = + ::narrow(remaining * (utils::get_tsc_freq() / 1000000ULL)); + + if (utils::has_waitpkg()) { + __tpause(us_in_tsc_clocks, 0x1); + } else { + __mwaitx(us_in_tsc_clocks, 0xf0); + } + } +#endif + else { + // Try yielding. May cause long wake latency but helps weaker CPUs a lot + // by alleviating resource pressure + std::this_thread::yield(); + } + } + + passed = get_system_time() - start_time; + } + + return true; +} + +void lv2_obj::prepare_for_sleep(cpu_thread &cpu) { + vm::temporary_unlock(cpu); + cpu_counter::remove(&cpu); +} + +void lv2_obj::notify_all() noexcept { + for (auto cpu : g_to_notify) { + if (!cpu) { + break; + } + + if (cpu != &g_to_notify) { + const auto res_start = vm::reservation_notifier(0).second; + const auto res_end = vm::reservation_notifier(umax).second; + + if (cpu >= res_start && cpu <= res_end) { + atomic_wait_engine::notify_all(cpu); + } else { + // Note: by the time of notification the thread could have been + // deallocated which is why the direct function is used + atomic_wait_engine::notify_one(cpu); + } + } + } + + g_to_notify[0] = nullptr; + g_postpone_notify_barrier = false; + + const auto cpu = cpu_thread::get_current(); + + if (!cpu) { + return; + } + + if (cpu->get_class() != thread_class::spu && + cpu->state.none_of(cpu_flag::suspend)) { + return; + } + + std::optional lock; + + constexpr usz total_waiters = std::size(spu_thread::g_spu_waiters_by_value); + + u32 notifies[total_waiters]{}; + + // There may be 6 waiters, but checking them all may be performance expensive + // Instead, check 2 at max, but use the CPU ID index to tell which index to + // start checking so the work would be distributed across all threads + + atomic_t *range_lock = nullptr; + + for (usz i = 0, checked = 0; checked < 3 && i < total_waiters; i++) { + auto &waiter = + spu_thread::g_spu_waiters_by_value[(i + cpu->id) % total_waiters]; + const u64 value = waiter.load(); + u32 raddr = static_cast(value) & -128; + + if (vm::check_addr(raddr)) { + if (((raddr >> 28) < 2 || (raddr >> 28) == 0xd)) { + checked++; + + if (compute_rdata_hash32(*vm::get_super_ptr(raddr)) != + static_cast(value >> 32)) { + // Clear address to avoid a race, keep waiter counter + if (waiter + .fetch_op([&](u64 &x) { + if ((x & -128) == (value & -128)) { + x &= 127; + return true; + } + + return false; + }) + .second) { + notifies[i] = raddr; + } + } + + continue; + } + + if (!range_lock) { + range_lock = vm::alloc_range_lock(); + } + + checked++; + + if (spu_thread::reservation_check(raddr, static_cast(value >> 32), + range_lock)) { + // Clear address to avoid a race, keep waiter counter + if (waiter + .fetch_op([&](u64 &x) { + if ((x & -128) == (value & -128)) { + x &= 127; + return true; + } + + return false; + }) + .second) { + notifies[i] = raddr; + } + } + } + } + + if (range_lock) { + vm::free_range_lock(range_lock); + } + + for (u32 addr : notifies) { + if (addr) { + vm::reservation_notifier_notify(addr); + } + } +} diff --git a/kernel/cellos/src/sys_bdemu.cpp b/kernel/cellos/src/sys_bdemu.cpp new file mode 100644 index 000000000..0e4f8a472 --- /dev/null +++ b/kernel/cellos/src/sys_bdemu.cpp @@ -0,0 +1,16 @@ +#include "stdafx.h" + +#include "Emu/Cell/ErrorCodes.h" + +#include "sys_bdemu.h" + +LOG_CHANNEL(sys_bdemu); + +error_code sys_bdemu_send_command(u64 cmd, u64 a2, u64 a3, vm::ptr buf, + u64 buf_len) { + sys_bdemu.todo("sys_bdemu_send_command(cmd=0%llx, a2=0x%x, a3=0x%x, " + "buf=0x%x, buf_len=0x%x)", + cmd, a2, a3, buf, buf_len); + + return CELL_OK; +} diff --git a/kernel/cellos/src/sys_btsetting.cpp b/kernel/cellos/src/sys_btsetting.cpp new file mode 100644 index 000000000..0a1f6e0cb --- /dev/null +++ b/kernel/cellos/src/sys_btsetting.cpp @@ -0,0 +1,12 @@ +#include "stdafx.h" + +#include "Emu/Cell/ErrorCodes.h" +#include "sys_btsetting.h" + +LOG_CHANNEL(sys_btsetting); + +error_code sys_btsetting_if(u64 cmd, vm::ptr msg) { + sys_btsetting.todo("sys_btsetting_if(cmd=0x%llx, msg=*0x%x)", cmd, msg); + + return CELL_OK; +} diff --git a/kernel/cellos/src/sys_cond.cpp b/kernel/cellos/src/sys_cond.cpp new file mode 100644 index 000000000..17c066b85 --- /dev/null +++ b/kernel/cellos/src/sys_cond.cpp @@ -0,0 +1,509 @@ +#include "stdafx.h" + +#include "Emu/IdManager.h" +#include "Emu/System.h" +#include "util/serialization.hpp" + +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/Cell/PPUThread.h" + +#include "sys_cond.h" + +#include "util/asm.hpp" + +LOG_CHANNEL(sys_cond); + +lv2_cond::lv2_cond(utils::serial &ar) noexcept + : key(ar), name(ar), mtx_id(ar), + mutex(idm::check_unlocked(mtx_id)), + _mutex(idm::get_unlocked(mtx_id)) // May be nullptr +{} + +lv2_cond::lv2_cond(u64 key, u64 name, u32 mtx_id, + shared_ptr mutex0) noexcept + : key(key), name(name), mtx_id(mtx_id), + mutex(static_cast(mutex0.get())), _mutex(mutex0) {} + +CellError lv2_cond::on_id_create() { + exists++; + + static auto do_it = [](lv2_cond *_this) -> CellError { + if (lv2_obj::check(_this->mutex)) { + _this->mutex->cond_count++; + return {}; + } + + // Mutex has been destroyed, cannot create conditional variable + return CELL_ESRCH; + }; + + if (mutex) { + return do_it(this); + } + + ensure(!!Emu.DeserialManager()); + + Emu.PostponeInitCode([this]() { + if (!mutex) { + _mutex = static_cast>( + ensure(idm::get_unlocked(mtx_id))); + } + + // Defer function + ensure(CellError{} == do_it(this)); + }); + + return {}; +} + +std::function lv2_cond::load(utils::serial &ar) { + return load_func(make_shared(exact_t(ar))); +} + +void lv2_cond::save(utils::serial &ar) { ar(key, name, mtx_id); } + +error_code sys_cond_create(ppu_thread &ppu, vm::ptr cond_id, u32 mutex_id, + vm::ptr attr) { + ppu.state += cpu_flag::wait; + + sys_cond.trace("sys_cond_create(cond_id=*0x%x, mutex_id=0x%x, attr=*0x%x)", + cond_id, mutex_id, attr); + + auto mutex = idm::get_unlocked(mutex_id); + + if (!mutex) { + return CELL_ESRCH; + } + + const auto _attr = *attr; + + const u64 ipc_key = lv2_obj::get_key(_attr); + + if (ipc_key) { + sys_cond.warning("sys_cond_create(cond_id=*0x%x, attr=*0x%x): IPC=0x%016x", + cond_id, attr, ipc_key); + } + + if (const auto error = + lv2_obj::create(_attr.pshared, ipc_key, _attr.flags, [&] { + return make_single(ipc_key, _attr.name_u64, mutex_id, + std::move(mutex)); + })) { + return error; + } + + ppu.check_state(); + *cond_id = idm::last_id(); + return CELL_OK; +} + +error_code sys_cond_destroy(ppu_thread &ppu, u32 cond_id) { + ppu.state += cpu_flag::wait; + + sys_cond.trace("sys_cond_destroy(cond_id=0x%x)", cond_id); + + const auto cond = idm::withdraw( + cond_id, [&](lv2_cond &cond) -> CellError { + std::lock_guard lock(cond.mutex->mutex); + + if (atomic_storage::load(cond.sq)) { + return CELL_EBUSY; + } + + cond.mutex->cond_count--; + lv2_obj::on_id_destroy(cond, cond.key); + return {}; + }); + + if (!cond) { + return CELL_ESRCH; + } + + if (cond->key) { + sys_cond.warning("sys_cond_destroy(cond_id=0x%x): IPC=0x%016x", cond_id, + cond->key); + } + + if (cond.ret) { + return cond.ret; + } + + return CELL_OK; +} + +error_code sys_cond_signal(ppu_thread &ppu, u32 cond_id) { + ppu.state += cpu_flag::wait; + + sys_cond.trace("sys_cond_signal(cond_id=0x%x)", cond_id); + + while (true) { + if (ppu.test_stopped()) { + ppu.state += cpu_flag::again; + return {}; + } + + bool finished = true; + + ppu.state += cpu_flag::wait; + + const auto cond = idm::check( + cond_id, [&, notify = lv2_obj::notify_all_t()](lv2_cond &cond) { + if (atomic_storage::load(cond.sq)) { + std::lock_guard lock(cond.mutex->mutex); + + if (ppu.state & cpu_flag::suspend) { + // Test if another signal caused the current thread to be + // suspended, in which case it needs to wait until the thread + // wakes up (otherwise the signal may cause unexpected results) + finished = false; + return; + } + + if (const auto cpu = + cond.schedule(cond.sq, cond.mutex->protocol)) { + if (static_cast(cpu)->state & cpu_flag::again) { + ppu.state += cpu_flag::again; + return; + } + + // TODO: Is EBUSY returned after reqeueing, on sys_cond_destroy? + + if (cond.mutex->try_own(*cpu)) { + cond.awake(cpu); + } + } + } else { + cond.mutex->mutex.lock_unlock(); + + if (ppu.state & cpu_flag::suspend) { + finished = false; + } + } + }); + + if (!finished) { + continue; + } + + if (!cond) { + return CELL_ESRCH; + } + + return CELL_OK; + } +} + +error_code sys_cond_signal_all(ppu_thread &ppu, u32 cond_id) { + ppu.state += cpu_flag::wait; + + sys_cond.trace("sys_cond_signal_all(cond_id=0x%x)", cond_id); + + while (true) { + if (ppu.test_stopped()) { + ppu.state += cpu_flag::again; + return {}; + } + + bool finished = true; + + ppu.state += cpu_flag::wait; + + const auto cond = idm::check( + cond_id, [&, notify = lv2_obj::notify_all_t()](lv2_cond &cond) { + if (atomic_storage::load(cond.sq)) { + std::lock_guard lock(cond.mutex->mutex); + + if (ppu.state & cpu_flag::suspend) { + // Test if another signal caused the current thread to be + // suspended, in which case it needs to wait until the thread + // wakes up (otherwise the signal may cause unexpected results) + finished = false; + return; + } + + for (auto cpu = +cond.sq; cpu; cpu = cpu->next_cpu) { + if (cpu->state & cpu_flag::again) { + ppu.state += cpu_flag::again; + return; + } + } + + cpu_thread *result = nullptr; + auto sq = cond.sq; + atomic_storage::release(cond.sq, nullptr); + + while (const auto cpu = + cond.schedule(sq, SYS_SYNC_PRIORITY)) { + if (cond.mutex->try_own(*cpu)) { + ensure(!std::exchange(result, cpu)); + } + } + + if (result) { + cond.awake(result); + } + } else { + cond.mutex->mutex.lock_unlock(); + + if (ppu.state & cpu_flag::suspend) { + finished = false; + } + } + }); + + if (!finished) { + continue; + } + + if (!cond) { + return CELL_ESRCH; + } + + return CELL_OK; + } +} + +error_code sys_cond_signal_to(ppu_thread &ppu, u32 cond_id, u32 thread_id) { + ppu.state += cpu_flag::wait; + + sys_cond.trace("sys_cond_signal_to(cond_id=0x%x, thread_id=0x%x)", cond_id, + thread_id); + + while (true) { + if (ppu.test_stopped()) { + ppu.state += cpu_flag::again; + return {}; + } + + bool finished = true; + + ppu.state += cpu_flag::wait; + + const auto cond = idm::check( + cond_id, [&, notify = lv2_obj::notify_all_t()](lv2_cond &cond) { + if (!idm::check_unlocked>(thread_id)) { + return -1; + } + + if (atomic_storage::load(cond.sq)) { + std::lock_guard lock(cond.mutex->mutex); + + if (ppu.state & cpu_flag::suspend) { + // Test if another signal caused the current thread to be + // suspended, in which case it needs to wait until the thread + // wakes up (otherwise the signal may cause unexpected results) + finished = false; + return 0; + } + + for (auto cpu = +cond.sq; cpu; cpu = cpu->next_cpu) { + if (cpu->id == thread_id) { + if (static_cast(cpu)->state & cpu_flag::again) { + ppu.state += cpu_flag::again; + return 0; + } + + ensure(cond.unqueue(cond.sq, cpu)); + + if (cond.mutex->try_own(*cpu)) { + cond.awake(cpu); + } + + return 1; + } + } + } else { + cond.mutex->mutex.lock_unlock(); + + if (ppu.state & cpu_flag::suspend) { + finished = false; + return 0; + } + } + + return 0; + }); + + if (!finished) { + continue; + } + + if (!cond || cond.ret == -1) { + return CELL_ESRCH; + } + + if (!cond.ret) { + return not_an_error(CELL_EPERM); + } + + return CELL_OK; + } +} + +error_code sys_cond_wait(ppu_thread &ppu, u32 cond_id, u64 timeout) { + ppu.state += cpu_flag::wait; + + sys_cond.trace("sys_cond_wait(cond_id=0x%x, timeout=%lld)", cond_id, timeout); + + // Further function result + ppu.gpr[3] = CELL_OK; + + auto &sstate = *ppu.optional_savestate_state; + + const auto cond = idm::get( + cond_id, [&, notify = lv2_obj::notify_all_t()](lv2_cond &cond) -> s64 { + if (!ppu.loaded_from_savestate && + atomic_storage::load(cond.mutex->control.raw().owner) != + ppu.id) { + return -1; + } + + lv2_obj::prepare_for_sleep(ppu); + + std::lock_guard lock(cond.mutex->mutex); + + const u64 syscall_state = sstate.try_read().second; + sstate.clear(); + + if (ppu.loaded_from_savestate) { + if (syscall_state & 1) { + // Mutex sleep + ensure(!cond.mutex->try_own(ppu)); + } else { + lv2_obj::emplace(cond.sq, &ppu); + } + + cond.sleep(ppu, timeout); + return static_cast(syscall_state >> 32); + } + + // Register waiter + lv2_obj::emplace(cond.sq, &ppu); + + // Unlock the mutex + const u32 count = cond.mutex->lock_count.exchange(0); + + if (const auto cpu = cond.mutex->reown()) { + if (cpu->state & cpu_flag::again) { + ensure(cond.unqueue(cond.sq, &ppu)); + ppu.state += cpu_flag::again; + return 0; + } + + cond.mutex->append(cpu); + } + + // Sleep current thread and schedule mutex waiter + cond.sleep(ppu, timeout); + + // Save the recursive value + return count; + }); + + if (!cond) { + return CELL_ESRCH; + } + + if (ppu.state & cpu_flag::again) { + return {}; + } + + if (cond.ret < 0) { + return CELL_EPERM; + } + + while (auto state = +ppu.state) { + if (state & cpu_flag::signal && + ppu.state.test_and_reset(cpu_flag::signal)) { + break; + } + + if (is_stopped(state)) { + std::lock_guard lock(cond->mutex->mutex); + + bool mutex_sleep = false; + bool cond_sleep = false; + + for (auto cpu = atomic_storage::load(cond->sq); cpu; + cpu = cpu->next_cpu) { + if (cpu == &ppu) { + cond_sleep = true; + break; + } + } + + for (auto cpu = atomic_storage::load( + cond->mutex->control.raw().sq); + cpu; cpu = cpu->next_cpu) { + if (cpu == &ppu) { + mutex_sleep = true; + break; + } + } + + if (!cond_sleep && !mutex_sleep) { + break; + } + + const u64 optional_syscall_state = + u32{mutex_sleep} | (u64{static_cast(cond.ret)} << 32); + sstate(optional_syscall_state); + + ppu.state += cpu_flag::again; + return {}; + } + + for (usz i = 0; cpu_flag::signal - ppu.state && i < 50; i++) { + busy_wait(500); + } + + if (ppu.state & cpu_flag::signal) { + continue; + } + + if (timeout) { + if (lv2_obj::wait_timeout(timeout, &ppu)) { + const u64 start_time = ppu.start_time; + + // Wait for rescheduling + if (ppu.check_state()) { + continue; + } + + ppu.state += cpu_flag::wait; + + std::lock_guard lock(cond->mutex->mutex); + + // Try to cancel the waiting + if (cond->unqueue(cond->sq, &ppu)) { + // TODO: Is EBUSY returned after reqeueing, on sys_cond_destroy? + ppu.gpr[3] = CELL_ETIMEDOUT; + + // Own or requeue + if (cond->mutex->try_own(ppu)) { + break; + } + } else if (atomic_storage::load( + cond->mutex->control.raw().owner) == ppu.id) { + break; + } + + cond->mutex->sleep(ppu); + ppu.start_time = + start_time; // Restore start time because awake has been called + timeout = 0; + continue; + } + } else { + ppu.state.wait(state); + } + } + + // Verify ownership + ensure(atomic_storage::load(cond->mutex->control.raw().owner) == ppu.id); + + // Restore the recursive value + cond->mutex->lock_count.release(static_cast(cond.ret)); + + return not_an_error(ppu.gpr[3]); +} diff --git a/kernel/cellos/src/sys_config.cpp b/kernel/cellos/src/sys_config.cpp new file mode 100644 index 000000000..65aa7edc3 --- /dev/null +++ b/kernel/cellos/src/sys_config.cpp @@ -0,0 +1,464 @@ +#include "stdafx.h" + +#include "Emu/IdManager.h" + +#include "Emu/Cell/ErrorCodes.h" +#include "cellos/sys_event.h" + +#include "sys_config.h" + +LOG_CHANNEL(sys_config); + +// Enums +template <> +void fmt_class_string::format(std::string &out, u64 id) { + const s64 s_id = static_cast(id); + + switch (s_id) { + case SYS_CONFIG_SERVICE_PADMANAGER: + out += "SYS_CONFIG_SERVICE_PADMANAGER"; + return; + case SYS_CONFIG_SERVICE_PADMANAGER2: + out += "SYS_CONFIG_SERVICE_PADMANAGER2"; + return; + case SYS_CONFIG_SERVICE_USER_LIBPAD: + out += "SYS_CONFIG_SERVICE_USER_LIBPAD"; + return; + case SYS_CONFIG_SERVICE_USER_LIBKB: + out += "SYS_CONFIG_SERVICE_USER_LIBKB"; + return; + case SYS_CONFIG_SERVICE_USER_LIBMOUSE: + out += "SYS_CONFIG_SERVICE_USER_LIBMOUSE"; + return; + } + + if (s_id < 0) { + fmt::append(out, "SYS_CONFIG_SERVICE_USER_%llx", id & ~(1ull << 63)); + } else { + fmt::append(out, "SYS_CONFIG_SERVICE_%llx", id); + } +} + +template <> +void fmt_class_string::format( + std::string &out, u64 arg) { + format_enum(out, arg, [](auto value) { + switch (value) { + STR_CASE(SYS_CONFIG_SERVICE_LISTENER_ONCE); + STR_CASE(SYS_CONFIG_SERVICE_LISTENER_REPEATING); + } + + return unknown; + }); +} + +// Utilities +void dump_buffer(std::string &out, const std::vector &buffer) { + if (!buffer.empty()) { + out.reserve(out.size() + buffer.size() * 2 + 1); + fmt::append(out, "0x"); + + for (u8 x : buffer) { + fmt::append(out, "%02x", x); + } + } else { + fmt::append(out, "EMPTY"); + } +} + +// LV2 Config +void lv2_config::initialize() { + if (m_state || !m_state.compare_and_swap_test(0, 1)) { + return; + } + + // Register padmanager service, notifying vsh that a controller is connected + static const u8 hid_info[0x1a] = { + 0x01, 0x01, // 2 unk + 0x02, 0x02, // 4 + 0x00, 0x00, // 6 + 0x00, 0x00, // 8 + 0x00, 0x00, // 10 + 0x05, 0x4c, // 12 vid + 0x02, 0x68, // 14 pid + 0x00, 0x10, // 16 unk2 + 0x91, 0x88, // 18 + 0x04, 0x00, // 20 + 0x00, 0x07, // 22 + 0x00, 0x00, // 24 + 0x00, 0x00 // 26 + }; + + // user_id for the padmanager seems to signify the controller port number, and + // the buffer contains some sort of HID descriptor + lv2_config_service::create(SYS_CONFIG_SERVICE_PADMANAGER, 0, 1, 0, hid_info, + 0x1a) + ->notify(); + lv2_config_service::create(SYS_CONFIG_SERVICE_PADMANAGER2, 0, 1, 0, hid_info, + 0x1a) + ->notify(); +} + +void lv2_config::add_service_event(shared_ptr event) { + std::lock_guard lock(m_mutex); + events.emplace(event->id, std::move(event)); +} + +void lv2_config::remove_service_event(u32 id) { + shared_ptr ptr; + + std::lock_guard lock(m_mutex); + + if (auto it = events.find(id); it != events.end()) { + ptr = std::move(it->second); + events.erase(it); + } +} + +lv2_config_service_event & +lv2_config_service_event::operator=(thread_state s) noexcept { + if (s == thread_state::destroying_context && !m_destroyed.exchange(true)) { + if (auto global = g_fxo->try_get()) { + global->remove_service_event(id); + } + } + + return *this; +} + +lv2_config_service_event::~lv2_config_service_event() noexcept { + operator=(thread_state::destroying_context); +} + +lv2_config::~lv2_config() noexcept { + for (auto &[key, event] : events) { + if (event) { + // Avoid collision with lv2_config_service_event destructor + event->m_destroyed = true; + } + } +} + +// LV2 Config Service Listener +bool lv2_config_service_listener::check_service( + const lv2_config_service &service) const { + // Filter by type + if (type == SYS_CONFIG_SERVICE_LISTENER_ONCE && !service_events.empty()) { + return false; + } + + // Filter by service ID or verbosity + if (service_id != service.id || min_verbosity > service.verbosity) { + return false; + } + + // realhw only seems to send the pad connected events to the listeners that + // provided 0x01 as the first byte of their data buffer + // TODO: Figure out how this filter works more properly + if (service_id == SYS_CONFIG_SERVICE_PADMANAGER && + (data.empty() || data[0] != 0x01)) { + return false; + } + + // Event applies to this listener! + return true; +} + +bool lv2_config_service_listener::notify( + const shared_ptr &event) { + service_events.emplace_back(event); + return event->notify(); +} + +bool lv2_config_service_listener::notify( + const shared_ptr &service) { + if (!check_service(*service)) + return false; + + // Create service event and notify queue! + const auto event = lv2_config_service_event::create(handle, service, *this); + return notify(event); +} + +void lv2_config_service_listener::notify_all() { + std::vector> services; + + // Grab all events + idm::select([&](u32 /*id*/, lv2_config_service &service) { + if (check_service(service)) { + services.push_back(service.get_shared_ptr()); + } + }); + + // Sort services by timestamp + sort(services.begin(), services.end(), + [](const shared_ptr &s1, + const shared_ptr &s2) { + return s1->timestamp < s2->timestamp; + }); + + // Notify listener (now with services in sorted order) + for (auto &service : services) { + this->notify(service); + } +} + +// LV2 Config Service +void lv2_config_service::unregister() { + registered = false; + + // Notify listeners + notify(); + + // Allow this object to be destroyed by withdrawing it from the IDM + // Note that it won't be destroyed while there are service events that hold a + // reference to it + idm::remove(idm_id); +} + +void lv2_config_service::notify() const { + std::vector> listeners; + + const shared_ptr sptr = get_shared_ptr(); + + idm::select( + [&](u32 /*id*/, lv2_config_service_listener &listener) { + if (listener.check_service(*sptr)) + listeners.push_back(listener.get_shared_ptr()); + }); + + for (auto &listener : listeners) { + listener->notify(sptr); + } +} + +bool lv2_config_service_event::notify() const { + const auto _handle = handle; + + if (!_handle) { + return false; + } + + // Send event + return _handle->notify(SYS_CONFIG_EVENT_SOURCE_SERVICE, + (static_cast(service->is_registered()) << 32) | + id, + service->get_size()); +} + +// LV2 Config Service Event +void lv2_config_service_event::write(sys_config_service_event_t *dst) const { + const auto registered = service->is_registered(); + + dst->service_listener_handle = listener.get_id(); + dst->registered = registered; + dst->service_id = service->id; + dst->user_id = service->user_id; + + if (registered) { + dst->verbosity = service->verbosity; + dst->padding = service->padding; + + const auto size = service->data.size(); + dst->data_size = static_cast(size); + memcpy(dst->data, service->data.data(), size); + } +} + +/* + * Syscalls + */ +error_code sys_config_open(u32 equeue_hdl, vm::ptr out_config_hdl) { + sys_config.trace("sys_config_open(equeue_hdl=0x%x, out_config_hdl=*0x%x)", + equeue_hdl, out_config_hdl); + + // Find queue with the given ID + const auto queue = idm::get_unlocked(equeue_hdl); + if (!queue) { + return CELL_ESRCH; + } + + // Initialize lv2_config global state + auto &global = g_fxo->get(); + if (true) { + global.initialize(); + } + + // Create a lv2_config_handle object + const auto config = lv2_config_handle::create(std::move(queue)); + if (config) { + *out_config_hdl = idm::last_id(); + return CELL_OK; + } + + // Failed to allocate sys_config object + return CELL_EAGAIN; +} + +error_code sys_config_close(u32 config_hdl) { + sys_config.trace("sys_config_close(config_hdl=0x%x)", config_hdl); + + if (!idm::remove(config_hdl)) { + return CELL_ESRCH; + } + + return CELL_OK; +} + +error_code sys_config_get_service_event(u32 config_hdl, u32 event_id, + vm::ptr dst, + u64 size) { + sys_config.trace("sys_config_get_service_event(config_hdl=0x%x, " + "event_id=0x%llx, dst=*0x%llx, size=0x%llx)", + config_hdl, event_id, dst, size); + + // Find sys_config handle object with the given ID + const auto cfg = idm::get_unlocked(config_hdl); + if (!cfg) { + return CELL_ESRCH; + } + + // Find service_event object + const auto event = g_fxo->get().find_event(event_id); + if (!event) { + return CELL_ESRCH; + } + + // Check buffer fits + if (!event->check_buffer_size(size)) { + return CELL_EAGAIN; + } + + // Write event to buffer + event->write(dst.get_ptr()); + + return CELL_OK; +} + +error_code sys_config_add_service_listener( + u32 config_hdl, sys_config_service_id service_id, u64 min_verbosity, + vm::ptr in, u64 size, sys_config_service_listener_type type, + vm::ptr out_listener_hdl) { + sys_config.trace("sys_config_add_service_listener(config_hdl=0x%x, " + "service_id=0x%llx, min_verbosity=0x%llx, in=*0x%x, " + "size=%lld, type=0x%llx, out_listener_hdl=*0x%x)", + config_hdl, service_id, min_verbosity, in, size, type, + out_listener_hdl); + + // Find sys_config handle object with the given ID + auto cfg = idm::get_unlocked(config_hdl); + if (!cfg) { + return CELL_ESRCH; + } + + // Create service listener + const auto listener = lv2_config_service_listener::create( + cfg, service_id, min_verbosity, type, static_cast(in.get_ptr()), + size); + if (!listener) { + return CELL_EAGAIN; + } + + if (size > 0) { + std::string buf_str; + dump_buffer(buf_str, listener->data); + sys_config.todo( + "Registered service listener for service %llx with non-zero buffer: %s", + service_id, buf_str.c_str()); + } + + // Notify listener with all past events + listener->notify_all(); + + // Done! + *out_listener_hdl = listener->get_id(); + return CELL_OK; +} + +error_code sys_config_remove_service_listener(u32 config_hdl, + u32 listener_hdl) { + sys_config.trace( + "sys_config_remove_service_listener(config_hdl=0x%x, listener_hdl=0x%x)", + config_hdl, listener_hdl); + + // Remove listener from IDM + if (!idm::remove(listener_hdl)) { + return CELL_ESRCH; + } + + return CELL_OK; +} + +error_code sys_config_register_service(u32 config_hdl, + sys_config_service_id service_id, + u64 user_id, u64 verbosity, + vm::ptr data_buf, u64 size, + vm::ptr out_service_hdl) { + sys_config.trace("sys_config_register_service(config_hdl=0x%x, " + "service_id=0x%llx, user_id=0x%llx, verbosity=0x%llx, " + "data_but=*0x%llx, size=%lld, out_service_hdl=*0x%llx)", + config_hdl, service_id, user_id, verbosity, data_buf, size, + out_service_hdl); + + // Find sys_config handle object with the given ID + const auto cfg = idm::get_unlocked(config_hdl); + if (!cfg) { + return CELL_ESRCH; + } + + // Create service + const auto service = lv2_config_service::create( + service_id, user_id, verbosity, 0, data_buf.get_ptr(), size); + if (!service) { + return CELL_EAGAIN; + } + + // Notify all listeners + service->notify(); + + // Done! + *out_service_hdl = service->get_id(); + return CELL_OK; +} + +error_code sys_config_unregister_service(u32 config_hdl, u32 service_hdl) { + sys_config.trace( + "sys_config_unregister_service(config_hdl=0x%x, service_hdl=0x%x)", + config_hdl, service_hdl); + + // Remove listener from IDM + auto service = idm::withdraw(service_hdl); + if (!service) { + return CELL_ESRCH; + } + + // Unregister service + service->unregister(); + + // Done! + return CELL_OK; +} + +/* + * IO Events - TODO + */ +error_code sys_config_get_io_event(u32 config_hdl, u32 event_id /*?*/, + vm::ptr out_buf /*?*/, + u64 size /*?*/) { + sys_config.todo("sys_config_get_io_event(config_hdl=0x%x, event_id=0x%x, " + "out_buf=*0x%x, size=%lld)", + config_hdl, event_id, out_buf, size); + return CELL_OK; +} + +error_code sys_config_register_io_error_listener(u32 config_hdl) { + sys_config.todo("sys_config_register_io_error_listener(config_hdl=0x%x)", + config_hdl); + return CELL_OK; +} + +error_code sys_config_unregister_io_error_listener(u32 config_hdl) { + sys_config.todo("sys_config_unregister_io_error_listener(config_hdl=0x%x)", + config_hdl); + return CELL_OK; +} diff --git a/kernel/cellos/src/sys_console.cpp b/kernel/cellos/src/sys_console.cpp new file mode 100644 index 000000000..407be0050 --- /dev/null +++ b/kernel/cellos/src/sys_console.cpp @@ -0,0 +1,13 @@ +#include "stdafx.h" + +#include "Emu/Cell/ErrorCodes.h" + +#include "sys_console.h" + +LOG_CHANNEL(sys_console); + +error_code sys_console_write(vm::cptr buf, u32 len) { + sys_console.todo("sys_console_write(buf=*0x%x, len=0x%x)", buf, len); + + return CELL_OK; +} diff --git a/kernel/cellos/src/sys_crypto_engine.cpp b/kernel/cellos/src/sys_crypto_engine.cpp new file mode 100644 index 000000000..d6cc2628c --- /dev/null +++ b/kernel/cellos/src/sys_crypto_engine.cpp @@ -0,0 +1,28 @@ +#include "stdafx.h" + +#include "Emu/Cell/ErrorCodes.h" + +#include "sys_crypto_engine.h" + +LOG_CHANNEL(sys_crypto_engine); + +error_code sys_crypto_engine_create(vm::ptr id) { + sys_crypto_engine.todo("sys_crypto_engine_create(id=*0x%x)", id); + + return CELL_OK; +} + +error_code sys_crypto_engine_destroy(u32 id) { + sys_crypto_engine.todo("sys_crypto_engine_destroy(id=0x%x)", id); + + return CELL_OK; +} + +error_code sys_crypto_engine_random_generate(vm::ptr buffer, + u64 buffer_size) { + sys_crypto_engine.todo( + "sys_crypto_engine_random_generate(buffer=*0x%x, buffer_size=0x%x", + buffer, buffer_size); + + return CELL_OK; +} diff --git a/kernel/cellos/src/sys_dbg.cpp b/kernel/cellos/src/sys_dbg.cpp new file mode 100644 index 000000000..40b3b38ba --- /dev/null +++ b/kernel/cellos/src/sys_dbg.cpp @@ -0,0 +1,128 @@ +#include "stdafx.h" + +#include "sys_dbg.h" + +#include "Emu/Cell/ErrorCodes.h" + +#include "Emu/Cell/PPUInterpreter.h" +#include "Emu/Memory/vm_locking.h" +#include "rpcsx/fw/ps3/sys_lv2dbg.h" + +#include "util/asm.hpp" + +void ppu_register_function_at(u32 addr, u32 size, + ppu_intrp_func_t ptr = nullptr); + +LOG_CHANNEL(sys_dbg); + +error_code sys_dbg_read_process_memory(s32 pid, u32 address, u32 size, + vm::ptr data) { + sys_dbg.warning("sys_dbg_read_process_memory(pid=0x%x, address=0x%llx, " + "size=0x%x, data=*0x%x)", + pid, address, size, data); + + // Todo(TGEnigma): Process lookup (only 1 process exists right now) + if (pid != 1) { + return CELL_LV2DBG_ERROR_DEINVALIDARGUMENTS; + } + + if (!size || !data) { + return CELL_LV2DBG_ERROR_DEINVALIDARGUMENTS; + } + + vm::writer_lock lock; + + // Check if data destination is writable + if (!vm::check_addr(data.addr(), vm::page_writable, size)) { + return CELL_EFAULT; + } + + // Check if the source is readable + if (!vm::check_addr(address, vm::page_readable, size)) { + return CELL_EFAULT; + } + + std::memmove(data.get_ptr(), vm::base(address), size); + + return CELL_OK; +} + +error_code sys_dbg_write_process_memory(s32 pid, u32 address, u32 size, + vm::cptr data) { + sys_dbg.warning("sys_dbg_write_process_memory(pid=0x%x, address=0x%llx, " + "size=0x%x, data=*0x%x)", + pid, address, size, data); + + // Todo(TGEnigma): Process lookup (only 1 process exists right now) + if (pid != 1) { + return CELL_LV2DBG_ERROR_DEINVALIDARGUMENTS; + } + + if (!size || !data) { + return CELL_LV2DBG_ERROR_DEINVALIDARGUMENTS; + } + + // Check if data source is readable + if (!vm::check_addr(data.addr(), vm::page_readable, size)) { + return CELL_EFAULT; + } + + // Check destination (can be read-only actually) + if (!vm::check_addr(address, vm::page_readable, size)) { + return CELL_EFAULT; + } + + vm::writer_lock lock; + + // Again + if (!vm::check_addr(data.addr(), vm::page_readable, size) || + !vm::check_addr(address, vm::page_readable, size)) { + return CELL_EFAULT; + } + + const u8 *data_ptr = static_cast(data.get_ptr()); + + if ((address >> 28) == 0xDu) { + // Stack pages (4k pages is the exception here) + std::memmove(vm::base(address), data_ptr, size); + return CELL_OK; + } + + const u32 end = address + size; + + for (u32 i = address, exec_update_size = 0; i < end;) { + const u32 op_size = + std::min(utils::align(i + 1, 0x10000), end) - i; + + const bool is_exec = + vm::check_addr(i, vm::page_executable | vm::page_readable); + + if (is_exec) { + exec_update_size += op_size; + i += op_size; + } + + if (!is_exec || i >= end) { + // Commit executable data update + // The read memory is also super ptr so memmove can work correctly on all + // implementations + const u32 before_addr = i - exec_update_size; + std::memmove(vm::get_super_ptr(before_addr), + vm::get_super_ptr(data.addr() + (before_addr - address)), + exec_update_size); + ppu_register_function_at(before_addr, exec_update_size); + exec_update_size = 0; + + if (i >= end) { + break; + } + } + + if (!is_exec) { + std::memmove(vm::base(i), data_ptr + (i - address), op_size); + i += op_size; + } + } + + return CELL_OK; +} diff --git a/kernel/cellos/src/sys_event.cpp b/kernel/cellos/src/sys_event.cpp new file mode 100644 index 000000000..c49f77b59 --- /dev/null +++ b/kernel/cellos/src/sys_event.cpp @@ -0,0 +1,732 @@ +#include "stdafx.h" + +#include "sys_event.h" + +#include "Emu/IPC.h" +#include "Emu/IdManager.h" +#include "Emu/System.h" + +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/Cell/PPUThread.h" +#include "Emu/Cell/SPUThread.h" +#include "sys_process.h" + +#include "util/asm.hpp" + +LOG_CHANNEL(sys_event); + +lv2_event_queue::lv2_event_queue(u32 protocol, s32 type, s32 size, u64 name, + u64 ipc_key) noexcept + : id(idm::last_id()), protocol{static_cast(protocol)}, + type(static_cast(type)), size(static_cast(size)), name(name), + key(ipc_key) {} + +lv2_event_queue::lv2_event_queue(utils::serial &ar) noexcept + : id(idm::last_id()), protocol(ar), type(ar), size(ar), name(ar), key(ar) { + ar(events); +} + +std::function lv2_event_queue::load(utils::serial &ar) { + auto queue = make_shared(exact_t(ar)); + return [ptr = lv2_obj::load(queue->key, queue)](void *storage) { + *static_cast *>(storage) = ptr; + }; +} + +void lv2_event_queue::save(utils::serial &ar) { + ar(protocol, type, size, name, key, events); +} + +void lv2_event_queue::save_ptr(utils::serial &ar, lv2_event_queue *q) { + if (!lv2_obj::check(q)) { + ar(u32{0}); + return; + } + + ar(q->id); +} + +shared_ptr +lv2_event_queue::load_ptr(utils::serial &ar, shared_ptr &queue, + std::string_view msg) { + const u32 id = ar.pop(); + + if (!id) { + return {}; + } + + if (auto q = idm::get_unlocked(id)) { + // Already initialized + return q; + } + + if (id >> 24 != id_base >> 24) { + fmt::throw_exception("Failed in event queue pointer deserialization " + "(invalid ID): location: %s, id=0x%x", + msg, id); + } + + Emu.PostponeInitCode([id, &queue, msg_str = std::string{msg}]() { + // Defer resolving + queue = idm::get_unlocked(id); + + if (!queue) { + fmt::throw_exception("Failed in event queue pointer deserialization (not " + "found): location: %s, id=0x%x", + msg_str, id); + } + }); + + // Null until resolved + return {}; +} + +lv2_event_port::lv2_event_port(utils::serial &ar) + : type(ar), name(ar), + queue(lv2_event_queue::load_ptr(ar, queue, "eventport")) {} + +void lv2_event_port::save(utils::serial &ar) { + ar(type, name); + + lv2_event_queue::save_ptr(ar, queue.get()); +} + +shared_ptr lv2_event_queue::find(u64 ipc_key) { + if (ipc_key == SYS_EVENT_QUEUE_LOCAL) { + // Invalid IPC key + return {}; + } + + return g_fxo->get>().get(ipc_key); +} + +extern void resume_spu_thread_group_from_waiting(spu_thread &spu); + +CellError lv2_event_queue::send(lv2_event event, bool *notified_thread, + lv2_event_port *port) { + if (notified_thread) { + *notified_thread = false; + } + + std::lock_guard lock(mutex); + + if (!exists) { + return CELL_ENOTCONN; + } + + if (!pq && !sq) { + if (events.size() < this->size + 0u) { + // Save event + events.emplace_back(event); + return {}; + } + + return CELL_EBUSY; + } + + if (type == SYS_PPU_QUEUE) { + // Store event in registers + auto &ppu = static_cast(*schedule(pq, protocol)); + + if (ppu.state & cpu_flag::again) { + if (auto cpu = get_current_cpu_thread()) { + cpu->state += cpu_flag::again; + cpu->state += cpu_flag::exit; + } + + sys_event.warning("Ignored event!"); + + // Fake error for abort + return CELL_EAGAIN; + } + + std::tie(ppu.gpr[4], ppu.gpr[5], ppu.gpr[6], ppu.gpr[7]) = event; + + awake(&ppu); + + if (port && + ppu.prio.load().prio < + ensure(cpu_thread::get_current())->prio.load().prio) { + // Block event port disconnection for the time being of sending events + // PPU -> lower prio PPU is the only case that can cause thread blocking + port->is_busy++; + ensure(notified_thread); + *notified_thread = true; + } + } else { + // Store event in In_MBox + auto &spu = static_cast(*schedule(sq, protocol)); + + if (spu.state & cpu_flag::again) { + if (auto cpu = get_current_cpu_thread()) { + cpu->state += cpu_flag::exit + cpu_flag::again; + } + + sys_event.warning("Ignored event!"); + + // Fake error for abort + return CELL_EAGAIN; + } + + const u32 data1 = static_cast(std::get<1>(event)); + const u32 data2 = static_cast(std::get<2>(event)); + const u32 data3 = static_cast(std::get<3>(event)); + spu.ch_in_mbox.set_values(4, CELL_OK, data1, data2, data3); + resume_spu_thread_group_from_waiting(spu); + } + + return {}; +} + +error_code sys_event_queue_create(cpu_thread &cpu, vm::ptr equeue_id, + vm::ptr attr, + u64 ipc_key, s32 size) { + cpu.state += cpu_flag::wait; + + sys_event.warning("sys_event_queue_create(equeue_id=*0x%x, attr=*0x%x, " + "ipc_key=0x%llx, size=%d)", + equeue_id, attr, ipc_key, size); + + if (size <= 0 || size > 127) { + return CELL_EINVAL; + } + + const u32 protocol = attr->protocol; + + if (protocol != SYS_SYNC_FIFO && protocol != SYS_SYNC_PRIORITY) { + sys_event.error("sys_event_queue_create(): unknown protocol (0x%x)", + protocol); + return CELL_EINVAL; + } + + const u32 type = attr->type; + + if (type != SYS_PPU_QUEUE && type != SYS_SPU_QUEUE) { + sys_event.error("sys_event_queue_create(): unknown type (0x%x)", type); + return CELL_EINVAL; + } + + const u32 pshared = ipc_key == SYS_EVENT_QUEUE_LOCAL + ? SYS_SYNC_NOT_PROCESS_SHARED + : SYS_SYNC_PROCESS_SHARED; + constexpr u32 flags = SYS_SYNC_NEWLY_CREATED; + const u64 name = attr->name_u64; + + if (const auto error = + lv2_obj::create(pshared, ipc_key, flags, [&]() { + return make_shared(protocol, type, size, name, + ipc_key); + })) { + return error; + } + + cpu.check_state(); + *equeue_id = idm::last_id(); + return CELL_OK; +} + +error_code sys_event_queue_destroy(ppu_thread &ppu, u32 equeue_id, s32 mode) { + ppu.state += cpu_flag::wait; + + sys_event.warning("sys_event_queue_destroy(equeue_id=0x%x, mode=%d)", + equeue_id, mode); + + if (mode && mode != SYS_EVENT_QUEUE_DESTROY_FORCE) { + return CELL_EINVAL; + } + + std::vector events; + + std::unique_lock qlock; + + cpu_thread *head{}; + + const auto queue = idm::withdraw( + equeue_id, [&](lv2_event_queue &queue) -> CellError { + qlock = std::unique_lock{queue.mutex}; + + head = queue.type == SYS_PPU_QUEUE + ? static_cast(+queue.pq) + : +queue.sq; + + if (!mode && head) { + return CELL_EBUSY; + } + + if (!queue.events.empty()) { + // Copy events for logging, does not empty + events.insert(events.begin(), queue.events.begin(), + queue.events.end()); + } + + lv2_obj::on_id_destroy(queue, queue.key); + + if (!head) { + qlock.unlock(); + } else { + for (auto cpu = head; cpu; cpu = cpu->get_next_cpu()) { + if (cpu->state & cpu_flag::again) { + ppu.state += cpu_flag::again; + return CELL_EAGAIN; + } + } + } + + return {}; + }); + + if (!queue) { + return CELL_ESRCH; + } + + if (ppu.state & cpu_flag::again) { + return {}; + } + + if (queue.ret) { + return queue.ret; + } + + std::string lost_data; + + if (qlock.owns_lock()) { + if (sys_event.warning) { + u32 size = 0; + + for (auto cpu = head; cpu; cpu = cpu->get_next_cpu()) { + size++; + } + + fmt::append(lost_data, "Forcefully awaken waiters (%u):\n", size); + + for (auto cpu = head; cpu; cpu = cpu->get_next_cpu()) { + lost_data += cpu->get_name(); + lost_data += '\n'; + } + } + + if (queue->type == SYS_PPU_QUEUE) { + for (auto cpu = +queue->pq; cpu; cpu = cpu->next_cpu) { + cpu->gpr[3] = CELL_ECANCELED; + queue->append(cpu); + } + + atomic_storage::release(queue->pq, nullptr); + lv2_obj::awake_all(); + } else { + for (auto cpu = +queue->sq; cpu; cpu = cpu->next_cpu) { + cpu->ch_in_mbox.set_values(1, CELL_ECANCELED); + resume_spu_thread_group_from_waiting(*cpu); + } + + atomic_storage::release(queue->sq, nullptr); + } + + qlock.unlock(); + } + + if (sys_event.warning) { + if (!events.empty()) { + fmt::append(lost_data, "Unread queue events (%u):\n", events.size()); + } + + for (const lv2_event &evt : events) { + fmt::append(lost_data, "data0=0x%x, data1=0x%x, data2=0x%x, data3=0x%x\n", + std::get<0>(evt), std::get<1>(evt), std::get<2>(evt), + std::get<3>(evt)); + } + + if (!lost_data.empty()) { + sys_event.warning("sys_event_queue_destroy(): %s", lost_data); + } + } + + return CELL_OK; +} + +error_code sys_event_queue_tryreceive(ppu_thread &ppu, u32 equeue_id, + vm::ptr event_array, + s32 size, vm::ptr number) { + ppu.state += cpu_flag::wait; + + sys_event.trace("sys_event_queue_tryreceive(equeue_id=0x%x, " + "event_array=*0x%x, size=%d, number=*0x%x)", + equeue_id, event_array, size, number); + + const auto queue = idm::get_unlocked(equeue_id); + + if (!queue) { + return CELL_ESRCH; + } + + if (queue->type != SYS_PPU_QUEUE) { + return CELL_EINVAL; + } + + std::array events; + + std::unique_lock lock(queue->mutex); + + if (!queue->exists) { + return CELL_ESRCH; + } + + s32 count = 0; + + while (count < size && !queue->events.empty()) { + auto &dest = events[count++]; + std::tie(dest.source, dest.data1, dest.data2, dest.data3) = + queue->events.front(); + queue->events.pop_front(); + } + + lock.unlock(); + ppu.check_state(); + + std::copy_n(events.begin(), count, event_array.get_ptr()); + *number = count; + + return CELL_OK; +} + +error_code sys_event_queue_receive(ppu_thread &ppu, u32 equeue_id, + vm::ptr dummy_event, + u64 timeout) { + ppu.state += cpu_flag::wait; + + sys_event.trace( + "sys_event_queue_receive(equeue_id=0x%x, *0x%x, timeout=0x%llx)", + equeue_id, dummy_event, timeout); + + ppu.gpr[3] = CELL_OK; + + const auto queue = idm::get( + equeue_id, + [&, + notify = lv2_obj::notify_all_t()](lv2_event_queue &queue) -> CellError { + if (queue.type != SYS_PPU_QUEUE) { + return CELL_EINVAL; + } + + lv2_obj::prepare_for_sleep(ppu); + + std::lock_guard lock(queue.mutex); + + // "/dev_flash/vsh/module/msmw2.sprx" seems to rely on some cryptic + // shared memory behaviour that we don't emulate correctly + // This is a hack to avoid waiting for 1m40s every time we boot vsh + if (queue.key == 0x8005911000000012 && Emu.IsVsh()) { + sys_event.todo("sys_event_queue_receive(equeue_id=0x%x, *0x%x, " + "timeout=0x%llx) Bypassing timeout for msmw2.sprx", + equeue_id, dummy_event, timeout); + timeout = 1; + } + + if (queue.events.empty()) { + queue.sleep(ppu, timeout); + lv2_obj::emplace(queue.pq, &ppu); + return CELL_EBUSY; + } + + std::tie(ppu.gpr[4], ppu.gpr[5], ppu.gpr[6], ppu.gpr[7]) = + queue.events.front(); + queue.events.pop_front(); + return {}; + }); + + if (!queue) { + return CELL_ESRCH; + } + + if (queue.ret) { + if (queue.ret != CELL_EBUSY) { + return queue.ret; + } + } else { + return CELL_OK; + } + + // If cancelled, gpr[3] will be non-zero. Other registers must contain event + // data. + while (auto state = +ppu.state) { + if (state & cpu_flag::signal && + ppu.state.test_and_reset(cpu_flag::signal)) { + break; + } + + if (is_stopped(state)) { + std::lock_guard lock_rsx(queue->mutex); + + for (auto cpu = +queue->pq; cpu; cpu = cpu->next_cpu) { + if (cpu == &ppu) { + ppu.state += cpu_flag::again; + return {}; + } + } + + break; + } + + for (usz i = 0; cpu_flag::signal - ppu.state && i < 50; i++) { + busy_wait(500); + } + + if (ppu.state & cpu_flag::signal) { + continue; + } + + if (timeout) { + if (lv2_obj::wait_timeout(timeout, &ppu)) { + // Wait for rescheduling + if (ppu.check_state()) { + continue; + } + + ppu.state += cpu_flag::wait; + + if (!atomic_storage::load(queue->pq)) { + // Waiters queue is empty, so the thread must have been signaled + queue->mutex.lock_unlock(); + break; + } + + std::lock_guard lock(queue->mutex); + + if (!queue->unqueue(queue->pq, &ppu)) { + break; + } + + ppu.gpr[3] = CELL_ETIMEDOUT; + break; + } + } else { + ppu.state.wait(state); + } + } + + return not_an_error(ppu.gpr[3]); +} + +error_code sys_event_queue_drain(ppu_thread &ppu, u32 equeue_id) { + ppu.state += cpu_flag::wait; + + sys_event.trace("sys_event_queue_drain(equeue_id=0x%x)", equeue_id); + + const auto queue = idm::check( + equeue_id, [&](lv2_event_queue &queue) { + std::lock_guard lock(queue.mutex); + + queue.events.clear(); + }); + + if (!queue) { + return CELL_ESRCH; + } + + return CELL_OK; +} + +error_code sys_event_port_create(cpu_thread &cpu, vm::ptr eport_id, + s32 port_type, u64 name) { + cpu.state += cpu_flag::wait; + + sys_event.warning( + "sys_event_port_create(eport_id=*0x%x, port_type=%d, name=0x%llx)", + eport_id, port_type, name); + + if (port_type != SYS_EVENT_PORT_LOCAL && port_type != 3) { + sys_event.error("sys_event_port_create(): unknown port type (%d)", + port_type); + return CELL_EINVAL; + } + + if (const u32 id = idm::make(port_type, name)) { + cpu.check_state(); + *eport_id = id; + return CELL_OK; + } + + return CELL_EAGAIN; +} + +error_code sys_event_port_destroy(ppu_thread &ppu, u32 eport_id) { + ppu.state += cpu_flag::wait; + + sys_event.warning("sys_event_port_destroy(eport_id=0x%x)", eport_id); + + const auto port = idm::withdraw( + eport_id, [](lv2_event_port &port) -> CellError { + if (lv2_obj::check(port.queue)) { + return CELL_EISCONN; + } + + return {}; + }); + + if (!port) { + return CELL_ESRCH; + } + + if (port.ret) { + return port.ret; + } + + return CELL_OK; +} + +error_code sys_event_port_connect_local(cpu_thread &cpu, u32 eport_id, + u32 equeue_id) { + cpu.state += cpu_flag::wait; + + sys_event.warning( + "sys_event_port_connect_local(eport_id=0x%x, equeue_id=0x%x)", eport_id, + equeue_id); + + std::lock_guard lock(id_manager::g_mutex); + + const auto port = idm::check_unlocked(eport_id); + + if (!port || !idm::check_unlocked(equeue_id)) { + return CELL_ESRCH; + } + + if (port->type != SYS_EVENT_PORT_LOCAL) { + return CELL_EINVAL; + } + + if (lv2_obj::check(port->queue)) { + return CELL_EISCONN; + } + + port->queue = idm::get_unlocked(equeue_id); + + return CELL_OK; +} + +error_code sys_event_port_connect_ipc(ppu_thread &ppu, u32 eport_id, + u64 ipc_key) { + ppu.state += cpu_flag::wait; + + sys_event.warning("sys_event_port_connect_ipc(eport_id=0x%x, ipc_key=0x%x)", + eport_id, ipc_key); + + if (ipc_key == 0) { + return CELL_EINVAL; + } + + auto queue = lv2_event_queue::find(ipc_key); + + std::lock_guard lock(id_manager::g_mutex); + + const auto port = idm::check_unlocked(eport_id); + + if (!port || !queue) { + return CELL_ESRCH; + } + + if (port->type != SYS_EVENT_PORT_IPC) { + return CELL_EINVAL; + } + + if (lv2_obj::check(port->queue)) { + return CELL_EISCONN; + } + + port->queue = std::move(queue); + + return CELL_OK; +} + +error_code sys_event_port_disconnect(ppu_thread &ppu, u32 eport_id) { + ppu.state += cpu_flag::wait; + + sys_event.warning("sys_event_port_disconnect(eport_id=0x%x)", eport_id); + + std::lock_guard lock(id_manager::g_mutex); + + const auto port = idm::check_unlocked(eport_id); + + if (!port) { + return CELL_ESRCH; + } + + if (!lv2_obj::check(port->queue)) { + return CELL_ENOTCONN; + } + + if (port->is_busy) { + return CELL_EBUSY; + } + + port->queue.reset(); + + return CELL_OK; +} + +error_code sys_event_port_send(u32 eport_id, u64 data1, u64 data2, u64 data3) { + const auto cpu = cpu_thread::get_current(); + const auto ppu = cpu ? cpu->try_get() : nullptr; + + if (cpu) { + cpu->state += cpu_flag::wait; + } + + sys_event.trace("sys_event_port_send(eport_id=0x%x, data1=0x%llx, " + "data2=0x%llx, data3=0x%llx)", + eport_id, data1, data2, data3); + + bool notified_thread = false; + + const auto port = idm::check( + eport_id, + [&, notify = lv2_obj::notify_all_t()](lv2_event_port &port) -> CellError { + if (ppu && ppu->loaded_from_savestate) { + port.is_busy++; + notified_thread = true; + return {}; + } + + if (lv2_obj::check(port.queue)) { + const u64 source = + port.name ? port.name + : (u64{process_getpid() + 0u} << 32) | u64{eport_id}; + + return port.queue->send( + source, data1, data2, data3, ¬ified_thread, + ppu && port.queue->type == SYS_PPU_QUEUE ? &port : nullptr); + } + + return CELL_ENOTCONN; + }); + + if (!port) { + return CELL_ESRCH; + } + + if (ppu && notified_thread) { + // Wait to be requeued + if (ppu->test_stopped()) { + // Wait again on savestate load + ppu->state += cpu_flag::again; + } + + port->is_busy--; + return CELL_OK; + } + + if (port.ret) { + if (port.ret == CELL_EAGAIN) { + // Not really an error code exposed to games (thread has raised + // cpu_flag::again) + return not_an_error(CELL_EAGAIN); + } + + if (port.ret == CELL_EBUSY) { + return not_an_error(CELL_EBUSY); + } + + return port.ret; + } + + return CELL_OK; +} diff --git a/kernel/cellos/src/sys_event_flag.cpp b/kernel/cellos/src/sys_event_flag.cpp new file mode 100644 index 000000000..8fa85dc6a --- /dev/null +++ b/kernel/cellos/src/sys_event_flag.cpp @@ -0,0 +1,514 @@ +#include "stdafx.h" + +#include "sys_event_flag.h" + +#include "Emu/IdManager.h" + +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/Cell/PPUThread.h" + +#include "util/asm.hpp" + +LOG_CHANNEL(sys_event_flag); + +lv2_event_flag::lv2_event_flag(utils::serial &ar) + : protocol(ar), key(ar), type(ar), name(ar) { + ar(pattern); +} + +std::function lv2_event_flag::load(utils::serial &ar) { + return load_func(make_shared(exact_t(ar))); +} + +void lv2_event_flag::save(utils::serial &ar) { + ar(protocol, key, type, name, pattern); +} + +error_code sys_event_flag_create(ppu_thread &ppu, vm::ptr id, + vm::ptr attr, + u64 init) { + ppu.state += cpu_flag::wait; + + sys_event_flag.warning( + "sys_event_flag_create(id=*0x%x, attr=*0x%x, init=0x%llx)", id, attr, + init); + + if (!id || !attr) { + return CELL_EFAULT; + } + + const auto _attr = *attr; + + const u32 protocol = _attr.protocol; + + if (protocol != SYS_SYNC_FIFO && protocol != SYS_SYNC_PRIORITY) { + sys_event_flag.error("sys_event_flag_create(): unknown protocol (0x%x)", + protocol); + return CELL_EINVAL; + } + + const u32 type = _attr.type; + + if (type != SYS_SYNC_WAITER_SINGLE && type != SYS_SYNC_WAITER_MULTIPLE) { + sys_event_flag.error("sys_event_flag_create(): unknown type (0x%x)", type); + return CELL_EINVAL; + } + + const u64 ipc_key = lv2_obj::get_key(_attr); + + if (const auto error = lv2_obj::create( + _attr.pshared, ipc_key, _attr.flags, [&] { + return make_shared( + _attr.protocol, ipc_key, _attr.type, _attr.name_u64, init); + })) { + return error; + } + + ppu.check_state(); + *id = idm::last_id(); + return CELL_OK; +} + +error_code sys_event_flag_destroy(ppu_thread &ppu, u32 id) { + ppu.state += cpu_flag::wait; + + sys_event_flag.warning("sys_event_flag_destroy(id=0x%x)", id); + + const auto flag = idm::withdraw( + id, [&](lv2_event_flag &flag) -> CellError { + if (flag.sq) { + return CELL_EBUSY; + } + + lv2_obj::on_id_destroy(flag, flag.key); + return {}; + }); + + if (!flag) { + return CELL_ESRCH; + } + + if (flag.ret) { + return flag.ret; + } + + return CELL_OK; +} + +error_code sys_event_flag_wait(ppu_thread &ppu, u32 id, u64 bitptn, u32 mode, + vm::ptr result, u64 timeout) { + ppu.state += cpu_flag::wait; + + sys_event_flag.trace("sys_event_flag_wait(id=0x%x, bitptn=0x%llx, mode=0x%x, " + "result=*0x%x, timeout=0x%llx)", + id, bitptn, mode, result, timeout); + + // Fix function arguments for external access + ppu.gpr[3] = -1; + ppu.gpr[4] = bitptn; + ppu.gpr[5] = mode; + ppu.gpr[6] = 0; + + // Always set result + struct store_result { + vm::ptr ptr; + u64 val = 0; + + ~store_result() noexcept { + if (ptr) { + cpu_thread::get_current()->check_state(); + *ptr = val; + } + } + } store{result}; + + if (!lv2_event_flag::check_mode(mode)) { + sys_event_flag.error("sys_event_flag_wait(): unknown mode (0x%x)", mode); + return CELL_EINVAL; + } + + const auto flag = idm::get( + id, + [&, notify = lv2_obj::notify_all_t()](lv2_event_flag &flag) -> CellError { + if (flag.pattern + .fetch_op([&](u64 &pat) { + return lv2_event_flag::check_pattern(pat, bitptn, mode, + &ppu.gpr[6]); + }) + .second) { + // TODO: is it possible to return EPERM in this case? + return {}; + } + + lv2_obj::prepare_for_sleep(ppu); + + std::lock_guard lock(flag.mutex); + + if (flag.pattern + .fetch_op([&](u64 &pat) { + return lv2_event_flag::check_pattern(pat, bitptn, mode, + &ppu.gpr[6]); + }) + .second) { + return {}; + } + + if (flag.type == SYS_SYNC_WAITER_SINGLE && flag.sq) { + return CELL_EPERM; + } + + flag.sleep(ppu, timeout); + lv2_obj::emplace(flag.sq, &ppu); + return CELL_EBUSY; + }); + + if (!flag) { + return CELL_ESRCH; + } + + if (flag.ret) { + if (flag.ret != CELL_EBUSY) { + return flag.ret; + } + } else { + store.val = ppu.gpr[6]; + return CELL_OK; + } + + while (auto state = +ppu.state) { + if (state & cpu_flag::signal && + ppu.state.test_and_reset(cpu_flag::signal)) { + break; + } + + if (is_stopped(state)) { + std::lock_guard lock(flag->mutex); + + for (auto cpu = +flag->sq; cpu; cpu = cpu->next_cpu) { + if (cpu == &ppu) { + ppu.state += cpu_flag::again; + return {}; + } + } + + break; + } + + for (usz i = 0; cpu_flag::signal - ppu.state && i < 50; i++) { + busy_wait(500); + } + + if (ppu.state & cpu_flag::signal) { + continue; + } + + if (timeout) { + if (lv2_obj::wait_timeout(timeout, &ppu)) { + // Wait for rescheduling + if (ppu.check_state()) { + continue; + } + + ppu.state += cpu_flag::wait; + + if (!atomic_storage::load(flag->sq)) { + // Waiters queue is empty, so the thread must have been signaled + flag->mutex.lock_unlock(); + break; + } + + std::lock_guard lock(flag->mutex); + + if (!flag->unqueue(flag->sq, &ppu)) { + break; + } + + ppu.gpr[3] = CELL_ETIMEDOUT; + ppu.gpr[6] = flag->pattern; + break; + } + } else { + ppu.state.wait(state); + } + } + + store.val = ppu.gpr[6]; + return not_an_error(ppu.gpr[3]); +} + +error_code sys_event_flag_trywait(ppu_thread &ppu, u32 id, u64 bitptn, u32 mode, + vm::ptr result) { + ppu.state += cpu_flag::wait; + + sys_event_flag.trace( + "sys_event_flag_trywait(id=0x%x, bitptn=0x%llx, mode=0x%x, result=*0x%x)", + id, bitptn, mode, result); + + // Always set result + struct store_result { + vm::ptr ptr; + u64 val = 0; + + ~store_result() noexcept { + if (ptr) { + cpu_thread::get_current()->check_state(); + *ptr = val; + } + } + } store{result}; + + if (!lv2_event_flag::check_mode(mode)) { + sys_event_flag.error("sys_event_flag_trywait(): unknown mode (0x%x)", mode); + return CELL_EINVAL; + } + + u64 pattern{}; + + const auto flag = + idm::check(id, [&](lv2_event_flag &flag) { + return flag.pattern + .fetch_op([&](u64 &pat) { + return lv2_event_flag::check_pattern(pat, bitptn, mode, &pattern); + }) + .second; + }); + + if (!flag) { + return CELL_ESRCH; + } + + if (!flag.ret) { + return not_an_error(CELL_EBUSY); + } + + store.val = pattern; + return CELL_OK; +} + +error_code sys_event_flag_set(cpu_thread &cpu, u32 id, u64 bitptn) { + cpu.state += cpu_flag::wait; + + // Warning: may be called from SPU thread. + sys_event_flag.trace("sys_event_flag_set(id=0x%x, bitptn=0x%llx)", id, + bitptn); + + const auto flag = idm::get_unlocked(id); + + if (!flag) { + return CELL_ESRCH; + } + + if ((flag->pattern & bitptn) == bitptn) { + return CELL_OK; + } + + if (lv2_obj::notify_all_t notify; true) { + std::lock_guard lock(flag->mutex); + + for (auto ppu = +flag->sq; ppu; ppu = ppu->next_cpu) { + if (ppu->state & cpu_flag::again) { + cpu.state += cpu_flag::again; + + // Fake error for abort + return not_an_error(CELL_EAGAIN); + } + } + + u32 count = 0; + + // Process all waiters in single atomic op + for (u64 pattern = flag->pattern, to_write = pattern, dependant_mask = 0;; + to_write = pattern, dependant_mask = 0) { + count = 0; + to_write |= bitptn; + dependant_mask = 0; + + for (auto ppu = +flag->sq; ppu; ppu = ppu->next_cpu) { + ppu->gpr[7] = 0; + } + + auto first = +flag->sq; + + auto get_next = [&]() -> ppu_thread * { + s32 prio = smax; + ppu_thread *it{}; + + for (auto ppu = first; ppu; ppu = ppu->next_cpu) { + if (!ppu->gpr[7] && (flag->protocol != SYS_SYNC_PRIORITY || + ppu->prio.load().prio <= prio)) { + it = ppu; + prio = ppu->prio.load().prio; + } + } + + if (it) { + // Mark it so it won't reappear + it->gpr[7] = 1; + } + + return it; + }; + + while (auto it = get_next()) { + auto &ppu = *it; + + const u64 pattern = ppu.gpr[4]; + const u64 mode = ppu.gpr[5]; + + // If it's OR mode, set bits must have waken up the thread therefore no + // dependency on old value + const u64 dependant_mask_or = + ((mode & 0xf) == SYS_EVENT_FLAG_WAIT_OR || + (bitptn & pattern & to_write) == pattern + ? 0 + : pattern); + + if (lv2_event_flag::check_pattern(to_write, pattern, mode, + &ppu.gpr[6])) { + dependant_mask |= dependant_mask_or; + ppu.gpr[3] = CELL_OK; + count++; + + if (!to_write) { + break; + } + } else { + ppu.gpr[3] = -1; + } + } + + dependant_mask &= ~bitptn; + + auto [new_val, ok] = flag->pattern.fetch_op([&](u64 &x) { + if ((x ^ pattern) & dependant_mask) { + return false; + } + + x |= bitptn; + + // Clear the bit-wise difference + x &= ~((pattern | bitptn) & ~to_write); + return true; + }); + + if (ok) { + break; + } + + pattern = new_val; + } + + if (!count) { + return CELL_OK; + } + + // Remove waiters + for (auto next_cpu = &flag->sq; *next_cpu;) { + auto &ppu = **next_cpu; + + if (ppu.gpr[3] == CELL_OK) { + atomic_storage::release(*next_cpu, ppu.next_cpu); + ppu.next_cpu = nullptr; + flag->append(&ppu); + continue; + } + + next_cpu = &ppu.next_cpu; + }; + + lv2_obj::awake_all(); + } + + return CELL_OK; +} + +error_code sys_event_flag_clear(ppu_thread &ppu, u32 id, u64 bitptn) { + ppu.state += cpu_flag::wait; + + sys_event_flag.trace("sys_event_flag_clear(id=0x%x, bitptn=0x%llx)", id, + bitptn); + + const auto flag = idm::check( + id, [&](lv2_event_flag &flag) { flag.pattern &= bitptn; }); + + if (!flag) { + return CELL_ESRCH; + } + + return CELL_OK; +} + +error_code sys_event_flag_cancel(ppu_thread &ppu, u32 id, vm::ptr num) { + ppu.state += cpu_flag::wait; + + sys_event_flag.trace("sys_event_flag_cancel(id=0x%x, num=*0x%x)", id, num); + + if (num) + *num = 0; + + const auto flag = idm::get_unlocked(id); + + if (!flag) { + return CELL_ESRCH; + } + + u32 value = 0; + { + lv2_obj::notify_all_t notify; + + std::lock_guard lock(flag->mutex); + + for (auto cpu = +flag->sq; cpu; cpu = cpu->next_cpu) { + if (cpu->state & cpu_flag::again) { + ppu.state += cpu_flag::again; + return {}; + } + } + + // Get current pattern + const u64 pattern = flag->pattern; + + // Signal all threads to return CELL_ECANCELED (protocol does not matter) + while (auto ppu = flag->schedule(flag->sq, SYS_SYNC_FIFO)) { + ppu->gpr[3] = CELL_ECANCELED; + ppu->gpr[6] = pattern; + + value++; + flag->append(ppu); + } + + if (value) { + lv2_obj::awake_all(); + } + } + + static_cast(ppu.test_stopped()); + + if (num) + *num = value; + return CELL_OK; +} + +error_code sys_event_flag_get(ppu_thread &ppu, u32 id, vm::ptr flags) { + ppu.state += cpu_flag::wait; + + sys_event_flag.trace("sys_event_flag_get(id=0x%x, flags=*0x%x)", id, flags); + + const auto flag = idm::check( + id, [](lv2_event_flag &flag) { return +flag.pattern; }); + + ppu.check_state(); + + if (!flag) { + if (flags) + *flags = 0; + return CELL_ESRCH; + } + + if (!flags) { + return CELL_EFAULT; + } + + *flags = flag.ret; + return CELL_OK; +} diff --git a/kernel/cellos/src/sys_fs.cpp b/kernel/cellos/src/sys_fs.cpp new file mode 100644 index 000000000..1eff5ddc3 --- /dev/null +++ b/kernel/cellos/src/sys_fs.cpp @@ -0,0 +1,3248 @@ +#include "stdafx.h" + +#include "sys_fs.h" +#include "sys_memory.h" +#include "sys_sync.h" +#include "util/asm.hpp" + +#include "Crypto/unedat.h" +#include "Emu/Cell/PPUThread.h" +#include "Emu/IdManager.h" +#include "Emu/System.h" +#include "Emu/VFS.h" +#include "Emu/system_config.h" +#include "Emu/system_utils.hpp" +#include "Emu/vfs_config.h" +#include "cellos/sys_process.h" + +#include +#include +#include + +LOG_CHANNEL(sys_fs); + +// clang-format off +lv2_fs_mount_point g_mp_sys_dev_usb{"/dev_usb", "CELL_FS_FAT", "CELL_FS_IOS:USB_MASS_STORAGE", 512, 0x100, 4096, lv2_mp_flag::no_uid_gid}; +lv2_fs_mount_point g_mp_sys_dev_dvd{"/dev_ps2disc", "CELL_FS_ISO9660", "CELL_FS_IOS:PATA1_BDVD_DRIVE", 2048, 0x100, 32768, lv2_mp_flag::read_only + lv2_mp_flag::no_uid_gid, &g_mp_sys_dev_usb}; +lv2_fs_mount_point g_mp_sys_dev_bdvd{"/dev_bdvd", "CELL_FS_ISO9660", "CELL_FS_IOS:PATA0_BDVD_DRIVE", 2048, 0x4D955, 2048, lv2_mp_flag::read_only + lv2_mp_flag::no_uid_gid, &g_mp_sys_dev_dvd}; +lv2_fs_mount_point g_mp_sys_dev_hdd1{"/dev_hdd1", "CELL_FS_FAT", "CELL_FS_UTILITY:HDD1", 512, 0x3FFFF8, 32768, lv2_mp_flag::no_uid_gid + lv2_mp_flag::cache, &g_mp_sys_dev_bdvd}; +lv2_fs_mount_point g_mp_sys_dev_hdd0{"/dev_hdd0", "CELL_FS_UFS", "CELL_FS_UTILITY:HDD0", 512, 0x24FAEA98, 4096, {}, &g_mp_sys_dev_hdd1}; +lv2_fs_mount_point g_mp_sys_dev_flash3{"/dev_flash3", "CELL_FS_FAT", "CELL_FS_IOS:BUILTIN_FLSH3", 512, 0x400, 8192, lv2_mp_flag::no_uid_gid, &g_mp_sys_dev_hdd0}; // TODO confirm +lv2_fs_mount_point g_mp_sys_dev_flash2{"/dev_flash2", "CELL_FS_FAT", "CELL_FS_IOS:BUILTIN_FLSH2", 512, 0x8000, 8192, lv2_mp_flag::no_uid_gid, &g_mp_sys_dev_flash3}; // TODO confirm +lv2_fs_mount_point g_mp_sys_dev_flash{"/dev_flash", "CELL_FS_FAT", "CELL_FS_IOS:BUILTIN_FLSH1", 512, 0x63E00, 8192, lv2_mp_flag::no_uid_gid, &g_mp_sys_dev_flash2}; +lv2_fs_mount_point g_mp_sys_host_root{"/host_root", "CELL_FS_DUMMYFS", "CELL_FS_DUMMY:/", 512, 0x100, 512, lv2_mp_flag::strict_get_block_size + lv2_mp_flag::no_uid_gid, &g_mp_sys_dev_flash}; +lv2_fs_mount_point g_mp_sys_app_home{"/app_home", "CELL_FS_DUMMYFS", "CELL_FS_DUMMY:", 512, 0x100, 512, lv2_mp_flag::strict_get_block_size + lv2_mp_flag::no_uid_gid, &g_mp_sys_host_root}; +lv2_fs_mount_point g_mp_sys_dev_root{"/", "CELL_FS_ADMINFS", "CELL_FS_ADMINFS:", 512, 0x100, 512, lv2_mp_flag::read_only + lv2_mp_flag::strict_get_block_size + lv2_mp_flag::no_uid_gid, &g_mp_sys_app_home}; +lv2_fs_mount_point g_mp_sys_no_device{}; +lv2_fs_mount_info g_mi_sys_not_found{}; // wrapper for &g_mp_sys_no_device +// clang-format on + +template <> +void fmt_class_string::format(std::string &out, u64 arg) { + format_enum(out, arg, [](lv2_file_type type) { + switch (type) { + case lv2_file_type::regular: + return "Regular file"; + case lv2_file_type::sdata: + return "SDATA"; + case lv2_file_type::edata: + return "EDATA"; + } + + return unknown; + }); +} + +template <> void fmt_class_string::format(std::string &out, u64 arg) { + const auto &file = get_object(arg); + + auto get_size = [](u64 size) -> std::string { + if (size == umax) { + return "N/A"; + } + + std::string size_str; + switch (std::bit_width(size) / 10 * 10) { + case 0: + fmt::append(size_str, "%u", size); + break; + case 10: + fmt::append(size_str, "%gKB", size / 1024.); + break; + case 20: + fmt::append(size_str, "%gMB", size / (1024. * 1024)); + break; + + default: + case 30: + fmt::append(size_str, "%gGB", size / (1024. * 1024 * 1024)); + break; + } + + return size_str; + }; + + const usz pos = file.file ? file.file.pos() : umax; + const usz size = file.file ? file.file.size() : umax; + + fmt::append( + out, u8"%s, '%s', Mode: 0x%x, Flags: 0x%x, Pos/Size: %s/%s (0x%x/0x%x)", + file.type, file.name.data(), file.mode, file.flags, get_size(pos), + get_size(size), pos, size); +} + +template <> void fmt_class_string::format(std::string &out, u64 arg) { + const auto &dir = get_object(arg); + + fmt::append(out, u8"Directory, '%s', Entries: %u/%u", dir.name.data(), + std::min(dir.pos, dir.entries.size()), dir.entries.size()); +} + +bool has_fs_write_rights(std::string_view vpath) { + // VSH has access to everything + if (g_ps3_process_info.has_root_perm()) + return true; + + const auto norm_vpath = lv2_fs_object::get_normalized_path(vpath); + const auto parent_dir = fs::get_parent_dir_view(norm_vpath); + + // This is not exhaustive, PS3 has a unix filesystem with rights for each + // directory and files This is mostly meant to protect against games doing + // insane things(ie NPUB30003 => NPUB30008) + if (parent_dir == "/dev_hdd0" || parent_dir == "/dev_hdd0/game") + return false; + + return true; +} + +bool verify_mself(const fs::file &mself_file) { + FsMselfHeader mself_header; + if (!mself_file.read(mself_header)) { + sys_fs.error("verify_mself: Didn't read expected bytes for header."); + return false; + } + + if (mself_header.m_magic != 0x4D534600u) { + sys_fs.error("verify_mself: Header magic is incorrect."); + return false; + } + + if (mself_header.m_format_version != 1u) { + sys_fs.error("verify_mself: Unexpected header format version."); + return false; + } + + // sanity check + if (mself_header.m_entry_size != sizeof(FsMselfEntry)) { + sys_fs.error("verify_mself: Unexpected header entry size."); + return false; + } + + mself_file.seek(0); + + return true; +} + +lv2_fs_mount_info_map::lv2_fs_mount_info_map() { + for (auto mp = &g_mp_sys_dev_root; mp; + mp = mp->next) // Scan and keep track of pre-mounted devices + { + if (mp == &g_mp_sys_dev_usb) { + for (int i = 0; i < 8; i++) { + if (!vfs::get(fmt::format("%s%03d", mp->root, i)).empty()) { + add(fmt::format("%s%03d", mp->root, i), mp, + fmt::format("%s%03d", mp->device, i), mp->file_system, false); + } + } + } else if (mp == &g_mp_sys_dev_root || !vfs::get(mp->root).empty()) { + add(std::string(mp->root), mp, mp->device, mp->file_system, + mp == &g_mp_sys_dev_flash); // /dev_flash is mounted in read only mode + // initially + } + } +} + +lv2_fs_mount_info_map::~lv2_fs_mount_info_map() { + for (const auto &[path, info] : map) + vfs_unmount( + path, + false); // Do not remove the value from the map we are iterating over. +} + +bool lv2_fs_mount_info_map::remove(std::string_view path) { + if (const auto iterator = map.find(path); iterator != map.end()) { + map.erase(iterator); + return true; + } + return false; +} + +const lv2_fs_mount_info & +lv2_fs_mount_info_map::lookup(std::string_view path, bool no_cell_fs_path, + std::string *mount_path) const { + if (path.starts_with("/"sv)) { + constexpr std::string_view cell_fs_path = "CELL_FS_PATH:"sv; + const std::string normalized_path = + lv2_fs_object::get_normalized_path(path); + std::string_view parent_dir; + u32 parent_level = 0; + + do { + parent_dir = fs::get_parent_dir_view(normalized_path, parent_level++); + if (const auto iterator = map.find(parent_dir); iterator != map.end()) { + if (iterator->second == &g_mp_sys_dev_root && parent_level > 1) + break; + if (no_cell_fs_path && + iterator->second.device.starts_with(cell_fs_path)) + return lookup( + iterator->second.device.substr(cell_fs_path.size()), + no_cell_fs_path, + mount_path); // Recursively look up the parent mount info + if (mount_path) + *mount_path = iterator->first; + return iterator->second; + } + } while (parent_dir.length() > + 1); // Exit the loop when parent_dir == "/" or empty + } + + return g_mi_sys_not_found; +} + +u64 lv2_fs_mount_info_map::get_all(CellFsMountInfo *info, u64 len) const { + if (!info) + return map.size(); + + u64 count = 0; + + for (const auto &[path, mount_info] : map) { + if (count >= len) + break; + + strcpy_trunc(info[count].mount_path, path); + strcpy_trunc(info[count].filesystem, mount_info.file_system); + strcpy_trunc(info[count].dev_name, mount_info.device); + if (mount_info.read_only) + info[count].unk[4] |= 0x10000000; + + count++; + } + + return count; +} + +bool lv2_fs_mount_info_map::is_device_mounted( + std::string_view device_name) const { + return std::any_of(map.begin(), map.end(), + [&](const decltype(map)::value_type &info) { + return info.second.device == device_name; + }); +} + +bool lv2_fs_mount_info_map::vfs_unmount(std::string_view vpath, + bool remove_from_map) { + const std::string local_path = vfs::get(vpath); + + if (local_path.empty()) + return false; + + if (fs::is_file(local_path)) { + if (fs::remove_file(local_path)) { + sys_fs.notice("Removed simplefs file \"%s\"", local_path); + } else { + sys_fs.error("Failed to remove simplefs file \"%s\"", local_path); + } + } + + const bool result = vfs::unmount(vpath); + + if (result && remove_from_map) + g_fxo->get().remove(vpath); + + return result; +} + +std::string lv2_fs_object::get_normalized_path(std::string_view path) { + std::string normalized_path = + std::filesystem::path(path).lexically_normal().string(); + +#ifdef _WIN32 + std::replace(normalized_path.begin(), normalized_path.end(), '\\', '/'); +#endif + + if (normalized_path.ends_with('/')) + normalized_path.pop_back(); + + return normalized_path.empty() ? "/" : normalized_path; +} + +std::string lv2_fs_object::get_device_root(std::string_view filename) { + std::string path = + get_normalized_path(filename); // Prevent getting fooled by ".." trick + // such as "/dev_usb000/../dev_flash" + + if (const auto first = path.find_first_not_of("/"sv); first != umax) { + if (const auto pos = path.substr(first).find_first_of("/"sv); pos != umax) + path = path.substr(0, first + pos); + path = path.substr(std::max>( + 0, first - 1)); // Remove duplicate leading '/' while keeping only one + } else { + path = path.substr(0, 1); + } + + return path; +} + +lv2_fs_mount_point *lv2_fs_object::get_mp(std::string_view filename, + std::string *vfs_path) { + constexpr std::string_view cell_fs_path = "CELL_FS_PATH:"sv; + const bool is_cell_fs_path = filename.starts_with(cell_fs_path); + + if (is_cell_fs_path) + filename.remove_prefix(cell_fs_path.size()); + + const bool is_path = filename.starts_with("/"sv); + std::string mp_name = + is_path ? get_device_root(filename) : std::string(filename); + + const auto check_mp = [&]() { + for (auto mp = &g_mp_sys_dev_root; mp; mp = mp->next) { + const auto &device_alias_check = + !is_path && + ((mp == &g_mp_sys_dev_hdd0 && + mp_name == "CELL_FS_IOS:PATA0_HDD_DRIVE"sv) || + (mp == &g_mp_sys_dev_hdd1 && + mp_name == "CELL_FS_IOS:PATA1_HDD_DRIVE"sv) || + (mp == &g_mp_sys_dev_flash2 && + mp_name == "CELL_FS_IOS:BUILTIN_FLASH"sv)); // TODO confirm + + if (mp == &g_mp_sys_dev_usb) { + if (mp_name.starts_with(is_path ? mp->root : mp->device)) { + if (!is_path) + mp_name = fmt::format("%s%s", mp->root, + mp_name.substr(mp->device.size())); + return mp; + } + } else if ((is_path ? mp->root : mp->device) == mp_name || + device_alias_check) { + if (!is_path) + mp_name = mp->root; + return mp; + } + } + return &g_mp_sys_no_device; // Default fallback + }; + + const auto result = check_mp(); + + if (vfs_path) { + if (is_cell_fs_path) + *vfs_path = vfs::get(filename); + else if (result == &g_mp_sys_dev_hdd0) + *vfs_path = + g_cfg_vfs.get(g_cfg_vfs.dev_hdd0, rpcs3::utils::get_emu_dir()); + else if (result == &g_mp_sys_dev_hdd1) + *vfs_path = + g_cfg_vfs.get(g_cfg_vfs.dev_hdd1, rpcs3::utils::get_emu_dir()); + else if (result == &g_mp_sys_dev_usb) + *vfs_path = g_cfg_vfs + .get_device(g_cfg_vfs.dev_usb, mp_name, + rpcs3::utils::get_emu_dir()) + .path; + else if (result == &g_mp_sys_dev_bdvd) + *vfs_path = + g_cfg_vfs.get(g_cfg_vfs.dev_bdvd, rpcs3::utils::get_emu_dir()); + else if (result == &g_mp_sys_dev_dvd) + *vfs_path = g_cfg_vfs.get( + g_cfg_vfs.dev_bdvd, rpcs3::utils::get_emu_dir()); // For compatibility + else if (result == &g_mp_sys_app_home) + *vfs_path = + g_cfg_vfs.get(g_cfg_vfs.app_home, rpcs3::utils::get_emu_dir()); + else if (result == &g_mp_sys_host_root && g_cfg.vfs.host_root) + *vfs_path = "/"; + else if (result == &g_mp_sys_dev_flash) + *vfs_path = g_cfg_vfs.get_dev_flash(); + else if (result == &g_mp_sys_dev_flash2) + *vfs_path = g_cfg_vfs.get_dev_flash2(); + else if (result == &g_mp_sys_dev_flash3) + *vfs_path = g_cfg_vfs.get_dev_flash3(); + else + *vfs_path = {}; + + if (is_path && !is_cell_fs_path && !vfs_path->empty()) + vfs_path->append(filename.substr(mp_name.size())); + } + + return result; +} + +lv2_fs_object::lv2_fs_object(std::string_view filename) + : name(get_name(filename)), + mp(g_fxo->get().lookup(name.data())) {} + +lv2_fs_object::lv2_fs_object(utils::serial &ar, bool) + : name(ar), mp(g_fxo->get().lookup(name.data())) {} + +u64 lv2_file::op_read(const fs::file &file, vm::ptr buf, u64 size, + u64 opt_pos) { + if (u64 region = buf.addr() >> 28, + region_end = (buf.addr() & 0xfff'ffff) + (size & 0xfff'ffff); + region == region_end && ((region >> 28) == 0 || region >= 0xC)) { + // Optimize reads from safe memory + return (opt_pos == umax ? file.read(buf.get_ptr(), size) + : file.read_at(opt_pos, buf.get_ptr(), size)); + } + + // Copy data from intermediate buffer (avoid passing vm pointer to a native + // API) + std::vector local_buf(std::min(size, 65536)); + + u64 result = 0; + + while (result < size) { + const u64 block = std::min(size - result, local_buf.size()); + const u64 nread = (opt_pos == umax ? file.read(local_buf.data(), block) + : file.read_at(opt_pos + result, + local_buf.data(), block)); + + std::memcpy(static_cast(buf.get_ptr()) + result, local_buf.data(), + nread); + result += nread; + + if (nread < block) { + break; + } + } + + return result; +} + +u64 lv2_file::op_write(const fs::file &file, vm::cptr buf, u64 size) { + // Copy data to intermediate buffer (avoid passing vm pointer to a native API) + std::vector local_buf(std::min(size, 65536)); + + u64 result = 0; + + while (result < size) { + const u64 block = std::min(size - result, local_buf.size()); + std::memcpy(local_buf.data(), + static_cast(buf.get_ptr()) + result, block); + const u64 nwrite = file.write(+local_buf.data(), block); + result += nwrite; + + if (nwrite < block) { + break; + } + } + + return result; +} + +lv2_file::lv2_file(utils::serial &ar) + : lv2_fs_object(ar, false), mode(ar), flags(ar), type(ar) { + [[maybe_unused]] const s32 version = GET_SERIALIZATION_VERSION(lv2_fs); + + ar(lock); + + be_t arg = 0; + u64 size = 0; + + switch (type) { + case lv2_file_type::regular: + break; + case lv2_file_type::sdata: + arg = 0x18000000010, size = 8; + break; // TODO: Fix + case lv2_file_type::edata: + arg = 0x2, size = 8; + break; + } + + const std::string retrieve_real = ar.pop(); + + if (type == lv2_file_type::edata && version >= 2) { + ar(g_fxo->get().one_time_key); + } + + open_result_t res = lv2_file::open(retrieve_real, flags & CELL_FS_O_ACCMODE, + mode, size ? &arg : nullptr, size); + file = std::move(res.file); + real_path = std::move(res.real_path); + + g_fxo->get().npdrm_fds.raw() += + type != lv2_file_type::regular; + g_fxo->get().one_time_key = {}; + + if (ar.pop()) // see lv2_file::save in_mem + { + const fs::stat_t stat = ar; + + std::vector buf(stat.size); + ar(std::span(buf.data(), buf.size())); + + file = fs::make_stream>(std::move(buf), stat); + } + + if (!file) { + sys_fs.error("Failed to load \'%s\' file for savestates (res=%s, " + "vpath=\'%s\', real-path=\'%s\', type=%s, flags=0x%x)", + name.data(), res.error, retrieve_real, real_path, type, flags); + ar.pos += sizeof(u64); + ensure(!!g_cfg.savestate.state_inspection_mode); + return; + } else { + sys_fs.success("Loaded file descriptor \'%s\' file for savestates " + "(vpath=\'%s\', type=%s, flags=0x%x, id=%d)", + name.data(), retrieve_real, type, flags, idm::last_id()); + } + + file.seek(ar); +} + +void lv2_file::save(utils::serial &ar) { + USING_SERIALIZATION_VERSION(lv2_fs); + ar(name, mode, flags, type, lock, + ensure(vfs::retrieve(real_path), FN(!x.empty()))); + + if (type == lv2_file_type::edata) { + auto file_ptr = file.release(); + ar(static_cast(file_ptr.get())->get_key()); + file.reset(std::move(file_ptr)); + } + + if (!mp.read_only && flags & CELL_FS_O_ACCMODE) { + // Ensure accurate timestamps and content on disk + file.sync(); + } + + // UNIX allows deletion of files while descriptors are still opened + // descriptors shall keep the data in memory in this case + const bool in_mem = [&]() { + if (mp.read_only) { + return false; + } + + fs::file test{real_path}; + + if (!test) { + if (fs::is_file(real_path + ".66600")) { + // May be a split-files descriptor, don't even bother + return false; + } + + return true; + } + + fs::file_id test_s = test.get_id(); + fs::file_id file_s = file.get_id(); + + return !test_s.is_coherent_with(file_s); + }(); + + ar(in_mem); + + if (in_mem) { + fs::stat_t stats = file.get_stat(); + + sys_fs.error("Saving \'%s\' LV2 file descriptor in memory! (exists=%s, " + "type=%s, flags=0x%x, size=0x%x)", + name.data(), fs::is_file(real_path), type, flags, stats.size); + + const usz patch_stats_pos = ar.seek_end(); + + ar(stats); + + const usz old_end = ar.pad_from_end(stats.size); + + if (usz read_size = file.read_at(0, &ar.data[old_end], stats.size); + read_size != stats.size) { + ensure(read_size < stats.size); + sys_fs.error("Read less than expected! (new-size=0x%x)", read_size); + stats.size = read_size; + ar.data.resize(old_end + stats.size); + write_to_ptr(&ar.data[patch_stats_pos], stats); + } + } + + ar(file.pos()); +} + +lv2_dir::lv2_dir(utils::serial &ar) + : lv2_fs_object(ar, false), entries([&] { + std::vector entries; + + u64 size = 0; + ar.deserialize_vle(size); + entries.resize(size); + + for (auto &entry : entries) { + ar(entry.name, static_cast(entry)); + } + + return entries; + }()), + pos(ar) {} + +void lv2_dir::save(utils::serial &ar) { + USING_SERIALIZATION_VERSION(lv2_fs); + + ar(name); + + ar.serialize_vle(entries.size()); + + for (auto &entry : entries) { + ar(entry.name, static_cast(entry)); + } + + ar(pos); +} + +loaded_npdrm_keys::loaded_npdrm_keys(utils::serial &ar) { save(ar); } + +void loaded_npdrm_keys::save(utils::serial &ar) { + ar(dec_keys_pos); + ar(std::span(dec_keys, std::min(std::size(dec_keys), dec_keys_pos))); +} + +struct lv2_file::file_view : fs::file_base { + const shared_ptr m_file; + const u64 m_off; + u64 m_pos; + + explicit file_view(const shared_ptr &_file, u64 offset) + : m_file(_file), m_off(offset), m_pos(0) {} + + ~file_view() override {} + + fs::stat_t get_stat() override { + fs::stat_t stat = m_file->file.get_stat(); + + // TODO: Check this on realhw + // stat.size = utils::sub_saturate(stat.size, m_off); + + stat.is_writable = false; + return stat; + } + + bool trunc(u64) override { return false; } + + u64 read(void *buffer, u64 size) override { + const u64 result = file_view::read_at(m_pos, buffer, size); + + m_pos += result; + return result; + } + + u64 read_at(u64 offset, void *buffer, u64 size) override { + return m_file->file.read_at(m_off + offset, buffer, size); + } + + u64 write(const void *, u64) override { return 0; } + + u64 seek(s64 offset, fs::seek_mode whence) override { + const s64 new_pos = whence == fs::seek_set ? offset + : whence == fs::seek_cur ? offset + m_pos + : whence == fs::seek_end ? offset + size() + : -1; + + if (new_pos < 0) { + fs::g_tls_error = fs::error::inval; + return -1; + } + + m_pos = new_pos; + return m_pos; + } + + u64 size() override { + return utils::sub_saturate(m_file->file.size(), m_off); + } + + fs::file_id get_id() override { + fs::file_id id = m_file->file.get_id(); + + be_t off = m_off; + const auto ptr = reinterpret_cast(&off); + + id.data.insert(id.data.end(), ptr, ptr + sizeof(off)); + id.type.insert(0, "lv2_file::file_view: "sv); + return id; + } +}; + +fs::file lv2_file::make_view(const shared_ptr &_file, u64 offset) { + fs::file result; + result.reset(std::make_unique(_file, offset)); + return result; +} + +std::pair translate_to_str(vm::cptr ptr, + bool is_path = true) { + constexpr usz max_length = CELL_FS_MAX_FS_PATH_LENGTH + 1; + + std::string path; + + if (!vm::read_string(ptr.addr(), max_length, path, true)) { + // Null character lookup has ended whilst pointing at invalid memory + return {CELL_EFAULT, std::move(path)}; + } + + if (path.size() == max_length) { + return {CELL_ENAMETOOLONG, {}}; + } + + if (is_path && !path.starts_with("/"sv)) { + return {CELL_ENOENT, std::move(path)}; + } + + return {{}, std::move(path)}; +} + +error_code sys_fs_test(ppu_thread &, u32 arg1, u32 arg2, vm::ptr arg3, + u32 arg4, vm::ptr buf, u32 buf_size) { + sys_fs.trace("sys_fs_test(arg1=0x%x, arg2=0x%x, arg3=*0x%x, arg4=0x%x, " + "buf=*0x%x, buf_size=0x%x)", + arg1, arg2, arg3, arg4, buf, buf_size); + + if (arg1 != 6 || arg2 != 0 || arg4 != sizeof(u32)) { + sys_fs.todo("sys_fs_test: unknown arguments (arg1=0x%x, arg2=0x%x, " + "arg3=*0x%x, arg4=0x%x)", + arg1, arg2, arg3, arg4); + } + + if (!arg3) { + return CELL_EFAULT; + } + + const auto file = idm::get_unlocked(*arg3); + + if (!file) { + return CELL_EBADF; + } + + for (u32 i = 0; i < buf_size; i++) { + if (!(buf[i] = file->name[i])) { + return CELL_OK; + } + } + + buf[buf_size - 1] = 0; + return CELL_OK; +} + +lv2_file::open_raw_result_t lv2_file::open_raw(const std::string &local_path, + s32 flags, s32 /*mode*/, + lv2_file_type type, + const lv2_fs_mount_info &mp) { + // TODO: other checks for path + + if (fs::is_dir(local_path)) { + return {CELL_EISDIR}; + } + + bs_t open_mode{}; + + switch (flags & CELL_FS_O_ACCMODE) { + case CELL_FS_O_RDONLY: + open_mode += fs::read; + break; + case CELL_FS_O_WRONLY: + open_mode += fs::write; + break; + case CELL_FS_O_RDWR: + open_mode += fs::read + fs::write; + break; + default: + break; + } + + if (mp.read_only) { + if ((flags & CELL_FS_O_ACCMODE) != CELL_FS_O_RDONLY && + fs::is_file(local_path)) { + return {CELL_EPERM}; + } + } + + if (flags & CELL_FS_O_CREAT) { + open_mode += fs::create; + + if (flags & CELL_FS_O_EXCL) { + open_mode += fs::excl; + } + } + + if (flags & CELL_FS_O_TRUNC) { + open_mode += fs::trunc; + } + + if (flags & CELL_FS_O_MSELF) { + open_mode = fs::read; + // mself can be mself or mself | rdonly + if (flags & ~(CELL_FS_O_MSELF | CELL_FS_O_RDONLY)) { + open_mode = {}; + } + } + + if (flags & CELL_FS_O_UNK) { + sys_fs.warning( + "lv2_file::open() called with CELL_FS_O_UNK flag enabled. FLAGS: %#o", + flags); + } + + if (mp.read_only) { + // Deactivate mutating flags on read-only FS + open_mode = fs::read; + } + + // Tests have shown that invalid combinations get resolved internally (without + // exceptions), but that would complicate code with minimal accuracy gains. + // For example, no games are known to try and call TRUNCATE | APPEND | RW, or + // APPEND | READ, which currently would cause an exception. + if (flags & + ~(CELL_FS_O_UNK | CELL_FS_O_ACCMODE | CELL_FS_O_CREAT | CELL_FS_O_TRUNC | + CELL_FS_O_APPEND | CELL_FS_O_EXCL | CELL_FS_O_MSELF)) { + open_mode = {}; // error + } + + if ((flags & CELL_FS_O_ACCMODE) == CELL_FS_O_ACCMODE) { + open_mode = {}; // error + } + + if (!open_mode) { + fmt::throw_exception( + "lv2_file::open_raw(): Invalid or unimplemented flags: %#o", flags); + } + + std::lock_guard lock(mp->mutex); + + fs::file file(local_path, open_mode); + + if (!file && open_mode == fs::read && fs::g_tls_error == fs::error::noent) { + // Try to gather split file (TODO) + std::vector fragments; + + for (u32 i = 66600; i <= 66699; i++) { + if (fs::file fragment{fmt::format("%s.%u", local_path, i)}) { + fragments.emplace_back(std::move(fragment)); + } else { + break; + } + } + + if (!fragments.empty()) { + file = fs::make_gather(std::move(fragments)); + } + } + + if (!file) { + if (mp.read_only) { + // Failed to create file on read-only FS (file doesn't exist) + if (flags & CELL_FS_O_ACCMODE && flags & CELL_FS_O_CREAT) { + return {CELL_EPERM}; + } + } + + if (open_mode & fs::excl && fs::g_tls_error == fs::error::exist) { + return {CELL_EEXIST}; + } + + switch (auto error = fs::g_tls_error) { + case fs::error::noent: + return {CELL_ENOENT}; + default: + sys_fs.error("lv2_file::open(): unknown error %s", error); + } + + return {CELL_EIO}; + } + + if (flags & CELL_FS_O_MSELF && !verify_mself(file)) { + return {CELL_ENOTMSELF}; + } + + if (type >= lv2_file_type::sdata) { + // check for sdata + switch (type) { + case lv2_file_type::sdata: { + // check if the file has the NPD header, or else assume its not encrypted + u32 magic; + file.read(magic); + file.seek(0); + if (magic == "NPD\0"_u32) { + auto sdata_file = std::make_unique(std::move(file)); + if (!sdata_file->ReadHeader()) { + return {CELL_EFSSPECIFIC}; + } + + file.reset(std::move(sdata_file)); + } + + break; + } + // edata + case lv2_file_type::edata: { + // check if the file has the NPD header, or else assume its not encrypted + u32 magic; + file.read(magic); + file.seek(0); + if (magic == "NPD\0"_u32) { + auto &edatkeys = g_fxo->get(); + + const u64 init_pos = edatkeys.dec_keys_pos; + const auto &dec_keys = edatkeys.dec_keys; + const u64 max_i = std::min(std::size(dec_keys), init_pos); + + if (edatkeys.one_time_key) { + auto edata_file = std::make_unique( + std::move(file), edatkeys.one_time_key); + edatkeys.one_time_key = {}; + + if (!edata_file->ReadHeader()) { + // Read failure + return {CELL_EFSSPECIFIC}; + } + + file.reset(std::move(edata_file)); + break; + } + + for (u64 i = 0;; i++) { + if (i == max_i) { + // Run out of keys to try + return {CELL_EFSSPECIFIC}; + } + + // Try all registered keys + auto edata_file = std::make_unique( + std::move(file), + dec_keys[(init_pos - i - 1) % std::size(dec_keys)].load()); + if (!edata_file->ReadHeader()) { + // Prepare file for the next iteration + file = std::move(edata_file->m_edata_file); + continue; + } + + file.reset(std::move(edata_file)); + break; + } + } + + break; + } + default: + break; + } + } + + return {.error = {}, .file = std::move(file)}; +} + +lv2_file::open_result_t lv2_file::open(std::string_view vpath, s32 flags, + s32 mode, const void *arg, u64 size) { + if (vpath.empty()) { + return {CELL_ENOENT}; + } + + std::string path; + std::string local_path = vfs::get(vpath, nullptr, &path); + + const auto &mp = g_fxo->get().lookup(vpath); + + if (mp == &g_mp_sys_dev_root) { + return {CELL_EISDIR, path}; + } + + if (local_path.empty()) { + return {CELL_ENOTMOUNTED, path}; + } + + if (flags & CELL_FS_O_CREAT && !has_fs_write_rights(vpath) && + !fs::is_dir(local_path)) { + return {CELL_EACCES}; + } + + lv2_file_type type = lv2_file_type::regular; + + if (size == 8) { + // see lv2_file::open_raw + switch (*static_cast *>(arg)) { + case 0x18000000010: + type = lv2_file_type::sdata; + break; + case 0x2: + type = lv2_file_type::edata; + break; + default: + break; + } + } + + auto [error, file] = open_raw(local_path, flags, mode, type, mp); + + return {.error = error, + .ppath = std::move(path), + .real_path = std::move(local_path), + .file = std::move(file), + .type = type}; +} + +error_code sys_fs_open(ppu_thread &ppu, vm::cptr path, s32 flags, + vm::ptr fd, s32 mode, vm::cptr arg, + u64 size) { + ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); + + sys_fs.warning("sys_fs_open(path=%s, flags=%#o, fd=*0x%x, mode=%#o, " + "arg=*0x%x, size=0x%llx)", + path, flags, fd, mode, arg, size); + + const auto [path_error, vpath] = translate_to_str(path); + + if (path_error) { + return {path_error, vpath}; + } + + auto [error, ppath, real, file, type] = + lv2_file::open(vpath, flags, mode, arg.get_ptr(), size); + + if (error) { + if (error == CELL_EEXIST) { + return not_an_error(CELL_EEXIST); + } + + return {g_fxo->get().lookup(vpath) == + &g_mp_sys_dev_hdd1 + ? sys_fs.warning + : sys_fs.error, + error, path}; + } + + if (const u32 id = idm::import ( + [&ppath = ppath, &file = file, mode, flags, &real = real, + &type = type]() -> shared_ptr { + shared_ptr result; + + if (type >= lv2_file_type::sdata && + !g_fxo->get().npdrm_fds.try_inc(16)) { + return result; + } + + result = stx::make_shared(ppath, std::move(file), mode, + flags, real, type); + sys_fs.warning("sys_fs_open(): fd=%u, %s", idm::last_id(), *result); + return result; + })) { + ppu.check_state(); + *fd = id; + return CELL_OK; + } + + // Out of file descriptors + return {CELL_EMFILE, path}; +} + +error_code sys_fs_read(ppu_thread &ppu, u32 fd, vm::ptr buf, u64 nbytes, + vm::ptr nread) { + ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); + + sys_fs.trace("sys_fs_read(fd=%d, buf=*0x%x, nbytes=0x%llx, nread=*0x%x)", fd, + buf, nbytes, nread); + + if (!nread) { + return CELL_EFAULT; + } + + if (!buf) { + nread.try_write(0); + return CELL_EFAULT; + } + + const auto file = idm::get_unlocked(fd); + + if (!file || (nbytes && file->flags & CELL_FS_O_WRONLY)) { + nread.try_write( + 0); // nread writing is allowed to fail, error code is unchanged + return CELL_EBADF; + } + + if (!nbytes) { + // Whole function is skipped, only EBADF and EBUSY are checked + if (file->lock == 1) { + nread.try_write(0); + return CELL_EBUSY; + } + + ppu.check_state(); + *nread = 0; + return CELL_OK; + } + + std::unique_lock lock(file->mp->mutex); + + if (!file->file) { + return CELL_EBADF; + } + + if (file->lock == 2) { + nread.try_write(0); + return CELL_EIO; + } + + const u64 read_bytes = file->op_read(buf, nbytes); + const bool failure = !read_bytes && file->file.pos() < file->file.size(); + lock.unlock(); + ppu.check_state(); + + *nread = read_bytes; + + if (failure) { + // EDATA corruption perhaps + return CELL_EFSSPECIFIC; + } + + return CELL_OK; +} + +error_code sys_fs_write(ppu_thread &ppu, u32 fd, vm::cptr buf, u64 nbytes, + vm::ptr nwrite) { + ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); + + sys_fs.trace("sys_fs_write(fd=%d, buf=*0x%x, nbytes=0x%llx, nwrite=*0x%x)", + fd, buf, nbytes, nwrite); + + if (!nwrite) { + return CELL_EFAULT; + } + + if (!buf) { + nwrite.try_write(0); + return CELL_EFAULT; + } + + const auto file = idm::get_unlocked(fd); + + if (!file || (nbytes && !(file->flags & CELL_FS_O_ACCMODE))) { + nwrite.try_write( + 0); // nwrite writing is allowed to fail, error code is unchanged + return CELL_EBADF; + } + + if (!nbytes) { + // Whole function is skipped, only EBADF and EBUSY are checked + if (file->lock == 1) { + nwrite.try_write(0); + return CELL_EBUSY; + } + + ppu.check_state(); + *nwrite = 0; + return CELL_OK; + } + + if (file->type != lv2_file_type::regular) { + sys_fs.error("%s type: Writing %u bytes to FD=%d (path=%s)", file->type, + nbytes, file->name.data()); + } + + if (file->mp.read_only) { + nwrite.try_write(0); + return CELL_EROFS; + } + + std::unique_lock lock(file->mp->mutex); + + if (!file->file) { + return CELL_EBADF; + } + + if (file->lock) { + if (file->lock == 2) { + nwrite.try_write(0); + return CELL_EIO; + } + + nwrite.try_write(0); + return CELL_EBUSY; + } + + if (file->flags & CELL_FS_O_APPEND) { + file->file.seek(0, fs::seek_end); + } + + const u64 written = file->op_write(buf, nbytes); + lock.unlock(); + ppu.check_state(); + + *nwrite = written; + return CELL_OK; +} + +error_code sys_fs_close(ppu_thread &ppu, u32 fd) { + ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); + + const auto file = idm::get_unlocked(fd); + + if (!file) { + return {CELL_EBADF, fd}; + } + + std::string FD_state_log; + + if (sys_fs.warning) { + FD_state_log = fmt::format("sys_fs_close(fd=%u)", fd); + } + + { + std::lock_guard lock(file->mp->mutex); + + if (!file->file) { + sys_fs.warning("%s", FD_state_log); + return {CELL_EBADF, fd}; + } + + if (!(file->mp.read_only && file->mp->flags & lv2_mp_flag::cache) && + file->flags & CELL_FS_O_ACCMODE) { + // Special: Ensure temporary directory for gamedata writes will remain on + // disk before final gamedata commitment + file->file.sync(); // For cellGameContentPermit atomicity + } + + if (!FD_state_log.empty()) { + sys_fs.warning("%s: %s", FD_state_log, *file); + } + + // Free memory associated with fd if any + if (file->ct_id && file->ct_used) { + auto &default_container = g_fxo->get(); + std::lock_guard lock(default_container.mutex); + + if (auto ct = idm::get_unlocked(file->ct_id)) { + ct->free(file->ct_used); + if (default_container.id == file->ct_id) { + default_container.used -= file->ct_used; + } + } + } + + // Ensure Host file handle won't be kept open after this syscall + file->file.close(); + } + + ensure(idm::withdraw( + fd, [&](lv2_file &_file) -> CellError { + if (_file.type >= lv2_file_type::sdata) { + g_fxo->get().npdrm_fds--; + } + + return {}; + })); + + if (file->lock == 1) { + return {CELL_EBUSY, fd}; + } + + return CELL_OK; +} + +error_code sys_fs_opendir(ppu_thread &ppu, vm::cptr path, + vm::ptr fd) { + ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); + + sys_fs.warning("sys_fs_opendir(path=%s, fd=*0x%x)", path, fd); + + const auto [path_error, vpath] = translate_to_str(path); + + if (path_error) { + return {path_error, vpath}; + } + + std::string processed_path; + std::vector ext; + const std::string local_path = vfs::get(vpath, &ext, &processed_path); + + processed_path += "/"; + + const auto &mp = g_fxo->get().lookup(vpath); + + if (local_path.empty() && ext.empty()) { + return {CELL_ENOTMOUNTED, path}; + } + + // TODO: other checks for path + + if (fs::is_file(local_path)) { + return {CELL_ENOTDIR, path}; + } + + std::unique_lock lock(mp->mutex); + + const fs::dir dir(local_path); + + if (!dir) { + switch (const auto error = fs::g_tls_error) { + case fs::error::noent: { + if (ext.empty()) { + return {mp == &g_mp_sys_dev_hdd1 ? sys_fs.warning : sys_fs.error, + CELL_ENOENT, path}; + } + + break; + } + default: { + sys_fs.error("sys_fs_opendir(): unknown error %s", error); + return {CELL_EIO, path}; + } + } + } + + // Build directory as a vector of entries + std::vector data; + + if (dir) { + // Add real directories + while (dir.read(data.emplace_back())) { + // Preprocess entries + data.back().name = vfs::unescape(data.back().name); + + if (!data.back().is_directory && data.back().name == "."sv) { + // Files hidden from emulation + data.resize(data.size() - 1); + continue; + } + + // Add additional entries for split file candidates (while ends with + // .66600) + while (data.back().name.ends_with(".66600")) { + data.emplace_back(data.back()).name.resize(data.back().name.size() - 6); + } + } + + data.resize(data.size() - 1); + } else { + data.emplace_back().name += '.'; + data.back().is_directory = true; + data.emplace_back().name = ".."; + data.back().is_directory = true; + } + + // Add mount points (TODO) + for (auto &&ex : ext) { + data.emplace_back().name = std::move(ex); + data.back().is_directory = true; + } + + // Sort files, keeping . and .. + std::stable_sort(data.begin() + 2, data.end(), FN(x.name < y.name)); + + // Remove duplicates + data.erase(std::unique(data.begin(), data.end(), FN(x.name == y.name)), + data.end()); + + if (const u32 id = + idm::make(processed_path, std::move(data))) { + lock.unlock(); + ppu.check_state(); + + *fd = id; + return CELL_OK; + } + + // Out of file descriptors + return CELL_EMFILE; +} + +error_code sys_fs_readdir(ppu_thread &ppu, u32 fd, vm::ptr dir, + vm::ptr nread) { + ppu.state += cpu_flag::wait; + + sys_fs.warning("sys_fs_readdir(fd=%d, dir=*0x%x, nread=*0x%x)", fd, dir, + nread); + + if (!dir || !nread) { + return CELL_EFAULT; + } + + const auto directory = idm::get_unlocked(fd); + + if (!directory) { + return CELL_EBADF; + } + + ppu.check_state(); + + auto *info = directory->dir_read(); + + u64 nread_to_write = 0; + + if (info) { + nread_to_write = sizeof(CellFsDirent); + } else { + // It does actually write polling the last entry. Seems consistent across + // HDD0 and HDD1 (TODO: check more partitions) + info = &directory->entries.back(); + nread_to_write = 0; + } + + CellFsDirent dir_write{}; + + dir_write.d_type = + info->is_directory ? CELL_FS_TYPE_DIRECTORY : CELL_FS_TYPE_REGULAR; + dir_write.d_namlen = + u8(std::min(info->name.size(), CELL_FS_MAX_FS_FILE_NAME_LENGTH)); + strcpy_trunc(dir_write.d_name, info->name); + + // TODO: Check more partitions (HDD1 is known to differ in actual filesystem + // implementation) + if (directory->mp != &g_mp_sys_dev_hdd1 && nread_to_write == 0) { + // First 3 bytes are being set to 0 here + dir_write.d_type = 0; + dir_write.d_namlen = 0; + dir_write.d_name[0] = '\0'; + } + + *dir = dir_write; + + // Write after dir + *nread = nread_to_write; + return CELL_OK; +} + +error_code sys_fs_closedir(ppu_thread &ppu, u32 fd) { + ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); + + sys_fs.warning("sys_fs_closedir(fd=%d)", fd); + + if (!idm::remove(fd)) { + return CELL_EBADF; + } + + return CELL_OK; +} + +error_code sys_fs_stat(ppu_thread &ppu, vm::cptr path, + vm::ptr sb) { + ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); + + sys_fs.warning("sys_fs_stat(path=%s, sb=*0x%x)", path, sb); + + const auto [path_error, vpath] = translate_to_str(path); + + if (path_error) { + return {path_error, vpath}; + } + + const std::string local_path = vfs::get(vpath); + + const auto &mp = g_fxo->get().lookup(vpath); + + if (mp == &g_mp_sys_dev_root) { + sb->mode = CELL_FS_S_IFDIR | 0711; + sb->uid = -1; + sb->gid = -1; + sb->atime = -1; + sb->mtime = -1; + sb->ctime = -1; + sb->size = 258; + sb->blksize = 512; + return CELL_OK; + } + + if (local_path.empty()) { + // This syscall can be used by games and VSH to test the presence of + // dev_usb000 ~ dev_usb127 Thus there is no need to fuss about + // CELL_ENOTMOUNTED in this case + return {sys_fs.warning, CELL_ENOTMOUNTED, path}; + } + + std::unique_lock lock(mp->mutex); + + fs::stat_t info{}; + + if (!fs::get_stat(local_path, info)) { + switch (auto error = fs::g_tls_error) { + case fs::error::noent: { + // Try to analyse split file (TODO) + u64 total_size = 0; + + for (u32 i = 66601; i <= 66699; i++) { + if (fs::get_stat(fmt::format("%s.%u", local_path, i), info) && + !info.is_directory) { + total_size += info.size; + } else { + break; + } + } + + // Use attributes from the first fragment (consistently with + // sys_fs_open+fstat) + if (fs::get_stat(local_path + ".66600", info) && !info.is_directory) { + // Success + info.size += total_size; + break; + } + + return {mp == &g_mp_sys_dev_hdd1 ? sys_fs.warning : sys_fs.error, + CELL_ENOENT, path}; + } + default: { + sys_fs.error("sys_fs_stat(): unknown error %s", error); + return {CELL_EIO, path}; + } + } + } + + lock.unlock(); + ppu.check_state(); + + s32 mode = + info.is_directory ? CELL_FS_S_IFDIR | 0777 : CELL_FS_S_IFREG | 0666; + + if (mp.read_only) { + // Remove write permissions + mode &= ~0222; + } + + sb->mode = mode; + sb->uid = mp->flags & lv2_mp_flag::no_uid_gid ? -1 : 0; + sb->gid = mp->flags & lv2_mp_flag::no_uid_gid ? -1 : 0; + sb->atime = info.atime; + sb->mtime = info.mtime; + sb->ctime = info.ctime; + sb->size = info.is_directory ? mp->block_size : info.size; + sb->blksize = mp->block_size; + + return CELL_OK; +} + +error_code sys_fs_fstat(ppu_thread &ppu, u32 fd, vm::ptr sb) { + ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); + + sys_fs.warning("sys_fs_fstat(fd=%d, sb=*0x%x)", fd, sb); + + const auto file = idm::get_unlocked(fd); + + if (!file) { + return CELL_EBADF; + } + + std::unique_lock lock(file->mp->mutex); + + if (!file->file) { + return CELL_EBADF; + } + + if (file->lock == 2) { + return CELL_EIO; + } + + const fs::stat_t info = file->file.get_stat(); + lock.unlock(); + ppu.check_state(); + + s32 mode = + info.is_directory ? CELL_FS_S_IFDIR | 0777 : CELL_FS_S_IFREG | 0666; + + if (file->mp.read_only) { + // Remove write permissions + mode &= ~0222; + } + + sb->mode = mode; + sb->uid = file->mp->flags & lv2_mp_flag::no_uid_gid ? -1 : 0; + sb->gid = file->mp->flags & lv2_mp_flag::no_uid_gid ? -1 : 0; + sb->atime = info.atime; + sb->mtime = info.mtime; + sb->ctime = info.ctime; // ctime may be incorrect + sb->size = info.size; + sb->blksize = file->mp->block_size; + return CELL_OK; +} + +error_code sys_fs_link(ppu_thread &, vm::cptr from, vm::cptr to) { + sys_fs.todo("sys_fs_link(from=%s, to=%s)", from, to); + + return CELL_OK; +} + +error_code sys_fs_mkdir(ppu_thread &ppu, vm::cptr path, s32 mode) { + ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); + + sys_fs.warning("sys_fs_mkdir(path=%s, mode=%#o)", path, mode); + + const auto [path_error, vpath] = translate_to_str(path); + + if (path_error) { + return {path_error, vpath}; + } + + const std::string local_path = vfs::get(vpath); + + const auto &mp = g_fxo->get().lookup(vpath); + + if (mp == &g_mp_sys_dev_root) { + return {CELL_EEXIST, path}; + } + + if (local_path.empty()) { + return {CELL_ENOTMOUNTED, path}; + } + + if (mp.read_only) { + return {CELL_EROFS, path}; + } + + if (!fs::exists(local_path) && !has_fs_write_rights(path.get_ptr())) { + return {CELL_EACCES, path}; + } + + std::lock_guard lock(mp->mutex); + + if (!fs::create_dir(local_path)) { + switch (auto error = fs::g_tls_error) { + case fs::error::noent: { + return {mp == &g_mp_sys_dev_hdd1 ? sys_fs.warning : sys_fs.error, + CELL_ENOENT, path}; + } + case fs::error::exist: { + return {sys_fs.warning, CELL_EEXIST, path}; + } + default: + sys_fs.error("sys_fs_mkdir(): unknown error %s", error); + } + + return {CELL_EIO, path}; // ??? + } + + sys_fs.notice("sys_fs_mkdir(): directory %s created", path); + return CELL_OK; +} + +error_code sys_fs_rename(ppu_thread &ppu, vm::cptr from, + vm::cptr to) { + ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); + + sys_fs.warning("sys_fs_rename(from=%s, to=%s)", from, to); + + const auto [from_error, vfrom] = translate_to_str(from); + + if (from_error) { + return {from_error, vfrom}; + } + + const auto [to_error, vto] = translate_to_str(to); + + if (to_error) { + return {to_error, vto}; + } + + const std::string local_from = vfs::get(vfrom); + const std::string local_to = vfs::get(vto); + + const auto &mp = g_fxo->get().lookup(vfrom); + const auto &mp_to = g_fxo->get().lookup(vto); + + if (mp == &g_mp_sys_dev_root || mp_to == &g_mp_sys_dev_root) { + return CELL_EPERM; + } + + if (local_from.empty() || local_to.empty()) { + return CELL_ENOTMOUNTED; + } + + if (mp != mp_to) { + return CELL_EXDEV; + } + + if (mp.read_only) { + return CELL_EROFS; + } + + // Done in vfs::host::rename + // std::lock_guard lock(mp->mutex); + + if (!vfs::host::rename(local_from, local_to, mp.mp, false)) { + switch (auto error = fs::g_tls_error) { + case fs::error::noent: + return {CELL_ENOENT, from}; + case fs::error::exist: + return {CELL_EEXIST, to}; + default: + sys_fs.error("sys_fs_rename(): unknown error %s", error); + } + + return {CELL_EIO, from}; // ??? + } + + sys_fs.notice("sys_fs_rename(): %s renamed to %s", from, to); + return CELL_OK; +} + +error_code sys_fs_rmdir(ppu_thread &ppu, vm::cptr path) { + ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); + + sys_fs.warning("sys_fs_rmdir(path=%s)", path); + + const auto [path_error, vpath] = translate_to_str(path); + + if (path_error) { + return {path_error, vpath}; + } + + const std::string local_path = vfs::get(vpath); + + const auto &mp = g_fxo->get().lookup(vpath); + + if (mp == &g_mp_sys_dev_root) { + return {CELL_EPERM, path}; + } + + if (local_path.empty()) { + return {CELL_ENOTMOUNTED, path}; + } + + if (mp.read_only) { + return {CELL_EROFS, path}; + } + + if (fs::is_dir(local_path) && !has_fs_write_rights(path.get_ptr())) { + return {CELL_EACCES}; + } + + std::lock_guard lock(mp->mutex); + + if (!fs::remove_dir(local_path)) { + switch (auto error = fs::g_tls_error) { + case fs::error::noent: + return {CELL_ENOENT, path}; + case fs::error::notempty: + return {CELL_ENOTEMPTY, path}; + default: + sys_fs.error("sys_fs_rmdir(): unknown error %s", error); + } + + return {CELL_EIO, path}; // ??? + } + + sys_fs.notice("sys_fs_rmdir(): directory %s removed", path); + return CELL_OK; +} + +error_code sys_fs_unlink(ppu_thread &ppu, vm::cptr path) { + ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); + + sys_fs.warning("sys_fs_unlink(path=%s)", path); + + const auto [path_error, vpath] = translate_to_str(path); + + if (path_error) { + return {path_error, vpath}; + } + + const std::string local_path = vfs::get(vpath); + + std::string mount_path = + fs::get_parent_dir(vpath); // Use its parent directory as fallback + const auto &mp = + g_fxo->get().lookup(vpath, true, &mount_path); + + if (mp == &g_mp_sys_dev_root) { + return {CELL_EISDIR, path}; + } + + if (local_path.empty()) { + return {CELL_ENOTMOUNTED, path}; + } + + if (fs::is_dir(local_path)) { + return {CELL_EISDIR, path}; + } + + if (mp.read_only) { + return {CELL_EROFS, path}; + } + + std::lock_guard lock(mp->mutex); + + if (!vfs::host::unlink(local_path, vfs::get(mount_path))) { + switch (auto error = fs::g_tls_error) { + case fs::error::noent: { + return {mp == &g_mp_sys_dev_hdd1 ? sys_fs.warning : sys_fs.error, + CELL_ENOENT, path}; + } + default: + sys_fs.error("sys_fs_unlink(): unknown error %s", error); + } + + return {CELL_EIO, path}; // ??? + } + + sys_fs.notice("sys_fs_unlink(): file %s deleted", path); + return CELL_OK; +} + +error_code sys_fs_access(ppu_thread &, vm::cptr path, s32 mode) { + sys_fs.todo("sys_fs_access(path=%s, mode=%#o)", path, mode); + + return CELL_OK; +} + +error_code sys_fs_fcntl(ppu_thread &ppu, u32 fd, u32 op, vm::ptr _arg, + u32 _size) { + ppu.state += cpu_flag::wait; + + sys_fs.trace("sys_fs_fcntl(fd=%d, op=0x%x, arg=*0x%x, size=0x%x)", fd, op, + _arg, _size); + + switch (op) { + case 0x80000004: // Unknown + { + if (_size > 4) { + return CELL_EINVAL; + } + + const auto arg = vm::static_ptr_cast(_arg); + *arg = 0; + break; + } + + case 0x80000006: // cellFsAllocateFileAreaByFdWithInitialData + { + break; + } + + case 0x80000007: // cellFsAllocateFileAreaByFdWithoutZeroFill + { + break; + } + + case 0x80000008: // cellFsChangeFileSizeByFdWithoutAllocation + { + break; + } + + case 0x8000000a: // cellFsReadWithOffset + case 0x8000000b: // cellFsWriteWithOffset + { + lv2_obj::sleep(ppu); + + const auto arg = vm::static_ptr_cast(_arg); + + if (_size < arg.size()) { + return CELL_EINVAL; + } + + const auto file = idm::get_unlocked(fd); + + if (!file) { + return CELL_EBADF; + } + + if (op == 0x8000000a && file->flags & CELL_FS_O_WRONLY) { + return CELL_EBADF; + } + + if (op == 0x8000000b && !(file->flags & CELL_FS_O_ACCMODE)) { + return CELL_EBADF; + } + + if (op == 0x8000000b && file->flags & CELL_FS_O_APPEND) { + return CELL_EBADF; + } + + if (op == 0x8000000b && file->mp.read_only) { + return CELL_EROFS; + } + + if (op == 0x8000000b && file->type != lv2_file_type::regular && arg->size) { + sys_fs.error("%s type: Writing %u bytes to FD=%d (path=%s)", file->type, + arg->size, file->name.data()); + } + + std::unique_lock wlock(file->mp->mutex, std::defer_lock); + std::shared_lock rlock(file->mp->mutex, std::defer_lock); + + if (op == 0x8000000b) { + // Writer lock + wlock.lock(); + } else { + // Reader lock (not needing exclusivity in this special case because the + // state should not change) + rlock.lock(); + } + + if (!file->file) { + return CELL_EBADF; + } + + if (file->lock == 2) { + return CELL_EIO; + } + + if (op == 0x8000000b && file->lock) { + return CELL_EBUSY; + } + + u64 old_pos = umax; + const u64 op_pos = arg->offset; + + if (op == 0x8000000b) { + old_pos = file->file.pos(); + file->file.seek(op_pos); + } + + arg->out_size = op == 0x8000000a + ? file->op_read(arg->buf, arg->size, op_pos) + : file->op_write(arg->buf, arg->size); + + if (op == 0x8000000b) { + ensure(old_pos == file->file.seek(old_pos)); + } + + // TODO: EDATA corruption detection + + arg->out_code = CELL_OK; + return CELL_OK; + } + + case 0x80000009: // cellFsSdataOpenByFd + { + lv2_obj::sleep(ppu); + + const auto arg = vm::static_ptr_cast(_arg); + + if (_size < arg.size()) { + return CELL_EINVAL; + } + + const auto file = idm::get_unlocked(fd); + + if (!file) { + return {CELL_EBADF, "fd=%u", fd}; + } + + sys_fs.warning("sys_fs_fcntl(0x80000009): fd=%d, arg->offset=0x%x, " + "size=0x%x (file: %s)", + fd, arg->offset, _size, *file); + + std::lock_guard lock(file->mp->mutex); + + if (!file->file) { + return {CELL_EBADF, "fd=%u", fd}; + } + + auto sdata_file = std::make_unique( + lv2_file::make_view(file, arg->offset)); + + if (!sdata_file->ReadHeader()) { + return {CELL_EFSSPECIFIC, "fd=%u", fd}; + } + + fs::file stream; + stream.reset(std::move(sdata_file)); + if (const u32 id = idm::import ( + [&file = *file, &stream = stream]() -> shared_ptr { + if (!g_fxo->get().npdrm_fds.try_inc(16)) { + return null_ptr; + } + + return stx::make_shared( + file, std::move(stream), file.mode, CELL_FS_O_RDONLY, + file.real_path, lv2_file_type::sdata); + })) { + arg->out_code = CELL_OK; + arg->out_fd = id; + return CELL_OK; + } + + // Out of file descriptors + return CELL_EMFILE; + } + + case 0xc0000002: // cellFsGetFreeSize (TODO) + { + lv2_obj::sleep(ppu); + + const auto arg = vm::static_ptr_cast(_arg); + + const auto &mp = g_fxo->get().lookup("/dev_hdd0"); + + arg->out_block_size = mp->block_size; + arg->out_block_count = + (40ull * 1024 * 1024 * 1024 - 1) / + mp->block_size; // Read explanation in cellHddGameCheck + return CELL_OK; + } + + case 0xc0000003: // cellFsUtilitySetFakeSize + { + break; + } + + case 0xc0000004: // cellFsUtilityGetFakeSize + { + break; + } + + case 0xc0000006: // Unknown + { + const auto arg = vm::static_ptr_cast(_arg); + + if (arg->size != 0x20u) { + sys_fs.error("sys_fs_fcntl(0xc0000006): invalid size (0x%x)", arg->size); + break; + } + + if (arg->_x4 != 0x10u || arg->_x8 != 0x18u) { + sys_fs.error("sys_fs_fcntl(0xc0000006): invalid args (0x%x, 0x%x)", + arg->_x4, arg->_x8); + break; + } + + // Load mountpoint (doesn't support multiple // at the start) + std::string_view vpath{arg->name.get_ptr(), arg->name_size}; + + sys_fs.notice("sys_fs_fcntl(0xc0000006): %s", vpath); + + // Check only mountpoint + vpath = vpath.substr(0, vpath.find_first_of("/", 1)); + + // Some mountpoints seem to be handled specially + if (false) { + // TODO: /dev_hdd1, /dev_usb000, /dev_flash + // arg->out_code = CELL_OK; + // arg->out_id = 0x1b5; + } + + arg->out_code = CELL_ENOTSUP; + arg->out_id = 0; + return CELL_OK; + } + + case 0xc0000007: // cellFsArcadeHddSerialNumber + { + const auto arg = vm::static_ptr_cast(_arg); + + arg->out_code = CELL_OK; + + if (const auto size = arg->model_size; size > 0) + strcpy_trunc( + std::span(arg->model.get_ptr(), size), + fmt::format("%-*s", size - 1, + g_cfg.sys.hdd_model + .to_string())); // Example: "TOSHIBA MK3265GSX H " + + if (const auto size = arg->serial_size; size > 0) + strcpy_trunc( + std::span(arg->serial.get_ptr(), size), + fmt::format( + "%*s", size - 1, + g_cfg.sys.hdd_serial.to_string())); // Example: " 0A1B2C3D4" + else + return CELL_EFAULT; // CELL_EFAULT is returned only when arg->serial_size + // == 0 + + return CELL_OK; + } + + case 0xc0000008: // cellFsSetDefaultContainer, cellFsSetIoBuffer, + // cellFsSetIoBufferFromDefaultContainer + { + // Allocates memory from a container/default container to a specific fd or + // default IO processing + const auto arg = vm::static_ptr_cast(_arg); + auto &default_container = g_fxo->get(); + + std::lock_guard def_container_lock(default_container.mutex); + + if (fd == 0xFFFFFFFF) { + // No check on container is done when setting default container + default_container.id = arg->size ? ::narrow(arg->container_id) : 0u; + default_container.cap = arg->size; + default_container.used = 0; + + arg->out_code = CELL_OK; + return CELL_OK; + } + + auto file = idm::get_unlocked(fd); + if (!file) { + return CELL_EBADF; + } + + if (auto ct = idm::get_unlocked(file->ct_id)) { + ct->free(file->ct_used); + if (default_container.id == file->ct_id) { + default_container.used -= file->ct_used; + } + } + + file->ct_id = 0; + file->ct_used = 0; + + // Aligns on lower bound + u32 actual_size = + arg->size - + (arg->size % ((arg->page_type & CELL_FS_IO_BUFFER_PAGE_SIZE_64KB) + ? 0x10000 + : 0x100000)); + + if (!actual_size) { + arg->out_code = CELL_OK; + return CELL_OK; + } + + u32 new_container_id = arg->container_id == 0xFFFFFFFF + ? default_container.id + : ::narrow(arg->container_id); + if (default_container.id == new_container_id && + (default_container.used + actual_size) > default_container.cap) { + return CELL_ENOMEM; + } + + const auto ct = idm::get( + new_container_id, [&](lv2_memory_container &ct) -> CellError { + if (!ct.take(actual_size)) { + return CELL_ENOMEM; + } + + return {}; + }); + + if (!ct) { + return CELL_ESRCH; + } + + if (ct.ret) { + return ct.ret; + } + + if (default_container.id == new_container_id) { + default_container.used += actual_size; + } + + file->ct_id = new_container_id; + file->ct_used = actual_size; + + arg->out_code = CELL_OK; + return CELL_OK; + } + + case 0xc0000015: // USB Vid/Pid query + case 0xc000001c: // USB Vid/Pid/Serial query + { + const auto arg = vm::static_ptr_cast(_arg); + const bool with_serial = op == 0xc000001c; + + if (arg->size != + (with_serial ? sizeof(lv2_file_c000001c) : sizeof(lv2_file_c0000015))) { + sys_fs.error("sys_fs_fcntl(0x%08x): invalid size (0x%x)", op, arg->size); + break; + } + + if (arg->_x4 != 0x10u || arg->_x8 != 0x18u) { + sys_fs.error("sys_fs_fcntl(0x%08x): invalid args (0x%x, 0x%x)", op, + arg->_x4, arg->_x8); + break; + } + + std::string_view vpath{arg->path.get_ptr(), arg->path_size}; + + if (vpath.size() == 0) + return CELL_ENOMEM; + + // Trim trailing '\0' + if (const auto trim_pos = vpath.find('\0'); trim_pos != umax) + vpath.remove_suffix(vpath.size() - trim_pos); + + arg->out_code = + CELL_ENOTMOUNTED; // arg->out_code is set to CELL_ENOTMOUNTED on real + // hardware when the device doesn't exist or when the + // device isn't USB + + if (!vfs::get(vpath).empty()) { + if (const auto &mp = + g_fxo->get().lookup(vpath, true); + mp == &g_mp_sys_dev_usb) { + const cfg::device_info device = g_cfg_vfs.get_device( + g_cfg_vfs.dev_usb, + fmt::format("%s%s", mp->root, mp.device.substr(mp->device.size()))); + const auto usb_ids = device.get_usb_ids(); + std::tie(arg->vendorID, arg->productID) = usb_ids; + + if (with_serial) { + const auto arg_c000001c = + vm::static_ptr_cast(_arg); + const std::u16string serial = utf8_to_utf16( + device.serial); // Serial needs to be encoded to utf-16 BE + std::copy_n(serial.begin(), + std::min(serial.size(), + sizeof(arg_c000001c->serial) / sizeof(u16)), + arg_c000001c->serial); + } + + arg->out_code = CELL_OK; + sys_fs.trace("sys_fs_fcntl(0x%08x): found device \"%s\" (vid=0x%04x, " + "pid=0x%04x, serial=\"%s\")", + op, mp.device, usb_ids.first, usb_ids.second, + device.serial); + } + } + + return CELL_OK; + } + + case 0xc0000016: // ps2disc_8160A811 + { + break; + } + + case 0xc000001a: // cellFsSetDiscReadRetrySetting, 5731DF45 + { + [[maybe_unused]] const auto arg = + vm::static_ptr_cast(_arg); + return CELL_OK; + } + + case 0xc0000021: // 9FDBBA89 + { + break; + } + + case 0xe0000000: // Unknown (cellFsGetBlockSize) + { + break; + } + + case 0xe0000001: // Unknown (cellFsStat) + { + break; + } + + case 0xe0000003: // Unknown + { + break; + } + + case 0xe0000004: // Unknown + { + break; + } + + case 0xe0000005: // Unknown (cellFsMkdir) + { + break; + } + + case 0xe0000006: // Unknown + { + break; + } + + case 0xe0000007: // Unknown + { + break; + } + + case 0xe0000008: // Unknown (cellFsAclRead) + { + break; + } + + case 0xe0000009: // Unknown (cellFsAccess) + { + break; + } + + case 0xe000000a: // Unknown (E3D28395) + { + break; + } + + case 0xe000000b: // Unknown (cellFsRename, FF29F478) + { + break; + } + + case 0xe000000c: // Unknown (cellFsTruncate) + { + break; + } + + case 0xe000000d: // Unknown (cellFsUtime) + { + break; + } + + case 0xe000000e: // Unknown (cellFsAclWrite) + { + break; + } + + case 0xe000000f: // Unknown (cellFsChmod) + { + break; + } + + case 0xe0000010: // Unknown (cellFsChown) + { + break; + } + + case 0xe0000011: // Unknown + { + break; + } + + case 0xe0000012: // cellFsGetDirectoryEntries + { + lv2_obj::sleep(ppu); + + const auto arg = vm::static_ptr_cast(_arg); + + if (_size < arg.size()) { + return CELL_EINVAL; + } + + const auto directory = idm::get_unlocked(fd); + + if (!directory) { + return CELL_EBADF; + } + + ppu.check_state(); + + u32 read_count = 0; + + // NOTE: This function is actually capable of reading only one entry at a + // time + if (const u32 max = arg->max) { + const auto arg_ptr = +arg->ptr; + + if (auto *info = directory->dir_read()) { + auto &entry = arg_ptr[read_count++]; + + s32 mode = info->is_directory ? CELL_FS_S_IFDIR | 0777 + : CELL_FS_S_IFREG | 0666; + + if (directory->mp.read_only) { + // Remove write permissions + mode &= ~0222; + } + + entry.attribute.mode = mode; + entry.attribute.uid = + directory->mp->flags & lv2_mp_flag::no_uid_gid ? -1 : 0; + entry.attribute.gid = + directory->mp->flags & lv2_mp_flag::no_uid_gid ? -1 : 0; + entry.attribute.atime = info->atime; + entry.attribute.mtime = info->mtime; + entry.attribute.ctime = info->ctime; + entry.attribute.size = info->size; + entry.attribute.blksize = directory->mp->block_size; + + entry.entry_name.d_type = + info->is_directory ? CELL_FS_TYPE_DIRECTORY : CELL_FS_TYPE_REGULAR; + entry.entry_name.d_namlen = u8( + std::min(info->name.size(), CELL_FS_MAX_FS_FILE_NAME_LENGTH)); + strcpy_trunc(entry.entry_name.d_name, info->name); + } + + // Apparently all this function does to additional buffer elements is to + // zeroize them + std::memset(arg_ptr.get_ptr() + read_count, 0, + (max - read_count) * arg->ptr.size()); + } + + arg->_size = read_count; + arg->_code = CELL_OK; + return CELL_OK; + } + + case 0xe0000015: // Unknown + { + break; + } + + case 0xe0000016: // cellFsAllocateFileAreaWithInitialData + { + break; + } + + case 0xe0000017: // cellFsAllocateFileAreaWithoutZeroFill + { + const auto arg = vm::static_ptr_cast(_arg); + + if (_size < arg->size || arg->_x4 != 0x10u || arg->_x8 != 0x20u) { + return CELL_EINVAL; + } + + arg->out_code = sys_fs_truncate(ppu, arg->file_path, arg->file_size); + return CELL_OK; + } + + case 0xe0000018: // cellFsChangeFileSizeWithoutAllocation + { + break; + } + + case 0xe0000019: // Unknown + { + break; + } + + case 0xe000001b: // Unknown + { + break; + } + + case 0xe000001d: // Unknown + { + break; + } + + case 0xe000001e: // Unknown + { + break; + } + + case 0xe000001f: // Unknown + { + break; + } + + case 0xe0000020: // Unknown + { + break; + } + + case 0xe0000025: // cellFsSdataOpenWithVersion + { + const auto arg = vm::static_ptr_cast(_arg); + + if (arg->size != 0x30u) { + sys_fs.error("sys_fs_fcntl(0xe0000025): invalid size (0x%x)", arg->size); + break; + } + + if (arg->_x4 != 0x10u || arg->_x8 != 0x28u) { + sys_fs.error("sys_fs_fcntl(0xe0000025): invalid args (0x%x, 0x%x)", + arg->_x4, arg->_x8); + break; + } + + std::string_view vpath{arg->name.get_ptr(), arg->name_size}; + vpath = vpath.substr(0, vpath.find_first_of('\0')); + + sys_fs.notice("sys_fs_fcntl(0xe0000025): %s", vpath); + + be_t sdata_identifier = 0x18000000010; + + lv2_file::open_result_t result = + lv2_file::open(vpath, 0, 0, &sdata_identifier, 8); + + if (result.error) { + return result.error; + } + + if (const u32 id = idm::import ( + [&]() -> shared_ptr { + if (!g_fxo->get().npdrm_fds.try_inc(16)) { + return null_ptr; + } + + return stx::make_shared( + result.ppath, std::move(result.file), 0, 0, + std::move(result.real_path), lv2_file_type::sdata); + })) { + arg->out_code = CELL_OK; + arg->fd = id; + return CELL_OK; + } + + // Out of file descriptors + return CELL_EMFILE; + } + } + + sys_fs.error( + "sys_fs_fcntl(): Unknown operation 0x%08x (fd=%d, arg=*0x%x, size=0x%x)", + op, fd, _arg, _size); + return CELL_OK; +} + +error_code sys_fs_lseek(ppu_thread &ppu, u32 fd, s64 offset, s32 whence, + vm::ptr pos) { + ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); + + sys_fs.trace("sys_fs_lseek(fd=%d, offset=0x%llx, whence=0x%x, pos=*0x%x)", fd, + offset, whence, pos); + + const auto file = idm::get_unlocked(fd); + + if (!file) { + return CELL_EBADF; + } + + std::unique_lock lock(file->mp->mutex); + + if (!file->file) { + return CELL_EBADF; + } + + if (whence + 0u >= 3) { + return {CELL_EINVAL, whence}; + } + + const u64 result = + file->file.seek(offset, static_cast(whence)); + + if (result == umax) { + switch (auto error = fs::g_tls_error) { + case fs::error::inval: + return {CELL_EINVAL, "fd=%u, offset=0x%x, whence=%d", fd, offset, whence}; + default: + sys_fs.error("sys_fs_lseek(): unknown error %s", error); + } + + return CELL_EIO; // ??? + } + + lock.unlock(); + ppu.check_state(); + + *pos = result; + return CELL_OK; +} + +error_code sys_fs_fdatasync(ppu_thread &ppu, u32 fd) { + ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); + + sys_fs.trace("sys_fs_fdadasync(fd=%d)", fd); + + const auto file = idm::get_unlocked(fd); + + if (!file || !(file->flags & CELL_FS_O_ACCMODE)) { + return CELL_EBADF; + } + + std::lock_guard lock(file->mp->mutex); + + if (!file->file) { + return CELL_EBADF; + } + + file->file.sync(); + return CELL_OK; +} + +error_code sys_fs_fsync(ppu_thread &ppu, u32 fd) { + ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); + + sys_fs.trace("sys_fs_fsync(fd=%d)", fd); + + const auto file = idm::get_unlocked(fd); + + if (!file || !(file->flags & CELL_FS_O_ACCMODE)) { + return CELL_EBADF; + } + + std::lock_guard lock(file->mp->mutex); + + if (!file->file) { + return CELL_EBADF; + } + + file->file.sync(); + return CELL_OK; +} + +error_code sys_fs_fget_block_size(ppu_thread &ppu, u32 fd, + vm::ptr sector_size, + vm::ptr block_size, vm::ptr arg4, + vm::ptr out_flags) { + ppu.state += cpu_flag::wait; + + sys_fs.warning("sys_fs_fget_block_size(fd=%d, sector_size=*0x%x, " + "block_size=*0x%x, arg4=*0x%x, out_flags=*0x%x)", + fd, sector_size, block_size, arg4, out_flags); + + const auto file = idm::get_unlocked(fd); + + if (!file) { + return CELL_EBADF; + } + + static_cast(ppu.test_stopped()); + + // TODO + *sector_size = file->mp->sector_size; + *block_size = file->mp->block_size; + *arg4 = file->mp->sector_size; + *out_flags = file->flags; + + return CELL_OK; +} + +error_code sys_fs_get_block_size(ppu_thread &ppu, vm::cptr path, + vm::ptr sector_size, + vm::ptr block_size, vm::ptr arg4) { + ppu.state += cpu_flag::wait; + + sys_fs.warning("sys_fs_get_block_size(path=%s, sector_size=*0x%x, " + "block_size=*0x%x, arg4=*0x%x)", + path, sector_size, block_size, arg4); + + const auto [path_error, vpath] = translate_to_str(path); + + if (path_error) { + return {path_error, vpath}; + } + + const std::string local_path = vfs::get(vpath); + + if (vpath.find_first_not_of('/') == umax) { + return {CELL_EISDIR, path}; + } + + if (local_path.empty()) { + return {CELL_ENOTMOUNTED, path}; + } + + const auto &mp = g_fxo->get().lookup(vpath); + + // It appears that /dev_hdd0 mount point is special in this function + if (mp != &g_mp_sys_dev_hdd0 && + (mp->flags & lv2_mp_flag::strict_get_block_size + ? !fs::is_file(local_path) + : !fs::exists(local_path))) { + switch (auto error = fs::g_tls_error) { + case fs::error::exist: + return {CELL_EISDIR, path}; + case fs::error::noent: + return {CELL_ENOENT, path}; + default: + sys_fs.error("sys_fs_get_block_size(): unknown error %s", error); + } + + return {CELL_EIO, path}; // ??? + } + + static_cast(ppu.test_stopped()); + + // TODO + *sector_size = mp->sector_size; + *block_size = mp->block_size; + *arg4 = mp->sector_size; + + return CELL_OK; +} + +error_code sys_fs_truncate(ppu_thread &ppu, vm::cptr path, u64 size) { + ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); + + sys_fs.warning("sys_fs_truncate(path=%s, size=0x%llx)", path, size); + + const auto [path_error, vpath] = translate_to_str(path); + + if (path_error) { + return {path_error, vpath}; + } + + const std::string local_path = vfs::get(vpath); + + const auto &mp = g_fxo->get().lookup(vpath); + + if (mp == &g_mp_sys_dev_root) { + return {CELL_EISDIR, path}; + } + + if (local_path.empty()) { + return {CELL_ENOTMOUNTED, path}; + } + + if (mp.read_only) { + return {CELL_EROFS, path}; + } + + std::lock_guard lock(mp->mutex); + + if (!fs::truncate_file(local_path, size)) { + switch (auto error = fs::g_tls_error) { + case fs::error::noent: { + return {mp == &g_mp_sys_dev_hdd1 ? sys_fs.warning : sys_fs.error, + CELL_ENOENT, path}; + } + default: + sys_fs.error("sys_fs_truncate(): unknown error %s", error); + } + + return {CELL_EIO, path}; // ??? + } + + return CELL_OK; +} + +error_code sys_fs_ftruncate(ppu_thread &ppu, u32 fd, u64 size) { + ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); + + sys_fs.warning("sys_fs_ftruncate(fd=%d, size=0x%llx)", fd, size); + + const auto file = idm::get_unlocked(fd); + + if (!file || !(file->flags & CELL_FS_O_ACCMODE)) { + return CELL_EBADF; + } + + if (file->mp.read_only) { + return CELL_EROFS; + } + + std::lock_guard lock(file->mp->mutex); + + if (!file->file) { + return CELL_EBADF; + } + + if (file->lock == 2) { + return CELL_EIO; + } + + if (file->lock) { + return CELL_EBUSY; + } + + if (!file->file.trunc(size)) { + switch (auto error = fs::g_tls_error) { + case fs::error::ok: + default: + sys_fs.error("sys_fs_ftruncate(): unknown error %s", error); + } + + return CELL_EIO; // ??? + } + + return CELL_OK; +} + +error_code sys_fs_symbolic_link(ppu_thread &, vm::cptr target, + vm::cptr linkpath) { + sys_fs.todo("sys_fs_symbolic_link(target=%s, linkpath=%s)", target, linkpath); + + return CELL_OK; +} + +error_code sys_fs_chmod(ppu_thread &, vm::cptr path, s32 mode) { + sys_fs.todo("sys_fs_chmod(path=%s, mode=%#o)", path, mode); + + const auto [path_error, vpath] = translate_to_str(path); + + if (path_error) { + return {path_error, vpath}; + } + + const std::string local_path = vfs::get(vpath); + + const auto mp = lv2_fs_object::get_mp(vpath); + + if (local_path.empty()) { + return {CELL_ENOTMOUNTED, path}; + } + + if (mp->flags & lv2_mp_flag::read_only) { + return {CELL_EROFS, path}; + } + + std::unique_lock lock(mp->mutex); + + fs::stat_t info{}; + + if (!fs::get_stat(local_path, info)) { + switch (auto error = fs::g_tls_error) { + case fs::error::noent: { + // Try to locate split files + + for (u32 i = 66601; i <= 66699; i++) { + if (!fs::get_stat(fmt::format("%s.%u", local_path, i), info) && + !info.is_directory) { + break; + } + } + + if (fs::get_stat(local_path + ".66600", info) && !info.is_directory) { + break; + } + + return {CELL_ENOENT, path}; + } + default: { + sys_fs.error("sys_fs_chmod(): unknown error %s", error); + return {CELL_EIO, path}; + } + } + } + + return CELL_OK; +} + +error_code sys_fs_chown(ppu_thread &, vm::cptr path, s32 uid, s32 gid) { + sys_fs.todo("sys_fs_chown(path=%s, uid=%d, gid=%d)", path, uid, gid); + + return CELL_OK; +} + +error_code sys_fs_disk_free(ppu_thread &ppu, vm::cptr path, + vm::ptr total_free, vm::ptr avail_free) { + ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); + + sys_fs.warning("sys_fs_disk_free(path=%s total_free=*0x%x avail_free=*0x%x)", + path, total_free, avail_free); + + if (!path) + return CELL_EFAULT; + + if (!path[0]) + return CELL_EINVAL; + + const std::string_view vpath = path.get_ptr(); + + if (vpath == "/"sv) { + return CELL_ENOTSUP; + } + + // It seems max length is 31, and multiple / at the start aren't supported + if (vpath.size() > CELL_FS_MAX_MP_LENGTH) { + return {CELL_ENAMETOOLONG, path}; + } + + if (vpath.find_first_not_of('/') != 1) { + return {CELL_EINVAL, path}; + } + + // Get only device path + const std::string local_path = + vfs::get(vpath.substr(0, vpath.find_first_of('/', 1))); + + if (local_path.empty()) { + return {CELL_EINVAL, path}; + } + + const auto &mp = g_fxo->get().lookup(vpath); + + if (mp->flags & lv2_mp_flag::strict_get_block_size) { + // TODO: + return {CELL_ENOTSUP, path}; + } + + if (mp.read_only) { + // TODO: check /dev_bdvd + ppu.check_state(); + *total_free = 0; + *avail_free = 0; + return CELL_OK; + } + + u64 available = 0; + + // avail_free is the only value used by cellFsGetFreeSize + if (mp == &g_mp_sys_dev_hdd1) { + available = + (1u << 31) - mp->sector_size; // 2GB (TODO: Should be the total size) + } else // if (mp == &g_mp_sys_dev_hdd0) + { + available = (40ull * 1024 * 1024 * 1024 - + mp->sector_size); // Read explanation in cellHddGameCheck + } + + // HACK: Hopefully nothing uses this value or once at max because its hacked + // here: The total size can change based on the size of the directory + const u64 total = available + fs::get_dir_size(local_path, mp->sector_size); + + ppu.check_state(); + *total_free = total; + *avail_free = available; + + return CELL_OK; +} + +error_code sys_fs_utime(ppu_thread &ppu, vm::cptr path, + vm::cptr timep) { + ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); + + sys_fs.warning("sys_fs_utime(path=%s, timep=*0x%x)", path, timep); + sys_fs.warning("** actime=%u, modtime=%u", timep->actime, timep->modtime); + + const auto [path_error, vpath] = translate_to_str(path); + + if (path_error) { + return {path_error, vpath}; + } + + const std::string local_path = vfs::get(vpath); + + const auto &mp = g_fxo->get().lookup(vpath); + + if (mp == &g_mp_sys_dev_root) { + return {CELL_EISDIR, path}; + } + + if (local_path.empty()) { + return {CELL_ENOTMOUNTED, path}; + } + + if (mp.read_only) { + return {CELL_EROFS, path}; + } + + std::lock_guard lock(mp->mutex); + + if (!fs::utime(local_path, timep->actime, timep->modtime)) { + switch (auto error = fs::g_tls_error) { + case fs::error::noent: { + return {mp == &g_mp_sys_dev_hdd1 ? sys_fs.warning : sys_fs.error, + CELL_ENOENT, path}; + } + default: + sys_fs.error("sys_fs_utime(): unknown error %s", error); + } + + return {CELL_EIO, path}; // ??? + } + + return CELL_OK; +} + +error_code sys_fs_acl_read(ppu_thread &, vm::cptr path, + vm::ptr ptr) { + sys_fs.todo("sys_fs_acl_read(path=%s, ptr=*0x%x)", path, ptr); + + return CELL_OK; +} + +error_code sys_fs_acl_write(ppu_thread &, vm::cptr path, + vm::ptr ptr) { + sys_fs.todo("sys_fs_acl_write(path=%s, ptr=*0x%x)", path, ptr); + + return CELL_OK; +} + +error_code sys_fs_lsn_get_cda_size(ppu_thread &, u32 fd, vm::ptr ptr) { + sys_fs.warning("sys_fs_lsn_get_cda_size(fd=%d, ptr=*0x%x)", fd, ptr); + + const auto file = idm::get_unlocked(fd); + + if (!file) { + return CELL_EBADF; + } + + // TODO + *ptr = 0; + return CELL_OK; +} + +error_code sys_fs_lsn_get_cda(ppu_thread &, u32 fd, vm::ptr arg2, + u64 arg3, vm::ptr arg4) { + sys_fs.todo("sys_fs_lsn_get_cda(fd=%d, arg2=*0x%x, arg3=0x%x, arg4=*0x%x)", + fd, arg2, arg3, arg4); + + return CELL_OK; +} + +error_code sys_fs_lsn_lock(ppu_thread &, u32 fd) { + sys_fs.trace("sys_fs_lsn_lock(fd=%d)", fd); + + const auto file = idm::get_unlocked(fd); + + if (!file) { + return CELL_EBADF; + } + + // TODO: seems to do nothing on /dev_hdd0 or /host_root + if (file->mp == &g_mp_sys_dev_hdd0 || + file->mp->flags & lv2_mp_flag::strict_get_block_size) { + return CELL_OK; + } + + std::lock_guard lock(file->mp->mutex); + file->lock.compare_and_swap(0, 1); + return CELL_OK; +} + +error_code sys_fs_lsn_unlock(ppu_thread &, u32 fd) { + sys_fs.trace("sys_fs_lsn_unlock(fd=%d)", fd); + + const auto file = idm::get_unlocked(fd); + + if (!file) { + return CELL_EBADF; + } + + // See sys_fs_lsn_lock + if (file->mp == &g_mp_sys_dev_hdd0 || + file->mp->flags & lv2_mp_flag::strict_get_block_size) { + return CELL_OK; + } + + // Unlock unconditionally + std::lock_guard lock(file->mp->mutex); + file->lock.compare_and_swap(1, 0); + return CELL_OK; +} + +error_code sys_fs_lsn_read(ppu_thread &, u32 fd, vm::cptr ptr, u64 size) { + sys_fs.todo("sys_fs_lsn_read(fd=%d, ptr=*0x%x, size=0x%x)", fd, ptr, size); + + return CELL_OK; +} + +error_code sys_fs_lsn_write(ppu_thread &, u32 fd, vm::cptr ptr, + u64 size) { + sys_fs.todo("sys_fs_lsn_write(fd=%d, ptr=*0x%x, size=0x%x)", fd, ptr, size); + + return CELL_OK; +} + +error_code sys_fs_mapped_allocate(ppu_thread &, u32 fd, u64 size, + vm::pptr out_ptr) { + sys_fs.todo("sys_fs_mapped_allocate(fd=%d, arg2=0x%x, out_ptr=**0x%x)", fd, + size, out_ptr); + + return CELL_OK; +} + +error_code sys_fs_mapped_free(ppu_thread &, u32 fd, vm::ptr ptr) { + sys_fs.todo("sys_fs_mapped_free(fd=%d, ptr=0x%#x)", fd, ptr); + + return CELL_OK; +} + +error_code sys_fs_truncate2(ppu_thread &, u32 fd, u64 size) { + sys_fs.todo("sys_fs_truncate2(fd=%d, size=0x%x)", fd, size); + + return CELL_OK; +} + +error_code sys_fs_get_mount_info_size(ppu_thread &, vm::ptr len) { + sys_fs.warning("sys_fs_get_mount_info_size(len=*0x%x)", len); + + if (!len) { + return CELL_EFAULT; + } + + *len = g_fxo->get().get_all(); + + return CELL_OK; +} + +error_code sys_fs_get_mount_info(ppu_thread &, vm::ptr info, + u64 len, vm::ptr out_len) { + sys_fs.warning("sys_fs_get_mount_info(info=*0x%x, len=0x%x, out_len=*0x%x)", + info, len, out_len); + + if (!info || !out_len) { + return CELL_EFAULT; + } + + *out_len = g_fxo->get().get_all(info.get_ptr(), len); + + return CELL_OK; +} + +error_code sys_fs_newfs(ppu_thread &ppu, vm::cptr dev_name, + vm::cptr file_system, s32 unk1, + vm::cptr str1) { + ppu.state += cpu_flag::wait; + + sys_fs.warning( + "sys_fs_newfs(dev_name=%s, file_system=%s, unk1=0x%x, str1=%s)", dev_name, + file_system, unk1, str1); + + const auto [dev_error, device_name] = translate_to_str(dev_name, false); + + if (dev_error) { + return {dev_error, device_name}; + } + + std::string vfs_path; + const auto mp = lv2_fs_object::get_mp(device_name, &vfs_path); + std::unique_lock lock(mp->mutex, std::defer_lock); + + if (!g_ps3_process_info.has_root_perm() && mp != &g_mp_sys_dev_usb) + return {CELL_EPERM, device_name}; + + if (mp == &g_mp_sys_no_device) + return {CELL_ENXIO, device_name}; + + if (g_fxo->get().is_device_mounted(device_name) || + !lock.try_lock()) + return {CELL_EBUSY, device_name}; + + if (vfs_path.empty()) + return {CELL_ENOTSUP, device_name}; + + if (mp->flags & lv2_mp_flag::read_only) + return {CELL_EROFS, device_name}; + + if (mp == &g_mp_sys_dev_hdd1) { + const std::string_view appname = g_ps3_process_info.get_cellos_appname(); + vfs_path = fmt::format("%s/caches/%s", vfs_path, + appname.substr(0, appname.find_last_of('.'))); + } + + if (!fs::remove_all(vfs_path, false)) { + sys_fs.error("sys_fs_newfs(): Failed to clear \"%s\" at \"%s\"", + device_name, vfs_path); + return {CELL_EIO, vfs_path}; + } + + sys_fs.success("sys_fs_newfs(): Successfully cleared \"%s\" at \"%s\"", + device_name, vfs_path); + return CELL_OK; +} + +error_code sys_fs_mount(ppu_thread &ppu, vm::cptr dev_name, + vm::cptr file_system, vm::cptr path, + s32 unk1, s32 prot, s32 unk2, vm::cptr str1, + u32 str_len) { + ppu.state += cpu_flag::wait; + + sys_fs.warning("sys_fs_mount(dev_name=%s, file_system=%s, path=%s, " + "unk1=0x%x, prot=%d, unk3=0x%x, str1=%s, str_len=%d)", + dev_name, file_system, path, unk1, prot, unk2, str1, str_len); + + const auto [dev_error, device_name] = translate_to_str(dev_name, false); + + if (dev_error) { + return {dev_error, device_name}; + } + + const auto [fs_error, filesystem] = translate_to_str(file_system, false); + + if (fs_error) { + return {fs_error, filesystem}; + } + + const auto [path_error, path_sv] = translate_to_str(path); + + if (path_error) { + return {path_error, path_sv}; + } + + const std::string vpath = lv2_fs_object::get_normalized_path(path_sv); + + std::string vfs_path; + const auto mp = lv2_fs_object::get_mp(device_name, &vfs_path); + std::unique_lock lock(mp->mutex, std::defer_lock); + + if (!g_ps3_process_info.has_root_perm() && mp != &g_mp_sys_dev_usb) + return {CELL_EPERM, device_name}; + + if (mp == &g_mp_sys_no_device) + return {CELL_ENXIO, device_name}; + + if (g_fxo->get().is_device_mounted(device_name) || + !lock.try_lock()) + return {CELL_EBUSY, device_name}; + + if (vfs_path.empty()) + return {CELL_ENOTSUP, device_name}; + + if (vpath.find_first_not_of('/') == umax || !vfs::get(vpath).empty()) + return {CELL_EEXIST, vpath}; + + if (mp == &g_mp_sys_dev_hdd1) { + const std::string_view appname = g_ps3_process_info.get_cellos_appname(); + vfs_path = fmt::format("%s/caches/%s", vfs_path, + appname.substr(0, appname.find_last_of('.'))); + } + + if (!vfs_path.ends_with('/')) + vfs_path += '/'; + + if (!fs::is_dir(vfs_path) && !fs::create_dir(vfs_path)) { + sys_fs.error("Failed to create directory \"%s\"", vfs_path); + return {CELL_EIO, vfs_path}; + } + + const bool is_simplefs = filesystem == "CELL_FS_SIMPLEFS"sv; + + if (is_simplefs) { + vfs_path += "simplefs.tmp"; + if (fs::file simplefs_file; + simplefs_file.open(vfs_path, fs::create + fs::read + fs::write + + fs::trunc + fs::lock)) { + const u64 file_size = mp->sector_size; // One sector's size is enough for + // VSH's simplefs check + simplefs_file.trunc(file_size); + sys_fs.notice("Created a simplefs file at \"%s\"", vfs_path); + } else { + sys_fs.error("Failed to create simplefs file \"%s\"", vfs_path); + return {CELL_EIO, vfs_path}; + } + } + + if (!vfs::mount(vpath, vfs_path, !is_simplefs)) { + if (is_simplefs) { + if (fs::remove_file(vfs_path)) { + sys_fs.notice("Removed simplefs file \"%s\"", vfs_path); + } else { + sys_fs.error("Failed to remove simplefs file \"%s\"", vfs_path); + } + } + + return CELL_EIO; + } + + g_fxo->get().add(vpath, mp, device_name, filesystem, + prot); + + return CELL_OK; +} + +error_code sys_fs_unmount(ppu_thread &ppu, vm::cptr path, s32 unk1, + s32 force) { + ppu.state += cpu_flag::wait; + + sys_fs.warning("sys_fs_unmount(path=%s, unk1=0x%x, force=%d)", path, unk1, + force); + + const auto [path_error, vpath] = translate_to_str(path); + + if (path_error) { + return {path_error, vpath}; + } + + const auto &mp = g_fxo->get().lookup(vpath); + std::unique_lock lock(mp->mutex, std::defer_lock); + + if (!g_ps3_process_info.has_root_perm() && mp != &g_mp_sys_dev_usb) + return {CELL_EPERM, vpath}; + + if (mp == &g_mp_sys_no_device) + return {CELL_EINVAL, vpath}; + + if (mp == &g_mp_sys_dev_root || (!lock.try_lock() && !force)) + return {CELL_EBUSY, vpath}; + + if (!lv2_fs_mount_info_map::vfs_unmount(vpath)) + return {CELL_EIO, vpath}; + + return CELL_OK; +} diff --git a/kernel/cellos/src/sys_game.cpp b/kernel/cellos/src/sys_game.cpp new file mode 100644 index 000000000..1c1064baf --- /dev/null +++ b/kernel/cellos/src/sys_game.cpp @@ -0,0 +1,267 @@ +#include "stdafx.h" + +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/IdManager.h" +#include "Emu/Memory/vm_ptr.h" +#include "Emu/System.h" +#include "Emu/system_utils.hpp" +#include "cellos/sys_process.h" +#include "util/StrUtil.h" +#include "util/Thread.h" +#include "util/sysinfo.hpp" +#include "util/v128.hpp" + +#include "Emu/Cell/timers.hpp" +#include "sys_game.h" + +LOG_CHANNEL(sys_game); + +struct system_sw_version { + system_sw_version() { + f64 version_f = 0; + if (!try_to_float(&version_f, utils::get_firmware_version(), 0.0f, + 99.9999f)) + sys_game.error("Error parsing firmware version"); + version = static_cast(version_f * 10000); + } + + system_sw_version(const system_sw_version &) = delete; + + system_sw_version &operator=(const system_sw_version &) = delete; + + ~system_sw_version() = default; + + atomic_t version; +}; + +struct board_storage { +public: + bool read(u8 *buffer) { + if (!buffer) + return false; + + const auto data = storage.load(); + memcpy(buffer, &data, size); + + return true; + } + + bool write(u8 *buffer) { + if (!buffer) + return false; + + storage.store(read_from_ptr>(buffer)); + written = true; + + return true; + } + + board_storage() { + memset(&storage.raw(), -1, size); + if (fs::file file; file.open(file_path, fs::read)) + file.read(&storage.raw(), std::min(file.size(), size)); + } + + board_storage(const board_storage &) = delete; + + board_storage &operator=(const board_storage &) = delete; + + ~board_storage() { + if (written) { + if (fs::file file; + file.open(file_path, fs::create + fs::write + fs::lock)) { + file.write(&storage.raw(), size); + file.trunc(size); + } + } + } + +private: + atomic_be_t storage; + bool written = false; + const std::string file_path = + rpcs3::utils::get_hdd1_dir() + "/caches/board_storage.bin"; + static constexpr u64 size = sizeof(v128); +}; + +struct watchdog_t { + struct alignas(8) control_t { + bool needs_restart = false; + bool active = false; + char pad[sizeof(u32) - sizeof(bool) * 2]{}; + u32 timeout = 0; + }; + + atomic_t control; + + void operator()() { + u64 start_time = get_system_time(); + u64 old_time = start_time; + u64 current_time = old_time; + + constexpr u64 sleep_time = 50'000; + + while (thread_ctrl::state() != thread_state::aborting) { + if (Emu.GetStatus(false) == system_state::paused) { + start_time += current_time - old_time; + old_time = current_time; + thread_ctrl::wait_for(sleep_time); + current_time = get_system_time(); + continue; + } + + old_time = std::exchange(current_time, get_system_time()); + + const auto old = control + .fetch_op([&](control_t &data) { + if (data.needs_restart) { + data.needs_restart = false; + return true; + } + + return false; + }) + .first; + + if (old.active && old.needs_restart) { + start_time = current_time; + old_time = current_time; + continue; + } + + if (old.active && current_time - start_time >= old.timeout) { + sys_game.success("Watchdog timeout! Restarting the game..."); + + Emu.CallFromMainThread([]() { Emu.Restart(false); }); + + return; + } + + thread_ctrl::wait_for(sleep_time); + } + } + + static constexpr auto thread_name = "LV2 Watchdog Thread"sv; +}; + +void abort_lv2_watchdog() { + if (auto thr = g_fxo->try_get>()) { + sys_game.notice("Aborting %s...", thr->thread_name); + *thr = thread_state::aborting; + } +} + +error_code _sys_game_watchdog_start(u32 timeout) { + sys_game.trace("sys_game_watchdog_start(timeout=%d)", timeout); + + // According to disassembly + timeout *= 1'000'000; + timeout &= -64; + + if (!g_fxo->get>() + .control + .fetch_op([&](watchdog_t::control_t &data) { + if (data.active) { + return false; + } + + data.needs_restart = true; + data.active = true; + data.timeout = timeout; + return true; + }) + .second) { + return CELL_EABORT; + } + + return CELL_OK; +} + +error_code _sys_game_watchdog_stop() { + sys_game.trace("sys_game_watchdog_stop()"); + + g_fxo->get>().control.fetch_op( + [](watchdog_t::control_t &data) { + if (!data.active) { + return false; + } + + data.active = false; + return true; + }); + + return CELL_OK; +} + +error_code _sys_game_watchdog_clear() { + sys_game.trace("sys_game_watchdog_clear()"); + + g_fxo->get>().control.fetch_op( + [](watchdog_t::control_t &data) { + if (!data.active || data.needs_restart) { + return false; + } + + data.needs_restart = true; + return true; + }); + + return CELL_OK; +} + +error_code _sys_game_set_system_sw_version(u64 version) { + sys_game.trace("sys_game_set_system_sw_version(version=%d)", version); + + if (!g_ps3_process_info.has_root_perm()) + return CELL_ENOSYS; + + g_fxo->get().version = version; + + return CELL_OK; +} + +u64 _sys_game_get_system_sw_version() { + sys_game.trace("sys_game_get_system_sw_version()"); + + return g_fxo->get().version; +} + +error_code _sys_game_board_storage_read(vm::ptr buffer, + vm::ptr status) { + sys_game.trace("sys_game_board_storage_read(buffer=*0x%x, status=*0x%x)", + buffer, status); + + if (!buffer || !status) { + return CELL_EFAULT; + } + + *status = g_fxo->get().read(buffer.get_ptr()) ? 0x00 : 0xFF; + + return CELL_OK; +} + +error_code _sys_game_board_storage_write(vm::ptr buffer, + vm::ptr status) { + sys_game.trace("sys_game_board_storage_write(buffer=*0x%x, status=*0x%x)", + buffer, status); + + if (!buffer || !status) { + return CELL_EFAULT; + } + + *status = g_fxo->get().write(buffer.get_ptr()) ? 0x00 : 0xFF; + + return CELL_OK; +} + +error_code _sys_game_get_rtc_status(vm::ptr status) { + sys_game.trace("sys_game_get_rtc_status(status=*0x%x)", status); + + if (!status) { + return CELL_EFAULT; + } + + *status = 0; + + return CELL_OK; +} diff --git a/kernel/cellos/src/sys_gamepad.cpp b/kernel/cellos/src/sys_gamepad.cpp new file mode 100644 index 000000000..1fa14a3e5 --- /dev/null +++ b/kernel/cellos/src/sys_gamepad.cpp @@ -0,0 +1,102 @@ +#include "stdafx.h" + +#include "Emu/Cell/ErrorCodes.h" +#include "sys_gamepad.h" + +LOG_CHANNEL(sys_gamepad); + +u32 sys_gamepad_ycon_initalize(vm::ptr in, vm::ptr out) { + sys_gamepad.todo("sys_gamepad_ycon_initalize(in=%d, out=%d) -> CELL_OK", in, + out); + return CELL_OK; +} + +u32 sys_gamepad_ycon_finalize(vm::ptr in, vm::ptr out) { + sys_gamepad.todo("sys_gamepad_ycon_finalize(in=%d, out=%d) -> CELL_OK", in, + out); + return CELL_OK; +} + +u32 sys_gamepad_ycon_has_input_ownership(vm::ptr in, vm::ptr out) { + sys_gamepad.todo( + "sys_gamepad_ycon_has_input_ownership(in=%d, out=%d) -> CELL_OK", in, + out); + return CELL_OK; +} + +u32 sys_gamepad_ycon_enumerate_device(vm::ptr in, vm::ptr out) { + sys_gamepad.todo( + "sys_gamepad_ycon_enumerate_device(in=%d, out=%d) -> CELL_OK", in, out); + return CELL_OK; +} + +u32 sys_gamepad_ycon_get_device_info(vm::ptr in, vm::ptr out) { + sys_gamepad.todo("sys_gamepad_ycon_get_device_info(in=%d, out=%d) -> CELL_OK", + in, out); + return CELL_OK; +} + +u32 sys_gamepad_ycon_read_raw_report(vm::ptr in, vm::ptr out) { + sys_gamepad.todo("sys_gamepad_ycon_read_raw_report(in=%d, out=%d) -> CELL_OK", + in, out); + return CELL_OK; +} + +u32 sys_gamepad_ycon_write_raw_report(vm::ptr in, vm::ptr out) { + sys_gamepad.todo( + "sys_gamepad_ycon_write_raw_report(in=%d, out=%d) -> CELL_OK", in, out); + return CELL_OK; +} + +u32 sys_gamepad_ycon_get_feature(vm::ptr in, vm::ptr out) { + sys_gamepad.todo("sys_gamepad_ycon_get_feature(in=%d, out=%d) -> CELL_OK", in, + out); + return CELL_OK; +} + +u32 sys_gamepad_ycon_set_feature(vm::ptr in, vm::ptr out) { + sys_gamepad.todo("sys_gamepad_ycon_set_feature(in=%d, out=%d) -> CELL_OK", in, + out); + return CELL_OK; +} + +u32 sys_gamepad_ycon_is_gem(vm::ptr in, vm::ptr out) { + sys_gamepad.todo("sys_gamepad_ycon_is_gem(in=%d, out=%d) -> CELL_OK", in, + out); + return CELL_OK; +} + +// syscall(621,packet_id,u8 *in,u8 *out) +// Talk:LV2_Functions_and_Syscalls#Syscall_621_.280x26D.29 gamepad_if usage +u32 sys_gamepad_ycon_if(u8 packet_id, vm::ptr in, vm::ptr out) { + + switch (packet_id) { + case 0: + return sys_gamepad_ycon_initalize(in, out); + case 1: + return sys_gamepad_ycon_finalize(in, out); + case 2: + return sys_gamepad_ycon_has_input_ownership(in, out); + case 3: + return sys_gamepad_ycon_enumerate_device(in, out); + case 4: + return sys_gamepad_ycon_get_device_info(in, out); + case 5: + return sys_gamepad_ycon_read_raw_report(in, out); + case 6: + return sys_gamepad_ycon_write_raw_report(in, out); + case 7: + return sys_gamepad_ycon_get_feature(in, out); + case 8: + return sys_gamepad_ycon_set_feature(in, out); + case 9: + return sys_gamepad_ycon_is_gem(in, out); + default: + sys_gamepad.error( + "sys_gamepad_ycon_if(packet_id=*%d, in=%d, out=%d), unknown packet id", + packet_id, in, out); + break; + } + + return CELL_OK; +} diff --git a/kernel/cellos/src/sys_gpio.cpp b/kernel/cellos/src/sys_gpio.cpp new file mode 100644 index 000000000..1c799532f --- /dev/null +++ b/kernel/cellos/src/sys_gpio.cpp @@ -0,0 +1,41 @@ +#include "stdafx.h" + +#include "sys_gpio.h" + +#include "Emu/Cell/ErrorCodes.h" + +LOG_CHANNEL(sys_gpio); + +error_code sys_gpio_get(u64 device_id, vm::ptr value) { + sys_gpio.trace("sys_gpio_get(device_id=0x%llx, value=*0x%x)", device_id, + value); + + if (device_id != SYS_GPIO_LED_DEVICE_ID && + device_id != SYS_GPIO_DIP_SWITCH_DEVICE_ID) { + return CELL_ESRCH; + } + + // Retail consoles dont have LEDs or DIPs switches, hence always sets 0 in + // paramenter + if (!value.try_write(0)) { + return CELL_EFAULT; + } + + return CELL_OK; +} + +error_code sys_gpio_set(u64 device_id, u64 mask, u64 value) { + sys_gpio.trace("sys_gpio_set(device_id=0x%llx, mask=0x%llx, value=0x%llx)", + device_id, mask, value); + + // Retail consoles dont have LEDs or DIPs switches, hence the syscall can't + // modify devices's value + switch (device_id) { + case SYS_GPIO_LED_DEVICE_ID: + return CELL_OK; + case SYS_GPIO_DIP_SWITCH_DEVICE_ID: + return CELL_EINVAL; + } + + return CELL_ESRCH; +} diff --git a/kernel/cellos/src/sys_hid.cpp b/kernel/cellos/src/sys_hid.cpp new file mode 100644 index 000000000..59b115a4a --- /dev/null +++ b/kernel/cellos/src/sys_hid.cpp @@ -0,0 +1,193 @@ +#include "stdafx.h" + +#include "sys_hid.h" + +#include "Emu/Memory/vm_var.h" + +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/Cell/PPUThread.h" +#include "rpcsx/fw/ps3/cellPad.h" + +#include "sys_process.h" + +LOG_CHANNEL(sys_hid); + +error_code sys_hid_manager_open(ppu_thread &ppu, u64 device_type, u64 port_no, + vm::ptr handle) { + sys_hid.todo("sys_hid_manager_open(device_type=0x%llx, port_no=0x%llx, " + "handle=*0x%llx)", + device_type, port_no, handle); + + // device type == 1 = pad, 2 = kb, 3 = mouse + if (device_type > 3) { + return CELL_EINVAL; + } + + if (!handle) { + return CELL_EFAULT; + } + + // 'handle' starts at 0x100 in realhw, and increments every time + // sys_hid_manager_open is called however, sometimes the handle is reused when + // opening sys_hid_manager again (even when the previous one hasn't been + // closed yet) - maybe when processes/threads get killed/finish they also + // release their handles? + static u32 ctr = 0x100; + *handle = ctr++; + + if (device_type == 1) { + cellPadInit(ppu, 7); + cellPadSetPortSetting(::narrow(port_no) /* 0 */, + CELL_PAD_SETTING_LDD | CELL_PAD_SETTING_PRESS_ON | + CELL_PAD_SETTING_SENSOR_ON); + } + + return CELL_OK; +} + +error_code sys_hid_manager_ioctl(u32 hid_handle, u32 pkg_id, vm::ptr buf, + u64 buf_size) { + sys_hid.todo("sys_hid_manager_ioctl(hid_handle=0x%x, pkg_id=0x%llx, " + "buf=*0x%x, buf_size=0x%llx)", + hid_handle, pkg_id, buf, buf_size); + + // clang-format off + // From realhw syscall dump when vsh boots + // SC count | handle | pkg_id | *buf (in) | *buf (out) | size -> ret + // ---------|--------|--------|---------------------------------------------------------------------------|---------------------------------------------------------------------------|------------ + // 28893 | 0x101 | 0x2 | 000000000000000000000000000000000000000000 | 054c02680102020000000000000008035000001c1f | 21 -> 0 + // 28894 | 0x101 | 0x3 | 00000000 | 00000000 | 4 -> 0 + // 28895 | 0x101 | 0x5 | 00000000 | 00000000 | 4 -> 0 + // 28896 | 0x101 | 0x68 | 01000000d0031cb020169e502006b7f80000000000606098000000000000000000000000d | 01000000d0031cb020169e502006b7f80000000000606098000000000000000000000000d | 64 -> 0 + // | | | 0031c90000000002006bac400000000d0031cb0000000002006b4d0 | 0031c90000000002006bac400000000d0031cb0000000002006b4d0 | + // 28898 | 0x102 | 0x2 | 000000000000000000000000000000000000000000 | 054c02680102020000000000000008035000001c1f | 21 -> 0 + // 28901 | 0x100 | 0x64 | 00000001 | 00000001 | 4 -> 0xffffffff80010002 # x3::hidportassign + // 2890 | 0x100 | 0x65 | 6b49d200 | 6b49d200 | 4 -> 0xffffffff80010002 # x3::hidportassign + // 28903 | 0x100 | 0x66 | 00000001 | 00000001 | 4 -> 0 # x3::hidportassign + // 28904 | 0x100 | 0x0 | 00000001000000ff000000ff000000ff000000ff000000010000000100000001000000010 | 00000001000000ff000000ff000000ff000000ff000000010000000100000001000000010 | 68 -> 0 # x3::hidportassign + // | | | 000000000000000000000000000000000000001000000010000000100000001 | 000000000000000000000000000000000000001000000010000000100000001 | + // 28907 | 0x101 | 0x3 | 00000001 | 00000001 | 4 -> 0 + // 28908 | 0x101 | 0x5 | 00000001 | 00000001 | 4 -> 0 + // 29404 | 0x100 | 0x4 | 00 | ee | 1 -> 0 + // *** repeats 30600, 31838, 33034, 34233, 35075 (35075 is x3::hidportassign) *** + // 35076 | 0x100 | 0x0 | 00000001000000ff000000ff000000ff000000ff000000320000003200000032000000320 | 00000001000000ff000000ff000000ff000000ff000000320000003200000032000000320 | 68 -> 0 + // | | | 000003200000032000000320000003200002710000027100000271000002710 | 000003200000032000000320000003200002710000027100000271000002710 | + // *** more 0x4 that have buf(in)=00 and buf(out)=ee *** + // clang-format on + + if (pkg_id == 2) { + // Return what realhw seems to return + // TODO: Figure out what this corresponds to + auto info = vm::static_ptr_cast(buf); + info->vid = 0x054C; + info->pid = 0x0268; + + u8 realhw[17] = {0x01, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x03, 0x50, 0x00, 0x00, 0x1c, 0x1f}; + memcpy(info->unk, &realhw, 17); + } else if (pkg_id == 5) { + auto info = vm::static_ptr_cast(buf); + info->vid = 0x054C; + info->pid = 0x0268; + } + // pkg_id == 6 == setpressmode? + else if (pkg_id == 0x68) { + [[maybe_unused]] auto info = vm::static_ptr_cast(buf); + // info->unk2 = 0; + } + + return CELL_OK; +} + +error_code sys_hid_manager_check_focus() { + // spammy sys_hid.todo("sys_hid_manager_check_focus()"); + + return not_an_error(1); +} + +error_code sys_hid_manager_513(u64 a1, u64 a2, vm::ptr buf, + u64 buf_size) { + sys_hid.todo("sys_hid_manager_513(%llx, %llx, buf=%llx, buf_size=%llx)", a1, + a2, buf, buf_size); + + return CELL_OK; +} + +error_code sys_hid_manager_514(u32 pkg_id, vm::ptr buf, u64 buf_size) { + if (pkg_id == 0xE) { + sys_hid.trace( + "sys_hid_manager_514(pkg_id=0x%x, buf=*0x%x, buf_size=0x%llx)", pkg_id, + buf, buf_size); + } else { + sys_hid.todo("sys_hid_manager_514(pkg_id=0x%x, buf=*0x%x, buf_size=0x%llx)", + pkg_id, buf, buf_size); + } + + if (pkg_id == 0xE) { + // buf holds device_type + // auto device_type = vm::static_ptr_cast(buf); + // spammy sys_hid.todo("device_type: 0x%x", device_type[0]); + + // return 1 or 0? look like almost like another check_focus type check, + // returning 0 looks to keep system focus + } else if (pkg_id == 0xD) { + auto inf = vm::static_ptr_cast(buf); + // unk1 = (pad# << 24) | pad# | 0x100 + // return value doesn't seem to be used again + sys_hid.todo("unk1: 0x%x, unk2:0x%x", inf->unk1, inf->unk2); + } + + return CELL_OK; +} + +error_code sys_hid_manager_is_process_permission_root(u32 pid) { + sys_hid.todo("sys_hid_manager_is_process_permission_root(pid=0x%x)", pid); + + return not_an_error(g_ps3_process_info.has_root_perm()); +} + +error_code sys_hid_manager_add_hot_key_observer(u32 event_queue, + vm::ptr unk) { + sys_hid.todo( + "sys_hid_manager_add_hot_key_observer(event_queue=0x%x, unk=*0x%x)", + event_queue, unk); + + return CELL_OK; +} + +error_code sys_hid_manager_read(u32 handle, u32 pkg_id, vm::ptr buf, + u64 buf_size) { + if (!buf) { + return CELL_EFAULT; + } + + (pkg_id == 2 || pkg_id == 0x81 ? sys_hid.trace : sys_hid.todo)( + "sys_hid_manager_read(handle=0x%x, pkg_id=0x%x, buf=*0x%x, " + "buf_size=0x%llx)", + handle, pkg_id, buf, buf_size); + + if (pkg_id == 2) { + // cellPadGetData + // it returns just button array from 'CellPadData' + // auto data = vm::static_ptr_cast(buf); + // todo: use handle and dont call cellpad here + vm::var tmpData; + if ((cellPadGetData(0, +tmpData) == CELL_OK) && tmpData->len > 0) { + u64 cpySize = std::min(static_cast(tmpData->len) * sizeof(u16), + buf_size * sizeof(u16)); + memcpy(buf.get_ptr(), &tmpData->button, cpySize); + return not_an_error(cpySize); + } + } else if (pkg_id == 0x81) { + // cellPadGetDataExtra? + vm::var tmpData; + if ((cellPadGetData(0, +tmpData) == CELL_OK) && tmpData->len > 0) { + u64 cpySize = std::min(static_cast(tmpData->len) * sizeof(u16), + buf_size * sizeof(u16)); + memcpy(buf.get_ptr(), &tmpData->button, cpySize); + return not_an_error(cpySize / 2); + } + } + + return CELL_OK; +} diff --git a/kernel/cellos/src/sys_interrupt.cpp b/kernel/cellos/src/sys_interrupt.cpp new file mode 100644 index 000000000..74957da07 --- /dev/null +++ b/kernel/cellos/src/sys_interrupt.cpp @@ -0,0 +1,262 @@ +#include "stdafx.h" + +#include "sys_interrupt.h" + +#include "Emu/IdManager.h" +#include "Emu/System.h" + +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/Cell/PPUOpcodes.h" +#include "Emu/Cell/PPUThread.h" +#include "Emu/Cell/SPUThread.h" + +LOG_CHANNEL(sys_interrupt); + +lv2_int_tag::lv2_int_tag() noexcept : lv2_obj(1), id(idm::last_id()) {} + +lv2_int_tag::lv2_int_tag(utils::serial &ar) noexcept + : lv2_obj(1), id(idm::last_id()), handler([&]() { + const u32 id = ar; + + auto ptr = idm::get_unlocked(id); + + if (!ptr && id) { + Emu.PostponeInitCode([id, &handler = this->handler]() { + handler = ensure(idm::get_unlocked(id)); + }); + } + + return ptr; + }()) {} + +void lv2_int_tag::save(utils::serial &ar) { + ar(lv2_obj::check(handler) ? handler->id : 0); +} + +lv2_int_serv::lv2_int_serv(shared_ptr> thread, + u64 arg1, u64 arg2) noexcept + : lv2_obj(1), id(idm::last_id()), thread(thread), arg1(arg1), arg2(arg2) {} + +lv2_int_serv::lv2_int_serv(utils::serial &ar) noexcept + : lv2_obj(1), id(idm::last_id()), + thread(idm::get_unlocked>(ar)), arg1(ar), + arg2(ar) {} + +void lv2_int_serv::save(utils::serial &ar) { + ar(thread && idm::check_unlocked>(thread->id) + ? thread->id + : 0, + arg1, arg2); +} + +void ppu_interrupt_thread_entry(ppu_thread &, ppu_opcode_t, be_t *, + struct ppu_intrp_func *); + +void lv2_int_serv::exec() const { + thread->cmd_list({{ppu_cmd::reset_stack, 0}, + {ppu_cmd::set_args, 2}, + arg1, + arg2, + {ppu_cmd::entry_call, 0}, + {ppu_cmd::sleep, 0}, + {ppu_cmd::ptr_call, 0}, + std::bit_cast(&ppu_interrupt_thread_entry)}); +} + +void ppu_thread_exit(ppu_thread &, ppu_opcode_t, be_t *, + struct ppu_intrp_func *); + +void lv2_int_serv::join() const { + thread->cmd_list( + {{ppu_cmd::ptr_call, 0}, std::bit_cast(&ppu_thread_exit)}); + + thread->cmd_notify.store(1); + thread->cmd_notify.notify_one(); + (*thread)(); + + idm::remove_verify>(thread->id, thread); +} + +error_code sys_interrupt_tag_destroy(ppu_thread &ppu, u32 intrtag) { + ppu.state += cpu_flag::wait; + + sys_interrupt.warning("sys_interrupt_tag_destroy(intrtag=0x%x)", intrtag); + + const auto tag = idm::withdraw( + intrtag, [](lv2_int_tag &tag) -> CellError { + if (lv2_obj::check(tag.handler)) { + return CELL_EBUSY; + } + + tag.exists.release(0); + return {}; + }); + + if (!tag) { + return CELL_ESRCH; + } + + if (tag.ret) { + return tag.ret; + } + + return CELL_OK; +} + +error_code _sys_interrupt_thread_establish(ppu_thread &ppu, vm::ptr ih, + u32 intrtag, u32 intrthread, + u64 arg1, u64 arg2) { + ppu.state += cpu_flag::wait; + + sys_interrupt.warning( + "_sys_interrupt_thread_establish(ih=*0x%x, intrtag=0x%x, " + "intrthread=0x%x, arg1=0x%llx, arg2=0x%llx)", + ih, intrtag, intrthread, arg1, arg2); + + CellError error = CELL_EAGAIN; + + const u32 id = idm::import ([&]() { + shared_ptr result; + + // Get interrupt tag + const auto tag = idm::check_unlocked(intrtag); + + if (!tag) { + error = CELL_ESRCH; + return result; + } + + // Get interrupt thread + const auto it = idm::get_unlocked>(intrthread); + + if (!it) { + error = CELL_ESRCH; + return result; + } + + // If interrupt thread is running, it's already established on another + // interrupt tag + if (cpu_flag::stop - it->state) { + error = CELL_EAGAIN; + return result; + } + + // It's unclear if multiple handlers can be established on single interrupt + // tag + if (lv2_obj::check(tag->handler)) { + error = CELL_ESTAT; + return result; + } + + result = make_shared(it, arg1, arg2); + tag->handler = result; + + it->cmd_list({{ppu_cmd::ptr_call, 0}, + std::bit_cast(&ppu_interrupt_thread_entry)}); + + it->state -= cpu_flag::stop; + it->state.notify_one(); + + return result; + }); + + if (id) { + ppu.check_state(); + *ih = id; + return CELL_OK; + } + + return error; +} + +error_code _sys_interrupt_thread_disestablish(ppu_thread &ppu, u32 ih, + vm::ptr r13) { + ppu.state += cpu_flag::wait; + + sys_interrupt.warning( + "_sys_interrupt_thread_disestablish(ih=0x%x, r13=*0x%x)", ih, r13); + + const auto handler = idm::withdraw( + ih, [](lv2_obj &obj) { obj.exists.release(0); }); + + if (!handler) { + if (const auto thread = idm::withdraw>(ih)) { + *r13 = thread->gpr[13]; + + // It is detached from IDM now so join must be done explicitly now + *thread = thread_state::finished; + return CELL_OK; + } + + return CELL_ESRCH; + } + + lv2_obj::sleep(ppu); + + // Wait for sys_interrupt_thread_eoi() and destroy interrupt thread + handler->join(); + + // Save TLS base + *r13 = handler->thread->gpr[13]; + + return CELL_OK; +} + +void sys_interrupt_thread_eoi(ppu_thread &ppu) { + ppu.state += cpu_flag::wait; + + sys_interrupt.trace("sys_interrupt_thread_eoi()"); + + ppu.state += cpu_flag::ret; + + lv2_obj::sleep(ppu); + + ppu.interrupt_thread_executing = false; +} + +void ppu_interrupt_thread_entry(ppu_thread &ppu, ppu_opcode_t, be_t *, + struct ppu_intrp_func *) { + while (true) { + shared_ptr serv = null_ptr; + + // Loop endlessly trying to invoke an interrupt if required + idm::select>([&](u32, spu_thread &spu) { + if (spu.get_type() != spu_type::threaded) { + auto &ctrl = spu.int_ctrl[2]; + + if (lv2_obj::check(ctrl.tag)) { + auto &handler = ctrl.tag->handler; + + if (lv2_obj::check(handler)) { + if (handler->thread.get() == &ppu) { + if (spu.ch_out_intr_mbox.get_count() && + ctrl.mask & SPU_INT2_STAT_MAILBOX_INT) { + ctrl.stat |= SPU_INT2_STAT_MAILBOX_INT; + } + + if (ctrl.mask & ctrl.stat) { + ensure(!serv); + serv = handler; + } + } + } + } + } + }); + + if (serv) { + // Queue interrupt, after the interrupt has finished the PPU returns to + // this loop + serv->exec(); + return; + } + + const auto state = +ppu.state; + + if (::is_stopped(state) || ppu.cmd_notify.exchange(0)) { + return; + } + + thread_ctrl::wait_on(ppu.cmd_notify, 0); + } +} diff --git a/kernel/cellos/src/sys_io.cpp b/kernel/cellos/src/sys_io.cpp new file mode 100644 index 000000000..746ce61b5 --- /dev/null +++ b/kernel/cellos/src/sys_io.cpp @@ -0,0 +1,70 @@ +#include "stdafx.h" + +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/IdManager.h" +#include "Emu/Memory/vm.h" + +#include "sys_io.h" + +LOG_CHANNEL(sys_io); + +error_code sys_io_buffer_create(u32 block_count, u32 block_size, u32 blocks, + u32 unk1, vm::ptr handle) { + sys_io.todo("sys_io_buffer_create(block_count=0x%x, block_size=0x%x, " + "blocks=0x%x, unk1=0x%x, handle=*0x%x)", + block_count, block_size, blocks, unk1, handle); + + if (!handle) { + return CELL_EFAULT; + } + + if (auto io = idm::make(block_count, block_size, blocks, unk1)) { + *handle = io; + return CELL_OK; + } + + return CELL_ESRCH; +} + +error_code sys_io_buffer_destroy(u32 handle) { + sys_io.todo("sys_io_buffer_destroy(handle=0x%x)", handle); + + idm::remove(handle); + + return CELL_OK; +} + +error_code sys_io_buffer_allocate(u32 handle, vm::ptr block) { + sys_io.todo("sys_io_buffer_allocate(handle=0x%x, block=*0x%x)", handle, + block); + + if (!block) { + return CELL_EFAULT; + } + + if (auto io = idm::get_unlocked(handle)) { + // no idea what we actually need to allocate + if (u32 addr = vm::alloc(io->block_count * io->block_size, vm::main)) { + *block = addr; + return CELL_OK; + } + + return CELL_ENOMEM; + } + + return CELL_ESRCH; +} + +error_code sys_io_buffer_free(u32 handle, u32 block) { + sys_io.todo("sys_io_buffer_free(handle=0x%x, block=0x%x)", handle, block); + + const auto io = idm::get_unlocked(handle); + + if (!io) { + return CELL_ESRCH; + } + + vm::dealloc(block); + + return CELL_OK; +} diff --git a/kernel/cellos/src/sys_lwcond.cpp b/kernel/cellos/src/sys_lwcond.cpp new file mode 100644 index 000000000..b4873020f --- /dev/null +++ b/kernel/cellos/src/sys_lwcond.cpp @@ -0,0 +1,565 @@ +#include "stdafx.h" + +#include "sys_lwcond.h" + +#include "Emu/IdManager.h" + +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/Cell/PPUThread.h" +#include "sys_lwmutex.h" + +#include "util/asm.hpp" + +LOG_CHANNEL(sys_lwcond); + +lv2_lwcond::lv2_lwcond(utils::serial &ar) + : name(ar.pop>()), lwid(ar), protocol(ar), + control(ar.pop()) {} + +void lv2_lwcond::save(utils::serial &ar) { + USING_SERIALIZATION_VERSION(lv2_sync); + ar(name, lwid, protocol, control); +} + +error_code _sys_lwcond_create(ppu_thread &ppu, vm::ptr lwcond_id, + u32 lwmutex_id, vm::ptr control, + u64 name) { + ppu.state += cpu_flag::wait; + + sys_lwcond.trace(u8"_sys_lwcond_create(lwcond_id=*0x%x, lwmutex_id=0x%x, " + u8"control=*0x%x, name=0x%llx (“%s”))", + lwcond_id, lwmutex_id, control, name, + lv2_obj::name_64{std::bit_cast>(name)}); + + u32 protocol; + + // Extract protocol from lwmutex + if (!idm::check( + lwmutex_id, + [&protocol](lv2_lwmutex &mutex) { protocol = mutex.protocol; })) { + return CELL_ESRCH; + } + + if (protocol == SYS_SYNC_RETRY) { + // Lwcond can't have SYS_SYNC_RETRY protocol + protocol = SYS_SYNC_PRIORITY; + } + + if (const u32 id = + idm::make(name, lwmutex_id, protocol, control)) { + ppu.check_state(); + *lwcond_id = id; + return CELL_OK; + } + + return CELL_EAGAIN; +} + +error_code _sys_lwcond_destroy(ppu_thread &ppu, u32 lwcond_id) { + ppu.state += cpu_flag::wait; + + sys_lwcond.trace("_sys_lwcond_destroy(lwcond_id=0x%x)", lwcond_id); + + shared_ptr _cond; + + while (true) { + s32 old_val = 0; + + auto [ptr, ret] = idm::withdraw( + lwcond_id, [&](lv2_lwcond &cond) -> CellError { + // Ignore check on first iteration + if (_cond && std::addressof(cond) != _cond.get()) { + // Other thread has destroyed the lwcond earlier + return CELL_ESRCH; + } + + std::lock_guard lock(cond.mutex); + + if (atomic_storage::load(cond.sq)) { + return CELL_EBUSY; + } + + old_val = cond.lwmutex_waiters.or_fetch(smin); + + if (old_val != smin) { + // De-schedule if waiters were found + lv2_obj::sleep(ppu); + + // Repeat loop: there are lwmutex waiters inside + // _sys_lwcond_queue_wait + return CELL_EAGAIN; + } + + return {}; + }); + + if (!ptr) { + return CELL_ESRCH; + } + + if (ret) { + if (ret != CELL_EAGAIN) { + return ret; + } + } else { + break; + } + + _cond = std::move(ptr); + + // Wait for all lwcond waiters to quit + while (old_val + 0u > 1u << 31) { + thread_ctrl::wait_on(_cond->lwmutex_waiters, old_val); + + if (ppu.is_stopped()) { + ppu.state += cpu_flag::again; + return {}; + } + + old_val = _cond->lwmutex_waiters; + } + + // Wake up from sleep + ppu.check_state(); + } + + return CELL_OK; +} + +error_code _sys_lwcond_signal(ppu_thread &ppu, u32 lwcond_id, u32 lwmutex_id, + u64 ppu_thread_id, u32 mode) { + ppu.state += cpu_flag::wait; + + sys_lwcond.trace("_sys_lwcond_signal(lwcond_id=0x%x, lwmutex_id=0x%x, " + "ppu_thread_id=0x%llx, mode=%d)", + lwcond_id, lwmutex_id, ppu_thread_id, mode); + + // Mode 1: lwmutex was initially owned by the calling thread + // Mode 2: lwmutex was not owned by the calling thread and waiter hasn't been + // increased Mode 3: lwmutex was forcefully owned by the calling thread + + if (mode < 1 || mode > 3) { + fmt::throw_exception("Unknown mode (%d)", mode); + } + + while (true) { + if (ppu.test_stopped()) { + ppu.state += cpu_flag::again; + return {}; + } + + bool finished = true; + + ppu.state += cpu_flag::wait; + + const auto cond = idm::check( + lwcond_id, + [&, notify = lv2_obj::notify_all_t()](lv2_lwcond &cond) -> int { + ppu_thread *cpu = nullptr; + + if (ppu_thread_id != u32{umax}) { + cpu = idm::check_unlocked>( + static_cast(ppu_thread_id)); + + if (!cpu) { + return -1; + } + } + + lv2_lwmutex *mutex = nullptr; + + if (mode != 2) { + mutex = idm::check_unlocked(lwmutex_id); + + if (!mutex) { + return -1; + } + } + + if (atomic_storage::load(cond.sq)) { + std::lock_guard lock(cond.mutex); + + if (ppu.state & cpu_flag::suspend) { + // Test if another signal caused the current thread to be + // suspended, in which case it needs to wait until the thread + // wakes up (otherwise the signal may cause unexpected results) + finished = false; + return 0; + } + + if (cpu) { + if (static_cast(cpu)->state & cpu_flag::again) { + ppu.state += cpu_flag::again; + return 0; + } + } + + auto result = + cpu ? cond.unqueue(cond.sq, cpu) + : cond.schedule(cond.sq, cond.protocol); + + if (result) { + if (static_cast(result)->state & cpu_flag::again) { + ppu.state += cpu_flag::again; + return 0; + } + + if (mode == 2) { + static_cast(result)->gpr[3] = CELL_EBUSY; + } else if (mode == 3 && mutex->load_sq()) [[unlikely]] { + std::lock_guard lock(mutex->mutex); + + // Respect ordering of the sleep queue + mutex->try_own(result, true); + auto result2 = mutex->reown(); + + if (result2->state & cpu_flag::again) { + ppu.state += cpu_flag::again; + return 0; + } + + if (result2 != result) { + cond.awake(result2); + result = nullptr; + } + } else if (mode == 1) { + mutex->try_own(result, true); + result = nullptr; + } + + if (result) { + cond.awake(result); + } + + return 1; + } + } else { + cond.mutex.lock_unlock(); + + if (ppu.state & cpu_flag::suspend) { + finished = false; + return 0; + } + } + + return 0; + }); + + if (!finished) { + continue; + } + + if (!cond || cond.ret == -1) { + return CELL_ESRCH; + } + + if (!cond.ret) { + if (ppu_thread_id == u32{umax}) { + if (mode == 3) { + return not_an_error(CELL_ENOENT); + } else if (mode == 2) { + return CELL_OK; + } + } + + return not_an_error(CELL_EPERM); + } + + return CELL_OK; + } +} + +error_code _sys_lwcond_signal_all(ppu_thread &ppu, u32 lwcond_id, + u32 lwmutex_id, u32 mode) { + ppu.state += cpu_flag::wait; + + sys_lwcond.trace( + "_sys_lwcond_signal_all(lwcond_id=0x%x, lwmutex_id=0x%x, mode=%d)", + lwcond_id, lwmutex_id, mode); + + // Mode 1: lwmutex was initially owned by the calling thread + // Mode 2: lwmutex was not owned by the calling thread and waiter hasn't been + // increased + + if (mode < 1 || mode > 2) { + fmt::throw_exception("Unknown mode (%d)", mode); + } + + while (true) { + if (ppu.test_stopped()) { + ppu.state += cpu_flag::again; + return {}; + } + + bool finished = true; + + ppu.state += cpu_flag::wait; + + const auto cond = idm::check( + lwcond_id, + [&, notify = lv2_obj::notify_all_t()](lv2_lwcond &cond) -> int { + lv2_lwmutex *mutex{}; + + if (mode != 2) { + mutex = idm::check_unlocked(lwmutex_id); + + if (!mutex) { + return -1; + } + } + + if (atomic_storage::load(cond.sq)) { + std::lock_guard lock(cond.mutex); + + if (ppu.state & cpu_flag::suspend) { + // Test if another signal caused the current thread to be + // suspended, in which case it needs to wait until the thread + // wakes up (otherwise the signal may cause unexpected results) + finished = false; + return 0; + } + + u32 result = 0; + + for (auto cpu = +cond.sq; cpu; cpu = cpu->next_cpu) { + if (cpu->state & cpu_flag::again) { + ppu.state += cpu_flag::again; + return 0; + } + } + + auto sq = cond.sq; + atomic_storage::release(cond.sq, nullptr); + + while (const auto cpu = + cond.schedule(sq, cond.protocol)) { + if (mode == 2) { + static_cast(cpu)->gpr[3] = CELL_EBUSY; + } + + if (mode == 1) { + mutex->try_own(cpu, true); + } else { + lv2_obj::append(cpu); + } + + result++; + } + + if (result && mode == 2) { + lv2_obj::awake_all(); + } + + return result; + } else { + cond.mutex.lock_unlock(); + + if (ppu.state & cpu_flag::suspend) { + finished = false; + return 0; + } + } + + return 0; + }); + + if (!finished) { + continue; + } + + if (!cond || cond.ret == -1) { + return CELL_ESRCH; + } + + if (mode == 1) { + // Mode 1: return the amount of threads (TODO) + return not_an_error(cond.ret); + } + + return CELL_OK; + } +} + +error_code _sys_lwcond_queue_wait(ppu_thread &ppu, u32 lwcond_id, + u32 lwmutex_id, u64 timeout) { + ppu.state += cpu_flag::wait; + + sys_lwcond.trace( + "_sys_lwcond_queue_wait(lwcond_id=0x%x, lwmutex_id=0x%x, timeout=0x%llx)", + lwcond_id, lwmutex_id, timeout); + + ppu.gpr[3] = CELL_OK; + + shared_ptr mutex; + + auto &sstate = *ppu.optional_savestate_state; + + const auto cond = idm::get( + lwcond_id, [&, notify = lv2_obj::notify_all_t()](lv2_lwcond &cond) { + mutex = idm::get_unlocked(lwmutex_id); + + if (!mutex) { + return; + } + + // Increment lwmutex's lwcond's waiters count + mutex->lwcond_waiters++; + + lv2_obj::prepare_for_sleep(ppu); + + std::lock_guard lock(cond.mutex); + + cond.lwmutex_waiters++; + + const bool mutex_sleep = sstate.try_read().second; + sstate.clear(); + + if (mutex_sleep) { + // Special: loading state from the point of waiting on lwmutex sleep + // queue + mutex->try_own(&ppu, true); + } else { + // Add a waiter + lv2_obj::emplace(cond.sq, &ppu); + } + + if (!ppu.loaded_from_savestate && !mutex->try_unlock(false)) { + std::lock_guard lock2(mutex->mutex); + + // Process lwmutex sleep queue + if (const auto cpu = mutex->reown()) { + if (static_cast(cpu)->state & cpu_flag::again) { + ensure(cond.unqueue(cond.sq, &ppu)); + ppu.state += cpu_flag::again; + return; + } + + // Put the current thread to sleep and schedule lwmutex waiter + // atomically + cond.append(cpu); + cond.sleep(ppu, timeout); + return; + } + } + + cond.sleep(ppu, timeout); + }); + + if (!cond || !mutex) { + return CELL_ESRCH; + } + + if (ppu.state & cpu_flag::again) { + return CELL_OK; + } + + while (auto state = +ppu.state) { + if (state & cpu_flag::signal && + ppu.state.test_and_reset(cpu_flag::signal)) { + break; + } + + if (is_stopped(state)) { + std::scoped_lock lock(cond->mutex, mutex->mutex); + + bool mutex_sleep = false; + bool cond_sleep = false; + + for (auto cpu = mutex->load_sq(); cpu; cpu = cpu->next_cpu) { + if (cpu == &ppu) { + mutex_sleep = true; + break; + } + } + + for (auto cpu = atomic_storage::load(cond->sq); cpu; + cpu = cpu->next_cpu) { + if (cpu == &ppu) { + cond_sleep = true; + break; + } + } + + if (!cond_sleep && !mutex_sleep) { + break; + } + + sstate(mutex_sleep); + ppu.state += cpu_flag::again; + break; + } + + for (usz i = 0; cpu_flag::signal - ppu.state && i < 50; i++) { + busy_wait(500); + } + + if (ppu.state & cpu_flag::signal) { + continue; + } + + if (timeout) { + if (lv2_obj::wait_timeout(timeout, &ppu)) { + // Wait for rescheduling + if (ppu.check_state()) { + continue; + } + + ppu.state += cpu_flag::wait; + + std::lock_guard lock(cond->mutex); + + if (cond->unqueue(cond->sq, &ppu)) { + ppu.gpr[3] = CELL_ETIMEDOUT; + break; + } + + std::lock_guard lock2(mutex->mutex); + + bool success = false; + + mutex->lv2_control.fetch_op([&](lv2_lwmutex::control_data_t &data) { + success = false; + + ppu_thread *sq = static_cast(data.sq); + + const bool retval = &ppu == sq; + + if (!mutex->unqueue(sq, &ppu)) { + return false; + } + + success = true; + + if (!retval) { + return false; + } + + data.sq = sq; + return true; + }); + + if (success) { + ppu.next_cpu = nullptr; + ppu.gpr[3] = CELL_ETIMEDOUT; + } + + break; + } + } else { + ppu.state.wait(state); + } + } + + if (--mutex->lwcond_waiters == smin) { + // Notify the thread destroying lwmutex on last waiter + mutex->lwcond_waiters.notify_all(); + } + + if (--cond->lwmutex_waiters == smin) { + // Notify the thread destroying lwcond on last waiter + cond->lwmutex_waiters.notify_all(); + } + + // Return cause + return not_an_error(ppu.gpr[3]); +} diff --git a/kernel/cellos/src/sys_lwmutex.cpp b/kernel/cellos/src/sys_lwmutex.cpp new file mode 100644 index 000000000..3b7c9aa92 --- /dev/null +++ b/kernel/cellos/src/sys_lwmutex.cpp @@ -0,0 +1,353 @@ +#include "stdafx.h" + +#include "sys_lwmutex.h" + +#include "Emu/IdManager.h" + +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/Cell/PPUThread.h" + +#include "util/asm.hpp" + +LOG_CHANNEL(sys_lwmutex); + +lv2_lwmutex::lv2_lwmutex(utils::serial &ar) + : protocol(ar), control(ar.pop()), + name(ar.pop>()) { + ar(lv2_control.raw().signaled); +} + +void lv2_lwmutex::save(utils::serial &ar) { + ar(protocol, control, name, lv2_control.raw().signaled); +} + +error_code _sys_lwmutex_create(ppu_thread &ppu, vm::ptr lwmutex_id, + u32 protocol, vm::ptr control, + s32 has_name, u64 name) { + ppu.state += cpu_flag::wait; + + sys_lwmutex.trace(u8"_sys_lwmutex_create(lwmutex_id=*0x%x, protocol=0x%x, " + u8"control=*0x%x, has_name=0x%x, name=0x%llx (“%s”))", + lwmutex_id, protocol, control, has_name, name, + lv2_obj::name_64{std::bit_cast>(name)}); + + if (protocol != SYS_SYNC_FIFO && protocol != SYS_SYNC_RETRY && + protocol != SYS_SYNC_PRIORITY) { + sys_lwmutex.error("_sys_lwmutex_create(): unknown protocol (0x%x)", + protocol); + return CELL_EINVAL; + } + + if (!(has_name < 0)) { + name = 0; + } + + if (const u32 id = idm::make(protocol, control, name)) { + ppu.check_state(); + *lwmutex_id = id; + return CELL_OK; + } + + return CELL_EAGAIN; +} + +error_code _sys_lwmutex_destroy(ppu_thread &ppu, u32 lwmutex_id) { + ppu.state += cpu_flag::wait; + + sys_lwmutex.trace("_sys_lwmutex_destroy(lwmutex_id=0x%x)", lwmutex_id); + + shared_ptr _mutex; + + while (true) { + s32 old_val = 0; + + auto [ptr, ret] = idm::withdraw( + lwmutex_id, [&](lv2_lwmutex &mutex) -> CellError { + // Ignore check on first iteration + if (_mutex && std::addressof(mutex) != _mutex.get()) { + // Other thread has destroyed the lwmutex earlier + return CELL_ESRCH; + } + + std::lock_guard lock(mutex.mutex); + + if (mutex.load_sq()) { + return CELL_EBUSY; + } + + old_val = mutex.lwcond_waiters.or_fetch(smin); + + if (old_val != smin) { + // Deschedule if waiters were found + lv2_obj::sleep(ppu); + + // Repeat loop: there are lwcond waiters + return CELL_EAGAIN; + } + + return {}; + }); + + if (!ptr) { + return CELL_ESRCH; + } + + if (ret) { + if (ret != CELL_EAGAIN) { + return ret; + } + } else { + break; + } + + _mutex = std::move(ptr); + + // Wait for all lwcond waiters to quit + while (old_val + 0u > 1u << 31) { + thread_ctrl::wait_on(_mutex->lwcond_waiters, old_val); + + if (ppu.is_stopped()) { + ppu.state += cpu_flag::again; + return {}; + } + + old_val = _mutex->lwcond_waiters; + } + + // Wake up from sleep + ppu.check_state(); + } + + return CELL_OK; +} + +error_code _sys_lwmutex_lock(ppu_thread &ppu, u32 lwmutex_id, u64 timeout) { + ppu.state += cpu_flag::wait; + + sys_lwmutex.trace("_sys_lwmutex_lock(lwmutex_id=0x%x, timeout=0x%llx)", + lwmutex_id, timeout); + + ppu.gpr[3] = CELL_OK; + + const auto mutex = idm::get( + lwmutex_id, [&, notify = lv2_obj::notify_all_t()](lv2_lwmutex &mutex) { + if (s32 signal = mutex.lv2_control + .fetch_op([](lv2_lwmutex::control_data_t &data) { + if (data.signaled) { + data.signaled = 0; + return true; + } + + return false; + }) + .first.signaled) { + if (~signal & 1) { + ppu.gpr[3] = CELL_EBUSY; + } + + return true; + } + + lv2_obj::prepare_for_sleep(ppu); + + ppu.cancel_sleep = 1; + + if (s32 signal = mutex.try_own(&ppu)) { + if (~signal & 1) { + ppu.gpr[3] = CELL_EBUSY; + } + + ppu.cancel_sleep = 0; + return true; + } + + const bool finished = !mutex.sleep(ppu, timeout); + notify.cleanup(); + return finished; + }); + + if (!mutex) { + return CELL_ESRCH; + } + + if (mutex.ret) { + return not_an_error(ppu.gpr[3]); + } + + while (auto state = +ppu.state) { + if (state & cpu_flag::signal && + ppu.state.test_and_reset(cpu_flag::signal)) { + break; + } + + if (is_stopped(state)) { + std::lock_guard lock(mutex->mutex); + + for (auto cpu = mutex->load_sq(); cpu; cpu = cpu->next_cpu) { + if (cpu == &ppu) { + ppu.state += cpu_flag::again; + return {}; + } + } + + break; + } + + for (usz i = 0; cpu_flag::signal - ppu.state && i < 50; i++) { + busy_wait(500); + } + + if (ppu.state & cpu_flag::signal) { + continue; + } + + if (timeout) { + if (lv2_obj::wait_timeout(timeout, &ppu)) { + // Wait for rescheduling + if (ppu.check_state()) { + continue; + } + + ppu.state += cpu_flag::wait; + + if (!mutex->load_sq()) { + // Sleep queue is empty, so the thread must have been signaled + mutex->mutex.lock_unlock(); + break; + } + + std::lock_guard lock(mutex->mutex); + + bool success = false; + + mutex->lv2_control.fetch_op([&](lv2_lwmutex::control_data_t &data) { + success = false; + + ppu_thread *sq = static_cast(data.sq); + + const bool retval = &ppu == sq; + + if (!mutex->unqueue(sq, &ppu)) { + return false; + } + + success = true; + + if (!retval) { + return false; + } + + data.sq = sq; + return true; + }); + + if (success) { + ppu.next_cpu = nullptr; + ppu.gpr[3] = CELL_ETIMEDOUT; + } + + break; + } + } else { + ppu.state.wait(state); + } + } + + return not_an_error(ppu.gpr[3]); +} + +error_code _sys_lwmutex_trylock(ppu_thread &ppu, u32 lwmutex_id) { + ppu.state += cpu_flag::wait; + + sys_lwmutex.trace("_sys_lwmutex_trylock(lwmutex_id=0x%x)", lwmutex_id); + + const auto mutex = + idm::check(lwmutex_id, [&](lv2_lwmutex &mutex) { + auto [_, ok] = + mutex.lv2_control.fetch_op([](lv2_lwmutex::control_data_t &data) { + if (data.signaled & 1) { + data.signaled = 0; + return true; + } + + return false; + }); + + return ok; + }); + + if (!mutex) { + return CELL_ESRCH; + } + + if (!mutex.ret) { + return not_an_error(CELL_EBUSY); + } + + return CELL_OK; +} + +error_code _sys_lwmutex_unlock(ppu_thread &ppu, u32 lwmutex_id) { + ppu.state += cpu_flag::wait; + + sys_lwmutex.trace("_sys_lwmutex_unlock(lwmutex_id=0x%x)", lwmutex_id); + + const auto mutex = idm::check( + lwmutex_id, [&, notify = lv2_obj::notify_all_t()](lv2_lwmutex &mutex) { + if (mutex.try_unlock(false)) { + return; + } + + std::lock_guard lock(mutex.mutex); + + if (const auto cpu = mutex.reown()) { + if (static_cast(cpu)->state & cpu_flag::again) { + ppu.state += cpu_flag::again; + return; + } + + mutex.awake(cpu); + notify.cleanup(); // lv2_lwmutex::mutex is not really active 99% of + // the time, can be ignored + } + }); + + if (!mutex) { + return CELL_ESRCH; + } + + return CELL_OK; +} + +error_code _sys_lwmutex_unlock2(ppu_thread &ppu, u32 lwmutex_id) { + ppu.state += cpu_flag::wait; + + sys_lwmutex.warning("_sys_lwmutex_unlock2(lwmutex_id=0x%x)", lwmutex_id); + + const auto mutex = idm::check( + lwmutex_id, [&, notify = lv2_obj::notify_all_t()](lv2_lwmutex &mutex) { + if (mutex.try_unlock(true)) { + return; + } + + std::lock_guard lock(mutex.mutex); + + if (const auto cpu = mutex.reown(true)) { + if (static_cast(cpu)->state & cpu_flag::again) { + ppu.state += cpu_flag::again; + return; + } + + static_cast(cpu)->gpr[3] = CELL_EBUSY; + mutex.awake(cpu); + notify.cleanup(); // lv2_lwmutex::mutex is not really active 99% of + // the time, can be ignored + } + }); + + if (!mutex) { + return CELL_ESRCH; + } + + return CELL_OK; +} diff --git a/kernel/cellos/src/sys_memory.cpp b/kernel/cellos/src/sys_memory.cpp new file mode 100644 index 000000000..180fed708 --- /dev/null +++ b/kernel/cellos/src/sys_memory.cpp @@ -0,0 +1,408 @@ +#include "stdafx.h" + +#include "sys_memory.h" + +#include "Emu/CPU/CPUThread.h" +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/Cell/SPUThread.h" +#include "Emu/IdManager.h" +#include "Emu/Memory/vm_locking.h" + +#include "util/asm.hpp" + +LOG_CHANNEL(sys_memory); + +// +static shared_mutex s_memstats_mtx; + +lv2_memory_container::lv2_memory_container(u32 size, bool from_idm) noexcept + : size(size), + id{from_idm ? idm::last_id() : SYS_MEMORY_CONTAINER_ID_INVALID} {} + +lv2_memory_container::lv2_memory_container(utils::serial &ar, + bool from_idm) noexcept + : size(ar), id{from_idm ? idm::last_id() : SYS_MEMORY_CONTAINER_ID_INVALID}, + used(ar) {} + +std::function lv2_memory_container::load(utils::serial &ar) { + // Use idm::last_id() only for the instances at IDM + return [ptr = make_shared(exact_t(ar), + true)](void *storage) { + *static_cast *>(storage) = ptr; + }; +} + +void lv2_memory_container::save(utils::serial &ar) { ar(size, used); } + +lv2_memory_container *lv2_memory_container::search(u32 id) { + if (id != SYS_MEMORY_CONTAINER_ID_INVALID) { + return idm::check_unlocked(id); + } + + return &g_fxo->get(); +} + +struct sys_memory_address_table { + atomic_t addrs[65536]{}; + + sys_memory_address_table() = default; + + SAVESTATE_INIT_POS( + id_manager::id_map::savestate_init_pos + 0.1); + + sys_memory_address_table(utils::serial &ar) { + // First: address, second: conatiner ID (SYS_MEMORY_CONTAINER_ID_INVALID for + // global FXO memory container) + std::unordered_map mm; + ar(mm); + + for (const auto &[addr, id] : mm) { + addrs[addr] = ensure(lv2_memory_container::search(id)); + } + } + + void save(utils::serial &ar) { + std::unordered_map mm; + + for (auto &ctr : addrs) { + if (const auto ptr = +ctr) { + mm[static_cast(&ctr - addrs)] = ptr->id; + } + } + + ar(mm); + } +}; + +std::shared_ptr reserve_map(u32 alloc_size, u32 align) { + return vm::reserve_map( + align == 0x10000 ? vm::user64k : vm::user1m, 0, + align == 0x10000 ? 0x20000000 : utils::align(alloc_size, 0x10000000), + align == 0x10000 ? (vm::page_size_64k | vm::bf0_0x1) + : (vm::page_size_1m | vm::bf0_0x1)); +} + +// Todo: fix order of error checks + +error_code sys_memory_allocate(cpu_thread &cpu, u64 size, u64 flags, + vm::ptr alloc_addr) { + cpu.state += cpu_flag::wait; + + sys_memory.warning( + "sys_memory_allocate(size=0x%x, flags=0x%llx, alloc_addr=*0x%x)", size, + flags, alloc_addr); + + if (!size) { + return {CELL_EALIGN, size}; + } + + // Check allocation size + const u32 align = flags == SYS_MEMORY_PAGE_SIZE_1M ? 0x100000 + : flags == SYS_MEMORY_PAGE_SIZE_64K ? 0x10000 + : flags == 0 ? 0x100000 + : 0; + + if (!align) { + return {CELL_EINVAL, flags}; + } + + if (size % align) { + return {CELL_EALIGN, size}; + } + + // Get "default" memory container + auto &dct = g_fxo->get(); + + // Try to get "physical memory" + if (!dct.take(size)) { + return {CELL_ENOMEM, dct.size - dct.used}; + } + + if (const auto area = reserve_map(static_cast(size), align)) { + if (const u32 addr = area->alloc(static_cast(size), nullptr, align)) { + ensure(!g_fxo->get().addrs[addr >> 16].exchange( + &dct)); + + if (alloc_addr) { + sys_memory.notice( + "sys_memory_allocate(): Allocated 0x%x address (size=0x%x)", addr, + size); + + vm::lock_sudo(addr, static_cast(size)); + cpu.check_state(); + *alloc_addr = addr; + return CELL_OK; + } + + // Dealloc using the syscall + sys_memory_free(cpu, addr); + return CELL_EFAULT; + } + } + + dct.free(size); + return CELL_ENOMEM; +} + +error_code sys_memory_allocate_from_container(cpu_thread &cpu, u64 size, + u32 cid, u64 flags, + vm::ptr alloc_addr) { + cpu.state += cpu_flag::wait; + + sys_memory.warning("sys_memory_allocate_from_container(size=0x%x, cid=0x%x, " + "flags=0x%llx, alloc_addr=*0x%x)", + size, cid, flags, alloc_addr); + + if (!size) { + return {CELL_EALIGN, size}; + } + + // Check allocation size + const u32 align = flags == SYS_MEMORY_PAGE_SIZE_1M ? 0x100000 + : flags == SYS_MEMORY_PAGE_SIZE_64K ? 0x10000 + : flags == 0 ? 0x100000 + : 0; + + if (!align) { + return {CELL_EINVAL, flags}; + } + + if (size % align) { + return {CELL_EALIGN, size}; + } + + const auto ct = idm::get( + cid, [&](lv2_memory_container &ct) -> CellError { + // Try to get "physical memory" + if (!ct.take(size)) { + return CELL_ENOMEM; + } + + return {}; + }); + + if (!ct) { + return CELL_ESRCH; + } + + if (ct.ret) { + return {ct.ret, ct->size - ct->used}; + } + + if (const auto area = reserve_map(static_cast(size), align)) { + if (const u32 addr = area->alloc(static_cast(size))) { + ensure(!g_fxo->get().addrs[addr >> 16].exchange( + ct.ptr.get())); + + if (alloc_addr) { + vm::lock_sudo(addr, static_cast(size)); + cpu.check_state(); + *alloc_addr = addr; + return CELL_OK; + } + + // Dealloc using the syscall + sys_memory_free(cpu, addr); + return CELL_EFAULT; + } + } + + ct->free(size); + return CELL_ENOMEM; +} + +error_code sys_memory_free(cpu_thread &cpu, u32 addr) { + cpu.state += cpu_flag::wait; + + sys_memory.warning("sys_memory_free(addr=0x%x)", addr); + + const auto ct = + addr % 0x10000 + ? nullptr + : g_fxo->get().addrs[addr >> 16].exchange( + nullptr); + + if (!ct) { + return {CELL_EINVAL, addr}; + } + + const auto size = (ensure(vm::dealloc(addr))); + reader_lock{id_manager::g_mutex}, ct->free(size); + return CELL_OK; +} + +error_code sys_memory_get_page_attribute(cpu_thread &cpu, u32 addr, + vm::ptr attr) { + cpu.state += cpu_flag::wait; + + sys_memory.trace("sys_memory_get_page_attribute(addr=0x%x, attr=*0x%x)", addr, + attr); + + vm::writer_lock rlock; + + if (!vm::check_addr(addr) || addr >= SPU_FAKE_BASE_ADDR) { + return CELL_EINVAL; + } + + if (!vm::check_addr(attr.addr(), vm::page_readable, attr.size())) { + return CELL_EFAULT; + } + + attr->attribute = 0x40000ull; // SYS_MEMORY_PROT_READ_WRITE (TODO) + attr->access_right = addr >> 28 == 0xdu + ? SYS_MEMORY_ACCESS_RIGHT_PPU_THR + : SYS_MEMORY_ACCESS_RIGHT_ANY; // (TODO) + + if (vm::check_addr(addr, vm::page_1m_size)) { + attr->page_size = 0x100000; + } else if (vm::check_addr(addr, vm::page_64k_size)) { + attr->page_size = 0x10000; + } else { + attr->page_size = 4096; + } + + attr->pad = 0; // Always write 0 + return CELL_OK; +} + +error_code +sys_memory_get_user_memory_size(cpu_thread &cpu, + vm::ptr mem_info) { + cpu.state += cpu_flag::wait; + + sys_memory.warning("sys_memory_get_user_memory_size(mem_info=*0x%x)", + mem_info); + + // Get "default" memory container + auto &dct = g_fxo->get(); + + sys_memory_info_t out{}; + { + ::reader_lock lock(s_memstats_mtx); + + out.total_user_memory = dct.size; + out.available_user_memory = dct.size - dct.used; + + // Scan other memory containers + idm::select([&](u32, lv2_memory_container &ct) { + out.total_user_memory -= ct.size; + }); + } + + cpu.check_state(); + *mem_info = out; + return CELL_OK; +} + +error_code sys_memory_get_user_memory_stat( + cpu_thread &cpu, vm::ptr mem_stat) { + cpu.state += cpu_flag::wait; + + sys_memory.todo("sys_memory_get_user_memory_stat(mem_stat=*0x%x)", mem_stat); + + return CELL_OK; +} + +error_code sys_memory_container_create(cpu_thread &cpu, vm::ptr cid, + u64 size) { + cpu.state += cpu_flag::wait; + + sys_memory.warning("sys_memory_container_create(cid=*0x%x, size=0x%x)", cid, + size); + + // Round down to 1 MB granularity + size &= ~0xfffff; + + if (!size) { + return CELL_ENOMEM; + } + + auto &dct = g_fxo->get(); + + std::lock_guard lock(s_memstats_mtx); + + // Try to obtain "physical memory" from the default container + if (!dct.take(size)) { + return CELL_ENOMEM; + } + + // Create the memory container + if (const u32 id = + idm::make(static_cast(size), true)) { + cpu.check_state(); + *cid = id; + return CELL_OK; + } + + dct.free(size); + return CELL_EAGAIN; +} + +error_code sys_memory_container_destroy(cpu_thread &cpu, u32 cid) { + cpu.state += cpu_flag::wait; + + sys_memory.warning("sys_memory_container_destroy(cid=0x%x)", cid); + + std::lock_guard lock(s_memstats_mtx); + + const auto ct = idm::withdraw( + cid, [](lv2_memory_container &ct) -> CellError { + // Check if some memory is not deallocated (the container cannot be + // destroyed in this case) + if (!ct.used.compare_and_swap_test(0, ct.size)) { + return CELL_EBUSY; + } + + return {}; + }); + + if (!ct) { + return CELL_ESRCH; + } + + if (ct.ret) { + return ct.ret; + } + + // Return "physical memory" to the default container + g_fxo->get().free(ct->size); + + return CELL_OK; +} + +error_code sys_memory_container_get_size(cpu_thread &cpu, + vm::ptr mem_info, + u32 cid) { + cpu.state += cpu_flag::wait; + + sys_memory.warning("sys_memory_container_get_size(mem_info=*0x%x, cid=0x%x)", + mem_info, cid); + + const auto ct = idm::get_unlocked(cid); + + if (!ct) { + return CELL_ESRCH; + } + + cpu.check_state(); + mem_info->total_user_memory = ct->size; // Total container memory + mem_info->available_user_memory = + ct->size - ct->used; // Available container memory + + return CELL_OK; +} + +error_code sys_memory_container_destroy_parent_with_childs( + cpu_thread &cpu, u32 cid, u32 must_0, vm::ptr mc_child) { + sys_memory.warning("sys_memory_container_destroy_parent_with_childs(cid=0x%x," + " must_0=%d, mc_child=*0x%x)", + cid, must_0, mc_child); + + if (must_0) { + return CELL_EINVAL; + } + + // Multi-process is not supported yet so child containers mean nothing at the + // moment Simply destroy parent + return sys_memory_container_destroy(cpu, cid); +} diff --git a/kernel/cellos/src/sys_mmapper.cpp b/kernel/cellos/src/sys_mmapper.cpp new file mode 100644 index 000000000..8e2d36572 --- /dev/null +++ b/kernel/cellos/src/sys_mmapper.cpp @@ -0,0 +1,805 @@ +#include "stdafx.h" + +#include "sys_mmapper.h" + +#include "Emu/Cell/PPUThread.h" +#include "Emu/Memory/vm_var.h" +#include "cellos/sys_event.h" +#include "sys_memory.h" +#include "sys_process.h" +#include "sys_sync.h" + +#include + +#include "util/vm.hpp" + +LOG_CHANNEL(sys_mmapper); + +template <> +void fmt_class_string::format(std::string &out, u64 arg) { + format_enum(out, arg, [](auto value) { + switch (value) { + case SYS_MEMORY_CONTAINER_ID_INVALID: + return "Global"; + } + + // Resort to hex formatting for other values + return unknown; + }); +} + +lv2_memory::lv2_memory(u32 size, u32 align, u64 flags, u64 key, bool pshared, + lv2_memory_container *ct) + : size(size), align(align), flags(flags), key(key), pshared(pshared), + ct(ct), shm(std::make_shared(size, 1 /* shareable flag */)) { +#ifndef _WIN32 + // Optimization that's useless on Windows :puke: + utils::memory_lock(shm->map_self(), size); +#endif +} + +lv2_memory::lv2_memory(utils::serial &ar) + : size(ar), align(ar), flags(ar), key(ar), pshared(ar), + ct(lv2_memory_container::search(ar.pop())), shm([&](u32 addr) { + if (addr) { + return ensure(vm::get(vm::any, addr)->peek(addr).second); + } + + const auto _shm = std::make_shared(size, 1); + ar(std::span(_shm->map_self(), size)); + return _shm; + }(ar.pop())), + counter(ar) { +#ifndef _WIN32 + // Optimization that's useless on Windows :puke: + utils::memory_lock(shm->map_self(), size); +#endif +} + +CellError lv2_memory::on_id_create() { + if (!exists && !ct->take(size)) { + sys_mmapper.error("lv2_memory::on_id_create(): Cannot allocate 0x%x bytes " + "(0x%x available)", + size, ct->size - ct->used); + return CELL_ENOMEM; + } + + exists++; + return {}; +} + +std::function lv2_memory::load(utils::serial &ar) { + auto mem = make_shared(exact_t(ar)); + mem->exists++; // Disable on_id_create() + auto func = load_func(mem, +mem->pshared); + mem->exists--; + return func; +} + +void lv2_memory::save(utils::serial &ar) { + USING_SERIALIZATION_VERSION(lv2_memory); + + ar(size, align, flags, key, pshared, ct->id); + ar(counter ? vm::get_shm_addr(shm) : 0); + + if (!counter) { + ar(std::span(shm->map_self(), size)); + } + + ar(counter); +} + +page_fault_notification_entries::page_fault_notification_entries( + utils::serial &ar) { + ar(entries); +} + +void page_fault_notification_entries::save(utils::serial &ar) { ar(entries); } + +template +error_code create_lv2_shm(bool pshared, u64 ipc_key, u64 size, u32 align, + u64 flags, lv2_memory_container *ct) { + const u32 _pshared = + pshared ? SYS_SYNC_PROCESS_SHARED : SYS_SYNC_NOT_PROCESS_SHARED; + + if (!pshared) { + ipc_key = 0; + } + + if (auto error = lv2_obj::create( + _pshared, ipc_key, + exclusive ? SYS_SYNC_NEWLY_CREATED : SYS_SYNC_NOT_CARE, + [&]() { + return make_shared(static_cast(size), align, flags, + ipc_key, pshared, ct); + }, + false)) { + return error; + } + + return CELL_OK; +} + +error_code sys_mmapper_allocate_address(ppu_thread &ppu, u64 size, u64 flags, + u64 alignment, + vm::ptr alloc_addr) { + ppu.state += cpu_flag::wait; + + sys_mmapper.warning("sys_mmapper_allocate_address(size=0x%x, flags=0x%x, " + "alignment=0x%x, alloc_addr=*0x%x)", + size, flags, alignment, alloc_addr); + + if (size % 0x10000000) { + return CELL_EALIGN; + } + + if (size > u32{umax}) { + return CELL_ENOMEM; + } + + // This is a workaround for psl1ght, which gives us an alignment of 0, which + // is technically invalid, but apparently is allowed on actual ps3 + // https://github.com/ps3dev/PSL1GHT/blob/534e58950732c54dc6a553910b653c99ba6e9edc/ppu/librt/sbrk.c#L71 + if (!alignment) { + alignment = 0x10000000; + } + + switch (alignment) { + case 0x10000000: + case 0x20000000: + case 0x40000000: + case 0x80000000: { + if (const auto area = + vm::find_map(static_cast(size), static_cast(alignment), + flags & SYS_MEMORY_PAGE_SIZE_MASK)) { + sys_mmapper.warning( + "sys_mmapper_allocate_address(): Found VM 0x%x area (vsize=0x%x)", + area->addr, size); + + ppu.check_state(); + *alloc_addr = area->addr; + return CELL_OK; + } + + return CELL_ENOMEM; + } + } + + return CELL_EALIGN; +} + +error_code sys_mmapper_allocate_fixed_address(ppu_thread &ppu) { + ppu.state += cpu_flag::wait; + + sys_mmapper.warning("sys_mmapper_allocate_fixed_address()"); + + if (!vm::map(0xB0000000, 0x10000000, SYS_MEMORY_PAGE_SIZE_1M)) { + return CELL_EEXIST; + } + + return CELL_OK; +} + +error_code sys_mmapper_allocate_shared_memory(ppu_thread &ppu, u64 ipc_key, + u64 size, u64 flags, + vm::ptr mem_id) { + ppu.state += cpu_flag::wait; + + sys_mmapper.warning("sys_mmapper_allocate_shared_memory(ipc_key=0x%x, " + "size=0x%x, flags=0x%x, mem_id=*0x%x)", + ipc_key, size, flags, mem_id); + + if (size == 0) { + return CELL_EALIGN; + } + + // Check page granularity + switch (flags & SYS_MEMORY_GRANULARITY_MASK) { + case 0: + case SYS_MEMORY_GRANULARITY_1M: { + if (size % 0x100000) { + return CELL_EALIGN; + } + + break; + } + case SYS_MEMORY_GRANULARITY_64K: { + if (size % 0x10000) { + return CELL_EALIGN; + } + + break; + } + default: { + return CELL_EINVAL; + } + } + + // Get "default" memory container + auto &dct = g_fxo->get(); + + if (auto error = create_lv2_shm( + ipc_key != SYS_MMAPPER_NO_SHM_KEY, ipc_key, size, + flags & SYS_MEMORY_PAGE_SIZE_64K ? 0x10000 : 0x100000, flags, &dct)) { + return error; + } + + ppu.check_state(); + *mem_id = idm::last_id(); + return CELL_OK; +} + +error_code +sys_mmapper_allocate_shared_memory_from_container(ppu_thread &ppu, u64 ipc_key, + u64 size, u32 cid, u64 flags, + vm::ptr mem_id) { + ppu.state += cpu_flag::wait; + + sys_mmapper.warning( + "sys_mmapper_allocate_shared_memory_from_container(ipc_key=0x%x, " + "size=0x%x, cid=0x%x, flags=0x%x, mem_id=*0x%x)", + ipc_key, size, cid, flags, mem_id); + + if (size == 0) { + return CELL_EALIGN; + } + + // Check page granularity. + switch (flags & SYS_MEMORY_GRANULARITY_MASK) { + case 0: + case SYS_MEMORY_GRANULARITY_1M: { + if (size % 0x100000) { + return CELL_EALIGN; + } + + break; + } + case SYS_MEMORY_GRANULARITY_64K: { + if (size % 0x10000) { + return CELL_EALIGN; + } + + break; + } + default: { + return CELL_EINVAL; + } + } + + const auto ct = idm::get_unlocked(cid); + + if (!ct) { + return CELL_ESRCH; + } + + if (auto error = + create_lv2_shm(ipc_key != SYS_MMAPPER_NO_SHM_KEY, ipc_key, size, + flags & SYS_MEMORY_PAGE_SIZE_64K ? 0x10000 : 0x100000, + flags, ct.get())) { + return error; + } + + ppu.check_state(); + *mem_id = idm::last_id(); + return CELL_OK; +} + +error_code sys_mmapper_allocate_shared_memory_ext( + ppu_thread &ppu, u64 ipc_key, u64 size, u32 flags, + vm::ptr entries, s32 entry_count, + vm::ptr mem_id) { + ppu.state += cpu_flag::wait; + + sys_mmapper.todo( + "sys_mmapper_allocate_shared_memory_ext(ipc_key=0x%x, size=0x%x, " + "flags=0x%x, entries=*0x%x, entry_count=0x%x, mem_id=*0x%x)", + ipc_key, size, flags, entries, entry_count, mem_id); + + if (size == 0) { + return CELL_EALIGN; + } + + switch (flags & SYS_MEMORY_GRANULARITY_MASK) { + case SYS_MEMORY_GRANULARITY_1M: + case 0: { + if (size % 0x100000) { + return CELL_EALIGN; + } + + break; + } + case SYS_MEMORY_GRANULARITY_64K: { + if (size % 0x10000) { + return CELL_EALIGN; + } + + break; + } + default: { + return CELL_EINVAL; + } + } + + if (flags & ~SYS_MEMORY_PAGE_SIZE_MASK) { + return CELL_EINVAL; + } + + if (entry_count <= 0 || entry_count > 0x10) { + return CELL_EINVAL; + } + + if constexpr (bool to_perm_check = false; true) { + for (s32 i = 0; i < entry_count; i++) { + const u64 type = entries[i].type; + + // The whole structure contents are unknown + sys_mmapper.todo( + "sys_mmapper_allocate_shared_memory_ext(): entry type = 0x%x", type); + + switch (type) { + case 0: + case 1: + case 3: { + break; + } + case 5: { + to_perm_check = true; + break; + } + default: { + return CELL_EPERM; + } + } + } + + if (to_perm_check) { + if (flags != SYS_MEMORY_PAGE_SIZE_64K || + !g_ps3_process_info.debug_or_root()) { + return CELL_EPERM; + } + } + } + + // Get "default" memory container + auto &dct = g_fxo->get(); + + if (auto error = create_lv2_shm( + true, ipc_key, size, + flags & SYS_MEMORY_PAGE_SIZE_64K ? 0x10000 : 0x100000, flags, &dct)) { + return error; + } + + ppu.check_state(); + *mem_id = idm::last_id(); + return CELL_OK; +} + +error_code sys_mmapper_allocate_shared_memory_from_container_ext( + ppu_thread &ppu, u64 ipc_key, u64 size, u64 flags, u32 cid, + vm::ptr entries, s32 entry_count, + vm::ptr mem_id) { + ppu.state += cpu_flag::wait; + + sys_mmapper.todo("sys_mmapper_allocate_shared_memory_from_container_ext(ipc_" + "key=0x%x, size=0x%x, flags=0x%x, cid=0x%x, entries=*0x%x, " + "entry_count=0x%x, mem_id=*0x%x)", + ipc_key, size, flags, cid, entries, entry_count, mem_id); + + switch (flags & SYS_MEMORY_PAGE_SIZE_MASK) { + case SYS_MEMORY_PAGE_SIZE_1M: + case 0: { + if (size % 0x100000) { + return CELL_EALIGN; + } + + break; + } + case SYS_MEMORY_PAGE_SIZE_64K: { + if (size % 0x10000) { + return CELL_EALIGN; + } + + break; + } + default: { + return CELL_EINVAL; + } + } + + if (flags & ~SYS_MEMORY_PAGE_SIZE_MASK) { + return CELL_EINVAL; + } + + if (entry_count <= 0 || entry_count > 0x10) { + return CELL_EINVAL; + } + + if constexpr (bool to_perm_check = false; true) { + for (s32 i = 0; i < entry_count; i++) { + const u64 type = entries[i].type; + + sys_mmapper.todo("sys_mmapper_allocate_shared_memory_from_container_ext()" + ": entry type = 0x%x", + type); + + switch (type) { + case 0: + case 1: + case 3: { + break; + } + case 5: { + to_perm_check = true; + break; + } + default: { + return CELL_EPERM; + } + } + } + + if (to_perm_check) { + if (flags != SYS_MEMORY_PAGE_SIZE_64K || + !g_ps3_process_info.debug_or_root()) { + return CELL_EPERM; + } + } + } + + const auto ct = idm::get_unlocked(cid); + + if (!ct) { + return CELL_ESRCH; + } + + if (auto error = create_lv2_shm( + true, ipc_key, size, + flags & SYS_MEMORY_PAGE_SIZE_64K ? 0x10000 : 0x100000, flags, + ct.get())) { + return error; + } + + ppu.check_state(); + *mem_id = idm::last_id(); + return CELL_OK; +} + +error_code sys_mmapper_change_address_access_right(ppu_thread &ppu, u32 addr, + u64 flags) { + ppu.state += cpu_flag::wait; + + sys_mmapper.todo( + "sys_mmapper_change_address_access_right(addr=0x%x, flags=0x%x)", addr, + flags); + + return CELL_OK; +} + +error_code sys_mmapper_free_address(ppu_thread &ppu, u32 addr) { + ppu.state += cpu_flag::wait; + + sys_mmapper.warning("sys_mmapper_free_address(addr=0x%x)", addr); + + if (addr < 0x20000000 || addr >= 0xC0000000) { + return {CELL_EINVAL, addr}; + } + + // If page fault notify exists and an address in this area is faulted, we + // can't free the memory. + auto &pf_events = g_fxo->get(); + std::lock_guard pf_lock(pf_events.pf_mutex); + + const auto mem = vm::get(vm::any, addr); + + if (!mem || mem->addr != addr) { + return {CELL_EINVAL, addr}; + } + + for (const auto &ev : pf_events.events) { + if (addr <= ev.second && ev.second <= addr + mem->size - 1) { + return CELL_EBUSY; + } + } + + // Try to unmap area + const auto [area, success] = vm::unmap(addr, true, &mem); + + if (!area) { + return {CELL_EINVAL, addr}; + } + + if (!success) { + return CELL_EBUSY; + } + + // If a memory block is freed, remove it from page notification table. + auto &pf_entries = g_fxo->get(); + std::lock_guard lock(pf_entries.mutex); + + auto ind_to_remove = pf_entries.entries.begin(); + for (; ind_to_remove != pf_entries.entries.end(); ++ind_to_remove) { + if (addr == ind_to_remove->start_addr) { + break; + } + } + if (ind_to_remove != pf_entries.entries.end()) { + pf_entries.entries.erase(ind_to_remove); + } + + return CELL_OK; +} + +error_code sys_mmapper_free_shared_memory(ppu_thread &ppu, u32 mem_id) { + ppu.state += cpu_flag::wait; + + sys_mmapper.warning("sys_mmapper_free_shared_memory(mem_id=0x%x)", mem_id); + + // Conditionally remove memory ID + const auto mem = idm::withdraw( + mem_id, [&](lv2_memory &mem) -> CellError { + if (mem.counter) { + return CELL_EBUSY; + } + + lv2_obj::on_id_destroy(mem, mem.key, +mem.pshared); + + if (!mem.exists) { + // Return "physical memory" to the memory container + mem.ct->free(mem.size); + } + + return {}; + }); + + if (!mem) { + return CELL_ESRCH; + } + + if (mem.ret) { + return mem.ret; + } + + return CELL_OK; +} + +error_code sys_mmapper_map_shared_memory(ppu_thread &ppu, u32 addr, u32 mem_id, + u64 flags) { + ppu.state += cpu_flag::wait; + + sys_mmapper.warning( + "sys_mmapper_map_shared_memory(addr=0x%x, mem_id=0x%x, flags=0x%x)", addr, + mem_id, flags); + + const auto area = vm::get(vm::any, addr); + + if (!area || addr < 0x20000000 || addr >= 0xC0000000) { + return CELL_EINVAL; + } + + const auto mem = + idm::get(mem_id, [&](lv2_memory &mem) -> CellError { + const u32 page_alignment = + area->flags & SYS_MEMORY_PAGE_SIZE_64K ? 0x10000 : 0x100000; + + if (mem.align < page_alignment) { + return CELL_EINVAL; + } + + if (addr % page_alignment) { + return CELL_EALIGN; + } + + mem.counter++; + return {}; + }); + + if (!mem) { + return CELL_ESRCH; + } + + if (mem.ret) { + return mem.ret; + } + + if (!area->falloc(addr, mem->size, &mem->shm, + mem->align == 0x10000 ? SYS_MEMORY_PAGE_SIZE_64K + : SYS_MEMORY_PAGE_SIZE_1M)) { + mem->counter--; + + if (!area->is_valid()) { + return {CELL_EINVAL, addr}; + } + + return CELL_EBUSY; + } + + vm::lock_sudo(addr, mem->size); + return CELL_OK; +} + +error_code sys_mmapper_search_and_map(ppu_thread &ppu, u32 start_addr, + u32 mem_id, u64 flags, + vm::ptr alloc_addr) { + ppu.state += cpu_flag::wait; + + sys_mmapper.warning("sys_mmapper_search_and_map(start_addr=0x%x, " + "mem_id=0x%x, flags=0x%x, alloc_addr=*0x%x)", + start_addr, mem_id, flags, alloc_addr); + + const auto area = vm::get(vm::any, start_addr); + + if (!area || start_addr != area->addr || start_addr < 0x20000000 || + start_addr >= 0xC0000000) { + return {CELL_EINVAL, start_addr}; + } + + const auto mem = + idm::get(mem_id, [&](lv2_memory &mem) -> CellError { + const u32 page_alignment = + area->flags & SYS_MEMORY_PAGE_SIZE_64K ? 0x10000 : 0x100000; + + if (mem.align < page_alignment) { + return CELL_EALIGN; + } + + mem.counter++; + return {}; + }); + + if (!mem) { + return CELL_ESRCH; + } + + if (mem.ret) { + return mem.ret; + } + + const u32 addr = area->alloc(mem->size, &mem->shm, mem->align, + mem->align == 0x10000 ? SYS_MEMORY_PAGE_SIZE_64K + : SYS_MEMORY_PAGE_SIZE_1M); + + if (!addr) { + mem->counter--; + + if (!area->is_valid()) { + return {CELL_EINVAL, start_addr}; + } + + return CELL_ENOMEM; + } + + sys_mmapper.notice("sys_mmapper_search_and_map(): Found 0x%x address", addr); + + vm::lock_sudo(addr, mem->size); + ppu.check_state(); + *alloc_addr = addr; + return CELL_OK; +} + +error_code sys_mmapper_unmap_shared_memory(ppu_thread &ppu, u32 addr, + vm::ptr mem_id) { + ppu.state += cpu_flag::wait; + + sys_mmapper.warning( + "sys_mmapper_unmap_shared_memory(addr=0x%x, mem_id=*0x%x)", addr, mem_id); + + const auto area = vm::get(vm::any, addr); + + if (!area || addr < 0x20000000 || addr >= 0xC0000000) { + return {CELL_EINVAL, addr}; + } + + const auto shm = area->peek(addr); + + if (!shm.second) { + return {CELL_EINVAL, addr}; + } + + const auto mem = + idm::select([&](u32 id, lv2_memory &mem) -> u32 { + if (mem.shm.get() == shm.second.get()) { + return id; + } + + return 0; + }); + + if (!mem) { + return {CELL_EINVAL, addr}; + } + + if (!area->dealloc(addr, &shm.second)) { + return {CELL_EINVAL, addr}; + } + + // Write out the ID + ppu.check_state(); + *mem_id = mem.ret; + + // Acknowledge + mem->counter--; + + return CELL_OK; +} + +error_code sys_mmapper_enable_page_fault_notification(ppu_thread &ppu, + u32 start_addr, + u32 event_queue_id) { + ppu.state += cpu_flag::wait; + + sys_mmapper.warning("sys_mmapper_enable_page_fault_notification(start_addr=" + "0x%x, event_queue_id=0x%x)", + start_addr, event_queue_id); + + auto mem = vm::get(vm::any, start_addr); + if (!mem || start_addr != mem->addr || start_addr < 0x20000000 || + start_addr >= 0xC0000000) { + return {CELL_EINVAL, start_addr}; + } + + // TODO: Check memory region's flags to make sure the memory can be used for + // page faults. + + auto queue = idm::get_unlocked(event_queue_id); + + if (!queue) { // Can't connect the queue if it doesn't exist. + return CELL_ESRCH; + } + + vm::var port_id(0); + error_code res = sys_event_port_create(ppu, port_id, SYS_EVENT_PORT_LOCAL, + SYS_MEMORY_PAGE_FAULT_EVENT_KEY); + sys_event_port_connect_local(ppu, *port_id, event_queue_id); + + if (res + 0u == CELL_EAGAIN) { + // Not enough system resources. + return CELL_EAGAIN; + } + + auto &pf_entries = g_fxo->get(); + std::unique_lock lock(pf_entries.mutex); + + // Return error code if page fault notifications are already enabled + for (const auto &entry : pf_entries.entries) { + if (entry.start_addr == start_addr) { + lock.unlock(); + sys_event_port_disconnect(ppu, *port_id); + sys_event_port_destroy(ppu, *port_id); + return CELL_EBUSY; + } + } + + page_fault_notification_entry entry{start_addr, event_queue_id, + port_id->value()}; + pf_entries.entries.emplace_back(entry); + + return CELL_OK; +} + +error_code mmapper_thread_recover_page_fault(cpu_thread *cpu) { + // We can only wake a thread if it is being suspended for a page fault. + auto &pf_events = g_fxo->get(); + { + std::lock_guard pf_lock(pf_events.pf_mutex); + const auto pf_event_ind = pf_events.events.find(cpu); + + if (pf_event_ind == pf_events.events.end()) { + // if not found... + return CELL_EINVAL; + } + + pf_events.events.erase(pf_event_ind); + + if (cpu->get_class() == thread_class::ppu) { + lv2_obj::awake(cpu); + } else { + cpu->state += cpu_flag::signal; + } + } + + if (cpu->state & cpu_flag::signal) { + cpu->state.notify_one(); + } + + return CELL_OK; +} diff --git a/kernel/cellos/src/sys_mutex.cpp b/kernel/cellos/src/sys_mutex.cpp new file mode 100644 index 000000000..93a749506 --- /dev/null +++ b/kernel/cellos/src/sys_mutex.cpp @@ -0,0 +1,338 @@ +#include "stdafx.h" + +#include "Emu/IdManager.h" + +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/Cell/PPUThread.h" + +#include "util/asm.hpp" + +#include "sys_mutex.h" + +LOG_CHANNEL(sys_mutex); + +lv2_mutex::lv2_mutex(utils::serial &ar) + : protocol(ar), recursive(ar), adaptive(ar), key(ar), name(ar) { + ar(lock_count, control.raw().owner); + + // For backwards compatibility + control.raw().owner >>= 1; +} + +std::function lv2_mutex::load(utils::serial &ar) { + return load_func(make_shared(exact_t(ar))); +} + +void lv2_mutex::save(utils::serial &ar) { + ar(protocol, recursive, adaptive, key, name, lock_count, + control.raw().owner << 1); +} + +error_code sys_mutex_create(ppu_thread &ppu, vm::ptr mutex_id, + vm::ptr attr) { + ppu.state += cpu_flag::wait; + + sys_mutex.trace("sys_mutex_create(mutex_id=*0x%x, attr=*0x%x)", mutex_id, + attr); + + if (!mutex_id || !attr) { + return CELL_EFAULT; + } + + const auto _attr = *attr; + + const u64 ipc_key = lv2_obj::get_key(_attr); + + if (ipc_key) { + sys_mutex.warning( + "sys_mutex_create(mutex_id=*0x%x, attr=*0x%x): IPC=0x%016x", mutex_id, + attr, ipc_key); + } + + switch (_attr.protocol) { + case SYS_SYNC_FIFO: + break; + case SYS_SYNC_PRIORITY: + break; + case SYS_SYNC_PRIORITY_INHERIT: + sys_mutex.warning("sys_mutex_create(): SYS_SYNC_PRIORITY_INHERIT"); + break; + default: { + sys_mutex.error("sys_mutex_create(): unknown protocol (0x%x)", + _attr.protocol); + return CELL_EINVAL; + } + } + + switch (_attr.recursive) { + case SYS_SYNC_RECURSIVE: + break; + case SYS_SYNC_NOT_RECURSIVE: + break; + default: { + sys_mutex.error("sys_mutex_create(): unknown recursive (0x%x)", + _attr.recursive); + return CELL_EINVAL; + } + } + + if (_attr.adaptive != SYS_SYNC_NOT_ADAPTIVE) { + sys_mutex.todo("sys_mutex_create(): unexpected adaptive (0x%x)", + _attr.adaptive); + } + + if (auto error = lv2_obj::create( + _attr.pshared, _attr.ipc_key, _attr.flags, [&]() { + return make_shared(_attr.protocol, _attr.recursive, + _attr.adaptive, ipc_key, + _attr.name_u64); + })) { + return error; + } + + ppu.check_state(); + *mutex_id = idm::last_id(); + return CELL_OK; +} + +error_code sys_mutex_destroy(ppu_thread &ppu, u32 mutex_id) { + ppu.state += cpu_flag::wait; + + sys_mutex.trace("sys_mutex_destroy(mutex_id=0x%x)", mutex_id); + + const auto mutex = idm::withdraw( + mutex_id, [](lv2_mutex &mutex) -> CellError { + std::lock_guard lock(mutex.mutex); + + if (atomic_storage::load(mutex.control.raw().owner)) { + return CELL_EBUSY; + } + + if (mutex.cond_count) { + return CELL_EPERM; + } + + lv2_obj::on_id_destroy(mutex, mutex.key); + return {}; + }); + + if (!mutex) { + return CELL_ESRCH; + } + + if (mutex->key) { + sys_mutex.warning("sys_mutex_destroy(mutex_id=0x%x): IPC=0x%016x", mutex_id, + mutex->key); + } + + if (mutex.ret) { + return mutex.ret; + } + + return CELL_OK; +} + +error_code sys_mutex_lock(ppu_thread &ppu, u32 mutex_id, u64 timeout) { + ppu.state += cpu_flag::wait; + + sys_mutex.trace("sys_mutex_lock(mutex_id=0x%x, timeout=0x%llx)", mutex_id, + timeout); + + const auto mutex = idm::get( + mutex_id, [&, notify = lv2_obj::notify_all_t()](lv2_mutex &mutex) { + CellError result = mutex.try_lock(ppu); + + if (result == CELL_EBUSY && + !atomic_storage::load(mutex.control.raw().sq)) { + // Try busy waiting a bit if advantageous + for (u32 i = 0, end = lv2_obj::has_ppus_in_running_state() ? 3 : 10; + id_manager::g_mutex.is_lockable() && i < end; i++) { + busy_wait(300); + result = mutex.try_lock(ppu); + + if (!result || + atomic_storage::load(mutex.control.raw().sq)) { + break; + } + } + } + + if (result == CELL_EBUSY) { + lv2_obj::prepare_for_sleep(ppu); + + ppu.cancel_sleep = 1; + + if (mutex.try_own(ppu) || !mutex.sleep(ppu, timeout)) { + result = {}; + } + + if (ppu.cancel_sleep != 1) { + notify.cleanup(); + } + + ppu.cancel_sleep = 0; + } + + return result; + }); + + if (!mutex) { + return CELL_ESRCH; + } + + if (mutex.ret) { + if (mutex.ret != CELL_EBUSY) { + return mutex.ret; + } + } else { + return CELL_OK; + } + + ppu.gpr[3] = CELL_OK; + + while (auto state = +ppu.state) { + if (state & cpu_flag::signal && + ppu.state.test_and_reset(cpu_flag::signal)) { + break; + } + + if (is_stopped(state)) { + std::lock_guard lock(mutex->mutex); + + for (auto cpu = + atomic_storage::load(mutex->control.raw().sq); + cpu; cpu = cpu->next_cpu) { + if (cpu == &ppu) { + ppu.state += cpu_flag::again; + return {}; + } + } + + break; + } + + for (usz i = 0; cpu_flag::signal - ppu.state && i < 40; i++) { + busy_wait(500); + } + + if (ppu.state & cpu_flag::signal) { + continue; + } + + if (timeout) { + if (lv2_obj::wait_timeout(timeout, &ppu)) { + // Wait for rescheduling + if (ppu.check_state()) { + continue; + } + + ppu.state += cpu_flag::wait; + + if (!atomic_storage::load(mutex->control.raw().sq)) { + // Waiters queue is empty, so the thread must have been signaled + mutex->mutex.lock_unlock(); + break; + } + + std::lock_guard lock(mutex->mutex); + + bool success = false; + + mutex->control.fetch_op([&](lv2_mutex::control_data_t &data) { + success = false; + + ppu_thread *sq = static_cast(data.sq); + + const bool retval = &ppu == sq; + + if (!mutex->unqueue(sq, &ppu)) { + return false; + } + + success = true; + + if (!retval) { + return false; + } + + data.sq = sq; + return true; + }); + + if (success) { + ppu.next_cpu = nullptr; + ppu.gpr[3] = CELL_ETIMEDOUT; + } + + break; + } + } else { + ppu.state.wait(state); + } + } + + return not_an_error(ppu.gpr[3]); +} + +error_code sys_mutex_trylock(ppu_thread &ppu, u32 mutex_id) { + ppu.state += cpu_flag::wait; + + sys_mutex.trace("sys_mutex_trylock(mutex_id=0x%x)", mutex_id); + + const auto mutex = idm::check( + mutex_id, [&](lv2_mutex &mutex) { return mutex.try_lock(ppu); }); + + if (!mutex) { + return CELL_ESRCH; + } + + if (mutex.ret) { + if (mutex.ret == CELL_EBUSY) { + return not_an_error(CELL_EBUSY); + } + + return mutex.ret; + } + + return CELL_OK; +} + +error_code sys_mutex_unlock(ppu_thread &ppu, u32 mutex_id) { + ppu.state += cpu_flag::wait; + + sys_mutex.trace("sys_mutex_unlock(mutex_id=0x%x)", mutex_id); + + const auto mutex = idm::check( + mutex_id, + [&, notify = lv2_obj::notify_all_t()](lv2_mutex &mutex) -> CellError { + auto result = mutex.try_unlock(ppu); + + if (result == CELL_EBUSY) { + std::lock_guard lock(mutex.mutex); + + if (auto cpu = mutex.reown()) { + if (cpu->state & cpu_flag::again) { + ppu.state += cpu_flag::again; + return {}; + } + + mutex.awake(cpu); + } + + result = {}; + } + + notify.cleanup(); + return result; + }); + + if (!mutex) { + return CELL_ESRCH; + } + + if (mutex.ret) { + return mutex.ret; + } + + return CELL_OK; +} diff --git a/kernel/cellos/src/sys_net.cpp b/kernel/cellos/src/sys_net.cpp new file mode 100644 index 000000000..337318e88 --- /dev/null +++ b/kernel/cellos/src/sys_net.cpp @@ -0,0 +1,1845 @@ +#include "stdafx.h" + +#include "sys_net.h" + +#include "Emu/Cell/PPUThread.h" +#include "Emu/IdManager.h" +#include "util/Thread.h" + +#include "sys_sync.h" + +#ifdef _WIN32 +#include +#include +#else +#ifdef __clang__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wold-style-cast" +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef __clang__ +#pragma GCC diagnostic pop +#endif +#endif + +#include "Emu/Cell/timers.hpp" +#include "Emu/NP/np_handler.h" +#include "Emu/NP/np_helpers.h" +#include + +#include "sys_net/lv2_socket.h" +#include "sys_net/lv2_socket_native.h" +#include "sys_net/lv2_socket_p2p.h" +#include "sys_net/lv2_socket_p2ps.h" +#include "sys_net/lv2_socket_raw.h" +#include "sys_net/network_context.h" +#include "sys_net/sys_net_helpers.h" + +LOG_CHANNEL(sys_net); +LOG_CHANNEL(sys_net_dump); + +template <> +void fmt_class_string::format(std::string &out, u64 arg) { + format_enum(out, arg, [](auto error) { + switch (static_cast(error)) { +#define SYS_NET_ERROR_CASE(x) \ + case -x: \ + return "-" #x; \ + case x: \ + return #x + SYS_NET_ERROR_CASE(SYS_NET_ENOENT); + SYS_NET_ERROR_CASE(SYS_NET_EINTR); + SYS_NET_ERROR_CASE(SYS_NET_EBADF); + SYS_NET_ERROR_CASE(SYS_NET_ENOMEM); + SYS_NET_ERROR_CASE(SYS_NET_EACCES); + SYS_NET_ERROR_CASE(SYS_NET_EFAULT); + SYS_NET_ERROR_CASE(SYS_NET_EBUSY); + SYS_NET_ERROR_CASE(SYS_NET_EINVAL); + SYS_NET_ERROR_CASE(SYS_NET_EMFILE); + SYS_NET_ERROR_CASE(SYS_NET_ENOSPC); + SYS_NET_ERROR_CASE(SYS_NET_EPIPE); + SYS_NET_ERROR_CASE(SYS_NET_EAGAIN); + static_assert(SYS_NET_EWOULDBLOCK == SYS_NET_EAGAIN); + SYS_NET_ERROR_CASE(SYS_NET_EINPROGRESS); + SYS_NET_ERROR_CASE(SYS_NET_EALREADY); + SYS_NET_ERROR_CASE(SYS_NET_EDESTADDRREQ); + SYS_NET_ERROR_CASE(SYS_NET_EMSGSIZE); + SYS_NET_ERROR_CASE(SYS_NET_EPROTOTYPE); + SYS_NET_ERROR_CASE(SYS_NET_ENOPROTOOPT); + SYS_NET_ERROR_CASE(SYS_NET_EPROTONOSUPPORT); + SYS_NET_ERROR_CASE(SYS_NET_EOPNOTSUPP); + SYS_NET_ERROR_CASE(SYS_NET_EPFNOSUPPORT); + SYS_NET_ERROR_CASE(SYS_NET_EAFNOSUPPORT); + SYS_NET_ERROR_CASE(SYS_NET_EADDRINUSE); + SYS_NET_ERROR_CASE(SYS_NET_EADDRNOTAVAIL); + SYS_NET_ERROR_CASE(SYS_NET_ENETDOWN); + SYS_NET_ERROR_CASE(SYS_NET_ENETUNREACH); + SYS_NET_ERROR_CASE(SYS_NET_ECONNABORTED); + SYS_NET_ERROR_CASE(SYS_NET_ECONNRESET); + SYS_NET_ERROR_CASE(SYS_NET_ENOBUFS); + SYS_NET_ERROR_CASE(SYS_NET_EISCONN); + SYS_NET_ERROR_CASE(SYS_NET_ENOTCONN); + SYS_NET_ERROR_CASE(SYS_NET_ESHUTDOWN); + SYS_NET_ERROR_CASE(SYS_NET_ETOOMANYREFS); + SYS_NET_ERROR_CASE(SYS_NET_ETIMEDOUT); + SYS_NET_ERROR_CASE(SYS_NET_ECONNREFUSED); + SYS_NET_ERROR_CASE(SYS_NET_EHOSTDOWN); + SYS_NET_ERROR_CASE(SYS_NET_EHOSTUNREACH); +#undef SYS_NET_ERROR_CASE + } + + return unknown; + }); +} + +template <> +void fmt_class_string::format(std::string &out, u64 arg) { + format_enum(out, arg, [](auto value) { + switch (value) { + case SYS_NET_SOCK_STREAM: + return "STREAM"; + case SYS_NET_SOCK_DGRAM: + return "DGRAM"; + case SYS_NET_SOCK_RAW: + return "RAW"; + case SYS_NET_SOCK_DGRAM_P2P: + return "DGRAM-P2P"; + case SYS_NET_SOCK_STREAM_P2P: + return "STREAM-P2P"; + } + + return unknown; + }); +} + +template <> +void fmt_class_string::format(std::string &out, u64 arg) { + format_enum(out, arg, [](auto value) { + switch (value) { + case SYS_NET_AF_UNSPEC: + return "UNSPEC"; + case SYS_NET_AF_LOCAL: + return "LOCAL"; + case SYS_NET_AF_INET: + return "INET"; + case SYS_NET_AF_INET6: + return "INET6"; + } + + return unknown; + }); +} + +template <> +void fmt_class_string::format(std::string &out, u64 arg) { + format_enum(out, arg, [](auto value) { + switch (value) { + case SYS_NET_IPPROTO_IP: + return "IPPROTO_IP"; + case SYS_NET_IPPROTO_ICMP: + return "IPPROTO_ICMP"; + case SYS_NET_IPPROTO_IGMP: + return "IPPROTO_IGMP"; + case SYS_NET_IPPROTO_TCP: + return "IPPROTO_TCP"; + case SYS_NET_IPPROTO_UDP: + return "IPPROTO_UDP"; + case SYS_NET_IPPROTO_ICMPV6: + return "IPPROTO_ICMPV6"; + } + + return unknown; + }); +} + +template <> +void fmt_class_string::format(std::string &out, u64 arg) { + format_enum(out, arg, [](auto value) { + switch (value) { + case SYS_NET_TCP_NODELAY: + return "TCP_NODELAY"; + case SYS_NET_TCP_MAXSEG: + return "TCP_MAXSEG"; + case SYS_NET_TCP_MSS_TO_ADVERTISE: + return "TCP_MSS_TO_ADVERTISE"; + } + + return unknown; + }); +} + +template <> +void fmt_class_string::format(std::string &out, u64 arg) { + format_enum(out, arg, [](auto value) { + switch (value) { + case SYS_NET_SO_SNDBUF: + return "SO_SNDBUF"; + case SYS_NET_SO_RCVBUF: + return "SO_RCVBUF"; + case SYS_NET_SO_SNDLOWAT: + return "SO_SNDLOWAT"; + case SYS_NET_SO_RCVLOWAT: + return "SO_RCVLOWAT"; + case SYS_NET_SO_SNDTIMEO: + return "SO_SNDTIMEO"; + case SYS_NET_SO_RCVTIMEO: + return "SO_RCVTIMEO"; + case SYS_NET_SO_ERROR: + return "SO_ERROR"; + case SYS_NET_SO_TYPE: + return "SO_TYPE"; + case SYS_NET_SO_NBIO: + return "SO_NBIO"; + case SYS_NET_SO_TPPOLICY: + return "SO_TPPOLICY"; + case SYS_NET_SO_REUSEADDR: + return "SO_REUSEADDR"; + case SYS_NET_SO_KEEPALIVE: + return "SO_KEEPALIVE"; + case SYS_NET_SO_BROADCAST: + return "SO_BROADCAST"; + case SYS_NET_SO_LINGER: + return "SO_LINGER"; + case SYS_NET_SO_OOBINLINE: + return "SO_OOBINLINE"; + case SYS_NET_SO_REUSEPORT: + return "SO_REUSEPORT"; + case SYS_NET_SO_ONESBCAST: + return "SO_ONESBCAST"; + case SYS_NET_SO_USECRYPTO: + return "SO_USECRYPTO"; + case SYS_NET_SO_USESIGNATURE: + return "SO_USESIGNATURE"; + case SYS_NET_SOL_SOCKET: + return "SOL_SOCKET"; + } + + return unknown; + }); +} + +template <> +void fmt_class_string::format(std::string &out, u64 arg) { + format_enum(out, arg, [](auto value) { + switch (value) { + case SYS_NET_IP_HDRINCL: + return "IP_HDRINCL"; + case SYS_NET_IP_TOS: + return "IP_TOS"; + case SYS_NET_IP_TTL: + return "IP_TTL"; + case SYS_NET_IP_MULTICAST_IF: + return "IP_MULTICAST_IF"; + case SYS_NET_IP_MULTICAST_TTL: + return "IP_MULTICAST_TTL"; + case SYS_NET_IP_MULTICAST_LOOP: + return "IP_MULTICAST_LOOP"; + case SYS_NET_IP_ADD_MEMBERSHIP: + return "IP_ADD_MEMBERSHIP"; + case SYS_NET_IP_DROP_MEMBERSHIP: + return "IP_DROP_MEMBERSHIP"; + case SYS_NET_IP_TTLCHK: + return "IP_TTLCHK"; + case SYS_NET_IP_MAXTTL: + return "IP_MAXTTL"; + case SYS_NET_IP_DONTFRAG: + return "IP_DONTFRAG"; + } + + return unknown; + }); +} + +template <> +void fmt_class_string::format(std::string &out, u64 arg) { + const u8 *data = reinterpret_cast(&get_object(arg)); + + fmt::append(out, "%u.%u.%u.%u", data[0], data[1], data[2], data[3]); +} + +lv2_socket::lv2_socket(utils::serial &ar, lv2_socket_type _type) + : family(ar), type(_type), protocol(ar), so_nbio(ar), so_error(ar), + so_tcp_maxseg(ar) +#ifdef _WIN32 + , + so_reuseaddr(ar), so_reuseport(ar) { +#else +{ + // Try to match structure between different platforms + ar.pos += 8; +#endif + + [[maybe_unused]] const s32 version = GET_SERIALIZATION_VERSION(lv2_net); + + ar(so_rcvtimeo, so_sendtimeo); + + lv2_id = idm::last_id(); + + ar(last_bound_addr); +} + +std::function lv2_socket::load(utils::serial &ar) { + const lv2_socket_type type{ar}; + + shared_ptr sock_lv2; + + switch (type) { + case SYS_NET_SOCK_STREAM: + case SYS_NET_SOCK_DGRAM: { + auto lv2_native = make_shared(ar, type); + ensure(lv2_native->create_socket() >= 0); + sock_lv2 = std::move(lv2_native); + break; + } + case SYS_NET_SOCK_RAW: + sock_lv2 = make_shared(ar, type); + break; + case SYS_NET_SOCK_DGRAM_P2P: + sock_lv2 = make_shared(ar, type); + break; + case SYS_NET_SOCK_STREAM_P2P: + sock_lv2 = make_shared(ar, type); + break; + } + + if (std::memcmp(&sock_lv2->last_bound_addr, std::array{}.data(), + 16)) { + // NOTE: It is allowed fail + sock_lv2->bind(sock_lv2->last_bound_addr); + } + + return [ptr = sock_lv2](void *storage) { + *static_cast *>(storage) = ptr; + }; + ; +} + +void lv2_socket::save(utils::serial &ar, bool save_only_this_class) { + USING_SERIALIZATION_VERSION(lv2_net); + + if (save_only_this_class) { + ar(family, protocol, so_nbio, so_error, so_tcp_maxseg); +#ifdef _WIN32 + ar(so_reuseaddr, so_reuseport); +#else + ar(std::array{}); +#endif + ar(so_rcvtimeo, so_sendtimeo); + ar(last_bound_addr); + return; + } + + ar(type); + + switch (type) { + case SYS_NET_SOCK_STREAM: + case SYS_NET_SOCK_DGRAM: { + static_cast(this)->save(ar); + break; + } + case SYS_NET_SOCK_RAW: + static_cast(this)->save(ar); + break; + case SYS_NET_SOCK_DGRAM_P2P: + static_cast(this)->save(ar); + break; + case SYS_NET_SOCK_STREAM_P2P: + static_cast(this)->save(ar); + break; + } +} + +void sys_net_dump_data(std::string_view desc, const u8 *data, s32 len, + const void *addr) { + const sys_net_sockaddr_in_p2p *p2p_addr = + reinterpret_cast(addr); + + if (p2p_addr) + sys_net_dump.trace("%s(%s:%d:%d): %s", desc, + np::ip_to_string(std::bit_cast(p2p_addr->sin_addr)), + p2p_addr->sin_port, p2p_addr->sin_vport, + fmt::buf_to_hexstring(data, len)); + else + sys_net_dump.trace("%s: %s", desc, fmt::buf_to_hexstring(data, len)); +} + +error_code sys_net_bnet_accept(ppu_thread &ppu, s32 s, + vm::ptr addr, + vm::ptr paddrlen) { + ppu.state += cpu_flag::wait; + + sys_net.warning("sys_net_bnet_accept(s=%d, addr=*0x%x, paddrlen=*0x%x)", s, + addr, paddrlen); + + if (addr.operator bool() != paddrlen.operator bool() || + (paddrlen && *paddrlen < addr.size())) { + return -SYS_NET_EINVAL; + } + + s32 result = 0; + sys_net_sockaddr sn_addr{}; + shared_ptr new_socket{}; + + const auto sock = idm::check( + s, [&, notify = lv2_obj::notify_all_t()](lv2_socket &sock) { + auto [success, res, res_socket, res_addr] = sock.accept(); + + if (success) { + result = res; + sn_addr = res_addr; + new_socket = std::move(res_socket); + return true; + } + + auto lock = sock.lock(); + + sock.poll_queue(idm::get_unlocked>(ppu.id), + lv2_socket::poll_t::read, + [&](bs_t events) -> bool { + if (events & lv2_socket::poll_t::read) { + auto [success, res, res_socket, res_addr] = + sock.accept(false); + if (success) { + result = res; + sn_addr = res_addr; + new_socket = std::move(res_socket); + lv2_obj::awake(&ppu); + return success; + } + } + + sock.set_poll_event(lv2_socket::poll_t::read); + return false; + }); + + lv2_obj::prepare_for_sleep(ppu); + lv2_obj::sleep(ppu); + return false; + }); + + if (!sock) { + return -SYS_NET_EBADF; + } + + if (!sock.ret) { + while (auto state = ppu.state.fetch_sub(cpu_flag::signal)) { + if (is_stopped(state)) { + return {}; + } + + if (state & cpu_flag::signal) { + break; + } + + ppu.state.wait(state); + } + + if (ppu.gpr[3] == static_cast(-SYS_NET_EINTR)) { + return -SYS_NET_EINTR; + } + + if (result < 0) { + return sys_net_error{result}; + } + } + + if (result < 0) { + return sys_net_error{result}; + } + + s32 id_ps3 = result; + + if (!id_ps3) { + ensure(new_socket); + id_ps3 = idm::import_existing(new_socket); + if (id_ps3 == id_manager::id_traits::invalid) { + return -SYS_NET_EMFILE; + } + } + + static_cast(ppu.test_stopped()); + + if (addr) { + *paddrlen = sizeof(sys_net_sockaddr_in); + *addr = sn_addr; + } + + // Socket ID + return not_an_error(id_ps3); +} + +error_code sys_net_bnet_bind(ppu_thread &ppu, s32 s, + vm::cptr addr, u32 addrlen) { + ppu.state += cpu_flag::wait; + + sys_net.warning("sys_net_bnet_bind(s=%d, addr=*0x%x, addrlen=%u)", s, addr, + addrlen); + + if (!addr || addrlen < addr.size()) { + return -SYS_NET_EINVAL; + } + + if (!idm::check_unlocked(s)) { + return -SYS_NET_EBADF; + } + + const sys_net_sockaddr sn_addr = *addr; + + // 0 presumably defaults to AF_INET(to check?) + if (sn_addr.sa_family != SYS_NET_AF_INET && + sn_addr.sa_family != SYS_NET_AF_UNSPEC) { + sys_net.error("sys_net_bnet_bind: unsupported sa_family (%d)", + sn_addr.sa_family); + return -SYS_NET_EAFNOSUPPORT; + } + + const auto sock = idm::check( + s, [&, notify = lv2_obj::notify_all_t()](lv2_socket &sock) -> s32 { + return sock.bind(sn_addr); + }); + + if (!sock) { + return -SYS_NET_EBADF; + } + + if (sock.ret) { + return sys_net_error{sock.ret}; + } + + return CELL_OK; +} + +error_code sys_net_bnet_connect(ppu_thread &ppu, s32 s, + vm::ptr addr, u32 addrlen) { + ppu.state += cpu_flag::wait; + + sys_net.warning("sys_net_bnet_connect(s=%d, addr=*0x%x, addrlen=%u)", s, addr, + addrlen); + + if (!addr || addrlen < addr.size()) { + return -SYS_NET_EINVAL; + } + + if (addr->sa_family != SYS_NET_AF_INET) { + sys_net.error("sys_net_bnet_connect(s=%d): unsupported sa_family (%d)", s, + addr->sa_family); + return -SYS_NET_EAFNOSUPPORT; + } + + if (!idm::check_unlocked(s)) { + return -SYS_NET_EBADF; + } + + s32 result = 0; + sys_net_sockaddr sn_addr = *addr; + + const auto sock = idm::check( + s, [&, notify = lv2_obj::notify_all_t()](lv2_socket &sock) { + const auto success = sock.connect(sn_addr); + + if (success) { + result = *success; + return true; + } + + auto lock = sock.lock(); + + sock.poll_queue(idm::get_unlocked>(ppu.id), + lv2_socket::poll_t::write, + [&](bs_t events) -> bool { + if (events & lv2_socket::poll_t::write) { + result = sock.connect_followup(); + + lv2_obj::awake(&ppu); + return true; + } + sock.set_poll_event(lv2_socket::poll_t::write); + return false; + }); + + lv2_obj::prepare_for_sleep(ppu); + lv2_obj::sleep(ppu); + return false; + }); + + if (!sock) { + return -SYS_NET_EBADF; + } + + if (sock.ret) { + if (result < 0) { + return sys_net_error{result}; + } + + return not_an_error(result); + } + + if (!sock.ret) { + while (auto state = ppu.state.fetch_sub(cpu_flag::signal)) { + if (is_stopped(state)) { + return {}; + } + + if (state & cpu_flag::signal) { + break; + } + + ppu.state.wait(state); + } + + if (ppu.gpr[3] == static_cast(-SYS_NET_EINTR)) { + return -SYS_NET_EINTR; + } + + if (result) { + if (result < 0) { + return sys_net_error{result}; + } + + return not_an_error(result); + } + } + + return CELL_OK; +} + +error_code sys_net_bnet_getpeername(ppu_thread &ppu, s32 s, + vm::ptr addr, + vm::ptr paddrlen) { + ppu.state += cpu_flag::wait; + + sys_net.warning("sys_net_bnet_getpeername(s=%d, addr=*0x%x, paddrlen=*0x%x)", + s, addr, paddrlen); + + // Note: paddrlen is both an input and output argument + if (!addr || !paddrlen || *paddrlen < addr.size()) { + return -SYS_NET_EINVAL; + } + + const auto sock = idm::check( + s, [&, notify = lv2_obj::notify_all_t()](lv2_socket &sock) -> s32 { + auto [res, sn_addr] = sock.getpeername(); + + if (res == CELL_OK) { + *paddrlen = sizeof(sys_net_sockaddr); + *addr = sn_addr; + } + + return res; + }); + + if (!sock) { + return -SYS_NET_EBADF; + } + + if (sock.ret < 0) { + return sys_net_error{sock.ret}; + } + + return CELL_OK; +} + +error_code sys_net_bnet_getsockname(ppu_thread &ppu, s32 s, + vm::ptr addr, + vm::ptr paddrlen) { + ppu.state += cpu_flag::wait; + + sys_net.warning("sys_net_bnet_getsockname(s=%d, addr=*0x%x, paddrlen=*0x%x)", + s, addr, paddrlen); + + // Note: paddrlen is both an input and output argument + if (!addr || !paddrlen || *paddrlen < addr.size()) { + return -SYS_NET_EINVAL; + } + + const auto sock = idm::check( + s, [&, notify = lv2_obj::notify_all_t()](lv2_socket &sock) -> s32 { + auto [res, sn_addr] = sock.getsockname(); + + if (res == CELL_OK) { + *paddrlen = sizeof(sys_net_sockaddr); + *addr = sn_addr; + } + + return res; + }); + + if (!sock) { + return -SYS_NET_EBADF; + } + + if (sock.ret < 0) { + return sys_net_error{sock.ret}; + } + + return CELL_OK; +} + +error_code sys_net_bnet_getsockopt(ppu_thread &ppu, s32 s, s32 level, + s32 optname, vm::ptr optval, + vm::ptr optlen) { + ppu.state += cpu_flag::wait; + + switch (level) { + case SYS_NET_SOL_SOCKET: + sys_net.warning("sys_net_bnet_getsockopt(s=%d, level=SYS_NET_SOL_SOCKET, " + "optname=%s, optval=*0x%x, optlen=%u)", + s, static_cast(optname), optval, optlen); + break; + case SYS_NET_IPPROTO_TCP: + sys_net.warning("sys_net_bnet_getsockopt(s=%d, level=SYS_NET_IPPROTO_TCP, " + "optname=%s, optval=*0x%x, optlen=%u)", + s, static_cast(optname), optval, optlen); + break; + case SYS_NET_IPPROTO_IP: + sys_net.warning("sys_net_bnet_getsockopt(s=%d, level=SYS_NET_IPPROTO_IP, " + "optname=%s, optval=*0x%x, optlen=%u)", + s, static_cast(optname), optval, optlen); + break; + default: + sys_net.warning("sys_net_bnet_getsockopt(s=%d, level=0x%x, optname=0x%x, " + "optval=*0x%x, optlen=%u)", + s, level, optname, optval, optlen); + break; + } + + if (!optval || !optlen) { + return -SYS_NET_EINVAL; + } + + const u32 len = *optlen; + + if (!len) { + return -SYS_NET_EINVAL; + } + + const auto sock = idm::check( + s, [&, notify = lv2_obj::notify_all_t()](lv2_socket &sock) -> s32 { + if (len < sizeof(s32)) { + return -SYS_NET_EINVAL; + } + + const auto &[res, out_val, out_len] = + sock.getsockopt(level, optname, *optlen); + + if (res == CELL_OK) { + std::memcpy(optval.get_ptr(), out_val.ch, out_len); + *optlen = out_len; + } + + return res; + }); + + if (!sock) { + return -SYS_NET_EBADF; + } + + if (sock.ret < 0) { + return sys_net_error{sock.ret}; + } + + return CELL_OK; +} + +error_code sys_net_bnet_listen(ppu_thread &ppu, s32 s, s32 backlog) { + ppu.state += cpu_flag::wait; + + sys_net.warning("sys_net_bnet_listen(s=%d, backlog=%d)", s, backlog); + + if (backlog <= 0) { + return -SYS_NET_EINVAL; + } + + const auto sock = idm::check( + s, [&, notify = lv2_obj::notify_all_t()](lv2_socket &sock) -> s32 { + return sock.listen(backlog); + }); + + if (!sock) { + return -SYS_NET_EBADF; + } + + if (sock.ret < 0) { + return sys_net_error{sock.ret}; + } + + return CELL_OK; +} + +error_code sys_net_bnet_recvfrom(ppu_thread &ppu, s32 s, vm::ptr buf, + u32 len, s32 flags, + vm::ptr addr, + vm::ptr paddrlen) { + ppu.state += cpu_flag::wait; + + sys_net.trace("sys_net_bnet_recvfrom(s=%d, buf=*0x%x, len=%u, flags=0x%x, " + "addr=*0x%x, paddrlen=*0x%x)", + s, buf, len, flags, addr, paddrlen); + + // If addr is null, paddrlen must be null as well + if (!buf || !len || addr.operator bool() != paddrlen.operator bool()) { + return -SYS_NET_EINVAL; + } + + if (flags & ~(SYS_NET_MSG_PEEK | SYS_NET_MSG_DONTWAIT | SYS_NET_MSG_WAITALL | + SYS_NET_MSG_USECRYPTO | SYS_NET_MSG_USESIGNATURE)) { + fmt::throw_exception("sys_net_bnet_recvfrom(s=%d): unknown flags (0x%x)", + flags); + } + + s32 result = 0; + sys_net_sockaddr sn_addr{}; + + const auto sock = idm::check( + s, [&, notify = lv2_obj::notify_all_t()](lv2_socket &sock) { + const auto success = sock.recvfrom(flags, len); + + if (success) { + const auto &[res, vec, res_addr] = *success; + if (res > 0) { + sn_addr = res_addr; + std::memcpy(buf.get_ptr(), vec.data(), res); + sys_net_dump_data("recvfrom", vec.data(), res, &res_addr); + } + + result = res; + return true; + } + + auto lock = sock.lock(); + + sock.poll_queue( + idm::get_unlocked>(ppu.id), + lv2_socket::poll_t::read, + [&](bs_t events) -> bool { + if (events & lv2_socket::poll_t::read) { + const auto success = sock.recvfrom(flags, len, false); + + if (success) { + const auto &[res, vec, res_addr] = *success; + if (res > 0) { + sn_addr = res_addr; + std::memcpy(buf.get_ptr(), vec.data(), res); + sys_net_dump_data("recvfrom", vec.data(), res, &res_addr); + } + result = res; + lv2_obj::awake(&ppu); + return true; + } + } + + if (sock.so_rcvtimeo && + get_guest_system_time() - ppu.start_time > sock.so_rcvtimeo) { + result = -SYS_NET_EWOULDBLOCK; + lv2_obj::awake(&ppu); + return true; + } + + sock.set_poll_event(lv2_socket::poll_t::read); + return false; + }); + + lv2_obj::prepare_for_sleep(ppu); + lv2_obj::sleep(ppu); + return false; + }); + + if (!sock) { + return -SYS_NET_EBADF; + } + + if (!sock.ret) { + + while (auto state = ppu.state.fetch_sub(cpu_flag::signal)) { + if (is_stopped(state)) { + return {}; + } + + if (state & cpu_flag::signal) { + break; + } + + ppu.state.wait(state); + } + + if (ppu.gpr[3] == static_cast(-SYS_NET_EINTR)) { + return -SYS_NET_EINTR; + } + } + + static_cast(ppu.test_stopped()); + + if (result == -SYS_NET_EWOULDBLOCK) { + return not_an_error(result); + } + + if (result >= 0) { + if (addr) { + *paddrlen = sizeof(sys_net_sockaddr_in); + *addr = sn_addr; + } + + return not_an_error(result); + } + + return sys_net_error{result}; +} + +error_code sys_net_bnet_recvmsg(ppu_thread &ppu, s32 s, + vm::ptr msg, s32 flags) { + ppu.state += cpu_flag::wait; + + sys_net.todo("sys_net_bnet_recvmsg(s=%d, msg=*0x%x, flags=0x%x)", s, msg, + flags); + return CELL_OK; +} + +error_code sys_net_bnet_sendmsg(ppu_thread &ppu, s32 s, + vm::cptr msg, s32 flags) { + ppu.state += cpu_flag::wait; + + sys_net.warning("sys_net_bnet_sendmsg(s=%d, msg=*0x%x, flags=0x%x)", s, msg, + flags); + + if (flags & ~(SYS_NET_MSG_DONTWAIT | SYS_NET_MSG_WAITALL | + SYS_NET_MSG_USECRYPTO | SYS_NET_MSG_USESIGNATURE)) { + fmt::throw_exception("sys_net_bnet_sendmsg(s=%d): unknown flags (0x%x)", + flags); + } + + s32 result{}; + + const auto sock = idm::check(s, [&](lv2_socket &sock) { + auto netmsg = msg.get_ptr(); + const auto success = sock.sendmsg(flags, *netmsg); + + if (success) { + result = *success; + + return true; + } + + sock.poll_queue(idm::get_unlocked>(ppu.id), + lv2_socket::poll_t::write, + [&](bs_t events) -> bool { + if (events & lv2_socket::poll_t::write) { + const auto success = + sock.sendmsg(flags, *netmsg, false); + + if (success) { + result = *success; + lv2_obj::awake(&ppu); + return true; + } + } + + sock.set_poll_event(lv2_socket::poll_t::write); + return false; + }); + + lv2_obj::sleep(ppu); + return false; + }); + + if (!sock) { + return -SYS_NET_EBADF; + } + + if (!sock.ret) { + while (true) { + const auto state = ppu.state.fetch_sub(cpu_flag::signal); + if (is_stopped(state) || state & cpu_flag::signal) { + break; + } + thread_ctrl::wait_on(ppu.state, state); + } + + if (ppu.gpr[3] == static_cast(-SYS_NET_EINTR)) { + return -SYS_NET_EINTR; + } + } + + if (result >= 0 || result == -SYS_NET_EWOULDBLOCK) { + return not_an_error(result); + } + + return sys_net_error{result}; +} + +error_code sys_net_bnet_sendto(ppu_thread &ppu, s32 s, vm::cptr buf, + u32 len, s32 flags, + vm::cptr addr, u32 addrlen) { + ppu.state += cpu_flag::wait; + + sys_net.trace("sys_net_bnet_sendto(s=%d, buf=*0x%x, len=%u, flags=0x%x, " + "addr=*0x%x, addrlen=%u)", + s, buf, len, flags, addr, addrlen); + + if (flags & ~(SYS_NET_MSG_DONTWAIT | SYS_NET_MSG_WAITALL | + SYS_NET_MSG_USECRYPTO | SYS_NET_MSG_USESIGNATURE)) { + fmt::throw_exception("sys_net_bnet_sendto(s=%d): unknown flags (0x%x)", + flags); + } + + if (addr && addrlen < 8) { + sys_net.error("sys_net_bnet_sendto(s=%d): bad addrlen (%u)", s, addrlen); + return -SYS_NET_EINVAL; + } + + if (addr && addr->sa_family != SYS_NET_AF_INET) { + sys_net.error("sys_net_bnet_sendto(s=%d): unsupported sa_family (%d)", s, + addr->sa_family); + return -SYS_NET_EAFNOSUPPORT; + } + + sys_net_dump_data("sendto", static_cast(buf.get_ptr()), len, + addr ? addr.get_ptr() : nullptr); + + const std::optional sn_addr = + addr ? std::optional(*addr) : std::nullopt; + const std::vector buf_copy(vm::_ptr(buf.addr()), + vm::_ptr(buf.addr()) + len); + s32 result{}; + + const auto sock = idm::check( + s, [&, notify = lv2_obj::notify_all_t()](lv2_socket &sock) { + auto success = sock.sendto(flags, buf_copy, sn_addr); + + if (success) { + result = *success; + return true; + } + + auto lock = sock.lock(); + + // Enable write event + sock.poll_queue(idm::get_unlocked>(ppu.id), + lv2_socket::poll_t::write, + [&](bs_t events) -> bool { + if (events & lv2_socket::poll_t::write) { + auto success = + sock.sendto(flags, buf_copy, sn_addr, false); + if (success) { + result = *success; + lv2_obj::awake(&ppu); + return true; + } + } + + if (sock.so_sendtimeo && + get_guest_system_time() - ppu.start_time > + sock.so_sendtimeo) { + result = -SYS_NET_EWOULDBLOCK; + lv2_obj::awake(&ppu); + return true; + } + + sock.set_poll_event(lv2_socket::poll_t::write); + return false; + }); + + lv2_obj::prepare_for_sleep(ppu); + lv2_obj::sleep(ppu); + return false; + }); + + if (!sock) { + return -SYS_NET_EBADF; + } + + if (!sock.ret) { + while (true) { + const auto state = ppu.state.fetch_sub(cpu_flag::signal); + if (is_stopped(state) || state & cpu_flag::signal) { + break; + } + ppu.state.wait(state); + } + + if (ppu.gpr[3] == static_cast(-SYS_NET_EINTR)) { + return -SYS_NET_EINTR; + } + } + + if (result >= 0 || result == -SYS_NET_EWOULDBLOCK) { + return not_an_error(result); + } + + return sys_net_error{result}; +} + +error_code sys_net_bnet_setsockopt(ppu_thread &ppu, s32 s, s32 level, + s32 optname, vm::cptr optval, + u32 optlen) { + ppu.state += cpu_flag::wait; + + switch (level) { + case SYS_NET_SOL_SOCKET: + sys_net.warning("sys_net_bnet_setsockopt(s=%d, level=SYS_NET_SOL_SOCKET, " + "optname=%s, optval=*0x%x, optlen=%u)", + s, static_cast(optname), optval, optlen); + break; + case SYS_NET_IPPROTO_TCP: + sys_net.warning("sys_net_bnet_setsockopt(s=%d, level=SYS_NET_IPPROTO_TCP, " + "optname=%s, optval=*0x%x, optlen=%u)", + s, static_cast(optname), optval, optlen); + break; + case SYS_NET_IPPROTO_IP: + sys_net.warning("sys_net_bnet_setsockopt(s=%d, level=SYS_NET_IPPROTO_IP, " + "optname=%s, optval=*0x%x, optlen=%u)", + s, static_cast(optname), optval, optlen); + break; + default: + sys_net.warning("sys_net_bnet_setsockopt(s=%d, level=0x%x, optname=0x%x, " + "optval=*0x%x, optlen=%u)", + s, level, optname, optval, optlen); + break; + } + + switch (optlen) { + case 1: + sys_net.warning("optval: 0x%02X", + *static_cast(optval.get_ptr())); + break; + case 2: + sys_net.warning("optval: 0x%04X", + *static_cast *>(optval.get_ptr())); + break; + case 4: + sys_net.warning("optval: 0x%08X", + *static_cast *>(optval.get_ptr())); + break; + case 8: + sys_net.warning("optval: 0x%016X", + *static_cast *>(optval.get_ptr())); + break; + } + + if (optlen < sizeof(s32)) { + return -SYS_NET_EINVAL; + } + + std::vector optval_copy(vm::_ptr(optval.addr()), + vm::_ptr(optval.addr() + optlen)); + + const auto sock = idm::check( + s, [&, notify = lv2_obj::notify_all_t()](lv2_socket &sock) -> s32 { + return sock.setsockopt(level, optname, optval_copy); + }); + + if (!sock) { + return -SYS_NET_EBADF; + } + + if (sock.ret < 0) { + return sys_net_error{sock.ret}; + } + + return not_an_error(sock.ret); +} + +error_code sys_net_bnet_shutdown(ppu_thread &ppu, s32 s, s32 how) { + ppu.state += cpu_flag::wait; + + sys_net.warning("sys_net_bnet_shutdown(s=%d, how=%d)", s, how); + + if (how < 0 || how > 2) { + return -SYS_NET_EINVAL; + } + + const auto sock = idm::check( + s, [&, notify = lv2_obj::notify_all_t()](lv2_socket &sock) -> s32 { + return sock.shutdown(how); + }); + + if (!sock) { + return -SYS_NET_EBADF; + } + + if (sock.ret < 0) { + return sys_net_error{sock.ret}; + } + + return CELL_OK; +} + +error_code sys_net_bnet_socket(ppu_thread &ppu, lv2_socket_family family, + lv2_socket_type type, lv2_ip_protocol protocol) { + ppu.state += cpu_flag::wait; + + sys_net.warning("sys_net_bnet_socket(family=%s, type=%s, protocol=%s)", + family, type, protocol); + + if (family != SYS_NET_AF_INET) { + sys_net.error("sys_net_bnet_socket(): unknown family (%d)", family); + } + + if (type != SYS_NET_SOCK_STREAM && type != SYS_NET_SOCK_DGRAM && + type != SYS_NET_SOCK_RAW && type != SYS_NET_SOCK_DGRAM_P2P && + type != SYS_NET_SOCK_STREAM_P2P) { + sys_net.error("sys_net_bnet_socket(): unsupported type (%d)", type); + return -SYS_NET_EPROTONOSUPPORT; + } + + shared_ptr sock_lv2; + + switch (type) { + case SYS_NET_SOCK_STREAM: + case SYS_NET_SOCK_DGRAM: { + auto lv2_native = make_shared(family, type, protocol); + if (s32 result = lv2_native->create_socket(); result < 0) { + return sys_net_error{result}; + } + + sock_lv2 = std::move(lv2_native); + break; + } + case SYS_NET_SOCK_RAW: + sock_lv2 = make_shared(family, type, protocol); + break; + case SYS_NET_SOCK_DGRAM_P2P: + sock_lv2 = make_shared(family, type, protocol); + break; + case SYS_NET_SOCK_STREAM_P2P: + sock_lv2 = make_shared(family, type, protocol); + break; + } + + const s32 s = idm::import_existing(sock_lv2); + + // Can't allocate more than 1000 sockets + if (s == id_manager::id_traits::invalid) { + return -SYS_NET_EMFILE; + } + + sock_lv2->set_lv2_id(s); + + return not_an_error(s); +} + +error_code sys_net_bnet_close(ppu_thread &ppu, s32 s) { + ppu.state += cpu_flag::wait; + + sys_net.warning("sys_net_bnet_close(s=%d)", s); + + auto sock = idm::withdraw(s); + + if (!sock) { + return -SYS_NET_EBADF; + } + + if (sock->get_queue_size()) { + sock->abort_socket(0); + } + + sock->close(); + + { + // Ensures the socket has no lingering copy from the network thread + std::lock_guard nw_lock(g_fxo->get().mutex_thread_loop); + sock.reset(); + } + + return CELL_OK; +} + +error_code sys_net_bnet_poll(ppu_thread &ppu, vm::ptr fds, + s32 nfds, s32 ms) { + ppu.state += cpu_flag::wait; + + sys_net.trace("sys_net_bnet_poll(fds=*0x%x, nfds=%d, ms=%d)", fds, nfds, ms); + + if (nfds <= 0) { + return not_an_error(0); + } + + atomic_t signaled{0}; + + u64 timeout = ms < 0 ? 0 : ms * 1000ull; + + std::vector fds_buf; + + { + fds_buf.assign(fds.get_ptr(), fds.get_ptr() + nfds); + + lv2_obj::prepare_for_sleep(ppu); + + std::unique_lock nw_lock(g_fxo->get().mutex_thread_loop); + std::shared_lock lock(id_manager::g_mutex); + + std::vector<::pollfd> _fds(nfds); +#ifdef _WIN32 + std::vector connecting(nfds); +#endif + + for (s32 i = 0; i < nfds; i++) { + _fds[i].fd = -1; + fds_buf[i].revents = 0; + + if (fds_buf[i].fd < 0) { + continue; + } + + if (auto sock = idm::check_unlocked(fds_buf[i].fd)) { + signaled += sock->poll(fds_buf[i], _fds[i]); +#ifdef _WIN32 + connecting[i] = sock->is_connecting(); +#endif + } else { + fds_buf[i].revents |= SYS_NET_POLLNVAL; + signaled++; + } + } + +#ifdef _WIN32 + windows_poll(_fds, nfds, 0, connecting); +#else + ::poll(_fds.data(), nfds, 0); +#endif + for (s32 i = 0; i < nfds; i++) { + if (_fds[i].revents & (POLLIN | POLLHUP)) + fds_buf[i].revents |= SYS_NET_POLLIN; + if (_fds[i].revents & POLLOUT) + fds_buf[i].revents |= SYS_NET_POLLOUT; + if (_fds[i].revents & POLLERR) + fds_buf[i].revents |= SYS_NET_POLLERR; + + if (fds_buf[i].revents) { + signaled++; + } + } + + if (ms == 0 || signaled) { + lock.unlock(); + nw_lock.unlock(); + std::memcpy(fds.get_ptr(), fds_buf.data(), nfds * sizeof(sys_net_pollfd)); + return not_an_error(signaled); + } + + for (s32 i = 0; i < nfds; i++) { + if (fds_buf[i].fd < 0) { + continue; + } + + if (auto sock = idm::check_unlocked(fds_buf[i].fd)) { + auto lock = sock->lock(); + +#ifdef _WIN32 + sock->set_connecting(connecting[i]); +#endif + + bs_t selected = +lv2_socket::poll_t::error; + + if (fds_buf[i].events & SYS_NET_POLLIN) + selected += lv2_socket::poll_t::read; + if (fds_buf[i].events & SYS_NET_POLLOUT) + selected += lv2_socket::poll_t::write; + // if (fds_buf[i].events & SYS_NET_POLLPRI) // Unimplemented + // selected += lv2_socket::poll::error; + + sock->poll_queue(idm::get_unlocked>(ppu.id), + selected, + [sock, selected, &fds_buf, i, &signaled, + &ppu](bs_t events) { + if (events & selected) { + if (events & selected & lv2_socket::poll_t::read) + fds_buf[i].revents |= SYS_NET_POLLIN; + if (events & selected & lv2_socket::poll_t::write) + fds_buf[i].revents |= SYS_NET_POLLOUT; + if (events & selected & lv2_socket::poll_t::error) + fds_buf[i].revents |= SYS_NET_POLLERR; + + signaled++; + sock->queue_wake(&ppu); + return true; + } + + sock->set_poll_event(selected); + return false; + }); + } + } + + lv2_obj::sleep(ppu, timeout); + } + + bool has_timedout = false; + + while (auto state = ppu.state.fetch_sub(cpu_flag::signal)) { + if (is_stopped(state)) { + return {}; + } + + if (state & cpu_flag::signal) { + break; + } + + if (timeout) { + if (lv2_obj::wait_timeout(timeout, &ppu)) { + // Wait for rescheduling + if (ppu.check_state()) { + return {}; + } + + has_timedout = network_clear_queue(ppu); + clear_ppu_to_awake(ppu); + ppu.state -= cpu_flag::signal; + break; + } + } else { + ppu.state.wait(state); + } + } + + if (!has_timedout && !signaled) { + return -SYS_NET_EINTR; + } + + std::memcpy(fds.get_ptr(), fds_buf.data(), nfds * sizeof(fds[0])); + + return not_an_error(signaled); +} + +error_code sys_net_bnet_select(ppu_thread &ppu, s32 nfds, + vm::ptr readfds, + vm::ptr writefds, + vm::ptr exceptfds, + vm::ptr _timeout) { + ppu.state += cpu_flag::wait; + + sys_net.trace("sys_net_bnet_select(nfds=%d, readfds=*0x%x, writefds=*0x%x, " + "exceptfds=*0x%x, timeout=*0x%x(%d:%d))", + nfds, readfds, writefds, exceptfds, _timeout, + _timeout ? _timeout->tv_sec.value() : 0, + _timeout ? _timeout->tv_usec.value() : 0); + + atomic_t signaled{0}; + + if (exceptfds) { + struct log_t { + atomic_t logged = false; + }; + + if (!g_fxo->get().logged.exchange(true)) { + sys_net.error("sys_net_bnet_select(): exceptfds not implemented"); + } + } + + sys_net_fd_set rread{}, _readfds{}; + sys_net_fd_set rwrite{}, _writefds{}; + sys_net_fd_set rexcept{}, _exceptfds{}; + u64 timeout = + !_timeout ? 0 : _timeout->tv_sec * 1000000ull + _timeout->tv_usec; + + if (nfds > 0 && nfds <= 1024) { + if (readfds) + _readfds = *readfds; + if (writefds) + _writefds = *writefds; + if (exceptfds) + _exceptfds = *exceptfds; + + std::lock_guard nw_lock(g_fxo->get().mutex_thread_loop); + reader_lock lock(id_manager::g_mutex); + + std::vector<::pollfd> _fds(nfds); +#ifdef _WIN32 + std::vector connecting(nfds); +#endif + + for (s32 i = 0; i < nfds; i++) { + _fds[i].fd = -1; + bs_t selected{}; + + if (readfds && _readfds.bit(i)) + selected += lv2_socket::poll_t::read; + if (writefds && _writefds.bit(i)) + selected += lv2_socket::poll_t::write; + // if (exceptfds && _exceptfds.bit(i)) + // selected += lv2_socket::poll::error; + + if (selected) { + selected += lv2_socket::poll_t::error; + } else { + continue; + } + + if (auto sock = idm::check_unlocked( + (lv2_socket::id_base & -1024) + i)) { + auto [read_set, write_set, except_set] = + sock->select(selected, _fds[i]); + + if (read_set || write_set || except_set) { + signaled++; + } + + if (read_set) { + rread.set(i); + } + + if (write_set) { + rwrite.set(i); + } + + if (except_set) { + rexcept.set(i); + } + +#ifdef _WIN32 + connecting[i] = sock->is_connecting(); +#endif + } else { + return -SYS_NET_EBADF; + } + } + +#ifdef _WIN32 + windows_poll(_fds, nfds, 0, connecting); +#else + ::poll(_fds.data(), nfds, 0); +#endif + for (s32 i = 0; i < nfds; i++) { + bool sig = false; + if (_fds[i].revents & (POLLIN | POLLHUP | POLLERR)) + sig = true, rread.set(i); + if (_fds[i].revents & (POLLOUT | POLLERR)) + sig = true, rwrite.set(i); + + if (sig) { + signaled++; + } + } + + if ((_timeout && !timeout) || signaled) { + if (readfds) + *readfds = rread; + if (writefds) + *writefds = rwrite; + if (exceptfds) + *exceptfds = rexcept; + return not_an_error(signaled); + } + + for (s32 i = 0; i < nfds; i++) { + bs_t selected{}; + + if (readfds && _readfds.bit(i)) + selected += lv2_socket::poll_t::read; + if (writefds && _writefds.bit(i)) + selected += lv2_socket::poll_t::write; + // if (exceptfds && _exceptfds.bit(i)) + // selected += lv2_socket::poll_t::error; + + if (selected) { + selected += lv2_socket::poll_t::error; + } else { + continue; + } + + if (auto sock = idm::check_unlocked( + (lv2_socket::id_base & -1024) + i)) { + auto lock = sock->lock(); +#ifdef _WIN32 + sock->set_connecting(connecting[i]); +#endif + + sock->poll_queue( + idm::get_unlocked>(ppu.id), selected, + [sock, selected, i, &rread, &rwrite, &rexcept, &signaled, + &ppu](bs_t events) { + if (events & selected) { + if (selected & lv2_socket::poll_t::read && + events & + (lv2_socket::poll_t::read + lv2_socket::poll_t::error)) + rread.set(i); + if (selected & lv2_socket::poll_t::write && + events & + (lv2_socket::poll_t::write + lv2_socket::poll_t::error)) + rwrite.set(i); + // if (events & (selected & lv2_socket::poll::error)) + // rexcept.set(i); + + signaled++; + sock->queue_wake(&ppu); + return true; + } + + sock->set_poll_event(selected); + return false; + }); + } else { + return -SYS_NET_EBADF; + } + } + + lv2_obj::sleep(ppu, timeout); + } else { + return -SYS_NET_EINVAL; + } + + bool has_timedout = false; + + while (auto state = ppu.state.fetch_sub(cpu_flag::signal)) { + if (is_stopped(state)) { + return {}; + } + + if (state & cpu_flag::signal) { + break; + } + + if (timeout) { + if (lv2_obj::wait_timeout(timeout, &ppu)) { + // Wait for rescheduling + if (ppu.check_state()) { + return {}; + } + + has_timedout = network_clear_queue(ppu); + clear_ppu_to_awake(ppu); + ppu.state -= cpu_flag::signal; + break; + } + } else { + ppu.state.wait(state); + } + } + + if (!has_timedout && !signaled) { + return -SYS_NET_EINTR; + } + + if (readfds) + *readfds = rread; + if (writefds) + *writefds = rwrite; + if (exceptfds) + *exceptfds = rexcept; + + return not_an_error(signaled); +} + +error_code _sys_net_open_dump(ppu_thread &ppu, s32 len, s32 flags) { + ppu.state += cpu_flag::wait; + + sys_net.todo("_sys_net_open_dump(len=%d, flags=0x%x)", len, flags); + return CELL_OK; +} + +error_code _sys_net_read_dump(ppu_thread &ppu, s32 id, vm::ptr buf, + s32 len, vm::ptr pflags) { + ppu.state += cpu_flag::wait; + + sys_net.todo("_sys_net_read_dump(id=0x%x, buf=*0x%x, len=%d, pflags=*0x%x)", + id, buf, len, pflags); + return CELL_OK; +} + +error_code _sys_net_close_dump(ppu_thread &ppu, s32 id, vm::ptr pflags) { + ppu.state += cpu_flag::wait; + + sys_net.todo("_sys_net_close_dump(id=0x%x, pflags=*0x%x)", id, pflags); + return CELL_OK; +} + +error_code _sys_net_write_dump(ppu_thread &ppu, s32 id, vm::cptr buf, + s32 len, u32 unknown) { + ppu.state += cpu_flag::wait; + + sys_net.todo("_sys_net_write_dump(id=0x%x, buf=*0x%x, len=%d, unk=0x%x)", id, + buf, len, unknown); + return CELL_OK; +} + +error_code lv2_socket::abort_socket(s32 flags) { + decltype(queue) qcopy; + { + std::lock_guard lock(mutex); + + if (queue.empty()) { + if (flags & SYS_NET_ABORT_STRICT_CHECK) { + // Strict error checking: ENOENT if nothing happened + return -SYS_NET_ENOENT; + } + + // TODO: Abort the subsequent function called on this socket (need to + // investigate correct behaviour) + return CELL_OK; + } + + qcopy = std::move(queue); + queue = {}; + events.store({}); + } + + for (auto &[ppu, _] : qcopy) { + if (!ppu) + continue; + + // Avoid possible double signaling + network_clear_queue(*ppu); + clear_ppu_to_awake(*ppu); + + sys_net.warning("lv2_socket::abort_socket(): waking up \"%s\": (func: %s, " + "r3=0x%x, r4=0x%x, r5=0x%x, r6=0x%x)", + ppu->get_name(), ppu->current_function, ppu->gpr[3], + ppu->gpr[4], ppu->gpr[5], ppu->gpr[6]); + ppu->gpr[3] = static_cast(-SYS_NET_EINTR); + lv2_obj::append(ppu.get()); + } + + const u32 num_waiters = ::size32(qcopy); + if (num_waiters && + (type == SYS_NET_SOCK_STREAM || type == SYS_NET_SOCK_DGRAM)) { + auto &nc = g_fxo->get(); + const u32 prev_value = nc.num_polls.fetch_sub(num_waiters); + ensure(prev_value >= num_waiters); + } + + lv2_obj::awake_all(); + return CELL_OK; +} + +error_code sys_net_abort(ppu_thread &ppu, s32 type, u64 arg, s32 flags) { + ppu.state += cpu_flag::wait; + + sys_net.warning("sys_net_abort(type=%d, arg=0x%x, flags=0x%x)", type, arg, + flags); + + enum abort_type : s32 { + _socket, + resolver, + type_2, // ?? + type_3, // ?? + all, + }; + + switch (type) { + case _socket: { + std::lock_guard nw_lock(g_fxo->get().mutex_thread_loop); + + const auto sock = idm::get_unlocked(static_cast(arg)); + + if (!sock) { + return -SYS_NET_EBADF; + } + + return sock->abort_socket(flags); + } + case all: { + std::vector sockets; + + idm::select( + [&](u32 id, lv2_socket &) { sockets.emplace_back(id); }); + + s32 failed = 0; + + for (u32 id : sockets) { + const auto sock = idm::withdraw(id); + + if (!sock) { + failed++; + continue; + } + + if (sock->get_queue_size()) + sys_net.error("ABORT 4"); + + sock->close(); + + sys_net.success("lv2_socket::handle_abort(): Closed socket %d", id); + } + + // Ensures the socket has no lingering copy from the network thread + g_fxo->get().mutex_thread_loop.lock_unlock(); + + return not_an_error(::narrow(sockets.size()) - failed); + } + case resolver: + case type_2: + case type_3: { + break; + } + default: + return -SYS_NET_EINVAL; + } + + return CELL_OK; +} + +struct net_infoctl_cmd_9_t { + be_t zero; + vm::bptr server_name; + // More (TODO) +}; + +error_code sys_net_infoctl(ppu_thread &ppu, s32 cmd, vm::ptr arg) { + ppu.state += cpu_flag::wait; + + sys_net.todo("sys_net_infoctl(cmd=%d, arg=*0x%x)", cmd, arg); + + // TODO + switch (cmd) { + case 9: { + constexpr auto nameserver = "nameserver \0"sv; + + char buffer[nameserver.size() + 80]{}; + std::memcpy(buffer, nameserver.data(), nameserver.size()); + + auto &nph = g_fxo->get>(); + const auto dns_str = np::ip_to_string(nph.get_dns_ip()); + std::memcpy(buffer + nameserver.size() - 1, dns_str.data(), dns_str.size()); + + std::string_view name{buffer}; + vm::static_ptr_cast(arg)->zero = 0; + std::memcpy( + vm::static_ptr_cast(arg)->server_name.get_ptr(), + name.data(), name.size()); + break; + } + default: + break; + } + + return CELL_OK; +} + +error_code sys_net_control(ppu_thread &ppu, u32 arg1, s32 arg2, + vm::ptr arg3, s32 arg4) { + ppu.state += cpu_flag::wait; + + sys_net.todo("sys_net_control(0x%x, %d, *0x%x, %d)", arg1, arg2, arg3, arg4); + return CELL_OK; +} + +error_code sys_net_bnet_ioctl(ppu_thread &ppu, s32 arg1, u32 arg2, u32 arg3) { + ppu.state += cpu_flag::wait; + + sys_net.todo("sys_net_bnet_ioctl(%d, 0x%x, 0x%x)", arg1, arg2, arg3); + return CELL_OK; +} + +error_code sys_net_bnet_sysctl(ppu_thread &ppu, u32 arg1, u32 arg2, u32 arg3, + vm::ptr arg4, u32 arg5, u32 arg6) { + ppu.state += cpu_flag::wait; + + sys_net.todo("sys_net_bnet_sysctl(0x%x, 0x%x, 0x%x, *0x%x, 0x%x, 0x%x)", arg1, + arg2, arg3, arg4, arg5, arg6); + return CELL_OK; +} + +error_code sys_net_eurus_post_command(ppu_thread &ppu, s32 arg1, u32 arg2, + u32 arg3) { + ppu.state += cpu_flag::wait; + + sys_net.todo("sys_net_eurus_post_command(%d, 0x%x, 0x%x)", arg1, arg2, arg3); + return CELL_OK; +} diff --git a/kernel/cellos/src/sys_net/lv2_socket.cpp b/kernel/cellos/src/sys_net/lv2_socket.cpp new file mode 100644 index 000000000..3ecbc47d3 --- /dev/null +++ b/kernel/cellos/src/sys_net/lv2_socket.cpp @@ -0,0 +1,157 @@ +#include "stdafx.h" + +#include "sys_net/lv2_socket.h" +#include "sys_net/network_context.h" + +LOG_CHANNEL(sys_net); + +lv2_socket::lv2_socket(lv2_socket_family family, lv2_socket_type type, + lv2_ip_protocol protocol) { + this->family = family; + this->type = type; + this->protocol = protocol; +} + +std::unique_lock lv2_socket::lock() { + return std::unique_lock(mutex); +} + +lv2_socket_family lv2_socket::get_family() const { return family; } + +lv2_socket_type lv2_socket::get_type() const { return type; } +lv2_ip_protocol lv2_socket::get_protocol() const { return protocol; } +std::size_t lv2_socket::get_queue_size() const { return queue.size(); } +socket_type lv2_socket::get_socket() const { return native_socket; } + +#ifdef _WIN32 +bool lv2_socket::is_connecting() const { return connecting; } +void lv2_socket::set_connecting(bool connecting) { + this->connecting = connecting; +} +#endif + +void lv2_socket::set_lv2_id(u32 id) { lv2_id = id; } + +bs_t lv2_socket::get_events() const { + return events.load(); +} + +void lv2_socket::set_poll_event(bs_t event) { + events += event; +} + +void lv2_socket::poll_queue( + shared_ptr ppu, bs_t event, + std::function)> poll_cb) { + set_poll_event(event); + queue.emplace_back(std::move(ppu), poll_cb); + + // Makes sure network_context thread is awaken + if (type == SYS_NET_SOCK_STREAM || type == SYS_NET_SOCK_DGRAM) { + auto &nc = g_fxo->get(); + const u32 prev_value = nc.num_polls.fetch_add(1); + if (!prev_value) { + nc.num_polls.notify_one(); + } + } +} + +u32 lv2_socket::clear_queue(ppu_thread *ppu) { + std::lock_guard lock(mutex); + + u32 cleared = 0; + + for (auto it = queue.begin(); it != queue.end();) { + if (it->first.get() == ppu) { + it = queue.erase(it); + cleared++; + continue; + } + + it++; + } + + if (queue.empty()) { + events.store({}); + } + + if (cleared && (type == SYS_NET_SOCK_STREAM || type == SYS_NET_SOCK_DGRAM)) { + // Makes sure network_context thread can go back to sleep if there is no + // active polling + const u32 prev_value = + g_fxo->get().num_polls.fetch_sub(cleared); + ensure(prev_value >= cleared); + } + + return cleared; +} + +void lv2_socket::handle_events(const pollfd &native_pfd, + [[maybe_unused]] bool unset_connecting) { + bs_t events_happening{}; + + if (native_pfd.revents & (POLLIN | POLLHUP) && + events.test_and_reset(lv2_socket::poll_t::read)) + events_happening += lv2_socket::poll_t::read; + if (native_pfd.revents & POLLOUT && + events.test_and_reset(lv2_socket::poll_t::write)) + events_happening += lv2_socket::poll_t::write; + if (native_pfd.revents & POLLERR && + events.test_and_reset(lv2_socket::poll_t::error)) + events_happening += lv2_socket::poll_t::error; + + if (events_happening || (!queue.empty() && (so_rcvtimeo || so_sendtimeo))) { + std::lock_guard lock(mutex); +#ifdef _WIN32 + if (unset_connecting) + set_connecting(false); +#endif + u32 handled = 0; + + for (auto it = queue.begin(); it != queue.end();) { + if (it->second(events_happening)) { + it = queue.erase(it); + handled++; + continue; + } + + it++; + } + + if (handled && + (type == SYS_NET_SOCK_STREAM || type == SYS_NET_SOCK_DGRAM)) { + const u32 prev_value = + g_fxo->get().num_polls.fetch_sub(handled); + ensure(prev_value >= handled); + } + + if (queue.empty()) { + events.store({}); + } + } +} + +void lv2_socket::queue_wake(ppu_thread *ppu) { + switch (type) { + case SYS_NET_SOCK_STREAM: + case SYS_NET_SOCK_DGRAM: + g_fxo->get().add_ppu_to_awake(ppu); + break; + case SYS_NET_SOCK_DGRAM_P2P: + case SYS_NET_SOCK_STREAM_P2P: + g_fxo->get().add_ppu_to_awake(ppu); + break; + default: + break; + } +} + +lv2_socket &lv2_socket::operator=(thread_state s) noexcept { + if (s == thread_state::destroying_context) { + close(); + } + + return *this; +} + +lv2_socket::~lv2_socket() noexcept {} diff --git a/kernel/cellos/src/sys_net/lv2_socket_native.cpp b/kernel/cellos/src/sys_net/lv2_socket_native.cpp new file mode 100644 index 000000000..c58c35c36 --- /dev/null +++ b/kernel/cellos/src/sys_net/lv2_socket_native.cpp @@ -0,0 +1,1182 @@ +#include "stdafx.h" + +#include "Emu/NP/np_dnshook.h" +#include "Emu/NP/np_handler.h" +#include "cellos/sys_net.h" +#include "sys_net/lv2_socket_native.h" +#include "sys_net/sys_net_helpers.h" + +#ifdef _WIN32 +constexpr SOCKET invalid_socket = INVALID_SOCKET; +#else +constexpr int invalid_socket = -1; +#endif + +LOG_CHANNEL(sys_net); + +lv2_socket_native::lv2_socket_native(lv2_socket_family family, + lv2_socket_type type, + lv2_ip_protocol protocol) + : lv2_socket(family, type, protocol) {} + +lv2_socket_native::lv2_socket_native(utils::serial &ar, lv2_socket_type type) + : lv2_socket(make_exact(ar), type) { + [[maybe_unused]] const s32 version = GET_SERIALIZATION_VERSION(lv2_net); + +#ifdef _WIN32 + ar(so_reuseaddr, so_reuseport); +#else + std::array dummy{}; + ar(dummy); + + if (dummy != std::array{}) { + sys_net.error("[Native] Savestate tried to load Win32 specific data, " + "compatibility may be affected"); + } +#endif + + if (version >= 2) { + // Flag to signal failure of TCP connection on socket start + ar(feign_tcp_conn_failure); + } +} + +void lv2_socket_native::save(utils::serial &ar) { + USING_SERIALIZATION_VERSION(lv2_net); + + lv2_socket::save(ar, true); +#ifdef _WIN32 + ar(so_reuseaddr, so_reuseport); +#else + ar(std::array{}); +#endif + + ar(is_socket_connected()); +} + +lv2_socket_native::~lv2_socket_native() noexcept { lv2_socket_native::close(); } + +s32 lv2_socket_native::create_socket() { + ensure(family == SYS_NET_AF_INET); + ensure(type == SYS_NET_SOCK_STREAM || type == SYS_NET_SOCK_DGRAM); + ensure(protocol == SYS_NET_IPPROTO_IP || protocol == SYS_NET_IPPROTO_TCP || + protocol == SYS_NET_IPPROTO_UDP); + + const int native_domain = AF_INET; + + const int native_type = + type == SYS_NET_SOCK_STREAM ? SOCK_STREAM : SOCK_DGRAM; + + int native_proto = protocol == SYS_NET_IPPROTO_TCP ? IPPROTO_TCP + : protocol == SYS_NET_IPPROTO_UDP ? IPPROTO_UDP + : IPPROTO_IP; + + auto socket_res = ::socket(native_domain, native_type, native_proto); + + if (socket_res == invalid_socket) { + return -get_last_error(false); + } + + set_socket(socket_res, family, type, protocol); + return CELL_OK; +} + +void lv2_socket_native::set_socket(socket_type native_socket, + lv2_socket_family family, + lv2_socket_type type, + lv2_ip_protocol protocol) { + this->native_socket = native_socket; + this->family = family; + this->type = type; + this->protocol = protocol; + + set_default_buffers(); + set_non_blocking(); +} + +std::tuple, sys_net_sockaddr> +lv2_socket_native::accept(bool is_lock) { + std::unique_lock lock(mutex, std::defer_lock); + + if (is_lock) { + lock.lock(); + } + + ::sockaddr_storage native_addr; + ::socklen_t native_addrlen = sizeof(native_addr); + + if (feign_tcp_conn_failure) { + sys_net.error( + "Calling socket::accept() from a previously connected socket!"); + } + + socket_type client_socket = + ::accept(native_socket, reinterpret_cast(&native_addr), + &native_addrlen); + + if (client_socket != invalid_socket) { + auto newsock = make_single(family, type, protocol); + newsock->set_socket(client_socket, family, type, protocol); + + // Sockets inherit non blocking behaviour from their parent + newsock->so_nbio = so_nbio; + + sys_net_sockaddr ps3_addr = native_addr_to_sys_net_addr(native_addr); + + return {true, 0, std::move(newsock), ps3_addr}; + } + + if (auto result = get_last_error(!so_nbio); result) { + return {true, -result, {}, {}}; + } + + return {false, {}, {}, {}}; +} + +s32 lv2_socket_native::bind(const sys_net_sockaddr &addr) { + std::lock_guard lock(mutex); + + const auto *psa_in = reinterpret_cast(&addr); + + auto &nph = g_fxo->get>(); + u32 saddr = nph.get_bind_ip(); + if (saddr == 0) { + // If zero use the supplied address + saddr = std::bit_cast(psa_in->sin_addr); + } + + if (feign_tcp_conn_failure) { + sys_net.error("Calling socket::bind() from a previously connected socket!"); + } + + ::sockaddr_in native_addr{}; + native_addr.sin_family = AF_INET; + native_addr.sin_port = std::bit_cast(psa_in->sin_port); + native_addr.sin_addr.s_addr = saddr; + ::socklen_t native_addr_len = sizeof(native_addr); + + // Note that this is a hack(TODO) + // ATM we don't support binding 3658 udp because we use it for the p2ps main + // socket Only Fat Princess is known to do this to my knowledge + if (psa_in->sin_port == 3658 && type == SYS_NET_SOCK_DGRAM) { + native_addr.sin_port = std::bit_cast>(3659); + } + + sys_net.warning("[Native] Trying to bind %s:%d", native_addr.sin_addr, + std::bit_cast, u16>(native_addr.sin_port)); + + if (::bind(native_socket, reinterpret_cast(&native_addr), + native_addr_len) == 0) { + // Only UPNP port forward binds to 0.0.0.0 + if (saddr == 0) { + if (native_addr.sin_port == 0) { + sockaddr_in client_addr; + socklen_t client_addr_size = sizeof(client_addr); + ensure(::getsockname(native_socket, + reinterpret_cast(&client_addr), + &client_addr_size) == 0); + bound_port = std::bit_cast>(client_addr.sin_port); + } else { + bound_port = std::bit_cast>(native_addr.sin_port); + } + + nph.upnp_add_port_mapping(bound_port, + type == SYS_NET_SOCK_STREAM ? "TCP" : "UDP"); + } + + last_bound_addr = addr; + return CELL_OK; + } + + auto error = get_last_error(false); + +#ifdef __linux__ + if (error == SYS_NET_EACCES && + std::bit_cast, u16>(native_addr.sin_port) < 1024) { + sys_net.error( + "The game tried to bind a port < 1024 which is privileged on Linux\n" + "Consider setting rpcs3 privileges for it with: setcap " + "'cap_net_bind_service=+ep' /path/to/rpcs3"); + } +#endif + + return -error; +} + +std::optional lv2_socket_native::connect(const sys_net_sockaddr &addr) { + std::lock_guard lock(mutex); + + const auto *psa_in = reinterpret_cast(&addr); + + ::sockaddr_in native_addr = sys_net_addr_to_native_addr(addr); + ::socklen_t native_addr_len = sizeof(native_addr); + + sys_net.notice("[Native] Attempting to connect on %s:%d", + native_addr.sin_addr, + std::bit_cast, u16>(native_addr.sin_port)); + + auto &nph = g_fxo->get>(); + if (!nph.get_net_status() && is_ip_public_address(native_addr)) { + return -SYS_NET_EADDRNOTAVAIL; + } + + if (psa_in->sin_port == 53) { + // Add socket to the dns hook list + sys_net.notice("[Native] sys_net_bnet_connect: using DNS..."); + auto &dnshook = g_fxo->get(); + dnshook.add_dns_spy(lv2_id); + } + +#ifdef _WIN32 + bool was_connecting = connecting; +#endif + + if (feign_tcp_conn_failure) { + // As if still connected + return -SYS_NET_EALREADY; + } + + if (::connect(native_socket, + reinterpret_cast(&native_addr), + native_addr_len) == 0) { + return CELL_OK; + } + + sys_net_error result = get_last_error(!so_nbio); + +#ifdef _WIN32 + // See + // https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-connect + if (was_connecting && + (result == SYS_NET_EINVAL || result == SYS_NET_EWOULDBLOCK)) + return -SYS_NET_EALREADY; +#endif + + if (result) { + if (result == SYS_NET_EWOULDBLOCK || result == SYS_NET_EINPROGRESS) { + result = SYS_NET_EINPROGRESS; +#ifdef _WIN32 + connecting = true; +#endif + this->poll_queue( + null_ptr, lv2_socket::poll_t::write, + [this](bs_t events) -> bool { + if (events & lv2_socket::poll_t::write) { + int native_error; + ::socklen_t size = sizeof(native_error); + if (::getsockopt(native_socket, SOL_SOCKET, SO_ERROR, + reinterpret_cast(&native_error), + &size) != 0 || + size != sizeof(int)) { + so_error = 1; + } else { + // TODO: check error formats (both native and translated) + so_error = + native_error ? convert_error(false, native_error) : 0; + } + + return true; + } + + events += lv2_socket::poll_t::write; + return false; + }); + } + + return -result; + } + +#ifdef _WIN32 + connecting = true; +#endif + + return std::nullopt; +} + +s32 lv2_socket_native::connect_followup() { + int native_error; + ::socklen_t size = sizeof(native_error); + if (::getsockopt(native_socket, SOL_SOCKET, SO_ERROR, + reinterpret_cast(&native_error), &size) != 0 || + size != sizeof(int)) { + return -1; + } + + // TODO: check error formats (both native and translated) + return native_error ? -convert_error(false, native_error) : 0; +} + +std::pair lv2_socket_native::getpeername() { + std::lock_guard lock(mutex); + + ::sockaddr_storage native_addr; + ::socklen_t native_addrlen = sizeof(native_addr); + + if (::getpeername(native_socket, + reinterpret_cast(&native_addr), + &native_addrlen) == 0) { + ensure(native_addr.ss_family == AF_INET); + + sys_net_sockaddr sn_addr = native_addr_to_sys_net_addr(native_addr); + + return {CELL_OK, sn_addr}; + } + + return {-get_last_error(false), {}}; +} + +std::pair lv2_socket_native::getsockname() { + std::lock_guard lock(mutex); + + ::sockaddr_storage native_addr; + ::socklen_t native_addrlen = sizeof(native_addr); + + if (::getsockname(native_socket, + reinterpret_cast(&native_addr), + &native_addrlen) == 0) { + ensure(native_addr.ss_family == AF_INET); + + sys_net_sockaddr sn_addr = native_addr_to_sys_net_addr(native_addr); + + return {CELL_OK, sn_addr}; + } +#ifdef _WIN32 + else { + // windows doesn't support getsockname for sockets that are not bound + if (get_native_error() == WSAEINVAL) { + return {CELL_OK, {}}; + } + } +#endif + + return {-get_last_error(false), {}}; +} + +std::tuple +lv2_socket_native::getsockopt(s32 level, s32 optname, u32 len) { + std::lock_guard lock(mutex); + + sockopt_data out_val; + u32 out_len = sizeof(out_val); + + int native_level = -1; + int native_opt = -1; + + union { + char ch[128]; + int _int = 0; + ::timeval timeo; + ::linger linger; + } native_val; + ::socklen_t native_len = sizeof(native_val); + + if (level == SYS_NET_SOL_SOCKET) { + native_level = SOL_SOCKET; + + switch (optname) { + case SYS_NET_SO_NBIO: { + // Special + out_val._int = so_nbio; + out_len = sizeof(s32); + return {CELL_OK, out_val, out_len}; + } + case SYS_NET_SO_ERROR: { + // Special + out_val._int = std::exchange(so_error, 0); + out_len = sizeof(s32); + return {CELL_OK, out_val, out_len}; + } + case SYS_NET_SO_KEEPALIVE: { + native_opt = SO_KEEPALIVE; + break; + } + case SYS_NET_SO_SNDBUF: { + native_opt = SO_SNDBUF; + break; + } + case SYS_NET_SO_RCVBUF: { + native_opt = SO_RCVBUF; + break; + } + case SYS_NET_SO_SNDLOWAT: { + native_opt = SO_SNDLOWAT; + break; + } + case SYS_NET_SO_RCVLOWAT: { + native_opt = SO_RCVLOWAT; + break; + } + case SYS_NET_SO_BROADCAST: { + native_opt = SO_BROADCAST; + break; + } +#ifdef _WIN32 + case SYS_NET_SO_REUSEADDR: { + out_val._int = so_reuseaddr; + out_len = sizeof(s32); + return {CELL_OK, out_val, out_len}; + } + case SYS_NET_SO_REUSEPORT: { + out_val._int = so_reuseport; + out_len = sizeof(s32); + return {CELL_OK, out_val, out_len}; + } +#else + case SYS_NET_SO_REUSEADDR: { + native_opt = SO_REUSEADDR; + break; + } + case SYS_NET_SO_REUSEPORT: { + native_opt = SO_REUSEPORT; + break; + } +#endif + case SYS_NET_SO_SNDTIMEO: + case SYS_NET_SO_RCVTIMEO: { + if (len < sizeof(sys_net_timeval)) + return {-SYS_NET_EINVAL, {}, {}}; + + native_opt = optname == SYS_NET_SO_SNDTIMEO ? SO_SNDTIMEO : SO_RCVTIMEO; + break; + } + case SYS_NET_SO_LINGER: { + if (len < sizeof(sys_net_linger)) + return {-SYS_NET_EINVAL, {}, {}}; + + native_opt = SO_LINGER; + break; + } + default: { + sys_net.error( + "sys_net_bnet_getsockopt(s=%d, SOL_SOCKET): unknown option (0x%x)", + lv2_id, optname); + return {-SYS_NET_EINVAL, {}, {}}; + } + } + } else if (level == SYS_NET_IPPROTO_TCP) { + native_level = IPPROTO_TCP; + + switch (optname) { + case SYS_NET_TCP_MAXSEG: { + // Special (no effect) + out_val._int = so_tcp_maxseg; + out_len = sizeof(s32); + return {CELL_OK, out_val, out_len}; + } + case SYS_NET_TCP_NODELAY: { + native_opt = TCP_NODELAY; + break; + } + default: { + sys_net.error( + "sys_net_bnet_getsockopt(s=%d, IPPROTO_TCP): unknown option (0x%x)", + lv2_id, optname); + return {-SYS_NET_EINVAL, {}, {}}; + } + } + } else if (level == SYS_NET_IPPROTO_IP) { + native_level = IPPROTO_IP; + switch (optname) { + case SYS_NET_IP_HDRINCL: { + native_opt = IP_HDRINCL; + break; + } + case SYS_NET_IP_TOS: { + native_opt = IP_TOS; + break; + } + case SYS_NET_IP_TTL: { + native_opt = IP_TTL; + break; + } + case SYS_NET_IP_MULTICAST_IF: { + native_opt = IP_MULTICAST_IF; + break; + } + case SYS_NET_IP_MULTICAST_TTL: { + native_opt = IP_MULTICAST_TTL; + break; + } + case SYS_NET_IP_MULTICAST_LOOP: { + native_opt = IP_MULTICAST_LOOP; + break; + } + case SYS_NET_IP_ADD_MEMBERSHIP: { + native_opt = IP_ADD_MEMBERSHIP; + break; + } + case SYS_NET_IP_DROP_MEMBERSHIP: { + native_opt = IP_DROP_MEMBERSHIP; + break; + } + case SYS_NET_IP_TTLCHK: { + sys_net.error("sys_net_bnet_getsockopt(IPPROTO_IP, SYS_NET_IP_TTLCHK): " + "stubbed option"); + return {CELL_OK, out_val, out_len}; + } + case SYS_NET_IP_MAXTTL: { + sys_net.error("sys_net_bnet_getsockopt(IPPROTO_IP, SYS_NET_IP_MAXTTL): " + "stubbed option"); + return {CELL_OK, out_val, out_len}; + } + case SYS_NET_IP_DONTFRAG: { +#ifdef _WIN32 + native_opt = IP_DONTFRAGMENT; +#else + native_opt = IP_DF; +#endif + break; + } + default: { + sys_net.error( + "sys_net_bnet_getsockopt(s=%d, IPPROTO_IP): unknown option (0x%x)", + lv2_id, optname); + return {-SYS_NET_EINVAL, {}, {}}; + } + } + } else { + sys_net.error("sys_net_bnet_getsockopt(s=%d): unknown level (0x%x)", lv2_id, + level); + return {-SYS_NET_EINVAL, {}, {}}; + } + + if (::getsockopt(native_socket, native_level, native_opt, native_val.ch, + &native_len) != 0) { + return {-get_last_error(false), {}, {}}; + } + + if (level == SYS_NET_SOL_SOCKET) { + switch (optname) { + case SYS_NET_SO_SNDTIMEO: + case SYS_NET_SO_RCVTIMEO: { + // TODO + out_val.timeo = {::narrow(native_val.timeo.tv_sec), + ::narrow(native_val.timeo.tv_usec)}; + out_len = sizeof(sys_net_timeval); + return {CELL_OK, out_val, out_len}; + } + case SYS_NET_SO_LINGER: { + // TODO + out_val.linger = {::narrow(native_val.linger.l_onoff), + ::narrow(native_val.linger.l_linger)}; + out_len = sizeof(sys_net_linger); + return {CELL_OK, out_val, out_len}; + } + default: + break; + } + } + + // Fallback to int + out_val._int = native_val._int; + out_len = sizeof(s32); + return {CELL_OK, out_val, out_len}; +} + +s32 lv2_socket_native::setsockopt(s32 level, s32 optname, + const std::vector &optval) { + std::lock_guard lock(mutex); + + int native_int = 0; + int native_level = -1; + int native_opt = -1; + const void *native_val = &native_int; + ::socklen_t native_len = sizeof(int); + ::linger native_linger; + ::ip_mreq native_mreq; + +#ifdef _WIN32 + u32 native_timeo; +#else + ::timeval native_timeo; +#endif + + native_int = *reinterpret_cast *>(optval.data()); + + if (level == SYS_NET_SOL_SOCKET) { + native_level = SOL_SOCKET; + + switch (optname) { + case SYS_NET_SO_NBIO: { + // Special + so_nbio = native_int; + return {}; + } + case SYS_NET_SO_KEEPALIVE: { + native_opt = SO_KEEPALIVE; + break; + } + case SYS_NET_SO_SNDBUF: { + native_opt = SO_SNDBUF; + break; + } + case SYS_NET_SO_RCVBUF: { + native_opt = SO_RCVBUF; + break; + } + case SYS_NET_SO_SNDLOWAT: { + native_opt = SO_SNDLOWAT; + break; + } + case SYS_NET_SO_RCVLOWAT: { + native_opt = SO_RCVLOWAT; + break; + } + case SYS_NET_SO_BROADCAST: { + native_opt = SO_BROADCAST; + break; + } +#ifdef _WIN32 + case SYS_NET_SO_REUSEADDR: { + native_opt = SO_REUSEADDR; + so_reuseaddr = native_int; + native_int = so_reuseaddr || so_reuseport ? 1 : 0; + break; + } + case SYS_NET_SO_REUSEPORT: { + native_opt = SO_REUSEADDR; + so_reuseport = native_int; + native_int = so_reuseaddr || so_reuseport ? 1 : 0; + break; + } +#else + case SYS_NET_SO_REUSEADDR: { + native_opt = SO_REUSEADDR; + break; + } + case SYS_NET_SO_REUSEPORT: { + native_opt = SO_REUSEPORT; + break; + } +#endif + case SYS_NET_SO_SNDTIMEO: + case SYS_NET_SO_RCVTIMEO: { + if (optval.size() < sizeof(sys_net_timeval)) + return -SYS_NET_EINVAL; + + native_opt = optname == SYS_NET_SO_SNDTIMEO ? SO_SNDTIMEO : SO_RCVTIMEO; + native_val = &native_timeo; + native_len = sizeof(native_timeo); + + const int tv_sec = ::narrow( + reinterpret_cast(optval.data())->tv_sec); + const int tv_usec = ::narrow( + reinterpret_cast(optval.data())->tv_usec); +#ifdef _WIN32 + native_timeo = tv_sec * 1000; + native_timeo += tv_usec / 1000; +#else + native_timeo.tv_sec = tv_sec; + native_timeo.tv_usec = tv_usec; +#endif + // TODO: Overflow detection? + (optname == SYS_NET_SO_SNDTIMEO ? so_sendtimeo : so_rcvtimeo) = + tv_usec + tv_sec * 1000000; + break; + } + case SYS_NET_SO_LINGER: { + if (optval.size() < sizeof(sys_net_linger)) + return -SYS_NET_EINVAL; + + // TODO + native_opt = SO_LINGER; + native_val = &native_linger; + native_len = sizeof(native_linger); + native_linger.l_onoff = + reinterpret_cast(optval.data())->l_onoff; + native_linger.l_linger = + reinterpret_cast(optval.data())->l_linger; + break; + } + case SYS_NET_SO_USECRYPTO: { + // TODO + sys_net.error("sys_net_bnet_setsockopt(s=%d, SOL_SOCKET): Stubbed option " + "(0x%x) (SYS_NET_SO_USECRYPTO)", + lv2_id, optname); + return {}; + } + case SYS_NET_SO_USESIGNATURE: { + // TODO + sys_net.error("sys_net_bnet_setsockopt(s=%d, SOL_SOCKET): Stubbed option " + "(0x%x) (SYS_NET_SO_USESIGNATURE)", + lv2_id, optname); + return {}; + } + default: { + sys_net.error( + "sys_net_bnet_setsockopt(s=%d, SOL_SOCKET): unknown option (0x%x)", + lv2_id, optname); + return -SYS_NET_EINVAL; + } + } + } else if (level == SYS_NET_IPPROTO_TCP) { + native_level = IPPROTO_TCP; + + switch (optname) { + case SYS_NET_TCP_MAXSEG: { + // Special (no effect) + so_tcp_maxseg = native_int; + return {}; + } + case SYS_NET_TCP_NODELAY: { + native_opt = TCP_NODELAY; + break; + } + default: { + sys_net.error( + "sys_net_bnet_setsockopt(s=%d, IPPROTO_TCP): unknown option (0x%x)", + lv2_id, optname); + return -SYS_NET_EINVAL; + } + } + } else if (level == SYS_NET_IPPROTO_IP) { + native_level = IPPROTO_IP; + + switch (optname) { + case SYS_NET_IP_HDRINCL: { + native_opt = IP_HDRINCL; + break; + } + case SYS_NET_IP_TOS: { + native_opt = IP_TOS; + break; + } + case SYS_NET_IP_TTL: { + native_opt = IP_TTL; + break; + } + case SYS_NET_IP_MULTICAST_IF: { + native_opt = IP_MULTICAST_IF; + break; + } + case SYS_NET_IP_MULTICAST_TTL: { + native_opt = IP_MULTICAST_TTL; + break; + } + case SYS_NET_IP_MULTICAST_LOOP: { + native_opt = IP_MULTICAST_LOOP; + break; + } + case SYS_NET_IP_ADD_MEMBERSHIP: + case SYS_NET_IP_DROP_MEMBERSHIP: { + if (optval.size() < sizeof(sys_net_ip_mreq)) + return -SYS_NET_EINVAL; + + native_opt = optname == SYS_NET_IP_ADD_MEMBERSHIP ? IP_ADD_MEMBERSHIP + : IP_DROP_MEMBERSHIP; + native_val = &native_mreq; + native_len = sizeof(::ip_mreq); + native_mreq.imr_interface.s_addr = std::bit_cast( + reinterpret_cast(optval.data()) + ->imr_interface); + native_mreq.imr_multiaddr.s_addr = std::bit_cast( + reinterpret_cast(optval.data()) + ->imr_multiaddr); + break; + } + case SYS_NET_IP_TTLCHK: { + sys_net.error("sys_net_bnet_setsockopt(s=%d, IPPROTO_IP): Stubbed option " + "(0x%x) (SYS_NET_IP_TTLCHK)", + lv2_id, optname); + break; + } + case SYS_NET_IP_MAXTTL: { + sys_net.error("sys_net_bnet_setsockopt(s=%d, IPPROTO_IP): Stubbed option " + "(0x%x) (SYS_NET_IP_MAXTTL)", + lv2_id, optname); + break; + } + case SYS_NET_IP_DONTFRAG: { +#ifdef _WIN32 + native_opt = IP_DONTFRAGMENT; +#else + native_opt = IP_DF; +#endif + break; + } + default: { + sys_net.error( + "sys_net_bnet_setsockopt(s=%d, IPPROTO_IP): unknown option (0x%x)", + lv2_id, optname); + return -SYS_NET_EINVAL; + } + } + } else { + sys_net.error("sys_net_bnet_setsockopt(s=%d): unknown level (0x%x)", lv2_id, + level); + return -SYS_NET_EINVAL; + } + + if (::setsockopt(native_socket, native_level, native_opt, + static_cast(native_val), native_len) == 0) { + return {}; + } + + return -get_last_error(false); +} + +s32 lv2_socket_native::listen(s32 backlog) { + std::lock_guard lock(mutex); + + if (::listen(native_socket, backlog) == 0) { + return CELL_OK; + } + + return -get_last_error(false); +} + +std::optional, sys_net_sockaddr>> +lv2_socket_native::recvfrom(s32 flags, u32 len, bool is_lock) { + std::unique_lock lock(mutex, std::defer_lock); + + if (is_lock) { + lock.lock(); + } + + if (feign_tcp_conn_failure) { + // As if just lost the connection + feign_tcp_conn_failure = false; + return {{-SYS_NET_ECONNRESET, {}, {}}}; + } + + int native_flags = 0; + ::sockaddr_storage native_addr{}; + ::socklen_t native_addrlen = sizeof(native_addr); + std::vector res_buf(len); + + auto &dnshook = g_fxo->get(); + if (dnshook.is_dns(lv2_id) && dnshook.is_dns_queue(lv2_id)) { + auto &nph = g_fxo->get>(); + const auto packet = dnshook.get_dns_packet(lv2_id); + ensure(packet.size() < len); + memcpy(res_buf.data(), packet.data(), packet.size()); + native_addr.ss_family = AF_INET; + (reinterpret_cast<::sockaddr_in *>(&native_addr))->sin_port = + std::bit_cast>(53); // htons(53) + (reinterpret_cast<::sockaddr_in *>(&native_addr))->sin_addr.s_addr = + nph.get_dns_ip(); + const auto sn_addr = native_addr_to_sys_net_addr(native_addr); + + return {{::narrow(packet.size()), res_buf, sn_addr}}; + } + + if (flags & SYS_NET_MSG_PEEK) { + native_flags |= MSG_PEEK; + } + + if (flags & SYS_NET_MSG_WAITALL) { + native_flags |= MSG_WAITALL; + } + + auto native_result = ::recvfrom( + native_socket, reinterpret_cast(res_buf.data()), len, + native_flags, reinterpret_cast(&native_addr), + &native_addrlen); + + if (native_result >= 0) { + const auto sn_addr = native_addr_to_sys_net_addr(native_addr); + return {{::narrow(native_result), res_buf, sn_addr}}; + } +#ifdef _WIN32 + else { + // Windows returns an error when trying to peek at a message and buffer not + // long enough to contain the whole message, should be ignored + if ((native_flags & MSG_PEEK) && get_native_error() == WSAEMSGSIZE) { + const auto sn_addr = native_addr_to_sys_net_addr(native_addr); + return {{len, res_buf, sn_addr}}; + } + // Windows will return WSASHUTDOWN when the connection is shutdown, POSIX + // just returns EOF (0) in this situation. + if (get_native_error() == WSAESHUTDOWN) { + const auto sn_addr = native_addr_to_sys_net_addr(native_addr); + return {{0, {}, sn_addr}}; + } + } + const auto result = get_last_error( + !so_nbio && (flags & SYS_NET_MSG_DONTWAIT) == 0, connecting); +#else + const auto result = + get_last_error(!so_nbio && (flags & SYS_NET_MSG_DONTWAIT) == 0); +#endif + + if (result) { + return {{-result, {}, {}}}; + } + + return std::nullopt; +} + +std::optional +lv2_socket_native::sendto(s32 flags, const std::vector &buf, + std::optional opt_sn_addr, + bool is_lock) { + std::unique_lock lock(mutex, std::defer_lock); + + if (is_lock) { + lock.lock(); + } + + int native_flags = 0; + int native_result = -1; + std::optional native_addr = std::nullopt; + + if (opt_sn_addr) { + native_addr = sys_net_addr_to_native_addr(*opt_sn_addr); + sys_net.trace("[Native] Attempting to send to %s:%d", + (*native_addr).sin_addr, + std::bit_cast, u16>((*native_addr).sin_port)); + + auto &nph = g_fxo->get>(); + if (!nph.get_net_status() && is_ip_public_address(*native_addr)) { + return -SYS_NET_EADDRNOTAVAIL; + } + } else if (feign_tcp_conn_failure) { + // As if just lost the connection + feign_tcp_conn_failure = false; + return -SYS_NET_ECONNRESET; + } + + sys_net_error result{}; + + if (flags & SYS_NET_MSG_WAITALL) { + native_flags |= MSG_WAITALL; + } + + auto &dnshook = g_fxo->get(); + if (opt_sn_addr && type == SYS_NET_SOCK_DGRAM && + reinterpret_cast(&*opt_sn_addr)->sin_port == + 53) { + dnshook.add_dns_spy(lv2_id); + } + + if (dnshook.is_dns(lv2_id)) { + const s32 ret_analyzer = dnshook.analyze_dns_packet( + lv2_id, reinterpret_cast(buf.data()), ::size32(buf)); + + // Check if the packet is intercepted + if (ret_analyzer >= 0) { + return {ret_analyzer}; + } + } + + native_result = ::sendto( + native_socket, reinterpret_cast(buf.data()), + ::narrow(buf.size()), native_flags, + native_addr ? reinterpret_cast(&native_addr.value()) + : nullptr, + native_addr ? sizeof(sockaddr_in) : 0); + + if (native_result >= 0) { + return {native_result}; + } + +#ifdef _WIN32 + result = get_last_error(!so_nbio && (flags & SYS_NET_MSG_DONTWAIT) == 0, + connecting); +#else + result = get_last_error(!so_nbio && (flags & SYS_NET_MSG_DONTWAIT) == 0); +#endif + + if (result) { + return {-result}; + } + + // Note that this can only happen if the send buffer is full + return std::nullopt; +} + +std::optional +lv2_socket_native::sendmsg(s32 flags, const sys_net_msghdr &msg, bool is_lock) { + std::unique_lock lock(mutex, std::defer_lock); + + if (is_lock) { + lock.lock(); + } + + int native_flags = 0; + int native_result = -1; + + sys_net_error result{}; + + if (flags & SYS_NET_MSG_WAITALL) { + native_flags |= MSG_WAITALL; + } + + if (feign_tcp_conn_failure) { + // As if just lost the connection + feign_tcp_conn_failure = false; + return {-SYS_NET_ECONNRESET}; + } + + for (int i = 0; i < msg.msg_iovlen; i++) { + auto iov_base = msg.msg_iov[i].iov_base; + const u32 len = msg.msg_iov[i].iov_len; + const std::vector buf_copy(vm::_ptr(iov_base.addr()), + vm::_ptr(iov_base.addr()) + len); + + native_result = + ::send(native_socket, reinterpret_cast(buf_copy.data()), + ::narrow(buf_copy.size()), native_flags); + + if (native_result >= 0) { + return {native_result}; + } + } + + result = get_last_error(!so_nbio && (flags & SYS_NET_MSG_DONTWAIT) == 0); + + if (result) { + return {-result}; + } + + return std::nullopt; +} + +void lv2_socket_native::close() { + std::lock_guard lock(mutex); + + np::close_socket(native_socket); + native_socket = {}; + + if (auto dnshook = g_fxo->try_get()) { + dnshook->remove_dns_spy(lv2_id); + } + + if (bound_port && g_fxo->is_init>()) { + auto &nph = g_fxo->get>(); + nph.upnp_remove_port_mapping(bound_port, + type == SYS_NET_SOCK_STREAM ? "TCP" : "UDP"); + bound_port = 0; + } +} + +s32 lv2_socket_native::shutdown(s32 how) { + std::lock_guard lock(mutex); + + if (feign_tcp_conn_failure) { + // As if still connected + return CELL_OK; + } + +#ifdef _WIN32 + const int native_how = how == SYS_NET_SHUT_RD ? SD_RECEIVE + : how == SYS_NET_SHUT_WR ? SD_SEND + : SD_BOTH; +#else + const int native_how = how == SYS_NET_SHUT_RD ? SHUT_RD + : how == SYS_NET_SHUT_WR ? SHUT_WR + : SHUT_RDWR; +#endif + + if (::shutdown(native_socket, native_how) == 0) { + return CELL_OK; + } + + return -get_last_error(false); +} + +s32 lv2_socket_native::poll(sys_net_pollfd &sn_pfd, pollfd &native_pfd) { + // Check for fake packet for dns interceptions + auto &dnshook = g_fxo->get(); + if (sn_pfd.events & SYS_NET_POLLIN && dnshook.is_dns(sn_pfd.fd) && + dnshook.is_dns_queue(sn_pfd.fd)) { + sn_pfd.revents |= SYS_NET_POLLIN; + return 1; + } + if (sn_pfd.events & ~(SYS_NET_POLLIN | SYS_NET_POLLOUT | SYS_NET_POLLERR)) { + sys_net.warning("sys_net_bnet_poll(fd=%d): events=0x%x", sn_pfd.fd, + sn_pfd.events); + } + + native_pfd.fd = native_socket; + + if (sn_pfd.events & SYS_NET_POLLIN) { + native_pfd.events |= POLLIN; + } + if (sn_pfd.events & SYS_NET_POLLOUT) { + native_pfd.events |= POLLOUT; + } + + return 0; +} + +std::tuple +lv2_socket_native::select(bs_t selected, + pollfd &native_pfd) { + native_pfd.fd = native_socket; + if (selected & lv2_socket::poll_t::read) { + native_pfd.events |= POLLIN; + } + if (selected & lv2_socket::poll_t::write) { + native_pfd.events |= POLLOUT; + } + + return {}; +} + +void lv2_socket_native::set_default_buffers() { + // Those are the default PS3 values + u32 default_RCVBUF = (type == SYS_NET_SOCK_STREAM) ? 65535 : 9216; + if (::setsockopt(native_socket, SOL_SOCKET, SO_RCVBUF, + reinterpret_cast(&default_RCVBUF), + sizeof(default_RCVBUF)) != 0) { + sys_net.error( + "Error setting default SO_RCVBUF on sys_net_bnet_socket socket"); + } + u32 default_SNDBUF = 131072; + if (::setsockopt(native_socket, SOL_SOCKET, SO_SNDBUF, + reinterpret_cast(&default_SNDBUF), + sizeof(default_SNDBUF)) != 0) { + sys_net.error( + "Error setting default SO_SNDBUF on sys_net_bnet_socket socket"); + } +} + +void lv2_socket_native::set_non_blocking() { + // Set non-blocking + // This is done to avoid having threads stuck on blocking socket functions + // Blocking functions just put the thread to sleep and delegate the waking up + // to network_thread which polls the sockets + np::set_socket_non_blocking(native_socket); +} + +bool lv2_socket_native::is_socket_connected() { + if (type != SYS_NET_SOCK_STREAM) { + return false; + } + + std::lock_guard lock(mutex); + + int listening = 0; + socklen_t len = sizeof(listening); + + if (::getsockopt(native_socket, SOL_SOCKET, SO_ACCEPTCONN, + reinterpret_cast(&listening), &len) == -1) { + return false; + } + + if (listening) { + // Would be handled in other ways + return false; + } + + fd_set readfds, writefds; + struct timeval timeout{0, 0}; // Zero timeout + + FD_ZERO(&readfds); + FD_ZERO(&writefds); + FD_SET(native_socket, &readfds); + FD_SET(native_socket, &writefds); + + // Use select to check for readability and writability + const int result = ::select(1, &readfds, &writefds, NULL, &timeout); + + if (result < 0) { + // Error occurred + return false; + } + + // Socket is connected if it's readable or writable + return FD_ISSET(native_socket, &readfds) || + FD_ISSET(native_socket, &writefds); +} diff --git a/kernel/cellos/src/sys_net/lv2_socket_p2p.cpp b/kernel/cellos/src/sys_net/lv2_socket_p2p.cpp new file mode 100644 index 000000000..85b2fa4ef1 --- /dev/null +++ b/kernel/cellos/src/sys_net/lv2_socket_p2p.cpp @@ -0,0 +1,400 @@ +#include "stdafx.h" + +#include "Emu/NP/np_helpers.h" +#include "sys_net/lv2_socket_p2p.h" +#include "sys_net/network_context.h" +#include "sys_net/sys_net_helpers.h" + +LOG_CHANNEL(sys_net); + +lv2_socket_p2p::lv2_socket_p2p(lv2_socket_family family, lv2_socket_type type, + lv2_ip_protocol protocol) + : lv2_socket(family, type, protocol) { + sockopt_cache cache_type; + cache_type.data._int = SYS_NET_SOCK_DGRAM_P2P; + cache_type.len = 4; + + sockopts[(static_cast(SYS_NET_SOL_SOCKET) << 32ull) | SYS_NET_SO_TYPE] = + cache_type; +} + +lv2_socket_p2p::lv2_socket_p2p(utils::serial &ar, lv2_socket_type type) + : lv2_socket(make_exact(ar), type) { + ar(port, vport, bound_addr); + + auto data_dequeue = + ar.pop>>>(); + + for (; !data_dequeue.empty(); data_dequeue.pop_front()) { + data.push(std::move(data_dequeue.front())); + } +} + +void lv2_socket_p2p::save(utils::serial &ar) { + lv2_socket::save(ar, true); + ar(port, vport, bound_addr); + + std::deque>> data_dequeue; + + for (auto save_data = ::as_rvalue(data); !save_data.empty(); + save_data.pop()) { + data_dequeue.push_back(std::move(save_data.front())); + } + + ar(data_dequeue); +} + +void lv2_socket_p2p::handle_new_data(sys_net_sockaddr_in_p2p p2p_addr, + std::vector p2p_data) { + std::lock_guard lock(mutex); + + sys_net.trace("Received a P2P packet for vport %d and saved it", + p2p_addr.sin_vport); + data.push(std::make_pair(std::move(p2p_addr), std::move(p2p_data))); + + // Check if poll is happening + if (events.test_and_reset(lv2_socket::poll_t::read)) { + bs_t read_event = lv2_socket::poll_t::read; + for (auto it = queue.begin(); it != queue.end();) { + if (it->second(read_event)) { + it = queue.erase(it); + continue; + } + it++; + } + + if (queue.empty()) { + events.store({}); + } + } +} + +std::tuple, sys_net_sockaddr> +lv2_socket_p2p::accept([[maybe_unused]] bool is_lock) { + sys_net.fatal("[P2P] accept() called on a P2P socket"); + return {}; +} + +std::optional +lv2_socket_p2p::connect([[maybe_unused]] const sys_net_sockaddr &addr) { + sys_net.fatal("[P2P] connect() called on a P2P socket"); + return {}; +} + +s32 lv2_socket_p2p::connect_followup() { + sys_net.fatal("[P2P] connect_followup() called on a P2P socket"); + return {}; +} + +std::pair lv2_socket_p2p::getpeername() { + sys_net.fatal("[P2P] getpeername() called on a P2P socket"); + return {}; +} + +s32 lv2_socket_p2p::listen([[maybe_unused]] s32 backlog) { + sys_net.fatal("[P2P] listen() called on a P2P socket"); + return {}; +} + +s32 lv2_socket_p2p::bind(const sys_net_sockaddr &addr) { + const auto *psa_in_p2p = + reinterpret_cast(&addr); + u16 p2p_port = psa_in_p2p->sin_port; + u16 p2p_vport = psa_in_p2p->sin_vport; + + sys_net.notice("[P2P] Trying to bind %s:%d:%d", + np::ip_to_string(std::bit_cast(psa_in_p2p->sin_addr)), + p2p_port, p2p_vport); + + if (p2p_port != SCE_NP_PORT) { + if (p2p_port == 0) { + return -SYS_NET_EINVAL; + } + sys_net.warning("[P2P] Attempting to bind a socket to a port != %d", + +SCE_NP_PORT); + } + + socket_type real_socket{}; + + auto &nc = g_fxo->get(); + { + std::lock_guard list_lock(nc.list_p2p_ports_mutex); + + nc.create_p2p_port(p2p_port); + auto &pport = ::at32(nc.list_p2p_ports, p2p_port); + real_socket = pport.p2p_socket; + + { + std::lock_guard lock(pport.bound_p2p_vports_mutex); + + if (p2p_vport == 0) { + // Find a free vport starting at 30000 + p2p_vport = 30000; + while (pport.bound_p2p_vports.contains(p2p_vport)) { + p2p_vport++; + } + } + + if (pport.bound_p2p_vports.contains(p2p_vport)) { + // Check that all other sockets are SO_REUSEADDR or SO_REUSEPORT + auto &bound_sockets = ::at32(pport.bound_p2p_vports, p2p_vport); + if (!sys_net_helpers::all_reusable(bound_sockets)) { + return -SYS_NET_EADDRINUSE; + } + + bound_sockets.insert(lv2_id); + } else { + std::set bound_ports{lv2_id}; + pport.bound_p2p_vports.insert( + std::make_pair(p2p_vport, std::move(bound_ports))); + } + } + } + + { + std::lock_guard lock(mutex); + port = p2p_port; + vport = p2p_vport; + native_socket = real_socket; + bound_addr = psa_in_p2p->sin_addr; + } + + return CELL_OK; +} + +std::pair lv2_socket_p2p::getsockname() { + std::lock_guard lock(mutex); + + // Unbound socket + if (!native_socket) { + return {CELL_OK, {}}; + } + + sys_net_sockaddr sn_addr{}; + sys_net_sockaddr_in_p2p *paddr = + reinterpret_cast(&sn_addr); + + paddr->sin_len = sizeof(sys_net_sockaddr_in); + paddr->sin_family = SYS_NET_AF_INET; + paddr->sin_port = port; + paddr->sin_vport = vport; + paddr->sin_addr = bound_addr; + + return {CELL_OK, sn_addr}; +} + +std::tuple +lv2_socket_p2p::getsockopt(s32 level, s32 optname, u32 len) { + std::lock_guard lock(mutex); + + const u64 key = (static_cast(level) << 32) | static_cast(optname); + + if (!sockopts.contains(key)) { + sys_net.error("Unhandled getsockopt(level=%d, optname=%d, len=%d)", level, + optname, len); + return {}; + } + + const auto &cache = ::at32(sockopts, key); + return {CELL_OK, cache.data, cache.len}; +} + +s32 lv2_socket_p2p::setsockopt(s32 level, s32 optname, + const std::vector &optval) { + std::lock_guard lock(mutex); + + int native_int = *reinterpret_cast *>(optval.data()); + + if (level == SYS_NET_SOL_SOCKET && optname == SYS_NET_SO_NBIO) { + so_nbio = native_int; + } + + const u64 key = (static_cast(level) << 32) | static_cast(optname); + sockopt_cache cache{}; + memcpy(&cache.data._int, optval.data(), optval.size()); + cache.len = ::size32(optval); + + sockopts[key] = std::move(cache); + + return CELL_OK; +} + +std::optional, sys_net_sockaddr>> +lv2_socket_p2p::recvfrom(s32 flags, u32 len, bool is_lock) { + std::unique_lock lock(mutex, std::defer_lock); + + if (is_lock) { + lock.lock(); + } + + if (data.empty()) { + if (so_nbio || (flags & SYS_NET_MSG_DONTWAIT)) + return {{-SYS_NET_EWOULDBLOCK, {}, {}}}; + + return std::nullopt; + } + + sys_net.trace("[P2P] p2p_data for vport %d contains %d elements", vport, + data.size()); + + std::vector res_buf(len); + + const auto &p2p_data = data.front(); + s32 native_result = std::min(len, static_cast(p2p_data.second.size())); + memcpy(res_buf.data(), p2p_data.second.data(), native_result); + + sys_net_sockaddr sn_addr; + memcpy(&sn_addr, &p2p_data.first, sizeof(sn_addr)); + + data.pop(); + + return {{native_result, res_buf, sn_addr}}; +} + +std::optional +lv2_socket_p2p::sendto(s32 flags, const std::vector &buf, + std::optional opt_sn_addr, + bool is_lock) { + std::unique_lock lock(mutex, std::defer_lock); + + if (is_lock) { + lock.lock(); + } + + ensure(opt_sn_addr); + ensure(socket); // ensures it has been bound + ensure( + buf.size() <= + static_cast( + 65535 - + VPORT_P2P_HEADER_SIZE)); // catch games using full payload for future + // fragmentation implementation if necessary + const u16 p2p_port = + reinterpret_cast(&*opt_sn_addr)->sin_port; + const u16 p2p_vport = + reinterpret_cast(&*opt_sn_addr) + ->sin_vport; + + auto native_addr = sys_net_addr_to_native_addr(*opt_sn_addr); + + char ip_str[16]; + inet_ntop(AF_INET, &native_addr.sin_addr, ip_str, sizeof(ip_str)); + sys_net.trace("[P2P] Sending a packet to %s:%d:%d", ip_str, p2p_port, + p2p_vport); + + std::vector p2p_data(buf.size() + VPORT_P2P_HEADER_SIZE); + const le_t p2p_vport_le = p2p_vport; + const le_t src_vport_le = vport; + const le_t p2p_flags_le = P2P_FLAG_P2P; + memcpy(p2p_data.data(), &p2p_vport_le, sizeof(u16)); + memcpy(p2p_data.data() + sizeof(u16), &src_vport_le, sizeof(u16)); + memcpy(p2p_data.data() + sizeof(u16) + sizeof(u16), &p2p_flags_le, + sizeof(u16)); + memcpy(p2p_data.data() + VPORT_P2P_HEADER_SIZE, buf.data(), buf.size()); + + int native_flags = 0; + if (flags & SYS_NET_MSG_WAITALL) { + native_flags |= MSG_WAITALL; + } + + auto native_result = np::sendto_possibly_ipv6( + native_socket, reinterpret_cast(p2p_data.data()), + ::size32(p2p_data), &native_addr, native_flags); + + if (native_result >= 0) { + return {std::max(native_result - VPORT_P2P_HEADER_SIZE, 0l)}; + } + + s32 result = get_last_error(!so_nbio && (flags & SYS_NET_MSG_DONTWAIT) == 0); + + if (result) { + return {-result}; + } + + // Note that this can only happen if the send buffer is full + return std::nullopt; +} + +std::optional +lv2_socket_p2p::sendmsg([[maybe_unused]] s32 flags, + [[maybe_unused]] const sys_net_msghdr &msg, + [[maybe_unused]] bool is_lock) { + sys_net.todo("lv2_socket_p2p::sendmsg"); + return {}; +} + +void lv2_socket_p2p::close() { + if (!port || !vport) { + return; + } + + if (g_fxo->is_init()) { + auto &nc = g_fxo->get(); + std::lock_guard lock(nc.list_p2p_ports_mutex); + + if (!nc.list_p2p_ports.contains(port)) + return; + + auto &p2p_port = ::at32(nc.list_p2p_ports, port); + { + std::lock_guard lock(p2p_port.bound_p2p_vports_mutex); + if (!p2p_port.bound_p2p_vports.contains(vport)) { + return; + } + + auto &bound_sockets = ::at32(p2p_port.bound_p2p_vports, vport); + bound_sockets.erase(lv2_id); + + if (bound_sockets.empty()) { + p2p_port.bound_p2p_vports.erase(vport); + } + } + } +} + +s32 lv2_socket_p2p::shutdown([[maybe_unused]] s32 how) { + sys_net.todo("[P2P] shutdown"); + return CELL_OK; +} + +s32 lv2_socket_p2p::poll(sys_net_pollfd &sn_pfd, + [[maybe_unused]] pollfd &native_pfd) { + std::lock_guard lock(mutex); + ensure(vport); + + // Check if it's a bound P2P socket + if ((sn_pfd.events & SYS_NET_POLLIN) && !data.empty()) { + sys_net.trace("[P2P] p2p_data for vport %d contains %d elements", vport, + data.size()); + sn_pfd.revents |= SYS_NET_POLLIN; + } + + // Data can always be written on a dgram socket + if (sn_pfd.events & SYS_NET_POLLOUT) { + sn_pfd.revents |= SYS_NET_POLLOUT; + } + + return sn_pfd.revents ? 1 : 0; +} + +std::tuple +lv2_socket_p2p::select(bs_t selected, + [[maybe_unused]] pollfd &native_pfd) { + std::lock_guard lock(mutex); + + bool read_set = false; + bool write_set = false; + + // Check if it's a bound P2P socket + if ((selected & lv2_socket::poll_t::read) && vport && !data.empty()) { + sys_net.trace("[P2P] p2p_data for vport %d contains %d elements", vport, + data.size()); + read_set = true; + } + + if (selected & lv2_socket::poll_t::write) { + write_set = true; + } + + return {read_set, write_set, false}; +} diff --git a/kernel/cellos/src/sys_net/lv2_socket_p2ps.cpp b/kernel/cellos/src/sys_net/lv2_socket_p2ps.cpp new file mode 100644 index 000000000..ba5438144 --- /dev/null +++ b/kernel/cellos/src/sys_net/lv2_socket_p2ps.cpp @@ -0,0 +1,1012 @@ +#include "stdafx.h" + +#include "Emu/NP/np_helpers.h" +#include "sys_net/lv2_socket_p2ps.h" +#include "sys_net/network_context.h" +#include "sys_net/nt_p2p_port.h" +#include "sys_net/sys_net_helpers.h" +#include "util/Thread.h" +#include "util/atomic.hpp" + +LOG_CHANNEL(sys_net); + +// Object in charge of retransmiting packets for STREAM_P2P sockets +class tcp_timeout_monitor { +public: + void add_message(s32 sock_id, const sockaddr_in *dst, std::vector data, + u64 seq) { + { + std::lock_guard lock(data_mutex); + + const auto now = steady_clock::now(); + + message msg; + msg.dst_addr = *dst; + msg.sock_id = sock_id; + msg.data = std::move(data); + msg.seq = seq; + msg.initial_sendtime = now; + + rtt_info rtt = rtts[sock_id]; + + const auto expected_time = now + rtt.rtt_time; + + msgs.insert(std::make_pair(expected_time, std::move(msg))); + } + wakey.release(1); + wakey.notify_one(); // TODO: Should be improved to only wake if new timeout + // < old timeout + } + + void confirm_data_received(s32 sock_id, u64 ack) { + std::lock_guard lock(data_mutex); + rtts[sock_id].num_retries = 0; + + const auto now = steady_clock::now(); + + for (auto it = msgs.begin(); it != msgs.end();) { + auto &msg = it->second; + if (msg.sock_id == sock_id && msg.seq < ack) { + // Decreases RTT if msg is early + if (now < it->first) { + const auto actual_rtt = + std::chrono::duration_cast( + now - it->second.initial_sendtime); + const auto cur_rtt = rtts[sock_id].rtt_time; + if (cur_rtt > actual_rtt) { + rtts[sock_id].rtt_time = (actual_rtt + cur_rtt) / 2; + } + } + it = msgs.erase(it); + continue; + } + it++; + } + } + + void clear_all_messages(s32 sock_id) { + std::lock_guard lock(data_mutex); + + for (auto it = msgs.begin(); it != msgs.end();) { + auto &msg = it->second; + + if (msg.sock_id == sock_id) { + it = msgs.erase(it); + continue; + } + it++; + } + } + + void operator()() { + atomic_wait_timeout timeout = atomic_wait_timeout::inf; + + while (thread_ctrl::state() != thread_state::aborting) { + if (!wakey) { + wakey.wait(0, timeout); + } + + wakey = 0; + + if (thread_ctrl::state() == thread_state::aborting) + return; + + std::lock_guard lock(data_mutex); + + const auto now = steady_clock::now(); + // Check for messages that haven't been acked + std::set rtt_increased; + for (auto it = msgs.begin(); it != msgs.end();) { + if (it->first > now) + break; + + // reply is late, increases rtt + auto &msg = it->second; + const auto addr = msg.dst_addr.sin_addr.s_addr; + rtt_info rtt = rtts[msg.sock_id]; + // Only increases rtt once per loop(in case a big number of packets are + // sent at once) + if (!rtt_increased.count(msg.sock_id)) { + rtt.num_retries += 1; + // Increases current rtt by 10% + rtt.rtt_time += (rtt.rtt_time / 10); + rtts[addr] = rtt; + + rtt_increased.emplace(msg.sock_id); + } + + if (rtt.num_retries >= 10) { + // Too many retries, need to notify the socket that the connection is + // dead + idm::check(msg.sock_id, [&](lv2_socket &sock) { + sys_net.error("[P2PS] Too many retries, closing the stream"); + ensure(sock.get_type() == SYS_NET_SOCK_STREAM_P2P); + auto &sock_p2ps = reinterpret_cast(sock); + sock_p2ps.close_stream(); + }); + it = msgs.erase(it); + continue; + } + + // resend the message + const auto res = idm::check< + lv2_socket>(msg.sock_id, [&](lv2_socket &sock) -> bool { + ensure(sock.get_type() == SYS_NET_SOCK_STREAM_P2P); + auto &sock_p2ps = reinterpret_cast(sock); + + while (np::sendto_possibly_ipv6( + sock_p2ps.get_socket(), + reinterpret_cast(msg.data.data()), + ::size32(msg.data), &msg.dst_addr, 0) == -1) { + const sys_net_error err = get_last_error(false); + // concurrency on the socket(from a sendto for example) can result + // in EAGAIN error in which case we try again + if (err == SYS_NET_EAGAIN) { + continue; + } + + sys_net.error( + "[P2PS] Resending the packet failed(%s), closing the stream", + err); + sock_p2ps.close_stream(); + return false; + } + return true; + }); + + if (!res || !res.ret) { + it = msgs.erase(it); + continue; + } + + // Update key timeout + msgs.insert(std::make_pair(now + rtt.rtt_time, std::move(msg))); + it = msgs.erase(it); + } + + if (!msgs.empty()) { + const auto current_timepoint = steady_clock::now(); + const auto expected_timepoint = msgs.begin()->first; + if (current_timepoint > expected_timepoint) { + wakey = 1; + } else { + timeout = static_cast( + std::chrono::duration_cast( + expected_timepoint - current_timepoint) + .count()); + } + } else { + timeout = atomic_wait_timeout::inf; + } + } + } + + tcp_timeout_monitor &operator=(thread_state) { + wakey.release(1); + wakey.notify_one(); + return *this; + } + +public: + static constexpr auto thread_name = "Tcp Over Udp Timeout Manager Thread"sv; + +private: + atomic_t wakey = 0; + shared_mutex data_mutex; + // List of outgoing messages + struct message { + s32 sock_id = 0; + ::sockaddr_in dst_addr{}; + std::vector data; + u64 seq = 0; + steady_clock::time_point initial_sendtime{}; + }; + std::map msgs; // (wakeup time, msg) + // List of rtts + struct rtt_info { + unsigned long num_retries = 0; + std::chrono::milliseconds rtt_time = 50ms; + }; + std::unordered_map rtts; // (sock_id, rtt) +}; + +u16 u2s_tcp_checksum(const le_t *buffer, usz size) { + u32 cksum = 0; + while (size > 1) { + cksum += *buffer++; + size -= sizeof(u16); + } + if (size) + cksum += *reinterpret_cast(buffer); + + cksum = (cksum >> 16) + (cksum & 0xffff); + cksum += (cksum >> 16); + return static_cast(~cksum); +} + +std::vector generate_u2s_packet(const p2ps_encapsulated_tcp &header, + const u8 *data, const u32 datasize) { + const u32 packet_size = + (VPORT_P2P_HEADER_SIZE + sizeof(p2ps_encapsulated_tcp) + datasize); + ensure(packet_size < + 65535); // packet size shouldn't be bigger than possible UDP payload + std::vector packet(packet_size); + u8 *packet_data = packet.data(); + le_t dst_port_le = +header.dst_port; + le_t src_port_le = +header.src_port; + le_t p2p_flags_le = P2P_FLAG_P2PS; + + memcpy(packet_data, &dst_port_le, sizeof(u16)); + memcpy(packet_data + sizeof(u16), &src_port_le, sizeof(u16)); + memcpy(packet_data + sizeof(u16) + sizeof(u16), &p2p_flags_le, sizeof(u16)); + memcpy(packet_data + VPORT_P2P_HEADER_SIZE, &header, + sizeof(p2ps_encapsulated_tcp)); + if (datasize) + memcpy(packet_data + VPORT_P2P_HEADER_SIZE + sizeof(p2ps_encapsulated_tcp), + data, datasize); + + auto *hdr_ptr = reinterpret_cast( + packet_data + VPORT_P2P_HEADER_SIZE); + hdr_ptr->checksum = 0; + hdr_ptr->checksum = + u2s_tcp_checksum(utils::bless>(hdr_ptr), + sizeof(p2ps_encapsulated_tcp) + datasize); + + return packet; +} + +lv2_socket_p2ps::lv2_socket_p2ps(lv2_socket_family family, lv2_socket_type type, + lv2_ip_protocol protocol) + : lv2_socket_p2p(family, type, protocol) { + sockopt_cache cache_type; + cache_type.data._int = SYS_NET_SOCK_STREAM_P2P; + cache_type.len = 4; + + sockopts[(static_cast(SYS_NET_SOL_SOCKET) << 32ull) | SYS_NET_SO_TYPE] = + cache_type; +} + +lv2_socket_p2ps::lv2_socket_p2ps(socket_type native_socket, u16 port, u16 vport, + u32 op_addr, u16 op_port, u16 op_vport, + u64 cur_seq, u64 data_beg_seq, s32 so_nbio) + : lv2_socket_p2p(SYS_NET_AF_INET, SYS_NET_SOCK_STREAM_P2P, + SYS_NET_IPPROTO_IP) { + this->native_socket = native_socket; + this->port = port; + this->vport = vport; + this->op_addr = op_addr; + this->op_port = op_port; + this->op_vport = op_vport; + this->cur_seq = cur_seq; + this->data_beg_seq = data_beg_seq; + this->so_nbio = so_nbio; + status = p2ps_stream_status::stream_connected; +} + +lv2_socket_p2ps::lv2_socket_p2ps(utils::serial &ar, lv2_socket_type type) + : lv2_socket_p2p(ar, type) { + ar(status, max_backlog, backlog, op_port, op_vport, op_addr, data_beg_seq, + received_data, cur_seq); +} + +void lv2_socket_p2ps::save(utils::serial &ar) { + static_cast(this)->save(ar); + ar(status, max_backlog, backlog, op_port, op_vport, op_addr, data_beg_seq, + received_data, cur_seq); +} + +bool lv2_socket_p2ps::handle_connected(p2ps_encapsulated_tcp *tcp_header, + u8 *data, ::sockaddr_storage *op_addr, + nt_p2p_port *p2p_port) { + std::lock_guard lock(mutex); + + if (status != p2ps_stream_status::stream_connected && + status != p2ps_stream_status::stream_handshaking) { + sys_net.error("[P2PS] lv2_socket_p2ps::handle_connected() called on a non " + "connected/handshaking socket(%d)!", + static_cast(status)); + return false; + } + + if (tcp_header->flags & static_cast(p2ps_tcp_flags::ACK)) { + auto &tcpm = g_fxo->get>(); + tcpm.confirm_data_received(lv2_id, tcp_header->ack); + } + + auto send_ack = [&]() { + auto final_ack = data_beg_seq; + while (received_data.contains(final_ack)) { + final_ack += ::at32(received_data, final_ack).size(); + } + data_available = final_ack - data_beg_seq; + + p2ps_encapsulated_tcp send_hdr; + send_hdr.src_port = tcp_header->dst_port; + send_hdr.dst_port = tcp_header->src_port; + send_hdr.flags = p2ps_tcp_flags::ACK; + send_hdr.ack = final_ack; + auto packet = generate_u2s_packet(send_hdr, nullptr, 0); + sys_net.trace("[P2PS] Sent ack %d", final_ack); + send_u2s_packet(std::move(packet), + reinterpret_cast<::sockaddr_in *>(op_addr), 0, false); + + // check if polling is happening + if (data_available && events.test_and_reset(lv2_socket::poll_t::read)) { + bs_t read_event = lv2_socket::poll_t::read; + for (auto it = queue.begin(); it != queue.end();) { + if (it->second(read_event)) { + it = queue.erase(it); + continue; + } + it++; + } + + if (queue.empty()) { + events.store({}); + } + } + }; + + if (status == p2ps_stream_status::stream_handshaking) { + // Only expect SYN|ACK + if (tcp_header->flags == (p2ps_tcp_flags::SYN | p2ps_tcp_flags::ACK)) { + sys_net.trace("[P2PS] Received SYN|ACK, status is now connected"); + data_beg_seq = tcp_header->seq + 1; + status = p2ps_stream_status::stream_connected; + send_ack(); + } else { + sys_net.error("[P2PS] Unexpected U2S TCP flag received with handshaking " + "state: 0x%02X", + tcp_header->flags); + } + + return true; + } else if (status == p2ps_stream_status::stream_connected) { + switch (tcp_header->flags) { + case 0: + case p2ps_tcp_flags::PSH: + case p2ps_tcp_flags::ACK: + case p2ps_tcp_flags::SYN: + case p2ps_tcp_flags::SYN | p2ps_tcp_flags::ACK: { + if (tcp_header->seq < data_beg_seq) { + // Data has already been processed + sys_net.trace("[P2PS] Data has already been processed"); + if (tcp_header->flags != p2ps_tcp_flags::ACK) + send_ack(); + return true; + } + + if (!received_data.count(tcp_header->seq)) { + // New data + received_data.emplace(tcp_header->seq, + std::vector(data, data + tcp_header->length)); + } else { + sys_net.trace("[P2PS] Data was not new!"); + } + + send_ack(); + return true; + } + case p2ps_tcp_flags::RST: + case p2ps_tcp_flags::FIN: { + sys_net.error("[P2PS] Received RST/FIN packet(%d), closing the stream", + tcp_header->flags); + close_stream_nl(p2p_port); + return false; + } + default: { + sys_net.error("[P2PS] Unexpected U2S TCP flag received with connected " + "state: 0x%02X", + tcp_header->flags); + return true; + } + } + } + + return true; +} + +bool lv2_socket_p2ps::handle_listening(p2ps_encapsulated_tcp *tcp_header, + [[maybe_unused]] u8 *data, + ::sockaddr_storage *op_addr) { + std::lock_guard lock(mutex); + + if (status != p2ps_stream_status::stream_listening) { + sys_net.error("[P2PS] lv2_socket_p2ps::handle_listening() called on a non " + "listening socket(%d)!", + static_cast(status)); + return false; + } + + // Only valid packet + if (tcp_header->flags == static_cast(p2ps_tcp_flags::SYN)) { + if (backlog.size() >= max_backlog) { + // Send a RST packet on backlog full + sys_net.trace("[P2PS] Backlog was full, sent a RST packet"); + p2ps_encapsulated_tcp send_hdr; + send_hdr.src_port = tcp_header->dst_port; + send_hdr.dst_port = tcp_header->src_port; + send_hdr.flags = p2ps_tcp_flags::RST; + auto packet = generate_u2s_packet(send_hdr, nullptr, 0); + send_u2s_packet(std::move(packet), + reinterpret_cast<::sockaddr_in *>(op_addr), 0, false); + return true; + } + + // Yes, new connection and a backlog is available, create a new lv2_socket + // for it and send SYN|ACK Prepare reply packet + sys_net.notice( + "[P2PS] Received connection on listening STREAM-P2P socket!"); + p2ps_encapsulated_tcp send_hdr; + send_hdr.src_port = tcp_header->dst_port; + send_hdr.dst_port = tcp_header->src_port; + send_hdr.flags = p2ps_tcp_flags::SYN | p2ps_tcp_flags::ACK; + send_hdr.ack = tcp_header->seq + 1; + // Generates random starting SEQ + send_hdr.seq = rand(); + + // Create new socket + const u32 new_op_addr = + reinterpret_cast(op_addr)->sin_addr.s_addr; + const u16 new_op_port = std::bit_cast>( + (reinterpret_cast(op_addr)->sin_port)); + const u16 new_op_vport = tcp_header->src_port; + const u64 new_cur_seq = send_hdr.seq + 1; + const u64 new_data_beg_seq = send_hdr.ack; + auto sock_lv2 = make_shared( + native_socket, port, vport, new_op_addr, new_op_port, new_op_vport, + new_cur_seq, new_data_beg_seq, so_nbio); + const s32 new_sock_id = idm::import_existing(sock_lv2); + sock_lv2->set_lv2_id(new_sock_id); + const u64 key_connected = + (reinterpret_cast(op_addr)->sin_addr.s_addr) | + (static_cast(tcp_header->src_port) << 48) | + (static_cast(tcp_header->dst_port) << 32); + + { + auto &nc = g_fxo->get(); + auto &pport = ::at32(nc.list_p2p_ports, port); + pport.bound_p2p_streams.emplace(key_connected, new_sock_id); + } + + auto packet = generate_u2s_packet(send_hdr, nullptr, 0); + { + std::lock_guard lock(sock_lv2->mutex); + sock_lv2->send_u2s_packet(std::move(packet), + reinterpret_cast<::sockaddr_in *>(op_addr), + send_hdr.seq, true); + } + + backlog.push_back(new_sock_id); + if (events.test_and_reset(lv2_socket::poll_t::read)) { + bs_t read_event = lv2_socket::poll_t::read; + for (auto it = queue.begin(); it != queue.end();) { + if (it->second(read_event)) { + it = queue.erase(it); + continue; + } + it++; + } + + if (queue.empty()) { + events.store({}); + } + } + } else { + sys_net.error( + "[P2PS] Unexpected U2S TCP flag received on listening socket: 0x%02X", + tcp_header->flags); + } + + // Ignore other packets? + + return true; +} + +void lv2_socket_p2ps::send_u2s_packet(std::vector data, + const ::sockaddr_in *dst, u64 seq, + bool require_ack) { + char ip_str[16]; + inet_ntop(AF_INET, &dst->sin_addr, ip_str, sizeof(ip_str)); + sys_net.trace("[P2PS] Sending U2S packet on socket %d(id:%d): data(%d, seq " + "%d, require_ack %d) to %s:%d", + native_socket, lv2_id, data.size(), seq, require_ack, ip_str, + std::bit_cast>(dst->sin_port)); + + while (np::sendto_possibly_ipv6(native_socket, + reinterpret_cast(data.data()), + ::size32(data), dst, 0) == -1) { + const sys_net_error err = get_last_error(false); + // concurrency on the socket can result in EAGAIN error in which case we try + // again + if (err == SYS_NET_EAGAIN) { + continue; + } + + sys_net.error("[P2PS] Attempting to send a u2s packet failed(%s)!", err); + return; + } + + // Adds to tcp timeout monitor to resend the message until an ack is received + if (require_ack) { + auto &tcpm = g_fxo->get>(); + tcpm.add_message(lv2_id, dst, std::move(data), seq); + } +} + +void lv2_socket_p2ps::close_stream_nl(nt_p2p_port *p2p_port) { + status = p2ps_stream_status::stream_closed; + + for (auto it = p2p_port->bound_p2p_streams.begin(); + it != p2p_port->bound_p2p_streams.end();) { + if (it->second == lv2_id) { + it = p2p_port->bound_p2p_streams.erase(it); + continue; + } + it++; + } + + auto &tcpm = g_fxo->get>(); + tcpm.clear_all_messages(lv2_id); +} + +void lv2_socket_p2ps::close_stream() { + auto &nc = g_fxo->get(); + + std::lock_guard lock(nc.list_p2p_ports_mutex); + auto &p2p_port = ::at32(nc.list_p2p_ports, port); + + std::scoped_lock more_lock(p2p_port.bound_p2p_vports_mutex, mutex); + close_stream_nl(&p2p_port); +} + +p2ps_stream_status lv2_socket_p2ps::get_status() const { return status; } + +void lv2_socket_p2ps::set_status(p2ps_stream_status new_status) { + status = new_status; +} + +std::pair lv2_socket_p2ps::getpeername() { + std::lock_guard lock(mutex); + + if (!op_addr || !op_port || !op_vport) { + return {-SYS_NET_ENOTCONN, {}}; + } + + sys_net_sockaddr res{}; + sys_net_sockaddr_in_p2p *p2p_addr = + reinterpret_cast(&res); + + p2p_addr->sin_len = sizeof(sys_net_sockaddr_in_p2p); + p2p_addr->sin_family = SYS_NET_AF_INET; + p2p_addr->sin_addr = std::bit_cast, u32>(op_addr); + p2p_addr->sin_port = op_vport; + p2p_addr->sin_vport = op_port; + + return {CELL_OK, res}; +} + +std::tuple, sys_net_sockaddr> +lv2_socket_p2ps::accept(bool is_lock) { + std::unique_lock lock(mutex, std::defer_lock); + + if (is_lock) { + lock.lock(); + } + + if (backlog.size() == 0) { + if (so_nbio) { + return {true, -SYS_NET_EWOULDBLOCK, {}, {}}; + } + + return {false, {}, {}, {}}; + } + + auto p2ps_client = backlog.front(); + backlog.pop_front(); + + sys_net_sockaddr ps3_addr{}; + auto *paddr = reinterpret_cast(&ps3_addr); + + lv2_socket_p2ps *sock_client = reinterpret_cast( + idm::check_unlocked(p2ps_client)); + { + std::lock_guard lock(sock_client->mutex); + paddr->sin_family = SYS_NET_AF_INET; + paddr->sin_addr = std::bit_cast, u32>(sock_client->op_addr); + paddr->sin_port = sock_client->op_vport; + paddr->sin_vport = sock_client->op_port; + paddr->sin_len = sizeof(sys_net_sockaddr_in_p2p); + } + + return {true, p2ps_client, {}, ps3_addr}; +} + +s32 lv2_socket_p2ps::bind(const sys_net_sockaddr &addr) { + const auto *psa_in_p2p = + reinterpret_cast(&addr); + + // For SYS_NET_SOCK_STREAM_P2P sockets, the port is the "fake" tcp port and + // the vport is the udp port it's bound to + u16 p2p_port = psa_in_p2p->sin_vport; + u16 p2p_vport = psa_in_p2p->sin_port; + + sys_net.notice("[P2PS] Trying to bind %s:%d:%d", + np::ip_to_string(std::bit_cast(psa_in_p2p->sin_addr)), + p2p_port, p2p_vport); + + if (p2p_port == 0) { + p2p_port = SCE_NP_PORT; + } + + if (p2p_port != SCE_NP_PORT) { + sys_net.warning("[P2PS] Attempting to bind a socket to a port != %d", + +SCE_NP_PORT); + } + + socket_type real_socket{}; + + auto &nc = g_fxo->get(); + { + std::lock_guard list_lock(nc.list_p2p_ports_mutex); + + nc.create_p2p_port(p2p_port); + auto &pport = ::at32(nc.list_p2p_ports, p2p_port); + real_socket = pport.p2p_socket; + + { + // Ensures the socket & the bound list are updated at the same time to + // avoid races + std::lock_guard vport_lock(pport.bound_p2p_vports_mutex); + std::lock_guard sock_lock(mutex); + + if (p2p_vport == 0) { + sys_net.warning("[P2PS] vport was unassigned in bind!"); + p2p_vport = pport.get_port(); + + while (pport.bound_p2ps_vports.contains(p2p_vport)) { + p2p_vport = pport.get_port(); + } + + std::set bound_ports{lv2_id}; + pport.bound_p2ps_vports.insert( + std::make_pair(p2p_vport, std::move(bound_ports))); + } else { + if (pport.bound_p2ps_vports.contains(p2p_vport)) { + auto &bound_sockets = ::at32(pport.bound_p2ps_vports, p2p_vport); + if (!sys_net_helpers::all_reusable(bound_sockets)) { + return -SYS_NET_EADDRINUSE; + } + + bound_sockets.insert(lv2_id); + } else { + std::set bound_ports{lv2_id}; + pport.bound_p2ps_vports.insert( + std::make_pair(p2p_vport, std::move(bound_ports))); + } + } + + port = p2p_port; + vport = p2p_vport; + native_socket = real_socket; + bound_addr = psa_in_p2p->sin_addr; + } + } + + return CELL_OK; +} + +std::pair lv2_socket_p2ps::getsockname() { + std::lock_guard lock(mutex); + + // Unbound socket + if (!native_socket) { + return {CELL_OK, {}}; + } + + sys_net_sockaddr sn_addr{}; + sys_net_sockaddr_in_p2p *paddr = + reinterpret_cast(&sn_addr); + + paddr->sin_len = sizeof(sys_net_sockaddr_in); + paddr->sin_family = SYS_NET_AF_INET; + paddr->sin_port = vport; + paddr->sin_vport = port; + paddr->sin_addr = bound_addr; + + return {CELL_OK, sn_addr}; +} + +std::optional lv2_socket_p2ps::connect(const sys_net_sockaddr &addr) { + std::lock_guard lock(mutex); + + if (status != p2ps_stream_status::stream_closed) { + sys_net.error("[P2PS] Called connect on a socket that is not closed!"); + return -SYS_NET_EALREADY; + } + + p2ps_encapsulated_tcp send_hdr; + const auto psa_in_p2p = + reinterpret_cast(&addr); + auto name = sys_net_addr_to_native_addr(addr); + + // This is purposefully inverted, not a bug + const u16 dst_vport = psa_in_p2p->sin_port; + const u16 dst_port = psa_in_p2p->sin_vport; + + socket_type real_socket{}; + + auto &nc = g_fxo->get(); + { + std::lock_guard list_lock(nc.list_p2p_ports_mutex); + + nc.create_p2p_port(port); + auto &pport = ::at32(nc.list_p2p_ports, port); + real_socket = pport.p2p_socket; + + { + std::lock_guard lock(pport.bound_p2p_vports_mutex); + if (vport == 0) { + // Unassigned vport, assigns one + sys_net.warning("[P2PS] vport was unassigned before connect!"); + vport = pport.get_port(); + + while (pport.bound_p2p_vports.count(vport) || + pport.bound_p2p_streams.count(static_cast(vport) << 32)) { + vport = pport.get_port(); + } + } + const u64 key = name.sin_addr.s_addr | (static_cast(vport) << 32) | + (static_cast(dst_vport) << 48); + pport.bound_p2p_streams.emplace(key, lv2_id); + } + } + + native_socket = real_socket; + + send_hdr.src_port = vport; + send_hdr.dst_port = dst_vport; + send_hdr.flags = p2ps_tcp_flags::SYN; + send_hdr.seq = rand(); + + op_addr = name.sin_addr.s_addr; + op_port = dst_port; + op_vport = dst_vport; + cur_seq = send_hdr.seq + 1; + data_beg_seq = 0; + data_available = 0u; + received_data.clear(); + status = p2ps_stream_status::stream_handshaking; + + std::vector packet = generate_u2s_packet(send_hdr, nullptr, 0); + name.sin_port = std::bit_cast>(dst_port); // not a bug + send_u2s_packet(std::move(packet), reinterpret_cast<::sockaddr_in *>(&name), + send_hdr.seq, true); + + return CELL_OK; +} + +s32 lv2_socket_p2ps::listen(s32 backlog) { + std::lock_guard lock(mutex); + + status = p2ps_stream_status::stream_listening; + max_backlog = backlog; + + return CELL_OK; +} + +std::optional, sys_net_sockaddr>> +lv2_socket_p2ps::recvfrom([[maybe_unused]] s32 flags, u32 len, bool is_lock) { + std::unique_lock lock(mutex, std::defer_lock); + + if (is_lock) { + lock.lock(); + } + + if (!data_available) { + if (status == p2ps_stream_status::stream_closed) { + sys_net.error("[P2PS] Called recvfrom on closed socket!"); + return {{0, {}, {}}}; + } + + if (so_nbio || (flags & SYS_NET_MSG_DONTWAIT)) { + return {{-SYS_NET_EWOULDBLOCK, {}, {}}}; + } + + return std::nullopt; + } + + const u32 to_give = static_cast(std::min(data_available, len)); + sys_net_sockaddr addr{}; + std::vector dest_buf(to_give); + + sys_net.trace("[P2PS] STREAM-P2P socket had %u available, given %u", + data_available, to_give); + + u32 left_to_give = to_give; + while (left_to_give) { + auto &cur_data = received_data.begin()->second; + auto to_give_for_this_packet = + std::min(static_cast(cur_data.size()), left_to_give); + memcpy(dest_buf.data() + (to_give - left_to_give), cur_data.data(), + to_give_for_this_packet); + if (cur_data.size() != to_give_for_this_packet) { + auto amount_left = cur_data.size() - to_give_for_this_packet; + std::vector new_vec(amount_left); + memcpy(new_vec.data(), cur_data.data() + to_give_for_this_packet, + amount_left); + auto new_key = (received_data.begin()->first) + to_give_for_this_packet; + received_data.emplace(new_key, std::move(new_vec)); + } + + received_data.erase(received_data.begin()); + + left_to_give -= to_give_for_this_packet; + } + + data_available -= to_give; + data_beg_seq += to_give; + + sys_net_sockaddr_in_p2p *addr_p2p = + reinterpret_cast(&addr); + addr_p2p->sin_family = AF_INET; + addr_p2p->sin_addr = std::bit_cast, u32>(op_addr); + addr_p2p->sin_port = op_vport; + addr_p2p->sin_vport = op_port; + addr_p2p->sin_len = sizeof(sys_net_sockaddr_in_p2p); + + return {{to_give, dest_buf, addr}}; +} + +std::optional +lv2_socket_p2ps::sendto([[maybe_unused]] s32 flags, const std::vector &buf, + std::optional opt_sn_addr, + bool is_lock) { + std::unique_lock lock(mutex, std::defer_lock); + + if (is_lock) { + lock.lock(); + } + + if (status == p2ps_stream_status::stream_closed) { + sys_net.error("[P2PS] Called sendto on a closed socket!"); + return -SYS_NET_ECONNRESET; + } + + constexpr u32 max_data_len = + (65535 - (VPORT_P2P_HEADER_SIZE + sizeof(p2ps_encapsulated_tcp))); + + ::sockaddr_in name{}; + if (opt_sn_addr) { + name = sys_net_addr_to_native_addr(*opt_sn_addr); + } + + // Prepare address + name.sin_family = AF_INET; + name.sin_port = std::bit_cast>(op_port); + name.sin_addr.s_addr = op_addr; + // Prepares encapsulated tcp + p2ps_encapsulated_tcp tcp_header; + tcp_header.src_port = vport; + tcp_header.dst_port = op_vport; + // chop it up + std::vector> stream_packets; + u32 cur_total_len = ::size32(buf); + while (cur_total_len > 0) { + u32 cur_data_len = std::min(cur_total_len, max_data_len); + + tcp_header.length = cur_data_len; + tcp_header.seq = cur_seq; + + auto packet = generate_u2s_packet( + tcp_header, &buf[buf.size() - cur_total_len], cur_data_len); + send_u2s_packet(std::move(packet), &name, tcp_header.seq, true); + + cur_total_len -= cur_data_len; + cur_seq += cur_data_len; + } + + return {::size32(buf)}; +} + +std::optional +lv2_socket_p2ps::sendmsg([[maybe_unused]] s32 flags, + [[maybe_unused]] const sys_net_msghdr &msg, + [[maybe_unused]] bool is_lock) { + sys_net.todo("lv2_socket_p2ps::sendmsg"); + return {}; +} + +void lv2_socket_p2ps::close() { + if (!port || !vport) { + return; + } + + if (g_fxo->is_init()) { + auto &nc = g_fxo->get(); + std::lock_guard lock(nc.list_p2p_ports_mutex); + auto &p2p_port = ::at32(nc.list_p2p_ports, port); + { + std::lock_guard lock(p2p_port.bound_p2p_vports_mutex); + for (auto it = p2p_port.bound_p2p_streams.begin(); + it != p2p_port.bound_p2p_streams.end();) { + if (it->second == lv2_id) { + it = p2p_port.bound_p2p_streams.erase(it); + continue; + } + it++; + } + + if (p2p_port.bound_p2ps_vports.contains(vport)) { + auto &bound_ports = ::at32(p2p_port.bound_p2ps_vports, vport); + bound_ports.erase(lv2_id); + + if (bound_ports.empty()) { + p2p_port.bound_p2ps_vports.erase(vport); + } + } + } + } + + if (const auto tcpm = g_fxo->try_get>()) { + tcpm->clear_all_messages(lv2_id); + } +} + +s32 lv2_socket_p2ps::shutdown([[maybe_unused]] s32 how) { + sys_net.todo("[P2PS] shutdown"); + return CELL_OK; +} + +s32 lv2_socket_p2ps::poll(sys_net_pollfd &sn_pfd, + [[maybe_unused]] pollfd &native_pfd) { + std::lock_guard lock(mutex); + sys_net.trace("[P2PS] poll checking for 0x%X", sn_pfd.events); + if (status == p2ps_stream_status::stream_connected) { + if ((sn_pfd.events & SYS_NET_POLLIN) && data_available) { + sys_net.trace("[P2PS] p2ps has %u bytes available", data_available); + sn_pfd.revents |= SYS_NET_POLLIN; + } + + // Data can only be written if the socket is connected + if (sn_pfd.events & SYS_NET_POLLOUT && + status == p2ps_stream_status::stream_connected) { + sn_pfd.revents |= SYS_NET_POLLOUT; + } + + if (sn_pfd.revents) { + return 1; + } + } + + return 0; +} + +std::tuple +lv2_socket_p2ps::select(bs_t selected, + [[maybe_unused]] pollfd &native_pfd) { + std::lock_guard lock(mutex); + + bool read_set = false; + bool write_set = false; + + if (status == p2ps_stream_status::stream_connected) { + if ((selected & lv2_socket::poll_t::read) && data_available) { + sys_net.trace("[P2PS] socket has %d bytes available", data_available); + read_set = true; + } + + if (selected & lv2_socket::poll_t::write) { + sys_net.trace("[P2PS] socket is writeable"); + write_set = true; + } + } else if (status == p2ps_stream_status::stream_listening) { + const auto bsize = backlog.size(); + if ((selected & lv2_socket::poll_t::read) && bsize) { + sys_net.trace("[P2PS] socket has %d clients available", bsize); + read_set = true; + } + } + + return {read_set, write_set, false}; +} diff --git a/kernel/cellos/src/sys_net/lv2_socket_raw.cpp b/kernel/cellos/src/sys_net/lv2_socket_raw.cpp new file mode 100644 index 000000000..ab408a0b9 --- /dev/null +++ b/kernel/cellos/src/sys_net/lv2_socket_raw.cpp @@ -0,0 +1,137 @@ +#include "stdafx.h" + +#include "Emu/NP/vport0.h" +#include "sys_net/lv2_socket_raw.h" + +LOG_CHANNEL(sys_net); + +template struct socket_raw_logging { + socket_raw_logging() = default; + + socket_raw_logging(const socket_raw_logging &) = delete; + socket_raw_logging &operator=(const socket_raw_logging &) = delete; + + atomic_t logged = false; +}; + +#define LOG_ONCE(raw_var, message) \ + if (!g_fxo->get>().logged.exchange( \ + true)) { \ + sys_net.todo(message); \ + } + +lv2_socket_raw::lv2_socket_raw(lv2_socket_family family, lv2_socket_type type, + lv2_ip_protocol protocol) + : lv2_socket(family, type, protocol) {} + +lv2_socket_raw::lv2_socket_raw(utils::serial &ar, lv2_socket_type type) + : lv2_socket(make_exact(ar), type) {} + +void lv2_socket_raw::save(utils::serial &ar) { lv2_socket::save(ar, true); } + +std::tuple, sys_net_sockaddr> +lv2_socket_raw::accept([[maybe_unused]] bool is_lock) { + sys_net.fatal("[RAW] accept() called on a RAW socket"); + return {}; +} + +std::optional +lv2_socket_raw::connect([[maybe_unused]] const sys_net_sockaddr &addr) { + sys_net.fatal("[RAW] connect() called on a RAW socket"); + return CELL_OK; +} + +s32 lv2_socket_raw::connect_followup() { + sys_net.fatal("[RAW] connect_followup() called on a RAW socket"); + return CELL_OK; +} + +std::pair lv2_socket_raw::getpeername() { + LOG_ONCE(raw_getpeername, "[RAW] getpeername() called on a RAW socket"); + return {}; +} + +s32 lv2_socket_raw::listen([[maybe_unused]] s32 backlog) { + LOG_ONCE(raw_listen, "[RAW] listen() called on a RAW socket"); + return {}; +} + +s32 lv2_socket_raw::bind([[maybe_unused]] const sys_net_sockaddr &addr) { + LOG_ONCE(raw_bind, "lv2_socket_raw::bind"); + return {}; +} + +std::pair lv2_socket_raw::getsockname() { + LOG_ONCE(raw_getsockname, "lv2_socket_raw::getsockname"); + return {}; +} + +std::tuple +lv2_socket_raw::getsockopt([[maybe_unused]] s32 level, + [[maybe_unused]] s32 optname, + [[maybe_unused]] u32 len) { + LOG_ONCE(raw_getsockopt, "lv2_socket_raw::getsockopt"); + return {}; +} + +s32 lv2_socket_raw::setsockopt(s32 level, s32 optname, + const std::vector &optval) { + LOG_ONCE(raw_setsockopt, "lv2_socket_raw::setsockopt"); + + // TODO + int native_int = *reinterpret_cast *>(optval.data()); + + if (level == SYS_NET_SOL_SOCKET && optname == SYS_NET_SO_NBIO) { + so_nbio = native_int; + } + + return {}; +} + +std::optional, sys_net_sockaddr>> +lv2_socket_raw::recvfrom(s32 flags, [[maybe_unused]] u32 len, + [[maybe_unused]] bool is_lock) { + LOG_ONCE(raw_recvfrom, "lv2_socket_raw::recvfrom"); + + if (so_nbio || (flags & SYS_NET_MSG_DONTWAIT)) { + return {{-SYS_NET_EWOULDBLOCK, {}, {}}}; + } + + return {}; +} + +std::optional lv2_socket_raw::sendto( + [[maybe_unused]] s32 flags, [[maybe_unused]] const std::vector &buf, + [[maybe_unused]] std::optional opt_sn_addr, + [[maybe_unused]] bool is_lock) { + LOG_ONCE(raw_sendto, "lv2_socket_raw::sendto"); + return ::size32(buf); +} + +std::optional +lv2_socket_raw::sendmsg([[maybe_unused]] s32 flags, + [[maybe_unused]] const sys_net_msghdr &msg, + [[maybe_unused]] bool is_lock) { + LOG_ONCE(raw_sendmsg, "lv2_socket_raw::sendmsg"); + return {}; +} + +void lv2_socket_raw::close() { LOG_ONCE(raw_close, "lv2_socket_raw::close"); } + +s32 lv2_socket_raw::shutdown([[maybe_unused]] s32 how) { + LOG_ONCE(raw_shutdown, "lv2_socket_raw::shutdown"); + return {}; +} + +s32 lv2_socket_raw::poll([[maybe_unused]] sys_net_pollfd &sn_pfd, + [[maybe_unused]] pollfd &native_pfd) { + LOG_ONCE(raw_poll, "lv2_socket_raw::poll"); + return {}; +} + +std::tuple +lv2_socket_raw::select([[maybe_unused]] bs_t selected, + [[maybe_unused]] pollfd &native_pfd) { + LOG_ONCE(raw_select, "lv2_socket_raw::select"); + return {}; +} diff --git a/kernel/cellos/src/sys_net/network_context.cpp b/kernel/cellos/src/sys_net/network_context.cpp new file mode 100644 index 000000000..df4ab38e2 --- /dev/null +++ b/kernel/cellos/src/sys_net/network_context.cpp @@ -0,0 +1,299 @@ +#include "stdafx.h" + +#include "Emu/NP/ip_address.h" +#include "cellos/sys_sync.h" +#include "rpcsx/fw/ps3/sceNp.h" // for SCE_NP_PORT + +#include "sys_net/network_context.h" +#include "sys_net/sys_net_helpers.h" + +LOG_CHANNEL(sys_net); + +// Used by RPCN to send signaling packets to RPCN server(for UDP hole punching) +bool send_packet_from_p2p_port_ipv4(const std::vector &data, + const sockaddr_in &addr) { + auto &nc = g_fxo->get(); + { + std::lock_guard list_lock(nc.list_p2p_ports_mutex); + if (nc.list_p2p_ports.contains(SCE_NP_PORT)) { + auto &def_port = ::at32(nc.list_p2p_ports, SCE_NP_PORT); + + if (def_port.is_ipv6) { + const auto addr6 = np::sockaddr_to_sockaddr6(addr); + + if (::sendto(def_port.p2p_socket, + reinterpret_cast(data.data()), + ::size32(data), 0, + reinterpret_cast(&addr6), + sizeof(sockaddr_in6)) == -1) { + sys_net.error( + "Failed to send IPv4 signaling packet on IPv6 socket: %s", + get_last_error(false, false)); + return false; + } + } else if (::sendto(def_port.p2p_socket, + reinterpret_cast(data.data()), + ::size32(data), 0, + reinterpret_cast(&addr), + sizeof(sockaddr_in)) == -1) { + sys_net.error("Failed to send signaling packet on IPv4 socket: %s", + get_last_error(false, false)); + return false; + } + } else { + sys_net.error("send_packet_from_p2p_port_ipv4: port %d not present", + +SCE_NP_PORT); + return false; + } + } + + return true; +} + +bool send_packet_from_p2p_port_ipv6(const std::vector &data, + const sockaddr_in6 &addr) { + auto &nc = g_fxo->get(); + { + std::lock_guard list_lock(nc.list_p2p_ports_mutex); + if (nc.list_p2p_ports.contains(SCE_NP_PORT)) { + auto &def_port = ::at32(nc.list_p2p_ports, SCE_NP_PORT); + ensure(def_port.is_ipv6); + + if (::sendto(def_port.p2p_socket, + reinterpret_cast(data.data()), ::size32(data), + 0, reinterpret_cast(&addr), + sizeof(sockaddr_in6)) == -1) { + sys_net.error("Failed to send signaling packet on IPv6 socket: %s", + get_last_error(false, false)); + return false; + } + } else { + sys_net.error("send_packet_from_p2p_port_ipv6: port %d not present", + +SCE_NP_PORT); + return false; + } + } + + return true; +} + +std::vector> get_rpcn_msgs() { + std::vector> msgs; + auto &nc = g_fxo->get(); + { + std::lock_guard list_lock(nc.list_p2p_ports_mutex); + if (nc.list_p2p_ports.contains(SCE_NP_PORT)) { + auto &def_port = ::at32(nc.list_p2p_ports, SCE_NP_PORT); + { + std::lock_guard lock(def_port.s_rpcn_mutex); + msgs = std::move(def_port.rpcn_msgs); + def_port.rpcn_msgs.clear(); + } + } else { + sys_net.error("get_rpcn_msgs: port %d not present", +SCE_NP_PORT); + } + } + + return msgs; +} + +std::vector get_sign_msgs() { + std::vector msgs; + auto &nc = g_fxo->get(); + { + std::lock_guard list_lock(nc.list_p2p_ports_mutex); + if (nc.list_p2p_ports.contains(SCE_NP_PORT)) { + auto &def_port = ::at32(nc.list_p2p_ports, SCE_NP_PORT); + { + std::lock_guard lock(def_port.s_sign_mutex); + msgs = std::move(def_port.sign_msgs); + def_port.sign_msgs.clear(); + } + } else { + sys_net.error("get_sign_msgs: port %d not present", +SCE_NP_PORT); + } + } + + return msgs; +} + +namespace np { +void init_np_handler_dependencies(); +} + +void base_network_thread::add_ppu_to_awake(ppu_thread *ppu) { + std::lock_guard lock(mutex_ppu_to_awake); + ppu_to_awake.emplace_back(ppu); +} + +void base_network_thread::del_ppu_to_awake(ppu_thread *ppu) { + std::lock_guard lock(mutex_ppu_to_awake); + + for (auto it = ppu_to_awake.begin(); it != ppu_to_awake.end();) { + if (*it == ppu) { + it = ppu_to_awake.erase(it); + continue; + } + + it++; + } +} + +void base_network_thread::wake_threads() { + std::lock_guard lock(mutex_ppu_to_awake); + + ppu_to_awake.erase(std::unique(ppu_to_awake.begin(), ppu_to_awake.end()), + ppu_to_awake.end()); + for (ppu_thread *ppu : ppu_to_awake) { + network_clear_queue(*ppu); + lv2_obj::append(ppu); + } + + if (!ppu_to_awake.empty()) { + ppu_to_awake.clear(); + lv2_obj::awake_all(); + } +} + +p2p_thread::p2p_thread() { np::init_np_handler_dependencies(); } + +void p2p_thread::bind_sce_np_port() { + std::lock_guard list_lock(list_p2p_ports_mutex); + create_p2p_port(SCE_NP_PORT); +} + +void network_thread::operator()() { + std::vector> socklist; + socklist.reserve(lv2_socket::id_count); + + { + std::lock_guard lock(mutex_ppu_to_awake); + ppu_to_awake.clear(); + } + + std::vector<::pollfd> fds(lv2_socket::id_count); +#ifdef _WIN32 + std::vector connecting(lv2_socket::id_count); + std::vector was_connecting(lv2_socket::id_count); +#endif + + while (thread_ctrl::state() != thread_state::aborting) { + if (!num_polls) { + thread_ctrl::wait_on(num_polls, 0); + continue; + } + + ensure(socklist.size() <= lv2_socket::id_count); + + // Wait with 1ms timeout +#ifdef _WIN32 + windows_poll(fds, ::size32(socklist), 1, connecting); +#else + ::poll(fds.data(), socklist.size(), 1); +#endif + + std::lock_guard lock(mutex_thread_loop); + + for (usz i = 0; i < socklist.size(); i++) { +#ifdef _WIN32 + socklist[i]->handle_events(fds[i], was_connecting[i] && !connecting[i]); +#else + socklist[i]->handle_events(fds[i]); +#endif + } + + wake_threads(); + socklist.clear(); + + // Obtain all native active sockets + idm::select([&](u32 id, lv2_socket &s) { + if (s.get_type() == SYS_NET_SOCK_DGRAM || + s.get_type() == SYS_NET_SOCK_STREAM) { + socklist.emplace_back(idm::get_unlocked(id)); + } + }); + + for (usz i = 0; i < socklist.size(); i++) { + auto events = socklist[i]->get_events(); + + fds[i].fd = events ? socklist[i]->get_socket() : -1; + fds[i].events = (events & lv2_socket::poll_t::read ? POLLIN : 0) | + (events & lv2_socket::poll_t::write ? POLLOUT : 0) | 0; + fds[i].revents = 0; +#ifdef _WIN32 + const auto cur_connecting = socklist[i]->is_connecting(); + was_connecting[i] = cur_connecting; + connecting[i] = cur_connecting; +#endif + } + } +} + +// Must be used under list_p2p_ports_mutex lock! +void p2p_thread::create_p2p_port(u16 p2p_port) { + if (!list_p2p_ports.contains(p2p_port)) { + list_p2p_ports.emplace(std::piecewise_construct, + std::forward_as_tuple(p2p_port), + std::forward_as_tuple(p2p_port)); + const u32 prev_value = num_p2p_ports.fetch_add(1); + if (!prev_value) { + num_p2p_ports.notify_one(); + } + } +} + +void p2p_thread::operator()() { + std::vector<::pollfd> p2p_fd(lv2_socket::id_count); + + while (thread_ctrl::state() != thread_state::aborting) { + if (!num_p2p_ports) { + thread_ctrl::wait_on(num_p2p_ports, 0); + continue; + } + + // Check P2P sockets for incoming packets + auto num_p2p_sockets = 0; + std::memset(p2p_fd.data(), 0, p2p_fd.size() * sizeof(::pollfd)); + { + auto set_fd = [&](socket_type socket) { + p2p_fd[num_p2p_sockets].events = POLLIN; + p2p_fd[num_p2p_sockets].revents = 0; + p2p_fd[num_p2p_sockets].fd = socket; + num_p2p_sockets++; + }; + + std::lock_guard lock(list_p2p_ports_mutex); + for (const auto &[_, p2p_port] : list_p2p_ports) { + set_fd(p2p_port.p2p_socket); + } + } + +#ifdef _WIN32 + const auto ret_p2p = WSAPoll(p2p_fd.data(), num_p2p_sockets, 1); +#else + const auto ret_p2p = ::poll(p2p_fd.data(), num_p2p_sockets, 1); +#endif + if (ret_p2p > 0) { + std::lock_guard lock(list_p2p_ports_mutex); + auto fd_index = 0; + + auto process_fd = [&](nt_p2p_port &p2p_port) { + if ((p2p_fd[fd_index].revents & POLLIN) == POLLIN || + (p2p_fd[fd_index].revents & POLLRDNORM) == POLLRDNORM) { + while (p2p_port.recv_data()) + ; + } + fd_index++; + }; + + for (auto &[_, p2p_port] : list_p2p_ports) { + process_fd(p2p_port); + } + + wake_threads(); + } else if (ret_p2p < 0) { + sys_net.error("[P2P] Error poll on master P2P socket: %d", + get_last_error(false)); + } + } +} diff --git a/kernel/cellos/src/sys_net/nt_p2p_port.cpp b/kernel/cellos/src/sys_net/nt_p2p_port.cpp new file mode 100644 index 000000000..ccb5c59ee --- /dev/null +++ b/kernel/cellos/src/sys_net/nt_p2p_port.cpp @@ -0,0 +1,377 @@ +#include "sys_net/nt_p2p_port.h" +#include "Emu/NP/ip_address.h" +#include "Emu/NP/np_handler.h" +#include "Emu/NP/signaling_handler.h" +#include "Emu/NP/vport0.h" +#include "stdafx.h" +#include "sys_net/lv2_socket_p2ps.h" +#include "sys_net/sys_net_helpers.h" + +LOG_CHANNEL(sys_net); + +namespace sys_net_helpers { +bool all_reusable(const std::set &sock_ids) { + for (const s32 sock_id : sock_ids) { + const auto [_, reusable] = + idm::check(sock_id, [&](lv2_socket &sock) -> bool { + auto [res_reuseaddr, optval_reuseaddr, optlen_reuseaddr] = + sock.getsockopt(SYS_NET_SOL_SOCKET, SYS_NET_SO_REUSEADDR, + sizeof(s32)); + auto [res_reuseport, optval_reuseport, optlen_reuseport] = + sock.getsockopt(SYS_NET_SOL_SOCKET, SYS_NET_SO_REUSEPORT, + sizeof(s32)); + + const bool reuse_addr = + optlen_reuseaddr == 4 && !!optval_reuseaddr._int; + const bool reuse_port = + optlen_reuseport == 4 && !!optval_reuseport._int; + + return (reuse_addr || reuse_port); + }); + + if (!reusable) { + return false; + } + } + + return true; +} +} // namespace sys_net_helpers + +nt_p2p_port::nt_p2p_port(u16 port) : port(port) { + is_ipv6 = np::is_ipv6_supported(); + + // Creates and bind P2P Socket + p2p_socket = is_ipv6 ? ::socket(AF_INET6, SOCK_DGRAM, 0) + : ::socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); +#ifdef _WIN32 + if (p2p_socket == INVALID_SOCKET) +#else + if (p2p_socket == -1) +#endif + fmt::throw_exception("Failed to create DGRAM socket for P2P socket: %s!", + get_last_error(true)); + + np::set_socket_non_blocking(p2p_socket); + + u32 optval = 131072; // value obtained from DECR for a SOCK_DGRAM_P2P + // socket(should maybe be bigger for actual socket?) + if (setsockopt(p2p_socket, SOL_SOCKET, SO_RCVBUF, + reinterpret_cast(&optval), sizeof(optval)) != 0) + fmt::throw_exception("Error setsockopt SO_RCVBUF on P2P socket: %s", + get_last_error(true)); + + int ret_bind = 0; + const u16 be_port = std::bit_cast>(port); + + if (is_ipv6) { + // Some OS(Windows, maybe more) will only support IPv6 adressing by default + // and we need IPv4 over IPv6 + optval = 0; + if (setsockopt(p2p_socket, IPPROTO_IPV6, IPV6_V6ONLY, + reinterpret_cast(&optval), + sizeof(optval)) != 0) + fmt::throw_exception("Error setsockopt IPV6_V6ONLY on P2P socket: %s", + get_last_error(true)); + + ::sockaddr_in6 p2p_ipv6_addr{.sin6_family = AF_INET6, .sin6_port = be_port}; + ret_bind = ::bind(p2p_socket, reinterpret_cast(&p2p_ipv6_addr), + sizeof(p2p_ipv6_addr)); + } else { + ::sockaddr_in p2p_ipv4_addr{.sin_family = AF_INET, .sin_port = be_port}; + ret_bind = ::bind(p2p_socket, reinterpret_cast(&p2p_ipv4_addr), + sizeof(p2p_ipv4_addr)); + } + + if (ret_bind == -1) + fmt::throw_exception("Failed to bind DGRAM socket to %d for P2P: %s!", port, + get_last_error(true)); + + auto &nph = g_fxo->get>(); + nph.upnp_add_port_mapping(port, "UDP"); + + sys_net.notice("P2P port %d was bound!", port); +} + +nt_p2p_port::~nt_p2p_port() { np::close_socket(p2p_socket); } + +void nt_p2p_port::dump_packet(p2ps_encapsulated_tcp *tcph) { + sys_net.trace("PACKET DUMP:\nsrc_port: %d\ndst_port: %d\nflags: %d\nseq: " + "%d\nack: %d\nlen: %d", + tcph->src_port, tcph->dst_port, tcph->flags, tcph->seq, + tcph->ack, tcph->length); +} + +// Must be used under bound_p2p_vports_mutex lock +u16 nt_p2p_port::get_port() { + if (binding_port == 0) { + binding_port = 30000; + } + + return binding_port++; +} + +bool nt_p2p_port::handle_connected(s32 sock_id, + p2ps_encapsulated_tcp *tcp_header, u8 *data, + ::sockaddr_storage *op_addr) { + const auto sock = + idm::check(sock_id, [&](lv2_socket &sock) -> bool { + ensure(sock.get_type() == SYS_NET_SOCK_STREAM_P2P); + auto &sock_p2ps = reinterpret_cast(sock); + + return sock_p2ps.handle_connected(tcp_header, data, op_addr, this); + }); + + if (!sock) { + sys_net.error("[P2PS] Couldn't find the socket!"); + return false; + } + + if (!sock.ret) { + sys_net.error("[P2PS] handle_connected() failed!"); + return false; + } + + return true; +} + +bool nt_p2p_port::handle_listening(s32 sock_id, + p2ps_encapsulated_tcp *tcp_header, u8 *data, + ::sockaddr_storage *op_addr) { + auto sock = idm::get_unlocked(sock_id); + if (!sock) + return false; + + auto &sock_p2ps = reinterpret_cast(*sock); + return sock_p2ps.handle_listening(tcp_header, data, op_addr); +} + +bool nt_p2p_port::recv_data() { + ::sockaddr_storage native_addr{}; + ::socklen_t native_addrlen = sizeof(native_addr); + const auto recv_res = ::recvfrom( + p2p_socket, reinterpret_cast(p2p_recv_data.data()), + ::size32(p2p_recv_data), 0, + reinterpret_cast(&native_addr), &native_addrlen); + + if (recv_res == -1) { + auto lerr = get_last_error(false); + if (lerr != SYS_NET_EINPROGRESS && lerr != SYS_NET_EWOULDBLOCK) + sys_net.error("Error recvfrom on %s P2P socket: %d", + is_ipv6 ? "IPv6" : "IPv4", lerr); + + return false; + } + + if (recv_res < static_cast(sizeof(u16))) { + sys_net.error("Received badly formed packet on P2P port(no vport)!"); + return true; + } + + u16 dst_vport = reinterpret_cast &>(p2p_recv_data[0]); + + if (is_ipv6) { + const auto *addr_ipv6 = reinterpret_cast(&native_addr); + const auto addr_ipv4 = np::sockaddr6_to_sockaddr(*addr_ipv6); + native_addr = {}; + std::memcpy(&native_addr, &addr_ipv4, sizeof(addr_ipv4)); + } + + if (dst_vport == 0) { + if (recv_res < VPORT_0_HEADER_SIZE) { + sys_net.error("Bad vport 0 packet(no subset)!"); + return true; + } + + const u8 subset = p2p_recv_data[2]; + const auto data_size = recv_res - VPORT_0_HEADER_SIZE; + std::vector vport_0_data(p2p_recv_data.data() + VPORT_0_HEADER_SIZE, + p2p_recv_data.data() + VPORT_0_HEADER_SIZE + + data_size); + + switch (subset) { + case SUBSET_RPCN: { + std::lock_guard lock(s_rpcn_mutex); + rpcn_msgs.push_back(std::move(vport_0_data)); + return true; + } + case SUBSET_SIGNALING: { + signaling_message msg; + msg.src_addr = + reinterpret_cast(&native_addr)->sin_addr.s_addr; + msg.src_port = std::bit_cast>( + reinterpret_cast(&native_addr)->sin_port); + msg.data = std::move(vport_0_data); + + { + std::lock_guard lock(s_sign_mutex); + sign_msgs.push_back(std::move(msg)); + } + + auto &sigh = g_fxo->get>(); + sigh.wake_up(); + return true; + } + default: { + sys_net.error("Invalid vport 0 subset!"); + return true; + } + } + } + + if (recv_res < VPORT_P2P_HEADER_SIZE) { + return true; + } + + const u16 src_vport = + *reinterpret_cast *>(p2p_recv_data.data() + sizeof(u16)); + const u16 vport_flags = *reinterpret_cast *>( + p2p_recv_data.data() + sizeof(u16) + sizeof(u16)); + std::vector p2p_data(recv_res - VPORT_P2P_HEADER_SIZE); + memcpy(p2p_data.data(), p2p_recv_data.data() + VPORT_P2P_HEADER_SIZE, + p2p_data.size()); + + if (vport_flags & P2P_FLAG_P2P) { + std::lock_guard lock(bound_p2p_vports_mutex); + if (bound_p2p_vports.contains(dst_vport)) { + sys_net_sockaddr_in_p2p p2p_addr{}; + + p2p_addr.sin_len = sizeof(sys_net_sockaddr_in); + p2p_addr.sin_family = SYS_NET_AF_INET; + p2p_addr.sin_addr = std::bit_cast, u32>( + reinterpret_cast(&native_addr) + ->sin_addr.s_addr); + p2p_addr.sin_vport = src_vport; + p2p_addr.sin_port = std::bit_cast, u16>( + reinterpret_cast(&native_addr)->sin_port); + + auto &bound_sockets = ::at32(bound_p2p_vports, dst_vport); + + for (const auto sock_id : bound_sockets) { + const auto sock = + idm::check(sock_id, [&](lv2_socket &sock) { + ensure(sock.get_type() == SYS_NET_SOCK_DGRAM_P2P); + auto &sock_p2p = reinterpret_cast(sock); + + sock_p2p.handle_new_data(p2p_addr, p2p_data); + }); + + if (!sock) { + sys_net.error("Socket %d found in bound_p2p_vports didn't exist!", + sock_id); + bound_sockets.erase(sock_id); + if (bound_sockets.empty()) { + bound_p2p_vports.erase(dst_vport); + } + } + } + + return true; + } + } else if (vport_flags & P2P_FLAG_P2PS) { + if (p2p_data.size() < sizeof(p2ps_encapsulated_tcp)) { + sys_net.notice("Received P2P packet targeted at unbound vport(likely) or " + "invalid(vport=%d)", + dst_vport); + return true; + } + + auto *tcp_header = + reinterpret_cast(p2p_data.data()); + + // Validate signature & length + if (tcp_header->signature != P2PS_U2S_SIG) { + sys_net.notice("Received P2P packet targeted at unbound vport(vport=%d)", + dst_vport); + return true; + } + + if (tcp_header->length != + (p2p_data.size() - sizeof(p2ps_encapsulated_tcp))) { + sys_net.error( + "Received STREAM-P2P packet tcp length didn't match packet length"); + return true; + } + + // Sanity check + if (tcp_header->dst_port != dst_vport) { + sys_net.error("Received STREAM-P2P packet with dst_port != vport"); + return true; + } + + // Validate checksum + u16 given_checksum = tcp_header->checksum; + tcp_header->checksum = 0; + if (given_checksum != + u2s_tcp_checksum(reinterpret_cast *>(p2p_data.data()), + p2p_data.size())) { + sys_net.error("Checksum is invalid, dropping packet!"); + return true; + } + + // The packet is valid + dump_packet(tcp_header); + + // Check if it's bound + const u64 key_connected = + (reinterpret_cast(&native_addr) + ->sin_addr.s_addr) | + (static_cast(tcp_header->src_port) << 48) | + (static_cast(tcp_header->dst_port) << 32); + + { + std::lock_guard lock(bound_p2p_vports_mutex); + if (bound_p2p_streams.contains(key_connected)) { + const auto sock_id = ::at32(bound_p2p_streams, key_connected); + sys_net.trace("Received packet for connected STREAM-P2P socket(s=%d)", + sock_id); + handle_connected(sock_id, tcp_header, + p2p_data.data() + sizeof(p2ps_encapsulated_tcp), + &native_addr); + return true; + } + + if (bound_p2ps_vports.contains(tcp_header->dst_port)) { + const auto &bound_sockets = + ::at32(bound_p2ps_vports, tcp_header->dst_port); + + for (const auto sock_id : bound_sockets) { + sys_net.trace("Received packet for listening STREAM-P2P socket(s=%d)", + sock_id); + handle_listening(sock_id, tcp_header, + p2p_data.data() + sizeof(p2ps_encapsulated_tcp), + &native_addr); + } + return true; + } + + if (tcp_header->flags == p2ps_tcp_flags::RST) { + sys_net.trace("[P2PS] Received RST on unbound P2PS"); + return true; + } + + // The P2PS packet was sent to an unbound vport, send a RST packet + p2ps_encapsulated_tcp send_hdr; + send_hdr.src_port = tcp_header->dst_port; + send_hdr.dst_port = tcp_header->src_port; + send_hdr.flags = p2ps_tcp_flags::RST; + auto packet = generate_u2s_packet(send_hdr, nullptr, 0); + + if (np::sendto_possibly_ipv6( + p2p_socket, reinterpret_cast(packet.data()), + ::size32(packet), + reinterpret_cast(&native_addr), 0) == -1) { + sys_net.error("[P2PS] Error sending RST to sender to unbound P2PS: %s", + get_last_error(false)); + return true; + } + + sys_net.trace("[P2PS] Sent RST to sender to unbound P2PS"); + return true; + } + } + + sys_net.notice("Received a P2P packet with no bound target(dst_vport = %d)", + dst_vport); + return true; +} diff --git a/kernel/cellos/src/sys_net/sys_net_helpers.cpp b/kernel/cellos/src/sys_net/sys_net_helpers.cpp new file mode 100644 index 000000000..7a6deafdc --- /dev/null +++ b/kernel/cellos/src/sys_net/sys_net_helpers.cpp @@ -0,0 +1,243 @@ +#include "stdafx.h" + +#include "Emu/Cell/PPUThread.h" +#include "Emu/IdManager.h" +#include "sys_net/lv2_socket.h" +#include "sys_net/network_context.h" +#include "sys_net/sys_net_helpers.h" + +LOG_CHANNEL(sys_net); + +int get_native_error() { + int native_error; +#ifdef _WIN32 + native_error = WSAGetLastError(); +#else + native_error = errno; +#endif + + return native_error; +} + +sys_net_error convert_error(bool is_blocking, int native_error, + [[maybe_unused]] bool is_connecting) { + // Convert the error code for socket functions to a one for sys_net + sys_net_error result{}; + const char *name{}; + +#ifdef _WIN32 +#define ERROR_CASE(error) \ + case WSA##error: \ + result = SYS_NET_##error; \ + name = #error; \ + break; +#else +#define ERROR_CASE(error) \ + case error: \ + result = SYS_NET_##error; \ + name = #error; \ + break; +#endif + switch (native_error) { +#ifndef _WIN32 + ERROR_CASE(ENOENT); + ERROR_CASE(ENOMEM); + ERROR_CASE(EBUSY); + ERROR_CASE(ENOSPC); + ERROR_CASE(EPIPE); +#endif + + // TODO: We don't currently support EFAULT or EINTR + // ERROR_CASE(EFAULT); + // ERROR_CASE(EINTR); + + ERROR_CASE(EBADF); + ERROR_CASE(EACCES); + ERROR_CASE(EINVAL); + ERROR_CASE(EMFILE); + ERROR_CASE(EWOULDBLOCK); + ERROR_CASE(EINPROGRESS); + ERROR_CASE(EALREADY); + ERROR_CASE(EDESTADDRREQ); + ERROR_CASE(EMSGSIZE); + ERROR_CASE(EPROTOTYPE); + ERROR_CASE(ENOPROTOOPT); + ERROR_CASE(EPROTONOSUPPORT); + ERROR_CASE(EOPNOTSUPP); + ERROR_CASE(EPFNOSUPPORT); + ERROR_CASE(EAFNOSUPPORT); + ERROR_CASE(EADDRINUSE); + ERROR_CASE(EADDRNOTAVAIL); + ERROR_CASE(ENETDOWN); + ERROR_CASE(ENETUNREACH); + ERROR_CASE(ECONNABORTED); + ERROR_CASE(ECONNRESET); + ERROR_CASE(ENOBUFS); + ERROR_CASE(EISCONN); + ERROR_CASE(ENOTCONN); + ERROR_CASE(ESHUTDOWN); + ERROR_CASE(ETOOMANYREFS); + ERROR_CASE(ETIMEDOUT); + ERROR_CASE(ECONNREFUSED); + ERROR_CASE(EHOSTDOWN); + ERROR_CASE(EHOSTUNREACH); +#ifdef _WIN32 + // Windows likes to be special with unique errors + case WSAENETRESET: + result = SYS_NET_ECONNRESET; + name = "WSAENETRESET"; + break; +#endif + default: + fmt::throw_exception("sys_net get_last_error(is_blocking=%d, " + "native_error=%d): Unknown/illegal socket error", + is_blocking, native_error); + } + +#ifdef _WIN32 + if (is_connecting) { + // Windows will return SYS_NET_ENOTCONN when recvfrom/sendto is called on a + // socket that is connecting but not yet connected + if (result == SYS_NET_ENOTCONN) + return SYS_NET_EAGAIN; + } +#endif + + if (name && result != SYS_NET_EWOULDBLOCK && result != SYS_NET_EINPROGRESS) { + sys_net.error("Socket error %s", name); + } + + if (is_blocking && result == SYS_NET_EWOULDBLOCK) { + return {}; + } + + if (is_blocking && result == SYS_NET_EINPROGRESS) { + return {}; + } + + return result; +#undef ERROR_CASE +} + +sys_net_error get_last_error(bool is_blocking, bool is_connecting) { + return convert_error(is_blocking, get_native_error(), is_connecting); +} + +sys_net_sockaddr +native_addr_to_sys_net_addr(const ::sockaddr_storage &native_addr) { + ensure(native_addr.ss_family == AF_INET || + native_addr.ss_family == AF_UNSPEC); + + sys_net_sockaddr sn_addr; + + sys_net_sockaddr_in *paddr = + reinterpret_cast(&sn_addr); + *paddr = {}; + + paddr->sin_len = sizeof(sys_net_sockaddr_in); + paddr->sin_family = SYS_NET_AF_INET; + paddr->sin_port = std::bit_cast, u16>( + reinterpret_cast(&native_addr)->sin_port); + paddr->sin_addr = std::bit_cast, u32>( + reinterpret_cast(&native_addr)->sin_addr.s_addr); + + return sn_addr; +} + +::sockaddr_in sys_net_addr_to_native_addr(const sys_net_sockaddr &sn_addr) { + ensure(sn_addr.sa_family == SYS_NET_AF_INET); + + const sys_net_sockaddr_in *psa_in = + reinterpret_cast(&sn_addr); + + ::sockaddr_in native_addr{}; + native_addr.sin_family = AF_INET; + native_addr.sin_port = std::bit_cast(psa_in->sin_port); + native_addr.sin_addr.s_addr = std::bit_cast(psa_in->sin_addr); + +#ifdef _WIN32 + // Windows doesn't support sending packets to 0.0.0.0 but it works on unixes, + // send to 127.0.0.1 instead + if (native_addr.sin_addr.s_addr == 0x00000000) { + sys_net.warning("[Native] Redirected 0.0.0.0 to 127.0.0.1"); + native_addr.sin_addr.s_addr = std::bit_cast>(0x7F000001); + } +#endif + + return native_addr; +} + +bool is_ip_public_address(const ::sockaddr_in &addr) { + const u8 *ip = reinterpret_cast(&addr.sin_addr.s_addr); + + if ((ip[0] == 10) || (ip[0] == 127) || + (ip[0] == 172 && (ip[1] >= 16 && ip[1] <= 31)) || + (ip[0] == 192 && ip[1] == 168)) { + return false; + } + + return true; +} + +u32 network_clear_queue(ppu_thread &ppu) { + u32 cleared = 0; + + idm::select( + [&](u32, lv2_socket &sock) { cleared += sock.clear_queue(&ppu); }); + + return cleared; +} + +void clear_ppu_to_awake(ppu_thread &ppu) { + g_fxo->get().del_ppu_to_awake(&ppu); + g_fxo->get().del_ppu_to_awake(&ppu); +} + +#ifdef _WIN32 +// Workaround function for WSAPoll not reporting failed connections +// Note that this was fixed in Windows 10 version 2004 (after more than 10 years +// lol) +void windows_poll(std::vector &fds, unsigned long nfds, int timeout, + std::vector &connecting) { + ensure(fds.size() >= nfds); + ensure(connecting.size() >= nfds); + + // Don't call WSAPoll with zero nfds (errors 10022 or 10038) + if (std::none_of(fds.begin(), fds.begin() + nfds, + [](pollfd &pfd) { return pfd.fd != INVALID_SOCKET; })) { + if (timeout > 0) { + Sleep(timeout); + } + + return; + } + + int r = ::WSAPoll(fds.data(), nfds, timeout); + + if (r == SOCKET_ERROR) { + sys_net.error( + "WSAPoll failed: %s", + fmt::win_error{static_cast(WSAGetLastError()), nullptr}); + return; + } + + for (unsigned long i = 0; i < nfds; i++) { + if (connecting[i]) { + if (!fds[i].revents) { + int error = 0; + socklen_t intlen = sizeof(error); + if (getsockopt(fds[i].fd, SOL_SOCKET, SO_ERROR, + reinterpret_cast(&error), &intlen) == -1 || + error != 0) { + // Connection silently failed + connecting[i] = false; + fds[i].revents = + POLLERR | POLLHUP | (fds[i].events & (POLLIN | POLLOUT)); + } + } else { + connecting[i] = false; + } + } + } +} +#endif diff --git a/kernel/cellos/src/sys_overlay.cpp b/kernel/cellos/src/sys_overlay.cpp new file mode 100644 index 000000000..7108e1027 --- /dev/null +++ b/kernel/cellos/src/sys_overlay.cpp @@ -0,0 +1,210 @@ +#include "stdafx.h" + +#include "Crypto/unedat.h" +#include "Crypto/unself.h" +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/IdManager.h" +#include "Emu/Memory/vm_ptr.h" +#include "Emu/VFS.h" +#include "Emu/system_config.h" +#include "Loader/ELF.h" + +#include "sys_fs.h" +#include "sys_overlay.h" +#include "sys_process.h" + +extern std::pair, CellError> +ppu_load_overlay(const ppu_exec_object &, bool virtual_load, + const std::string &path, s64 file_offset, + utils::serial *ar = nullptr); + +extern bool ppu_initialize(const ppu_module &, bool check_only = false, + u64 file_size = 0); +extern void ppu_finalize(const ppu_module &info, + bool force_mem_release = false); + +LOG_CHANNEL(sys_overlay); + +static error_code overlay_load_module(vm::ptr ovlmid, + const std::string &vpath, u64 /*flags*/, + vm::ptr entry, fs::file src = {}, + s64 file_offset = 0) { + if (!src) { + auto [fs_error, ppath, path, lv2_file, type] = lv2_file::open(vpath, 0, 0); + + if (fs_error) { + return {fs_error, vpath}; + } + + src = std::move(lv2_file); + } + + u128 klic = g_fxo->get().last_key(); + + src = decrypt_self(std::move(src), reinterpret_cast(&klic)); + + if (!src) { + return {CELL_ENOEXEC, +"Failed to decrypt file"}; + } + + ppu_exec_object obj = std::move(src); + src.close(); + + if (obj != elf_error::ok) { + return {CELL_ENOEXEC, obj.operator elf_error()}; + } + + const auto [ovlm, error] = + ppu_load_overlay(obj, false, vfs::get(vpath), file_offset); + + obj.clear(); + + if (error) { + if (error == CELL_CANCEL + 0u) { + // Emulation stopped + return {}; + } + + return error; + } + + ppu_initialize(*ovlm); + + sys_overlay.success("Loaded overlay: \"%s\" (id=0x%x)", vpath, + idm::last_id()); + + *ovlmid = idm::last_id(); + *entry = ovlm->entry; + + return CELL_OK; +} + +fs::file make_file_view(fs::file &&file, u64 offset, u64 size); + +std::function lv2_overlay::load(utils::serial &ar) { + const std::string vpath = ar.pop(); + const std::string path = vfs::get(vpath); + const s64 offset = ar.pop(); + + sys_overlay.success("lv2_overlay::load(): vpath='%s', path='%s', offset=0x%x", + vpath, path, offset); + + shared_ptr ovlm; + + fs::file file{path.substr( + 0, path.size() - (offset ? fmt::format("_x%x", offset).size() : 0))}; + + if (file) { + u128 klic = g_fxo->get().last_key(); + file = make_file_view(std::move(file), offset, umax); + ovlm = + ppu_load_overlay(ppu_exec_object{decrypt_self( + std::move(file), reinterpret_cast(&klic))}, + false, path, 0, &ar) + .first; + + if (!ovlm) { + fmt::throw_exception("lv2_overlay::load(): ppu_load_overlay() failed. " + "(vpath='%s', offset=0x%x)", + vpath, offset); + } + } else if (!g_cfg.savestate.state_inspection_mode.get()) { + fmt::throw_exception( + "lv2_overlay::load(): Failed to find file. (vpath='%s', offset=0x%x)", + vpath, offset); + } else { + sys_overlay.error( + "lv2_overlay::load(): Failed to find file. (vpath='%s', offset=0x%x)", + vpath, offset); + } + + return [ovlm](void *storage) { + *static_cast *>(storage) = ovlm; + }; +} + +void lv2_overlay::save(utils::serial &ar) { + USING_SERIALIZATION_VERSION(lv2_prx_overlay); + + const std::string vpath = vfs::retrieve(path); + (vpath.empty() ? sys_overlay.error : sys_overlay.success)( + "lv2_overlay::save(): vpath='%s', offset=0x%x", vpath, offset); + + ar(vpath, offset); +} + +error_code sys_overlay_load_module(vm::ptr ovlmid, vm::cptr path, + u64 flags, vm::ptr entry) { + sys_overlay.warning( + "sys_overlay_load_module(ovlmid=*0x%x, path=%s, flags=0x%x, entry=*0x%x)", + ovlmid, path, flags, entry); + + if (!g_ps3_process_info.ppc_seg) { + // Process not permitted + return CELL_ENOSYS; + } + + if (!path) { + return CELL_EFAULT; + } + + return overlay_load_module(ovlmid, path.get_ptr(), flags, entry); +} + +error_code sys_overlay_load_module_by_fd(vm::ptr ovlmid, u32 fd, + u64 offset, u64 flags, + vm::ptr entry) { + sys_overlay.warning("sys_overlay_load_module_by_fd(ovlmid=*0x%x, fd=%d, " + "offset=0x%llx, flags=0x%x, entry=*0x%x)", + ovlmid, fd, offset, flags, entry); + + if (!g_ps3_process_info.ppc_seg) { + // Process not permitted + return CELL_ENOSYS; + } + + if (static_cast(offset) < 0) { + return CELL_EINVAL; + } + + const auto file = idm::get_unlocked(fd); + + if (!file) { + return CELL_EBADF; + } + + std::lock_guard lock(file->mp->mutex); + + if (!file->file) { + return CELL_EBADF; + } + + return overlay_load_module( + ovlmid, + offset ? fmt::format("%s_x%x", file->name.data(), offset) + : file->name.data(), + flags, entry, lv2_file::make_view(file, offset), offset); +} + +error_code sys_overlay_unload_module(u32 ovlmid) { + sys_overlay.warning("sys_overlay_unload_module(ovlmid=0x%x)", ovlmid); + + if (!g_ps3_process_info.ppc_seg) { + // Process not permitted + return CELL_ENOSYS; + } + + const auto _main = idm::withdraw(ovlmid); + + if (!_main) { + return CELL_ESRCH; + } + + for (auto &seg : _main->segs) { + vm::dealloc(seg.addr); + } + + ppu_finalize(*_main); + + return CELL_OK; +} diff --git a/kernel/cellos/src/sys_ppu_thread.cpp b/kernel/cellos/src/sys_ppu_thread.cpp new file mode 100644 index 000000000..7e0ca06bd --- /dev/null +++ b/kernel/cellos/src/sys_ppu_thread.cpp @@ -0,0 +1,645 @@ +#include "stdafx.h" + +#include "sys_ppu_thread.h" + +#include "Emu/IdManager.h" +#include "Emu/System.h" + +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/Cell/PPUCallback.h" +#include "Emu/Cell/PPUOpcodes.h" +#include "Emu/Cell/PPUThread.h" +#include "Emu/Memory/vm_locking.h" +#include "sys_event.h" +#include "sys_memory.h" +#include "sys_mmapper.h" +#include "sys_process.h" + +#include "util/asm.hpp" + +#include + +LOG_CHANNEL(sys_ppu_thread); + +// Simple structure to cleanup previous thread, because can't remove its own +// thread +struct ppu_thread_cleaner { + shared_ptr> old; + + shared_ptr> + clean(shared_ptr> ptr) { + return std::exchange(old, std::move(ptr)); + } + + ppu_thread_cleaner() = default; + + ppu_thread_cleaner(const ppu_thread_cleaner &) = delete; + + ppu_thread_cleaner &operator=(const ppu_thread_cleaner &) = delete; + + ppu_thread_cleaner &operator=(thread_state state) noexcept { + reader_lock lock(id_manager::g_mutex); + + if (old) { + // It is detached from IDM now so join must be done explicitly now + *static_cast *>(old.get()) = state; + } + + return *this; + } +}; + +void ppu_thread_exit(ppu_thread &ppu, ppu_opcode_t, be_t *, + struct ppu_intrp_func *) { + ppu.state += cpu_flag::exit + cpu_flag::wait; + + // Deallocate Stack Area + ensure(vm::dealloc(ppu.stack_addr, vm::stack) == ppu.stack_size); + + if (auto dct = g_fxo->try_get()) { + dct->free(ppu.stack_size); + } + + if (ppu.call_history.index) { + ppu_log.notice("Calling history: %s", ppu.call_history); + ppu.call_history.index = 0; + } + + if (ppu.syscall_history.index) { + ppu_log.notice("HLE/LV2 history: %s", ppu.syscall_history); + ppu.syscall_history.index = 0; + } +} + +constexpr u32 c_max_ppu_name_size = 28; + +void _sys_ppu_thread_exit(ppu_thread &ppu, u64 errorcode) { + ppu.state += cpu_flag::wait; + u64 writer_mask = 0; + + sys_ppu_thread.trace("_sys_ppu_thread_exit(errorcode=0x%llx)", errorcode); + + ppu_join_status old_status; + + // Avoid cases where cleaning causes the destructor to be called inside IDM + // lock scope (for performance) + shared_ptr> old_ppu; + { + lv2_obj::notify_all_t notify; + lv2_obj::prepare_for_sleep(ppu); + + std::lock_guard lock(id_manager::g_mutex); + + // Get joiner ID + old_status = ppu.joiner.fetch_op([](ppu_join_status &status) { + if (status == ppu_join_status::joinable) { + // Joinable, not joined + status = ppu_join_status::zombie; + return; + } + + // Set deleted thread status + status = ppu_join_status::exited; + }); + + if (old_status >= ppu_join_status::max) { + lv2_obj::append(idm::check_unlocked>( + static_cast(old_status))); + } + + if (old_status != ppu_join_status::joinable) { + // Remove self ID from IDM, move owning ptr + old_ppu = g_fxo->get().clean( + idm::withdraw>(ppu.id, 0, + std::false_type{})); + } + + // Get writers mask (wait for all current writers to quit) + writer_mask = vm::g_range_lock_bits[1]; + + // Unqueue + lv2_obj::sleep(ppu); + notify.cleanup(); + + // Remove suspend state (TODO) + ppu.state -= cpu_flag::suspend; + } + + while (ppu.joiner == ppu_join_status::zombie) { + if (ppu.is_stopped() && + ppu.joiner.compare_and_swap_test(ppu_join_status::zombie, + ppu_join_status::joinable)) { + // Abort + ppu.state += cpu_flag::again; + return; + } + + // Wait for termination + thread_ctrl::wait_on(ppu.joiner, ppu_join_status::zombie); + } + + ppu_thread_exit(ppu, {}, nullptr, nullptr); + + if (old_ppu) { + // It is detached from IDM now so join must be done explicitly now + *old_ppu = thread_state::finished; + } + + // Need to wait until the current writers finish + if (ppu.state & cpu_flag::memory) { + for (; writer_mask; writer_mask &= vm::g_range_lock_bits[1]) { + busy_wait(200); + } + } +} + +s32 sys_ppu_thread_yield(ppu_thread &ppu) { + ppu.state += cpu_flag::wait; + + sys_ppu_thread.trace("sys_ppu_thread_yield()"); + + const s32 success = lv2_obj::yield(ppu) ? CELL_OK : CELL_CANCEL; + + if (success == CELL_CANCEL) { + // Do other work in the meantime + lv2_obj::notify_all(); + } + + // Return 0 on successful context switch, 1 otherwise + return success; +} + +error_code sys_ppu_thread_join(ppu_thread &ppu, u32 thread_id, + vm::ptr vptr) { + lv2_obj::prepare_for_sleep(ppu); + + sys_ppu_thread.trace("sys_ppu_thread_join(thread_id=0x%x, vptr=*0x%x)", + thread_id, vptr); + + if (thread_id == ppu.id) { + return CELL_EDEADLK; + } + + auto thread = idm::get>( + thread_id, + [&, notify = lv2_obj::notify_all_t()](ppu_thread &thread) -> CellError { + CellError result = + thread.joiner.atomic_op([&](ppu_join_status &value) -> CellError { + switch (value) { + case ppu_join_status::joinable: + value = ppu_join_status{ppu.id}; + return {}; + case ppu_join_status::zombie: + value = ppu_join_status::exited; + return CELL_EAGAIN; + case ppu_join_status::exited: + return CELL_ESRCH; + case ppu_join_status::detached: + default: + return CELL_EINVAL; + } + }); + + if (!result) { + lv2_obj::prepare_for_sleep(ppu); + lv2_obj::sleep(ppu); + } + + notify.cleanup(); + return result; + }); + + if (!thread) { + return CELL_ESRCH; + } + + if (thread.ret && thread.ret != CELL_EAGAIN) { + return thread.ret; + } + + if (thread.ret == CELL_EAGAIN) { + // Notify thread if waiting for a joiner + thread->joiner.notify_one(); + } + + // Wait for cleanup + (*thread.ptr)(); + + if (thread->joiner != ppu_join_status::exited) { + // Thread aborted, log it later + ppu.state += cpu_flag::again; + return {}; + } + + static_cast(ppu.test_stopped()); + + // Get the exit status from the register + const u64 vret = thread->gpr[3]; + + if (thread.ret == CELL_EAGAIN) { + // Cleanup + ensure(idm::remove_verify>(thread_id, + std::move(thread.ptr))); + } + + if (!vptr) { + return not_an_error(CELL_EFAULT); + } + + *vptr = vret; + return CELL_OK; +} + +error_code sys_ppu_thread_detach(ppu_thread &ppu, u32 thread_id) { + ppu.state += cpu_flag::wait; + + sys_ppu_thread.trace("sys_ppu_thread_detach(thread_id=0x%x)", thread_id); + + CellError result = CELL_ESRCH; + + auto [ptr, _] = idm::withdraw>( + thread_id, [&](ppu_thread &thread) { + result = + thread.joiner.atomic_op([](ppu_join_status &value) -> CellError { + switch (value) { + case ppu_join_status::joinable: + value = ppu_join_status::detached; + return {}; + case ppu_join_status::detached: + return CELL_EINVAL; + case ppu_join_status::zombie: + value = ppu_join_status::exited; + return CELL_EAGAIN; + case ppu_join_status::exited: + return CELL_ESRCH; + default: + return CELL_EBUSY; + } + }); + + // Remove ID on EAGAIN + return result != CELL_EAGAIN; + }); + + if (result) { + if (result == CELL_EAGAIN) { + // Join and notify thread (it is detached from IDM now so it must be done + // explicitly now) + *ptr = thread_state::finished; + } + + return result; + } + + return CELL_OK; +} + +error_code sys_ppu_thread_get_join_state(ppu_thread &ppu, + vm::ptr isjoinable) { + sys_ppu_thread.trace("sys_ppu_thread_get_join_state(isjoinable=*0x%x)", + isjoinable); + + if (!isjoinable) { + return CELL_EFAULT; + } + + *isjoinable = ppu.joiner != ppu_join_status::detached; + return CELL_OK; +} + +error_code sys_ppu_thread_set_priority(ppu_thread &ppu, u32 thread_id, + s32 prio) { + ppu.state += cpu_flag::wait; + + sys_ppu_thread.trace("sys_ppu_thread_set_priority(thread_id=0x%x, prio=%d)", + thread_id, prio); + + if (prio < (g_ps3_process_info.debug_or_root() ? -512 : 0) || prio > 3071) { + return CELL_EINVAL; + } + + if (thread_id == ppu.id) { + // Fast path for self + if (ppu.prio.load().prio != prio) { + lv2_obj::set_priority(ppu, prio); + } + + return CELL_OK; + } + + const auto thread = idm::check>( + thread_id, [&, notify = lv2_obj::notify_all_t()](ppu_thread &thread) { + lv2_obj::set_priority(thread, prio); + }); + + if (!thread) { + return CELL_ESRCH; + } + + return CELL_OK; +} + +error_code sys_ppu_thread_get_priority(ppu_thread &ppu, u32 thread_id, + vm::ptr priop) { + ppu.state += cpu_flag::wait; + + sys_ppu_thread.trace( + "sys_ppu_thread_get_priority(thread_id=0x%x, priop=*0x%x)", thread_id, + priop); + u32 prio{}; + + if (thread_id == ppu.id) { + // Fast path for self + for (; !ppu.is_stopped(); std::this_thread::yield()) { + if (reader_lock lock(lv2_obj::g_mutex); cpu_flag::suspend - ppu.state) { + prio = ppu.prio.load().prio; + break; + } + + ppu.check_state(); + ppu.state += cpu_flag::wait; + } + + ppu.check_state(); + *priop = prio; + return CELL_OK; + } + + for (; !ppu.is_stopped(); std::this_thread::yield()) { + bool check_state = false; + const auto thread = idm::check>( + thread_id, [&](ppu_thread &thread) { + if (reader_lock lock(lv2_obj::g_mutex); + cpu_flag::suspend - ppu.state) { + prio = thread.prio.load().prio; + } else { + check_state = true; + } + }); + + if (check_state) { + ppu.check_state(); + ppu.state += cpu_flag::wait; + continue; + } + + if (!thread) { + return CELL_ESRCH; + } + + ppu.check_state(); + *priop = prio; + break; + } + + return CELL_OK; +} + +error_code +sys_ppu_thread_get_stack_information(ppu_thread &ppu, + vm::ptr sp) { + sys_ppu_thread.trace("sys_ppu_thread_get_stack_information(sp=*0x%x)", sp); + + sp->pst_addr = ppu.stack_addr; + sp->pst_size = ppu.stack_size; + + return CELL_OK; +} + +error_code sys_ppu_thread_stop(ppu_thread &ppu, u32 thread_id) { + ppu.state += cpu_flag::wait; + + sys_ppu_thread.todo("sys_ppu_thread_stop(thread_id=0x%x)", thread_id); + + if (!g_ps3_process_info.has_root_perm()) { + return CELL_ENOSYS; + } + + const auto thread = idm::check>( + thread_id, [](named_thread &) {}); + + if (!thread) { + return CELL_ESRCH; + } + + return CELL_OK; +} + +error_code sys_ppu_thread_restart(ppu_thread &ppu) { + ppu.state += cpu_flag::wait; + + sys_ppu_thread.todo("sys_ppu_thread_restart()"); + + if (!g_ps3_process_info.has_root_perm()) { + return CELL_ENOSYS; + } + + return CELL_OK; +} + +error_code _sys_ppu_thread_create(ppu_thread &ppu, vm::ptr thread_id, + vm::ptr param, u64 arg, + u64 unk, s32 prio, u32 _stacksz, u64 flags, + vm::cptr threadname) { + ppu.state += cpu_flag::wait; + + sys_ppu_thread.warning( + "_sys_ppu_thread_create(thread_id=*0x%x, param=*0x%x, arg=0x%llx, " + "unk=0x%llx, prio=%d, stacksize=0x%x, flags=0x%llx, threadname=*0x%x)", + thread_id, param, arg, unk, prio, _stacksz, flags, threadname); + + // thread_id is checked for null in stub -> CELL_ENOMEM + // unk is set to 0 in sys_ppu_thread_create stub + + if (!param || !param->entry) { + return CELL_EFAULT; + } + + if (prio < (g_ps3_process_info.debug_or_root() ? -512 : 0) || prio > 3071) { + return CELL_EINVAL; + } + + if ((flags & 3) == 3) // Check two flags: joinable + interrupt not allowed + { + return CELL_EPERM; + } + + const ppu_func_opd_t entry = param->entry.opd(); + const u32 tls = param->tls; + + // Compute actual stack size and allocate + const u32 stack_size = utils::align(std::max(_stacksz, 4096), 4096); + + auto &dct = g_fxo->get(); + + // Try to obtain "physical memory" from the default container + if (!dct.take(stack_size)) { + return {CELL_ENOMEM, dct.size - dct.used}; + } + + const vm::addr_t stack_base{vm::alloc(stack_size, vm::stack, 4096)}; + + if (!stack_base) { + dct.free(stack_size); + return CELL_ENOMEM; + } + + std::string ppu_name; + + if (threadname) { + constexpr u32 max_size = + c_max_ppu_name_size - 1; // max size excluding null terminator + + if (!vm::read_string(threadname.addr(), max_size, ppu_name, true)) { + dct.free(stack_size); + return CELL_EFAULT; + } + } + + const u32 tid = idm::import >([&]() { + ppu_thread_params p; + p.stack_addr = stack_base; + p.stack_size = stack_size; + p.tls_addr = tls; + p.entry = entry; + p.arg0 = arg; + p.arg1 = unk; + + return stx::make_shared>( + p, ppu_name, prio, 1 - static_cast(flags & 3)); + }); + + if (!tid) { + vm::dealloc(stack_base); + dct.free(stack_size); + return CELL_EAGAIN; + } + + sys_ppu_thread.warning("_sys_ppu_thread_create(): Thread \"%s\" created " + "(id=0x%x, func=*0x%x, rtoc=0x%x, user-tls=0x%x)", + ppu_name, tid, entry.addr, entry.rtoc, tls); + + ppu.check_state(); + *thread_id = tid; + return CELL_OK; +} + +error_code sys_ppu_thread_start(ppu_thread &ppu, u32 thread_id) { + ppu.state += cpu_flag::wait; + + sys_ppu_thread.trace("sys_ppu_thread_start(thread_id=0x%x)", thread_id); + + const auto thread = idm::get>( + thread_id, + [&, notify = lv2_obj::notify_all_t()](ppu_thread &thread) -> CellError { + if (!thread.state.test_and_reset(cpu_flag::stop)) { + // Already started + return CELL_EBUSY; + } + + ensure(lv2_obj::awake(&thread)); + + thread.cmd_list({ + {ppu_cmd::entry_call, 0}, + }); + + return {}; + }); + + if (!thread) { + return CELL_ESRCH; + } + + if (thread.ret) { + return thread.ret; + } else { + thread->cmd_notify.store(1); + thread->cmd_notify.notify_one(); + } + + return CELL_OK; +} + +error_code sys_ppu_thread_rename(ppu_thread &ppu, u32 thread_id, + vm::cptr name) { + ppu.state += cpu_flag::wait; + + sys_ppu_thread.warning("sys_ppu_thread_rename(thread_id=0x%x, name=*0x%x)", + thread_id, name); + + const auto thread = idm::get_unlocked>(thread_id); + + if (!thread) { + return CELL_ESRCH; + } + + if (!name) { + return CELL_EFAULT; + } + + constexpr u32 max_size = + c_max_ppu_name_size - 1; // max size excluding null terminator + + // Make valid name + std::string out_str; + if (!vm::read_string(name.addr(), max_size, out_str, true)) { + return CELL_EFAULT; + } + + auto _name = make_single(std::move(out_str)); + + // thread_ctrl name is not changed (TODO) + sys_ppu_thread.warning("sys_ppu_thread_rename(): Thread renamed to \"%s\"", + *_name); + thread->ppu_tname.store(std::move(_name)); + thread_ctrl::set_name( + *thread, thread->thread_name); // TODO: Currently sets debugger thread + // name only for local thread + + return CELL_OK; +} + +error_code sys_ppu_thread_recover_page_fault(ppu_thread &ppu, u32 thread_id) { + ppu.state += cpu_flag::wait; + + sys_ppu_thread.warning("sys_ppu_thread_recover_page_fault(thread_id=0x%x)", + thread_id); + + const auto thread = idm::get_unlocked>(thread_id); + + if (!thread) { + return CELL_ESRCH; + } + + return mmapper_thread_recover_page_fault(thread.get()); +} + +error_code +sys_ppu_thread_get_page_fault_context(ppu_thread &ppu, u32 thread_id, + vm::ptr ctxt) { + ppu.state += cpu_flag::wait; + + sys_ppu_thread.todo( + "sys_ppu_thread_get_page_fault_context(thread_id=0x%x, ctxt=*0x%x)", + thread_id, ctxt); + + const auto thread = idm::get_unlocked>(thread_id); + + if (!thread) { + return CELL_ESRCH; + } + + // We can only get a context if the thread is being suspended for a page + // fault. + auto &pf_events = g_fxo->get(); + reader_lock lock(pf_events.pf_mutex); + + const auto evt = pf_events.events.find(thread.get()); + if (evt == pf_events.events.end()) { + return CELL_EINVAL; + } + + // TODO: Fill ctxt with proper information. + + return CELL_OK; +} diff --git a/kernel/cellos/src/sys_process.cpp b/kernel/cellos/src/sys_process.cpp new file mode 100644 index 000000000..3fa91bc32 --- /dev/null +++ b/kernel/cellos/src/sys_process.cpp @@ -0,0 +1,590 @@ +#include "stdafx.h" + +#include "Emu/IdManager.h" +#include "Emu/Memory/vm_ptr.h" +#include "Emu/System.h" +#include "Emu/VFS.h" +#include "sys_process.h" + +#include "Crypto/unedat.h" +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/Cell/PPUThread.h" +#include "sys_cond.h" +#include "sys_event.h" +#include "sys_event_flag.h" +#include "sys_fs.h" +#include "sys_interrupt.h" +#include "sys_lwcond.h" +#include "sys_lwmutex.h" +#include "sys_memory.h" +#include "sys_mmapper.h" +#include "sys_mutex.h" +#include "sys_overlay.h" +#include "sys_prx.h" +#include "sys_rwlock.h" +#include "sys_semaphore.h" +#include "sys_spu.h" +#include "sys_timer.h" +#include "sys_vm.h" + +// Check all flags known to be related to extended permissions (TODO) +// It's possible anything which has root flags implicitly has debug perm as well +// But I haven't confirmed it. +bool ps3_process_info_t::debug_or_root() const { + return (ctrl_flags1 & (0xe << 28)) != 0; +} + +bool ps3_process_info_t::has_root_perm() const { + return (ctrl_flags1 & (0xc << 28)) != 0; +} + +bool ps3_process_info_t::has_debug_perm() const { + return (ctrl_flags1 & (0xa << 28)) != 0; +} + +// If a SELF file is of CellOS return its filename, otheriwse return an empty +// string +std::string_view ps3_process_info_t::get_cellos_appname() const { + if (!has_root_perm() || !Emu.GetTitleID().empty()) { + return {}; + } + + return std::string_view(Emu.GetBoot()) + .substr(Emu.GetBoot().find_last_of('/') + 1); +} + +LOG_CHANNEL(sys_process); + +ps3_process_info_t g_ps3_process_info; + +s32 process_getpid() { + // TODO: get current process id + return 1; +} + +s32 sys_process_getpid() { + sys_process.trace("sys_process_getpid() -> 1"); + return process_getpid(); +} + +s32 sys_process_getppid() { + sys_process.todo("sys_process_getppid() -> 0"); + return 0; +} + +template u32 idm_get_count() { + return idm::select([&](u32, Get &) {}); +} + +error_code sys_process_get_number_of_object(u32 object, vm::ptr nump) { + sys_process.error("sys_process_get_number_of_object(object=0x%x, nump=*0x%x)", + object, nump); + + switch (object) { + case SYS_MEM_OBJECT: + *nump = idm_get_count(); + break; + case SYS_MUTEX_OBJECT: + *nump = idm_get_count(); + break; + case SYS_COND_OBJECT: + *nump = idm_get_count(); + break; + case SYS_RWLOCK_OBJECT: + *nump = idm_get_count(); + break; + case SYS_INTR_TAG_OBJECT: + *nump = idm_get_count(); + break; + case SYS_INTR_SERVICE_HANDLE_OBJECT: + *nump = idm_get_count(); + break; + case SYS_EVENT_QUEUE_OBJECT: + *nump = idm_get_count(); + break; + case SYS_EVENT_PORT_OBJECT: + *nump = idm_get_count(); + break; + case SYS_TRACE_OBJECT: + sys_process.error( + "sys_process_get_number_of_object: object = SYS_TRACE_OBJECT"); + *nump = 0; + break; + case SYS_SPUIMAGE_OBJECT: + *nump = idm_get_count(); + break; + case SYS_PRX_OBJECT: + *nump = idm_get_count(); + break; + case SYS_SPUPORT_OBJECT: + sys_process.error( + "sys_process_get_number_of_object: object = SYS_SPUPORT_OBJECT"); + *nump = 0; + break; + case SYS_OVERLAY_OBJECT: + *nump = idm_get_count(); + break; + case SYS_LWMUTEX_OBJECT: + *nump = idm_get_count(); + break; + case SYS_TIMER_OBJECT: + *nump = idm_get_count(); + break; + case SYS_SEMAPHORE_OBJECT: + *nump = idm_get_count(); + break; + case SYS_FS_FD_OBJECT: + *nump = idm_get_count(); + break; + case SYS_LWCOND_OBJECT: + *nump = idm_get_count(); + break; + case SYS_EVENT_FLAG_OBJECT: + *nump = idm_get_count(); + break; + + default: { + return CELL_EINVAL; + } + } + + return CELL_OK; +} + +#include + +template void idm_get_set(std::set &out) { + idm::select([&](u32 id, Get &) { out.emplace(id); }); +} + +static error_code process_get_id(u32 object, vm::ptr buffer, u32 size, + vm::ptr set_size) { + std::set objects; + + switch (object) { + case SYS_MEM_OBJECT: + idm_get_set(objects); + break; + case SYS_MUTEX_OBJECT: + idm_get_set(objects); + break; + case SYS_COND_OBJECT: + idm_get_set(objects); + break; + case SYS_RWLOCK_OBJECT: + idm_get_set(objects); + break; + case SYS_INTR_TAG_OBJECT: + idm_get_set(objects); + break; + case SYS_INTR_SERVICE_HANDLE_OBJECT: + idm_get_set(objects); + break; + case SYS_EVENT_QUEUE_OBJECT: + idm_get_set(objects); + break; + case SYS_EVENT_PORT_OBJECT: + idm_get_set(objects); + break; + case SYS_TRACE_OBJECT: + fmt::throw_exception("SYS_TRACE_OBJECT"); + case SYS_SPUIMAGE_OBJECT: + idm_get_set(objects); + break; + case SYS_PRX_OBJECT: + idm_get_set(objects); + break; + case SYS_OVERLAY_OBJECT: + idm_get_set(objects); + break; + case SYS_LWMUTEX_OBJECT: + idm_get_set(objects); + break; + case SYS_TIMER_OBJECT: + idm_get_set(objects); + break; + case SYS_SEMAPHORE_OBJECT: + idm_get_set(objects); + break; + case SYS_FS_FD_OBJECT: + idm_get_set(objects); + break; + case SYS_LWCOND_OBJECT: + idm_get_set(objects); + break; + case SYS_EVENT_FLAG_OBJECT: + idm_get_set(objects); + break; + case SYS_SPUPORT_OBJECT: + fmt::throw_exception("SYS_SPUPORT_OBJECT"); + default: { + return CELL_EINVAL; + } + } + + u32 i = 0; + + // NOTE: Treats negative and 0 values as 1 due to signed checks and "do-while" + // behavior of fw + for (auto id = objects.begin(); + i < std::max(size, 1) + 0u && id != objects.end(); id++, i++) { + buffer[i] = *id; + } + + *set_size = i; + + return CELL_OK; +} + +error_code sys_process_get_id(u32 object, vm::ptr buffer, u32 size, + vm::ptr set_size) { + sys_process.error( + "sys_process_get_id(object=0x%x, buffer=*0x%x, size=%d, set_size=*0x%x)", + object, buffer, size, set_size); + + if (object == SYS_SPUPORT_OBJECT) { + // Unallowed for this syscall + return CELL_EINVAL; + } + + return process_get_id(object, buffer, size, set_size); +} + +error_code sys_process_get_id2(u32 object, vm::ptr buffer, u32 size, + vm::ptr set_size) { + sys_process.error( + "sys_process_get_id2(object=0x%x, buffer=*0x%x, size=%d, set_size=*0x%x)", + object, buffer, size, set_size); + + if (!g_ps3_process_info.has_root_perm()) { + // This syscall is more capable than sys_process_get_id but also needs a + // root perm check + return CELL_ENOSYS; + } + + return process_get_id(object, buffer, size, set_size); +} + +CellError process_is_spu_lock_line_reservation_address(u32 addr, u64 flags) { + if (!flags || flags & ~(SYS_MEMORY_ACCESS_RIGHT_SPU_THR | + SYS_MEMORY_ACCESS_RIGHT_RAW_SPU)) { + return CELL_EINVAL; + } + + // TODO: respect sys_mmapper region's access rights + switch (addr >> 28) { + case 0x0: // Main memory + case 0x1: // Main memory + case 0x2: // User 64k (sys_memory) + case 0xc: // RSX Local memory + case 0xe: // RawSPU MMIO + break; + + case 0xf: // Private SPU MMIO + { + if (flags & SYS_MEMORY_ACCESS_RIGHT_RAW_SPU) { + // Cannot be accessed by RawSPU + return CELL_EPERM; + } + + break; + } + + case 0xd: // PPU Stack area + return CELL_EPERM; + default: { + if (auto vm0 = idm::get_unlocked(sys_vm_t::find_id(addr))) { + // sys_vm area was not covering the address specified but made a + // reservation on the entire 256mb region + if (vm0->addr + vm0->size - 1 < addr) { + return CELL_EINVAL; + } + + // sys_vm memory is not allowed + return CELL_EPERM; + } + + if (!vm::get(vm::any, addr & -0x1000'0000)) { + return CELL_EINVAL; + } + + break; + } + } + + return {}; +} + +error_code sys_process_is_spu_lock_line_reservation_address(u32 addr, + u64 flags) { + sys_process.warning("sys_process_is_spu_lock_line_reservation_address(addr=" + "0x%x, flags=0x%llx)", + addr, flags); + + if (auto err = process_is_spu_lock_line_reservation_address(addr, flags)) { + return err; + } + + return CELL_OK; +} + +error_code _sys_process_get_paramsfo(vm::ptr buffer) { + sys_process.warning("_sys_process_get_paramsfo(buffer=0x%x)", buffer); + + if (Emu.GetTitleID().empty()) { + return CELL_ENOENT; + } + + memset(buffer.get_ptr(), 0, 0x40); + memcpy(buffer.get_ptr() + 1, Emu.GetTitleID().c_str(), + std::min(Emu.GetTitleID().length(), 9)); + + return CELL_OK; +} + +s32 process_get_sdk_version(u32 /*pid*/, s32 &ver) { + // get correct SDK version for selected pid + ver = g_ps3_process_info.sdk_ver; + + return CELL_OK; +} + +error_code sys_process_get_sdk_version(u32 pid, vm::ptr version) { + sys_process.warning("sys_process_get_sdk_version(pid=0x%x, version=*0x%x)", + pid, version); + + s32 sdk_ver; + const s32 ret = process_get_sdk_version(pid, sdk_ver); + if (ret != CELL_OK) { + return CellError{ret + 0u}; // error code + } else { + *version = sdk_ver; + return CELL_OK; + } +} + +error_code sys_process_kill(u32 pid) { + sys_process.todo("sys_process_kill(pid=0x%x)", pid); + return CELL_OK; +} + +error_code sys_process_wait_for_child(u32 pid, vm::ptr status, u64 unk) { + sys_process.todo( + "sys_process_wait_for_child(pid=0x%x, status=*0x%x, unk=0x%llx", pid, + status, unk); + + return CELL_OK; +} + +error_code sys_process_wait_for_child2(u64 unk1, u64 unk2, u64 unk3, u64 unk4, + u64 unk5, u64 unk6) { + sys_process.todo("sys_process_wait_for_child2(unk1=0x%llx, unk2=0x%llx, " + "unk3=0x%llx, unk4=0x%llx, unk5=0x%llx, unk6=0x%llx)", + unk1, unk2, unk3, unk4, unk5, unk6); + return CELL_OK; +} + +error_code sys_process_get_status(u64 unk) { + sys_process.todo("sys_process_get_status(unk=0x%llx)", unk); + // vm::write32(CPU.gpr[4], GetPPUThreadStatus(CPU)); + return CELL_OK; +} + +error_code sys_process_detach_child(u64 unk) { + sys_process.todo("sys_process_detach_child(unk=0x%llx)", unk); + return CELL_OK; +} + +extern void signal_system_cache_can_stay(); + +void _sys_process_exit(ppu_thread &ppu, s32 status, u32 arg2, u32 arg3) { + ppu.state += cpu_flag::wait; + + sys_process.warning("_sys_process_exit(status=%d, arg2=0x%x, arg3=0x%x)", + status, arg2, arg3); + + Emu.CallFromMainThread([]() { + sys_process.success("Process finished"); + signal_system_cache_can_stay(); + Emu.Kill(); + }); + + // Wait for GUI thread + while (auto state = +ppu.state) { + if (is_stopped(state)) { + break; + } + + ppu.state.wait(state); + } +} + +void _sys_process_exit2(ppu_thread &ppu, s32 status, + vm::ptr arg, u32 arg_size, u32 arg4) { + ppu.state += cpu_flag::wait; + + sys_process.warning( + "_sys_process_exit2(status=%d, arg=*0x%x, arg_size=0x%x, arg4=0x%x)", + status, arg, arg_size, arg4); + + auto pstr = +arg->args; + + std::vector argv; + std::vector envp; + + while (auto ptr = *pstr++) { + argv.emplace_back(ptr.get_ptr()); + sys_process.notice(" *** arg: %s", ptr); + } + + while (auto ptr = *pstr++) { + envp.emplace_back(ptr.get_ptr()); + sys_process.notice(" *** env: %s", ptr); + } + + std::vector data; + + if (arg_size > 0x1030) { + data.resize(0x1000); + std::memcpy(data.data(), vm::base(arg.addr() + arg_size - 0x1000), 0x1000); + } + + if (argv.empty()) { + return _sys_process_exit(ppu, status, 0, 0); + } + + // TODO: set prio, flags + + lv2_exitspawn(ppu, argv, envp, data); +} + +void lv2_exitspawn(ppu_thread &ppu, std::vector &argv, + std::vector &envp, std::vector &data) { + ppu.state += cpu_flag::wait; + + // sys_sm_shutdown + const bool is_real_reboot = (ppu.gpr[11] == 379); + + Emu.CallFromMainThread([is_real_reboot, argv = std::move(argv), + envp = std::move(envp), + data = std::move(data)]() mutable { + sys_process.success("Process finished -> %s", argv[0]); + + std::string disc; + + if (Emu.GetCat() == "DG" || Emu.GetCat() == "GD") + disc = vfs::get("/dev_bdvd/"); + if (disc.empty() && !Emu.GetTitleID().empty()) + disc = vfs::get(Emu.GetDir()); + + std::string path = vfs::get(argv[0]); + std::string hdd1 = vfs::get("/dev_hdd1/"); + + const u128 klic = g_fxo->get().last_key(); + + using namespace id_manager; + + shared_ptr idm_capture = make_shared(); + + if (!is_real_reboot) { + reader_lock rlock{id_manager::g_mutex}; + g_fxo->get>().save(*idm_capture); + stx::serial_breathe_and_tag(*idm_capture, "id_map", + false); + } + + idm_capture->set_reading_state(); + + auto func = [is_real_reboot, + old_size = g_fxo->get().size, + idm_capture](u32 sdk_suggested_mem) mutable { + if (is_real_reboot) { + // Do not save containers on actual reboot + ensure(g_fxo->init>()); + } else { + // Save LV2 memory containers + ensure(g_fxo->init>(*idm_capture)); + } + + // Empty the containers, accumulate their total size + u32 total_size = 0; + idm::select([&](u32, lv2_memory_container &ctr) { + ctr.used = 0; + total_size += ctr.size; + }); + + // The default memory container capacity can only decrease after exitspawn + // 1. If newer SDK version suggests higher memory capacity - it is ignored + // 2. If newer SDK version suggests lower memory capacity - it is lowered + // And if 2. happens while user memory containers exist, the left space + // can be spent on user memory containers + ensure(g_fxo->init( + std::min(old_size - total_size, sdk_suggested_mem) + total_size)); + }; + + Emu.after_kill_callback = [func = std::move(func), argv = std::move(argv), + envp = std::move(envp), data = std::move(data), + disc = std::move(disc), path = std::move(path), + hdd1 = std::move(hdd1), + old_config = Emu.GetUsedConfig(), + klic]() mutable { + Emu.argv = std::move(argv); + Emu.envp = std::move(envp); + Emu.data = std::move(data); + Emu.disc = std::move(disc); + Emu.hdd1 = std::move(hdd1); + Emu.init_mem_containers = std::move(func); + + if (klic) { + Emu.klic.emplace_back(klic); + } + + Emu.SetForceBoot(true); + + auto res = Emu.BootGame(path, "", true, cfg_mode::continuous, old_config); + + if (res != game_boot_result::no_errors) { + sys_process.fatal( + "Failed to boot from exitspawn! (path=\"%s\", error=%s)", path, + res); + } + }; + + signal_system_cache_can_stay(); + + // Make sure we keep the game window opened + Emu.SetContinuousMode(true); + Emu.Kill(false); + }); + + // Wait for GUI thread + while (auto state = +ppu.state) { + if (is_stopped(state)) { + break; + } + + ppu.state.wait(state); + } +} + +void sys_process_exit3(ppu_thread &ppu, s32 status) { + ppu.state += cpu_flag::wait; + + sys_process.warning("_sys_process_exit3(status=%d)", status); + + return _sys_process_exit(ppu, status, 0, 0); +} + +error_code sys_process_spawns_a_self2(vm::ptr pid, u32 primary_prio, + u64 flags, vm::ptr stack, + u32 stack_size, u32 mem_id, + vm::ptr param_sfo, + vm::ptr dbg_data) { + sys_process.todo("sys_process_spawns_a_self2(pid=*0x%x, primary_prio=0x%x, " + "flags=0x%llx, stack=*0x%x, stack_size=0x%x, mem_id=0x%x, " + "param_sfo=*0x%x, dbg_data=*0x%x", + pid, primary_prio, flags, stack, stack_size, mem_id, + param_sfo, dbg_data); + + return CELL_OK; +} diff --git a/kernel/cellos/src/sys_prx.cpp b/kernel/cellos/src/sys_prx.cpp new file mode 100644 index 000000000..7e823ec4f --- /dev/null +++ b/kernel/cellos/src/sys_prx.cpp @@ -0,0 +1,1185 @@ +#include "stdafx.h" + +#include "sys_prx.h" + +#include "Crypto/unself.h" +#include "Emu/IdManager.h" +#include "Emu/System.h" +#include "Emu/VFS.h" +#include "Emu/system_config.h" +#include "Loader/ELF.h" + +#include "Crypto/unedat.h" +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/Cell/PPUThread.h" +#include "sys_fs.h" +#include "sys_memory.h" +#include "sys_process.h" +#include "util/StrUtil.h" +#include + +extern void dump_executable(std::span data, + const ppu_module *_module, + std::string_view title_id); + +extern shared_ptr ppu_load_prx(const ppu_prx_object &, + bool virtual_load, const std::string &, + s64, utils::serial * = nullptr); +extern void ppu_unload_prx(const lv2_prx &prx); +extern bool ppu_initialize(const ppu_module &, bool check_only = false, + u64 file_size = 0); +extern void ppu_finalize(const ppu_module &info, + bool force_mem_release = false); +extern void +ppu_manual_load_imports_exports(u32 imports_start, u32 imports_size, + u32 exports_start, u32 exports_size, + std::basic_string &loaded_flags); + +LOG_CHANNEL(sys_prx); + +// +extern const std::map g_prx_list{ + {"/dev_flash/sys/internal/libfs_utility_init.sprx", 1}, + {"libaacenc.sprx", 0}, + {"libaacenc_spurs.sprx", 0}, + {"libac3dec.sprx", 0}, + {"libac3dec2.sprx", 0}, + {"libadec.sprx", 1}, + {"libadec2.sprx", 0}, + {"libadec_internal.sprx", 0}, + {"libad_async.sprx", 0}, + {"libad_billboard_util.sprx", 0}, + {"libad_core.sprx", 0}, + {"libapostsrc_mini.sprx", 0}, + {"libasfparser2_astd.sprx", 0}, + {"libat3dec.sprx", 0}, + {"libat3multidec.sprx", 0}, + {"libatrac3multi.sprx", 0}, + {"libatrac3plus.sprx", 0}, + {"libatxdec.sprx", 1}, + {"libatxdec2.sprx", 0}, + {"libaudio.sprx", 1}, + {"libavcdec.sprx", 0}, + {"libavcenc.sprx", 0}, + {"libavcenc_small.sprx", 0}, + {"libavchatjpgdec.sprx", 0}, + {"libbeisobmf.sprx", 0}, + {"libbemp2sys.sprx", 0}, + {"libcamera.sprx", 1}, + {"libcelp8dec.sprx", 0}, + {"libcelp8enc.sprx", 0}, + {"libcelpdec.sprx", 0}, + {"libcelpenc.sprx", 0}, + {"libddpdec.sprx", 0}, + {"libdivxdec.sprx", 0}, + {"libdmux.sprx", 0}, + {"libdmuxpamf.sprx", 0}, + {"libdtslbrdec.sprx", 0}, + {"libfiber.sprx", 0}, + {"libfont.sprx", 0}, + {"libfontFT.sprx", 0}, + {"libfreetype.sprx", 0}, + {"libfreetypeTT.sprx", 0}, + {"libfs.sprx", 0}, + {"libfs_155.sprx", 0}, + {"libgcm_sys.sprx", 0}, + {"libgem.sprx", 1}, + {"libgifdec.sprx", 0}, + {"libhttp.sprx", 0}, + {"libio.sprx", 1}, + {"libjpgdec.sprx", 0}, + {"libjpgenc.sprx", 0}, + {"libkey2char.sprx", 0}, + {"libl10n.sprx", 0}, + {"liblv2.sprx", 0}, + {"liblv2coredump.sprx", 0}, + {"liblv2dbg_for_cex.sprx", 0}, + {"libm2bcdec.sprx", 0}, + {"libm4aacdec.sprx", 0}, + {"libm4aacdec2ch.sprx", 0}, + {"libm4hdenc.sprx", 0}, + {"libm4venc.sprx", 0}, + {"libmedi.sprx", 1}, + {"libmic.sprx", 1}, + {"libmp3dec.sprx", 0}, + {"libmp4.sprx", 0}, + {"libmpl1dec.sprx", 0}, + {"libmvcdec.sprx", 0}, + {"libnet.sprx", 0}, + {"libnetctl.sprx", 1}, + {"libpamf.sprx", 1}, + {"libpngdec.sprx", 0}, + {"libpngenc.sprx", 0}, + {"libresc.sprx", 0}, + {"librtc.sprx", 1}, + {"librudp.sprx", 0}, + {"libsail.sprx", 0}, + {"libsail_avi.sprx", 0}, + {"libsail_rec.sprx", 0}, + {"libsjvtd.sprx", 0}, + {"libsmvd2.sprx", 0}, + {"libsmvd4.sprx", 0}, + {"libspurs_jq.sprx", 0}, + {"libsre.sprx", 0}, + {"libssl.sprx", 0}, + {"libsvc1d.sprx", 0}, + {"libsync2.sprx", 0}, + {"libsysmodule.sprx", 0}, + {"libsysutil.sprx", 1}, + {"libsysutil_ap.sprx", 1}, + {"libsysutil_authdialog.sprx", 1}, + {"libsysutil_avc2.sprx", 1}, + {"libsysutil_avconf_ext.sprx", 1}, + {"libsysutil_avc_ext.sprx", 1}, + {"libsysutil_bgdl.sprx", 1}, + {"libsysutil_cross_controller.sprx", 1}, + {"libsysutil_dec_psnvideo.sprx", 1}, + {"libsysutil_dtcp_ip.sprx", 1}, + {"libsysutil_game.sprx", 1}, + {"libsysutil_game_exec.sprx", 1}, + {"libsysutil_imejp.sprx", 1}, + {"libsysutil_misc.sprx", 1}, + {"libsysutil_music.sprx", 1}, + {"libsysutil_music_decode.sprx", 1}, + {"libsysutil_music_export.sprx", 1}, + {"libsysutil_np.sprx", 1}, + {"libsysutil_np2.sprx", 1}, + {"libsysutil_np_clans.sprx", 1}, + {"libsysutil_np_commerce2.sprx", 1}, + {"libsysutil_np_eula.sprx", 1}, + {"libsysutil_np_installer.sprx", 1}, + {"libsysutil_np_sns.sprx", 1}, + {"libsysutil_np_trophy.sprx", 1}, + {"libsysutil_np_tus.sprx", 1}, + {"libsysutil_np_util.sprx", 1}, + {"libsysutil_oskdialog_ext.sprx", 1}, + {"libsysutil_pesm.sprx", 1}, + {"libsysutil_photo_decode.sprx", 1}, + {"libsysutil_photo_export.sprx", 1}, + {"libsysutil_photo_export2.sprx", 1}, + {"libsysutil_photo_import.sprx", 1}, + {"libsysutil_photo_network_sharing.sprx", 1}, + {"libsysutil_print.sprx", 1}, + {"libsysutil_rec.sprx", 1}, + {"libsysutil_remoteplay.sprx", 1}, + {"libsysutil_rtcalarm.sprx", 1}, + {"libsysutil_savedata.sprx", 1}, + {"libsysutil_savedata_psp.sprx", 1}, + {"libsysutil_screenshot.sprx", 1}, + {"libsysutil_search.sprx", 1}, + {"libsysutil_storagedata.sprx", 1}, + {"libsysutil_subdisplay.sprx", 1}, + {"libsysutil_syschat.sprx", 1}, + {"libsysutil_sysconf_ext.sprx", 1}, + {"libsysutil_userinfo.sprx", 1}, + {"libsysutil_video_export.sprx", 1}, + {"libsysutil_video_player.sprx", 1}, + {"libsysutil_video_upload.sprx", 1}, + {"libusbd.sprx", 0}, + {"libusbpspcm.sprx", 0}, + {"libvdec.sprx", 1}, + {"libvoice.sprx", 1}, + {"libvpost.sprx", 0}, + {"libvpost2.sprx", 0}, + {"libwmadec.sprx", 0}, +}; + +bool ppu_register_library_lock(std::string_view libname, bool lock_lib); + +static error_code +prx_load_module(const std::string &vpath, u64 flags, + vm::ptr /*pOpt*/, + fs::file src = {}, s64 file_offset = 0) { + if (flags != 0) { + if (flags & SYS_PRX_LOAD_MODULE_FLAGS_INVALIDMASK) { + return CELL_EINVAL; + } + + if (flags & SYS_PRX_LOAD_MODULE_FLAGS_FIXEDADDR && + !g_ps3_process_info.ppc_seg) { + return CELL_ENOSYS; + } + + fmt::throw_exception("sys_prx: Unimplemented fixed address allocations"); + } + + std::string vpath0; + std::string path = vfs::get(vpath, nullptr, &vpath0); + std::string name = vpath0.substr(vpath0.find_last_of('/') + 1); + + bool ignore = false; + + constexpr std::string_view firmware_sprx_dir = "/dev_flash/sys/external/"; + const bool is_firmware_sprx = + vpath0.starts_with(firmware_sprx_dir) && + g_prx_list.count( + std::string_view(vpath0).substr(firmware_sprx_dir.size())); + + if (is_firmware_sprx) { + if (g_cfg.core.libraries_control.get_set().count(name + ":lle")) { + // Force LLE + ignore = false; + } else if (g_cfg.core.libraries_control.get_set().count(name + ":hle")) { + // Force HLE + ignore = true; + } else { + // Use list + ignore = ::at32(g_prx_list, name) != 0; + } + } else if (vpath0.starts_with("/")) { + // Special case : HLE for files outside of "/dev_flash/sys/external/" + // Have to specify full path for them + ignore = g_prx_list.count(vpath0) && ::at32(g_prx_list, vpath0); + } + + auto hle_load = [&]() { + const auto prx = idm::make_ptr(); + + prx->name = std::move(name); + prx->path = std::move(path); + + sys_prx.warning("Ignored module: \"%s\" (id=0x%x)", vpath, idm::last_id()); + + return not_an_error(idm::last_id()); + }; + + if (ignore) { + return hle_load(); + } + + if (!src) { + auto [fs_error, ppath, path0, lv2_file, type] = lv2_file::open(vpath, 0, 0); + + if (fs_error) { + if (fs_error + 0u == CELL_ENOENT && is_firmware_sprx) { + sys_prx.error( + "firmware SPRX not found: \"%s\" (forcing HLE implementation)", + vpath, idm::last_id()); + return hle_load(); + } + + return {fs_error, vpath}; + } + + src = std::move(lv2_file); + } + + u128 klic = g_fxo->get().last_key(); + + src = decrypt_self(std::move(src), reinterpret_cast(&klic)); + + if (!src) { + return {CELL_PRX_ERROR_UNSUPPORTED_PRX_TYPE, +"Failed to decrypt file"}; + } + + const auto src_data = + g_cfg.core.ppu_debug ? src.to_vector() : std::vector{}; + + ppu_prx_object obj = std::move(src); + src.close(); + + if (obj != elf_error::ok) { + return {CELL_PRX_ERROR_UNSUPPORTED_PRX_TYPE, obj.get_error()}; + } + + const auto prx = ppu_load_prx(obj, false, path, file_offset); + + if (g_cfg.core.ppu_debug) { + dump_executable({src_data.data(), src_data.size()}, prx.get(), + Emu.GetTitleID()); + } + + obj.clear(); + + if (!prx) { + return CELL_PRX_ERROR_ILLEGAL_LIBRARY; + } + + ppu_initialize(*prx); + + sys_prx.success("Loaded module: \"%s\" (id=0x%x)", vpath, idm::last_id()); + + return not_an_error(idm::last_id()); +} + +fs::file make_file_view(fs::file &&file, u64 offset, u64 size); + +std::function lv2_prx::load(utils::serial &ar) { + [[maybe_unused]] const s32 version = + GET_SERIALIZATION_VERSION(lv2_prx_overlay); + + const std::string path = vfs::get(ar.pop()); + const s64 offset = ar; + const u32 state = ar; + + usz seg_count = 0; + ar.deserialize_vle(seg_count); + + shared_ptr prx; + + auto hle_load = [&]() { + prx = make_shared(); + prx->path = path; + prx->name = path.substr(path.find_last_of(fs::delim) + 1); + }; + + if (seg_count) { + std::basic_string loaded_flags, external_flags; + + ar(loaded_flags, external_flags); + + fs::file file{path.substr( + 0, path.size() - (offset ? fmt::format("_x%x", offset).size() : 0))}; + + if (file) { + u128 klic = g_fxo->get().last_key(); + file = make_file_view(std::move(file), offset, umax); + prx = ppu_load_prx(ppu_prx_object{decrypt_self( + std::move(file), reinterpret_cast(&klic))}, + false, path, 0, &ar); + prx->m_loaded_flags = std::move(loaded_flags); + prx->m_external_loaded_flags = std::move(external_flags); + + if (state <= PRX_STATE_STARTED) { + prx->restore_exports(); + } + + ensure(prx); + } else { + ensure(g_cfg.savestate.state_inspection_mode.get()); + + hle_load(); + + // Partially recover information + for (usz i = 0; i < seg_count; i++) { + auto &seg = prx->segs.emplace_back(); + seg.addr = ar; + seg.size = 1; // TODO + } + } + } else { + hle_load(); + } + + prx->state = state; + + return [prx](void *storage) { + *static_cast *>(storage) = prx; + }; +} + +void lv2_prx::save(utils::serial &ar) { + USING_SERIALIZATION_VERSION(lv2_prx_overlay); + + ar(vfs::retrieve(path), offset, state); + + // Save segments count + ar.serialize_vle(segs.size()); + + if (!segs.empty()) { + ar(m_loaded_flags); + ar(m_external_loaded_flags); + } + + for (const ppu_segment &seg : segs) { + if (seg.type == 0x1u && seg.size) + ar(seg.addr); + } +} + +error_code sys_prx_get_ppu_guid(ppu_thread &ppu) { + ppu.state += cpu_flag::wait; + + sys_prx.todo("sys_prx_get_ppu_guid()"); + return CELL_OK; +} + +error_code +_sys_prx_load_module_by_fd(ppu_thread &ppu, s32 fd, u64 offset, u64 flags, + vm::ptr pOpt) { + ppu.state += cpu_flag::wait; + + sys_prx.warning( + "_sys_prx_load_module_by_fd(fd=%d, offset=0x%x, flags=0x%x, pOpt=*0x%x)", + fd, offset, flags, pOpt); + + const auto file = idm::get_unlocked(fd); + + if (!file) { + return CELL_EBADF; + } + + std::lock_guard lock(file->mp->mutex); + + if (!file->file) { + return CELL_EBADF; + } + + return prx_load_module( + offset ? fmt::format("%s_x%x", file->name.data(), offset) + : file->name.data(), + flags, pOpt, lv2_file::make_view(file, offset), offset); +} + +error_code _sys_prx_load_module_on_memcontainer_by_fd( + ppu_thread &ppu, s32 fd, u64 offset, u32 mem_ct, u64 flags, + vm::ptr pOpt) { + ppu.state += cpu_flag::wait; + + sys_prx.warning("_sys_prx_load_module_on_memcontainer_by_fd(fd=%d, " + "offset=0x%x, mem_ct=0x%x, flags=0x%x, pOpt=*0x%x)", + fd, offset, mem_ct, flags, pOpt); + + return _sys_prx_load_module_by_fd(ppu, fd, offset, flags, pOpt); +} + +static error_code +prx_load_module_list(ppu_thread &ppu, s32 count, + vm::cpptr path_list, u32 /*mem_ct*/, + u64 flags, vm::ptr pOpt, + vm::ptr id_list) { + if (flags != 0) { + if (flags & SYS_PRX_LOAD_MODULE_FLAGS_INVALIDMASK) { + return CELL_EINVAL; + } + + if (flags & SYS_PRX_LOAD_MODULE_FLAGS_FIXEDADDR && + !g_ps3_process_info.ppc_seg) { + return CELL_ENOSYS; + } + + fmt::throw_exception("sys_prx: Unimplemented fixed address allocations"); + } + + for (s32 i = 0; i < count; ++i) { + const auto result = prx_load_module(path_list[i].get_ptr(), flags, pOpt); + + if (result < 0) { + while (--i >= 0) { + // Unload already loaded modules + _sys_prx_unload_module(ppu, id_list[i], 0, vm::null); + } + + // Fill with -1 + std::memset(id_list.get_ptr(), -1, count * sizeof(id_list[0])); + return result; + } + + id_list[i] = result; + } + + return CELL_OK; +} + +error_code _sys_prx_load_module_list(ppu_thread &ppu, s32 count, + vm::cpptr path_list, + u64 flags, + vm::ptr pOpt, + vm::ptr id_list) { + ppu.state += cpu_flag::wait; + + sys_prx.warning("_sys_prx_load_module_list(count=%d, path_list=**0x%x, " + "flags=0x%x, pOpt=*0x%x, id_list=*0x%x)", + count, path_list, flags, pOpt, id_list); + + return prx_load_module_list(ppu, count, path_list, + SYS_MEMORY_CONTAINER_ID_INVALID, flags, pOpt, + id_list); +} +error_code _sys_prx_load_module_list_on_memcontainer( + ppu_thread &ppu, s32 count, vm::cpptr path_list, u32 mem_ct, + u64 flags, vm::ptr pOpt, + vm::ptr id_list) { + ppu.state += cpu_flag::wait; + + sys_prx.warning( + "_sys_prx_load_module_list_on_memcontainer(count=%d, path_list=**0x%x, " + "mem_ct=0x%x, flags=0x%x, pOpt=*0x%x, id_list=*0x%x)", + count, path_list, mem_ct, flags, pOpt, id_list); + + return prx_load_module_list(ppu, count, path_list, mem_ct, flags, pOpt, + id_list); +} + +error_code _sys_prx_load_module_on_memcontainer( + ppu_thread &ppu, vm::cptr path, u32 mem_ct, u64 flags, + vm::ptr pOpt) { + ppu.state += cpu_flag::wait; + + sys_prx.warning("_sys_prx_load_module_on_memcontainer(path=%s, mem_ct=0x%x, " + "flags=0x%x, pOpt=*0x%x)", + path, mem_ct, flags, pOpt); + + return prx_load_module(path.get_ptr(), flags, pOpt); +} + +error_code _sys_prx_load_module(ppu_thread &ppu, vm::cptr path, u64 flags, + vm::ptr pOpt) { + ppu.state += cpu_flag::wait; + + sys_prx.warning("_sys_prx_load_module(path=%s, flags=0x%x, pOpt=*0x%x)", path, + flags, pOpt); + + return prx_load_module(path.get_ptr(), flags, pOpt); +} + +error_code +_sys_prx_start_module(ppu_thread &ppu, u32 id, u64 flags, + vm::ptr pOpt) { + ppu.state += cpu_flag::wait; + + sys_prx.warning("_sys_prx_start_module(id=0x%x, flags=0x%x, pOpt=*0x%x)", id, + flags, pOpt); + + if (id == 0 || !pOpt) { + return CELL_EINVAL; + } + + const auto prx = idm::get_unlocked(id); + + if (!prx) { + return CELL_ESRCH; + } + + switch (pOpt->cmd & 0xf) { + case 1: { + std::lock_guard lock(prx->mutex); + + if (!prx->state.compare_and_swap_test(PRX_STATE_INITIALIZED, + PRX_STATE_STARTING)) { + if (prx->state == PRX_STATE_DESTROYED) { + return CELL_ESRCH; + } + + return CELL_PRX_ERROR_ERROR; + } + + prx->load_exports(); + break; + } + case 2: { + switch (const u64 res = pOpt->res) { + case SYS_PRX_RESIDENT: { + // No error code on invalid state, so throw on unexpected state + ensure(prx->state.compare_and_swap_test(PRX_STATE_STARTING, + PRX_STATE_STARTED)); + return CELL_OK; + } + default: { + if (res & 0xffff'ffffu) { + // Unload the module (SYS_PRX_NO_RESIDENT expected) + sys_prx.warning("_sys_prx_start_module(): Start entry function " + "returned SYS_PRX_NO_RESIDENT (res=0x%llx)", + res); + + // Thread-safe if called from liblv2.sprx, due to internal lwmutex lock + // before it + prx->state = PRX_STATE_STOPPED; + prx->unload_exports(); + _sys_prx_unload_module(ppu, id, 0, vm::null); + + // Return the exact value returned by the start function (as an error) + return static_cast(res); + } + + // Return type of start entry function is s32 + // And according to RE this path results in weird behavior + sys_prx.error("_sys_prx_start_module(): Start entry function returned " + "weird value (res=0x%llx)", + res); + return CELL_OK; + } + } + } + default: + return CELL_PRX_ERROR_ERROR; + } + + ppu.check_state(); + pOpt->entry.set(prx->start ? prx->start.addr() : ~0ull); + + // This check is probably for older fw + if (pOpt->size != 0x20u) { + pOpt->entry2.set(prx->prologue ? prx->prologue.addr() : ~0ull); + } + + return CELL_OK; +} + +error_code +_sys_prx_stop_module(ppu_thread &ppu, u32 id, u64 flags, + vm::ptr pOpt) { + ppu.state += cpu_flag::wait; + + sys_prx.warning("_sys_prx_stop_module(id=0x%x, flags=0x%x, pOpt=*0x%x)", id, + flags, pOpt); + + const auto prx = idm::get_unlocked(id); + + if (!prx) { + return CELL_ESRCH; + } + + if (!pOpt) { + return CELL_EINVAL; + } + + auto set_entry2 = [&](u64 addr) { + if (pOpt->size != 0x20u) { + pOpt->entry2.set(addr); + } + }; + + switch (pOpt->cmd & 0xf) { + case 1: { + switch (const auto old = prx->state.compare_and_swap(PRX_STATE_STARTED, + PRX_STATE_STOPPING)) { + case PRX_STATE_INITIALIZED: + return CELL_PRX_ERROR_NOT_STARTED; + case PRX_STATE_STOPPED: + return CELL_PRX_ERROR_ALREADY_STOPPED; + case PRX_STATE_STOPPING: + return CELL_PRX_ERROR_ALREADY_STOPPING; // Internal error + case PRX_STATE_STARTING: + return CELL_PRX_ERROR_ERROR; // Internal error + case PRX_STATE_DESTROYED: + return CELL_ESRCH; + case PRX_STATE_STARTED: + break; + default: + fmt::throw_exception("Invalid prx state (%d)", old); + } + + ppu.check_state(); + pOpt->entry.set(prx->stop ? prx->stop.addr() : ~0ull); + set_entry2(prx->epilogue ? prx->epilogue.addr() : ~0ull); + return CELL_OK; + } + case 2: { + switch (pOpt->res) { + case 0: { + // No error code on invalid state, so throw on unexpected state + std::lock_guard lock(prx->mutex); + ensure(prx->exports_end <= prx->exports_start || + (prx->state == PRX_STATE_STOPPING)); + + prx->unload_exports(); + + ensure(prx->state.compare_and_swap_test(PRX_STATE_STOPPING, + PRX_STATE_STOPPED)); + return CELL_OK; + } + case 1: + return CELL_PRX_ERROR_CAN_NOT_STOP; // Internal error + default: + // Nothing happens (probably unexpected value) + return CELL_OK; + } + } + + // These commands are not used by liblv2.sprx + case 4: // Get start entry and stop functions + case 8: // Disable stop function execution + { + switch (const auto old = +prx->state) { + case PRX_STATE_INITIALIZED: + return CELL_PRX_ERROR_NOT_STARTED; + case PRX_STATE_STOPPED: + return CELL_PRX_ERROR_ALREADY_STOPPED; + case PRX_STATE_STOPPING: + return CELL_PRX_ERROR_ALREADY_STOPPING; // Internal error + case PRX_STATE_STARTING: + return CELL_PRX_ERROR_ERROR; // Internal error + case PRX_STATE_DESTROYED: + return CELL_ESRCH; + case PRX_STATE_STARTED: + break; + default: + fmt::throw_exception("Invalid prx state (%d)", old); + } + + if (pOpt->cmd == 4u) { + ppu.check_state(); + pOpt->entry.set(prx->stop ? prx->stop.addr() : ~0ull); + set_entry2(prx->epilogue ? prx->epilogue.addr() : ~0ull); + } else { + // Disables stop function execution (but the real value can be read + // through _sys_prx_get_module_info) + sys_prx.todo("_sys_prx_stop_module(): cmd is 8 (stop function = *0x%x)", + prx->stop); + // prx->stop = vm::null; + } + + return CELL_OK; + } + default: + return CELL_PRX_ERROR_ERROR; + } +} + +error_code +_sys_prx_unload_module(ppu_thread &ppu, u32 id, u64 flags, + vm::ptr pOpt) { + ppu.state += cpu_flag::wait; + + // Get the PRX, free the used memory and delete the object and its ID + const auto prx = + idm::withdraw(id, [](lv2_prx &prx) -> CellPrxError { + switch (prx.state + .fetch_op([](u32 &value) { + if (value == PRX_STATE_INITIALIZED || + value == PRX_STATE_STOPPED) { + value = PRX_STATE_DESTROYED; + return true; + } + + return false; + }) + .first) { + case PRX_STATE_INITIALIZED: + case PRX_STATE_STOPPED: + return {}; + default: + break; + } + + return CELL_PRX_ERROR_NOT_REMOVABLE; + }); + + if (!prx) { + return {CELL_PRX_ERROR_UNKNOWN_MODULE, id}; + } + + if (prx.ret) { + return {prx.ret, "%s (id=%s)", prx->name, id}; + } + + sys_prx.success( + "_sys_prx_unload_module(id=0x%x, flags=0x%x, pOpt=*0x%x): name='%s'", id, + flags, pOpt, prx->name); + + prx->mutex.lock_unlock(); + + ppu_unload_prx(*prx); + + ppu_finalize(*prx); + + // s32 result = prx->exit ? prx->exit() : CELL_OK; + + return CELL_OK; +} + +void lv2_prx::load_exports() { + if (exports_end <= exports_start) { + // Nothing to load + return; + } + + if (!m_loaded_flags.empty()) { + // Already loaded + return; + } + + ppu_manual_load_imports_exports(0, 0, exports_start, + exports_end - exports_start, m_loaded_flags); +} + +void lv2_prx::restore_exports() { + constexpr usz sizeof_export_data = 0x1C; + + std::basic_string loaded_flags_empty; + + for (u32 start = exports_start, i = 0; start < exports_end; + i++, start += vm::read8(start) ? vm::read8(start) : sizeof_export_data) { + if (::at32(m_external_loaded_flags, i) || + (!m_loaded_flags.empty() && ::at32(m_loaded_flags, i))) { + loaded_flags_empty.clear(); + ppu_manual_load_imports_exports(0, 0, start, sizeof_export_data, + loaded_flags_empty); + } + } +} + +void lv2_prx::unload_exports() { + if (m_loaded_flags.empty()) { + // Not loaded + return; + } + + std::basic_string merged = m_loaded_flags; + + for (usz i = 0; i < merged.size(); i++) { + merged[i] |= ::at32(m_external_loaded_flags, i); + } + + ppu_manual_load_imports_exports(0, 0, exports_start, + exports_end - exports_start, merged); +} + +error_code _sys_prx_register_module(ppu_thread &ppu, vm::cptr name, + vm::ptr opt) { + ppu.state += cpu_flag::wait; + + sys_prx.todo("_sys_prx_register_module(name=%s, opt=*0x%x)", name, opt); + + if (!opt) { + return CELL_EINVAL; + } + + sys_prx_register_module_0x30_type_1_t info{}; + + switch (const u64 size_struct = vm::read64(opt.addr())) { + case 0x1c: + case 0x20: { + const auto _info = vm::static_ptr_cast(opt); + + sys_prx.todo("_sys_prx_register_module(): opt size is 0x%x", size_struct); + + // Rebuild info with corresponding members of old structures + // Weird that type is set to 0 because 0 means NO-OP in this syscall + info.size = 0x30; + info.lib_stub_size = _info->stubs_size; + info.lib_stub_ea = _info->stubs_ea; + info.error_handler = _info->error_handler; + info.type = 0; + break; + } + case 0x30: { + std::memcpy(&info, opt.get_ptr(), sizeof(info)); + break; + } + default: + return CELL_EINVAL; + } + + sys_prx.warning("opt: size=0x%x, type=0x%x, unk3=0x%x, unk4=0x%x, " + "lib_entries_ea=%s, lib_entries_size=0x%x" + ", lib_stub_ea=%s, lib_stub_size=0x%x, error_handler=%s", + info.size, info.type, info.unk3, info.unk4, + info.lib_entries_ea, info.lib_entries_size, info.lib_stub_ea, + info.lib_stub_size, info.error_handler); + + if (info.type & 0x1) { + if (Emu.IsVsh()) { + ppu_manual_load_imports_exports( + info.lib_stub_ea.addr(), info.lib_stub_size, + info.lib_entries_ea.addr(), info.lib_entries_size, + *std::make_unique>()); + } else { + // Only VSH is allowed to load it manually + return not_an_error(CELL_PRX_ERROR_ELF_IS_REGISTERED); + } + } + + return CELL_OK; +} + +error_code _sys_prx_query_module(ppu_thread &ppu) { + ppu.state += cpu_flag::wait; + + sys_prx.todo("_sys_prx_query_module()"); + return CELL_OK; +} + +error_code _sys_prx_register_library(ppu_thread &ppu, vm::ptr library) { + ppu.state += cpu_flag::wait; + + sys_prx.notice("_sys_prx_register_library(library=*0x%x)", library); + + if (!vm::check_addr(library.addr())) { + return CELL_EFAULT; + } + + constexpr u32 sizeof_lib = 0x1c; + + std::array mem_copy{}; + std::memcpy(mem_copy.data(), library.get_ptr(), sizeof_lib); + + std::basic_string flags; + ppu_manual_load_imports_exports(0, 0, library.addr(), sizeof_lib, flags); + + if (flags.front()) { + const bool success = + idm::select([&](u32 /*id*/, lv2_prx &prx) { + if (prx.state == PRX_STATE_INITIALIZED) { + for (u32 lib_addr = prx.exports_start, index = 0; + lib_addr < prx.exports_end; + index++, lib_addr += vm::read8(lib_addr) ? vm::read8(lib_addr) + : sizeof_lib) { + if (std::memcpy(vm::base(lib_addr), mem_copy.data(), + sizeof_lib) == 0) { + atomic_storage::release( + prx.m_external_loaded_flags[index], true); + return true; + } + } + } + + return false; + }).ret; + + if (!success) { + sys_prx.error( + "_sys_prx_register_library(): Failed to associate library to PRX!"); + } + } + + return CELL_OK; +} + +error_code _sys_prx_unregister_library(ppu_thread &ppu, vm::ptr library) { + ppu.state += cpu_flag::wait; + + sys_prx.todo("_sys_prx_unregister_library(library=*0x%x)", library); + return CELL_OK; +} + +error_code _sys_prx_link_library(ppu_thread &ppu) { + ppu.state += cpu_flag::wait; + + sys_prx.todo("_sys_prx_link_library()"); + return CELL_OK; +} + +error_code _sys_prx_unlink_library(ppu_thread &ppu) { + ppu.state += cpu_flag::wait; + + sys_prx.todo("_sys_prx_unlink_library()"); + return CELL_OK; +} + +error_code _sys_prx_query_library(ppu_thread &ppu) { + ppu.state += cpu_flag::wait; + + sys_prx.todo("_sys_prx_query_library()"); + return CELL_OK; +} + +error_code +_sys_prx_get_module_list(ppu_thread &ppu, u64 flags, + vm::ptr pInfo) { + ppu.state += cpu_flag::wait; + + if (flags & 0x1) { + sys_prx.todo("_sys_prx_get_module_list(flags=%d, pInfo=*0x%x)", flags, + pInfo); + } else { + sys_prx.warning("_sys_prx_get_module_list(flags=%d, pInfo=*0x%x)", flags, + pInfo); + } + + // TODO: Some action occurs if LSB of flags is set here + + if (!(flags & 0x2)) { + // Do nothing + return CELL_OK; + } + + if (pInfo->size == pInfo.size()) { + const u32 max_count = pInfo->max; + const auto idlist = +pInfo->idlist; + u32 count = 0; + + if (max_count) { + const std::string liblv2_path = + vfs::get("/dev_flash/sys/external/liblv2.sprx"); + + idm::select([&](u32 id, lv2_prx &prx) { + if (count >= max_count) { + return true; + } + + if (prx.path == liblv2_path) { + // Hide liblv2.sprx for now + return false; + } + + idlist[count++] = id; + return false; + }); + } + + pInfo->count = count; + } else { + // TODO: A different structure should be served here with sizeof == 0x18 + sys_prx.todo( + "_sys_prx_get_module_list(): Unknown structure specified (size=0x%llx)", + pInfo->size); + } + + return CELL_OK; +} + +error_code +_sys_prx_get_module_info(ppu_thread &ppu, u32 id, u64 flags, + vm::ptr pOpt) { + ppu.state += cpu_flag::wait; + + sys_prx.warning("_sys_prx_get_module_info(id=0x%x, flags=%d, pOpt=*0x%x)", id, + flags, pOpt); + + const auto prx = idm::get_unlocked(id); + + if (!pOpt) { + return CELL_EFAULT; + } + + if (pOpt->size != pOpt.size()) { + return CELL_EINVAL; + } + + if (!pOpt->info) { + return CELL_EFAULT; + } + + if (pOpt->info->size != pOpt->info.size() && + pOpt->info_v2->size != pOpt->info_v2.size()) { + return CELL_EINVAL; + } + + if (!prx) { + return CELL_PRX_ERROR_UNKNOWN_MODULE; + } + + strcpy_trunc(pOpt->info->name, prx->module_info_name); + pOpt->info->version[0] = prx->module_info_version[0]; + pOpt->info->version[1] = prx->module_info_version[1]; + pOpt->info->modattribute = prx->module_info_attributes; + pOpt->info->start_entry = prx->start.addr(); + pOpt->info->stop_entry = prx->stop.addr(); + pOpt->info->all_segments_num = ::size32(prx->segs); + if (pOpt->info->filename) { + std::span dst(pOpt->info->filename.get_ptr(), pOpt->info->filename_size); + strcpy_trunc(dst, vfs::retrieve(prx->path)); + } + + if (pOpt->info->segments) { + u32 i = 0; + for (; i < prx->segs.size() && i < pOpt->info->segments_num; i++) { + if (!prx->segs[i].addr) + continue; // TODO: Check this + pOpt->info->segments[i].index = i; + pOpt->info->segments[i].base = prx->segs[i].addr; + pOpt->info->segments[i].filesz = prx->segs[i].filesz; + pOpt->info->segments[i].memsz = prx->segs[i].size; + pOpt->info->segments[i].type = prx->segs[i].type; + } + pOpt->info->segments_num = i; + } + + if (pOpt->info_v2->size == pOpt->info_v2.size()) { + pOpt->info_v2->exports_addr = prx->exports_start; + pOpt->info_v2->exports_size = prx->exports_end - prx->exports_start; + pOpt->info_v2->imports_addr = prx->imports_start; + pOpt->info_v2->imports_size = prx->imports_end - prx->imports_start; + } + + return CELL_OK; +} + +error_code _sys_prx_get_module_id_by_name( + ppu_thread &ppu, vm::cptr name, u64 flags, + vm::ptr pOpt) { + ppu.state += cpu_flag::wait; + + sys_prx.warning( + "_sys_prx_get_module_id_by_name(name=%s, flags=%d, pOpt=*0x%x)", name, + flags, pOpt); + + std::string module_name; + if (!vm::read_string(name.addr(), 28, module_name)) { + return CELL_EINVAL; + } + + const auto [prx, id] = + idm::select([&](u32 id, lv2_prx &prx) -> u32 { + if (strncmp(module_name.c_str(), prx.module_info_name, + sizeof(prx.module_info_name)) == 0) { + return id; + } + + return 0; + }); + + if (!id) { + return CELL_PRX_ERROR_UNKNOWN_MODULE; + } + + return not_an_error(id); +} + +error_code _sys_prx_get_module_id_by_address(ppu_thread &ppu, u32 addr) { + ppu.state += cpu_flag::wait; + + sys_prx.warning("_sys_prx_get_module_id_by_address(addr=0x%x)", addr); + + if (!vm::check_addr(addr)) { + // Fast check for an invalid argument + return {CELL_PRX_ERROR_UNKNOWN_MODULE, addr}; + } + + const auto [prx, id] = + idm::select([&](u32 id, lv2_prx &prx) -> u32 { + for (const ppu_segment &seg : prx.segs) { + if (seg.size && addr >= seg.addr && addr < seg.addr + seg.size) { + return id; + } + } + + return 0; + }); + + if (!id) { + return {CELL_PRX_ERROR_UNKNOWN_MODULE, addr}; + } + + return not_an_error(id); +} + +error_code _sys_prx_start(ppu_thread &ppu) { + ppu.state += cpu_flag::wait; + + sys_prx.todo("sys_prx_start()"); + return CELL_OK; +} + +error_code _sys_prx_stop(ppu_thread &ppu) { + ppu.state += cpu_flag::wait; + + sys_prx.todo("sys_prx_stop()"); + return CELL_OK; +} + +template <> +void fmt_class_string::format(std::string &out, u64 arg) { + format_enum(out, arg, [](CellPrxError value) { + switch (value) { + STR_CASE(CELL_PRX_ERROR_ERROR); + STR_CASE(CELL_PRX_ERROR_ILLEGAL_PERM); + STR_CASE(CELL_PRX_ERROR_UNKNOWN_MODULE); + STR_CASE(CELL_PRX_ERROR_ALREADY_STARTED); + STR_CASE(CELL_PRX_ERROR_NOT_STARTED); + STR_CASE(CELL_PRX_ERROR_ALREADY_STOPPED); + STR_CASE(CELL_PRX_ERROR_CAN_NOT_STOP); + STR_CASE(CELL_PRX_ERROR_NOT_REMOVABLE); + STR_CASE(CELL_PRX_ERROR_LIBRARY_NOT_YET_LINKED); + STR_CASE(CELL_PRX_ERROR_LIBRARY_FOUND); + STR_CASE(CELL_PRX_ERROR_LIBRARY_NOTFOUND); + STR_CASE(CELL_PRX_ERROR_ILLEGAL_LIBRARY); + STR_CASE(CELL_PRX_ERROR_LIBRARY_INUSE); + STR_CASE(CELL_PRX_ERROR_ALREADY_STOPPING); + STR_CASE(CELL_PRX_ERROR_UNSUPPORTED_PRX_TYPE); + STR_CASE(CELL_PRX_ERROR_INVAL); + STR_CASE(CELL_PRX_ERROR_ILLEGAL_PROCESS); + STR_CASE(CELL_PRX_ERROR_NO_LIBLV2); + STR_CASE(CELL_PRX_ERROR_UNSUPPORTED_ELF_TYPE); + STR_CASE(CELL_PRX_ERROR_UNSUPPORTED_ELF_CLASS); + STR_CASE(CELL_PRX_ERROR_UNDEFINED_SYMBOL); + STR_CASE(CELL_PRX_ERROR_UNSUPPORTED_RELOCATION_TYPE); + STR_CASE(CELL_PRX_ERROR_ELF_IS_REGISTERED); + STR_CASE(CELL_PRX_ERROR_NO_EXIT_ENTRY); + } + + return unknown; + }); +} diff --git a/kernel/cellos/src/sys_rsx.cpp b/kernel/cellos/src/sys_rsx.cpp new file mode 100644 index 000000000..ade5a3a26 --- /dev/null +++ b/kernel/cellos/src/sys_rsx.cpp @@ -0,0 +1,989 @@ +#include "stdafx.h" + +#include "sys_rsx.h" + +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/Cell/PPUModule.h" +#include "Emu/Cell/timers.hpp" +#include "Emu/Memory/vm_locking.h" +#include "Emu/RSX/Core/RSXEngLock.hpp" +#include "Emu/RSX/Core/RSXReservationLock.hpp" +#include "Emu/RSX/RSXThread.h" +#include "Emu/System.h" +#include "sys_event.h" +#include "sys_vm.h" +#include "util/asm.hpp" + +LOG_CHANNEL(sys_rsx); + +// Unknown error code returned by sys_rsx_context_attribute +enum sys_rsx_error : s32 { SYS_RSX_CONTEXT_ATTRIBUTE_ERROR = -17 }; + +template <> +void fmt_class_string::format(std::string &out, u64 arg) { + format_enum(out, arg, [](auto error) { + switch (error) { STR_CASE(SYS_RSX_CONTEXT_ATTRIBUTE_ERROR); } + + return unknown; + }); +} + +static u64 rsx_timeStamp() { return get_timebased_time(); } + +static void set_rsx_dmactl(rsx::thread *render, u64 get_put) { + { + rsx::eng_lock rlock(render); + render->fifo_ctrl->abort(); + + // Unconditional set + while (!render->new_get_put.compare_and_swap_test(u64{umax}, get_put)) { + // Wait for the first store to complete (or be aborted) + if (auto cpu = cpu_thread::get_current()) { + if (cpu->state & cpu_flag::exit) { + // Retry + cpu->state += cpu_flag::again; + return; + } + } + + utils::pause(); + } + + // Schedule FIFO interrupt to deal with this immediately + render->m_eng_interrupt_mask |= rsx::dma_control_interrupt; + } + + if (auto cpu = cpu_thread::get_current()) { + // Wait for the first store to complete (or be aborted) + while (render->new_get_put != usz{umax}) { + if (cpu->state & cpu_flag::exit) { + if (render->new_get_put.compare_and_swap_test(get_put, umax)) { + // Retry + cpu->state += cpu_flag::again; + return; + } + } + + thread_ctrl::wait_for(1000); + } + } +} + +bool rsx::thread::send_event(u64 data1, u64 event_flags, u64 data3) { + // Filter event bits, send them only if they are masked by gcm + // Except the upper 32-bits, they are reserved for unmapped io events and + // execute unconditionally + event_flags &= vm::_ref(driver_info).handlers | 0xffff'ffffull + << 32; + + if (!event_flags) { + // Nothing to do + return true; + } + + auto error = sys_event_port_send(rsx_event_port, data1, event_flags, data3); + + while (error + 0u == CELL_EBUSY) { + auto cpu = get_current_cpu_thread(); + + if (cpu && cpu->get_class() == thread_class::ppu) { + // Deschedule + lv2_obj::sleep(*cpu, 100); + } + + // Wait a bit before resending event + thread_ctrl::wait_for(100); + + if (cpu && cpu->get_class() == thread_class::rsx) + cpu->cpu_wait({}); + + if (Emu.IsStopped() || (cpu && cpu->check_state())) { + error = 0; + break; + } + + error = sys_event_port_send(rsx_event_port, data1, event_flags, data3); + } + + if (error + 0u == CELL_EAGAIN) { + // Thread has aborted when sending event (VBLANK duplicates are allowed) + ensure((unsent_gcm_events.fetch_or(event_flags) & event_flags & + ~(SYS_RSX_EVENT_VBLANK | SYS_RSX_EVENT_SECOND_VBLANK_BASE | + SYS_RSX_EVENT_SECOND_VBLANK_BASE * 2)) == 0); + return false; + } + + if (error && error + 0u != CELL_ENOTCONN) { + fmt::throw_exception( + "rsx::thread::send_event() Failed to send event! (error=%x)", +error); + } + + return true; +} + +error_code sys_rsx_device_open(cpu_thread &cpu) { + cpu.state += cpu_flag::wait; + + sys_rsx.todo("sys_rsx_device_open()"); + + return CELL_OK; +} + +error_code sys_rsx_device_close(cpu_thread &cpu) { + cpu.state += cpu_flag::wait; + + sys_rsx.todo("sys_rsx_device_close()"); + + return CELL_OK; +} + +/** + * lv2 SysCall 668 (0x29C): sys_rsx_memory_allocate + * @param mem_handle (OUT): Context / ID, which is used by sys_rsx_memory_free + * to free allocated memory. + * @param mem_addr (OUT): Returns the local memory base address, usually + * 0xC0000000. + * @param size (IN): Local memory size. E.g. 0x0F900000 (249 MB). (changes with + * sdk version) + * @param flags (IN): E.g. Immediate value passed in cellGcmSys is 8. + * @param a5 (IN): E.g. Immediate value passed in cellGcmSys is 0x00300000 (3 + * MB?). + * @param a6 (IN): E.g. Immediate value passed in cellGcmSys is 16. + * @param a7 (IN): E.g. Immediate value passed in cellGcmSys is 8. + */ +error_code sys_rsx_memory_allocate(cpu_thread &cpu, vm::ptr mem_handle, + vm::ptr mem_addr, u32 size, u64 flags, + u64 a5, u64 a6, u64 a7) { + cpu.state += cpu_flag::wait; + + sys_rsx.warning("sys_rsx_memory_allocate(mem_handle=*0x%x, mem_addr=*0x%x, " + "size=0x%x, flags=0x%llx, a5=0x%llx, a6=0x%llx, a7=0x%llx)", + mem_handle, mem_addr, size, flags, a5, a6, a7); + + if (vm::falloc(rsx::constants::local_mem_base, size, vm::video)) { + rsx::get_current_renderer()->local_mem_size = size; + + if (u32 addr = rsx::get_current_renderer()->driver_info) { + vm::_ref(addr).memory_size = size; + } + + *mem_addr = rsx::constants::local_mem_base; + *mem_handle = 0x5a5a5a5b; + return CELL_OK; + } + + return CELL_ENOMEM; +} + +/** + * lv2 SysCall 669 (0x29D): sys_rsx_memory_free + * @param mem_handle (OUT): Context / ID, for allocated local memory generated + * by sys_rsx_memory_allocate + */ +error_code sys_rsx_memory_free(cpu_thread &cpu, u32 mem_handle) { + cpu.state += cpu_flag::wait; + + sys_rsx.warning("sys_rsx_memory_free(mem_handle=0x%x)", mem_handle); + + if (!vm::check_addr(rsx::constants::local_mem_base)) { + return CELL_ENOMEM; + } + + if (rsx::get_current_renderer()->dma_address) { + fmt::throw_exception("Attempting to dealloc rsx memory when the context is " + "still being used"); + } + + if (!vm::dealloc(rsx::constants::local_mem_base)) { + return CELL_ENOMEM; + } + + return CELL_OK; +} + +/** + * lv2 SysCall 670 (0x29E): sys_rsx_context_allocate + * @param context_id (OUT): RSX context, E.g. 0x55555555 (in vsh.self) + * @param lpar_dma_control (OUT): Control register area. E.g. 0x60100000 (in + * vsh.self) + * @param lpar_driver_info (OUT): RSX data like frequencies, sizes, version... + * E.g. 0x60200000 (in vsh.self) + * @param lpar_reports (OUT): Report data area. E.g. 0x60300000 (in vsh.self) + * @param mem_ctx (IN): mem_ctx given by sys_rsx_memory_allocate + * @param system_mode (IN): + */ +error_code sys_rsx_context_allocate(cpu_thread &cpu, vm::ptr context_id, + vm::ptr lpar_dma_control, + vm::ptr lpar_driver_info, + vm::ptr lpar_reports, u64 mem_ctx, + u64 system_mode) { + cpu.state += cpu_flag::wait; + + sys_rsx.warning("sys_rsx_context_allocate(context_id=*0x%x, " + "lpar_dma_control=*0x%x, lpar_driver_info=*0x%x, " + "lpar_reports=*0x%x, mem_ctx=0x%llx, system_mode=0x%llx)", + context_id, lpar_dma_control, lpar_driver_info, lpar_reports, + mem_ctx, system_mode); + + if (!vm::check_addr(rsx::constants::local_mem_base)) { + return CELL_EINVAL; + } + + const auto render = rsx::get_current_renderer(); + + std::lock_guard lock(render->sys_rsx_mtx); + + if (render->dma_address) { + // We currently do not support multiple contexts + fmt::throw_exception("sys_rsx_context_allocate was called twice"); + } + + const auto area = vm::reserve_map(vm::rsx_context, 0, 0x10000000, 0x403); + const u32 dma_address = area ? area->alloc(0x300000) : 0; + + if (!dma_address) { + return CELL_ENOMEM; + } + + sys_rsx.warning("sys_rsx_context_allocate(): Mapped address 0x%x", + dma_address); + + *lpar_dma_control = dma_address; + *lpar_driver_info = dma_address + 0x100000; + *lpar_reports = dma_address + 0x200000; + + auto &reports = vm::_ref(vm::cast(*lpar_reports)); + std::memset(&reports, 0, sizeof(RsxReports)); + + for (usz i = 0; i < std::size(reports.notify); ++i) + reports.notify[i].timestamp = -1; + + for (usz i = 0; i < std::size(reports.semaphore); i += 4) { + reports.semaphore[i + 0].val.raw() = 0x1337C0D3; + reports.semaphore[i + 1].val.raw() = 0x1337BABE; + reports.semaphore[i + 2].val.raw() = 0x1337BEEF; + reports.semaphore[i + 3].val.raw() = 0x1337F001; + } + + for (usz i = 0; i < std::size(reports.report); ++i) { + reports.report[i].val = 0; + reports.report[i].timestamp = -1; + reports.report[i].pad = -1; + } + + auto &driverInfo = vm::_ref(vm::cast(*lpar_driver_info)); + + std::memset(&driverInfo, 0, sizeof(RsxDriverInfo)); + + driverInfo.version_driver = 0x211; + driverInfo.version_gpu = 0x5c; + driverInfo.memory_size = render->local_mem_size; + driverInfo.nvcore_frequency = 500000000; // 0x1DCD6500 + driverInfo.memory_frequency = 650000000; // 0x26BE3680 + driverInfo.reportsNotifyOffset = 0x1000; + driverInfo.reportsOffset = 0; + driverInfo.reportsReportOffset = 0x1400; + driverInfo.systemModeFlags = static_cast(system_mode); + driverInfo.hardware_channel = 1; // * i think* this 1 for games, 0 for vsh + + render->driver_info = vm::cast(*lpar_driver_info); + + auto &dmaControl = vm::_ref(vm::cast(*lpar_dma_control)); + dmaControl.get = 0; + dmaControl.put = 0; + dmaControl.ref = 0; // Set later to -1 by cellGcmSys + + if ((false /*system_mode & something*/ || g_cfg.video.decr_memory_layout) && + g_cfg.core.debug_console_mode) + rsx::get_current_renderer()->main_mem_size = 0x20000000; // 512MB + else + rsx::get_current_renderer()->main_mem_size = 0x10000000; // 256MB + + vm::var> attr; + attr->protocol = SYS_SYNC_PRIORITY; + attr->type = SYS_PPU_QUEUE; + attr->name_u64 = 0; + + sys_event_port_create(cpu, vm::get_addr(&driverInfo.handler_queue), + SYS_EVENT_PORT_LOCAL, 0); + render->rsx_event_port = driverInfo.handler_queue; + sys_event_queue_create(cpu, vm::get_addr(&driverInfo.handler_queue), attr, 0, + 0x20); + sys_event_port_connect_local(cpu, render->rsx_event_port, + driverInfo.handler_queue); + + render->display_buffers_count = 0; + render->current_display_buffer = 0; + render->label_addr = vm::cast(*lpar_reports); + render->init(dma_address); + + *context_id = 0x55555555; + + return CELL_OK; +} + +/** + * lv2 SysCall 671 (0x29F): sys_rsx_context_free + * @param context_id (IN): RSX context generated by sys_rsx_context_allocate to + * free the context. + */ +error_code sys_rsx_context_free(ppu_thread &ppu, u32 context_id) { + ppu.state += cpu_flag::wait; + + sys_rsx.todo("sys_rsx_context_free(context_id=0x%x)", context_id); + + const auto render = rsx::get_current_renderer(); + + rsx::eng_lock fifo_lock(render); + std::scoped_lock lock(render->sys_rsx_mtx); + + const u32 dma_address = render->dma_address; + render->dma_address = 0; + + if (context_id != 0x55555555 || !dma_address || + render->state & cpu_flag::ret) { + return CELL_EINVAL; + } + + g_fxo->get() = thread_state::finished; + + const u32 queue_id = + vm::_ptr(render->driver_info)->handler_queue; + + render->state += cpu_flag::ret; + + while (render->state & cpu_flag::ret) { + thread_ctrl::wait_for(1000); + } + + sys_event_port_disconnect(ppu, render->rsx_event_port); + sys_event_port_destroy(ppu, render->rsx_event_port); + sys_event_queue_destroy(ppu, queue_id, SYS_EVENT_QUEUE_DESTROY_FORCE); + + render->label_addr = 0; + render->driver_info = 0; + render->main_mem_size = 0; + render->rsx_event_port = 0; + render->display_buffers_count = 0; + render->current_display_buffer = 0; + render->ctrl = nullptr; + render->rsx_thread_running = false; + render->serialized = false; + + ensure(vm::get(vm::rsx_context)->dealloc(dma_address)); + + return CELL_OK; +} + +/** + * lv2 SysCall 672 (0x2A0): sys_rsx_context_iomap + * @param context_id (IN): RSX context, E.g. 0x55555555 (in vsh.self) + * @param io (IN): IO offset mapping area. E.g. 0x00600000 + * @param ea (IN): Start address of mapping area. E.g. 0x20400000 + * @param size (IN): Size of mapping area in bytes. E.g. 0x00200000 + * @param flags (IN): + */ +error_code sys_rsx_context_iomap(cpu_thread &cpu, u32 context_id, u32 io, + u32 ea, u32 size, u64 flags) { + cpu.state += cpu_flag::wait; + + sys_rsx.warning("sys_rsx_context_iomap(context_id=0x%x, io=0x%x, ea=0x%x, " + "size=0x%x, flags=0x%llx)", + context_id, io, ea, size, flags); + + const auto render = rsx::get_current_renderer(); + + if (!size || io & 0xFFFFF || + ea + u64{size} > rsx::constants::local_mem_base || ea & 0xFFFFF || + size & 0xFFFFF || context_id != 0x55555555 || + render->main_mem_size < io + u64{size}) { + return CELL_EINVAL; + } + + if (!render->is_fifo_idle()) { + sys_rsx.warning( + "sys_rsx_context_iomap(): RSX is not idle while mapping io"); + } + + // Wait until we have no active RSX locks and reserve iomap for use. Must do + // so before acquiring vm lock to avoid deadlocks + rsx::reservation_lock rsx_lock(ea, size); + + vm::writer_lock rlock; + + for (u32 addr = ea, end = ea + size; addr < end; addr += 0x100000) { + if (!vm::check_addr(addr, vm::page_readable | + (addr < 0x20000000 ? 0 : vm::page_1m_size))) { + return CELL_EINVAL; + } + + if ((addr == ea || !(addr % 0x1000'0000)) && + idm::check_unlocked(sys_vm_t::find_id(addr))) { + // Virtual memory is disallowed + return CELL_EINVAL; + } + } + + io >>= 20, ea >>= 20, size >>= 20; + + rsx::eng_lock fifo_lock(render); + std::scoped_lock lock(render->sys_rsx_mtx); + + for (u32 i = 0; i < size; i++) { + auto &table = render->iomap_table; + + // TODO: Investigate relaxed memory ordering + const u32 prev_ea = table.ea[io + i]; + table.ea[io + i].release((ea + i) << 20); + if (prev_ea + 1) + table.io[prev_ea >> 20].release(-1); // Clear previous mapping if exists + table.io[ea + i].release((io + i) << 20); + } + + return CELL_OK; +} + +/** + * lv2 SysCall 673 (0x2A1): sys_rsx_context_iounmap + * @param context_id (IN): RSX context, E.g. 0x55555555 (in vsh.self) + * @param io (IN): IO address. E.g. 0x00600000 (Start page 6) + * @param size (IN): Size to unmap in byte. E.g. 0x00200000 + */ +error_code sys_rsx_context_iounmap(cpu_thread &cpu, u32 context_id, u32 io, + u32 size) { + cpu.state += cpu_flag::wait; + + sys_rsx.warning( + "sys_rsx_context_iounmap(context_id=0x%x, io=0x%x, size=0x%x)", + context_id, io, size); + + const auto render = rsx::get_current_renderer(); + + if (!size || size & 0xFFFFF || io & 0xFFFFF || context_id != 0x55555555 || + render->main_mem_size < io + u64{size}) { + return CELL_EINVAL; + } + + if (!render->is_fifo_idle()) { + sys_rsx.warning( + "sys_rsx_context_iounmap(): RSX is not idle while unmapping io"); + } + + vm::writer_lock rlock; + + std::scoped_lock lock(render->sys_rsx_mtx); + + for (const u32 end = (io >>= 20) + (size >>= 20); io < end;) { + auto &table = render->iomap_table; + + const u32 ea_entry = table.ea[io]; + table.ea[io++].release(-1); + if (ea_entry + 1) + table.io[ea_entry >> 20].release(-1); + } + + return CELL_OK; +} + +/** + * lv2 SysCall 674 (0x2A2): sys_rsx_context_attribute + * @param context_id (IN): RSX context, e.g. 0x55555555 + * @param package_id (IN): + * @param a3 (IN): + * @param a4 (IN): + * @param a5 (IN): + * @param a6 (IN): + */ +error_code sys_rsx_context_attribute(u32 context_id, u32 package_id, u64 a3, + u64 a4, u64 a5, u64 a6) { + if (auto cpu = get_current_cpu_thread()) { + cpu->state += cpu_flag::wait; + } + + // Flip/queue/reset flip/flip event/user command/vblank as trace to help with + // log spam + const bool trace_log = + (package_id == 0x102 || package_id == 0x103 || package_id == 0x10a || + package_id == 0xFEC || package_id == 0xFED || package_id == 0xFEF); + (trace_log ? sys_rsx.trace : sys_rsx.warning)( + "sys_rsx_context_attribute(context_id=0x%x, package_id=0x%x, a3=0x%llx, " + "a4=0x%llx, a5=0x%llx, a6=0x%llx)", + context_id, package_id, a3, a4, a5, a6); + + // todo: these event ports probly 'shouldnt' be here as i think its supposed + // to be interrupts that are sent from rsx somewhere in lv1 + + const auto render = rsx::get_current_renderer(); + + if (!render->dma_address) { + return {CELL_EINVAL, "dma_address is 0"}; + } + + if (context_id != 0x55555555) { + return {CELL_EINVAL, "context_id is 0x%x", context_id}; + } + + auto &driverInfo = vm::_ref(render->driver_info); + switch (package_id) { + case 0x001: // FIFO + { + const u64 get = static_cast(a3); + const u64 put = static_cast(a4); + const u64 get_put = put << 32 | get; + + std::lock_guard lock(render->sys_rsx_mtx); + set_rsx_dmactl(render, get_put); + break; + } + case 0x100: // Display mode set + break; + case 0x101: // Display sync set, cellGcmSetFlipMode + // a4 == 2 is vsync, a4 == 1 is hsync + render->requested_vsync.store(a4 == 2); + break; + + case 0x102: // Display flip + { + u32 flip_idx = ~0u; + + // high bit signifys grabbing a queued buffer + // otherwise it contains a display buffer offset + if ((a4 & 0x80000000) != 0) { + // NOTE: There currently seem to only be 2 active heads on PS3 + ensure(a3 < 2); + + // last half byte gives buffer, 0xf seems to trigger just last queued + u8 idx_check = a4 & 0xf; + if (idx_check > 7) + flip_idx = driverInfo.head[a3].lastQueuedBufferId; + else + flip_idx = idx_check; + + // fyi -- u32 hardware_channel = (a4 >> 8) & 0xFF; + + // sanity check, the head should have a 'queued' buffer on it, and it + // should have been previously 'queued' + const u32 sanity_check = 0x40000000 & (1 << flip_idx); + if ((driverInfo.head[a3].flipFlags & sanity_check) != sanity_check) + rsx_log.error( + "Display Flip Queued: Flipping non previously queued buffer 0x%llx", + a4); + } else { + for (u32 i = 0; i < render->display_buffers_count; ++i) { + if (render->display_buffers[i].offset == a4) { + flip_idx = i; + break; + } + } + if (flip_idx == ~0u) { + rsx_log.error("Display Flip: Couldn't find display buffer offset, " + "flipping 0. Offset: 0x%x", + a4); + flip_idx = 0; + } + } + + if (!render->request_emu_flip(flip_idx)) { + if (auto cpu = get_current_cpu_thread()) { + cpu->state += cpu_flag::exit; + cpu->state += cpu_flag::again; + } + + return {}; + } + break; + } + case 0x103: // Display Queue + { + // NOTE: There currently seem to only be 2 active heads on PS3 + ensure(a3 < 2); + + driverInfo.head[a3].lastQueuedBufferId = static_cast(a4); + driverInfo.head[a3].flipFlags |= 0x40000000 | (1 << a4); + + render->on_frame_end(static_cast(a4)); + if (!render->send_event(0, SYS_RSX_EVENT_QUEUE_BASE << a3, 0)) { + break; + } + + if (g_cfg.video.frame_limit == frame_limit_type::infinite) { + render->post_vblank_event(get_system_time()); + } + break; + } + case 0x104: // Display buffer + { + const u8 id = a3 & 0xFF; + if (id > 7) { + return SYS_RSX_CONTEXT_ATTRIBUTE_ERROR; + } + + std::lock_guard lock(render->sys_rsx_mtx); + + // Note: no error checking is being done + + const u32 width = (a4 >> 32) & 0xFFFFFFFF; + const u32 height = a4 & 0xFFFFFFFF; + const u32 pitch = (a5 >> 32) & 0xFFFFFFFF; + const u32 offset = a5 & 0xFFFFFFFF; + + render->display_buffers[id].width = width; + render->display_buffers[id].height = height; + render->display_buffers[id].pitch = pitch; + render->display_buffers[id].offset = offset; + + render->display_buffers_count = + std::max(id + 1, render->display_buffers_count); + break; + } + case 0x105: // destroy buffer? + break; + + case 0x106: // ? (Used by cellGcmInitPerfMon) + break; + + case 0x108: // cellGcmSetVBlankFrequency, cellGcmSetSecondVFrequency + // a4 == 3, CELL_GCM_DISPLAY_FREQUENCY_59_94HZ + // a4 == 2, CELL_GCM_DISPLAY_FREQUENCY_SCANOUT + // a4 == 4, CELL_GCM_DISPLAY_FREQUENCY_DISABLE + + if (a5 == 1u) { + // This function resets vsync state to enabled + render->requested_vsync = true; + + // TODO: Set vblank frequency + } else if (ensure(a5 == 2u)) { + // TODO: Implement its frequency as well + render->enable_second_vhandler.store(a4 != 4); + } + + break; + + case 0x10a: // ? Involved in managing flip status through + // cellGcmResetFlipStatus + { + if (a3 > 7) { + return SYS_RSX_CONTEXT_ATTRIBUTE_ERROR; + } + + // NOTE: There currently seem to only be 2 active heads on PS3 + ensure(a3 < 2); + + driverInfo.head[a3].flipFlags.atomic_op([&](be_t &flipStatus) { + flipStatus = (flipStatus & static_cast(a4)) | static_cast(a5); + }); + break; + } + case 0x10D: // Called by cellGcmInitCursor + break; + + case 0x300: // Tiles + { + // a4 high bits = ret.tile = (location + 1) | (bank << 4) | ((offset / + // 0x10000) << 16) | (location << 31); a4 low bits = ret.limit = ((offset + + // size - 1) / 0x10000) << 16 | (location << 31); a5 high bits = ret.pitch = + // (pitch / 0x100) << 8; a5 low bits = ret.format = base | ((base + ((size - + // 1) / 0x10000)) << 13) | (comp << 26) | (1 << 30); + + ensure(a3 < std::size(render->tiles)); + + if (!render->is_fifo_idle()) { + sys_rsx.warning( + "sys_rsx_context_attribute(): RSX is not idle while setting tile"); + } + + auto &tile = render->tiles[a3]; + + const u32 location = ((a4 >> 32) & 0x3) - 1; + const u32 offset = ((((a4 >> 32) & 0x7FFFFFFF) >> 16) * 0x10000); + const u32 size = ((((a4 & 0x7FFFFFFF) >> 16) + 1) * 0x10000) - offset; + const u32 pitch = (((a5 >> 32) & 0xFFFFFFFF) >> 8) * 0x100; + const u32 comp = ((a5 & 0xFFFFFFFF) >> 26) & 0xF; + const u32 base = (a5 & 0xFFFFFFFF) & 0x7FF; + // const u32 bank = (((a4 >> 32) & 0xFFFFFFFF) >> 4) & 0xF; + const bool bound = ((a4 >> 32) & 0x3) != 0; + + const auto range = utils::address_range::start_length(offset, size); + + if (bound) { + if (!size || !pitch) { + return {CELL_EINVAL, "size or pitch are 0 (size=%d, pitch=%d)", size, + pitch}; + } + + u32 limit = -1; + + switch (location) { + case CELL_GCM_LOCATION_MAIN: + limit = render->main_mem_size; + break; + case CELL_GCM_LOCATION_LOCAL: + limit = render->local_mem_size; + break; + default: + fmt::throw_exception("sys_rsx_context_attribute(): Unexpected location " + "value (location=0x%x)", + location); + } + + if (!range.valid() || range.end >= limit) { + return {CELL_EINVAL, "range invalid (valid=%d, end=%d, limit=%d)", + range.valid(), range.end, limit}; + } + + // Hardcoded value in gcm + ensure(a5 & (1 << 30)); + } + + std::lock_guard lock(render->sys_rsx_mtx); + + // When tile is going to be unbound, we can use it as a hint that the + // address will no longer be used as a surface and can be + // removed/invalidated Todo: There may be more checks such as + // format/size/width can could be done + if (tile.bound && !bound) + render->notify_tile_unbound(static_cast(a3)); + + if (location == CELL_GCM_LOCATION_MAIN && bound) { + vm::writer_lock rlock; + + for (u32 io = (offset >> 20), end = (range.end >> 20); io <= end; io++) { + if (render->iomap_table.ea[io] == umax) { + return {CELL_EINVAL, "iomap_table ea is umax"}; + } + } + } + + tile.location = location; + tile.offset = offset; + tile.size = size; + tile.pitch = pitch; + tile.comp = comp; + tile.base = base; + tile.bank = base; + tile.bound = bound; + break; + } + case 0x301: // Depth-buffer (Z-cull) + { + // a4 high = region = (1 << 0) | (zFormat << 4) | (aaFormat << 8); + // a4 low = size = ((width >> 6) << 22) | ((height >> 6) << 6); + // a5 high = start = cullStart&(~0xFFF); + // a5 low = offset = offset; + // a6 high = status0 = (zcullDir << 1) | (zcullFormat << 2) | ((sFunc & 0xF) + // << 12) | (sRef << 16) | (sMask << 24); a6 low = status1 = (0x2000 << 0) | + // (0x20 << 16); + + if (a3 >= std::size(render->zculls)) { + return SYS_RSX_CONTEXT_ATTRIBUTE_ERROR; + } + + if (!render->is_fifo_idle()) { + sys_rsx.warning( + "sys_rsx_context_attribute(): RSX is not idle while setting zcull"); + } + + const u32 width = ((a4 & 0xFFFFFFFF) >> 22) << 6; + const u32 height = ((a4 & 0x0000FFFF) >> 6) << 6; + const u32 cullStart = (a5 >> 32) & ~0xFFF; + const u32 offset = (a5 & 0x0FFFFFFF); + const bool bound = (a6 & 0xFFFFFFFF) != 0; + + if (bound) { + const auto cull_range = + utils::address_range::start_length(cullStart, width * height); + + // cullStart is an offset inside ZCULL RAM which is 3MB long, check bounds + // width and height are not allowed to be zero (checked by range.valid()) + if (!cull_range.valid() || cull_range.end >= 3u << 20 || + offset >= render->local_mem_size) { + return {CELL_EINVAL, + "cull_range invalid (valid=%d, end=%d, offset=%d, " + "local_mem_size=%d)", + cull_range.valid(), + cull_range.end, + offset, + render->local_mem_size}; + } + + if (a5 & 0xF0000000) { + sys_rsx.warning("sys_rsx_context_attribute(): ZCULL offset greater " + "than 256MB (offset=0x%x)", + offset); + } + + // Hardcoded values in gcm + ensure(a4 & (1ull << 32)); + ensure((a6 & 0xFFFFFFFF) == 0u + ((0x2000 << 0) | (0x20 << 16))); + } + + std::lock_guard lock(render->sys_rsx_mtx); + + auto &zcull = render->zculls[a3]; + + zcull.zFormat = ((a4 >> 32) >> 4) & 0xF; + zcull.aaFormat = ((a4 >> 32) >> 8) & 0xF; + zcull.width = width; + zcull.height = height; + zcull.cullStart = cullStart; + zcull.offset = offset; + zcull.zcullDir = ((a6 >> 32) >> 1) & 0x1; + zcull.zcullFormat = ((a6 >> 32) >> 2) & 0x3FF; + zcull.sFunc = ((a6 >> 32) >> 12) & 0xF; + zcull.sRef = ((a6 >> 32) >> 16) & 0xFF; + zcull.sMask = ((a6 >> 32) >> 24) & 0xFF; + zcull.bound = bound; + break; + } + + case 0x302: // something with zcull + break; + + case 0x600: // Framebuffer setup + break; + + case 0x601: // Framebuffer blit + break; + + case 0x602: // Framebuffer blit sync + break; + + case 0x603: // Framebuffer close + break; + + case 0xFEC: // hack: flip event notification + { + // we only ever use head 1 for now + driverInfo.head[1].flipFlags |= 0x80000000; + driverInfo.head[1].lastFlipTime = + rsx_timeStamp(); // should rsxthread set this? + driverInfo.head[1].flipBufferId = static_cast(a3); + + // seems gcmSysWaitLabel uses this offset, so lets set it to 0 every flip + // NOTE: Realhw resets 16 bytes of this semaphore for some reason + vm::_ref>(render->label_addr + 0x10).store(u128{}); + + render->send_event(0, SYS_RSX_EVENT_FLIP_BASE << 1, 0); + break; + } + case 0xFED: // hack: vblank command + { + if (cpu_thread::get_current()) { + // VBLANK/RSX thread only + return {CELL_EINVAL, "wrong thread"}; + } + + // NOTE: There currently seem to only be 2 active heads on PS3 + ensure(a3 < 2); + + // todo: this is wrong and should be 'second' vblank handler and freq, but + // since currently everything is reported as being 59.94, this should be + // fine + driverInfo.head[a3].lastSecondVTime.atomic_op([&](be_t &time) { + a4 = std::max(a4, time + 1); + time = a4; + }); + + // Time point is supplied in argument 4 (todo: convert it to MFTB rate and + // use it) + const u64 current_time = rsx_timeStamp(); + + // Note: not atomic + driverInfo.head[a3].lastVTimeLow = static_cast(current_time); + driverInfo.head[a3].lastVTimeHigh = static_cast(current_time >> 32); + + driverInfo.head[a3].vBlankCount++; + + u64 event_flags = SYS_RSX_EVENT_VBLANK; + + if (render->enable_second_vhandler) + event_flags |= SYS_RSX_EVENT_SECOND_VBLANK_BASE << a3; // second vhandler + + render->send_event(0, event_flags, 0); + break; + } + + case 0xFEF: // hack: user command + { + // 'custom' invalid package id for now + // as i think we need custom lv1 interrupts to handle this accurately + // this also should probly be set by rsxthread + driverInfo.userCmdParam = static_cast(a4); + render->send_event(0, SYS_RSX_EVENT_USER_CMD, 0); + break; + } + + default: + return {CELL_EINVAL, "unsupported package id %d", package_id}; + } + + return CELL_OK; +} + +/** + * lv2 SysCall 675 (0x2A3): sys_rsx_device_map + * @param a1 (OUT): rsx device map address : 0x40000000, 0x50000000.. 0xB0000000 + * @param a2 (OUT): Unused + * @param dev_id (IN): An immediate value and always 8. (cellGcmInitPerfMon uses + * 11, 10, 9, 7, 12 successively). + */ +error_code sys_rsx_device_map(cpu_thread &cpu, vm::ptr dev_addr, + vm::ptr a2, u32 dev_id) { + cpu.state += cpu_flag::wait; + + sys_rsx.warning("sys_rsx_device_map(dev_addr=*0x%x, a2=*0x%x, dev_id=0x%x)", + dev_addr, a2, dev_id); + + if (dev_id != 8) { + // TODO: lv1 related + fmt::throw_exception("sys_rsx_device_map: Invalid dev_id %d", dev_id); + } + + const auto render = rsx::get_current_renderer(); + + std::scoped_lock lock(render->sys_rsx_mtx); + + if (!render->device_addr) { + const auto area = vm::reserve_map(vm::rsx_context, 0, 0x10000000, 0x403); + const u32 addr = area ? area->alloc(0x100000) : 0; + + if (!addr) { + return CELL_ENOMEM; + } + + sys_rsx.warning("sys_rsx_device_map(): Mapped address 0x%x", addr); + + *dev_addr = addr; + render->device_addr = addr; + return CELL_OK; + } + + *dev_addr = render->device_addr; + return CELL_OK; +} + +/** + * lv2 SysCall 676 (0x2A4): sys_rsx_device_unmap + * @param dev_id (IN): An immediate value and always 8. + */ +error_code sys_rsx_device_unmap(cpu_thread &cpu, u32 dev_id) { + cpu.state += cpu_flag::wait; + + sys_rsx.todo("sys_rsx_device_unmap(dev_id=0x%x)", dev_id); + + return CELL_OK; +} + +/** + * lv2 SysCall 677 (0x2A5): sys_rsx_attribute + */ +error_code sys_rsx_attribute(cpu_thread &cpu, u32 packageId, u32 a2, u32 a3, + u32 a4, u32 a5) { + cpu.state += cpu_flag::wait; + + sys_rsx.warning( + "sys_rsx_attribute(packageId=0x%x, a2=0x%x, a3=0x%x, a4=0x%x, a5=0x%x)", + packageId, a2, a3, a4, a5); + + return CELL_OK; +} diff --git a/kernel/cellos/src/sys_rsxaudio.cpp b/kernel/cellos/src/sys_rsxaudio.cpp new file mode 100644 index 000000000..2b86c6959 --- /dev/null +++ b/kernel/cellos/src/sys_rsxaudio.cpp @@ -0,0 +1,2214 @@ +#include "stdafx.h" + +#include "Emu/Audio/audio_utils.h" +#include "Emu/IdManager.h" +#include "Emu/Memory/vm.h" +#include "Emu/System.h" +#include "Emu/system_config.h" +#include "util/video_provider.h" + +#include "sys_rsxaudio.h" + +#include +#include +#include + +#ifdef __linux__ +#include +#include +#include +#include +#elif defined(BSD) || defined(__APPLE__) +#include +#endif + +LOG_CHANNEL(sys_rsxaudio); + +extern atomic_t g_recording_mode; + +namespace rsxaudio_ringbuf_reader { +static constexpr void clean_buf(rsxaudio_shmem::ringbuf_t &ring_buf) { + ring_buf.unk2 = 100; + ring_buf.read_idx = 0; + ring_buf.write_idx = 0; + ring_buf.queue_notify_idx = 0; + ring_buf.next_blk_idx = 0; + + for (auto &ring_entry : ring_buf.entries) { + ring_entry.valid = 0; + ring_entry.audio_blk_idx = 0; + ring_entry.timestamp = 0; + } +} + +static void set_timestamp(rsxaudio_shmem::ringbuf_t &ring_buf, u64 timestamp) { + const s32 entry_idx_raw = (ring_buf.read_idx + ring_buf.rw_max_idx - + (ring_buf.rw_max_idx > 2) - 1) % + ring_buf.rw_max_idx; + const s32 entry_idx = + std::clamp(entry_idx_raw, 0, SYS_RSXAUDIO_RINGBUF_SZ); + + ring_buf.entries[entry_idx].timestamp = convert_to_timebased_time(timestamp); +} + +static std::tuple +update_status(rsxaudio_shmem::ringbuf_t &ring_buf) { + const s32 read_idx = + std::clamp(ring_buf.read_idx, 0, SYS_RSXAUDIO_RINGBUF_SZ); + + if ((ring_buf.entries[read_idx].valid & 1) == 0U) { + return {}; + } + + const s32 entry_idx_raw = + (ring_buf.read_idx + ring_buf.rw_max_idx - (ring_buf.rw_max_idx > 2)) % + ring_buf.rw_max_idx; + const s32 entry_idx = + std::clamp(entry_idx_raw, 0, SYS_RSXAUDIO_RINGBUF_SZ); + + ring_buf.entries[read_idx].valid = 0; + ring_buf.queue_notify_idx = + (ring_buf.queue_notify_idx + 1) % ring_buf.queue_notify_step; + ring_buf.read_idx = (ring_buf.read_idx + 1) % ring_buf.rw_max_idx; + + return std::make_tuple( + ((ring_buf.rw_max_idx > 2) ^ ring_buf.queue_notify_idx) == 0, + ring_buf.entries[entry_idx].audio_blk_idx, + ring_buf.entries[entry_idx].timestamp); +} + +static std::pair +get_addr(const rsxaudio_shmem::ringbuf_t &ring_buf) { + const s32 read_idx = + std::clamp(ring_buf.read_idx, 0, SYS_RSXAUDIO_RINGBUF_SZ); + + if (ring_buf.entries[read_idx].valid & 1) { + return std::make_pair(true, ring_buf.entries[read_idx].dma_addr); + } + + return std::make_pair(false, ring_buf.dma_silence_addr); +} + +[[maybe_unused]] +static std::optional get_spdif_channel_data(RsxaudioPort dst, + rsxaudio_shmem &shmem) { + if (dst == RsxaudioPort::SPDIF_0) { + if (shmem.ctrl.spdif_ch0_channel_data_tx_cycles) { + shmem.ctrl.spdif_ch0_channel_data_tx_cycles--; + return static_cast(shmem.ctrl.spdif_ch0_channel_data_hi) << 32 | + shmem.ctrl.spdif_ch0_channel_data_lo; + } + } else { + if (shmem.ctrl.spdif_ch1_channel_data_tx_cycles) { + shmem.ctrl.spdif_ch1_channel_data_tx_cycles--; + return static_cast(shmem.ctrl.spdif_ch1_channel_data_hi) << 32 | + shmem.ctrl.spdif_ch1_channel_data_lo; + } + } + + return std::nullopt; +} +} // namespace rsxaudio_ringbuf_reader + +lv2_rsxaudio::lv2_rsxaudio(utils::serial &ar) noexcept : lv2_obj{1}, init(ar) { + if (init) { + ar(shmem); + + for (auto &port : event_queue) { + port = lv2_event_queue::load_ptr(ar, port, "rsxaudio"); + } + } +} + +void lv2_rsxaudio::save(utils::serial &ar) { + USING_SERIALIZATION_VERSION(LLE); + + ar(init); + + if (init) { + ar(shmem); + + for (const auto &port : event_queue) { + lv2_event_queue::save_ptr(ar, port.get()); + } + } +} + +error_code sys_rsxaudio_initialize(vm::ptr handle) { + sys_rsxaudio.trace("sys_rsxaudio_initialize(handle=*0x%x)", handle); + + auto &rsxaudio_thread = g_fxo->get(); + + if (rsxaudio_thread.rsxaudio_ctx_allocated.test_and_set()) { + return CELL_EINVAL; + } + + if (!vm::check_addr(handle.addr(), vm::page_writable, sizeof(u32))) { + rsxaudio_thread.rsxaudio_ctx_allocated = false; + return CELL_EFAULT; + } + + const u32 id = idm::make(); + + if (!id) { + rsxaudio_thread.rsxaudio_ctx_allocated = false; + return CELL_ENOMEM; + } + + const auto rsxaudio_obj = idm::get_unlocked(id); + std::lock_guard lock(rsxaudio_obj->mutex); + + rsxaudio_obj->shmem = vm::addr_t{vm::alloc(sizeof(rsxaudio_shmem), vm::main)}; + + if (!rsxaudio_obj->shmem) { + idm::remove(id); + rsxaudio_thread.rsxaudio_ctx_allocated = false; + return CELL_ENOMEM; + } + + rsxaudio_obj->page_lock(); + + rsxaudio_shmem *sh_page = rsxaudio_obj->get_rw_shared_page(); + sh_page->ctrl = {}; + + for (auto &uf : sh_page->ctrl.channel_uf) { + uf.uf_event_cnt = 0; + uf.unk1 = 0; + } + + sh_page->ctrl.unk4 = 0x8000; + sh_page->ctrl.intr_thread_prio = 0xDEADBEEF; + sh_page->ctrl.unk5 = 0; + + rsxaudio_obj->init = true; + *handle = id; + + return CELL_OK; +} + +error_code sys_rsxaudio_finalize(u32 handle) { + sys_rsxaudio.trace("sys_rsxaudio_finalize(handle=0x%x)", handle); + + const auto rsxaudio_obj = idm::get_unlocked(handle); + + if (!rsxaudio_obj) { + return CELL_ESRCH; + } + + std::lock_guard lock(rsxaudio_obj->mutex); + + if (!rsxaudio_obj->init) { + return CELL_ESRCH; + } + + auto &rsxaudio_thread = g_fxo->get(); + + { + std::lock_guard ra_obj_lock{rsxaudio_thread.rsxaudio_obj_upd_m}; + rsxaudio_thread.rsxaudio_obj_ptr = null_ptr; + } + + rsxaudio_obj->init = false; + vm::dealloc(rsxaudio_obj->shmem, vm::main); + + idm::remove(handle); + rsxaudio_thread.rsxaudio_ctx_allocated = false; + + return CELL_OK; +} + +error_code sys_rsxaudio_import_shared_memory(u32 handle, vm::ptr addr) { + sys_rsxaudio.trace( + "sys_rsxaudio_import_shared_memory(handle=0x%x, addr=*0x%x)", handle, + addr); + + const auto rsxaudio_obj = idm::get_unlocked(handle); + + if (!rsxaudio_obj) { + return CELL_ESRCH; + } + + std::lock_guard lock(rsxaudio_obj->mutex); + + if (!rsxaudio_obj->init) { + return CELL_ESRCH; + } + + if (!vm::check_addr(addr.addr(), vm::page_writable, sizeof(u64))) { + return CELL_EFAULT; + } + + *addr = rsxaudio_obj->shmem; + rsxaudio_obj->page_unlock(); + + return CELL_OK; +} + +error_code sys_rsxaudio_unimport_shared_memory(u32 handle, + vm::ptr addr /* unused */) { + sys_rsxaudio.trace( + "sys_rsxaudio_unimport_shared_memory(handle=0x%x, addr=*0x%x)", handle, + addr); + + const auto rsxaudio_obj = idm::get_unlocked(handle); + + if (!rsxaudio_obj) { + return CELL_ESRCH; + } + + std::lock_guard lock(rsxaudio_obj->mutex); + + if (!rsxaudio_obj->init) { + return CELL_ESRCH; + } + + rsxaudio_obj->page_lock(); + + return CELL_OK; +} + +error_code sys_rsxaudio_create_connection(u32 handle) { + sys_rsxaudio.trace("sys_rsxaudio_create_connection(handle=0x%x)", handle); + + const auto rsxaudio_obj = idm::get_unlocked(handle); + + if (!rsxaudio_obj) { + return CELL_ESRCH; + } + + std::lock_guard lock(rsxaudio_obj->mutex); + + if (!rsxaudio_obj->init) { + return CELL_ESRCH; + } + + rsxaudio_shmem *sh_page = rsxaudio_obj->get_rw_shared_page(); + + const error_code port_create_status = [&]() -> error_code { + if (auto queue1 = idm::get_unlocked( + sh_page->ctrl.event_queue_1_id)) { + rsxaudio_obj->event_queue[0] = queue1; + + if (auto queue2 = idm::get_unlocked( + sh_page->ctrl.event_queue_2_id)) { + rsxaudio_obj->event_queue[1] = queue2; + + if (auto queue3 = idm::get_unlocked( + sh_page->ctrl.event_queue_3_id)) { + rsxaudio_obj->event_queue[2] = queue3; + + return CELL_OK; + } + } + } + + return CELL_ESRCH; + }(); + + if (port_create_status != CELL_OK) { + return port_create_status; + } + + for (auto &rb : sh_page->ctrl.ringbuf) { + rb.dma_silence_addr = rsxaudio_obj->dma_io_base + + offsetof(rsxaudio_shmem, dma_silence_region); + rb.unk2 = 100; + } + + for (u32 entry_idx = 0; entry_idx < SYS_RSXAUDIO_RINGBUF_SZ; entry_idx++) { + sh_page->ctrl.ringbuf[static_cast(RsxaudioPort::SERIAL)] + .entries[entry_idx] + .dma_addr = rsxaudio_obj->dma_io_base + + u32{offsetof(rsxaudio_shmem, dma_serial_region)} + + SYS_RSXAUDIO_RINGBUF_BLK_SZ_SERIAL * entry_idx; + sh_page->ctrl.ringbuf[static_cast(RsxaudioPort::SPDIF_0)] + .entries[entry_idx] + .dma_addr = rsxaudio_obj->dma_io_base + + u32{offsetof(rsxaudio_shmem, dma_spdif_0_region)} + + SYS_RSXAUDIO_RINGBUF_BLK_SZ_SPDIF * entry_idx; + sh_page->ctrl.ringbuf[static_cast(RsxaudioPort::SPDIF_1)] + .entries[entry_idx] + .dma_addr = rsxaudio_obj->dma_io_base + + u32{offsetof(rsxaudio_shmem, dma_spdif_1_region)} + + SYS_RSXAUDIO_RINGBUF_BLK_SZ_SPDIF * entry_idx; + } + + return CELL_OK; +} + +error_code sys_rsxaudio_close_connection(u32 handle) { + sys_rsxaudio.trace("sys_rsxaudio_close_connection(handle=0x%x)", handle); + + const auto rsxaudio_obj = idm::get_unlocked(handle); + + if (!rsxaudio_obj) { + return CELL_ESRCH; + } + + std::lock_guard lock(rsxaudio_obj->mutex); + + if (!rsxaudio_obj->init) { + return CELL_ESRCH; + } + + { + auto &rsxaudio_thread = g_fxo->get(); + std::lock_guard ra_obj_lock{rsxaudio_thread.rsxaudio_obj_upd_m}; + rsxaudio_thread.rsxaudio_obj_ptr = null_ptr; + } + + for (u32 q_idx = 0; q_idx < SYS_RSXAUDIO_PORT_CNT; q_idx++) { + rsxaudio_obj->event_queue[q_idx].reset(); + } + + return CELL_OK; +} + +error_code sys_rsxaudio_prepare_process(u32 handle) { + sys_rsxaudio.trace("sys_rsxaudio_prepare_process(handle=0x%x)", handle); + + const auto rsxaudio_obj = idm::get_unlocked(handle); + + if (!rsxaudio_obj) { + return CELL_ESRCH; + } + + std::lock_guard lock(rsxaudio_obj->mutex); + + if (!rsxaudio_obj->init) { + return CELL_ESRCH; + } + + auto &rsxaudio_thread = g_fxo->get(); + std::lock_guard ra_obj_lock{rsxaudio_thread.rsxaudio_obj_upd_m}; + + if (rsxaudio_thread.rsxaudio_obj_ptr) { + return -1; + } + + rsxaudio_thread.rsxaudio_obj_ptr = rsxaudio_obj; + + return CELL_OK; +} + +error_code sys_rsxaudio_start_process(u32 handle) { + sys_rsxaudio.trace("sys_rsxaudio_start_process(handle=0x%x)", handle); + + const auto rsxaudio_obj = idm::get_unlocked(handle); + + if (!rsxaudio_obj) { + return CELL_ESRCH; + } + + std::lock_guard lock(rsxaudio_obj->mutex); + + if (!rsxaudio_obj->init) { + return CELL_ESRCH; + } + + rsxaudio_shmem *sh_page = rsxaudio_obj->get_rw_shared_page(); + + for (auto &rb : sh_page->ctrl.ringbuf) { + if (rb.active) + rsxaudio_ringbuf_reader::clean_buf(rb); + } + + for (auto &uf : sh_page->ctrl.channel_uf) { + uf.uf_event_cnt = 0; + uf.unk1 = 0; + } + + auto &rsxaudio_thread = g_fxo->get(); + rsxaudio_thread.update_hw_param([&](auto ¶m) { + if (sh_page->ctrl.ringbuf[static_cast(RsxaudioPort::SERIAL)].active) + param.serial.dma_en = true; + if (sh_page->ctrl.ringbuf[static_cast(RsxaudioPort::SPDIF_0)].active) + param.spdif[0].dma_en = true; + if (sh_page->ctrl.ringbuf[static_cast(RsxaudioPort::SPDIF_1)].active) + param.spdif[1].dma_en = true; + }); + + for (u32 q_idx = 0; q_idx < SYS_RSXAUDIO_PORT_CNT; q_idx++) { + if (const auto &queue = rsxaudio_obj->event_queue[q_idx]; + queue && sh_page->ctrl.ringbuf[q_idx].active) { + queue->send(rsxaudio_obj->event_port_name[q_idx], q_idx, 0, 0); + } + } + + return CELL_OK; +} + +error_code sys_rsxaudio_stop_process(u32 handle) { + sys_rsxaudio.trace("sys_rsxaudio_stop_process(handle=0x%x)", handle); + + const auto rsxaudio_obj = idm::get_unlocked(handle); + + if (!rsxaudio_obj) { + return CELL_ESRCH; + } + + std::lock_guard lock(rsxaudio_obj->mutex); + + if (!rsxaudio_obj->init) { + return CELL_ESRCH; + } + + auto &rsxaudio_thread = g_fxo->get(); + + rsxaudio_thread.update_hw_param([&](auto ¶m) { + param.serial.dma_en = false; + param.serial.muted = true; + param.serial.en = false; + + for (auto &spdif : param.spdif) { + spdif.dma_en = false; + if (!spdif.use_serial_buf) { + spdif.en = false; + } + } + + param.spdif[1].muted = true; + }); + + rsxaudio_shmem *sh_page = rsxaudio_obj->get_rw_shared_page(); + + for (auto &rb : sh_page->ctrl.ringbuf) { + if (rb.active) + rsxaudio_ringbuf_reader::clean_buf(rb); + } + + return CELL_OK; +} + +error_code sys_rsxaudio_get_dma_param(u32 handle, u32 flag, vm::ptr out) { + sys_rsxaudio.trace( + "sys_rsxaudio_get_dma_param(handle=0x%x, flag=0x%x, out=0x%x)", handle, + flag, out); + + const auto rsxaudio_obj = idm::get_unlocked(handle); + + if (!rsxaudio_obj) { + return CELL_ESRCH; + } + + std::lock_guard lock(rsxaudio_obj->mutex); + + if (!rsxaudio_obj->init) { + return CELL_ESRCH; + } + + if (!vm::check_addr(out.addr(), vm::page_writable, sizeof(u64))) { + return CELL_EFAULT; + } + + if (flag == rsxaudio_dma_flag::IO_ID) { + *out = rsxaudio_obj->dma_io_id; + } else if (flag == rsxaudio_dma_flag::IO_BASE) { + *out = rsxaudio_obj->dma_io_base; + } + + return CELL_OK; +} + +rsxaudio_data_container::rsxaudio_data_container( + const rsxaudio_hw_param_t &hw_param, const buf_t &buf, bool serial_rdy, + bool spdif_0_rdy, bool spdif_1_rdy) + : hwp(hw_param), out_buf(buf) { + if (serial_rdy) { + avport_data_avail[static_cast(RsxaudioAvportIdx::AVMULTI)] = true; + + if (hwp.spdif[0].use_serial_buf) { + avport_data_avail[static_cast(RsxaudioAvportIdx::SPDIF_0)] = true; + } + + if (hwp.spdif[1].use_serial_buf) { + avport_data_avail[static_cast(RsxaudioAvportIdx::SPDIF_1)] = true; + } + } + + if (spdif_0_rdy && !hwp.spdif[0].use_serial_buf) { + avport_data_avail[static_cast(RsxaudioAvportIdx::SPDIF_0)] = true; + } + + if (spdif_1_rdy && !hwp.spdif[1].use_serial_buf) { + avport_data_avail[static_cast(RsxaudioAvportIdx::SPDIF_1)] = true; + } + + if (hwp.hdmi[0].init) { + if (hwp.hdmi[0].use_spdif_1) { + avport_data_avail[static_cast(RsxaudioAvportIdx::HDMI_0)] = + avport_data_avail[static_cast(RsxaudioAvportIdx::SPDIF_1)]; + } else { + avport_data_avail[static_cast(RsxaudioAvportIdx::HDMI_0)] = + serial_rdy; + } + + hdmi_stream_cnt[0] = static_cast(hwp.hdmi[0].ch_cfg.total_ch_cnt) / + SYS_RSXAUDIO_CH_PER_STREAM; + } + + if (hwp.hdmi[1].init) { + if (hwp.hdmi[1].use_spdif_1) { + avport_data_avail[static_cast(RsxaudioAvportIdx::HDMI_1)] = + avport_data_avail[static_cast(RsxaudioAvportIdx::SPDIF_1)]; + } else { + avport_data_avail[static_cast(RsxaudioAvportIdx::HDMI_1)] = + serial_rdy; + } + + hdmi_stream_cnt[1] = static_cast(hwp.hdmi[1].ch_cfg.total_ch_cnt) / + SYS_RSXAUDIO_CH_PER_STREAM; + } +} + +u32 rsxaudio_data_container::get_data_size(RsxaudioAvportIdx avport) { + if (!avport_data_avail[static_cast(avport)]) { + return 0; + } + + switch (avport) { + case RsxaudioAvportIdx::HDMI_0: { + const RsxaudioSampleSize depth = + hwp.hdmi[0].use_spdif_1 ? hwp.spdif[1].depth : hwp.serial.depth; + + return (depth == RsxaudioSampleSize::_16BIT ? SYS_RSXAUDIO_STREAM_SIZE * 2 + : SYS_RSXAUDIO_STREAM_SIZE) * + hdmi_stream_cnt[0]; + } + case RsxaudioAvportIdx::HDMI_1: { + const RsxaudioSampleSize depth = + hwp.hdmi[1].use_spdif_1 ? hwp.spdif[1].depth : hwp.serial.depth; + + return (depth == RsxaudioSampleSize::_16BIT ? SYS_RSXAUDIO_STREAM_SIZE * 2 + : SYS_RSXAUDIO_STREAM_SIZE) * + hdmi_stream_cnt[1]; + } + case RsxaudioAvportIdx::AVMULTI: { + return hwp.serial.depth == RsxaudioSampleSize::_16BIT + ? SYS_RSXAUDIO_STREAM_SIZE * 2 + : SYS_RSXAUDIO_STREAM_SIZE; + } + case RsxaudioAvportIdx::SPDIF_0: { + const RsxaudioSampleSize depth = + hwp.spdif[0].use_serial_buf ? hwp.serial.depth : hwp.spdif[0].depth; + + return depth == RsxaudioSampleSize::_16BIT ? SYS_RSXAUDIO_STREAM_SIZE * 2 + : SYS_RSXAUDIO_STREAM_SIZE; + } + case RsxaudioAvportIdx::SPDIF_1: { + const RsxaudioSampleSize depth = + hwp.spdif[1].use_serial_buf ? hwp.serial.depth : hwp.spdif[1].depth; + + return depth == RsxaudioSampleSize::_16BIT ? SYS_RSXAUDIO_STREAM_SIZE * 2 + : SYS_RSXAUDIO_STREAM_SIZE; + } + default: { + return 0; + } + } +} + +void rsxaudio_data_container::get_data(RsxaudioAvportIdx avport, + data_blk_t &data_out) { + if (!avport_data_avail[static_cast(avport)]) { + return; + } + + data_was_written = true; + + auto spdif_filter_map = [&](u8 hdmi_idx) { + std::array result; + + for (u64 i = 0; i < SYS_RSXAUDIO_SERIAL_MAX_CH; i++) { + const u8 old_val = hwp.hdmi[hdmi_idx].ch_cfg.map[i]; + result[i] = old_val >= SYS_RSXAUDIO_SPDIF_MAX_CH + ? rsxaudio_hw_param_t::hdmi_param_t::MAP_SILENT_CH + : old_val; + } + + return result; + }; + + switch (avport) { + case RsxaudioAvportIdx::HDMI_0: + case RsxaudioAvportIdx::HDMI_1: { + const u8 hdmi_idx = avport == RsxaudioAvportIdx::HDMI_1; + + switch (hdmi_stream_cnt[hdmi_idx]) { + default: + case 0: { + return; + } + case 1: { + if (hwp.hdmi[hdmi_idx].use_spdif_1) { + if (hwp.spdif[1].use_serial_buf) { + mix<2>(spdif_filter_map(hdmi_idx), hwp.serial.depth, out_buf.serial, + data_out); + } else { + mix<2>(hwp.hdmi[hdmi_idx].ch_cfg.map, hwp.spdif[1].depth, + out_buf.spdif[1], data_out); + } + } else { + mix<2>(hwp.hdmi[hdmi_idx].ch_cfg.map, hwp.serial.depth, out_buf.serial, + data_out); + } + break; + } + case 3: { + if (hwp.hdmi[hdmi_idx].use_spdif_1) { + if (hwp.spdif[1].use_serial_buf) { + mix<6>(spdif_filter_map(hdmi_idx), hwp.serial.depth, out_buf.serial, + data_out); + } else { + mix<6>(hwp.hdmi[hdmi_idx].ch_cfg.map, hwp.spdif[1].depth, + out_buf.spdif[1], data_out); + } + } else { + mix<6>(hwp.hdmi[hdmi_idx].ch_cfg.map, hwp.serial.depth, out_buf.serial, + data_out); + } + break; + } + case 4: { + if (hwp.hdmi[hdmi_idx].use_spdif_1) { + if (hwp.spdif[1].use_serial_buf) { + mix<8>(spdif_filter_map(hdmi_idx), hwp.serial.depth, out_buf.serial, + data_out); + } else { + mix<8>(hwp.hdmi[hdmi_idx].ch_cfg.map, hwp.spdif[1].depth, + out_buf.spdif[1], data_out); + } + } else { + mix<8>(hwp.hdmi[hdmi_idx].ch_cfg.map, hwp.serial.depth, out_buf.serial, + data_out); + } + break; + } + } + + break; + } + case RsxaudioAvportIdx::AVMULTI: { + mix<2>({2, 3}, hwp.serial.depth, out_buf.serial, data_out); + break; + } + case RsxaudioAvportIdx::SPDIF_0: + case RsxaudioAvportIdx::SPDIF_1: { + const u8 spdif_idx = avport == RsxaudioAvportIdx::SPDIF_1; + + if (hwp.spdif[spdif_idx].use_serial_buf) { + mix<2>({0, 1}, hwp.serial.depth, out_buf.serial, data_out); + } else { + mix<2>({0, 1}, hwp.spdif[spdif_idx].depth, out_buf.spdif[spdif_idx], + data_out); + } + break; + } + default: { + return; + } + } +} + +bool rsxaudio_data_container::data_was_used() { return data_was_written; } + +rsxaudio_data_thread::rsxaudio_data_thread() {} + +void rsxaudio_data_thread::operator()() { + thread_ctrl::scoped_priority high_prio(+1); + + while (thread_ctrl::state() != thread_state::aborting) { + static const std::function tmr_callback = [this]() { + extract_audio_data(); + }; + + switch (timer.wait(tmr_callback)) { + case rsxaudio_periodic_tmr::wait_result::SUCCESS: + case rsxaudio_periodic_tmr::wait_result::TIMEOUT: + case rsxaudio_periodic_tmr::wait_result::TIMER_CANCELED: { + continue; + } + case rsxaudio_periodic_tmr::wait_result::INVALID_PARAM: + case rsxaudio_periodic_tmr::wait_result::TIMER_ERROR: + default: { + fmt::throw_exception("rsxaudio_periodic_tmr::wait() failed"); + } + } + } +} + +rsxaudio_data_thread & +rsxaudio_data_thread::operator=(thread_state /* state */) { + timer.cancel_wait(); + return *this; +} + +void rsxaudio_data_thread::advance_all_timers() { + const u64 crnt_time = get_system_time(); + + timer.vtimer_skip_periods(static_cast(RsxaudioPort::SERIAL), crnt_time); + timer.vtimer_skip_periods(static_cast(RsxaudioPort::SPDIF_0), crnt_time); + timer.vtimer_skip_periods(static_cast(RsxaudioPort::SPDIF_1), crnt_time); +} + +void rsxaudio_data_thread::extract_audio_data() { + // Accessing timer state is safe here, since we're in timer::wait() + + const auto rsxaudio_obj = [&]() { + std::lock_guard ra_obj_lock{rsxaudio_obj_upd_m}; + return rsxaudio_obj_ptr; + }(); + + if (Emu.IsPausedOrReady() || !rsxaudio_obj) { + advance_all_timers(); + return; + } + + std::lock_guard rsxaudio_lock(rsxaudio_obj->mutex); + + if (!rsxaudio_obj->init) { + advance_all_timers(); + return; + } + + rsxaudio_shmem *sh_page = rsxaudio_obj->get_rw_shared_page(); + const auto hw_cfg = hw_param_ts.get_current(); + const u64 crnt_time = get_system_time(); + + auto process_rb = [&](RsxaudioPort dst, bool dma_en) { + // SPDIF channel data and underflow events are always disabled by lv1 + + const u32 dst_raw = static_cast(dst); + rsxaudio_ringbuf_reader::set_timestamp( + sh_page->ctrl.ringbuf[dst_raw], timer.vtimer_get_sched_time(dst_raw)); + + const auto [data_present, rb_addr] = get_ringbuf_addr(dst, *rsxaudio_obj); + bool reset_periods = + !enqueue_data(dst, rb_addr == nullptr, rb_addr, *hw_cfg); + + if (dma_en) { + if (const auto [notify, blk_idx, timestamp] = + rsxaudio_ringbuf_reader::update_status( + sh_page->ctrl.ringbuf[dst_raw]); + notify) { + // Too late to recover + reset_periods = true; + + if (const auto &queue = rsxaudio_obj->event_queue[dst_raw]) { + queue->send(rsxaudio_obj->event_port_name[dst_raw], dst_raw, blk_idx, + timestamp); + } + } + } + + if (reset_periods) { + timer.vtimer_skip_periods(dst_raw, crnt_time); + } else { + timer.vtimer_incr(dst_raw, crnt_time); + } + }; + + if (timer.is_vtimer_behind(static_cast(RsxaudioPort::SERIAL), + crnt_time)) { + process_rb(RsxaudioPort::SERIAL, hw_cfg->serial.dma_en); + } + + if (timer.is_vtimer_behind(static_cast(RsxaudioPort::SPDIF_0), + crnt_time)) { + process_rb(RsxaudioPort::SPDIF_0, hw_cfg->spdif[0].dma_en); + } + + if (timer.is_vtimer_behind(static_cast(RsxaudioPort::SPDIF_1), + crnt_time)) { + process_rb(RsxaudioPort::SPDIF_1, hw_cfg->spdif[1].dma_en); + } +} + +std::pair +rsxaudio_data_thread::get_ringbuf_addr(RsxaudioPort dst, + const lv2_rsxaudio &rsxaudio_obj) { + ensure(dst <= RsxaudioPort::SPDIF_1); + + rsxaudio_shmem *sh_page = rsxaudio_obj.get_rw_shared_page(); + const auto [data_present, addr] = rsxaudio_ringbuf_reader::get_addr( + sh_page->ctrl.ringbuf[static_cast(dst)]); + const u32 buf_size = dst == RsxaudioPort::SERIAL + ? SYS_RSXAUDIO_RINGBUF_BLK_SZ_SERIAL + : SYS_RSXAUDIO_RINGBUF_BLK_SZ_SPDIF; + + if (addr >= rsxaudio_obj.dma_io_base && + addr < rsxaudio_obj.dma_io_base + sizeof(rsxaudio_shmem) - buf_size) { + return std::make_pair(data_present, reinterpret_cast( + rsxaudio_obj.get_rw_shared_page()) + + addr - rsxaudio_obj.dma_io_base); + } + + // Buffer address is invalid + return std::make_pair(false, nullptr); +} + +void rsxaudio_data_thread::reset_hw() { + update_hw_param([&](rsxaudio_hw_param_t ¤t) { + const bool serial_dma_en = current.serial.dma_en; + current.serial = {}; + current.serial.dma_en = serial_dma_en; + + for (auto &spdif : current.spdif) { + const bool spdif_dma_en = spdif.dma_en; + spdif = {}; + spdif.dma_en = spdif_dma_en; + } + + current.serial_freq_base = SYS_RSXAUDIO_FREQ_BASE_384K; + current.spdif_freq_base = SYS_RSXAUDIO_FREQ_BASE_352K; + current.avport_src.fill(RsxaudioPort::INVALID); + }); +} + +void rsxaudio_data_thread::update_hw_param( + std::function update_callback) { + ensure(update_callback); + + hw_param_ts.add_op([&]() { + auto new_hw_param = + std::make_shared(*hw_param_ts.get_current()); + + update_callback(*new_hw_param); + + const bool serial_active = + calc_port_active_state(RsxaudioPort::SERIAL, *new_hw_param); + const bool spdif_active[SYS_RSXAUDIO_SPDIF_CNT] = { + calc_port_active_state(RsxaudioPort::SPDIF_0, *new_hw_param), + calc_port_active_state(RsxaudioPort::SPDIF_1, *new_hw_param)}; + + std::array + port_cfg{}; + port_cfg[static_cast(RsxaudioAvportIdx::AVMULTI)] = { + static_cast(new_hw_param->serial_freq_base / + new_hw_param->serial.freq_div), + AudioChannelCnt::STEREO}; + + auto gen_spdif_port_cfg = [&](u8 spdif_idx) { + if (new_hw_param->spdif[spdif_idx].use_serial_buf) { + return port_cfg[static_cast(RsxaudioAvportIdx::AVMULTI)]; + } + + return rsxaudio_backend_thread::port_config{ + static_cast(new_hw_param->spdif_freq_base / + new_hw_param->spdif[spdif_idx].freq_div), + AudioChannelCnt::STEREO}; + }; + + port_cfg[static_cast(RsxaudioAvportIdx::SPDIF_0)] = + gen_spdif_port_cfg(0); + port_cfg[static_cast(RsxaudioAvportIdx::SPDIF_1)] = + gen_spdif_port_cfg(1); + + auto gen_hdmi_port_cfg = [&](u8 hdmi_idx) { + if (new_hw_param->hdmi[hdmi_idx].use_spdif_1) { + return rsxaudio_backend_thread::port_config{ + port_cfg[static_cast(RsxaudioAvportIdx::SPDIF_1)].freq, + new_hw_param->hdmi[hdmi_idx].ch_cfg.total_ch_cnt}; + } + + return rsxaudio_backend_thread::port_config{ + port_cfg[static_cast(RsxaudioAvportIdx::AVMULTI)].freq, + new_hw_param->hdmi[hdmi_idx].ch_cfg.total_ch_cnt}; + }; + + port_cfg[static_cast(RsxaudioAvportIdx::HDMI_0)] = gen_hdmi_port_cfg(0); + port_cfg[static_cast(RsxaudioAvportIdx::HDMI_1)] = gen_hdmi_port_cfg(1); + // TODO: ideally, old data must be flushed from backend buffers if channel + // became inactive or its src changed + g_fxo->get().set_new_stream_param( + port_cfg, calc_avport_mute_state(*new_hw_param)); + + timer.vtimer_access_sec([&]() { + const u64 crnt_time = get_system_time(); + + if (serial_active) { + // 2 channels per stream, streams go in parallel + const u32 new_timer_rate = + static_cast( + port_cfg[static_cast(RsxaudioAvportIdx::AVMULTI)].freq) * + static_cast(new_hw_param->serial.depth) * + SYS_RSXAUDIO_CH_PER_STREAM; + + timer.enable_vtimer(static_cast(RsxaudioPort::SERIAL), + new_timer_rate, crnt_time); + } else { + timer.disable_vtimer(static_cast(RsxaudioPort::SERIAL)); + } + + for (u8 spdif_idx = 0; spdif_idx < SYS_RSXAUDIO_SPDIF_CNT; spdif_idx++) { + const u32 vtimer_id = + static_cast(RsxaudioPort::SPDIF_0) + spdif_idx; + + if (spdif_active[spdif_idx] && + !new_hw_param->spdif[spdif_idx].use_serial_buf) { + // 2 channels per stream, single stream + const u32 new_timer_rate = + static_cast( + port_cfg[static_cast(RsxaudioAvportIdx::SPDIF_0) + + spdif_idx] + .freq) * + static_cast(new_hw_param->spdif[spdif_idx].depth) * + SYS_RSXAUDIO_CH_PER_STREAM; + + timer.enable_vtimer(vtimer_id, new_timer_rate, crnt_time); + } else { + timer.disable_vtimer(vtimer_id); + } + } + }); + + return new_hw_param; + }); +} + +void rsxaudio_data_thread::update_mute_state(RsxaudioPort port, bool muted) { + hw_param_ts.add_op([&]() { + auto new_hw_param = + std::make_shared(*hw_param_ts.get_current()); + + switch (port) { + case RsxaudioPort::SERIAL: { + new_hw_param->serial.muted = muted; + break; + } + case RsxaudioPort::SPDIF_0: { + new_hw_param->spdif[0].muted = muted; + break; + } + case RsxaudioPort::SPDIF_1: { + new_hw_param->spdif[1].muted = muted; + break; + } + default: { + fmt::throw_exception("Invalid RSXAudio port: %u", static_cast(port)); + } + } + + g_fxo->get().set_mute_state( + calc_avport_mute_state(*new_hw_param)); + + return new_hw_param; + }); +} + +void rsxaudio_data_thread::update_av_mute_state(RsxaudioAvportIdx avport, + bool muted, bool force_mute, + bool set) { + hw_param_ts.add_op([&]() { + auto new_hw_param = + std::make_shared(*hw_param_ts.get_current()); + + switch (avport) { + case RsxaudioAvportIdx::HDMI_0: + case RsxaudioAvportIdx::HDMI_1: { + const u32 hdmi_idx = avport == RsxaudioAvportIdx::HDMI_1; + + if (muted) { + new_hw_param->hdmi[hdmi_idx].muted = set; + } + + if (force_mute) { + new_hw_param->hdmi[hdmi_idx].force_mute = set; + } + break; + } + case RsxaudioAvportIdx::AVMULTI: { + if (muted) { + new_hw_param->avmulti_av_muted = set; + } + break; + } + default: { + fmt::throw_exception("Invalid RSXAudio avport: %u", + static_cast(avport)); + } + } + + g_fxo->get().set_mute_state( + calc_avport_mute_state(*new_hw_param)); + + return new_hw_param; + }); +} + +rsxaudio_backend_thread::avport_bit +rsxaudio_data_thread::calc_avport_mute_state(const rsxaudio_hw_param_t &hwp) { + const bool serial_active = calc_port_active_state(RsxaudioPort::SERIAL, hwp); + + const bool spdif_active[SYS_RSXAUDIO_SPDIF_CNT] = { + calc_port_active_state(RsxaudioPort::SPDIF_0, hwp), + calc_port_active_state(RsxaudioPort::SPDIF_1, hwp)}; + + const bool avmulti = + !serial_active || hwp.serial.muted || hwp.avmulti_av_muted; + + auto spdif_muted = [&](u8 spdif_idx) { + const u8 spdif_port = spdif_idx == 1; + + if (hwp.spdif[spdif_port].use_serial_buf) { + // TODO: HW test if both serial and spdif mutes are used in serial mode + // for spdif + return !serial_active || + hwp.spdif[spdif_port].freq_div != hwp.serial.freq_div || + hwp.serial.muted || hwp.spdif[spdif_port].muted; + } + + return !spdif_active[spdif_port] || hwp.spdif[spdif_port].muted; + }; + + auto hdmi_muted = [&](u8 hdmi_idx) { + const u8 hdmi_port = hdmi_idx == 1; + + if (hwp.hdmi[hdmi_idx].use_spdif_1) { + return spdif_muted(1) || hwp.hdmi[hdmi_port].muted || + hwp.hdmi[hdmi_port].force_mute || !hwp.hdmi[hdmi_port].init; + } + + return !serial_active || hwp.serial.muted || hwp.hdmi[hdmi_port].muted || + hwp.hdmi[hdmi_port].force_mute || !hwp.hdmi[hdmi_port].init; + }; + + return {hdmi_muted(0), hdmi_muted(1), avmulti, spdif_muted(0), + spdif_muted(1)}; +} + +bool rsxaudio_data_thread::calc_port_active_state( + RsxaudioPort port, const rsxaudio_hw_param_t &hwp) { + auto gen_serial_active = [&]() { + return hwp.serial.dma_en && hwp.serial.buf_empty_en && hwp.serial.en; + }; + + auto gen_spdif_active = [&](u8 spdif_idx) { + if (hwp.spdif[spdif_idx].use_serial_buf) { + return gen_serial_active() && + (hwp.spdif[spdif_idx].freq_div == hwp.serial.freq_div); + } + + return hwp.spdif[spdif_idx].dma_en && hwp.spdif[spdif_idx].buf_empty_en && + hwp.spdif[spdif_idx].en; + }; + + switch (port) { + case RsxaudioPort::SERIAL: { + return gen_serial_active(); + } + case RsxaudioPort::SPDIF_0: { + return gen_spdif_active(0); + } + case RsxaudioPort::SPDIF_1: { + return gen_spdif_active(1); + } + default: { + return false; + } + } +} + +f32 rsxaudio_data_thread::pcm_to_float(s32 sample) { + return sample * (1.0f / 2147483648.0f); +} + +f32 rsxaudio_data_thread::pcm_to_float(s16 sample) { + return sample * (1.0f / 32768.0f); +} + +void rsxaudio_data_thread::pcm_serial_process_channel( + RsxaudioSampleSize word_bits, ra_stream_blk_t &buf_out_l, + ra_stream_blk_t &buf_out_r, const void *buf_in, u8 src_stream) { + const u8 input_word_sz = static_cast(word_bits); + u64 ch_dst = 0; + + for (u64 blk_idx = 0; blk_idx < SYS_RSXAUDIO_STREAM_DATA_BLK_CNT; blk_idx++) { + for (u64 offset = 0; offset < SYS_RSXAUDIO_DATA_BLK_SIZE / 2; + offset += input_word_sz, ch_dst++) { + const u64 left_ch_src = + (blk_idx * SYS_RSXAUDIO_STREAM_SIZE + + src_stream * SYS_RSXAUDIO_DATA_BLK_SIZE + offset) / + input_word_sz; + const u64 right_ch_src = + left_ch_src + (SYS_RSXAUDIO_DATA_BLK_SIZE / 2) / input_word_sz; + + if (word_bits == RsxaudioSampleSize::_16BIT) { + buf_out_l[ch_dst] = + pcm_to_float(static_cast *>(buf_in)[left_ch_src]); + buf_out_r[ch_dst] = + pcm_to_float(static_cast *>(buf_in)[right_ch_src]); + } else { + // Looks like rsx treats 20bit/24bit samples as 32bit ones + buf_out_l[ch_dst] = + pcm_to_float(static_cast *>(buf_in)[left_ch_src]); + buf_out_r[ch_dst] = + pcm_to_float(static_cast *>(buf_in)[right_ch_src]); + } + } + } +} + +void rsxaudio_data_thread::pcm_spdif_process_channel( + RsxaudioSampleSize word_bits, ra_stream_blk_t &buf_out_l, + ra_stream_blk_t &buf_out_r, const void *buf_in) { + const u8 input_word_sz = static_cast(word_bits); + + for (u64 offset = 0; offset < SYS_RSXAUDIO_RINGBUF_BLK_SZ_SPDIF / + (input_word_sz * SYS_RSXAUDIO_SPDIF_MAX_CH); + offset++) { + const u64 left_ch_src = offset * SYS_RSXAUDIO_SPDIF_MAX_CH; + const u64 right_ch_src = left_ch_src + 1; + + if (word_bits == RsxaudioSampleSize::_16BIT) { + buf_out_l[offset] = + pcm_to_float(static_cast *>(buf_in)[left_ch_src]); + buf_out_r[offset] = + pcm_to_float(static_cast *>(buf_in)[right_ch_src]); + } else { + // Looks like rsx treats 20bit/24bit samples as 32bit ones + buf_out_l[offset] = + pcm_to_float(static_cast *>(buf_in)[left_ch_src]); + buf_out_r[offset] = + pcm_to_float(static_cast *>(buf_in)[right_ch_src]); + } + } +} + +bool rsxaudio_data_thread::enqueue_data(RsxaudioPort dst, bool silence, + const void *src_addr, + const rsxaudio_hw_param_t &hwp) { + auto &backend_thread = g_fxo->get(); + + if (dst == RsxaudioPort::SERIAL) { + if (!silence) { + for (u8 stream_idx = 0; stream_idx < SYS_RSXAUDIO_SERIAL_STREAM_CNT; + stream_idx++) { + pcm_serial_process_channel( + hwp.serial.depth, output_buf.serial[stream_idx * 2], + output_buf.serial[stream_idx * 2 + 1], src_addr, stream_idx); + } + } else { + output_buf.serial.fill({}); + } + + rsxaudio_data_container cont{hwp, output_buf, true, false, false}; + backend_thread.add_data(cont); + return cont.data_was_used(); + } else if (dst == RsxaudioPort::SPDIF_0) { + if (!silence) { + pcm_spdif_process_channel(hwp.spdif[0].depth, output_buf.spdif[0][0], + output_buf.spdif[0][1], src_addr); + } else { + output_buf.spdif[0].fill({}); + } + + rsxaudio_data_container cont{hwp, output_buf, false, true, false}; + backend_thread.add_data(cont); + return cont.data_was_used(); + } else if (dst == RsxaudioPort::SPDIF_1) { + if (!silence) { + pcm_spdif_process_channel(hwp.spdif[1].depth, output_buf.spdif[1][0], + output_buf.spdif[1][1], src_addr); + } else { + output_buf.spdif[1].fill({}); + } + + rsxaudio_data_container cont{hwp, output_buf, false, false, true}; + backend_thread.add_data(cont); + return cont.data_was_used(); + } + + return false; +} + +namespace audio { +extern void configure_rsxaudio() { + if (g_cfg.audio.provider == audio_provider::rsxaudio && + g_fxo->is_init()) { + g_fxo->get().update_emu_cfg(); + } +} +} // namespace audio + +rsxaudio_backend_thread::rsxaudio_backend_thread() { + new_emu_cfg = get_emu_cfg(); + + const f32 new_vol = audio::get_volume(); + + callback_cfg.atomic_op([&](callback_config &val) { + val.target_volume = + static_cast(new_vol * callback_config::VOL_NOMINAL); + val.initial_volume = val.current_volume; + }); +} + +rsxaudio_backend_thread::~rsxaudio_backend_thread() { + if (backend) { + backend->Close(); + backend->SetWriteCallback(nullptr); + backend->SetStateCallback(nullptr); + backend = nullptr; + } +} + +void rsxaudio_backend_thread::update_emu_cfg() { + std::unique_lock lock(state_update_m); + const emu_audio_cfg _new_emu_cfg = get_emu_cfg(); + const f32 new_vol = audio::get_volume(); + + callback_cfg.atomic_op([&](callback_config &val) { + val.target_volume = + static_cast(new_vol * callback_config::VOL_NOMINAL); + val.initial_volume = val.current_volume; + }); + + if (new_emu_cfg != _new_emu_cfg) { + new_emu_cfg = _new_emu_cfg; + emu_cfg_changed = true; + lock.unlock(); + state_update_c.notify_all(); + } +} + +u32 rsxaudio_backend_thread::get_sample_rate() const { + return callback_cfg.load().freq; +} + +u8 rsxaudio_backend_thread::get_channel_count() const { + return callback_cfg.load().input_ch_cnt; +} + +rsxaudio_backend_thread::emu_audio_cfg rsxaudio_backend_thread::get_emu_cfg() { + // Get max supported channel count + AudioChannelCnt out_ch_cnt = + AudioBackend::get_max_channel_count(0); // CELL_AUDIO_OUT_PRIMARY + + emu_audio_cfg cfg = { + .audio_device = g_cfg.audio.audio_device, + .desired_buffer_duration = g_cfg.audio.desired_buffer_duration, + .time_stretching_threshold = + g_cfg.audio.time_stretching_threshold / 100.0, + .buffering_enabled = static_cast(g_cfg.audio.enable_buffering), + .convert_to_s16 = static_cast(g_cfg.audio.convert_to_s16), + .enable_time_stretching = + static_cast(g_cfg.audio.enable_time_stretching), + .dump_to_file = static_cast(g_cfg.audio.dump_to_file), + .channels = out_ch_cnt, + .channel_layout = g_cfg.audio.channel_layout, + .renderer = g_cfg.audio.renderer, + .provider = g_cfg.audio.provider, + .avport = convert_avport(g_cfg.audio.rsxaudio_port)}; + + cfg.buffering_enabled = + cfg.buffering_enabled && cfg.renderer != audio_renderer::null; + cfg.enable_time_stretching = cfg.buffering_enabled && + cfg.enable_time_stretching && + cfg.time_stretching_threshold > 0.0; + + return cfg; +} + +void rsxaudio_backend_thread::operator()() { + if (g_cfg.audio.provider != audio_provider::rsxaudio) { + return; + } + + static rsxaudio_state ra_state{}; + static emu_audio_cfg emu_cfg{}; + static bool backend_failed = false; + + for (;;) { + bool should_update_backend = false; + bool reset_backend = false; + bool checkDefaultDevice = false; + bool should_service_stream = false; + + { + std::unique_lock lock(state_update_m); + for (;;) { + // Unsafe to access backend under lock (state_changed_callback uses + // state_update_m -> possible deadlock) + + if (thread_ctrl::state() == thread_state::aborting) { + lock.unlock(); + backend_stop(); + return; + } + + if (backend_device_changed) { + should_update_backend = true; + checkDefaultDevice = true; + backend_device_changed = false; + } + + // Emulated state changed + if (ra_state_changed) { + const callback_config cb_cfg = callback_cfg.observe(); + ra_state_changed = false; + ra_state = new_ra_state; + + if (cb_cfg.cfg_changed) { + should_update_backend = true; + checkDefaultDevice = false; + callback_cfg.atomic_op([&](callback_config &val) { + val.cfg_changed = false; // Acknowledge cfg update + }); + } + } + + // Update emu config + if (emu_cfg_changed) { + reset_backend |= emu_cfg.renderer != new_emu_cfg.renderer; + emu_cfg_changed = false; + emu_cfg = new_emu_cfg; + should_update_backend = true; + checkDefaultDevice = false; + } + + // Handle backend error notification + if (backend_error_occured) { + reset_backend = true; + should_update_backend = true; + checkDefaultDevice = false; + backend_error_occured = false; + } + + if (should_update_backend) { + backend_current_cfg.cfg = + ra_state.port[static_cast(emu_cfg.avport)]; + backend_current_cfg.avport = emu_cfg.avport; + break; + } + + if (backend_failed) { + state_update_c.wait(state_update_m, ERROR_SERVICE_PERIOD); + break; + } + + if (use_aux_ringbuf) { + const u64 next_period_time = get_time_until_service(); + should_service_stream = next_period_time <= SERVICE_THRESHOLD; + + if (should_service_stream) { + break; + } + + state_update_c.wait(state_update_m, next_period_time); + } else { + // Nothing to do - wait for events + state_update_c.wait(state_update_m, umax); + } + } + } + + if (should_update_backend && + (!checkDefaultDevice || backend->DefaultDeviceChanged())) { + backend_init(ra_state, emu_cfg, reset_backend); + + if (emu_cfg.enable_time_stretching) { + resampler.set_params(backend_current_cfg.cfg.ch_cnt, + backend_current_cfg.cfg.freq); + resampler.set_tempo(RESAMPLER_MAX_FREQ_VAL); + } + + if (emu_cfg.dump_to_file) { + dumper.Open(backend_current_cfg.cfg.ch_cnt, + backend_current_cfg.cfg.freq, AudioSampleSize::FLOAT); + } else { + dumper.Close(); + } + } + + if (!backend->Operational()) { + if (!backend_failed) { + sys_rsxaudio.warning("Backend stopped unexpectedly (likely device " + "change). Attempting to recover..."); + } + + backend_init(ra_state, emu_cfg); + backend_failed = true; + continue; + } + + if (backend_failed) { + sys_rsxaudio.warning("Backend recovered"); + backend_failed = false; + } + + if (!Emu.IsPausedOrReady() || + !use_aux_ringbuf) // Don't pause if thread is in direct mode + { + if (!backend_playing()) { + backend_start(); + reset_service_time(); + continue; + } + + if (should_service_stream) { + void *crnt_buf = thread_tmp_buf.data(); + + const u64 bytes_req = ringbuf.get_free_size(); + const u64 bytes_read = aux_ringbuf.pop(crnt_buf, bytes_req, true); + u64 crnt_buf_size = bytes_read; + + if (emu_cfg.enable_time_stretching) { + const u64 input_ch_cnt = static_cast( + ra_state.port[static_cast(emu_cfg.avport)].ch_cnt); + const u64 bytes_per_sample = + static_cast(AudioSampleSize::FLOAT) * input_ch_cnt; + const u64 samples_req = bytes_req / bytes_per_sample; + const u64 samples_avail = crnt_buf_size / bytes_per_sample; + const f64 resampler_ratio = resampler.get_resample_ratio(); + f64 fullness_ratio = + static_cast(samples_avail + resampler.samples_available()) / + samples_req; + + if (fullness_ratio < emu_cfg.time_stretching_threshold) { + fullness_ratio /= emu_cfg.time_stretching_threshold; + const f64 new_resampler_ratio = + (resampler_ratio + fullness_ratio) / 2.0; + if (std::abs(new_resampler_ratio - resampler_ratio) >= + TIME_STRETCHING_STEP) { + resampler.set_tempo(new_resampler_ratio); + } + } else if (resampler_ratio != RESAMPLER_MAX_FREQ_VAL) { + resampler.set_tempo(RESAMPLER_MAX_FREQ_VAL); + } + + resampler.put_samples(static_cast(crnt_buf), + static_cast(samples_avail)); + const auto [resampled_data, sample_cnt] = + resampler.get_samples(static_cast(samples_req)); + crnt_buf = resampled_data; + crnt_buf_size = sample_cnt * bytes_per_sample; + } + + // Dump audio if enabled + if (emu_cfg.dump_to_file) { + dumper.WriteData(crnt_buf, static_cast(crnt_buf_size)); + } + + ringbuf.push(crnt_buf, crnt_buf_size); + + update_service_time(); + } + } else { + if (backend_playing()) { + backend_stop(); + } + + if (should_service_stream) { + update_service_time(); + } + } + } +} + +rsxaudio_backend_thread & +rsxaudio_backend_thread::operator=(thread_state /* state */) { + { + std::lock_guard lock(state_update_m); + } + state_update_c.notify_all(); + return *this; +} + +void rsxaudio_backend_thread::set_new_stream_param( + const std::array &cfg, + avport_bit muted_avports) { + std::unique_lock lock(state_update_m); + + const auto new_mute_state = gen_mute_state(muted_avports); + const bool should_update = backend_current_cfg.cfg != + cfg[static_cast(backend_current_cfg.avport)]; + + callback_cfg.atomic_op([&](callback_config &val) { + val.mute_state = new_mute_state; + + if (should_update) { + val.ready = false; // Prevent audio playback until backend is reconfigured + val.cfg_changed = true; + } + }); + + if (new_ra_state.port != cfg) { + new_ra_state.port = cfg; + ra_state_changed = true; + lock.unlock(); + state_update_c.notify_all(); + } +} + +void rsxaudio_backend_thread::set_mute_state(avport_bit muted_avports) { + const auto new_mute_state = gen_mute_state(muted_avports); + + callback_cfg.atomic_op( + [&](callback_config &val) { val.mute_state = new_mute_state; }); +} + +u8 rsxaudio_backend_thread::gen_mute_state(avport_bit avports) { + std::bitset mute_state{0}; + + if (avports.hdmi_0) + mute_state[static_cast(RsxaudioAvportIdx::HDMI_0)] = true; + if (avports.hdmi_1) + mute_state[static_cast(RsxaudioAvportIdx::HDMI_1)] = true; + if (avports.avmulti) + mute_state[static_cast(RsxaudioAvportIdx::AVMULTI)] = true; + if (avports.spdif_0) + mute_state[static_cast(RsxaudioAvportIdx::SPDIF_0)] = true; + if (avports.spdif_1) + mute_state[static_cast(RsxaudioAvportIdx::SPDIF_1)] = true; + + return static_cast(mute_state.to_ulong()); +} + +void rsxaudio_backend_thread::add_data(rsxaudio_data_container &cont) { + std::unique_lock lock(ringbuf_mutex, std::try_to_lock); + if (!lock.owns_lock()) { + return; + } + + const callback_config cb_cfg = callback_cfg.observe(); + if (!cb_cfg.ready || !cb_cfg.callback_active) { + return; + } + + static rsxaudio_data_container::data_blk_t in_data_blk{}; + + if (u32 len = cont.get_data_size(cb_cfg.avport_idx)) { + if (use_aux_ringbuf) { + if (aux_ringbuf.get_free_size() >= len) { + cont.get_data(cb_cfg.avport_idx, in_data_blk); + aux_ringbuf.push(in_data_blk.data(), len); + } + } else { + if (ringbuf.get_free_size() >= len) { + cont.get_data(cb_cfg.avport_idx, in_data_blk); + ringbuf.push(in_data_blk.data(), len); + } + } + } +} + +RsxaudioAvportIdx rsxaudio_backend_thread::convert_avport(audio_avport avport) { + switch (avport) { + case audio_avport::hdmi_0: + return RsxaudioAvportIdx::HDMI_0; + case audio_avport::hdmi_1: + return RsxaudioAvportIdx::HDMI_1; + case audio_avport::avmulti: + return RsxaudioAvportIdx::AVMULTI; + case audio_avport::spdif_0: + return RsxaudioAvportIdx::SPDIF_0; + case audio_avport::spdif_1: + return RsxaudioAvportIdx::SPDIF_1; + default: { + fmt::throw_exception("Invalid RSXAudio avport: %u", + static_cast(avport)); + } + } +} + +void rsxaudio_backend_thread::backend_init(const rsxaudio_state &ra_state, + const emu_audio_cfg &emu_cfg, + bool reset_backend) { + if (reset_backend || !backend) { + backend = nullptr; + backend = Emu.GetCallbacks().get_audio(); + backend->SetWriteCallback( + std::bind(&rsxaudio_backend_thread::write_data_callback, this, + std::placeholders::_1, std::placeholders::_2)); + backend->SetStateCallback( + std::bind(&rsxaudio_backend_thread::state_changed_callback, this, + std::placeholders::_1)); + } + + const port_config &port_cfg = ra_state.port[static_cast(emu_cfg.avport)]; + const AudioSampleSize sample_size = + emu_cfg.convert_to_s16 ? AudioSampleSize::S16 : AudioSampleSize::FLOAT; + const AudioChannelCnt ch_cnt = static_cast(std::min( + static_cast(port_cfg.ch_cnt), static_cast(emu_cfg.channels))); + + f64 cb_frame_len = 0.0; + audio_channel_layout backend_channel_layout = audio_channel_layout::stereo; + + if (backend->Open(emu_cfg.audio_device, port_cfg.freq, sample_size, ch_cnt, + emu_cfg.channel_layout)) { + cb_frame_len = backend->GetCallbackFrameLen(); + backend_channel_layout = backend->get_channel_layout(); + sys_rsxaudio.notice("Opened audio backend (sampling_rate=%d, " + "sample_size=%d, channels=%d, layout=%s)", + backend->get_sampling_rate(), + backend->get_sample_size(), backend->get_channels(), + backend->get_channel_layout()); + } else { + sys_rsxaudio.error( + "Failed to open audio backend. Make sure that no other application is " + "running that might block audio access (e.g. Netflix)."); + } + + static constexpr f64 _10ms = 512.0 / 48000.0; + const f64 buffering_len = emu_cfg.buffering_enabled + ? (emu_cfg.desired_buffer_duration / 1000.0) + : 0.0; + const u64 bytes_per_sec = static_cast(AudioSampleSize::FLOAT) * + static_cast(port_cfg.ch_cnt) * + static_cast(port_cfg.freq); + + { + std::lock_guard lock(ringbuf_mutex); + use_aux_ringbuf = emu_cfg.enable_time_stretching || emu_cfg.dump_to_file; + + if (use_aux_ringbuf) { + const f64 frame_len = + std::max(buffering_len * 0.5, SERVICE_PERIOD_SEC) + + cb_frame_len + _10ms; + const u64 frame_len_bytes = + static_cast(std::round(frame_len * bytes_per_sec)); + aux_ringbuf.set_buf_size(frame_len_bytes); + ringbuf.set_buf_size(frame_len_bytes); + thread_tmp_buf.resize(frame_len_bytes); + } else { + const f64 frame_len = std::max(buffering_len, cb_frame_len) + _10ms; + ringbuf.set_buf_size( + static_cast(std::round(frame_len * bytes_per_sec))); + thread_tmp_buf.resize(0); + } + + callback_tmp_buf.resize(static_cast( + (cb_frame_len + _10ms) * static_cast(AudioSampleSize::FLOAT) * + static_cast(port_cfg.ch_cnt) * static_cast(port_cfg.freq))); + } + + callback_cfg.atomic_op([&](callback_config &val) { + val.callback_active = false; // Backend may take some time to activate. This + // prevents overflows on input side. + + if (!val.cfg_changed) { + val.freq = static_cast(port_cfg.freq); + val.input_ch_cnt = static_cast(port_cfg.ch_cnt); + val.output_channel_layout = static_cast(backend_channel_layout); + val.convert_to_s16 = emu_cfg.convert_to_s16; + val.avport_idx = emu_cfg.avport; + val.ready = true; + } + }); +} + +void rsxaudio_backend_thread::backend_start() { + ensure(backend != nullptr); + + if (use_aux_ringbuf) { + resampler.set_tempo(RESAMPLER_MAX_FREQ_VAL); + resampler.flush(); + aux_ringbuf.reader_flush(); + } + + ringbuf.reader_flush(); + backend->Play(); +} + +void rsxaudio_backend_thread::backend_stop() { + if (backend == nullptr) { + return; + } + + backend->Pause(); + callback_cfg.atomic_op( + [&](callback_config &val) { val.callback_active = false; }); +} + +bool rsxaudio_backend_thread::backend_playing() { + if (backend == nullptr) { + return false; + } + + return backend->IsPlaying(); +} + +u32 rsxaudio_backend_thread::write_data_callback(u32 bytes, void *buf) { + const callback_config cb_cfg = + callback_cfg.atomic_op([&](callback_config &val) { + val.callback_active = true; + return val; + }); + + const std::bitset mute_state{cb_cfg.mute_state}; + + if (cb_cfg.ready && !mute_state[static_cast(cb_cfg.avport_idx)] && + Emu.IsRunning()) { + const audio_channel_layout output_channel_layout = + static_cast(cb_cfg.output_channel_layout); + const u32 output_ch_cnt = + AudioBackend::default_layout_channel_count(output_channel_layout); + const u32 bytes_ch_adjusted = bytes / output_ch_cnt * cb_cfg.input_ch_cnt; + const u32 bytes_from_rb = cb_cfg.convert_to_s16 + ? bytes_ch_adjusted / + static_cast(AudioSampleSize::S16) * + static_cast(AudioSampleSize::FLOAT) + : bytes_ch_adjusted; + + ensure(callback_tmp_buf.size() * static_cast(AudioSampleSize::FLOAT) >= + bytes_from_rb); + + const u32 byte_cnt = static_cast( + ringbuf.pop(callback_tmp_buf.data(), bytes_from_rb, true)); + const u32 sample_cnt = byte_cnt / static_cast(AudioSampleSize::FLOAT); + const u32 sample_cnt_out = sample_cnt / cb_cfg.input_ch_cnt * output_ch_cnt; + + // Buffer is in weird state - drop acquired data + if (sample_cnt == 0 || sample_cnt % cb_cfg.input_ch_cnt != 0) { + memset(buf, 0, bytes); + return bytes; + } + + // Record audio if enabled + if (g_recording_mode != recording_mode::stopped) { + utils::video_provider &provider = g_fxo->get(); + provider.present_samples(reinterpret_cast(callback_tmp_buf.data()), + sample_cnt / cb_cfg.input_ch_cnt, + cb_cfg.input_ch_cnt); + } + + // Downmix if necessary + AudioBackend::downmix(sample_cnt, cb_cfg.input_ch_cnt, + output_channel_layout, callback_tmp_buf.data(), + callback_tmp_buf.data()); + + if (cb_cfg.target_volume != cb_cfg.current_volume) { + const AudioBackend::VolumeParam param = { + .initial_volume = + cb_cfg.initial_volume * callback_config::VOL_NOMINAL_INV, + .current_volume = + cb_cfg.current_volume * callback_config::VOL_NOMINAL_INV, + .target_volume = + cb_cfg.target_volume * callback_config::VOL_NOMINAL_INV, + .freq = cb_cfg.freq, + .ch_cnt = cb_cfg.input_ch_cnt}; + + const u16 new_vol = static_cast( + std::round(AudioBackend::apply_volume(param, sample_cnt_out, + callback_tmp_buf.data(), + callback_tmp_buf.data()) * + callback_config::VOL_NOMINAL)); + callback_cfg.atomic_op([&](callback_config &val) { + if (val.target_volume != cb_cfg.target_volume) { + val.initial_volume = new_vol; + } + + // We don't care about proper volume adjustment if underflow has occured + val.current_volume = + bytes_from_rb != byte_cnt ? val.target_volume : new_vol; + }); + } else if (cb_cfg.current_volume != callback_config::VOL_NOMINAL) { + AudioBackend::apply_volume_static( + cb_cfg.current_volume * callback_config::VOL_NOMINAL_INV, + sample_cnt_out, callback_tmp_buf.data(), callback_tmp_buf.data()); + } + + if (cb_cfg.convert_to_s16) { + AudioBackend::convert_to_s16(sample_cnt_out, callback_tmp_buf.data(), + buf); + return sample_cnt_out * static_cast(AudioSampleSize::S16); + } + + AudioBackend::normalize(sample_cnt_out, callback_tmp_buf.data(), + static_cast(buf)); + return sample_cnt_out * static_cast(AudioSampleSize::FLOAT); + } + + ringbuf.reader_flush(); + memset(buf, 0, bytes); + return bytes; +} + +void rsxaudio_backend_thread::state_changed_callback(AudioStateEvent event) { + { + std::lock_guard lock(state_update_m); + switch (event) { + case AudioStateEvent::UNSPECIFIED_ERROR: { + backend_error_occured = true; + break; + } + case AudioStateEvent::DEFAULT_DEVICE_MAYBE_CHANGED: { + backend_device_changed = true; + break; + } + default: { + fmt::throw_exception("Unknown audio state event"); + } + } + } + state_update_c.notify_all(); +} + +u64 rsxaudio_backend_thread::get_time_until_service() { + const u64 next_service_time = start_time + time_period_idx * SERVICE_PERIOD; + const u64 current_time = get_system_time(); + + return next_service_time >= current_time ? next_service_time - current_time + : 0; +} + +void rsxaudio_backend_thread::update_service_time() { + if (get_time_until_service() <= SERVICE_THRESHOLD) + time_period_idx++; +} + +void rsxaudio_backend_thread::reset_service_time() { + start_time = get_system_time(); + time_period_idx = 1; +} + +void rsxaudio_periodic_tmr::sched_timer() { + u64 interval = get_rel_next_time(); + + if (interval == 0) { + zero_period = true; + } else if (interval == UINT64_MAX) { + interval = 0; + zero_period = false; + } else { + zero_period = false; + } + +#if defined(_WIN32) + if (interval) { + LARGE_INTEGER due_time{}; + due_time.QuadPart = -static_cast(interval * 10); + ensure(SetWaitableTimerEx(timer_handle, &due_time, 0, nullptr, nullptr, + nullptr, 0)); + } else { + ensure(CancelWaitableTimer(timer_handle)); + } +#elif defined(__linux__) + const time_t secs = interval / 1'000'000; + const long nsecs = (interval - secs * 1'000'000) * 1000; + const itimerspec tspec = {{}, {secs, nsecs}}; + ensure(timerfd_settime(timer_handle, 0, &tspec, nullptr) == 0); +#elif defined(BSD) || defined(__APPLE__) + handle[TIMER_ID].data = interval * 1000; + if (interval) { + handle[TIMER_ID].flags = (handle[TIMER_ID].flags & ~EV_DISABLE) | EV_ENABLE; + } else { + handle[TIMER_ID].flags = (handle[TIMER_ID].flags & ~EV_ENABLE) | EV_DISABLE; + } + ensure(kevent(kq, &handle[TIMER_ID], 1, nullptr, 0, nullptr) >= 0); +#else +#error "Implement" +#endif +} + +void rsxaudio_periodic_tmr::cancel_timer_unlocked() { + zero_period = false; + +#if defined(_WIN32) + ensure(CancelWaitableTimer(timer_handle)); + if (in_wait) { + ensure(SetEvent(cancel_event)); + } +#elif defined(__linux__) + const itimerspec tspec{}; + ensure(timerfd_settime(timer_handle, 0, &tspec, nullptr) == 0); + if (in_wait) { + const u64 flag = 1; + const auto wr_res = write(cancel_event, &flag, sizeof(flag)); + ensure(wr_res == sizeof(flag) || wr_res == -EAGAIN); + } +#elif defined(BSD) || defined(__APPLE__) + handle[TIMER_ID].flags = (handle[TIMER_ID].flags & ~EV_ENABLE) | EV_DISABLE; + handle[TIMER_ID].data = 0; + if (in_wait) { + ensure(kevent(kq, handle, 2, nullptr, 0, nullptr) >= 0); + } else { + ensure(kevent(kq, &handle[TIMER_ID], 1, nullptr, 0, nullptr) >= 0); + } +#else +#error "Implement" +#endif +} + +void rsxaudio_periodic_tmr::reset_cancel_flag() { +#if defined(_WIN32) + ensure(ResetEvent(cancel_event)); +#elif defined(__linux__) + u64 tmp_buf{}; + [[maybe_unused]] const auto nread = + read(cancel_event, &tmp_buf, sizeof(tmp_buf)); +#elif defined(BSD) || defined(__APPLE__) + // Cancel event is reset automatically +#else +#error "Implement" +#endif +} + +rsxaudio_periodic_tmr::rsxaudio_periodic_tmr() { +#if defined(_WIN32) + ensure(cancel_event = CreateEvent(nullptr, false, false, nullptr)); + ensure(timer_handle = CreateWaitableTimer(nullptr, false, nullptr)); +#elif defined(__linux__) + timer_handle = timerfd_create(CLOCK_MONOTONIC, 0); + ensure((epoll_fd = epoll_create(2)) >= 0); + epoll_event evnt{EPOLLIN, {}}; + evnt.data.fd = timer_handle; + ensure(timer_handle >= 0 && + epoll_ctl(epoll_fd, EPOLL_CTL_ADD, timer_handle, &evnt) == 0); + cancel_event = eventfd(0, EFD_NONBLOCK); + evnt.data.fd = cancel_event; + ensure(cancel_event >= 0 && + epoll_ctl(epoll_fd, EPOLL_CTL_ADD, cancel_event, &evnt) == 0); +#elif defined(BSD) || defined(__APPLE__) + +#if defined(__APPLE__) + static constexpr unsigned int TMR_CFG = NOTE_NSECONDS | NOTE_CRITICAL; +#else + static constexpr unsigned int TMR_CFG = NOTE_NSECONDS; +#endif + + ensure((kq = kqueue()) >= 0); + EV_SET(&handle[TIMER_ID], TIMER_ID, EVFILT_TIMER, + EV_ADD | EV_ENABLE | EV_ONESHOT, TMR_CFG, 0, nullptr); + EV_SET(&handle[CANCEL_ID], CANCEL_ID, EVFILT_USER, + EV_ADD | EV_ENABLE | EV_CLEAR, NOTE_FFNOP, 0, nullptr); + ensure(kevent(kq, &handle[CANCEL_ID], 1, nullptr, 0, nullptr) >= 0); + handle[CANCEL_ID].fflags |= NOTE_TRIGGER; +#else +#error "Implement" +#endif +} + +rsxaudio_periodic_tmr::~rsxaudio_periodic_tmr() { +#if defined(_WIN32) + CloseHandle(timer_handle); + CloseHandle(cancel_event); +#elif defined(__linux__) + close(epoll_fd); + close(timer_handle); + close(cancel_event); +#elif defined(BSD) || defined(__APPLE__) + close(kq); +#else +#error "Implement" +#endif +} + +rsxaudio_periodic_tmr::wait_result +rsxaudio_periodic_tmr::wait(const std::function &callback) { + std::unique_lock lock(mutex); + + if (in_wait || !callback) { + return wait_result::INVALID_PARAM; + } + + in_wait = true; + + bool tmr_error = false; + bool timeout = false; + bool wait_canceled = false; + + if (!zero_period) { + lock.unlock(); + constexpr u8 obj_wait_cnt = 2; + +#if defined(_WIN32) + const HANDLE wait_arr[obj_wait_cnt] = {timer_handle, cancel_event}; + const auto wait_status = + WaitForMultipleObjects(obj_wait_cnt, wait_arr, false, INFINITE); + + if (wait_status == WAIT_FAILED || + (wait_status >= WAIT_ABANDONED_0 && + wait_status < WAIT_ABANDONED_0 + obj_wait_cnt)) { + tmr_error = true; + } else if (wait_status == WAIT_TIMEOUT) { + timeout = true; + } else if (wait_status == WAIT_OBJECT_0 + 1) { + wait_canceled = true; + } +#elif defined(__linux__) + epoll_event event[obj_wait_cnt]{}; + int wait_status = 0; + do { + wait_status = epoll_wait(epoll_fd, event, obj_wait_cnt, -1); + } while (wait_status == -1 && errno == EINTR); + + if (wait_status < 0 || wait_status > obj_wait_cnt) { + tmr_error = true; + } else if (wait_status == 0) { + timeout = true; + } else { + for (int i = 0; i < wait_status; i++) { + if (event[i].data.fd == cancel_event) { + wait_canceled = true; + break; + } + } + } +#elif defined(BSD) || defined(__APPLE__) + struct kevent event[obj_wait_cnt]{}; + int wait_status = 0; + do { + wait_status = kevent(kq, nullptr, 0, event, obj_wait_cnt, nullptr); + } while (wait_status == -1 && errno == EINTR); + + if (wait_status < 0 || wait_status > obj_wait_cnt) { + tmr_error = true; + } else if (wait_status == 0) { + timeout = true; + } else { + for (int i = 0; i < wait_status; i++) { + if (event[i].ident == CANCEL_ID) { + wait_canceled = true; + break; + } + } + } +#else +#error "Implement" +#endif + lock.lock(); + } else { + zero_period = false; + } + + in_wait = false; + + if (wait_canceled) { + reset_cancel_flag(); + } + + if (tmr_error) { + return wait_result::TIMER_ERROR; + } + + if (timeout) { + return wait_result::TIMEOUT; + } + + if (wait_canceled) { + sched_timer(); + return wait_result::TIMER_CANCELED; + } + + callback(); + sched_timer(); + return wait_result::SUCCESS; +} + +u64 rsxaudio_periodic_tmr::get_rel_next_time() { + const u64 crnt_time = get_system_time(); + u64 next_time = UINT64_MAX; + + for (vtimer &vtimer : vtmr_pool) { + if (!vtimer.active) + continue; + + u64 next_blk_time = static_cast(vtimer.blk_cnt * vtimer.blk_time); + + if (crnt_time >= next_blk_time) { + const u64 crnt_blk = get_crnt_blk(crnt_time, vtimer.blk_time); + + if (crnt_blk > vtimer.blk_cnt + MAX_BURST_PERIODS) { + vtimer.blk_cnt = std::max(vtimer.blk_cnt, crnt_blk - MAX_BURST_PERIODS); + next_blk_time = static_cast(vtimer.blk_cnt * vtimer.blk_time); + } + } + + if (crnt_time >= next_blk_time) { + next_time = 0; + } else { + next_time = std::min(next_time, next_blk_time - crnt_time); + } + } + + return next_time; +} + +void rsxaudio_periodic_tmr::cancel_wait() { + std::lock_guard lock(mutex); + cancel_timer_unlocked(); +} + +void rsxaudio_periodic_tmr::enable_vtimer(u32 vtimer_id, u32 rate, + u64 crnt_time) { + ensure(vtimer_id < VTIMER_MAX && rate); + + vtimer &vtimer = vtmr_pool[vtimer_id]; + const f64 new_blk_time = get_blk_time(rate); + + // Avoid timer reset when possible + if (!vtimer.active || new_blk_time != vtimer.blk_time) { + vtimer.blk_cnt = get_crnt_blk(crnt_time, new_blk_time); + } + + vtimer.blk_time = new_blk_time; + vtimer.active = true; +} + +void rsxaudio_periodic_tmr::disable_vtimer(u32 vtimer_id) { + ensure(vtimer_id < VTIMER_MAX); + + vtimer &vtimer = vtmr_pool[vtimer_id]; + vtimer.active = false; +} + +bool rsxaudio_periodic_tmr::is_vtimer_behind(u32 vtimer_id, + u64 crnt_time) const { + ensure(vtimer_id < VTIMER_MAX); + + const vtimer &vtimer = vtmr_pool[vtimer_id]; + + return is_vtimer_behind(vtimer, crnt_time); +} + +void rsxaudio_periodic_tmr::vtimer_skip_periods(u32 vtimer_id, u64 crnt_time) { + ensure(vtimer_id < VTIMER_MAX); + + vtimer &vtimer = vtmr_pool[vtimer_id]; + + if (is_vtimer_behind(vtimer, crnt_time)) { + vtimer.blk_cnt = get_crnt_blk(crnt_time, vtimer.blk_time); + } +} + +void rsxaudio_periodic_tmr::vtimer_incr(u32 vtimer_id, u64 crnt_time) { + ensure(vtimer_id < VTIMER_MAX); + + vtimer &vtimer = vtmr_pool[vtimer_id]; + + if (is_vtimer_behind(vtimer, crnt_time)) { + vtimer.blk_cnt++; + } +} + +bool rsxaudio_periodic_tmr::is_vtimer_active(u32 vtimer_id) const { + ensure(vtimer_id < VTIMER_MAX); + + const vtimer &vtimer = vtmr_pool[vtimer_id]; + + return vtimer.active; +} + +u64 rsxaudio_periodic_tmr::vtimer_get_sched_time(u32 vtimer_id) const { + ensure(vtimer_id < VTIMER_MAX); + + const vtimer &vtimer = vtmr_pool[vtimer_id]; + + return static_cast(vtimer.blk_cnt * vtimer.blk_time); +} + +bool rsxaudio_periodic_tmr::is_vtimer_behind(const vtimer &vtimer, + u64 crnt_time) const { + return vtimer.active && + vtimer.blk_cnt < get_crnt_blk(crnt_time, vtimer.blk_time); +} + +u64 rsxaudio_periodic_tmr::get_crnt_blk(u64 crnt_time, f64 blk_time) const { + return static_cast(std::floor(static_cast(crnt_time) / blk_time)) + + 1; +} + +f64 rsxaudio_periodic_tmr::get_blk_time(u32 data_rate) const { + return static_cast(SYS_RSXAUDIO_STREAM_SIZE * 1'000'000) / data_rate; +} diff --git a/kernel/cellos/src/sys_rwlock.cpp b/kernel/cellos/src/sys_rwlock.cpp new file mode 100644 index 000000000..c6cdef964 --- /dev/null +++ b/kernel/cellos/src/sys_rwlock.cpp @@ -0,0 +1,496 @@ +#include "stdafx.h" + +#include "sys_rwlock.h" + +#include "Emu/IdManager.h" + +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/Cell/PPUThread.h" + +#include "util/asm.hpp" + +LOG_CHANNEL(sys_rwlock); + +lv2_rwlock::lv2_rwlock(utils::serial &ar) : protocol(ar), key(ar), name(ar) { + ar(owner); +} + +std::function lv2_rwlock::load(utils::serial &ar) { + return load_func(make_shared(exact_t(ar))); +} + +void lv2_rwlock::save(utils::serial &ar) { + USING_SERIALIZATION_VERSION(lv2_sync); + ar(protocol, key, name, owner); +} + +error_code sys_rwlock_create(ppu_thread &ppu, vm::ptr rw_lock_id, + vm::ptr attr) { + ppu.state += cpu_flag::wait; + + sys_rwlock.warning("sys_rwlock_create(rw_lock_id=*0x%x, attr=*0x%x)", + rw_lock_id, attr); + + if (!rw_lock_id || !attr) { + return CELL_EFAULT; + } + + const auto _attr = *attr; + + const u32 protocol = _attr.protocol; + + if (protocol != SYS_SYNC_FIFO && protocol != SYS_SYNC_PRIORITY) { + sys_rwlock.error("sys_rwlock_create(): unknown protocol (0x%x)", protocol); + return CELL_EINVAL; + } + + const u64 ipc_key = lv2_obj::get_key(_attr); + + if (auto error = + lv2_obj::create(_attr.pshared, ipc_key, _attr.flags, [&] { + return make_shared(protocol, ipc_key, _attr.name_u64); + })) { + return error; + } + + ppu.check_state(); + *rw_lock_id = idm::last_id(); + return CELL_OK; +} + +error_code sys_rwlock_destroy(ppu_thread &ppu, u32 rw_lock_id) { + ppu.state += cpu_flag::wait; + + sys_rwlock.warning("sys_rwlock_destroy(rw_lock_id=0x%x)", rw_lock_id); + + const auto rwlock = idm::withdraw( + rw_lock_id, [](lv2_rwlock &rw) -> CellError { + if (rw.owner) { + return CELL_EBUSY; + } + + lv2_obj::on_id_destroy(rw, rw.key); + return {}; + }); + + if (!rwlock) { + return CELL_ESRCH; + } + + if (rwlock.ret) { + return rwlock.ret; + } + + return CELL_OK; +} + +error_code sys_rwlock_rlock(ppu_thread &ppu, u32 rw_lock_id, u64 timeout) { + ppu.state += cpu_flag::wait; + + sys_rwlock.trace("sys_rwlock_rlock(rw_lock_id=0x%x, timeout=0x%llx)", + rw_lock_id, timeout); + + const auto rwlock = idm::get( + rw_lock_id, [&, notify = lv2_obj::notify_all_t()](lv2_rwlock &rwlock) { + const s64 val = rwlock.owner; + + if (val <= 0 && !(val & 1)) { + if (rwlock.owner.compare_and_swap_test(val, val - 2)) { + return true; + } + } + + lv2_obj::prepare_for_sleep(ppu); + + std::lock_guard lock(rwlock.mutex); + + const s64 _old = rwlock.owner.fetch_op([&](s64 &val) { + if (val <= 0 && !(val & 1)) { + val -= 2; + } else { + val |= 1; + } + }); + + if (_old > 0 || _old & 1) { + rwlock.sleep(ppu, timeout); + lv2_obj::emplace(rwlock.rq, &ppu); + return false; + } + + return true; + }); + + if (!rwlock) { + return CELL_ESRCH; + } + + if (rwlock.ret) { + return CELL_OK; + } + + ppu.gpr[3] = CELL_OK; + + while (auto state = +ppu.state) { + if (state & cpu_flag::signal && + ppu.state.test_and_reset(cpu_flag::signal)) { + break; + } + + if (is_stopped(state)) { + std::lock_guard lock(rwlock->mutex); + + for (auto cpu = +rwlock->rq; cpu; cpu = cpu->next_cpu) { + if (cpu == &ppu) { + ppu.state += cpu_flag::again; + return {}; + } + } + + break; + } + + for (usz i = 0; cpu_flag::signal - ppu.state && i < 50; i++) { + busy_wait(500); + } + + if (ppu.state & cpu_flag::signal) { + continue; + } + + if (timeout) { + if (lv2_obj::wait_timeout(timeout, &ppu)) { + // Wait for rescheduling + if (ppu.check_state()) { + continue; + } + + ppu.state += cpu_flag::wait; + + if (!atomic_storage::load(rwlock->rq)) { + // Waiters queue is empty, so the thread must have been signaled + rwlock->mutex.lock_unlock(); + break; + } + + std::lock_guard lock(rwlock->mutex); + + if (!rwlock->unqueue(rwlock->rq, &ppu)) { + break; + } + + ppu.gpr[3] = CELL_ETIMEDOUT; + break; + } + } else { + ppu.state.wait(state); + } + } + + return not_an_error(ppu.gpr[3]); +} + +error_code sys_rwlock_tryrlock(ppu_thread &ppu, u32 rw_lock_id) { + ppu.state += cpu_flag::wait; + + sys_rwlock.trace("sys_rwlock_tryrlock(rw_lock_id=0x%x)", rw_lock_id); + + const auto rwlock = + idm::check(rw_lock_id, [](lv2_rwlock &rwlock) { + auto [_, ok] = rwlock.owner.fetch_op([](s64 &val) { + if (val <= 0 && !(val & 1)) { + val -= 2; + return true; + } + + return false; + }); + + return ok; + }); + + if (!rwlock) { + return CELL_ESRCH; + } + + if (!rwlock.ret) { + return not_an_error(CELL_EBUSY); + } + + return CELL_OK; +} + +error_code sys_rwlock_runlock(ppu_thread &ppu, u32 rw_lock_id) { + ppu.state += cpu_flag::wait; + + sys_rwlock.trace("sys_rwlock_runlock(rw_lock_id=0x%x)", rw_lock_id); + + const auto rwlock = + idm::get(rw_lock_id, [](lv2_rwlock &rwlock) { + const s64 val = rwlock.owner; + + if (val < 0 && !(val & 1)) { + if (rwlock.owner.compare_and_swap_test(val, val + 2)) { + return true; + } + } + + return false; + }); + + if (!rwlock) { + return CELL_ESRCH; + } + + lv2_obj::notify_all_t notify; + + if (rwlock.ret) { + return CELL_OK; + } else { + std::lock_guard lock(rwlock->mutex); + + // Remove one reader + const s64 _old = rwlock->owner.fetch_op([](s64 &val) { + if (val < -1) { + val += 2; + } + }); + + if (_old >= 0) { + return CELL_EPERM; + } + + if (_old == -1) { + if (const auto cpu = + rwlock->schedule(rwlock->wq, rwlock->protocol)) { + if (static_cast(cpu)->state & cpu_flag::again) { + ppu.state += cpu_flag::again; + return {}; + } + + rwlock->owner = cpu->id << 1 | !!rwlock->wq | !!rwlock->rq; + + rwlock->awake(cpu); + } else { + rwlock->owner = 0; + + ensure(!rwlock->rq); + } + } + } + + return CELL_OK; +} + +error_code sys_rwlock_wlock(ppu_thread &ppu, u32 rw_lock_id, u64 timeout) { + ppu.state += cpu_flag::wait; + + sys_rwlock.trace("sys_rwlock_wlock(rw_lock_id=0x%x, timeout=0x%llx)", + rw_lock_id, timeout); + + const auto rwlock = idm::get( + rw_lock_id, + [&, notify = lv2_obj::notify_all_t()](lv2_rwlock &rwlock) -> s64 { + const s64 val = rwlock.owner; + + if (val == 0) { + if (rwlock.owner.compare_and_swap_test(0, ppu.id << 1)) { + return 0; + } + } else if (val >> 1 == ppu.id) { + return val; + } + + lv2_obj::prepare_for_sleep(ppu); + + std::lock_guard lock(rwlock.mutex); + + const s64 _old = rwlock.owner.fetch_op([&](s64 &val) { + if (val == 0) { + val = ppu.id << 1; + } else { + val |= 1; + } + }); + + if (_old != 0) { + rwlock.sleep(ppu, timeout); + lv2_obj::emplace(rwlock.wq, &ppu); + } + + return _old; + }); + + if (!rwlock) { + return CELL_ESRCH; + } + + if (rwlock.ret == 0) { + return CELL_OK; + } + + if (rwlock.ret >> 1 == ppu.id) { + return CELL_EDEADLK; + } + + ppu.gpr[3] = CELL_OK; + + while (auto state = +ppu.state) { + if (state & cpu_flag::signal && + ppu.state.test_and_reset(cpu_flag::signal)) { + break; + } + + if (is_stopped(state)) { + std::lock_guard lock(rwlock->mutex); + + for (auto cpu = +rwlock->wq; cpu; cpu = cpu->next_cpu) { + if (cpu == &ppu) { + ppu.state += cpu_flag::again; + return {}; + } + } + + break; + } + + for (usz i = 0; cpu_flag::signal - ppu.state && i < 50; i++) { + busy_wait(500); + } + + if (ppu.state & cpu_flag::signal) { + continue; + } + + if (timeout) { + if (lv2_obj::wait_timeout(timeout, &ppu)) { + // Wait for rescheduling + if (ppu.check_state()) { + continue; + } + + std::lock_guard lock(rwlock->mutex); + + if (!rwlock->unqueue(rwlock->wq, &ppu)) { + break; + } + + // If the last waiter quit the writer sleep queue, wake blocked readers + if (rwlock->rq && !rwlock->wq && rwlock->owner < 0) { + s64 size = 0; + + // Protocol doesn't matter here since they are all enqueued anyways + while (auto cpu = + rwlock->schedule(rwlock->rq, SYS_SYNC_FIFO)) { + size++; + rwlock->append(cpu); + } + + rwlock->owner.atomic_op([&](s64 &owner) { + owner -= 2 * size; // Add readers to value + owner &= -2; // Clear wait bit + }); + + lv2_obj::awake_all(); + } else if (!rwlock->rq && !rwlock->wq) { + rwlock->owner &= -2; + } + + ppu.gpr[3] = CELL_ETIMEDOUT; + break; + } + } else { + ppu.state.wait(state); + } + } + + return not_an_error(ppu.gpr[3]); +} + +error_code sys_rwlock_trywlock(ppu_thread &ppu, u32 rw_lock_id) { + ppu.state += cpu_flag::wait; + + sys_rwlock.trace("sys_rwlock_trywlock(rw_lock_id=0x%x)", rw_lock_id); + + const auto rwlock = + idm::check(rw_lock_id, [&](lv2_rwlock &rwlock) { + const s64 val = rwlock.owner; + + // Return previous value + return val ? val : rwlock.owner.compare_and_swap(0, ppu.id << 1); + }); + + if (!rwlock) { + return CELL_ESRCH; + } + + if (rwlock.ret != 0) { + if (rwlock.ret >> 1 == ppu.id) { + return CELL_EDEADLK; + } + + return not_an_error(CELL_EBUSY); + } + + return CELL_OK; +} + +error_code sys_rwlock_wunlock(ppu_thread &ppu, u32 rw_lock_id) { + ppu.state += cpu_flag::wait; + + sys_rwlock.trace("sys_rwlock_wunlock(rw_lock_id=0x%x)", rw_lock_id); + + const auto rwlock = + idm::get(rw_lock_id, [&](lv2_rwlock &rwlock) { + const s64 val = rwlock.owner; + + // Return previous value + return val != ppu.id << 1 ? val : rwlock.owner.compare_and_swap(val, 0); + }); + + if (!rwlock) { + return CELL_ESRCH; + } + + if (rwlock.ret >> 1 != ppu.id) { + return CELL_EPERM; + } + + if (lv2_obj::notify_all_t notify; rwlock.ret & 1) { + std::lock_guard lock(rwlock->mutex); + + if (auto cpu = rwlock->schedule(rwlock->wq, rwlock->protocol)) { + if (static_cast(cpu)->state & cpu_flag::again) { + ppu.state += cpu_flag::again; + return {}; + } + + rwlock->owner = cpu->id << 1 | !!rwlock->wq | !!rwlock->rq; + + rwlock->awake(cpu); + } else if (rwlock->rq) { + for (auto cpu = +rwlock->rq; cpu; cpu = cpu->next_cpu) { + if (cpu->state & cpu_flag::again) { + ppu.state += cpu_flag::again; + return {}; + } + } + + s64 size = 0; + + // Protocol doesn't matter here since they are all enqueued anyways + while (auto cpu = + rwlock->schedule(rwlock->rq, SYS_SYNC_FIFO)) { + size++; + rwlock->append(cpu); + } + + rwlock->owner.release(-2 * static_cast(size)); + lv2_obj::awake_all(); + } else { + rwlock->owner = 0; + } + } + + return CELL_OK; +} diff --git a/kernel/cellos/src/sys_semaphore.cpp b/kernel/cellos/src/sys_semaphore.cpp new file mode 100644 index 000000000..d24632dbe --- /dev/null +++ b/kernel/cellos/src/sys_semaphore.cpp @@ -0,0 +1,318 @@ +#include "stdafx.h" + +#include "sys_semaphore.h" + +#include "Emu/IdManager.h" + +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/Cell/PPUThread.h" + +#include "util/asm.hpp" + +LOG_CHANNEL(sys_semaphore); + +lv2_sema::lv2_sema(utils::serial &ar) + : protocol(ar), key(ar), name(ar), max(ar) { + ar(val); +} + +std::function lv2_sema::load(utils::serial &ar) { + return load_func(make_shared(exact_t(ar))); +} + +void lv2_sema::save(utils::serial &ar) { + USING_SERIALIZATION_VERSION(lv2_sync); + ar(protocol, key, name, max, std::max(+val, 0)); +} + +error_code sys_semaphore_create(ppu_thread &ppu, vm::ptr sem_id, + vm::ptr attr, + s32 initial_val, s32 max_val) { + ppu.state += cpu_flag::wait; + + sys_semaphore.trace("sys_semaphore_create(sem_id=*0x%x, attr=*0x%x, " + "initial_val=%d, max_val=%d)", + sem_id, attr, initial_val, max_val); + + if (!sem_id || !attr) { + return CELL_EFAULT; + } + + if (max_val <= 0 || initial_val > max_val || initial_val < 0) { + sys_semaphore.error("sys_semaphore_create(): invalid parameters " + "(initial_val=%d, max_val=%d)", + initial_val, max_val); + return CELL_EINVAL; + } + + const auto _attr = *attr; + + const u32 protocol = _attr.protocol; + + if (protocol != SYS_SYNC_FIFO && protocol != SYS_SYNC_PRIORITY) { + sys_semaphore.error("sys_semaphore_create(): unknown protocol (0x%x)", + protocol); + return CELL_EINVAL; + } + + const u64 ipc_key = lv2_obj::get_key(_attr); + + if (ipc_key) { + sys_semaphore.warning("sys_semaphore_create(sem_id=*0x%x, attr=*0x%x, " + "initial_val=%d, max_val=%d): IPC=0x%016x", + sem_id, attr, initial_val, max_val, ipc_key); + } + + if (auto error = + lv2_obj::create(_attr.pshared, ipc_key, _attr.flags, [&] { + return make_shared(protocol, ipc_key, _attr.name_u64, + max_val, initial_val); + })) { + return error; + } + + static_cast(ppu.test_stopped()); + + *sem_id = idm::last_id(); + return CELL_OK; +} + +error_code sys_semaphore_destroy(ppu_thread &ppu, u32 sem_id) { + ppu.state += cpu_flag::wait; + + sys_semaphore.trace("sys_semaphore_destroy(sem_id=0x%x)", sem_id); + + const auto sem = + idm::withdraw(sem_id, [](lv2_sema &sema) -> CellError { + if (sema.val < 0) { + return CELL_EBUSY; + } + + lv2_obj::on_id_destroy(sema, sema.key); + return {}; + }); + + if (!sem) { + return CELL_ESRCH; + } + + if (sem->key) { + sys_semaphore.warning("sys_semaphore_destroy(sem_id=0x%x): IPC=0x%016x", + sem_id, sem->key); + } + + if (sem.ret) { + return sem.ret; + } + + return CELL_OK; +} + +error_code sys_semaphore_wait(ppu_thread &ppu, u32 sem_id, u64 timeout) { + ppu.state += cpu_flag::wait; + + sys_semaphore.trace("sys_semaphore_wait(sem_id=0x%x, timeout=0x%llx)", sem_id, + timeout); + + const auto sem = idm::get( + sem_id, [&, notify = lv2_obj::notify_all_t()](lv2_sema &sema) { + const s32 val = sema.val; + + if (val > 0) { + if (sema.val.compare_and_swap_test(val, val - 1)) { + return true; + } + } + + lv2_obj::prepare_for_sleep(ppu); + + std::lock_guard lock(sema.mutex); + + if (sema.val-- <= 0) { + sema.sleep(ppu, timeout); + lv2_obj::emplace(sema.sq, &ppu); + return false; + } + + return true; + }); + + if (!sem) { + return CELL_ESRCH; + } + + if (sem.ret) { + return CELL_OK; + } + + ppu.gpr[3] = CELL_OK; + + while (auto state = +ppu.state) { + if (state & cpu_flag::signal && + ppu.state.test_and_reset(cpu_flag::signal)) { + break; + } + + if (is_stopped(state)) { + std::lock_guard lock(sem->mutex); + + for (auto cpu = +sem->sq; cpu; cpu = cpu->next_cpu) { + if (cpu == &ppu) { + ppu.state += cpu_flag::again; + return {}; + } + } + + break; + } + + for (usz i = 0; cpu_flag::signal - ppu.state && i < 50; i++) { + busy_wait(500); + } + + if (ppu.state & cpu_flag::signal) { + continue; + } + + if (timeout) { + if (lv2_obj::wait_timeout(timeout, &ppu)) { + // Wait for rescheduling + if (ppu.check_state()) { + continue; + } + + ppu.state += cpu_flag::wait; + + std::lock_guard lock(sem->mutex); + + if (!sem->unqueue(sem->sq, &ppu)) { + break; + } + + ensure(0 > sem->val.fetch_op([](s32 &val) { + if (val < 0) { + val++; + } + })); + + ppu.gpr[3] = CELL_ETIMEDOUT; + break; + } + } else { + ppu.state.wait(state); + } + } + + return not_an_error(ppu.gpr[3]); +} + +error_code sys_semaphore_trywait(ppu_thread &ppu, u32 sem_id) { + ppu.state += cpu_flag::wait; + + sys_semaphore.trace("sys_semaphore_trywait(sem_id=0x%x)", sem_id); + + const auto sem = idm::check( + sem_id, [&](lv2_sema &sema) { return sema.val.try_dec(0); }); + + if (!sem) { + return CELL_ESRCH; + } + + if (!sem.ret) { + return not_an_error(CELL_EBUSY); + } + + return CELL_OK; +} + +error_code sys_semaphore_post(ppu_thread &ppu, u32 sem_id, s32 count) { + ppu.state += cpu_flag::wait; + + sys_semaphore.trace("sys_semaphore_post(sem_id=0x%x, count=%d)", sem_id, + count); + + const auto sem = idm::get(sem_id, [&](lv2_sema &sema) { + const s32 val = sema.val; + + if (val >= 0 && count > 0 && count <= sema.max - val) { + if (sema.val.compare_and_swap_test(val, val + count)) { + return true; + } + } + + return false; + }); + + if (!sem) { + return CELL_ESRCH; + } + + if (count <= 0) { + return CELL_EINVAL; + } + + lv2_obj::notify_all_t notify; + + if (sem.ret) { + return CELL_OK; + } else { + std::lock_guard lock(sem->mutex); + + for (auto cpu = +sem->sq; cpu; cpu = cpu->next_cpu) { + if (static_cast(cpu)->state & cpu_flag::again) { + ppu.state += cpu_flag::again; + return {}; + } + } + + const auto [val, ok] = sem->val.fetch_op([&](s32 &val) { + if (count + 0u <= sem->max + 0u - val) { + val += count; + return true; + } + + return false; + }); + + if (!ok) { + return not_an_error(CELL_EBUSY); + } + + // Wake threads + const s32 to_awake = std::min(-std::min(val, 0), count); + + for (s32 i = 0; i < to_awake; i++) { + sem->append((ensure(sem->schedule(sem->sq, sem->protocol)))); + } + + if (to_awake > 0) { + lv2_obj::awake_all(); + } + } + + return CELL_OK; +} + +error_code sys_semaphore_get_value(ppu_thread &ppu, u32 sem_id, + vm::ptr count) { + ppu.state += cpu_flag::wait; + + sys_semaphore.trace("sys_semaphore_get_value(sem_id=0x%x, count=*0x%x)", + sem_id, count); + + const auto sema = idm::check( + sem_id, [](lv2_sema &sema) { return std::max(0, sema.val); }); + + if (!sema) { + return CELL_ESRCH; + } + + if (!count) { + return CELL_EFAULT; + } + + static_cast(ppu.test_stopped()); + + *count = sema.ret; + return CELL_OK; +} diff --git a/kernel/cellos/src/sys_sm.cpp b/kernel/cellos/src/sys_sm.cpp new file mode 100644 index 000000000..c7890808d --- /dev/null +++ b/kernel/cellos/src/sys_sm.cpp @@ -0,0 +1,123 @@ +#include "stdafx.h" + +#include "Emu/System.h" + +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/Cell/PPUThread.h" +#include "cellos/sys_process.h" + +#include "sys_sm.h" + +LOG_CHANNEL(sys_sm); + +error_code sys_sm_get_params(vm::ptr a, vm::ptr b, vm::ptr c, + vm::ptr d) { + sys_sm.todo("sys_sm_get_params(a=*0x%x, b=*0x%x, c=*0x%x, d=*0x%x)", a, b, c, + d); + + if (a) + *a = 0; + else + return CELL_EFAULT; + if (b) + *b = 0; + else + return CELL_EFAULT; + if (c) + *c = 0x200; + else + return CELL_EFAULT; + if (d) + *d = 7; + else + return CELL_EFAULT; + + return CELL_OK; +} + +error_code sys_sm_get_ext_event2(vm::ptr a1, vm::ptr a2, + vm::ptr a3, u64 a4) { + sys_sm.todo( + "sys_sm_get_ext_event2(a1=*0x%x, a2=*0x%x, a3=*0x%x, a4=*0x%x, a4=0x%xll", + a1, a2, a3, a4); + + if (a4 != 0 && a4 != 1) { + return CELL_EINVAL; + } + + // a1 == 7 - 'console too hot, restart' + // a2 looks to be used if a1 is either 5 or 3? + // a3 looks to be ignored in vsh + + if (a1) + *a1 = 0; + else + return CELL_EFAULT; + if (a2) + *a2 = 0; + else + return CELL_EFAULT; + if (a3) + *a3 = 0; + else + return CELL_EFAULT; + + // eagain for no event + return not_an_error(CELL_EAGAIN); +} + +error_code sys_sm_shutdown(ppu_thread &ppu, u16 op, vm::ptr param, + u64 size) { + ppu.state += cpu_flag::wait; + + sys_sm.success("sys_sm_shutdown(op=0x%x, param=*0x%x, size=0x%x)", op, param, + size); + + if (!g_ps3_process_info.has_root_perm()) { + return CELL_ENOSYS; + } + + switch (op) { + case 0x100: + case 0x1100: { + sys_sm.success("Received shutdown request from application"); + _sys_process_exit(ppu, 0, 0, 0); + break; + } + case 0x200: + case 0x1200: { + sys_sm.success("Received reboot request from application"); + lv2_exitspawn(ppu, Emu.argv, Emu.envp, Emu.data); + break; + } + case 0x8201: + case 0x8202: + case 0x8204: { + sys_sm.warning("Unsupported LPAR operation: 0x%x", op); + return CELL_ENOTSUP; + } + default: + return CELL_EINVAL; + } + + return CELL_OK; +} + +error_code sys_sm_set_shop_mode(s32 mode) { + sys_sm.todo("sys_sm_set_shop_mode(mode=0x%x)", mode); + + return CELL_OK; +} + +error_code sys_sm_control_led(u8 led, u8 action) { + sys_sm.todo("sys_sm_control_led(led=0x%x, action=0x%x)", led, action); + + return CELL_OK; +} + +error_code sys_sm_ring_buzzer(u64 packet, u64 a1, u64 a2) { + sys_sm.todo("sys_sm_ring_buzzer(packet=0x%x, a1=0x%x, a2=0x%x)", packet, a1, + a2); + + return CELL_OK; +} diff --git a/kernel/cellos/src/sys_spu.cpp b/kernel/cellos/src/sys_spu.cpp new file mode 100644 index 000000000..8a963fe6c --- /dev/null +++ b/kernel/cellos/src/sys_spu.cpp @@ -0,0 +1,2672 @@ +#include "stdafx.h" + +#include "sys_spu.h" + +#include "Crypto/sha1.h" +#include "Crypto/unedat.h" +#include "Crypto/unself.h" +#include "Emu/IdManager.h" +#include "Emu/System.h" +#include "Loader/ELF.h" +#include "util/bin_patch.h" + +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/Cell/PPUThread.h" +#include "Emu/Cell/timers.hpp" +#include "Emu/Memory/vm_reservation.h" +#include "sys_event.h" +#include "sys_fs.h" +#include "sys_interrupt.h" +#include "sys_memory.h" +#include "sys_mmapper.h" +#include "sys_process.h" + +#include "util/asm.hpp" + +LOG_CHANNEL(sys_spu); + +template <> +void fmt_class_string::format(std::string &out, u64 arg) { + format_enum(out, arg, [](spu_group_status value) { + switch (value) { + case SPU_THREAD_GROUP_STATUS_NOT_INITIALIZED: + return "uninitialized"; + case SPU_THREAD_GROUP_STATUS_INITIALIZED: + return "initialized"; + case SPU_THREAD_GROUP_STATUS_READY: + return "ready"; + case SPU_THREAD_GROUP_STATUS_WAITING: + return "waiting"; + case SPU_THREAD_GROUP_STATUS_SUSPENDED: + return "suspended"; + case SPU_THREAD_GROUP_STATUS_WAITING_AND_SUSPENDED: + return "waiting and suspended"; + case SPU_THREAD_GROUP_STATUS_RUNNING: + return "running"; + case SPU_THREAD_GROUP_STATUS_STOPPED: + return "stopped"; + case SPU_THREAD_GROUP_STATUS_DESTROYED: + return "destroyed"; + case SPU_THREAD_GROUP_STATUS_UNKNOWN: + break; + } + + return unknown; + }); +} + +template <> +void fmt_class_string::format(std::string &out, u64 arg) { + format_enum(out, arg, [](spu_stop_syscall value) { + switch (value) { + case SYS_SPU_THREAD_STOP_YIELD: + return "sys_spu_thread_yield"; + case SYS_SPU_THREAD_STOP_GROUP_EXIT: + return "sys_spu_thread_group_exit"; + case SYS_SPU_THREAD_STOP_THREAD_EXIT: + return "sys_spu_thread_thread_exit"; + case SYS_SPU_THREAD_STOP_RECEIVE_EVENT: + return "sys_spu_thread_receive_event"; + case SYS_SPU_THREAD_STOP_TRY_RECEIVE_EVENT: + return "sys_spu_thread_tryreceive_event"; + case SYS_SPU_THREAD_STOP_SWITCH_SYSTEM_MODULE: + return "sys_spu_thread_switch_system_module"; + } + + return unknown; + }); +} + +void sys_spu_image::load(const fs::file &stream) { + const spu_exec_object obj{stream, 0, elf_opt::no_sections + elf_opt::no_data}; + + if (obj != elf_error::ok) { + fmt::throw_exception("Failed to load SPU image: %s", obj.get_error()); + } + + for (const auto &shdr : obj.shdrs) { + spu_log.notice( + "** Section: sh_type=0x%x, addr=0x%llx, size=0x%llx, flags=0x%x", + std::bit_cast(shdr.sh_type), shdr.sh_addr, shdr.sh_size, + shdr._sh_flags); + } + + for (const auto &prog : obj.progs) { + spu_log.notice("** Segment: p_type=0x%x, p_vaddr=0x%llx, p_filesz=0x%llx, " + "p_memsz=0x%llx, flags=0x%x", + prog.p_type, prog.p_vaddr, prog.p_filesz, prog.p_memsz, + prog.p_flags); + + if (prog.p_type != u32{SYS_SPU_SEGMENT_TYPE_COPY} && + prog.p_type != u32{SYS_SPU_SEGMENT_TYPE_INFO}) { + spu_log.error("Unknown program type (0x%x)", prog.p_type); + } + } + + this->type = SYS_SPU_IMAGE_TYPE_KERNEL; + const s32 nsegs = sys_spu_image::get_nsegs(obj.progs); + + const u32 mem_size = nsegs * sizeof(sys_spu_segment) + ::size32(stream); + const vm::ptr segs = vm::cast(vm::alloc(mem_size, vm::main)); + + // const u32 entry = obj.header.e_entry; + + const u32 src = (segs + nsegs).addr(); + + stream.seek(0); + stream.read(vm::base(src), stream.size()); + + if (nsegs <= 0 || nsegs > 0x20 || + sys_spu_image::fill(segs, nsegs, obj.progs, src) != nsegs) { + fmt::throw_exception("Failed to load SPU segments (%d)", nsegs); + } + + // Write ID and save entry + this->entry_point = + idm::make(+obj.header.e_entry, segs, nsegs); + + // Unused and set to 0 + this->nsegs = 0; + this->segs = vm::null; + + vm::page_protect(segs.addr(), utils::align(mem_size, 4096), 0, 0, + vm::page_writable); +} + +void sys_spu_image::free() const { + if (type == SYS_SPU_IMAGE_TYPE_KERNEL) { + // TODO: Remove, should be handled by syscalls + ensure(vm::dealloc(segs.addr(), vm::main)); + } +} + +void sys_spu_image::deploy(u8 *loc, std::span segs, + bool is_verbose) { + // Segment info dump + std::string dump; + + // Executable hash + sha1_context sha; + sha1_starts(&sha); + u8 sha1_hash[20]; + + for (const auto &seg : segs) { + fmt::append(dump, "\n\t[%u] t=0x%x, ls=0x%x, size=0x%x, addr=0x%x", + &seg - segs.data(), seg.type, seg.ls, seg.size, seg.addr); + + sha1_update(&sha, reinterpret_cast(&seg.type), + sizeof(seg.type)); + + // Hash big-endian values + if (seg.type == SYS_SPU_SEGMENT_TYPE_COPY) { + std::memcpy(loc + seg.ls, vm::base(seg.addr), seg.size); + sha1_update(&sha, reinterpret_cast(&seg.size), + sizeof(seg.size)); + sha1_update(&sha, reinterpret_cast(&seg.ls), + sizeof(seg.ls)); + sha1_update(&sha, vm::_ptr(seg.addr), seg.size); + } else if (seg.type == SYS_SPU_SEGMENT_TYPE_FILL) { + if ((seg.ls | seg.size) % 4) { + spu_log.error("Unaligned SPU FILL type segment (ls=0x%x, size=0x%x)", + seg.ls, seg.size); + } + + std::fill_n(reinterpret_cast *>(loc + seg.ls), seg.size / 4, + seg.addr); + sha1_update(&sha, reinterpret_cast(&seg.size), + sizeof(seg.size)); + sha1_update(&sha, reinterpret_cast(&seg.ls), + sizeof(seg.ls)); + sha1_update(&sha, reinterpret_cast(&seg.addr), + sizeof(seg.addr)); + } else if (seg.type == SYS_SPU_SEGMENT_TYPE_INFO) { + const be_t size = seg.size + 0x14; // Workaround + sha1_update(&sha, reinterpret_cast(&size), sizeof(size)); + } + } + + sha1_finish(&sha, sha1_hash); + + // Format patch name + std::string hash("SPU-0000000000000000000000000000000000000000"); + for (u32 i = 0; i < sizeof(sha1_hash); i++) { + constexpr auto pal = "0123456789abcdef"; + hash[4 + i * 2] = pal[sha1_hash[i] >> 4]; + hash[5 + i * 2] = pal[sha1_hash[i] & 15]; + } + + auto mem_translate = [loc](u32 addr, u32 size) { + return utils::add_saturate(addr, size) <= SPU_LS_SIZE ? loc + addr + : nullptr; + }; + + // Apply the patch + std::vector applied; + g_fxo->get().apply(applied, hash, mem_translate); + + if (!Emu.GetTitleID().empty()) { + // Alternative patch + g_fxo->get().apply(applied, Emu.GetTitleID() + '-' + hash, + mem_translate); + } + + (is_verbose ? spu_log.notice : sys_spu.trace)( + "Loaded SPU image: %s (<- %u)%s", hash, applied.size(), dump); +} + +lv2_spu_group::lv2_spu_group(utils::serial &ar) noexcept + : name(ar.pop()), id(idm::last_id()), max_num(ar), + mem_size(ar), type(ar) // SPU Thread Group Type + , + ct(lv2_memory_container::search(ar)), has_scheduler_context(ar.pop()), + max_run(ar), init(ar), prio([&ar]() { + std::common_type_t prio{}; + + ar(prio.all); + + return prio; + }()), + run_state(ar.pop()), exit_status(ar) { + for (auto &thread : threads) { + if (ar.pop()) { + ar(id_manager::g_id); + thread = stx::make_shared>( + stx::launch_retainer{}, ar, this); + ensure(idm::import_existing>(thread, + idm::last_id())); + running += !thread->stop_flag_removal_protection; + } + } + + ar(threads_map); + ar(imgs); + ar(args); + + for (auto ep : {&ep_run, &ep_exception, &ep_sysmodule}) { + *ep = idm::get_unlocked(ar.pop()); + } + + waiter_spu_index = -1; + + switch (run_state) { + // Commented stuff are handled by different means currently + // case SPU_THREAD_GROUP_STATUS_NOT_INITIALIZED: + // case SPU_THREAD_GROUP_STATUS_INITIALIZED: + // case SPU_THREAD_GROUP_STATUS_READY: + case SPU_THREAD_GROUP_STATUS_WAITING: { + run_state = SPU_THREAD_GROUP_STATUS_RUNNING; + ar(waiter_spu_index); + [[fallthrough]]; + } + case SPU_THREAD_GROUP_STATUS_WAITING_AND_SUSPENDED: { + if (run_state == SPU_THREAD_GROUP_STATUS_WAITING_AND_SUSPENDED) { + run_state = SPU_THREAD_GROUP_STATUS_SUSPENDED; + } + + [[fallthrough]]; + } + case SPU_THREAD_GROUP_STATUS_SUSPENDED: { + // Suspend all SPU threads except a thread that waits on + // sys_spu_thread_receive_event + for (const auto &thread : threads) { + if (thread) { + if (thread->index == waiter_spu_index) { + lv2_obj::set_future_sleep(thread.get()); + continue; + } + + thread->state += cpu_flag::suspend; + } + } + + break; + } + // case SPU_THREAD_GROUP_STATUS_RUNNING: + // case SPU_THREAD_GROUP_STATUS_STOPPED: + // case SPU_THREAD_GROUP_STATUS_UNKNOWN: + default: { + break; + } + } +} + +void lv2_spu_group::save(utils::serial &ar) { + USING_SERIALIZATION_VERSION(spu); + + ar(name, max_num, mem_size, type, ct->id, has_scheduler_context, max_run, + init, prio.load().all, run_state, exit_status); + + for (const auto &thread : threads) { + ar(u8{thread.operator bool()}); + + if (thread) { + ar(thread->id); + thread->save(ar); + } + } + + ar(threads_map); + ar(imgs); + ar(args); + + for (auto ep : {&ep_run, &ep_exception, &ep_sysmodule}) { + ar(lv2_obj::check(*ep) ? (*ep)->id : 0); + } + + if (run_state == SPU_THREAD_GROUP_STATUS_WAITING) { + ar(waiter_spu_index); + } +} + +lv2_spu_image::lv2_spu_image(utils::serial &ar) + : e_entry(ar), segs(ar.pop()), nsegs(ar) {} + +void lv2_spu_image::save(utils::serial &ar) { ar(e_entry, segs, nsegs); } + +// Get spu thread ptr, returns group ptr as well for refcounting +std::pair *, shared_ptr> +lv2_spu_group::get_thread(u32 id) { + if (id >= 0x06000000) { + // thread index is out of range (5 max) + return {}; + } + + // Bits 0-23 contain group id (without id base) + decltype(get_thread(0)) res{ + nullptr, idm::get_unlocked( + (id & 0xFFFFFF) | (lv2_spu_group::id_base & ~0xFFFFFF))}; + + // Bits 24-31 contain thread index within the group + const u32 index = id >> 24; + + if (auto group = res.second.get(); group && group->init > index) { + res.first = group->threads[index].get(); + } + + return res; +} + +struct limits_data { + u32 physical = 0; + u32 raw_spu = 0; + u32 controllable = 0; + u32 spu_limit = umax; + u32 raw_limit = umax; +}; + +struct spu_limits_t { + u32 max_raw = 0; + u32 max_spu = 6; + shared_mutex mutex; + + spu_limits_t() = default; + + spu_limits_t(utils::serial &ar) noexcept { ar(max_raw, max_spu); } + + void save(utils::serial &ar) { ar(max_raw, max_spu); } + + SAVESTATE_INIT_POS(47); + + bool check(const limits_data &init) const { + u32 physical_spus_count = init.physical; + u32 raw_spu_count = init.raw_spu; + u32 controllable_spu_count = init.controllable; + const u32 spu_limit = init.spu_limit != umax ? init.spu_limit : max_spu; + const u32 raw_limit = init.raw_limit != umax ? init.raw_limit : max_raw; + + idm::select([&](u32, lv2_spu_group &group) { + if (group.has_scheduler_context) { + controllable_spu_count = + std::max(controllable_spu_count, group.max_num); + } else { + physical_spus_count += group.max_num; + } + }); + + raw_spu_count += spu_thread::g_raw_spu_ctr; + + if (spu_limit + raw_limit > 6 || raw_spu_count > raw_limit || + physical_spus_count >= spu_limit || + physical_spus_count + controllable_spu_count > spu_limit) { + return false; + } + + return true; + } +}; + +error_code sys_spu_initialize(ppu_thread &ppu, u32 max_usable_spu, + u32 max_raw_spu) { + ppu.state += cpu_flag::wait; + + sys_spu.warning("sys_spu_initialize(max_usable_spu=%d, max_raw_spu=%d)", + max_usable_spu, max_raw_spu); + + auto &limits = g_fxo->get(); + + if (max_raw_spu > 5) { + return CELL_EINVAL; + } + + // NOTE: This value can be changed by VSH in theory + max_usable_spu = 6; + + std::lock_guard lock(limits.mutex); + + if (!limits.check(limits_data{.spu_limit = max_usable_spu - max_raw_spu, + .raw_limit = max_raw_spu})) { + return CELL_EBUSY; + } + + limits.max_raw = max_raw_spu; + limits.max_spu = max_usable_spu - max_raw_spu; + return CELL_OK; +} + +error_code _sys_spu_image_get_information(ppu_thread &ppu, + vm::ptr img, + vm::ptr entry_point, + vm::ptr nsegs) { + ppu.state += cpu_flag::wait; + + sys_spu.warning("_sys_spu_image_get_information(img=*0x%x, " + "entry_point=*0x%x, nsegs=*0x%x)", + img, entry_point, nsegs); + + if (img->type != SYS_SPU_IMAGE_TYPE_KERNEL) { + return CELL_EINVAL; + } + + const auto image = + idm::get_unlocked(img->entry_point); + + if (!image) { + return CELL_ESRCH; + } + + ppu.check_state(); + *entry_point = image->e_entry; + *nsegs = image->nsegs; + return CELL_OK; +} + +error_code sys_spu_image_open(ppu_thread &ppu, vm::ptr img, + vm::cptr path) { + ppu.state += cpu_flag::wait; + + sys_spu.warning("sys_spu_image_open(img=*0x%x, path=%s)", img, path); + + auto [fs_error, ppath, path0, file, type] = + lv2_file::open(path.get_ptr(), 0, 0); + + if (fs_error) { + return {fs_error, path}; + } + + u128 klic = g_fxo->get().last_key(); + + const fs::file elf_file = + decrypt_self(std::move(file), reinterpret_cast(&klic)); + + if (!elf_file) { + sys_spu.error("sys_spu_image_open(): file %s is illegal for SPU image!", + path); + return {CELL_ENOEXEC, path}; + } + + img->load(elf_file); + return CELL_OK; +} + +error_code _sys_spu_image_import(ppu_thread &ppu, vm::ptr img, + u32 src, u32 size, u32 arg4) { + ppu.state += cpu_flag::wait; + + sys_spu.warning( + "_sys_spu_image_import(img=*0x%x, src=*0x%x, size=0x%x, arg4=0x%x)", img, + src, size, arg4); + + img->load(fs::file{vm::base(src), size}); + return CELL_OK; +} + +error_code _sys_spu_image_close(ppu_thread &ppu, vm::ptr img) { + ppu.state += cpu_flag::wait; + + sys_spu.warning("_sys_spu_image_close(img=*0x%x)", img); + + if (img->type != SYS_SPU_IMAGE_TYPE_KERNEL) { + return CELL_EINVAL; + } + + const auto handle = idm::withdraw(img->entry_point); + + if (!handle) { + return CELL_ESRCH; + } + + ensure(vm::dealloc(handle->segs.addr(), vm::main)); + return CELL_OK; +} + +error_code _sys_spu_image_get_segments(ppu_thread &ppu, + vm::ptr img, + vm::ptr segments, + s32 nseg) { + ppu.state += cpu_flag::wait; + + sys_spu.error( + "_sys_spu_image_get_segments(img=*0x%x, segments=*0x%x, nseg=%d)", img, + segments, nseg); + + if (nseg <= 0 || nseg > 0x20 || img->type != SYS_SPU_IMAGE_TYPE_KERNEL) { + return CELL_EINVAL; + } + + const auto handle = + idm::get_unlocked(img->entry_point); + + if (!handle) { + return CELL_ESRCH; + } + + // TODO: apply SPU patches + ppu.check_state(); + std::memcpy(segments.get_ptr(), handle->segs.get_ptr(), + sizeof(sys_spu_segment) * std::min(nseg, handle->nsegs)); + return CELL_OK; +} + +error_code sys_spu_thread_initialize(ppu_thread &ppu, vm::ptr thread, + u32 group_id, u32 spu_num, + vm::ptr img, + vm::ptr attr, + vm::ptr arg) { + ppu.state += cpu_flag::wait; + + sys_spu.warning("sys_spu_thread_initialize(thread=*0x%x, group=0x%x, " + "spu_num=%d, img=*0x%x, attr=*0x%x, arg=*0x%x)", + thread, group_id, spu_num, img, attr, arg); + + if (spu_num >= std::size(decltype(lv2_spu_group::threads_map){})) { + return CELL_EINVAL; + } + + if (!attr) { + return CELL_EFAULT; + } + + const sys_spu_thread_attribute attr_data = *attr; + + if (attr_data.name_len > 0x80) { + return CELL_EINVAL; + } + + if (!arg) { + return CELL_EFAULT; + } + + const sys_spu_thread_argument args = *arg; + const u32 option = attr_data.option; + + if (option & ~(SYS_SPU_THREAD_OPTION_DEC_SYNC_TB_ENABLE | + SYS_SPU_THREAD_OPTION_ASYNC_INTR_ENABLE)) { + return CELL_EINVAL; + } + + if (!img) { + return CELL_EFAULT; + } + + sys_spu_image image = *img; + + switch (image.type) { + case SYS_SPU_IMAGE_TYPE_KERNEL: { + const auto handle = + idm::get_unlocked(image.entry_point); + + if (!handle) { + return CELL_ESRCH; + } + + // Image information is stored in IDM + image.entry_point = handle->e_entry; + image.nsegs = handle->nsegs; + image.segs = handle->segs; + image.type = SYS_SPU_IMAGE_TYPE_KERNEL; + break; + } + case SYS_SPU_IMAGE_TYPE_USER: { + if (image.entry_point > 0x3fffc || image.nsegs <= 0 || image.nsegs > 0x20) { + return CELL_EINVAL; + } + + break; + } + default: + return CELL_EINVAL; + } + + std::vector spu_segs(image.segs.get_ptr(), + image.segs.get_ptr() + image.nsegs); + + bool found_info_segment = false; + bool found_copy_segment = false; + + for (const auto &seg : spu_segs) { + if (image.type == SYS_SPU_IMAGE_TYPE_KERNEL) { + // Assume valid, values are coming from LV2 + found_copy_segment = true; + break; + } + + switch (seg.type) { + case SYS_SPU_SEGMENT_TYPE_COPY: { + if (seg.addr % 4) { + // 4-bytes unaligned address is not valid + return CELL_EINVAL; + } + + found_copy_segment = true; + break; + } + case SYS_SPU_SEGMENT_TYPE_FILL: { + break; + } + case SYS_SPU_SEGMENT_TYPE_INFO: { + // There can only be one INFO segment at max + if (seg.size > 256u || found_info_segment) { + return CELL_EINVAL; + } + + found_info_segment = true; + continue; + } + default: + return CELL_EINVAL; + } + + if (!seg.size || (seg.ls | seg.size) % 0x10 || seg.ls >= SPU_LS_SIZE || + seg.size > SPU_LS_SIZE) { + return CELL_EINVAL; + } + + for (auto it = spu_segs.data(); it != &seg; it++) { + if (it->type != SYS_SPU_SEGMENT_TYPE_INFO) { + if (seg.ls + seg.size > it->ls && it->ls + it->size > seg.ls) { + // Overlapping segments are not allowed + return CELL_EINVAL; + } + } + } + } + + // There must be at least one COPY segment + if (!found_copy_segment) { + return CELL_EINVAL; + } + + // Read thread name + const std::string thread_name(attr_data.name.get_ptr(), + std::max(attr_data.name_len, 1) - 1); + + const auto group = idm::get_unlocked(group_id); + + if (!group) { + return CELL_ESRCH; + } + + std::unique_lock lock(group->mutex); + + if (auto state = +group->run_state; + state != SPU_THREAD_GROUP_STATUS_NOT_INITIALIZED) { + if (state == SPU_THREAD_GROUP_STATUS_DESTROYED) { + return CELL_ESRCH; + } + + return CELL_EBUSY; + } + + if (group->threads_map[spu_num] != -1) { + return CELL_EBUSY; + } + + if (option & SYS_SPU_THREAD_OPTION_ASYNC_INTR_ENABLE) { + sys_spu.warning("Unimplemented SPU Thread options (0x%x)", option); + } + + const u32 inited = group->init; + + const u32 tid = (inited << 24) | (group_id & 0xffffff); + + ensure(idm::import >([&]() { + const auto spu = stx::make_shared>( + group.get(), spu_num, thread_name, tid, false, option); + group->threads[inited] = spu; + group->threads_map[spu_num] = static_cast(inited); + return spu; + })); + + // alloc_hidden indicates falloc to allocate page with no access rights in + // base memory + auto &spu = group->threads[inited]; + ensure(vm::get(vm::spu)->falloc(spu->vm_offset(), SPU_LS_SIZE, &spu->shm, + static_cast(vm::page_size_64k) | + static_cast(vm::alloc_hidden))); + spu->map_ls(*spu->shm, spu->ls); + + group->args[inited] = {args.arg1, args.arg2, args.arg3, args.arg4}; + group->imgs[inited].first = image.entry_point; + group->imgs[inited].second = std::move(spu_segs); + + if (++group->init == group->max_num) { + const auto old = group->run_state.compare_and_swap( + SPU_THREAD_GROUP_STATUS_NOT_INITIALIZED, + SPU_THREAD_GROUP_STATUS_INITIALIZED); + + if (old == SPU_THREAD_GROUP_STATUS_DESTROYED) { + return CELL_ESRCH; + } + } + + lock.unlock(); + sys_spu.warning( + "sys_spu_thread_initialize(): Thread \"%s\" created (id=0x%x)", + thread_name, tid); + + ppu.check_state(); + *thread = tid; + return CELL_OK; +} + +error_code sys_spu_thread_set_argument(ppu_thread &ppu, u32 id, + vm::ptr arg) { + ppu.state += cpu_flag::wait; + + sys_spu.warning("sys_spu_thread_set_argument(id=0x%x, arg=*0x%x)", id, arg); + + const auto [thread, group] = lv2_spu_group::get_thread(id); + + if (!thread) [[unlikely]] { + return CELL_ESRCH; + } + + std::lock_guard lock(group->mutex); + + group->args[id >> 24] = {arg->arg1, arg->arg2, arg->arg3, arg->arg4}; + + return CELL_OK; +} + +error_code sys_spu_thread_get_exit_status(ppu_thread &ppu, u32 id, + vm::ptr status) { + ppu.state += cpu_flag::wait; + + sys_spu.warning("sys_spu_thread_get_exit_status(id=0x%x, status=*0x%x)", id, + status); + + const auto [thread, group] = lv2_spu_group::get_thread(id); + + if (!thread) [[unlikely]] { + return CELL_ESRCH; + } + + u32 data; + + if (thread->exit_status.try_read(data)) { + ppu.check_state(); + *status = static_cast(data); + return CELL_OK; + } + + return CELL_ESTAT; +} + +error_code +sys_spu_thread_group_create(ppu_thread &ppu, vm::ptr id, u32 num, s32 prio, + vm::ptr attr) { + ppu.state += cpu_flag::wait; + + sys_spu.warning( + "sys_spu_thread_group_create(id=*0x%x, num=%d, prio=%d, attr=*0x%x)", id, + num, prio, attr); + + const s32 min_prio = g_ps3_process_info.has_root_perm() ? 0 : 16; + + const sys_spu_thread_group_attribute attr_data = *attr; + + if (attr_data.nsize > 0x80 || !num) { + return CELL_EINVAL; + } + + const s32 type = attr_data.type; + + bool use_scheduler = true; + bool use_memct = !!(type & SYS_SPU_THREAD_GROUP_TYPE_MEMORY_FROM_CONTAINER); + bool needs_root = false; + u32 max_threads = 6; + u32 min_threads = 1; + u32 mem_size = 0; + lv2_memory_container *ct{}; + + if (type) { + sys_spu.warning( + "sys_spu_thread_group_create(): SPU Thread Group type (0x%x)", type); + } + + switch (type) { + case 0x0: + case 0x4: + case 0x18: { + break; + } + + case 0x20: + case 0x22: + case 0x24: + case 0x26: { + if (type == 0x22 || type == 0x26) { + needs_root = true; + } + + min_threads = 2; // That's what appears from reversing + break; + } + + case 0x2: + case 0x6: + case 0xA: + + case 0x102: + case 0x106: + case 0x10A: + + case 0x202: + case 0x206: + case 0x20A: + + case 0x902: + case 0x906: + + case 0xA02: + case 0xA06: + + case 0xC02: + case 0xC06: { + if (type & 0x700) { + max_threads = 1; + } + + needs_root = true; + break; + } + default: + return CELL_EINVAL; + } + + const bool is_system_coop = + type & SYS_SPU_THREAD_GROUP_TYPE_COOPERATE_WITH_SYSTEM; + + if (is_system_coop) { + // Constant size, unknown what it means + mem_size = SPU_LS_SIZE; + } else if (type & SYS_SPU_THREAD_GROUP_TYPE_NON_CONTEXT) { + // No memory consumed + mem_size = 0; + use_scheduler = false; + } else { + // 256kb for each spu thread, probably for saving and restoring SPU LS (used + // by scheduler?) + mem_size = SPU_LS_SIZE * num; + } + + if (num < min_threads || num > max_threads || + (needs_root && min_prio == 0x10) || + (use_scheduler && !is_system_coop && (prio > 255 || prio < min_prio))) { + return CELL_EINVAL; + } + + if (use_memct && mem_size) { + const auto sct = idm::get_unlocked(attr_data.ct); + + if (!sct) { + return CELL_ESRCH; + } + + if (sct->take(mem_size) != mem_size) { + return CELL_ENOMEM; + } + + ct = sct.get(); + } else { + ct = &g_fxo->get(); + + if (ct->take(mem_size) != mem_size) { + return CELL_ENOMEM; + } + } + + auto &limits = g_fxo->get(); + + std::unique_lock lock(limits.mutex); + + if (!limits.check(use_scheduler ? limits_data{.controllable = num} + : limits_data{.physical = num})) { + ct->free(mem_size); + return CELL_EBUSY; + } + + const auto group = idm::make_ptr( + std::string(attr_data.name.get_ptr(), + std::max(attr_data.nsize, 1) - 1), + num, prio, type, ct, use_scheduler, mem_size); + + if (!group) { + ct->free(mem_size); + return CELL_EAGAIN; + } + + lock.unlock(); + sys_spu.warning( + "sys_spu_thread_group_create(): Thread group \"%s\" created (id=0x%x)", + group->name, idm::last_id()); + + ppu.check_state(); + *id = idm::last_id(); + return CELL_OK; +} + +error_code sys_spu_thread_group_destroy(ppu_thread &ppu, u32 id) { + ppu.state += cpu_flag::wait; + + sys_spu.warning("sys_spu_thread_group_destroy(id=0x%x)", id); + + auto &limits = g_fxo->get(); + + std::lock_guard lock(limits.mutex); + + const auto group = + idm::withdraw(id, [](lv2_spu_group &group) -> CellError { + if (!group.run_state + .fetch_op([](spu_group_status &state) { + if (state <= SPU_THREAD_GROUP_STATUS_INITIALIZED) { + state = SPU_THREAD_GROUP_STATUS_DESTROYED; + return true; + } + + return false; + }) + .second) { + return CELL_EBUSY; + } + + group.ct->free(group.mem_size); + return {}; + }); + + if (!group) { + return CELL_ESRCH; + } + + if (group.ret) { + return group.ret; + } + + group->mutex.lock_unlock(); + + for (const auto &t : group->threads) { + if (auto thread = t.get()) { + // Deallocate LS + thread->cleanup(); + + // Remove ID from IDM (destruction will occur in group destructor) + idm::remove>(thread->id); + } + } + + return CELL_OK; +} + +error_code sys_spu_thread_group_start(ppu_thread &ppu, u32 id) { + ppu.state += cpu_flag::wait; + + sys_spu.trace("sys_spu_thread_group_start(id=0x%x)", id); + + const auto group = idm::get_unlocked(id); + + if (!group) { + return CELL_ESRCH; + } + + struct notify_on_exit { + usz index = umax; + std::array threads; // Raw pointer suffices, as long as + // group is referenced its SPUs exist + + ~notify_on_exit() noexcept { + for (; index != umax; index--) { + threads[index]->state.notify_one(); + } + } + } notify_threads; + + std::lock_guard lock(group->mutex); + + // SPU_THREAD_GROUP_STATUS_READY state is not used + switch (group->run_state.compare_and_swap(SPU_THREAD_GROUP_STATUS_INITIALIZED, + SPU_THREAD_GROUP_STATUS_RUNNING)) { + case SPU_THREAD_GROUP_STATUS_INITIALIZED: + break; + case SPU_THREAD_GROUP_STATUS_DESTROYED: + return CELL_ESRCH; + default: + return CELL_ESTAT; + } + + const u32 max_threads = group->max_num; + + group->join_state = 0; + group->exit_status = 0; + group->running = max_threads; + group->set_terminate = false; + + for (auto &thread : group->threads) { + if (thread) { + auto &args = group->args[thread->lv2_id >> 24]; + auto &img = group->imgs[thread->lv2_id >> 24]; + + sys_spu_image::deploy(thread->ls, + std::span(img.second.data(), img.second.size()), + group->stop_count < 5); + + thread->cpu_init(); + thread->gpr[3] = v128::from64(0, args[0]); + thread->gpr[4] = v128::from64(0, args[1]); + thread->gpr[5] = v128::from64(0, args[2]); + thread->gpr[6] = v128::from64(0, args[3]); + + thread->status_npc = {SPU_STATUS_RUNNING, img.first}; + } + } + + // Because SPU_THREAD_GROUP_STATUS_READY is not possible, run event is + // delivered immediately + // TODO: check data2 and data3 + group->send_run_event(id, 0, 0); + + u32 ran_threads = max_threads; + + for (auto &thread : group->threads) { + if (!ran_threads) { + break; + } + + if (thread && ran_threads--) { + thread->state -= cpu_flag::stop; + notify_threads.threads[++notify_threads.index] = thread.get(); + } + } + + return CELL_OK; +} + +error_code sys_spu_thread_group_suspend(ppu_thread &ppu, u32 id) { + ppu.state += cpu_flag::wait; + + sys_spu.trace("sys_spu_thread_group_suspend(id=0x%x)", id); + + const auto group = idm::get_unlocked(id); + + if (!group) { + return CELL_ESRCH; + } + + if (!group->has_scheduler_context || group->type & 0xf00) { + return CELL_EINVAL; + } + + std::lock_guard lock(group->mutex); + + CellError error; + + group->run_state.fetch_op([&error](spu_group_status &state) { + if (state == SPU_THREAD_GROUP_STATUS_DESTROYED) { + error = CELL_ESRCH; + return false; + } + + if (state <= SPU_THREAD_GROUP_STATUS_INITIALIZED || + state == SPU_THREAD_GROUP_STATUS_STOPPED) { + error = CELL_ESTAT; + return false; + } + + // SPU_THREAD_GROUP_STATUS_READY state is not used + + if (state == SPU_THREAD_GROUP_STATUS_RUNNING) { + state = SPU_THREAD_GROUP_STATUS_SUSPENDED; + } else if (state == SPU_THREAD_GROUP_STATUS_WAITING) { + state = SPU_THREAD_GROUP_STATUS_WAITING_AND_SUSPENDED; + } else if (state == SPU_THREAD_GROUP_STATUS_SUSPENDED || + state == SPU_THREAD_GROUP_STATUS_WAITING_AND_SUSPENDED) { + error = {}; + return false; + } else { + + error = CELL_ESTAT; + return false; + } + + error = CellError{CELL_CANCEL + 0u}; + return true; + }); + + if (error != CELL_CANCEL + 0u) { + if (!error) { + return CELL_OK; + } + + return error; + } + + for (auto &thread : group->threads) { + if (thread) { + thread->state += cpu_flag::suspend; + } + } + + return CELL_OK; +} + +error_code sys_spu_thread_group_resume(ppu_thread &ppu, u32 id) { + ppu.state += cpu_flag::wait; + + sys_spu.trace("sys_spu_thread_group_resume(id=0x%x)", id); + + const auto group = idm::get_unlocked(id); + + if (!group) { + return CELL_ESRCH; + } + + if (!group->has_scheduler_context || group->type & 0xf00) { + return CELL_EINVAL; + } + + struct notify_on_exit { + usz index = umax; + std::array threads; // Raw pointer suffices, as long as + // group is referenced its SPUs exist + + ~notify_on_exit() noexcept { + for (; index != umax; index--) { + threads[index]->state.notify_one(); + } + } + } notify_threads; + + std::lock_guard lock(group->mutex); + + CellError error; + + group->run_state.fetch_op([&error](spu_group_status &state) { + if (state == SPU_THREAD_GROUP_STATUS_DESTROYED) { + error = CELL_ESRCH; + return false; + } + + // SPU_THREAD_GROUP_STATUS_READY state is not used + + if (state == SPU_THREAD_GROUP_STATUS_SUSPENDED) { + state = SPU_THREAD_GROUP_STATUS_RUNNING; + } else if (state == SPU_THREAD_GROUP_STATUS_WAITING_AND_SUSPENDED) { + state = SPU_THREAD_GROUP_STATUS_WAITING; + error = CellError{}; + return true; + } else { + error = CELL_ESTAT; + return false; + } + + error = CellError{CELL_CANCEL + 0u}; + return true; + }); + + if (error != CELL_CANCEL + 0u) { + if (error) { + return error; + } + + return CELL_OK; + } + + for (auto &thread : group->threads) { + if (thread) { + thread->state -= cpu_flag::suspend; + notify_threads.threads[++notify_threads.index] = thread.get(); + } + } + + return CELL_OK; +} + +error_code sys_spu_thread_group_yield(ppu_thread &ppu, u32 id) { + ppu.state += cpu_flag::wait; + + sys_spu.trace("sys_spu_thread_group_yield(id=0x%x)", id); + + const auto group = idm::get_unlocked(id); + + if (!group) { + return CELL_ESRCH; + } + + // No effect on these group types + if (!group->has_scheduler_context || group->type & 0xf00) { + return CELL_OK; + } + + if (auto state = +group->run_state; + state != SPU_THREAD_GROUP_STATUS_RUNNING) { + if (state == SPU_THREAD_GROUP_STATUS_DESTROYED) { + return CELL_ESRCH; + } + + return CELL_ESTAT; + } + + // SPU_THREAD_GROUP_STATUS_READY state is not used, so this function does + // nothing + + return CELL_OK; +} + +error_code sys_spu_thread_group_terminate(ppu_thread &ppu, u32 id, s32 value) { + ppu.state += cpu_flag::wait; + + sys_spu.trace("sys_spu_thread_group_terminate(id=0x%x, value=0x%x)", id, + value); + + const auto group = idm::get_unlocked(id); + + if (!group) { + return CELL_ESRCH; + } + + std::unique_lock lock(group->mutex); + + // There should be a small period of sleep when the PPU waits for a signal of + // termination + auto short_sleep = [](ppu_thread &ppu) { + lv2_obj::sleep(ppu); + busy_wait(3000); + ppu.check_state(); + ppu.state += cpu_flag::wait; + }; + + if (auto state = +group->run_state; + state <= SPU_THREAD_GROUP_STATUS_INITIALIZED || + state == SPU_THREAD_GROUP_STATUS_WAITING || + state == SPU_THREAD_GROUP_STATUS_WAITING_AND_SUSPENDED || + state == SPU_THREAD_GROUP_STATUS_DESTROYED) { + if (state == SPU_THREAD_GROUP_STATUS_DESTROYED) { + return CELL_ESRCH; + } + + return CELL_ESTAT; + } + + if (group->set_terminate) { + // Wait for termination, only then return error code + const u32 last_stop = group->stop_count; + group->wait_term_count++; + lock.unlock(); + short_sleep(ppu); + + while (group->stop_count == last_stop) { + group->stop_count.wait(last_stop); + } + + group->wait_term_count--; + return CELL_ESTAT; + } + + group->set_terminate = true; + + for (auto &thread : group->threads) { + if (thread) { + thread->state.fetch_op([](bs_t &flags) { + if (flags & cpu_flag::stop) { + // In case the thread raised the ret flag itself at some point do not + // raise it again + return false; + } + + flags += cpu_flag::stop + cpu_flag::ret; + return true; + }); + } + } + + u32 prev_resv = 0; + + for (auto &thread : group->threads) { + while (thread && group->running && thread->state & cpu_flag::wait) { + thread_ctrl::notify(*thread); + + if (u32 resv = atomic_storage::load(thread->raddr)) { + if (prev_resv && prev_resv != resv) { + // Batch reservation notifications if possible + vm::reservation_notifier_notify(prev_resv); + } + + prev_resv = resv; + } + } + } + + if (prev_resv) { + vm::reservation_notifier_notify(prev_resv); + } + + group->exit_status = value; + group->join_state = SYS_SPU_THREAD_GROUP_JOIN_TERMINATED; + + // Wait until the threads are actually stopped + const u32 last_stop = group->stop_count; + group->wait_term_count++; + lock.unlock(); + short_sleep(ppu); + + while (group->stop_count == last_stop) { + group->stop_count.wait(last_stop); + } + + group->wait_term_count--; + return CELL_OK; +} + +error_code sys_spu_thread_group_join(ppu_thread &ppu, u32 id, + vm::ptr cause, vm::ptr status) { + ppu.state += cpu_flag::wait; + + sys_spu.trace("sys_spu_thread_group_join(id=0x%x, cause=*0x%x, status=*0x%x)", + id, cause, status); + + const auto group = idm::get_unlocked(id); + + if (!group) { + return CELL_ESRCH; + } + + do { + lv2_obj::prepare_for_sleep(ppu); + + std::unique_lock lock(group->mutex); + + const auto state = +group->run_state; + + if (state == SPU_THREAD_GROUP_STATUS_DESTROYED) { + return CELL_ESRCH; + } + + if (state < SPU_THREAD_GROUP_STATUS_INITIALIZED) { + return CELL_ESTAT; + } + + if (group->waiter) { + // another PPU thread is joining this thread group + return CELL_EBUSY; + } + + if (group->join_state && state == SPU_THREAD_GROUP_STATUS_INITIALIZED) { + // Already signaled + ppu.gpr[4] = group->join_state; + ppu.gpr[5] = group->exit_status; + group->join_state.release(0); + break; + } else { + // Subscribe to receive status in r4-r5 + group->waiter = &ppu; + } + + { + lv2_obj::notify_all_t notify; + lv2_obj::sleep(ppu); + lock.unlock(); + } + + while (auto state = +ppu.state) { + if (state & cpu_flag::signal && + ppu.state.test_and_reset(cpu_flag::signal)) { + break; + } + + if (is_stopped(state)) { + std::lock_guard lock(group->mutex); + + if (group->waiter != &ppu) { + break; + } + + ppu.state += cpu_flag::again; + break; + } + + ppu.state.wait(state); + } + } while (false); + + ppu.check_state(); + + if (!cause) { + if (status) { + // Report unwritten data + return CELL_EFAULT; + } + + return not_an_error(CELL_EFAULT); + } + + *cause = static_cast(ppu.gpr[4]); + + if (!status) { + return not_an_error(CELL_EFAULT); + } + + *status = static_cast(ppu.gpr[5]); + return CELL_OK; +} + +error_code sys_spu_thread_group_set_priority(ppu_thread &ppu, u32 id, + s32 priority) { + ppu.state += cpu_flag::wait; + + sys_spu.trace("sys_spu_thread_group_set_priority(id=0x%x, priority=%d)", id, + priority); + + const auto group = idm::get_unlocked(id); + + if (!group) { + return CELL_ESRCH; + } + + if (!group->has_scheduler_context || + priority < (g_ps3_process_info.has_root_perm() ? 0 : 16) || + priority > 255) { + return CELL_EINVAL; + } + + group->prio.atomic_op( + [&](std::common_type_t &prio) { + prio.prio = priority; + }); + + return CELL_OK; +} + +error_code sys_spu_thread_group_get_priority(ppu_thread &ppu, u32 id, + vm::ptr priority) { + ppu.state += cpu_flag::wait; + + sys_spu.trace("sys_spu_thread_group_get_priority(id=0x%x, priority=*0x%x)", + id, priority); + + const auto group = idm::get_unlocked(id); + + if (!group) { + return CELL_ESRCH; + } + + ppu.check_state(); + + if (!group->has_scheduler_context) { + *priority = 0; + } else { + *priority = group->prio.load().prio; + } + + return CELL_OK; +} + +error_code sys_spu_thread_group_set_cooperative_victims(ppu_thread &ppu, u32 id, + u32 threads_mask) { + ppu.state += cpu_flag::wait; + + sys_spu.warning("sys_spu_thread_group_set_cooperative_victims(id=0x%x, " + "threads_mask=0x%x)", + id, threads_mask); + + const auto group = idm::get_unlocked(id); + + if (!group) { + return CELL_ESRCH; + } + + if (!(group->type & SYS_SPU_THREAD_GROUP_TYPE_COOPERATE_WITH_SYSTEM)) { + return CELL_EINVAL; + } + + if (threads_mask >= 1u << group->max_num) { + return CELL_EINVAL; + } + + // TODO + + return CELL_OK; +} + +error_code sys_spu_thread_group_syscall_253( + ppu_thread &ppu, u32 id, + vm::ptr info) { + ppu.state += cpu_flag::wait; + + sys_spu.warning("sys_spu_thread_group_syscall_253(id=0x%x, info=*0x%x)", id, + info); + + const auto group = idm::get_unlocked(id); + + if (!group) { + return CELL_ESRCH; + } + + if (!(group->type & SYS_SPU_THREAD_GROUP_TYPE_COOPERATE_WITH_SYSTEM)) { + return CELL_EINVAL; + } + + // TODO + + ppu.check_state(); + info->deadlineMissCounter = 0; + info->deadlineMeetCounter = 0; + info->timestamp = get_timebased_time(); + return CELL_OK; +} + +error_code sys_spu_thread_write_ls(ppu_thread &ppu, u32 id, u32 lsa, u64 value, + u32 type) { + ppu.state += cpu_flag::wait; + + sys_spu.trace( + "sys_spu_thread_write_ls(id=0x%x, lsa=0x%05x, value=0x%llx, type=%d)", id, + lsa, value, type); + + if (lsa >= SPU_LS_SIZE || type > 8 || !type || + (type | lsa) & (type - 1)) // check range and alignment + { + return CELL_EINVAL; + } + + const auto [thread, group] = lv2_spu_group::get_thread(id); + + if (!thread) [[unlikely]] { + return CELL_ESRCH; + } + + std::lock_guard lock(group->mutex); + + if (auto state = +group->run_state; state < SPU_THREAD_GROUP_STATUS_WAITING || + state > SPU_THREAD_GROUP_STATUS_RUNNING) { + if (state == SPU_THREAD_GROUP_STATUS_DESTROYED) { + return CELL_ESRCH; + } + + return CELL_ESTAT; + } + + switch (type) { + case 1: + thread->_ref(lsa) = static_cast(value); + break; + case 2: + thread->_ref(lsa) = static_cast(value); + break; + case 4: + thread->_ref(lsa) = static_cast(value); + break; + case 8: + thread->_ref(lsa) = value; + break; + default: + fmt::throw_exception("Unreachable"); + } + + return CELL_OK; +} + +error_code sys_spu_thread_read_ls(ppu_thread &ppu, u32 id, u32 lsa, + vm::ptr value, u32 type) { + ppu.state += cpu_flag::wait; + + sys_spu.trace( + "sys_spu_thread_read_ls(id=0x%x, lsa=0x%05x, value=*0x%x, type=%d)", id, + lsa, value, type); + + if (lsa >= SPU_LS_SIZE || type > 8 || !type || + (type | lsa) & (type - 1)) // check range and alignment + { + return CELL_EINVAL; + } + + const auto [thread, group] = lv2_spu_group::get_thread(id); + + if (!thread) [[unlikely]] { + return CELL_ESRCH; + } + + std::unique_lock lock(group->mutex); + + if (auto state = +group->run_state; state < SPU_THREAD_GROUP_STATUS_WAITING || + state > SPU_THREAD_GROUP_STATUS_RUNNING) { + if (state == SPU_THREAD_GROUP_STATUS_DESTROYED) { + return CELL_ESRCH; + } + + return CELL_ESTAT; + } + + u64 _value{}; + + switch (type) { + case 1: + _value = thread->_ref(lsa); + break; + case 2: + _value = thread->_ref(lsa); + break; + case 4: + _value = thread->_ref(lsa); + break; + case 8: + _value = thread->_ref(lsa); + break; + default: + fmt::throw_exception("Unreachable"); + } + + lock.unlock(); + ppu.check_state(); + + *value = _value; + return CELL_OK; +} + +error_code sys_spu_thread_write_spu_mb(ppu_thread &ppu, u32 id, u32 value) { + ppu.state += cpu_flag::wait; + + sys_spu.warning("sys_spu_thread_write_spu_mb(id=0x%x, value=0x%x)", id, + value); + + const auto [thread, group] = lv2_spu_group::get_thread(id); + + if (!thread) [[unlikely]] { + return CELL_ESRCH; + } + + spu_channel_op_state state{}; + { + std::lock_guard lock(group->mutex); + + state = thread->ch_in_mbox.push(value, true); + } + + if (!state.op_done) { + ppu.state += cpu_flag::again; + return {}; + } + + if (state.notify) { + thread->ch_in_mbox.notify(); + } + + return CELL_OK; +} + +error_code sys_spu_thread_set_spu_cfg(ppu_thread &ppu, u32 id, u64 value) { + ppu.state += cpu_flag::wait; + + sys_spu.warning("sys_spu_thread_set_spu_cfg(id=0x%x, value=0x%x)", id, value); + + if (value > 3) { + return CELL_EINVAL; + } + + const auto [thread, group] = lv2_spu_group::get_thread(id); + + if (!thread) [[unlikely]] { + return CELL_ESRCH; + } + + thread->snr_config = value; + + return CELL_OK; +} + +error_code sys_spu_thread_get_spu_cfg(ppu_thread &ppu, u32 id, + vm::ptr value) { + ppu.state += cpu_flag::wait; + + sys_spu.warning("sys_spu_thread_get_spu_cfg(id=0x%x, value=*0x%x)", id, + value); + + const auto [thread, group] = lv2_spu_group::get_thread(id); + + if (!thread) [[unlikely]] { + return CELL_ESRCH; + } + + ppu.check_state(); + *value = thread->snr_config; + + return CELL_OK; +} + +error_code sys_spu_thread_write_snr(ppu_thread &ppu, u32 id, u32 number, + u32 value) { + ppu.state += cpu_flag::wait; + + sys_spu.trace("sys_spu_thread_write_snr(id=0x%x, number=%d, value=0x%x)", id, + number, value); + + if (number > 1) { + return CELL_EINVAL; + } + + const auto [thread, group] = lv2_spu_group::get_thread(id); + + if (!thread) [[unlikely]] { + return CELL_ESRCH; + } + + thread->push_snr(number, value); + + return CELL_OK; +} + +error_code sys_spu_thread_group_connect_event(ppu_thread &ppu, u32 id, u32 eq, + u32 et) { + ppu.state += cpu_flag::wait; + + sys_spu.warning("sys_spu_thread_group_connect_event(id=0x%x, eq=0x%x, et=%d)", + id, eq, et); + + const auto group = idm::get_unlocked(id); + + if (!group) { + return CELL_ESRCH; + } + + const auto ep = + et == SYS_SPU_THREAD_GROUP_EVENT_SYSTEM_MODULE ? &group->ep_sysmodule + : et == SYS_SPU_THREAD_GROUP_EVENT_EXCEPTION ? &group->ep_exception + : et == SYS_SPU_THREAD_GROUP_EVENT_RUN ? &group->ep_run + : nullptr; + + if (!ep) { + sys_spu.error( + "sys_spu_thread_group_connect_event(): unknown event type (%d)", et); + return CELL_EINVAL; + } + + if (et == SYS_SPU_THREAD_GROUP_EVENT_SYSTEM_MODULE && + !(group->type & SYS_SPU_THREAD_GROUP_TYPE_COOPERATE_WITH_SYSTEM)) { + return CELL_EINVAL; + } + + auto queue = idm::get_unlocked(eq); + + std::lock_guard lock(group->mutex); + + if (lv2_obj::check(*ep)) { + return CELL_EBUSY; + } + + // ESRCH of event queue after EBUSY + if (!queue) { + return CELL_ESRCH; + } + + *ep = std::move(queue); + return CELL_OK; +} + +error_code sys_spu_thread_group_disconnect_event(ppu_thread &ppu, u32 id, + u32 et) { + ppu.state += cpu_flag::wait; + + sys_spu.warning("sys_spu_thread_group_disconnect_event(id=0x%x, et=%d)", id, + et); + + const auto group = idm::get_unlocked(id); + + if (!group) { + return CELL_ESRCH; + } + + const auto ep = + et == SYS_SPU_THREAD_GROUP_EVENT_SYSTEM_MODULE ? &group->ep_sysmodule + : et == SYS_SPU_THREAD_GROUP_EVENT_EXCEPTION ? &group->ep_exception + : et == SYS_SPU_THREAD_GROUP_EVENT_RUN ? &group->ep_run + : nullptr; + + if (!ep) { + sys_spu.error( + "sys_spu_thread_group_disconnect_event(): unknown event type (%d)", et); + return CELL_OK; + } + + // No error checking is performed + + std::lock_guard lock(group->mutex); + + ep->reset(); + + return CELL_OK; +} + +error_code sys_spu_thread_connect_event(ppu_thread &ppu, u32 id, u32 eq, u32 et, + u32 spup) { + ppu.state += cpu_flag::wait; + + sys_spu.warning( + "sys_spu_thread_connect_event(id=0x%x, eq=0x%x, et=%d, spup=%d)", id, eq, + et, spup); + + const auto [thread, group] = lv2_spu_group::get_thread(id); + auto queue = idm::get_unlocked(eq); + + if (!queue || !thread) [[unlikely]] { + return CELL_ESRCH; + } + + if (et != SYS_SPU_THREAD_EVENT_USER || spup > 63) { + sys_spu.error("sys_spu_thread_connect_event(): invalid arguments (et=%d, " + "spup=%d, queue->type=%d)", + et, spup, queue->type); + return CELL_EINVAL; + } + + std::lock_guard lock(group->mutex); + + auto &port = thread->spup[spup]; + + if (lv2_obj::check(port)) { + return CELL_EISCONN; + } + + port = std::move(queue); + + return CELL_OK; +} + +error_code sys_spu_thread_disconnect_event(ppu_thread &ppu, u32 id, u32 et, + u32 spup) { + ppu.state += cpu_flag::wait; + + sys_spu.warning("sys_spu_thread_disconnect_event(id=0x%x, et=%d, spup=%d)", + id, et, spup); + + const auto [thread, group] = lv2_spu_group::get_thread(id); + + if (!thread) [[unlikely]] { + return CELL_ESRCH; + } + + if (et != SYS_SPU_THREAD_EVENT_USER || spup > 63) { + sys_spu.error( + "sys_spu_thread_disconnect_event(): invalid arguments (et=%d, spup=%d)", + et, spup); + return CELL_EINVAL; + } + + std::lock_guard lock(group->mutex); + + auto &port = thread->spup[spup]; + + if (!lv2_obj::check(port)) { + return CELL_ENOTCONN; + } + + port.reset(); + + return CELL_OK; +} + +error_code sys_spu_thread_bind_queue(ppu_thread &ppu, u32 id, u32 spuq, + u32 spuq_num) { + ppu.state += cpu_flag::wait; + + sys_spu.warning( + "sys_spu_thread_bind_queue(id=0x%x, spuq=0x%x, spuq_num=0x%x)", id, spuq, + spuq_num); + + const auto [thread, group] = lv2_spu_group::get_thread(id); + auto queue = idm::get_unlocked(spuq); + ; + + if (!queue || !thread) [[unlikely]] { + return CELL_ESRCH; + } + + if (queue->type != SYS_SPU_QUEUE) { + return CELL_EINVAL; + } + + std::lock_guard lock(group->mutex); + + decltype(std::data(thread->spuq)) q{}; + + for (auto &v : thread->spuq) { + // Check if the entry is assigned at all + if (!v.second) { + if (!q) { + q = &v; + } + + continue; + } + + if (v.first == spuq_num || v.second == queue) { + return CELL_EBUSY; + } + } + + if (!q) { + return CELL_EAGAIN; + } + + q->first = spuq_num; + q->second = std::move(queue); + return CELL_OK; +} + +error_code sys_spu_thread_unbind_queue(ppu_thread &ppu, u32 id, u32 spuq_num) { + ppu.state += cpu_flag::wait; + + sys_spu.warning("sys_spu_thread_unbind_queue(id=0x%x, spuq_num=0x%x)", id, + spuq_num); + + const auto [thread, group] = lv2_spu_group::get_thread(id); + + if (!thread) [[unlikely]] { + return CELL_ESRCH; + } + + std::lock_guard lock(group->mutex); + + for (auto &v : thread->spuq) { + if (v.first != spuq_num) { + continue; + } + + if (!v.second) { + continue; + } + + v.second.reset(); + return CELL_OK; + } + + return CELL_ESRCH; +} + +error_code sys_spu_thread_group_connect_event_all_threads(ppu_thread &ppu, + u32 id, u32 eq, + u64 req, + vm::ptr spup) { + ppu.state += cpu_flag::wait; + + sys_spu.warning("sys_spu_thread_group_connect_event_all_threads(id=0x%x, " + "eq=0x%x, req=0x%llx, spup=*0x%x)", + id, eq, req, spup); + + if (!req) { + return CELL_EINVAL; + } + + const auto group = idm::get_unlocked(id); + const auto queue = idm::get_unlocked(eq); + + if (!group || !queue) { + return CELL_ESRCH; + } + + std::unique_lock lock(group->mutex); + + if (auto state = +group->run_state; + state < SPU_THREAD_GROUP_STATUS_INITIALIZED || + state == SPU_THREAD_GROUP_STATUS_DESTROYED) { + if (state == SPU_THREAD_GROUP_STATUS_DESTROYED) { + return CELL_ESRCH; + } + + return CELL_ESTAT; + } + + u8 port = 0; // SPU Port number + + for (; port < 64; port++) { + if (!(req & (1ull << port))) { + continue; + } + + bool found = true; + + for (auto &t : group->threads) { + if (t) { + if (lv2_obj::check(t->spup[port])) { + found = false; + break; + } + } + } + + if (found) { + break; + } + } + + if (port == 64) { + return CELL_EISCONN; + } + + for (auto &t : group->threads) { + if (t) { + t->spup[port] = queue; + } + } + + lock.unlock(); + ppu.check_state(); + + *spup = port; + + return CELL_OK; +} + +error_code sys_spu_thread_group_disconnect_event_all_threads(ppu_thread &ppu, + u32 id, u32 spup) { + ppu.state += cpu_flag::wait; + + sys_spu.warning( + "sys_spu_thread_group_disconnect_event_all_threads(id=0x%x, spup=%d)", id, + spup); + + if (spup > 63) { + return CELL_EINVAL; + } + + const auto group = idm::get_unlocked(id); + + if (!group) { + return CELL_ESRCH; + } + + std::lock_guard lock(group->mutex); + + for (auto &t : group->threads) { + if (t) { + t->spup[spup].reset(); + } + } + + return CELL_OK; +} + +error_code sys_spu_thread_group_log(ppu_thread &ppu, s32 command, + vm::ptr stat) { + ppu.state += cpu_flag::wait; + + sys_spu.warning("sys_spu_thread_group_log(command=0x%x, stat=*0x%x)", command, + stat); + + struct spu_group_log_state_t { + atomic_t state = SYS_SPU_THREAD_GROUP_LOG_ON; + }; + + auto &state = g_fxo->get(); + + switch (command) { + case SYS_SPU_THREAD_GROUP_LOG_GET_STATUS: { + if (!stat) { + return CELL_EFAULT; + } + + ppu.check_state(); + *stat = state.state; + break; + } + case SYS_SPU_THREAD_GROUP_LOG_ON: + case SYS_SPU_THREAD_GROUP_LOG_OFF: { + state.state.release(command); + break; + } + default: + return CELL_EINVAL; + } + + return CELL_OK; +} + +error_code sys_spu_thread_recover_page_fault(ppu_thread &ppu, u32 id) { + ppu.state += cpu_flag::wait; + + sys_spu.warning("sys_spu_thread_recover_page_fault(id=0x%x)", id); + + const auto [thread, group] = lv2_spu_group::get_thread(id); + + if (!thread) [[unlikely]] { + return CELL_ESRCH; + } + + return mmapper_thread_recover_page_fault(thread); +} + +error_code sys_raw_spu_recover_page_fault(ppu_thread &ppu, u32 id) { + ppu.state += cpu_flag::wait; + + sys_spu.warning("sys_raw_spu_recover_page_fault(id=0x%x)", id); + + const auto thread = + idm::get_unlocked>(spu_thread::find_raw_spu(id)); + + if (!thread) [[unlikely]] { + return CELL_ESRCH; + } + + return mmapper_thread_recover_page_fault(thread.get()); +} + +error_code sys_raw_spu_create(ppu_thread &ppu, vm::ptr id, + vm::ptr attr) { + ppu.state += cpu_flag::wait; + + sys_spu.warning("sys_raw_spu_create(id=*0x%x, attr=*0x%x)", id, attr); + + auto &limits = g_fxo->get(); + + std::lock_guard lock(limits.mutex); + + if (!limits.check(limits_data{.raw_spu = 1})) { + return CELL_EAGAIN; + } + + if (!spu_thread::g_raw_spu_ctr.try_inc(5)) { + return CELL_EAGAIN; + } + + u32 index = 0; + + // Find free RawSPU ID + while (!spu_thread::g_raw_spu_id[index].try_inc(1)) { + if (++index == 5) + index = 0; + } + + const auto spu = + idm::make_ptr>(nullptr, index, "", index); + ensure(vm::get(vm::spu)->falloc(spu->vm_offset(), SPU_LS_SIZE, &spu->shm, + vm::page_size_64k)); + spu->map_ls(*spu->shm, spu->ls); + + spu_thread::g_raw_spu_id[index] = idm::last_id(); + + ppu.check_state(); + *id = index; + + return CELL_OK; +} + +error_code sys_isolated_spu_create(ppu_thread &ppu, vm::ptr id, + vm::ptr image, u64 arg1, u64 arg2, + u64 arg3, u64 arg4) { + ppu.state += cpu_flag::wait; + + sys_spu.todo("sys_isolated_spu_create(id=*0x%x, image=*0x%x, arg1=0x%llx, " + "arg2=0x%llx, arg3=0x%llx, arg4=0x%llx)", + id, image, arg1, arg2, arg3, arg4); + + // TODO: More accurate SPU image memory size calculation + u32 max = image.addr() & -4096; + + while (max != 0u - 4096 && vm::check_addr(max)) { + max += 4096; + } + + const auto obj = decrypt_self(fs::file{image.get_ptr(), max - image.addr()}); + + if (!obj) { + return CELL_EAUTHFAIL; + } + + auto &limits = g_fxo->get(); + + std::lock_guard lock(limits.mutex); + + if (!limits.check(limits_data{.raw_spu = 1})) { + return CELL_EAGAIN; + } + + if (!spu_thread::g_raw_spu_ctr.try_inc(5)) { + return CELL_EAGAIN; + } + + u32 index = 0; + + // Find free RawSPU ID + while (!spu_thread::g_raw_spu_id[index].try_inc(1)) { + if (++index == 5) + index = 0; + } + + const u32 ls_addr = RAW_SPU_BASE_ADDR + RAW_SPU_OFFSET * index; + + const auto thread = + idm::make_ptr>(nullptr, index, "", index, true); + + thread->gpr[3] = v128::from64(0, arg1); + thread->gpr[4] = v128::from64(0, arg2); + thread->gpr[5] = v128::from64(0, arg3); + thread->gpr[6] = v128::from64(0, arg4); + + spu_thread::g_raw_spu_id[index] = (ensure(thread->id)); + + sys_spu_image img; + img.load(obj); + + auto image_info = idm::get_unlocked(img.entry_point); + img.deploy(thread->ls, + std::span(image_info->segs.get_ptr(), image_info->nsegs)); + + thread->write_reg(ls_addr + RAW_SPU_PROB_OFFSET + SPU_NPC_offs, + image_info->e_entry); + ensure(idm::remove_verify(img.entry_point, + std::move(image_info))); + + *id = index; + return CELL_OK; +} + +template +error_code raw_spu_destroy(ppu_thread &ppu, u32 id) { + const u32 idm_id = spu_thread::find_raw_spu(id); + + auto thread = idm::get>( + idm_id, [](named_thread &thread) { + if (thread.get_type() != + (isolated ? spu_type::isolated : spu_type::raw)) { + return false; + } + + // Stop thread + thread = thread_state::aborting; + return true; + }); + + if (!thread || !thread.ret) [[unlikely]] { + return CELL_ESRCH; + } + + // TODO: CELL_EBUSY is not returned + + // Kernel objects which must be removed + std::vector, u32>> to_remove; + + // Clear interrupt handlers + for (auto &intr : thread->int_ctrl) { + if (auto &tag = intr.tag; lv2_obj::check(tag)) { + if (auto &handler = tag->handler; lv2_obj::check(handler)) { + // SLEEP + lv2_obj::sleep(ppu); + handler->join(); + to_remove.emplace_back(handler, handler->id); + } + + to_remove.emplace_back(tag, tag->id); + } + } + + // Remove IDs + for (auto &&pair : to_remove) { + if (pair.second >> 24 == 0xa) + idm::remove_verify(pair.second, + std::move(pair.first)); + if (pair.second >> 24 == 0xb) + idm::remove_verify(pair.second, + std::move(pair.first)); + } + + (*thread)(); + + auto &limits = g_fxo->get(); + + std::lock_guard lock(limits.mutex); + + if (auto ret = idm::withdraw>( + idm_id, + [&](spu_thread &spu) -> CellError { + if (std::addressof(spu) != std::addressof(*thread)) { + return CELL_ESRCH; + } + + spu.cleanup(); + return {}; + }); + !ret || ret.ret) { + // Other thread destroyed beforehead + return CELL_ESRCH; + } + + return CELL_OK; +} + +error_code sys_raw_spu_destroy(ppu_thread &ppu, u32 id) { + ppu.state += cpu_flag::wait; + + sys_spu.warning("sys_raw_spu_destroy(id=%d)", id); + + return raw_spu_destroy(ppu, id); +} + +error_code sys_isolated_spu_destroy(ppu_thread &ppu, u32 id) { + ppu.state += cpu_flag::wait; + + sys_spu.todo("sys_isolated_spu_destroy(id=%d)", id); + + return raw_spu_destroy(ppu, id); +} + +template +error_code raw_spu_create_interrupt_tag(u32 id, u32 class_id, u32 /*hwthread*/, + vm::ptr intrtag) { + if (class_id != 0 && class_id != 2) { + return CELL_EINVAL; + } + + CellError error = {}; + + const auto tag = idm::import ([&]() { + shared_ptr result; + + auto thread = idm::check_unlocked>( + spu_thread::find_raw_spu(id)); + + if (!thread || *thread == thread_state::aborting || + thread->get_type() != (isolated ? spu_type::isolated : spu_type::raw)) { + error = CELL_ESRCH; + return result; + } + + auto &int_ctrl = thread->int_ctrl[class_id]; + + if (lv2_obj::check(int_ctrl.tag)) { + error = CELL_EAGAIN; + return result; + } + + result = make_single(); + int_ctrl.tag = result; + return result; + }); + + if (tag) { + cpu_thread::get_current()->check_state(); + *intrtag = tag; + return CELL_OK; + } + + return error; +} + +error_code sys_raw_spu_create_interrupt_tag(ppu_thread &ppu, u32 id, + u32 class_id, u32 hwthread, + vm::ptr intrtag) { + ppu.state += cpu_flag::wait; + + sys_spu.warning("sys_raw_spu_create_interrupt_tag(id=%d, class_id=%d, " + "hwthread=0x%x, intrtag=*0x%x)", + id, class_id, hwthread, intrtag); + + return raw_spu_create_interrupt_tag(id, class_id, hwthread, intrtag); +} + +error_code sys_isolated_spu_create_interrupt_tag(ppu_thread &ppu, u32 id, + u32 class_id, u32 hwthread, + vm::ptr intrtag) { + ppu.state += cpu_flag::wait; + + sys_spu.todo("sys_isolated_spu_create_interrupt_tag(id=%d, class_id=%d, " + "hwthread=0x%x, intrtag=*0x%x)", + id, class_id, hwthread, intrtag); + + return raw_spu_create_interrupt_tag(id, class_id, hwthread, intrtag); +} + +template +error_code raw_spu_set_int_mask(u32 id, u32 class_id, u64 mask) { + if (class_id != 0 && class_id != 2) { + return CELL_EINVAL; + } + + const auto thread = + idm::get_unlocked>(spu_thread::find_raw_spu(id)); + + if (!thread || + thread->get_type() != (isolated ? spu_type::isolated : spu_type::raw)) + [[unlikely]] { + return CELL_ESRCH; + } + + thread->int_ctrl[class_id].mask.exchange(mask); + + return CELL_OK; +} + +error_code sys_raw_spu_set_int_mask(ppu_thread &ppu, u32 id, u32 class_id, + u64 mask) { + ppu.state += cpu_flag::wait; + + sys_spu.trace("sys_raw_spu_set_int_mask(id=%d, class_id=%d, mask=0x%llx)", id, + class_id, mask); + + return raw_spu_set_int_mask(id, class_id, mask); +} + +error_code sys_isolated_spu_set_int_mask(ppu_thread &ppu, u32 id, u32 class_id, + u64 mask) { + ppu.state += cpu_flag::wait; + + sys_spu.todo("sys_isolated_spu_set_int_mask(id=%d, class_id=%d, mask=0x%llx)", + id, class_id, mask); + + return raw_spu_set_int_mask(id, class_id, mask); +} + +template +error_code raw_spu_set_int_stat(u32 id, u32 class_id, u64 stat) { + if (class_id != 0 && class_id != 2) { + return CELL_EINVAL; + } + + const auto thread = + idm::get_unlocked>(spu_thread::find_raw_spu(id)); + + if (!thread || + thread->get_type() != (isolated ? spu_type::isolated : spu_type::raw)) + [[unlikely]] { + return CELL_ESRCH; + } + + thread->int_ctrl[class_id].clear(stat); + + return CELL_OK; +} + +error_code sys_raw_spu_set_int_stat(ppu_thread &ppu, u32 id, u32 class_id, + u64 stat) { + ppu.state += cpu_flag::wait; + + sys_spu.trace("sys_raw_spu_set_int_stat(id=%d, class_id=%d, stat=0x%llx)", id, + class_id, stat); + + return raw_spu_set_int_stat(id, class_id, stat); +} + +error_code sys_isolated_spu_set_int_stat(ppu_thread &ppu, u32 id, u32 class_id, + u64 stat) { + ppu.state += cpu_flag::wait; + + sys_spu.todo("sys_isolated_spu_set_int_stat(id=%d, class_id=%d, stat=0x%llx)", + id, class_id, stat); + + return raw_spu_set_int_stat(id, class_id, stat); +} + +template +error_code raw_spu_get_int_control(u32 id, u32 class_id, vm::ptr value, + atomic_t spu_int_ctrl_t::*control) { + if (class_id != 0 && class_id != 2) { + return CELL_EINVAL; + } + + const auto thread = + idm::get_unlocked>(spu_thread::find_raw_spu(id)); + + if (!thread || + thread->get_type() != (isolated ? spu_type::isolated : spu_type::raw)) + [[unlikely]] { + return CELL_ESRCH; + } + + cpu_thread::get_current()->check_state(); + *value = thread->int_ctrl[class_id].*control; + + return CELL_OK; +} + +error_code sys_raw_spu_get_int_mask(ppu_thread &ppu, u32 id, u32 class_id, + vm::ptr mask) { + ppu.state += cpu_flag::wait; + + sys_spu.trace("sys_raw_spu_get_int_mask(id=%d, class_id=%d, mask=*0x%x)", id, + class_id, mask); + + return raw_spu_get_int_control(id, class_id, mask, &spu_int_ctrl_t::mask); +} + +error_code sys_isolated_spu_get_int_mask(ppu_thread &ppu, u32 id, u32 class_id, + vm::ptr mask) { + ppu.state += cpu_flag::wait; + + sys_spu.trace("sys_isolated_spu_get_int_mask(id=%d, class_id=%d, mask=*0x%x)", + id, class_id, mask); + + return raw_spu_get_int_control(id, class_id, mask, + &spu_int_ctrl_t::mask); +} + +error_code sys_raw_spu_get_int_stat(ppu_thread &ppu, u32 id, u32 class_id, + vm::ptr stat) { + ppu.state += cpu_flag::wait; + + sys_spu.trace("sys_raw_spu_get_int_stat(id=%d, class_id=%d, stat=*0x%x)", id, + class_id, stat); + + return raw_spu_get_int_control(id, class_id, stat, &spu_int_ctrl_t::stat); +} + +error_code sys_isolated_spu_get_int_stat(ppu_thread &ppu, u32 id, u32 class_id, + vm::ptr stat) { + ppu.state += cpu_flag::wait; + + sys_spu.todo("sys_isolated_spu_get_int_stat(id=%d, class_id=%d, stat=*0x%x)", + id, class_id, stat); + + return raw_spu_get_int_control(id, class_id, stat, + &spu_int_ctrl_t::stat); +} + +template +error_code raw_spu_read_puint_mb(u32 id, vm::ptr value) { + const auto thread = + idm::get_unlocked>(spu_thread::find_raw_spu(id)); + + if (!thread || + thread->get_type() != (isolated ? spu_type::isolated : spu_type::raw)) + [[unlikely]] { + return CELL_ESRCH; + } + + cpu_thread::get_current()->check_state(); + *value = thread->ch_out_intr_mbox.pop(); + + return CELL_OK; +} + +error_code sys_raw_spu_read_puint_mb(ppu_thread &ppu, u32 id, + vm::ptr value) { + ppu.state += cpu_flag::wait; + + sys_spu.trace("sys_raw_spu_read_puint_mb(id=%d, value=*0x%x)", id, value); + + return raw_spu_read_puint_mb(id, value); +} + +error_code sys_isolated_spu_read_puint_mb(ppu_thread &ppu, u32 id, + vm::ptr value) { + ppu.state += cpu_flag::wait; + + sys_spu.todo("sys_isolated_spu_read_puint_mb(id=%d, value=*0x%x)", id, value); + + return raw_spu_read_puint_mb(id, value); +} + +template +error_code raw_spu_set_spu_cfg(u32 id, u32 value) { + if (value > 3) { + fmt::throw_exception("Unexpected value (0x%x)", value); + } + + const auto thread = + idm::get_unlocked>(spu_thread::find_raw_spu(id)); + + if (!thread || + thread->get_type() != (isolated ? spu_type::isolated : spu_type::raw)) + [[unlikely]] { + return CELL_ESRCH; + } + + thread->snr_config = value; + + return CELL_OK; +} + +error_code sys_raw_spu_set_spu_cfg(ppu_thread &ppu, u32 id, u32 value) { + ppu.state += cpu_flag::wait; + + sys_spu.trace("sys_raw_spu_set_spu_cfg(id=%d, value=0x%x)", id, value); + + return raw_spu_set_spu_cfg(id, value); +} + +error_code sys_isolated_spu_set_spu_cfg(ppu_thread &ppu, u32 id, u32 value) { + ppu.state += cpu_flag::wait; + + sys_spu.todo("sys_isolated_spu_set_spu_cfg(id=%d, value=0x%x)", id, value); + + return raw_spu_set_spu_cfg(id, value); +} + +template +error_code raw_spu_get_spu_cfg(u32 id, vm::ptr value) { + const auto thread = + idm::get_unlocked>(spu_thread::find_raw_spu(id)); + + if (!thread || + thread->get_type() != (isolated ? spu_type::isolated : spu_type::raw)) + [[unlikely]] { + return CELL_ESRCH; + } + + cpu_thread::get_current()->check_state(); + *value = static_cast(thread->snr_config); + + return CELL_OK; +} + +error_code sys_raw_spu_get_spu_cfg(ppu_thread &ppu, u32 id, + vm::ptr value) { + ppu.state += cpu_flag::wait; + + sys_spu.trace("sys_raw_spu_get_spu_afg(id=%d, value=*0x%x)", id, value); + + return raw_spu_get_spu_cfg(id, value); +} + +error_code sys_isolated_spu_get_spu_cfg(ppu_thread &ppu, u32 id, + vm::ptr value) { + ppu.state += cpu_flag::wait; + + sys_spu.todo("sys_isolated_spu_get_spu_afg(id=%d, value=*0x%x)", id, value); + + return raw_spu_get_spu_cfg(id, value); +} + +error_code sys_isolated_spu_start(ppu_thread &ppu, u32 id) { + ppu.state += cpu_flag::wait; + + sys_spu.todo("sys_isolated_spu_start(id=%d)", id); + + const auto thread = + idm::get_unlocked>(spu_thread::find_raw_spu(id)); + + if (!thread) [[unlikely]] { + return CELL_ESRCH; + } + + // TODO: Can return ESTAT if called twice + thread->write_reg(RAW_SPU_BASE_ADDR + thread->lv2_id * RAW_SPU_OFFSET + + RAW_SPU_PROB_OFFSET + SPU_RunCntl_offs, + SPU_RUNCNTL_RUN_REQUEST); + return CELL_OK; +} diff --git a/kernel/cellos/src/sys_ss.cpp b/kernel/cellos/src/sys_ss.cpp new file mode 100644 index 000000000..75e0d9951 --- /dev/null +++ b/kernel/cellos/src/sys_ss.cpp @@ -0,0 +1,531 @@ +#include "stdafx.h" + +#include "sys_ss.h" + +#include "Emu/Cell/timers.hpp" +#include "Emu/IdManager.h" +#include "Emu/system_config.h" +#include "sys_process.h" +#include "util/sysinfo.hpp" + +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#endif + +struct lv2_update_manager { + lv2_update_manager() { + std::string version_str = utils::get_firmware_version(); + + // For example, 4.90 should be converted to 0x4900000000000 + std::erase(version_str, '.'); + if (std::from_chars(version_str.data(), + version_str.data() + version_str.size(), + system_sw_version, 16) + .ec != std::errc{}) + system_sw_version <<= 40; + else + system_sw_version = 0; + } + + lv2_update_manager(const lv2_update_manager &) = delete; + lv2_update_manager &operator=(const lv2_update_manager &) = delete; + ~lv2_update_manager() = default; + + u64 system_sw_version; + + std::unordered_map eeprom_map // offset, value + { + // system language + // *i think* this gives english + {0x48C18, 0x00}, + {0x48C19, 0x00}, + {0x48C1A, 0x00}, + {0x48C1B, 0x01}, + // system language end + + // vsh target (seems it can be 0xFFFFFFFE, 0xFFFFFFFF, 0x00000001 + // default: 0x00000000 / vsh sets it to 0x00000000 on boot if it isn't + // 0x00000000) + {0x48C1C, 0x00}, + {0x48C1D, 0x00}, + {0x48C1E, 0x00}, + {0x48C1F, 0x00} // vsh target end + }; + mutable std::shared_mutex eeprom_mutex; + + std::unordered_set malloc_set; + mutable std::shared_mutex malloc_mutex; + + // return address + u32 allocate(u32 size) { + std::unique_lock unique_lock(malloc_mutex); + + if (const auto addr = vm::alloc(size, vm::main); addr) { + malloc_set.emplace(addr); + return addr; + } + + return 0; + } + + // return size + u32 deallocate(u32 addr) { + std::unique_lock unique_lock(malloc_mutex); + + if (malloc_set.count(addr)) { + return vm::dealloc(addr, vm::main); + } + + return 0; + } +}; + +template <> +void fmt_class_string::format(std::string &out, u64 arg) { + format_enum(out, arg, [](auto error) { + switch (error) { + STR_CASE(SYS_SS_RNG_ERROR_INVALID_PKG); + STR_CASE(SYS_SS_RNG_ERROR_ENOMEM); + STR_CASE(SYS_SS_RNG_ERROR_EAGAIN); + STR_CASE(SYS_SS_RNG_ERROR_EFAULT); + STR_CASE(SYS_SS_RTC_ERROR_UNK); + } + + return unknown; + }); +} + +LOG_CHANNEL(sys_ss); + +error_code sys_ss_random_number_generator(u64 pkg_id, vm::ptr buf, + u64 size) { + sys_ss.warning( + "sys_ss_random_number_generator(pkg_id=%u, buf=*0x%x, size=0x%x)", pkg_id, + buf, size); + + if (pkg_id != 2) { + if (pkg_id == 1) { + if (!g_ps3_process_info.has_root_perm()) { + return CELL_ENOSYS; + } + + sys_ss.todo("sys_ss_random_number_generator(): pkg_id=1"); + std::memset(buf.get_ptr(), 0, 0x18); + return CELL_OK; + } + + return SYS_SS_RNG_ERROR_INVALID_PKG; + } + + // TODO + if (size > 0x10000000) { + return SYS_SS_RNG_ERROR_ENOMEM; + } + + std::unique_ptr temp(new u8[size]); + +#ifdef _WIN32 + if (auto ret = BCryptGenRandom(nullptr, temp.get(), static_cast(size), + BCRYPT_USE_SYSTEM_PREFERRED_RNG)) { + fmt::throw_exception( + "sys_ss_random_number_generator(): BCryptGenRandom failed (0x%08x)", + ret); + } +#else + fs::file rnd{"/dev/urandom"}; + + if (!rnd || rnd.read(temp.get(), size) != size) { + fmt::throw_exception("sys_ss_random_number_generator(): Failed to generate " + "pseudo-random numbers"); + } +#endif + + std::memcpy(buf.get_ptr(), temp.get(), size); + return CELL_OK; +} + +error_code sys_ss_access_control_engine(u64 pkg_id, u64 a2, u64 a3) { + sys_ss.success( + "sys_ss_access_control_engine(pkg_id=0x%llx, a2=0x%llx, a3=0x%llx)", + pkg_id, a2, a3); + + const u64 authid = + g_ps3_process_info.self_info.valid + ? g_ps3_process_info.self_info.prog_id_hdr.program_authority_id + : 0; + + switch (pkg_id) { + case 0x1: { + if (!g_ps3_process_info.debug_or_root()) { + return not_an_error(CELL_ENOSYS); + } + + if (!a2) { + return CELL_ESRCH; + } + + ensure(a2 == static_cast(process_getpid())); + vm::write64(vm::cast(a3), authid); + break; + } + case 0x2: { + vm::write64(vm::cast(a2), authid); + break; + } + case 0x3: { + if (!g_ps3_process_info.debug_or_root()) { + return CELL_ENOSYS; + } + + break; + } + default: + return 0x8001051du; + } + + return CELL_OK; +} + +error_code sys_ss_get_console_id(vm::ptr buf) { + sys_ss.notice("sys_ss_get_console_id(buf=*0x%x)", buf); + + return sys_ss_appliance_info_manager(0x19003, buf); +} + +error_code sys_ss_get_open_psid(vm::ptr psid) { + sys_ss.notice("sys_ss_get_open_psid(psid=*0x%x)", psid); + + psid->high = g_cfg.sys.console_psid_high; + psid->low = g_cfg.sys.console_psid_low; + + return CELL_OK; +} + +error_code sys_ss_appliance_info_manager(u32 code, vm::ptr buffer) { + sys_ss.notice("sys_ss_appliance_info_manager(code=0x%x, buffer=*0x%x)", code, + buffer); + + if (!g_ps3_process_info.has_root_perm()) + return CELL_ENOSYS; + + if (!buffer) + return CELL_EFAULT; + + switch (code) { + case 0x19002: { + // AIM_get_device_type + constexpr u8 product_code[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x89}; + std::memcpy(buffer.get_ptr(), product_code, 16); + if (g_cfg.core.debug_console_mode) + buffer[15] = 0x81; // DECR + break; + } + case 0x19003: { + // AIM_get_device_id + constexpr u8 idps[] = {0x00, 0x00, 0x00, 0x01, 0x00, 0x89, 0x00, 0x0B, + 0x14, 0x00, 0xEF, 0xDD, 0xCA, 0x25, 0x52, 0x66}; + std::memcpy(buffer.get_ptr(), idps, 16); + if (g_cfg.core.debug_console_mode) { + buffer[5] = 0x81; // DECR + buffer[7] = 0x09; // DECR-1400 + } + break; + } + case 0x19004: { + // AIM_get_ps_code + constexpr u8 pscode[] = {0x00, 0x01, 0x00, 0x85, 0x00, 0x07, 0x00, 0x04}; + std::memcpy(buffer.get_ptr(), pscode, 8); + break; + } + case 0x19005: { + // AIM_get_open_ps_id + be_t psid[2] = {+g_cfg.sys.console_psid_high, + +g_cfg.sys.console_psid_low}; + std::memcpy(buffer.get_ptr(), psid, 16); + break; + } + case 0x19006: { + // qa values (dex only) ?? + [[fallthrough]]; + } + default: + sys_ss.todo("sys_ss_appliance_info_manager(code=0x%x, buffer=*0x%x)", code, + buffer); + } + + return CELL_OK; +} + +error_code sys_ss_get_cache_of_product_mode(vm::ptr ptr) { + sys_ss.todo("sys_ss_get_cache_of_product_mode(ptr=*0x%x)", ptr); + + if (!ptr) { + return CELL_EINVAL; + } + // 0xff Happens when hypervisor call returns an error + // 0 - disabled + // 1 - enabled + + // except something segfaults when using 0, so error it is! + *ptr = 0xFF; + + return CELL_OK; +} + +error_code sys_ss_secure_rtc(u64 cmd, u64 a2, u64 a3, u64 a4) { + sys_ss.todo("sys_ss_secure_rtc(cmd=0x%llx, a2=0x%x, a3=0x%llx, a4=0x%llx)", + cmd, a2, a3, a4); + if (cmd == 0x3001) { + if (a3 != 0x20) + return 0x80010500; // bad packet id + + return CELL_OK; + } else if (cmd == 0x3002) { + // Get time + if (a2 > 1) + return 0x80010500; // bad packet id + + // a3 is actual output, not 100% sure, but best guess is its tb val + vm::write64(::narrow(a3), get_timebased_time()); + // a4 is a pointer to status, non 0 on error + vm::write64(::narrow(a4), 0); + return CELL_OK; + } else if (cmd == 0x3003) { + return CELL_OK; + } + + return 0x80010500; // bad packet id +} + +error_code sys_ss_get_cache_of_flash_ext_flag(vm::ptr flag) { + sys_ss.todo("sys_ss_get_cache_of_flash_ext_flag(flag=*0x%x)", flag); + + if (!flag) { + return CELL_EFAULT; + } + + *flag = 0xFE; // nand vs nor from lsb + + return CELL_OK; +} + +error_code sys_ss_get_boot_device(vm::ptr dev) { + sys_ss.todo("sys_ss_get_boot_device(dev=*0x%x)", dev); + + if (!dev) { + return CELL_EINVAL; + } + + *dev = 0x190; + + return CELL_OK; +} + +error_code sys_ss_update_manager(u64 pkg_id, u64 a1, u64 a2, u64 a3, u64 a4, + u64 a5, u64 a6) { + sys_ss.notice("sys_ss_update_manager(pkg=0x%x, a1=0x%x, a2=0x%x, a3=0x%x, " + "a4=0x%x, a5=0x%x, a6=0x%x)", + pkg_id, a1, a2, a3, a4, a5, a6); + + if (!g_ps3_process_info.has_root_perm()) + return CELL_ENOSYS; + + auto &update_manager = g_fxo->get(); + + switch (pkg_id) { + case 0x6001: { + // update package async + break; + } + case 0x6002: { + // inspect package async + break; + } + case 0x6003: { + // get installed package info + [[maybe_unused]] const auto type = ::narrow(a1); + const auto info_ptr = ::narrow(a2); + + if (!info_ptr) + return CELL_EFAULT; + + vm::write64(info_ptr, update_manager.system_sw_version); + + break; + } + case 0x6004: { + // get fix instruction + break; + } + case 0x6005: { + // extract package async + break; + } + case 0x6006: { + // get extract package + break; + } + case 0x6007: { + // get flash initialized + break; + } + case 0x6008: { + // set flash initialized + break; + } + case 0x6009: { + // get seed token + break; + } + case 0x600A: { + // set seed token + break; + } + case 0x600B: { + // read eeprom + const auto offset = ::narrow(a1); + const auto value_ptr = ::narrow(a2); + + if (!value_ptr) + return CELL_EFAULT; + + std::shared_lock shared_lock(update_manager.eeprom_mutex); + + if (const auto iterator = update_manager.eeprom_map.find(offset); + iterator != update_manager.eeprom_map.end()) + vm::write8(value_ptr, iterator->second); + else + vm::write8(value_ptr, 0xFF); // 0xFF if not set + + break; + } + case 0x600C: { + // write eeprom + const auto offset = ::narrow(a1); + const auto value = ::narrow(a2); + + std::unique_lock unique_lock(update_manager.eeprom_mutex); + + if (value != 0xFF) + update_manager.eeprom_map[offset] = value; + else + update_manager.eeprom_map.erase(offset); // 0xFF: unset + + break; + } + case 0x600D: { + // get async status + break; + } + case 0x600E: { + // allocate buffer + const auto size = ::narrow(a1); + const auto addr_ptr = ::narrow(a2); + + if (!addr_ptr) + return CELL_EFAULT; + + const auto addr = update_manager.allocate(size); + + if (!addr) + return CELL_ENOMEM; + + vm::write32(addr_ptr, addr); + + break; + } + case 0x600F: { + // release buffer + const auto addr = ::narrow(a1); + + if (!update_manager.deallocate(addr)) + return CELL_ENOMEM; + + break; + } + case 0x6010: { + // check integrity + break; + } + case 0x6011: { + // get applicable version + const auto addr_ptr = ::narrow(a2); + + if (!addr_ptr) + return CELL_EFAULT; + + vm::write64(addr_ptr, 0x30040ULL << 32); // 3.40 + + break; + } + case 0x6012: { + // allocate buffer from memory container + [[maybe_unused]] const auto mem_ct = ::narrow(a1); + const auto size = ::narrow(a2); + const auto addr_ptr = ::narrow(a3); + + if (!addr_ptr) + return CELL_EFAULT; + + const auto addr = update_manager.allocate(size); + + if (!addr) + return CELL_ENOMEM; + + vm::write32(addr_ptr, addr); + + break; + } + case 0x6013: { + // unknown + break; + } + default: { + sys_ss.error("sys_ss_update_manager(): invalid packet id 0x%x ", pkg_id); + return CELL_EINVAL; + } + } + + return CELL_OK; +} + +error_code sys_ss_virtual_trm_manager(u64 pkg_id, u64 a1, u64 a2, u64 a3, + u64 a4) { + sys_ss.todo("sys_ss_virtual_trm_manager(pkg=0x%llx, a1=0x%llx, a2=0x%llx, " + "a3=0x%llx, a4=0x%llx)", + pkg_id, a1, a2, a3, a4); + + return CELL_OK; +} + +error_code sys_ss_individual_info_manager(u64 pkg_id, u64 a2, + vm::ptr out_size, u64 a4, u64 a5, + u64 a6) { + sys_ss.todo("sys_ss_individual_info_manager(pkg=0x%llx, a2=0x%llx, " + "out_size=*0x%llx, a4=0x%llx, a5=0x%llx, a6=0x%llx)", + pkg_id, a2, out_size, a4, a5, a6); + + switch (pkg_id) { + // Read EID + case 0x17002: { + // TODO + vm::_ref(a5) = a4; // Write back size of buffer + break; + } + // Get EID size + case 0x17001: + *out_size = 0x100; + break; + default: + break; + } + + return CELL_OK; +} diff --git a/kernel/cellos/src/sys_storage.cpp b/kernel/cellos/src/sys_storage.cpp new file mode 100644 index 000000000..3dc934ce3 --- /dev/null +++ b/kernel/cellos/src/sys_storage.cpp @@ -0,0 +1,456 @@ +#include "stdafx.h" + +#include "Emu/IdManager.h" + +#include "Emu/Cell/ErrorCodes.h" +#include "sys_event.h" +#include "sys_fs.h" +#include "util/shared_ptr.hpp" + +#include "sys_storage.h" + +LOG_CHANNEL(sys_storage); + +namespace { +struct storage_manager { + // This is probably wrong and should be assigned per fd or something + atomic_ptr asyncequeue; +}; +} // namespace + +error_code sys_storage_open(u64 device, u64 mode, vm::ptr fd, u64 flags) { + sys_storage.todo( + "sys_storage_open(device=0x%x, mode=0x%x, fd=*0x%x, flags=0x%x)", device, + mode, fd, flags); + + if (device == 0) { + return CELL_ENOENT; + } + + if (!fd) { + return CELL_EFAULT; + } + + [[maybe_unused]] u64 storage_id = device & 0xFFFFF00FFFFFFFF; + fs::file file; + + if (const u32 id = + idm::make(device, std::move(file), mode, flags)) { + *fd = id; + return CELL_OK; + } + + return CELL_EAGAIN; +} + +error_code sys_storage_close(u32 fd) { + sys_storage.todo("sys_storage_close(fd=0x%x)", fd); + + idm::remove(fd); + + return CELL_OK; +} + +error_code sys_storage_read(u32 fd, u32 mode, u32 start_sector, u32 num_sectors, + vm::ptr bounce_buf, vm::ptr sectors_read, + u64 flags) { + sys_storage.todo( + "sys_storage_read(fd=0x%x, mode=0x%x, start_sector=0x%x, " + "num_sectors=0x%x, bounce_buf=*0x%x, sectors_read=*0x%x, flags=0x%x)", + fd, mode, start_sector, num_sectors, bounce_buf, sectors_read, flags); + + if (!bounce_buf || !sectors_read) { + return CELL_EFAULT; + } + + std::memset(bounce_buf.get_ptr(), 0, num_sectors * 0x200ull); + const auto handle = idm::get_unlocked(fd); + + if (!handle) { + return CELL_ESRCH; + } + + if (handle->file) { + handle->file.seek(start_sector * 0x200ull); + const u64 size = num_sectors * 0x200ull; + const u64 result = lv2_file::op_read(handle->file, bounce_buf, size); + num_sectors = ::narrow(result / 0x200ull); + } + + *sectors_read = num_sectors; + + return CELL_OK; +} + +error_code sys_storage_write(u32 fd, u32 mode, u32 start_sector, + u32 num_sectors, vm::ptr data, + vm::ptr sectors_wrote, u64 flags) { + sys_storage.todo( + "sys_storage_write(fd=0x%x, mode=0x%x, start_sector=0x%x, " + "num_sectors=0x%x, data=*=0x%x, sectors_wrote=*0x%x, flags=0x%llx)", + fd, mode, start_sector, num_sectors, data, sectors_wrote, flags); + + if (!sectors_wrote) { + return CELL_EFAULT; + } + + const auto handle = idm::get_unlocked(fd); + + if (!handle) { + return CELL_ESRCH; + } + + *sectors_wrote = num_sectors; + + return CELL_OK; +} + +error_code sys_storage_send_device_command(u32 dev_handle, u64 cmd, + vm::ptr in, u64 inlen, + vm::ptr out, u64 outlen) { + sys_storage.todo("sys_storage_send_device_command(dev_handle=0x%x, " + "cmd=0x%llx, in=*0x%, inlen=0x%x, out=*0x%x, outlen=0x%x)", + dev_handle, cmd, in, inlen, out, outlen); + + return CELL_OK; +} + +error_code sys_storage_async_configure(u32 fd, u32 io_buf, u32 equeue_id, + u32 unk) { + sys_storage.todo("sys_storage_async_configure(fd=0x%x, io_buf=0x%x, " + "equeue_id=0x%x, unk=*0x%x)", + fd, io_buf, equeue_id, unk); + + auto &manager = g_fxo->get(); + + if (auto queue = idm::get_unlocked(equeue_id)) { + manager.asyncequeue.store(queue); + } else { + return CELL_ESRCH; + } + + return CELL_OK; +} + +error_code sys_storage_async_send_device_command(u32 dev_handle, u64 cmd, + vm::ptr in, u64 inlen, + vm::ptr out, u64 outlen, + u64 unk) { + sys_storage.todo( + "sys_storage_async_send_device_command(dev_handle=0x%x, cmd=0x%llx, " + "in=*0x%x, inlen=0x%x, out=*0x%x, outlen=0x%x, unk=0x%x)", + dev_handle, cmd, in, inlen, out, outlen, unk); + + auto &manager = g_fxo->get(); + + if (auto q = manager.asyncequeue.load()) { + q->send(0, unk, unk, unk); + } + + return CELL_OK; +} + +error_code sys_storage_async_read() { + sys_storage.todo("sys_storage_async_read()"); + + return CELL_OK; +} + +error_code sys_storage_async_write() { + sys_storage.todo("sys_storage_async_write()"); + + return CELL_OK; +} + +error_code sys_storage_async_cancel() { + sys_storage.todo("sys_storage_async_cancel()"); + + return CELL_OK; +} + +error_code sys_storage_get_device_info(u64 device, + vm::ptr buffer) { + sys_storage.todo("sys_storage_get_device_info(device=0x%x, buffer=*0x%x)", + device, buffer); + + if (!buffer) { + return CELL_EFAULT; + } + + memset(buffer.get_ptr(), 0, sizeof(StorageDeviceInfo)); + + u64 storage = device & 0xFFFFF00FFFFFFFF; + u32 dev_num = (device >> 32) & 0xFF; + + if (storage == ATA_HDD) // dev_hdd? + { + if (dev_num > 2) { + return not_an_error(-5); + } + + std::string u = "unnamed"; + memcpy(buffer->name, u.c_str(), u.size()); + buffer->sector_size = 0x200; + buffer->one = 1; + buffer->flags[1] = 1; + buffer->flags[2] = 1; + buffer->flags[7] = 1; + + // set partition size based on dev_num + // stole these sizes from kernel dump, unknown if they are 100% correct + // vsh reports only 2 partitions even though there is 3 sizes + switch (dev_num) { + case 0: + buffer->sector_count = 0x2542EAB0; // possibly total size + break; + case 1: + buffer->sector_count = 0x24FAEA98; // which makes this hdd0 + break; + case 2: + buffer->sector_count = 0x3FFFF8; // and this one hdd1 + break; + } + } else if (storage == BDVD_DRIVE) // dev_bdvd? + { + if (dev_num > 0) { + return not_an_error(-5); + } + + std::string u = "unnamed"; + memcpy(buffer->name, u.c_str(), u.size()); + buffer->sector_count = 0x4D955; + buffer->sector_size = 0x800; + buffer->one = 1; + buffer->flags[1] = 0; + buffer->flags[2] = 1; + buffer->flags[7] = 1; + } else if (storage == USB_MASS_STORAGE_1(0)) { + if (dev_num > 0) { + return not_an_error(-5); + } + + std::string u = "unnamed"; + memcpy(buffer->name, u.c_str(), u.size()); + /*buffer->sector_count = 0x4D955;*/ + buffer->sector_size = 0x200; + buffer->one = 1; + buffer->flags[1] = 0; + buffer->flags[2] = 1; + buffer->flags[7] = 1; + } else if (storage == NAND_FLASH) { + if (dev_num > 6) { + return not_an_error(-5); + } + + std::string u = "unnamed"; + memcpy(buffer->name, u.c_str(), u.size()); + buffer->sector_size = 0x200; + buffer->one = 1; + buffer->flags[1] = 1; + buffer->flags[2] = 1; + buffer->flags[7] = 1; + + // see ata_hdd for explanation + switch (dev_num) { + case 0: + buffer->sector_count = 0x80000; + break; + case 1: + buffer->sector_count = 0x75F8; + break; + case 2: + buffer->sector_count = 0x63E00; + break; + case 3: + buffer->sector_count = 0x8000; + break; + case 4: + buffer->sector_count = 0x400; + break; + case 5: + buffer->sector_count = 0x2000; + break; + case 6: + buffer->sector_count = 0x200; + break; + } + } else if (storage == NOR_FLASH) { + if (dev_num > 3) { + return not_an_error(-5); + } + + std::string u = "unnamed"; + memcpy(buffer->name, u.c_str(), u.size()); + buffer->sector_size = 0x200; + buffer->one = 1; + buffer->flags[1] = 0; + buffer->flags[2] = 1; + buffer->flags[7] = 1; + + // see ata_hdd for explanation + switch (dev_num) { + case 0: + buffer->sector_count = 0x8000; + break; + case 1: + buffer->sector_count = 0x77F8; + break; + case 2: + buffer->sector_count = 0x100; // offset, 0x20000 + break; + case 3: + buffer->sector_count = 0x400; + break; + } + } else if (storage == NAND_UNK) { + if (dev_num > 1) { + return not_an_error(-5); + } + + std::string u = "unnamed"; + memcpy(buffer->name, u.c_str(), u.size()); + buffer->sector_size = 0x800; + buffer->one = 1; + buffer->flags[1] = 0; + buffer->flags[2] = 1; + buffer->flags[7] = 1; + + // see ata_hdd for explanation + switch (dev_num) { + case 0: + buffer->sector_count = 0x7FFFFFFF; + break; + } + } else { + sys_storage.error("sys_storage_get_device_info(device=0x%x, buffer=*0x%x)", + device, buffer); + } + + return CELL_OK; +} + +error_code sys_storage_get_device_config(vm::ptr storages, + vm::ptr devices) { + sys_storage.todo( + "sys_storage_get_device_config(storages=*0x%x, devices=*0x%x)", storages, + devices); + + if (storages) + *storages = 6; + else + return CELL_EFAULT; + if (devices) + *devices = 17; + else + return CELL_EFAULT; + + return CELL_OK; +} + +error_code sys_storage_report_devices(u32 storages, u32 start, u32 devices, + vm::ptr device_ids) { + sys_storage.todo("sys_storage_report_devices(storages=0x%x, start=0x%x, " + "devices=0x%x, device_ids=0x%x)", + storages, start, devices, device_ids); + + if (!device_ids) { + return CELL_EFAULT; + } + + static constexpr std::array all_devs = [] { + std::array all_devs{}; + all_devs[0] = 0x10300000000000A; + + for (int i = 0; i < 7; ++i) { + all_devs[i + 1] = 0x100000000000001 | (static_cast(i) << 32); + } + + for (int i = 0; i < 3; ++i) { + all_devs[i + 8] = 0x101000000000007 | (static_cast(i) << 32); + } + + all_devs[11] = 0x101000000000006; + + for (int i = 0; i < 4; ++i) { + all_devs[i + 12] = 0x100000000000004 | (static_cast(i) << 32); + } + + all_devs[16] = 0x100000000000003; + return all_devs; + }(); + + if (!devices || start >= all_devs.size() || + devices > all_devs.size() - start) { + return CELL_EINVAL; + } + + std::copy_n(all_devs.begin() + start, devices, device_ids.get_ptr()); + + return CELL_OK; +} + +error_code sys_storage_configure_medium_event(u32 fd, u32 equeue_id, u32 c) { + sys_storage.todo( + "sys_storage_configure_medium_event(fd=0x%x, equeue_id=0x%x, c=0x%x)", fd, + equeue_id, c); + + return CELL_OK; +} + +error_code sys_storage_set_medium_polling_interval() { + sys_storage.todo("sys_storage_set_medium_polling_interval()"); + + return CELL_OK; +} + +error_code sys_storage_create_region() { + sys_storage.todo("sys_storage_create_region()"); + + return CELL_OK; +} + +error_code sys_storage_delete_region() { + sys_storage.todo("sys_storage_delete_region()"); + + return CELL_OK; +} + +error_code sys_storage_execute_device_command( + u32 fd, u64 cmd, vm::ptr cmdbuf, u64 cmdbuf_size, + vm::ptr databuf, u64 databuf_size, vm::ptr driver_status) { + sys_storage.todo("sys_storage_execute_device_command(fd=0x%x, cmd=0x%llx, " + "cmdbuf=*0x%x, cmdbuf_size=0x%llx, databuf=*0x%x, " + "databuf_size=0x%llx, driver_status=*0x%x)", + fd, cmd, cmdbuf, cmdbuf_size, databuf, databuf_size, + driver_status); + + // cmd == 2 is get device info, + // databuf, first byte 0 == status ok? + // byte 1, if < 0 , not ata device + return CELL_OK; +} + +error_code sys_storage_check_region_acl() { + sys_storage.todo("sys_storage_check_region_acl()"); + + return CELL_OK; +} + +error_code sys_storage_set_region_acl() { + sys_storage.todo("sys_storage_set_region_acl()"); + + return CELL_OK; +} + +error_code sys_storage_get_region_offset() { + sys_storage.todo("sys_storage_get_region_offset()"); + + return CELL_OK; +} + +error_code sys_storage_set_emulated_speed() { + sys_storage.todo("sys_storage_set_emulated_speed()"); + + // todo: only debug kernel has this + return CELL_ENOSYS; +} diff --git a/kernel/cellos/src/sys_time.cpp b/kernel/cellos/src/sys_time.cpp new file mode 100644 index 000000000..06ea57d17 --- /dev/null +++ b/kernel/cellos/src/sys_time.cpp @@ -0,0 +1,446 @@ +#include "stdafx.h" + +#include "sys_time.h" + +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/Cell/timers.hpp" +#include "Emu/system_config.h" +#include "sys_process.h" +#include "util/tsc.hpp" + +#include "util/sysinfo.hpp" + +u64 g_timebase_offs{}; +static u64 systemtime_offset; + +#ifndef __linux__ +#include "util/asm.hpp" +#endif + +#ifdef _WIN32 + +#include + +struct time_aux_info_t { + u64 perf_freq; + u64 start_time; + u64 start_ftime; // time in 100ns units since Epoch +}; + +// Initialize time-related values +const auto s_time_aux_info = []() -> time_aux_info_t { + LARGE_INTEGER freq; + if (!QueryPerformanceFrequency(&freq)) { + MessageBox( + nullptr, + L"Your hardware doesn't support a high-resolution performance counter", + L"Error", MB_OK | MB_ICONERROR); + return {}; + } + + LARGE_INTEGER start; + QueryPerformanceCounter(&start); // get time in 1/perf_freq units from RDTSC + + FILETIME ftime; + GetSystemTimeAsFileTime( + &ftime); // get time in 100ns units since January 1, 1601 (UTC) + + time_aux_info_t result; + result.perf_freq = freq.QuadPart; + result.start_time = start.QuadPart; + result.start_ftime = + (ftime.dwLowDateTime | static_cast(ftime.dwHighDateTime) << 32) - + 116444736000000000; + + return result; +}(); + +#elif __APPLE__ + +// XXX only supports a single timer +#if !defined(HAVE_CLOCK_GETTIME) +#define TIMER_ABSTIME -1 +// The opengroup spec isn't clear on the mapping from REALTIME to CALENDAR being +// appropriate or not. +// http://pubs.opengroup.org/onlinepubs/009695299/basedefs/time.h.html +#define CLOCK_REALTIME 1 // #define CALENDAR_CLOCK 1 from mach/clock_types.h +#define CLOCK_MONOTONIC 0 // #define SYSTEM_CLOCK 0 + +// the mach kernel uses struct mach_timespec, so struct timespec is loaded from +// for compatability struct timespec { time_t tv_sec; +// long tv_nsec; }; + +#include +#include +#include +#include +#include +#undef CPU_STATE_MAX + +#define MT_NANO (+1.0E-9) +#define MT_GIGA UINT64_C(1000000000) + +// TODO create a list of timers, +static double mt_timebase = 0.0; +static u64 mt_timestart = 0; + +static int clock_gettime(int clk_id, struct timespec *tp) { + kern_return_t retval = KERN_SUCCESS; + if (clk_id == TIMER_ABSTIME) { + if (!mt_timestart) { + // only one timer, initilized on the first call to the TIMER + mach_timebase_info_data_t tb = {0}; + mach_timebase_info(&tb); + mt_timebase = tb.numer; + mt_timebase /= tb.denom; + mt_timestart = mach_absolute_time(); + } + + double diff = (mach_absolute_time() - mt_timestart) * mt_timebase; + tp->tv_sec = diff * MT_NANO; + tp->tv_nsec = diff - (tp->tv_sec * MT_GIGA); + } else // other clk_ids are mapped to the coresponding mach clock_service + { + clock_serv_t cclock; + mach_timespec_t mts; + + host_get_clock_service(mach_host_self(), clk_id, &cclock); + retval = clock_get_time(cclock, &mts); + mach_port_deallocate(mach_task_self(), cclock); + + tp->tv_sec = mts.tv_sec; + tp->tv_nsec = mts.tv_nsec; + } + + return retval; +} +#endif + +#endif + +#ifndef _WIN32 + +#include + +static struct timespec start_time = []() { + struct timespec ts; + + if (::clock_gettime(CLOCK_REALTIME, &ts) != 0) { + // Fatal error + std::terminate(); + } + + tzset(); + + return ts; +}(); + +#endif + +LOG_CHANNEL(sys_time); + +static constexpr u64 g_timebase_freq = /*79800000*/ 80000000ull; // 80 Mhz + +// Convert time is microseconds to timebased time +u64 convert_to_timebased_time(u64 time) { + const u64 result = + time * (g_timebase_freq / 1000000ull) * g_cfg.core.clocks_scale / 100u; + ensure(result >= g_timebase_offs); + return result - g_timebase_offs; +} + +u64 get_timebased_time() { + if (u64 freq = utils::get_tsc_freq()) { + const u64 tsc = utils::get_tsc(); + +#if _MSC_VER + const u64 result = + static_cast(u128_from_mul(tsc, g_timebase_freq) / freq) * + g_cfg.core.clocks_scale / 100u; +#else + const u64 result = + (tsc / freq * g_timebase_freq + tsc % freq * g_timebase_freq / freq) * + g_cfg.core.clocks_scale / 100u; +#endif + return result - g_timebase_offs; + } + + while (true) { +#ifdef _WIN32 + LARGE_INTEGER count; + ensure(QueryPerformanceCounter(&count)); + + const u64 time = count.QuadPart; + const u64 freq = s_time_aux_info.perf_freq; + +#if _MSC_VER + const u64 result = static_cast( + u128_from_mul(time * g_cfg.core.clocks_scale, g_timebase_freq) / freq / + 100u); +#else + const u64 result = + (time / freq * g_timebase_freq + time % freq * g_timebase_freq / freq) * + g_cfg.core.clocks_scale / 100u; +#endif +#else + struct timespec ts; + ensure(::clock_gettime(CLOCK_MONOTONIC, &ts) == 0); + + const u64 result = + (static_cast(ts.tv_sec) * g_timebase_freq + + static_cast(ts.tv_nsec) * g_timebase_freq / 1000000000ull) * + g_cfg.core.clocks_scale / 100u; +#endif + if (result) + return result - g_timebase_offs; + } +} + +// Add an offset to get_timebased_time to avoid leaking PC's uptime into the +// game As if PS3 starts at value 0 (base time) when the game boots If none-zero +// arg is specified it will become the base time (for savestates) +void initialize_timebased_time(u64 timebased_init, bool reset) { + g_timebase_offs = 0; + + if (reset) { + // We simply want to zero-out these values + systemtime_offset = 0; + return; + } + + const u64 current = get_timebased_time(); + timebased_init = current - timebased_init; + + g_timebase_offs = timebased_init; + systemtime_offset = timebased_init / (g_timebase_freq / 1000000); +} + +// Returns some relative time in microseconds, don't change this fact +u64 get_system_time() { + if (u64 freq = utils::get_tsc_freq()) { + const u64 tsc = utils::get_tsc(); + +#if _MSC_VER + const u64 result = static_cast(u128_from_mul(tsc, 1000000ull) / freq); +#else + const u64 result = + (tsc / freq * 1000000ull + tsc % freq * 1000000ull / freq); +#endif + return result; + } + + while (true) { +#ifdef _WIN32 + LARGE_INTEGER count; + ensure(QueryPerformanceCounter(&count)); + + const u64 time = count.QuadPart; + const u64 freq = s_time_aux_info.perf_freq; + +#if _MSC_VER + const u64 result = static_cast(u128_from_mul(time, 1000000ull) / freq); +#else + const u64 result = + time / freq * 1000000ull + (time % freq) * 1000000ull / freq; +#endif +#else + struct timespec ts; + ensure(::clock_gettime(CLOCK_MONOTONIC, &ts) == 0); + + const u64 result = static_cast(ts.tv_sec) * 1000000ull + + static_cast(ts.tv_nsec) / 1000u; +#endif + + if (result) + return result; + } +} + +// As get_system_time but obeys Clocks scaling setting +u64 get_guest_system_time(u64 time) { + const u64 result = + (time != umax ? time : get_system_time()) * g_cfg.core.clocks_scale / 100; + return result - systemtime_offset; +} + +// Functions +error_code sys_time_set_timezone(s32 timezone, s32 summertime) { + sys_time.trace("sys_time_set_timezone(timezone=0x%x, summertime=0x%x)", + timezone, summertime); + + if (!g_ps3_process_info.has_root_perm()) { + return CELL_ENOSYS; + } + + return CELL_OK; +} + +error_code sys_time_get_timezone(vm::ptr timezone, + vm::ptr summertime) { + sys_time.trace("sys_time_get_timezone(timezone=*0x%x, summertime=*0x%x)", + timezone, summertime); + +#ifdef _WIN32 + TIME_ZONE_INFORMATION tz{}; + switch (GetTimeZoneInformation(&tz)) { + case TIME_ZONE_ID_UNKNOWN: { + *timezone = -tz.Bias; + *summertime = 0; + break; + } + case TIME_ZONE_ID_STANDARD: { + *timezone = -tz.Bias; + *summertime = -tz.StandardBias; + + if (tz.StandardBias) { + sys_time.error("Unexpected timezone bias (base=%d, std=%d, daylight=%d)", + tz.Bias, tz.StandardBias, tz.DaylightBias); + } + break; + } + case TIME_ZONE_ID_DAYLIGHT: { + *timezone = -tz.Bias; + *summertime = -tz.DaylightBias; + break; + } + default: { + ensure(0); + } + } +#elif __linux__ + *timezone = ::narrow(-::timezone / 60); + *summertime = !::daylight ? 0 : []() -> s32 { + struct tm test{}; + ensure(&test == localtime_r(&start_time.tv_sec, &test)); + + // Check bounds [0,1] + if (test.tm_isdst & -2) { + sys_time.error( + "No information for timezone DST bias (timezone=%.2fh, tm_gmtoff=%d)", + -::timezone / 3600.0, test.tm_gmtoff); + return 0; + } else { + return test.tm_isdst ? ::narrow((test.tm_gmtoff + ::timezone) / 60) + : 0; + } + }(); +#else + // gettimeofday doesn't return timezone on linux anymore, but this should work + // on other OSes? + struct timezone tz{}; + ensure(gettimeofday(nullptr, &tz) == 0); + *timezone = ::narrow(-tz.tz_minuteswest); + *summertime = !tz.tz_dsttime ? 0 : [&]() -> s32 { + struct tm test{}; + ensure(&test == localtime_r(&start_time.tv_sec, &test)); + + return test.tm_isdst + ? ::narrow(test.tm_gmtoff / 60 + tz.tz_minuteswest) + : 0; + }(); +#endif + + return CELL_OK; +} + +error_code sys_time_get_current_time(vm::ptr sec, vm::ptr nsec) { + sys_time.trace("sys_time_get_current_time(sec=*0x%x, nsec=*0x%x)", sec, nsec); + + if (!sec) { + return CELL_EFAULT; + } + +#ifdef _WIN32 + LARGE_INTEGER count; + ensure(QueryPerformanceCounter(&count)); + + const u64 diff_base = count.QuadPart - s_time_aux_info.start_time; + + // Get time difference in nanoseconds (using 128 bit accumulator) + const u64 diff_sl = diff_base * 1000000000ull; + const u64 diff_sh = utils::umulh64(diff_base, 1000000000ull); + const u64 diff = utils::udiv128(diff_sh, diff_sl, s_time_aux_info.perf_freq); + + // get time since Epoch in nanoseconds + const u64 time = s_time_aux_info.start_ftime * 100u + + (diff * g_cfg.core.clocks_scale / 100u); + + // scale to seconds, and add the console time offset (which might be negative) + *sec = (time / 1000000000ull) + g_cfg.sys.console_time_offset; + + if (!nsec) { + return CELL_EFAULT; + } + + *nsec = time % 1000000000ull; +#else + struct timespec ts; + ensure(::clock_gettime(CLOCK_REALTIME, &ts) == 0); + + if (g_cfg.core.clocks_scale == 100) { + // get the seconds from the system clock, and add the console time offset + // (which might be negative) + *sec = ts.tv_sec + g_cfg.sys.console_time_offset; + + if (!nsec) { + return CELL_EFAULT; + } + + *nsec = ts.tv_nsec; + return CELL_OK; + } + + u64 tv_sec = ts.tv_sec, stv_sec = start_time.tv_sec; + u64 tv_nsec = ts.tv_nsec, stv_nsec = start_time.tv_nsec; + + // Substruct time since Epoch and since start time + tv_sec -= stv_sec; + + if (tv_nsec < stv_nsec) { + // Correct value if borrow encountered + tv_sec -= 1; + tv_nsec = 1'000'000'000ull - (stv_nsec - tv_nsec); + } else { + tv_nsec -= stv_nsec; + } + + // Scale nanocseconds + tv_nsec = stv_nsec + (tv_nsec * g_cfg.core.clocks_scale / 100); + + // Scale seconds and add from nanoseconds / 1'000'000'000, and add the console + // time offset (which might be negative) + *sec = stv_sec + (tv_sec * g_cfg.core.clocks_scale / 100u) + + (tv_nsec / 1000000000ull) + g_cfg.sys.console_time_offset; + + if (!nsec) { + return CELL_EFAULT; + } + + // Set nanoseconds + *nsec = tv_nsec % 1000000000ull; +#endif + + return CELL_OK; +} + +error_code sys_time_set_current_time(s64 sec, s64 nsec) { + sys_time.trace("sys_time_set_current_time(sec=0x%x, nsec=0x%x)", sec, nsec); + + if (!g_ps3_process_info.has_root_perm()) { + return CELL_ENOSYS; + } + + return CELL_OK; +} + +u64 sys_time_get_timebase_frequency() { + sys_time.trace("sys_time_get_timebase_frequency()"); + + return g_timebase_freq; +} + +error_code sys_time_get_rtc(vm::ptr rtc) { + sys_time.todo("sys_time_get_rtc(rtc=*0x%x)", rtc); + + return CELL_OK; +} diff --git a/kernel/cellos/src/sys_timer.cpp b/kernel/cellos/src/sys_timer.cpp new file mode 100644 index 000000000..5409081f6 --- /dev/null +++ b/kernel/cellos/src/sys_timer.cpp @@ -0,0 +1,450 @@ +#include "stdafx.h" + +#include "sys_timer.h" + +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/Cell/PPUThread.h" +#include "Emu/Cell/timers.hpp" +#include "Emu/IdManager.h" + +#include "Emu/System.h" +#include "Emu/system_config.h" +#include "sys_event.h" +#include "sys_process.h" +#include "util/asm.hpp" + +#include +#include + +LOG_CHANNEL(sys_timer); + +struct lv2_timer_thread { + shared_mutex mutex; + std::deque> timers; + + lv2_timer_thread(); + void operator()(); + + // SAVESTATE_INIT_POS(46); // FREE SAVESTATE_INIT_POS number + + static constexpr auto thread_name = "Timer Thread"sv; +}; + +lv2_timer::lv2_timer(utils::serial &ar) + : lv2_obj(1), state(ar), port(lv2_event_queue::load_ptr(ar, port, "timer")), + source(ar), data1(ar), data2(ar), expire(ar), period(ar) {} + +void lv2_timer::save(utils::serial &ar) { + USING_SERIALIZATION_VERSION(lv2_sync); + ar(state), lv2_event_queue::save_ptr(ar, port.get()), + ar(source, data1, data2, expire, period); +} + +u64 lv2_timer::check(u64 _now) noexcept { + while (true) { + const u32 _state = +state; + + if (_state == SYS_TIMER_STATE_RUN) { + u64 next = expire; + + // If aborting, perform the last accurate check for event + if (_now >= next) { + lv2_obj::notify_all_t notify; + + std::lock_guard lock(mutex); + return check_unlocked(_now); + } + + return (next - _now); + } + + break; + } + + return umax; +} + +u64 lv2_timer::check_unlocked(u64 _now) noexcept { + const u64 next = expire; + + if (_now < next || state != SYS_TIMER_STATE_RUN) { + return umax; + } + + if (port) { + port->send(source, data1, data2, next); + } + + if (period) { + // Set next expiration time and check again + const u64 expire0 = utils::add_saturate(next, period); + expire.release(expire0); + return utils::sub_saturate(expire0, _now); + } + + // Stop after oneshot + state.release(SYS_TIMER_STATE_STOP); + return umax; +} + +lv2_timer_thread::lv2_timer_thread() { + Emu.PostponeInitCode([this]() { + idm::select([&](u32 id, lv2_timer &) { + timers.emplace_back(idm::get_unlocked(id)); + }); + }); +} + +void lv2_timer_thread::operator()() { + u64 sleep_time = 0; + + while (true) { + if (sleep_time != umax) { + // Scale time + sleep_time = + std::min(sleep_time, u64{umax} / 100) * 100 / g_cfg.core.clocks_scale; + } + + thread_ctrl::wait_for(sleep_time); + + if (thread_ctrl::state() == thread_state::aborting) { + break; + } + + sleep_time = umax; + + if (Emu.IsPausedOrReady()) { + sleep_time = 10000; + continue; + } + + const u64 _now = get_guest_system_time(); + + reader_lock lock(mutex); + + for (const auto &timer : timers) { + while (lv2_obj::check(timer)) { + if (thread_ctrl::state() == thread_state::aborting) { + break; + } + + if (const u64 advised_sleep_time = timer->check(_now)) { + if (sleep_time > advised_sleep_time) { + sleep_time = advised_sleep_time; + } + + break; + } + } + } + } +} + +error_code sys_timer_create(ppu_thread &ppu, vm::ptr timer_id) { + ppu.state += cpu_flag::wait; + + sys_timer.warning("sys_timer_create(timer_id=*0x%x)", timer_id); + + if (auto ptr = idm::make_ptr()) { + auto &thread = g_fxo->get>(); + { + std::lock_guard lock(thread.mutex); + + // Theoretically could have been destroyed by sys_timer_destroy by now + if (auto it = std::find(thread.timers.begin(), thread.timers.end(), ptr); + it == thread.timers.end()) { + thread.timers.emplace_back(std::move(ptr)); + } + } + + ppu.check_state(); + *timer_id = idm::last_id(); + return CELL_OK; + } + + return CELL_EAGAIN; +} + +error_code sys_timer_destroy(ppu_thread &ppu, u32 timer_id) { + ppu.state += cpu_flag::wait; + + sys_timer.warning("sys_timer_destroy(timer_id=0x%x)", timer_id); + + auto timer = idm::withdraw( + timer_id, [&](lv2_timer &timer) -> CellError { + if (reader_lock lock(timer.mutex); lv2_obj::check(timer.port)) { + return CELL_EISCONN; + } + + timer.exists--; + return {}; + }); + + if (!timer) { + return CELL_ESRCH; + } + + if (timer.ret) { + return timer.ret; + } + + auto &thread = g_fxo->get>(); + std::lock_guard lock(thread.mutex); + + if (auto it = + std::find(thread.timers.begin(), thread.timers.end(), timer.ptr); + it != thread.timers.end()) { + thread.timers.erase(it); + } + + return CELL_OK; +} + +error_code sys_timer_get_information(ppu_thread &ppu, u32 timer_id, + vm::ptr info) { + ppu.state += cpu_flag::wait; + + sys_timer.trace("sys_timer_get_information(timer_id=0x%x, info=*0x%x)", + timer_id, info); + + sys_timer_information_t _info{}; + const u64 now = get_guest_system_time(); + + const auto timer = + idm::check(timer_id, [&](lv2_timer &timer) { + std::lock_guard lock(timer.mutex); + + timer.check_unlocked(now); + timer.get_information(_info); + }); + + if (!timer) { + return CELL_ESRCH; + } + + ppu.check_state(); + std::memcpy(info.get_ptr(), &_info, info.size()); + return CELL_OK; +} + +error_code _sys_timer_start(ppu_thread &ppu, u32 timer_id, u64 base_time, + u64 period) { + ppu.state += cpu_flag::wait; + + (period ? sys_timer.warning : sys_timer.trace)( + "_sys_timer_start(timer_id=0x%x, base_time=0x%llx, period=0x%llx)", + timer_id, base_time, period); + + const u64 start_time = get_guest_system_time(); + + if (period && period < 100) { + // Invalid periodic timer + return CELL_EINVAL; + } + + const auto timer = idm::check( + timer_id, [&](lv2_timer &timer) -> CellError { + std::lock_guard lock(timer.mutex); + + // LV2 Disassembly: Simple nullptr check (assignment test, do not use + // lv2_obj::check here) + if (!timer.port) { + return CELL_ENOTCONN; + } + + timer.check_unlocked(start_time); + if (timer.state != SYS_TIMER_STATE_STOP) { + return CELL_EBUSY; + } + + if (!period && start_time >= base_time) { + // Invalid oneshot + return CELL_ETIMEDOUT; + } + + const u64 expire = + period == 0 ? base_time : // oneshot + base_time == 0 + ? utils::add_saturate(start_time, period) + : + // periodic timer with no base (using start time as base) + start_time < utils::add_saturate(base_time, period) + ? utils::add_saturate(base_time, period) + : + // periodic with base time over start time + [&]() -> u64 // periodic timer base before start time (align to + // be at least a period over start time) + { + // Optimized from a loop in LV2: + // do + // { + // base_time += period; + // } + // while (base_time < start_time); + + const u64 start_time_with_base_time_reminder = utils::add_saturate( + start_time - start_time % period, base_time % period); + + return utils::add_saturate( + start_time_with_base_time_reminder, + start_time_with_base_time_reminder < start_time ? period : 0); + }(); + + timer.expire = expire; + timer.period = period; + timer.state = SYS_TIMER_STATE_RUN; + return {}; + }); + + if (!timer) { + return CELL_ESRCH; + } + + if (timer.ret) { + if (timer.ret == CELL_ETIMEDOUT) { + return not_an_error(timer.ret); + } + + return timer.ret; + } + + g_fxo->get>()([] {}); + + return CELL_OK; +} + +error_code sys_timer_stop(ppu_thread &ppu, u32 timer_id) { + ppu.state += cpu_flag::wait; + + sys_timer.trace("sys_timer_stop()"); + + const auto timer = idm::check( + timer_id, [now = get_guest_system_time(), + notify = lv2_obj::notify_all_t()](lv2_timer &timer) { + std::lock_guard lock(timer.mutex); + timer.check_unlocked(now); + timer.state = SYS_TIMER_STATE_STOP; + }); + + if (!timer) { + return CELL_ESRCH; + } + + return CELL_OK; +} + +error_code sys_timer_connect_event_queue(ppu_thread &ppu, u32 timer_id, + u32 queue_id, u64 name, u64 data1, + u64 data2) { + ppu.state += cpu_flag::wait; + + sys_timer.warning("sys_timer_connect_event_queue(timer_id=0x%x, " + "queue_id=0x%x, name=0x%llx, data1=0x%llx, data2=0x%llx)", + timer_id, queue_id, name, data1, data2); + + const auto timer = idm::check( + timer_id, [&](lv2_timer &timer) -> CellError { + auto found = idm::get_unlocked(queue_id); + + if (!found) { + return CELL_ESRCH; + } + + std::lock_guard lock(timer.mutex); + + if (lv2_obj::check(timer.port)) { + return CELL_EISCONN; + } + + // Connect event queue + timer.port = found; + timer.source = + name ? name : (u64{process_getpid() + 0u} << 32) | u64{timer_id}; + timer.data1 = data1; + timer.data2 = data2; + return {}; + }); + + if (!timer) { + return CELL_ESRCH; + } + + if (timer.ret) { + return timer.ret; + } + + return CELL_OK; +} + +error_code sys_timer_disconnect_event_queue(ppu_thread &ppu, u32 timer_id) { + ppu.state += cpu_flag::wait; + + sys_timer.warning("sys_timer_disconnect_event_queue(timer_id=0x%x)", + timer_id); + + const auto timer = idm::check( + timer_id, + [now = get_guest_system_time(), + notify = lv2_obj::notify_all_t()](lv2_timer &timer) -> CellError { + std::lock_guard lock(timer.mutex); + + timer.check_unlocked(now); + timer.state = SYS_TIMER_STATE_STOP; + + if (!lv2_obj::check(timer.port)) { + return CELL_ENOTCONN; + } + + timer.port.reset(); + return {}; + }); + + if (!timer) { + return CELL_ESRCH; + } + + if (timer.ret) { + return timer.ret; + } + + return CELL_OK; +} + +error_code sys_timer_sleep(ppu_thread &ppu, u32 sleep_time) { + ppu.state += cpu_flag::wait; + + sys_timer.trace("sys_timer_sleep(sleep_time=%d)", sleep_time); + + return sys_timer_usleep(ppu, sleep_time * u64{1000000}); +} + +error_code sys_timer_usleep(ppu_thread &ppu, u64 sleep_time) { + ppu.state += cpu_flag::wait; + + sys_timer.trace("sys_timer_usleep(sleep_time=0x%llx)", sleep_time); + + if (sleep_time) { + const s64 add_time = g_cfg.core.usleep_addend; + + // Over/underflow checks + if (add_time >= 0) { + sleep_time = utils::add_saturate(sleep_time, add_time); + } else { + sleep_time = + std::max(1, utils::sub_saturate(sleep_time, -add_time)); + } + + lv2_obj::sleep(ppu, g_cfg.core.sleep_timers_accuracy < + sleep_timers_accuracy_level::_usleep + ? sleep_time + : 0); + + if (!lv2_obj::wait_timeout(sleep_time, &ppu, true, true)) { + ppu.state += cpu_flag::again; + } + } else { + std::this_thread::yield(); + } + + return CELL_OK; +} diff --git a/kernel/cellos/src/sys_trace.cpp b/kernel/cellos/src/sys_trace.cpp new file mode 100644 index 000000000..2624f5ebe --- /dev/null +++ b/kernel/cellos/src/sys_trace.cpp @@ -0,0 +1,59 @@ +#include "stdafx.h" + +#include "sys_trace.h" + +#include "Emu/Cell/ErrorCodes.h" + +LOG_CHANNEL(sys_trace); + +// TODO: DEX/DECR mode support? + +s32 sys_trace_create() { + sys_trace.todo("sys_trace_create()"); + return CELL_ENOSYS; +} + +s32 sys_trace_start() { + sys_trace.todo("sys_trace_start()"); + return CELL_ENOSYS; +} + +s32 sys_trace_stop() { + sys_trace.todo("sys_trace_stop()"); + return CELL_ENOSYS; +} + +s32 sys_trace_update_top_index() { + sys_trace.todo("sys_trace_update_top_index()"); + return CELL_ENOSYS; +} + +s32 sys_trace_destroy() { + sys_trace.todo("sys_trace_destroy()"); + return CELL_ENOSYS; +} + +s32 sys_trace_drain() { + sys_trace.todo("sys_trace_drain()"); + return CELL_ENOSYS; +} + +s32 sys_trace_attach_process() { + sys_trace.todo("sys_trace_attach_process()"); + return CELL_ENOSYS; +} + +s32 sys_trace_allocate_buffer() { + sys_trace.todo("sys_trace_allocate_buffer()"); + return CELL_ENOSYS; +} + +s32 sys_trace_free_buffer() { + sys_trace.todo("sys_trace_free_buffer()"); + return CELL_ENOSYS; +} + +s32 sys_trace_create2() { + sys_trace.todo("sys_trace_create2()"); + return CELL_ENOSYS; +} diff --git a/kernel/cellos/src/sys_tty.cpp b/kernel/cellos/src/sys_tty.cpp new file mode 100644 index 000000000..feb6f3674 --- /dev/null +++ b/kernel/cellos/src/sys_tty.cpp @@ -0,0 +1,188 @@ +#include "stdafx.h" + +#include "Emu/Cell/PPUThread.h" +#include "Emu/Cell/timers.hpp" +#include "Emu/system_config.h" + +#include "sys_tty.h" + +#include +#include + +LOG_CHANNEL(sys_tty); + +extern fs::file g_tty; +extern atomic_t g_tty_size; +extern std::array, 16> g_tty_input; +extern std::mutex g_tty_mutex; + +error_code sys_tty_read(s32 ch, vm::ptr buf, u32 len, + vm::ptr preadlen) { + sys_tty.trace("sys_tty_read(ch=%d, buf=*0x%x, len=%d, preadlen=*0x%x)", ch, + buf, len, preadlen); + + if (!g_cfg.core.debug_console_mode) { + return CELL_EIO; + } + + if (ch > 15 || ch < 0 || !buf) { + return CELL_EINVAL; + } + + if (ch < SYS_TTYP_USER1) { + sys_tty.warning("sys_tty_read called with system channel %d", ch); + } + + usz chars_to_read = + 0; // number of chars that will be read from the input string + std::string tty_read; // string for storage of read chars + + if (len > 0) { + std::lock_guard lock(g_tty_mutex); + + if (!g_tty_input[ch].empty()) { + // reference to our first queue element + std::string &input = g_tty_input[ch].front(); + + // we have to stop reading at either a new line, the param len, or our + // input string size + usz new_line_pos = input.find_first_of('\n'); + + if (new_line_pos != input.npos) { + chars_to_read = std::min(new_line_pos, static_cast(len)); + } else { + chars_to_read = std::min(input.size(), static_cast(len)); + } + + // read the previously calculated number of chars from the beginning of + // the input string + tty_read = input.substr(0, chars_to_read); + + // remove the just read text from the input string + input = input.substr(chars_to_read, input.size() - 1); + + if (input.empty()) { + // pop the first queue element if it was completely consumed + g_tty_input[ch].pop_front(); + } + } + } + + if (!preadlen) { + return CELL_EFAULT; + } + + *preadlen = static_cast(chars_to_read); + + if (chars_to_read > 0) { + std::memcpy(buf.get_ptr(), tty_read.c_str(), chars_to_read); + sys_tty.success("sys_tty_read(ch=%d, len=%d) read %s with length %d", ch, + len, tty_read, *preadlen); + } + + return CELL_OK; +} + +std::string dump_useful_thread_info(); + +error_code sys_tty_write([[maybe_unused]] ppu_thread &ppu, s32 ch, + vm::cptr buf, u32 len, vm::ptr pwritelen) { + ppu.state += cpu_flag::wait; + + sys_tty.notice("sys_tty_write(ch=%d, buf=*0x%x, len=%d, pwritelen=*0x%x)", ch, + buf, len, pwritelen); + + std::string msg; + + if (static_cast(len) > 0 && + vm::check_addr(buf.addr(), vm::page_readable, len)) { + msg.resize(len); + + if (!vm::try_access(buf.addr(), msg.data(), len, false)) { + msg.clear(); + } + } + + auto find_word = [](std::string_view msg, std::string_view word) -> bool { + // Match uppercase and lowercase starting words + const usz index = msg.find(word.substr(1)); + + if (index != umax && index >= 1u) { + return std::tolower(static_cast(msg[index - 1])) == word[0]; + } + + return false; + }; + + std::string_view sample = std::string_view(msg).substr(0, 1024); + + const bool warning = + find_word(sample, "failed"sv) || find_word(sample, "abort"sv) || + find_word(sample, "crash"sv) || find_word(sample, "error"sv) || + find_word(sample, "unexpected"sv) || find_word(sample, "0x8001"sv); + + sample = {}; // Remove reference to string + + if (msg.size() >= 2u && [&]() { + static thread_local u64 last_write = 0; + + // Dump thread about every period which TTY was not being touched for + // about half a second + const u64 current = get_system_time(); + return current - std::exchange(last_write, current) >= + (warning ? 500'000 : 3'000'000); + }()) { + ppu_log.notice("\n%s", dump_useful_thread_info()); + } + + // Hack: write to tty even on CEX mode, but disable all error checks + if (ch < 0 || ch > 15) { + if (g_cfg.core.debug_console_mode) { + return CELL_EINVAL; + } else { + msg.clear(); + } + } + + if (g_cfg.core.debug_console_mode) { + // Don't modify it in CEX mode + len = static_cast(len) > 0 ? len : 0; + } + + if (static_cast(len) > 0) { + if (!msg.empty()) { + if (msg.ends_with("\n")) { + // Avoid logging trailing newlines, log them verbosely instead + const std::string_view msg_clear = + std::string_view(msg).substr(0, msg.find_last_not_of('\n') + 1); + + if (msg.size() - 1 == msg_clear.size()) { + (warning ? sys_tty.warning : sys_tty.notice)( + u8"sys_tty_write(): “%s“ << endl", msg_clear); + } else { + (warning ? sys_tty.warning + : sys_tty.notice)(u8"sys_tty_write(): “%s” << endl(%u)", + msg_clear, msg.size() - msg_clear.size()); + } + } else { + (warning ? sys_tty.warning : sys_tty.notice)(u8"sys_tty_write(): “%s”", + msg); + } + + if (g_tty) { + // Lock size by making it negative + g_tty_size -= (1ll << 48); + g_tty.write(msg); + g_tty_size += (1ll << 48) + len; + } + } else if (g_cfg.core.debug_console_mode) { + return {CELL_EFAULT, buf.addr()}; + } + } + + if (!pwritelen.try_write(len)) { + return {CELL_EFAULT, pwritelen}; + } + + return CELL_OK; +} diff --git a/kernel/cellos/src/sys_uart.cpp b/kernel/cellos/src/sys_uart.cpp new file mode 100644 index 000000000..8138c23c4 --- /dev/null +++ b/kernel/cellos/src/sys_uart.cpp @@ -0,0 +1,2313 @@ +#include "stdafx.h" + +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/Cell/PPUThread.h" +#include "Emu/System.h" +#include "Emu/system_config.h" +#include "cellos/sys_process.h" +#include "cellos/sys_rsxaudio.h" +#include "cellos/sys_sync.h" + +#include "sys_uart.h" + +LOG_CHANNEL(sys_uart); + +template <> +void fmt_class_string::format(std::string &out, u64 arg) { + format_enum(out, arg, [](UartAudioCtrlID value) { + switch (value) { + STR_CASE(UartAudioCtrlID::DAC_RESET); + STR_CASE(UartAudioCtrlID::DAC_DE_EMPHASIS); + STR_CASE(UartAudioCtrlID::AVCLK); + } + + return unknown; + }); +} + +struct av_init_cmd : public ps3av_cmd { + u16 get_size(vuart_av_thread & /*vuart*/, const void * /*pkt_buf*/) override { + return sizeof(ps3av_pkt_av_init); + } + + void execute(vuart_av_thread &vuart, const void *pkt_buf) override { + const auto pkt = static_cast(pkt_buf); + + if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) { + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_BUFFER_OVERFLOW); + return; + } + + vuart.av_cmd_ver = pkt->hdr.version; + vuart.hdmi_events_bitmask |= pkt->event_bit; + + if (pkt->event_bit & PS3AV_EVENT_BIT_UNK) { + // 0 or 255, probably ps2 backwards compatibility (inverted) + const ps3av_pkt_av_init_reply reply = {0}; + + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS, &reply, + sizeof(ps3av_pkt_av_init_reply)); + return; + } + + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); + } +}; + +struct av_fini_cmd : public ps3av_cmd { + u16 get_size(vuart_av_thread & /*vuart*/, const void * /*pkt_buf*/) override { + return sizeof(ps3av_header); + } + + void execute(vuart_av_thread &vuart, const void *pkt_buf) override { + const auto pkt = static_cast(pkt_buf); + + if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) { + vuart.write_resp(pkt->cid, PS3AV_STATUS_BUFFER_OVERFLOW); + return; + } + + vuart.hdmi_events_bitmask = 0; + + vuart.write_resp(pkt->cid, PS3AV_STATUS_SUCCESS); + } +}; + +struct av_get_monitor_info_cmd : public ps3av_cmd { + u16 get_size(vuart_av_thread & /*vuart*/, const void * /*pkt_buf*/) override { + return sizeof(ps3av_get_monitor_info); + } + + void execute(vuart_av_thread &vuart, const void *pkt_buf) override { + const auto pkt = static_cast(pkt_buf); + + if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) { + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_BUFFER_OVERFLOW); + return; + } + + ps3av_get_monitor_info_reply cfg{}; + + if (pkt->avport == static_cast(UartAudioAvport::AVMULTI_0)) { + cfg.avport = static_cast(UartAudioAvport::AVMULTI_0); + cfg.monitor_type = PS3AV_MONITOR_TYPE_AVMULTI; + cfg.res_60.res_bits = UINT32_MAX; + cfg.res_50.res_bits = UINT32_MAX; + cfg.res_vesa.res_bits = UINT32_MAX; + cfg.cs.rgb = PS3AV_CS_SUPPORTED; + cfg.cs.yuv444 = PS3AV_CS_SUPPORTED; + cfg.cs.yuv422 = PS3AV_CS_SUPPORTED; + cfg.speaker_info = 1; + cfg.num_of_audio_block = 1; + cfg.audio_info[0].sbit = 7; + cfg.audio_info[0].max_num_of_ch = 2; + cfg.audio_info[0].type = PS3AV_MON_INFO_AUDIO_TYPE_LPCM; + cfg.audio_info[0].fs = 127; + + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS, &cfg, + sizeof(ps3av_get_monitor_info_reply)); + } else if (pkt->avport <= static_cast(UartAudioAvport::HDMI_1)) { + if (pkt->avport == static_cast(UartAudioAvport::HDMI_1) && + !g_cfg.core.debug_console_mode) { + vuart.write_resp(pkt->hdr.cid, + PS3AV_STATUS_SYSCON_COMMUNICATE_FAIL); + return; + } + + set_hdmi_display_cfg(vuart, cfg, static_cast(pkt->avport)); + + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS, &cfg, + sizeof(ps3av_get_monitor_info_reply) - + 4); // Length is different for some reason + } else { + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_INVALID_PORT); + } + } + + static void set_hdmi_display_cfg(vuart_av_thread &vuart, + ps3av_get_monitor_info_reply &cfg, + u8 avport) { + if (vuart.hdmi_behavior_mode != PS3AV_HDMI_BEHAVIOR_NORMAL && + (vuart.hdmi_behavior_mode & PS3AV_HDMI_BEHAVIOR_EDID_PASS)) { + cfg.monitor_type = vuart.hdmi_behavior_mode & PS3AV_HDMI_BEHAVIOR_DVI + ? PS3AV_MONITOR_TYPE_DVI + : PS3AV_MONITOR_TYPE_HDMI; + return; + } + + // Report maximum support + + static constexpr u8 mon_id[sizeof(cfg.monitor_id)] = { + 0x4A, 0x13, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15}; + static constexpr u8 mon_name[sizeof(cfg.monitor_name)] = { + 'R', 'P', 'C', 'S', '3', ' ', 'V', 'i', + 'r', 't', 'M', 'o', 'n', '\0', '\0', '\0'}; + static constexpr ps3av_info_audio audio_info[PS3AV_MON_INFO_AUDIO_BLK_MAX] = + { + {PS3AV_MON_INFO_AUDIO_TYPE_LPCM, 8, 0x7F, 0x07}, + {PS3AV_MON_INFO_AUDIO_TYPE_AC3, 8, 0x7F, 0xFF}, + {PS3AV_MON_INFO_AUDIO_TYPE_AAC, 8, 0x7F, 0xFF}, + {PS3AV_MON_INFO_AUDIO_TYPE_DTS, 8, 0x7F, 0xFF}, + {PS3AV_MON_INFO_AUDIO_TYPE_DDP, 8, 0x7F, 0xFF}, + {PS3AV_MON_INFO_AUDIO_TYPE_DTS_HD, 8, 0x7F, 0xFF}, + {PS3AV_MON_INFO_AUDIO_TYPE_DOLBY_THD, 8, 0x7F, 0xFF}, + }; + + cfg.avport = avport; + memcpy(cfg.monitor_id, mon_id, sizeof(cfg.monitor_id)); + cfg.monitor_type = PS3AV_MONITOR_TYPE_HDMI; + memcpy(cfg.monitor_name, mon_name, sizeof(cfg.monitor_name)); + + const u32 native_res = [&]() { + switch (g_cfg.video.resolution) { + case video_resolution::_1080p: + return PS3AV_RESBIT_1920x1080P; + case video_resolution::_1080i: + return PS3AV_RESBIT_1920x1080I; + case video_resolution::_1600x1080p: + case video_resolution::_1440x1080p: + case video_resolution::_1280x1080p: + case video_resolution::_720p: + return PS3AV_RESBIT_1280x720P; + case video_resolution::_576p: + return PS3AV_RESBIT_720x576P; + default: + return PS3AV_RESBIT_720x480P; + } + }(); + + cfg.res_60.res_bits = UINT32_MAX; + cfg.res_60.native = native_res; + cfg.res_50.res_bits = UINT32_MAX; + cfg.res_50.native = native_res; + cfg.res_other.res_bits = UINT32_MAX; + cfg.res_vesa.res_bits = 1; // Always one mode at a time + + cfg.cs.rgb = PS3AV_CS_SUPPORTED | PS3AV_RGB_SELECTABLE_QAUNTIZATION_RANGE | + PS3AV_12BIT_COLOR; + cfg.cs.yuv444 = PS3AV_CS_SUPPORTED | PS3AV_12BIT_COLOR; + cfg.cs.yuv422 = PS3AV_CS_SUPPORTED; + cfg.cs.colorimetry_data = + PS3AV_COLORIMETRY_xvYCC_601 | PS3AV_COLORIMETRY_xvYCC_709 | + PS3AV_COLORIMETRY_MD0 | PS3AV_COLORIMETRY_MD1 | PS3AV_COLORIMETRY_MD2; + + cfg.color.red_x = 1023; + cfg.color.red_y = 0; + cfg.color.green_x = 0; + cfg.color.green_y = 1023; + cfg.color.blue_x = 0; + cfg.color.blue_y = 0; + cfg.color.white_x = 341; + cfg.color.white_y = 341; + cfg.color.gamma = 100; + + cfg.supported_ai = 1; + cfg.speaker_info = 0x4F; + + // Audio formats + cfg.num_of_audio_block = 7; + memcpy(cfg.audio_info, audio_info, sizeof(cfg.audio_info)); + + // 16:9 27-inch (as a default) + cfg.hor_screen_size = 60; + cfg.ver_screen_size = 34; + + cfg.supported_content_types = 0b1111; // Graphics, cinema, photo, game + + // 3D modes, no native formats + cfg.res_60_packed_3D.res_bits = UINT32_MAX; + cfg.res_50_packed_3D.res_bits = UINT32_MAX; + cfg.res_other_3D.res_bits = UINT32_MAX; + cfg.res_60_sbs_3D.res_bits = UINT32_MAX; + cfg.res_50_sbs_3D.res_bits = UINT32_MAX; + + cfg.vendor_specific_flags = 0; // values from 0-3 (unk) + } +}; + +struct av_get_bksv_list_cmd : public ps3av_cmd { + u16 get_size(vuart_av_thread & /*vuart*/, const void * /*pkt_buf*/) override { + return sizeof(ps3av_pkt_get_bksv); + } + + void execute(vuart_av_thread &vuart, const void *pkt_buf) override { + const auto pkt = static_cast(pkt_buf); + + if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) { + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_BUFFER_OVERFLOW); + return; + } + + if (pkt->avport > static_cast(UartAudioAvport::HDMI_1)) { + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_INVALID_PORT); + return; + } + + if (pkt->avport == static_cast(UartAudioAvport::HDMI_1) && + !g_cfg.core.debug_console_mode) { + vuart.write_resp(pkt->hdr.cid, + PS3AV_STATUS_SYSCON_COMMUNICATE_FAIL); + return; + } + + ps3av_pkt_get_bksv_reply reply{}; + reply.avport = pkt->avport; + u16 pkt_size = offsetof(ps3av_pkt_get_bksv_reply, ksv_arr); + + if (vuart.hdmi_behavior_mode == PS3AV_HDMI_BEHAVIOR_NORMAL || + !(vuart.hdmi_behavior_mode & PS3AV_HDMI_BEHAVIOR_HDCP_OFF)) { + reply.ksv_cnt = 1; + memcpy(reply.ksv_arr[0], PS3AV_BKSV_VALUE, sizeof(PS3AV_BKSV_VALUE)); + pkt_size = (pkt_size + 5 * reply.ksv_cnt + 3) & 0xFFFC; + } + + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS, &reply, + pkt_size); + } +}; + +struct av_enable_event_cmd : public ps3av_cmd { + u16 get_size(vuart_av_thread & /*vuart*/, const void * /*pkt_buf*/) override { + return sizeof(ps3av_pkt_enable_event); + } + + void execute(vuart_av_thread &vuart, const void *pkt_buf) override { + const auto pkt = static_cast(pkt_buf); + + if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) { + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_BUFFER_OVERFLOW); + return; + } + + vuart.hdmi_events_bitmask |= pkt->event_bit; + + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); + } +}; + +struct av_disable_event_cmd : public ps3av_cmd { + u16 get_size(vuart_av_thread & /*vuart*/, const void * /*pkt_buf*/) override { + return sizeof(ps3av_pkt_enable_event); + } + + void execute(vuart_av_thread &vuart, const void *pkt_buf) override { + const auto pkt = static_cast(pkt_buf); + + if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) { + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_BUFFER_OVERFLOW); + return; + } + + vuart.hdmi_events_bitmask &= ~pkt->event_bit; + + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); + } +}; + +struct av_tv_mute_cmd : public ps3av_cmd { + u16 get_size(vuart_av_thread & /*vuart*/, const void * /*pkt_buf*/) override { + return sizeof(ps3av_pkt_av_audio_mute); + } + + // Behavior is unknown, but it seems that this pkt could be ignored + void execute(vuart_av_thread &vuart, const void *pkt_buf) override { + const auto pkt = static_cast(pkt_buf); + + if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) { + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_BUFFER_OVERFLOW); + return; + } + + if (pkt->avport < (g_cfg.core.debug_console_mode ? 2 : 1)) { + sys_uart.notice("[av_tv_mute_cmd] tv mute set to %u", pkt->mute > 0); + } + + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); + } +}; + +struct av_null_cmd : public ps3av_cmd { + u16 get_size(vuart_av_thread & /*vuart*/, const void * /*pkt_buf*/) override { + return 12; + } + + void execute(vuart_av_thread &vuart, const void *pkt_buf) override { + const auto pkt = static_cast(pkt_buf); + + if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) { + vuart.write_resp(pkt->cid, PS3AV_STATUS_BUFFER_OVERFLOW); + return; + } + + vuart.write_resp(pkt->cid, PS3AV_STATUS_SUCCESS); + } +}; + +struct av_get_aksv_list_cmd : public ps3av_cmd { + u16 get_size(vuart_av_thread & /*vuart*/, const void * /*pkt_buf*/) override { + return sizeof(ps3av_header); + } + + void execute(vuart_av_thread &vuart, const void *pkt_buf) override { + const auto pkt = static_cast(pkt_buf); + + if (vuart.get_reply_buf_free_size() < + sizeof(ps3av_pkt_reply_hdr) + sizeof(ps3av_pkt_get_aksv_reply)) { + vuart.write_resp(pkt->cid, PS3AV_STATUS_BUFFER_OVERFLOW); + return; + } + + ps3av_pkt_get_aksv_reply reply{}; + memcpy(reply.ksv_arr[0], PS3AV_AKSV_VALUE, sizeof(PS3AV_AKSV_VALUE)); + + if (g_cfg.core.debug_console_mode) { + memcpy(reply.ksv_arr[1], PS3AV_AKSV_VALUE, sizeof(PS3AV_AKSV_VALUE)); + reply.ksv_size = 2 * sizeof(PS3AV_AKSV_VALUE); + } else { + reply.ksv_size = sizeof(PS3AV_AKSV_VALUE); + } + + vuart.write_resp(pkt->cid, PS3AV_STATUS_SUCCESS, &reply, + sizeof(ps3av_pkt_get_aksv_reply)); + } +}; + +struct video_disable_signal_cmd : public ps3av_cmd { + u16 get_size(vuart_av_thread & /*vuart*/, const void * /*pkt_buf*/) override { + return sizeof(ps3av_pkt_video_disable_sig); + } + + // Cross color reduction filter setting in vsh. (AVMULTI) + void execute(vuart_av_thread &vuart, const void *pkt_buf) override { + const auto pkt = static_cast(pkt_buf); + + if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) { + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_BUFFER_OVERFLOW); + return; + } + + if (pkt->avport <= static_cast(UartAudioAvport::HDMI_1)) { + g_fxo->get().update_av_mute_state( + vuart.avport_to_idx(static_cast(pkt->avport.get())), + false, true); + + if (pkt->avport == static_cast(UartAudioAvport::HDMI_1) && + !g_cfg.core.debug_console_mode) { + vuart.write_resp(pkt->hdr.cid, + PS3AV_STATUS_SYSCON_COMMUNICATE_FAIL); + return; + } + + vuart.hdmi_res_set[pkt->avport == + static_cast(UartAudioAvport::HDMI_1)] = false; + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); + } else if (pkt->avport == static_cast(UartAudioAvport::AVMULTI_0)) { + if (vuart.head_b_initialized) { + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); + } + } else { + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); + } + } +}; + +struct av_video_ytrapcontrol_cmd : public ps3av_cmd { + u16 get_size(vuart_av_thread & /*vuart*/, const void * /*pkt_buf*/) override { + return sizeof(ps3av_pkt_av_video_ytrapcontrol); + } + + // Cross color reduction filter setting in vsh. (AVMULTI) + void execute(vuart_av_thread &vuart, const void *pkt_buf) override { + const auto pkt = + static_cast(pkt_buf); + + if (vuart.get_reply_buf_free_size() < + sizeof(ps3av_get_hw_info_reply) + sizeof(ps3av_pkt_reply_hdr)) { + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_BUFFER_OVERFLOW); + return; + } + + if (pkt->unk1 && pkt->unk1 != 5U) { + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); + return; + } + + sys_uart.notice("[av_video_ytrapcontrol_cmd] unk1=0x%04x unk2=0x%04x", + pkt->unk1, pkt->unk2); + + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); + } +}; + +struct av_audio_mute_cmd : public ps3av_cmd { + u16 get_size(vuart_av_thread & /*vuart*/, const void * /*pkt_buf*/) override { + return sizeof(ps3av_pkt_av_audio_mute); + } + + void execute(vuart_av_thread &vuart, const void *pkt_buf) override { + const auto pkt = static_cast(pkt_buf); + + if (pkt->avport == static_cast(UartAudioAvport::AVMULTI_1)) { + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_INVALID_PORT); + return; + } + + if ((pkt->avport > static_cast(UartAudioAvport::HDMI_1) && + pkt->avport != static_cast(UartAudioAvport::AVMULTI_0)) || + (pkt->avport == static_cast(UartAudioAvport::HDMI_1) && + !g_cfg.core.debug_console_mode)) { + vuart.write_resp(pkt->hdr.cid, + PS3AV_STATUS_SYSCON_COMMUNICATE_FAIL); + return; + } + + g_fxo->get().update_av_mute_state( + vuart.avport_to_idx(static_cast(pkt->avport.get())), + true, false, pkt->mute); + + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); + } +}; + +struct av_acp_ctrl_cmd : public ps3av_cmd { + u16 get_size(vuart_av_thread & /*vuart*/, const void * /*pkt_buf*/) override { + return sizeof(ps3av_pkt_acp_ctrl); + } + + void execute(vuart_av_thread &vuart, const void *pkt_buf) override { + const auto pkt = static_cast(pkt_buf); + + if (pkt->avport > static_cast(UartAudioAvport::HDMI_1) || + (pkt->avport == static_cast(UartAudioAvport::HDMI_1) && + !g_cfg.core.debug_console_mode)) { + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_INVALID_PORT); + return; + } + + sys_uart.notice( + "[av_acp_ctrl_cmd] HDMI_%u data island ctrl pkt ctrl=0x%02x", + pkt->avport, pkt->packetctl); + + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); + } +}; + +struct av_set_acp_packet_cmd : public ps3av_cmd { + u16 get_size(vuart_av_thread & /*vuart*/, const void * /*pkt_buf*/) override { + return sizeof(ps3av_pkt_set_acp_packet); + } + + void execute(vuart_av_thread &vuart, const void *pkt_buf) override { + const auto pkt = static_cast(pkt_buf); + + if (pkt->avport > static_cast(UartAudioAvport::HDMI_1) || + (pkt->avport == static_cast(UartAudioAvport::HDMI_1) && + !g_cfg.core.debug_console_mode) || + (pkt->pkt_type > 0x0A && pkt->pkt_type < 0x81) || + pkt->pkt_type > 0x85) { + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_INVALID_PORT); + return; + } + + sys_uart.notice( + "[av_set_acp_packet_cmd] HDMI_%u data island pkt type=0x%02x", + pkt->avport, pkt->pkt_type); + + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); + } +}; + +struct av_add_signal_ctl_cmd : public ps3av_cmd { + u16 get_size(vuart_av_thread & /*vuart*/, const void * /*pkt_buf*/) override { + return sizeof(ps3av_pkt_add_signal_ctl); + } + + void execute(vuart_av_thread &vuart, const void *pkt_buf) override { + const auto pkt = static_cast(pkt_buf); + + if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) { + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_BUFFER_OVERFLOW); + return; + } + + if (pkt->avport != static_cast(UartAudioAvport::AVMULTI_0)) { + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_INVALID_PORT); + return; + } + + sys_uart.notice("[av_add_signal_ctl_cmd] signal_ctl=0x%04x", + pkt->signal_ctl); + + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); + } +}; + +struct av_set_cgms_wss_cmd : public ps3av_cmd { + u16 get_size(vuart_av_thread & /*vuart*/, const void * /*pkt_buf*/) override { + return sizeof(ps3av_pkt_av_set_cgms_wss); + } + + // Something related to copy control on AVMULTI. + void execute(vuart_av_thread &vuart, const void *pkt_buf) override { + const auto pkt = static_cast(pkt_buf); + + if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) { + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_BUFFER_OVERFLOW); + return; + } + + if (pkt->avport != static_cast(UartAudioAvport::AVMULTI_0)) { + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_INVALID_PORT); + return; + } + + sys_uart.notice("[av_set_cgms_wss_cmd] cgms_wss=0x%08x", pkt->cgms_wss); + + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); + } +}; + +struct av_get_hw_conf_cmd : public ps3av_cmd { + u16 get_size(vuart_av_thread & /*vuart*/, const void * /*pkt_buf*/) override { + return sizeof(ps3av_header); + } + + void execute(vuart_av_thread &vuart, const void *pkt_buf) override { + const auto pkt = static_cast(pkt_buf); + + if (vuart.get_reply_buf_free_size() < + sizeof(ps3av_get_hw_info_reply) + sizeof(ps3av_pkt_reply_hdr)) { + vuart.write_resp(pkt->cid, PS3AV_STATUS_BUFFER_OVERFLOW); + return; + } + + ps3av_get_hw_info_reply out{}; + out.num_of_hdmi = g_cfg.core.debug_console_mode ? 2 : 1; + out.num_of_avmulti = 1; + out.num_of_spdif = 1; + out.extra_bistream_support = 1; + + vuart.write_resp(pkt->cid, PS3AV_STATUS_SUCCESS, &out, + sizeof(ps3av_get_hw_info_reply)); + } +}; + +struct av_set_hdmi_mode_cmd : public ps3av_cmd { + u16 get_size(vuart_av_thread & /*vuart*/, const void * /*pkt_buf*/) override { + return sizeof(ps3av_pkt_set_hdmi_mode); + } + + void execute(vuart_av_thread &vuart, const void *pkt_buf) override { + const auto pkt = static_cast(pkt_buf); + + if (pkt->mode != PS3AV_HDMI_BEHAVIOR_NORMAL) { + if ((pkt->mode & PS3AV_HDMI_BEHAVIOR_HDCP_OFF) && + !g_cfg.core.debug_console_mode) { + vuart.write_resp(pkt->hdr.cid, + PS3AV_STATUS_UNSUPPORTED_HDMI_MODE); + return; + } + + if (pkt->mode & + ~(PS3AV_HDMI_BEHAVIOR_HDCP_OFF | PS3AV_HDMI_BEHAVIOR_EDID_PASS | + PS3AV_HDMI_BEHAVIOR_DVI)) { + sys_uart.warning( + "[av_set_hdmi_mode_cmd] Unknown bits in hdmi mode: 0x%02x", + pkt->mode); + } + } + + vuart.hdmi_behavior_mode = pkt->mode; + + vuart.add_hdmi_events(UartHdmiEvent::UNPLUGGED, + vuart.hdmi_res_set[0] ? UartHdmiEvent::HDCP_DONE + : UartHdmiEvent::PLUGGED, + true, false); + vuart.add_hdmi_events(UartHdmiEvent::UNPLUGGED, + vuart.hdmi_res_set[1] ? UartHdmiEvent::HDCP_DONE + : UartHdmiEvent::PLUGGED, + false, true); + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); + } +}; + +struct av_get_cec_status_cmd : public ps3av_cmd { + u16 get_size(vuart_av_thread & /*vuart*/, const void * /*pkt_buf*/) override { + return sizeof(ps3av_header); + } + + void execute(vuart_av_thread &vuart, const void *pkt_buf) override { + const auto pkt = static_cast(pkt_buf); + + if (vuart.get_reply_buf_free_size() < + sizeof(ps3av_pkt_av_get_cec_config_reply) + + sizeof(ps3av_pkt_reply_hdr)) { + vuart.write_resp(pkt->cid, PS3AV_STATUS_BUFFER_OVERFLOW); + return; + } + + const ps3av_pkt_av_get_cec_config_reply reply{1}; + vuart.write_resp(pkt->cid, PS3AV_STATUS_SUCCESS, &reply, + sizeof(ps3av_pkt_av_get_cec_config_reply)); + } +}; + +struct video_init_cmd : public ps3av_cmd { + u16 get_size(vuart_av_thread & /*vuart*/, const void * /*pkt_buf*/) override { + return sizeof(ps3av_header); + } + + void execute(vuart_av_thread &vuart, const void *pkt_buf) override { + const auto pkt = static_cast(pkt_buf); + + if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) { + vuart.write_resp(pkt->cid, PS3AV_STATUS_BUFFER_OVERFLOW); + return; + } + + vuart.write_resp(pkt->cid, PS3AV_STATUS_SUCCESS); + } +}; + +struct video_set_format_cmd : public ps3av_cmd { + u16 get_size(vuart_av_thread & /*vuart*/, const void * /*pkt_buf*/) override { + return sizeof(ps3av_pkt_video_format); + } + + void execute(vuart_av_thread &vuart, const void *pkt_buf) override { + const auto pkt = static_cast(pkt_buf); + + if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) { + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_BUFFER_OVERFLOW); + return; + } + + if (pkt->video_head > PS3AV_HEAD_B_ANALOG || pkt->video_order > 1 || + pkt->video_format > 16) { + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_INVALID_VIDEO_PARAM); + return; + } + + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); + } +}; + +struct video_set_route_cmd : public ps3av_cmd { + u16 get_size(vuart_av_thread & /*vuart*/, const void * /*pkt_buf*/) override { + return 24; + } + + void execute(vuart_av_thread &vuart, const void *pkt_buf) override { + const auto pkt = static_cast(pkt_buf); + + if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) { + vuart.write_resp(pkt->cid, PS3AV_STATUS_BUFFER_OVERFLOW); + return; + } + + vuart.write_resp(pkt->cid, + PS3AV_STATUS_NO_SEL); // Only available in PS2_GX_LPAR + } +}; + +struct video_set_pitch_cmd : public ps3av_cmd { + u16 get_size(vuart_av_thread & /*vuart*/, const void * /*pkt_buf*/) override { + return sizeof(ps3av_pkt_video_set_pitch); + } + + void execute(vuart_av_thread &vuart, const void *pkt_buf) override { + const auto pkt = static_cast(pkt_buf); + + if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) { + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_BUFFER_OVERFLOW); + return; + } + + if (pkt->video_head > PS3AV_HEAD_B_ANALOG || (pkt->pitch & 7) != 0U || + pkt->pitch > UINT16_MAX) { + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_INVALID_VIDEO_PARAM); + return; + } + + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); + } +}; + +struct video_get_hw_cfg_cmd : public ps3av_cmd { + u16 get_size(vuart_av_thread & /*vuart*/, const void * /*pkt_buf*/) override { + return sizeof(ps3av_header); + } + + void execute(vuart_av_thread &vuart, const void *pkt_buf) override { + const auto pkt = static_cast(pkt_buf); + + if (vuart.get_reply_buf_free_size() < + sizeof(ps3av_pkt_video_get_hw_cfg_reply) + + sizeof(ps3av_pkt_reply_hdr)) { + vuart.write_resp(pkt->cid, PS3AV_STATUS_BUFFER_OVERFLOW); + return; + } + + ps3av_pkt_video_get_hw_cfg_reply reply{}; + reply.gx_available = 0; // Set to 1 only in PS2_GX_LPAR + + vuart.write_resp(pkt->cid, PS3AV_STATUS_SUCCESS, &reply, + sizeof(ps3av_pkt_video_get_hw_cfg_reply)); + } +}; + +struct audio_init_cmd : public ps3av_cmd { + u16 get_size(vuart_av_thread & /*vuart*/, const void * /*pkt_buf*/) override { + return sizeof(ps3av_header); + } + + void execute(vuart_av_thread &vuart, const void *pkt_buf) override { + const auto pkt = static_cast(pkt_buf); + + if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) { + vuart.write_resp(pkt->cid, PS3AV_STATUS_BUFFER_OVERFLOW); + return; + } + + g_fxo->get().reset_hw(); + + vuart.write_resp(pkt->cid, PS3AV_STATUS_SUCCESS); + } +}; + +struct audio_set_mode_cmd : public ps3av_cmd { + u16 get_size(vuart_av_thread & /*vuart*/, const void * /*pkt_buf*/) override { + return sizeof(ps3av_pkt_audio_mode); + } + + void execute(vuart_av_thread &vuart, const void *pkt_buf) override { + const auto pkt = static_cast(pkt_buf); + + if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) { + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_BUFFER_OVERFLOW); + return; + } + + if (!set_mode(*pkt)) { + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_INVALID_AUDIO_PARAM); + } else { + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); + } + } + +private: + bool set_mode(const ps3av_pkt_audio_mode &pkt) { + bool spdif_use_serial_buf = false; + RsxaudioPort avport_src, rsxaudio_port; + RsxaudioAvportIdx avport_idx; + + switch (pkt.avport) { + case UartAudioAvport::HDMI_0: { + avport_idx = RsxaudioAvportIdx::HDMI_0; + if (pkt.audio_source == UartAudioSource::SPDIF) { + avport_src = rsxaudio_port = RsxaudioPort::SPDIF_1; + } else { + avport_src = rsxaudio_port = RsxaudioPort::SERIAL; + } + break; + } + case UartAudioAvport::HDMI_1: { + avport_idx = RsxaudioAvportIdx::HDMI_1; + if (pkt.audio_source == UartAudioSource::SPDIF) { + avport_src = rsxaudio_port = RsxaudioPort::SPDIF_1; + } else { + avport_src = rsxaudio_port = RsxaudioPort::SERIAL; + } + break; + } + case UartAudioAvport::AVMULTI_0: { + avport_idx = RsxaudioAvportIdx::AVMULTI; + avport_src = rsxaudio_port = RsxaudioPort::SERIAL; + break; + } + case UartAudioAvport::SPDIF_0: { + avport_idx = RsxaudioAvportIdx::SPDIF_0; + rsxaudio_port = RsxaudioPort::SPDIF_0; + if (pkt.audio_source == UartAudioSource::SERIAL) { + spdif_use_serial_buf = true; + avport_src = RsxaudioPort::SERIAL; + } else { + avport_src = RsxaudioPort::SPDIF_0; + } + + break; + } + case UartAudioAvport::SPDIF_1: { + avport_idx = RsxaudioAvportIdx::SPDIF_1; + rsxaudio_port = RsxaudioPort::SPDIF_1; + if (pkt.audio_source == UartAudioSource::SERIAL) { + spdif_use_serial_buf = true; + avport_src = RsxaudioPort::SERIAL; + } else { + avport_src = RsxaudioPort::SPDIF_1; + } + + break; + } + default: { + return false; + } + } + + if (static_cast(pkt.audio_fs.value()) > + static_cast(UartAudioFreq::_192K)) + return false; + + const auto bit_cnt = [&]() { + if ((rsxaudio_port != RsxaudioPort::SERIAL && + pkt.audio_format != UartAudioFormat::PCM) || + pkt.audio_word_bits == UartAudioSampleSize::_16BIT) { + return UartAudioSampleSize::_16BIT; + } else { + return UartAudioSampleSize::_24BIT; + } + }(); + + return commit_param(rsxaudio_port, avport_idx, avport_src, pkt.audio_fs, + bit_cnt, spdif_use_serial_buf, pkt.audio_cs_info); + } + + bool commit_param(RsxaudioPort rsxaudio_port, RsxaudioAvportIdx avport, + RsxaudioPort avport_src, UartAudioFreq freq, + UartAudioSampleSize bit_cnt, bool spdif_use_serial_buf, + const u8 *cs_data) { + auto &rsxaudio_thread = g_fxo->get(); + const auto avport_idx = + static_cast>(avport); + const auto rsxaudio_word_depth = bit_cnt == UartAudioSampleSize::_16BIT + ? RsxaudioSampleSize::_16BIT + : RsxaudioSampleSize::_32BIT; + const auto freq_param = [&]() { + switch (freq) { + case UartAudioFreq::_44K: + return std::make_pair(8, SYS_RSXAUDIO_FREQ_BASE_352K); + default: + case UartAudioFreq::_48K: + return std::make_pair(8, SYS_RSXAUDIO_FREQ_BASE_384K); + case UartAudioFreq::_88K: + return std::make_pair(4, SYS_RSXAUDIO_FREQ_BASE_352K); + case UartAudioFreq::_96K: + return std::make_pair(4, SYS_RSXAUDIO_FREQ_BASE_384K); + case UartAudioFreq::_176K: + return std::make_pair(2, SYS_RSXAUDIO_FREQ_BASE_352K); + case UartAudioFreq::_192K: + return std::make_pair(2, SYS_RSXAUDIO_FREQ_BASE_384K); + } + }(); + + switch (rsxaudio_port) { + case RsxaudioPort::SERIAL: { + rsxaudio_thread.update_hw_param([&](auto &obj) { + obj.serial_freq_base = freq_param.second; + obj.serial.freq_div = freq_param.first; + obj.serial.depth = rsxaudio_word_depth; + obj.serial.buf_empty_en = true; + obj.avport_src[avport_idx] = avport_src; + }); + break; + } + case RsxaudioPort::SPDIF_0: + case RsxaudioPort::SPDIF_1: { + const u8 spdif_idx = rsxaudio_port == RsxaudioPort::SPDIF_1; + + rsxaudio_thread.update_hw_param([&](auto &obj) { + obj.spdif_freq_base = freq_param.second; + obj.spdif[spdif_idx].freq_div = freq_param.first; + obj.spdif[spdif_idx].depth = rsxaudio_word_depth; + obj.spdif[spdif_idx].use_serial_buf = spdif_use_serial_buf; + obj.spdif[spdif_idx].buf_empty_en = true; + obj.avport_src[avport_idx] = avport_src; + memcpy(obj.spdif[spdif_idx].cs_data.data(), cs_data, + sizeof(obj.spdif[spdif_idx].cs_data)); + }); + break; + } + default: { + return false; + } + } + + return true; + } +}; + +struct audio_mute_cmd : public ps3av_cmd { + u16 get_size(vuart_av_thread & /*vuart*/, const void * /*pkt_buf*/) override { + // From RE + return 0; + } + + void execute(vuart_av_thread &vuart, const void *pkt_buf) override { + const auto pkt = static_cast(pkt_buf); + + if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) { + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_BUFFER_OVERFLOW); + return; + } + + switch (pkt->avport) { + case UartAudioAvport::HDMI_0: + case UartAudioAvport::HDMI_1: + case UartAudioAvport::AVMULTI_0: + case UartAudioAvport::AVMULTI_1: + g_fxo->get().update_mute_state(RsxaudioPort::SERIAL, + pkt->mute); + break; + case UartAudioAvport::SPDIF_0: + g_fxo->get().update_mute_state(RsxaudioPort::SPDIF_0, + pkt->mute); + break; + case UartAudioAvport::SPDIF_1: + g_fxo->get().update_mute_state(RsxaudioPort::SPDIF_1, + pkt->mute); + break; + default: + break; + } + + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); + } +}; + +struct audio_set_active_cmd : public ps3av_cmd { + u16 get_size(vuart_av_thread & /*vuart*/, const void * /*pkt_buf*/) override { + return sizeof(ps3av_pkt_audio_set_active); + } + + void execute(vuart_av_thread &vuart, const void *pkt_buf) override { + const auto pkt = static_cast(pkt_buf); + + if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) { + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_BUFFER_OVERFLOW); + return; + } + + const bool requested_avports[SYS_RSXAUDIO_AVPORT_CNT] = { + (pkt->audio_port & PS3AV_AUDIO_PORT_HDMI_0) != 0U, + (pkt->audio_port & PS3AV_AUDIO_PORT_HDMI_1) != 0U, + (pkt->audio_port & PS3AV_AUDIO_PORT_AVMULTI) != 0U, + (pkt->audio_port & PS3AV_AUDIO_PORT_SPDIF_0) != 0U, + (pkt->audio_port & PS3AV_AUDIO_PORT_SPDIF_1) != 0U}; + + g_fxo->get().update_hw_param([&](auto &obj) { + for (u8 avport_idx = 0; avport_idx < SYS_RSXAUDIO_AVPORT_CNT; + avport_idx++) { + if (requested_avports[avport_idx]) { + switch (obj.avport_src[avport_idx]) { + case RsxaudioPort::SERIAL: + obj.serial.en = true; + break; + case RsxaudioPort::SPDIF_0: + case RsxaudioPort::SPDIF_1: { + const u8 spdif_idx = + obj.avport_src[avport_idx] == RsxaudioPort::SPDIF_1; + if (!obj.spdif[spdif_idx].use_serial_buf) { + obj.spdif[spdif_idx].en = true; + } + break; + } + default: + break; + } + } + } + + obj.serial.muted = false; + obj.spdif[1].muted = false; + }); + + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); + } +}; + +struct audio_set_inactive_cmd : public ps3av_cmd { + u16 get_size(vuart_av_thread & /*vuart*/, const void * /*pkt_buf*/) override { + return sizeof(ps3av_pkt_audio_set_active); + } + + void execute(vuart_av_thread &vuart, const void *pkt_buf) override { + const auto pkt = static_cast(pkt_buf); + + if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) { + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_BUFFER_OVERFLOW); + return; + } + + g_fxo->get().update_hw_param([&](auto &obj) { + if ((pkt->audio_port & 0x8000'0000) == 0U) { + obj.avport_src.fill(RsxaudioPort::INVALID); + } + + obj.serial.en = false; + obj.serial.muted = true; + obj.spdif[1].muted = true; + for (u8 spdif_idx = 0; spdif_idx < SYS_RSXAUDIO_SPDIF_CNT; spdif_idx++) { + if (!obj.spdif[spdif_idx].use_serial_buf) { + obj.spdif[spdif_idx].en = false; + } + } + }); + + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); + } +}; + +struct audio_spdif_bit_cmd : public ps3av_cmd { + u16 get_size(vuart_av_thread & /*vuart*/, const void * /*pkt_buf*/) override { + return sizeof(ps3av_pkt_audio_spdif_bit); + } + + void execute(vuart_av_thread &vuart, const void *pkt_buf) override { + const auto pkt = static_cast(pkt_buf); + + if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) { + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_BUFFER_OVERFLOW); + return; + } + + const bool requested_avports[SYS_RSXAUDIO_AVPORT_CNT] = { + (pkt->audio_port & PS3AV_AUDIO_PORT_HDMI_0) != 0U, + (pkt->audio_port & PS3AV_AUDIO_PORT_HDMI_1) != 0U, + (pkt->audio_port & PS3AV_AUDIO_PORT_AVMULTI) != 0U, + (pkt->audio_port & PS3AV_AUDIO_PORT_SPDIF_0) != 0U, + (pkt->audio_port & PS3AV_AUDIO_PORT_SPDIF_1) != 0U}; + + g_fxo->get().update_hw_param([&](auto &obj) { + for (u8 avport_idx = 0; avport_idx < SYS_RSXAUDIO_AVPORT_CNT; + avport_idx++) { + if (requested_avports[avport_idx] && + obj.avport_src[avport_idx] == RsxaudioPort::SPDIF_0) { + auto &b_data = pkt->spdif_bit_data; + + sys_uart.notice("[audio_spdif_bit_cmd] Data 0x%x 0x%x 0x%x 0x%x 0x%x " + "0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x", + b_data[0], b_data[1], b_data[2], b_data[3], b_data[4], + b_data[5], b_data[6], b_data[7], b_data[8], b_data[9], + b_data[10], b_data[11]); + break; + } + } + }); + + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); + } +}; + +struct audio_ctrl_cmd : public ps3av_cmd { + u16 get_size(vuart_av_thread & /*vuart*/, const void * /*pkt_buf*/) override { + return sizeof(ps3av_pkt_audio_ctrl); + } + + void execute(vuart_av_thread &vuart, const void *pkt_buf) override { + const auto pkt = static_cast(pkt_buf); + + if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) { + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_BUFFER_OVERFLOW); + return; + } + + switch (pkt->audio_ctrl_id) { + case UartAudioCtrlID::DAC_RESET: + case UartAudioCtrlID::DAC_DE_EMPHASIS: + case UartAudioCtrlID::AVCLK: + sys_uart.notice("[audio_ctrl_cmd] Option 0x%x", pkt->audio_ctrl_id); + break; + default: + break; + } + + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); + } +}; + +struct inc_avset_cmd : public ps3av_cmd { + u16 get_size(vuart_av_thread & /*vuart*/, const void *pkt_buf) override { + const auto pkt = static_cast(pkt_buf); + + if (pkt->num_of_video_pkt > 2 || pkt->num_of_av_video_pkt > 4 || + pkt->num_of_av_audio_pkt > 4) { + return -1; + } + + const auto data_start = + static_cast(pkt_buf) + sizeof(ps3av_pkt_inc_avset); + + u64 video_pkt_sec_size = 0; + u64 av_video_pkt_sec_size = 0; + u64 av_audio_pkt_sec_size = 0; + + for (u16 pkt_idx = 0; pkt_idx < pkt->num_of_video_pkt; pkt_idx++) { + video_pkt_sec_size += reinterpret_cast( + &data_start[video_pkt_sec_size]) + ->length + + 4ULL; + } + + for (u16 pkt_idx = 0; pkt_idx < pkt->num_of_av_video_pkt; pkt_idx++) { + av_video_pkt_sec_size += + reinterpret_cast( + &data_start[video_pkt_sec_size + av_video_pkt_sec_size]) + ->length + + 4ULL; + } + + for (u16 pkt_idx = 0; pkt_idx < pkt->num_of_av_audio_pkt; pkt_idx++) { + av_audio_pkt_sec_size += + reinterpret_cast( + &data_start[video_pkt_sec_size + av_video_pkt_sec_size + + av_audio_pkt_sec_size]) + ->length + + 4ULL; + } + + return static_cast(sizeof(ps3av_pkt_inc_avset) + video_pkt_sec_size + + av_video_pkt_sec_size + av_audio_pkt_sec_size); + } + + void execute(vuart_av_thread &vuart, const void *pkt_buf) override { + const auto pkt = static_cast(pkt_buf); + auto pkt_data_addr = + static_cast(pkt_buf) + sizeof(ps3av_pkt_inc_avset); + + if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) { + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_BUFFER_OVERFLOW); + return; + } + + bool syscon_check_passed = true; + + // Video + u32 video_cmd_status = PS3AV_STATUS_SUCCESS; + for (u32 video_pkt_idx = 0; video_pkt_idx < pkt->num_of_video_pkt; + ++video_pkt_idx) { + const auto video_pkt = + reinterpret_cast(pkt_data_addr); + const u32 subcmd_status = video_pkt_parse(*video_pkt); + + if (video_pkt->video_head == PS3AV_HEAD_B_ANALOG) { + vuart.head_b_initialized = true; + } + + if (subcmd_status != PS3AV_STATUS_SUCCESS) { + video_cmd_status = subcmd_status; + } + + pkt_data_addr += video_pkt->hdr.length + 4ULL; + } + + if (pkt->num_of_av_video_pkt == 0U && pkt->num_of_av_audio_pkt == 0U) { + vuart.write_resp(pkt->hdr.cid, video_cmd_status); + return; + } + + bool hdcp_done[2]{}; + + // AV Video + for (u32 video_av_pkt_idx = 0; video_av_pkt_idx < pkt->num_of_av_video_pkt; + video_av_pkt_idx++) { + const auto av_video_pkt = + reinterpret_cast(pkt_data_addr); + const av_video_resp subcmd_resp = + av_video_pkt_parse(*av_video_pkt, syscon_check_passed); + + if (subcmd_resp.status != PS3AV_STATUS_SUCCESS) { + vuart.write_resp(pkt->hdr.cid, subcmd_resp.status); + return; + } + + if (syscon_check_passed) { + hdcp_done[0] |= subcmd_resp.hdcp_done_event[0]; + hdcp_done[1] |= subcmd_resp.hdcp_done_event[1]; + } + + pkt_data_addr += av_video_pkt->hdr.length + 4ULL; + } + + vuart.hdmi_res_set[0] = hdcp_done[0]; + vuart.hdmi_res_set[1] = hdcp_done[1]; + vuart.add_hdmi_events(UartHdmiEvent::HDCP_DONE, vuart.hdmi_res_set[0], + vuart.hdmi_res_set[1]); + + if (vuart.hdmi_res_set[0]) { + g_fxo->get().update_av_mute_state( + RsxaudioAvportIdx::HDMI_0, false, true); + } + if (vuart.hdmi_res_set[1]) { + g_fxo->get().update_av_mute_state( + RsxaudioAvportIdx::HDMI_1, false, true); + } + + bool valid_av_audio_pkt = false; + + // AV Audio + for (u32 audio_av_pkt_idx = 0; audio_av_pkt_idx < pkt->num_of_av_audio_pkt; + audio_av_pkt_idx++) { + const auto av_audio_pkt = + reinterpret_cast(pkt_data_addr); + + if (av_audio_pkt->avport <= static_cast(UartAudioAvport::HDMI_1)) { + valid_av_audio_pkt = true; + + if (!syscon_check_passed || + (av_audio_pkt->avport == + static_cast(UartAudioAvport::HDMI_1) && + !g_cfg.core.debug_console_mode)) { + syscon_check_passed = false; + break; + } + + const u8 hdmi_idx = + av_audio_pkt->avport == static_cast(UartAudioAvport::HDMI_1); + + g_fxo->get().update_hw_param([&](auto &obj) { + auto &hdmi = obj.hdmi[hdmi_idx]; + hdmi.init = true; + + const std::array fifomap = { + static_cast((av_audio_pkt->fifomap >> 0) & 3U), + static_cast((av_audio_pkt->fifomap >> 2) & 3U), + static_cast((av_audio_pkt->fifomap >> 4) & 3U), + static_cast((av_audio_pkt->fifomap >> 6) & 3U)}; + + const std::array en_streams = { + static_cast(av_audio_pkt->enable & 0x10), + static_cast(av_audio_pkt->enable & 0x20), + static_cast(av_audio_pkt->enable & 0x40), + static_cast(av_audio_pkt->enable & 0x80)}; + + // Might be wrong + const std::array swap_lr = { + static_cast(av_audio_pkt->swaplr & 0x10), + static_cast(av_audio_pkt->swaplr & 0x20), + static_cast(av_audio_pkt->swaplr & 0x40), + static_cast(av_audio_pkt->swaplr & 0x80)}; + + memcpy(hdmi.info_frame.data(), av_audio_pkt->info, + sizeof(av_audio_pkt->info)); + memcpy(hdmi.chstat.data(), av_audio_pkt->chstat, + sizeof(av_audio_pkt->chstat)); + + hdmi.ch_cfg = hdmi_param_conv(fifomap, en_streams, swap_lr); + }); + } + + pkt_data_addr += av_audio_pkt->hdr.length + 4ULL; + } + + if (pkt->num_of_av_video_pkt || valid_av_audio_pkt) { + if (!syscon_check_passed) { + vuart.write_resp(pkt->hdr.cid, + PS3AV_STATUS_SYSCON_COMMUNICATE_FAIL); + return; + } + + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); + } else { + vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); + } + } + +private: + struct video_sce_param { + u32 width_div; + u32 width; + u32 height; + }; + + struct av_video_resp { + u32 status = PS3AV_STATUS_SUCCESS; + bool hdcp_done_event[2]{}; + }; + + u32 video_pkt_parse(const ps3av_pkt_video_mode &video_head_cfg) { + static constexpr video_sce_param sce_param_arr[28] = { + {0, 0, 0}, {4, 2880, 480}, {4, 2880, 480}, {4, 2880, 576}, + {4, 2880, 576}, {2, 1440, 480}, {2, 1440, 576}, {1, 1920, 1080}, + {1, 1920, 1080}, {1, 1920, 1080}, {1, 1280, 720}, {1, 1280, 720}, + {1, 1920, 1080}, {1, 1920, 1080}, {1, 1920, 1080}, {1, 1920, 1080}, + {1, 1280, 768}, {1, 1280, 1024}, {1, 1920, 1200}, {1, 1360, 768}, + {1, 1280, 1470}, {1, 1280, 1470}, {1, 1920, 1080}, {1, 1920, 2205}, + {1, 1920, 2205}, {1, 1280, 721}, {1, 720, 481}, {1, 720, 577}}; + + const auto sce_idx = [&]() -> u8 { + switch (video_head_cfg.video_vid) { + case 16: + return 1; + case 1: + return 2; + case 3: + return 4; + case 17: + return 4; + case 5: + return 5; + case 6: + return 6; + case 18: + return 7; + case 7: + return 8; + case 33: + return 8; + case 8: + return 9; + case 34: + return 9; + case 9: + return 10; + case 31: + return 10; + case 10: + return 11; + case 32: + return 11; + case 11: + return 12; + case 35: + return 12; + case 37: + return 12; + case 12: + return 13; + case 36: + return 13; + case 38: + return 13; + case 19: + return 14; + case 20: + return 15; + case 13: + return 16; + case 14: + return 17; + case 15: + return 18; + case 21: + return 19; + case 22: + return 20; + case 27: + return 20; + case 23: + return 21; + case 28: + return 21; + case 24: + return 22; + case 25: + return 23; + case 29: + return 23; + case 26: + return 24; + case 30: + return 24; + case 39: + return 25; + case 40: + return 26; + case 41: + return 27; + default: + return umax; + } + }(); + + const video_sce_param &sce_param = sce_param_arr[sce_idx]; + if (sce_idx == umax || video_head_cfg.video_head > PS3AV_HEAD_B_ANALOG || + video_head_cfg.video_order > 1 || video_head_cfg.video_format > 16 || + video_head_cfg.video_out_format > 16 || + ((1ULL << video_head_cfg.video_out_format) & 0x1CE07) == 0U || + video_head_cfg.unk2 > 3 || video_head_cfg.pitch & 7 || + video_head_cfg.pitch > UINT16_MAX || + (video_head_cfg.width != 1280U && + ((video_head_cfg.width & 7) != 0U || + video_head_cfg.width > UINT16_MAX)) || + (sce_param.width != 720 && + video_head_cfg.width > sce_param.width / sce_param.width_div) || + !((video_head_cfg.height == 1470U && + (sce_param.height == 721 || sce_param.height == 481 || + sce_param.height == 577)) || + (video_head_cfg.height <= sce_param.height && + video_head_cfg.height <= UINT16_MAX))) { + return PS3AV_STATUS_INVALID_VIDEO_PARAM; + } + + sys_uart.notice( + "[inc_avset_cmd] new resolution on HEAD_%c width=%u height=%u", + video_head_cfg.video_head == PS3AV_HEAD_A_HDMI ? 'A' : 'B', + video_head_cfg.width, video_head_cfg.height); + + return PS3AV_STATUS_SUCCESS; + } + + av_video_resp av_video_pkt_parse(const ps3av_pkt_av_video_cs &pkt, + bool &syscon_pkt_valid) { + if (pkt.avport <= static_cast(UartAudioAvport::HDMI_1)) { + if (pkt.av_vid > 23) { + return {PS3AV_STATUS_INVALID_AV_PARAM}; + } + + if (pkt.avport == static_cast(UartAudioAvport::HDMI_1) && + !g_cfg.core.debug_console_mode) { + syscon_pkt_valid = false; + } else if (syscon_pkt_valid) { + // HDMI setup, code 0x80 + + av_video_resp resp{}; + resp.hdcp_done_event[pkt.avport] = true; + return resp; + } + } else { + if ((pkt.avport != static_cast(UartAudioAvport::AVMULTI_0) && + pkt.avport != static_cast(UartAudioAvport::AVMULTI_1)) || + pkt.av_vid > 23 || (pkt.av_vid > 12 && pkt.av_vid != 18U)) { + return {PS3AV_STATUS_INVALID_AV_PARAM}; + } + + if (pkt.avport == static_cast(UartAudioAvport::AVMULTI_1)) { + syscon_pkt_valid = false; + } else if (syscon_pkt_valid) { + // AVMULTI setup + } + } + + return {}; + } + + static rsxaudio_hw_param_t::hdmi_param_t::hdmi_ch_cfg_t hdmi_param_conv( + const std::array &map, + const std::array &en, + const std::array &swap) { + std::array result{}; + u8 ch_cnt = 0; + + for (usz stream_idx = 0; stream_idx < SYS_RSXAUDIO_SERIAL_STREAM_CNT; + stream_idx++) { + const u8 stream_pos = map[stream_idx]; + if (en[stream_pos]) { + result[stream_idx * 2 + 0] = stream_pos * 2 + swap[stream_pos]; + result[stream_idx * 2 + 1] = stream_pos * 2 + !swap[stream_pos]; + ch_cnt = static_cast((stream_idx + 1) * 2); + } else { + result[stream_idx * 2 + 0] = + rsxaudio_hw_param_t::hdmi_param_t::MAP_SILENT_CH; + result[stream_idx * 2 + 1] = + rsxaudio_hw_param_t::hdmi_param_t::MAP_SILENT_CH; + } + } + + const AudioChannelCnt ch_cnt_conv = [&]() { + switch (ch_cnt) { + default: + case 0: + case 2: + return AudioChannelCnt::STEREO; + case 4: + case 6: + return AudioChannelCnt::SURROUND_5_1; + case 8: + return AudioChannelCnt::SURROUND_7_1; + } + }(); + + return {result, ch_cnt_conv}; + } +}; + +struct generic_reply_cmd : public ps3av_cmd { + u16 get_size(vuart_av_thread & /*vuart*/, const void * /*pkt_buf*/) override { + return 0; + } + + void execute(vuart_av_thread &vuart, const void *pkt_buf) override { + const auto pkt = static_cast(pkt_buf); + + if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) { + vuart.write_resp(pkt->cid, PS3AV_STATUS_BUFFER_OVERFLOW); + return; + } + + sys_uart.todo("Unimplemented cid=0x%08x", pkt->cid); + + vuart.write_resp(pkt->cid, PS3AV_STATUS_SUCCESS); + } +}; + +error_code sys_uart_initialize(ppu_thread &ppu) { + ppu.state += cpu_flag::wait; + + sys_uart.trace("sys_uart_initialize()"); + + if (!g_ps3_process_info.has_root_perm()) { + return CELL_ENOSYS; + } + + auto &vuart_thread = g_fxo->get(); + + if (vuart_thread.initialized.test_and_set()) { + return CELL_EPERM; + } + + return CELL_OK; +} + +error_code sys_uart_receive(ppu_thread &ppu, vm::ptr buffer, u64 size, + u32 mode) { + sys_uart.trace("sys_uart_receive(buffer=*0x%x, size=0x%llx, mode=0x%x)", + buffer, size, mode); + + if (!g_ps3_process_info.has_root_perm()) { + return CELL_ENOSYS; + } + + if (!size) { + return CELL_OK; + } + + if (mode & ~(BLOCKING_BIG_OP | NOT_BLOCKING_BIG_OP)) { + return CELL_EINVAL; + } + + auto &vuart_thread = g_fxo->get(); + + if (!vuart_thread.initialized) { + return CELL_ESRCH; + } + + if (size > 0x20000U) { + // kmalloc restriction + fmt::throw_exception("Buffer is too big"); + } + + const std::unique_ptr data = std::make_unique(size); + u32 read_size = 0; + + auto vuart_read = [&](u8 *buf, u32 buf_size) -> s32 { + constexpr u32 ITER_SIZE = 4096; + std::unique_lock lock(vuart_thread.rx_mutex, std::defer_lock); + + if (!lock.try_lock()) { + return CELL_EBUSY; + } + + u32 read_size = 0; + u32 remaining = buf_size; + + while (read_size < buf_size) { + const u32 packet_size = std::min(remaining, ITER_SIZE); + const u32 nread = vuart_thread.read_rx_data(buf + read_size, packet_size); + read_size += nread; + remaining -= nread; + + if (nread < packet_size) + break; + } + + return read_size; + }; + + if (mode & BLOCKING_BIG_OP) { + // Yield before checking for packets + lv2_obj::sleep(ppu); + + for (;;) { + if (ppu.is_stopped()) { + return {}; + } + + std::unique_lock lock(vuart_thread.rx_wake_m); + const s32 read_result = vuart_read(data.get(), static_cast(size)); + + if (read_result > CELL_OK) { + read_size = read_result; + break; + } + + vuart_thread.rx_wake_c.wait_unlock(5000, lock); + } + + ppu.check_state(); + } else // NOT_BLOCKING_BIG_OP + { + const s32 read_result = vuart_read(data.get(), static_cast(size)); + + if (read_result <= CELL_OK) { + return read_result; + } + + read_size = read_result; + } + + if (!vm::check_addr(buffer.addr(), vm::page_writable, read_size)) { + return CELL_EFAULT; + } + + memcpy(buffer.get_ptr(), data.get(), read_size); + return not_an_error(read_size); +} + +error_code sys_uart_send(ppu_thread &ppu, vm::cptr buffer, u64 size, + u32 mode) { + sys_uart.trace("sys_uart_send(buffer=0x%x, size=0x%llx, mode=0x%x)", buffer, + size, mode); + + if (!g_ps3_process_info.has_root_perm()) { + return CELL_ENOSYS; + } + + if (!size) { + return CELL_OK; + } + + if (mode & ~(BLOCKING_BIG_OP | NOT_BLOCKING_OP | NOT_BLOCKING_BIG_OP)) { + return CELL_EINVAL; + } + + auto &vuart_thread = g_fxo->get(); + + if (!vuart_thread.initialized) { + return CELL_ESRCH; + } + + if (size > 0x20000U) { + // kmalloc restriction + fmt::throw_exception("Buffer is too big"); + } + + if (!vm::check_addr(buffer.addr(), vm::page_readable, + static_cast(size))) { + return CELL_EFAULT; + } + + const std::unique_ptr data = std::make_unique(size); + memcpy(data.get(), buffer.get_ptr(), size); + + std::unique_lock lock(vuart_thread.tx_mutex, std::defer_lock); + + constexpr u32 ITER_SIZE = 4096; + + if (mode & BLOCKING_BIG_OP) { + // Yield before sending packets + lv2_obj::sleep(ppu); + + lock.lock(); + + auto vuart_send_all = [&](const u8 *data, u32 data_sz) { + u32 rem_size = data_sz; + + while (rem_size) { + if (ppu.is_stopped()) { + return false; + } + + std::unique_lock lock(vuart_thread.tx_rdy_m); + if (vuart_thread.get_tx_bytes() >= PS3AV_TX_BUF_SIZE) { + vuart_thread.tx_rdy_c.wait_unlock(5000, lock); + } else { + lock.unlock(); + } + rem_size -= + vuart_thread.enque_tx_data(data + data_sz - rem_size, rem_size); + } + + return true; + }; + + u32 sent_size = 0; + u32 remaining = static_cast(size); + + while (remaining) { + const u32 packet_size = std::min(remaining, ITER_SIZE); + if (!vuart_send_all(data.get() + sent_size, packet_size)) + return {}; + sent_size += packet_size; + remaining -= packet_size; + } + + ppu.check_state(); + } else if (mode & NOT_BLOCKING_OP) { + if (!lock.try_lock()) { + return CELL_EBUSY; + } + + if (PS3AV_TX_BUF_SIZE - vuart_thread.get_tx_bytes() < size) { + return CELL_EAGAIN; + } + + return not_an_error( + vuart_thread.enque_tx_data(data.get(), static_cast(size))); + } else // NOT_BLOCKING_BIG_OP + { + if (!lock.try_lock()) { + return CELL_EBUSY; + } + + u32 sent_size = 0; + u32 remaining = static_cast(size); + + while (sent_size < size) { + const u32 packet_size = std::min(remaining, ITER_SIZE); + const u32 nsent = + vuart_thread.enque_tx_data(data.get() + sent_size, packet_size); + remaining -= nsent; + + if (nsent < packet_size) { + // Based on RE + if (sent_size == 0) { + return not_an_error(packet_size); // First iteration + } else if (sent_size + nsent < size) { + return not_an_error(sent_size + nsent); + } else { + break; // Last iteration + } + } + + sent_size += nsent; + } + } + + return not_an_error(size); +} + +error_code sys_uart_get_params(vm::ptr buffer) { + sys_uart.trace("sys_uart_get_params(buffer=0x%x)", buffer); + + if (!g_ps3_process_info.has_root_perm()) { + return CELL_ENOSYS; + } + + auto &vuart_thread = g_fxo->get(); + + if (!vuart_thread.initialized) { + return CELL_ESRCH; + } + + if (!vm::check_addr(buffer.addr(), vm::page_writable, sizeof(vuart_params))) { + return CELL_EFAULT; + } + + buffer->rx_buf_size = PS3AV_RX_BUF_SIZE; + buffer->tx_buf_size = PS3AV_TX_BUF_SIZE; + + return CELL_OK; +} + +void vuart_av_thread::operator()() { + while (thread_ctrl::state() != thread_state::aborting) { + if (Emu.IsPausedOrReady()) { + thread_ctrl::wait_for(5000); + continue; + } + + const u64 hdmi_event_dist[2] = {hdmi_event_handler[0].time_until_next(), + hdmi_event_handler[1].time_until_next()}; + bool update_dist = false; + + if (hdmi_event_dist[0] == 0) { + dispatch_hdmi_event(hdmi_event_handler[0].get_occured_event(), + UartAudioAvport::HDMI_0); + update_dist |= hdmi_event_handler[0].events_available(); + } + + if (hdmi_event_dist[1] == 0) { + dispatch_hdmi_event(hdmi_event_handler[1].get_occured_event(), + UartAudioAvport::HDMI_1); + update_dist |= hdmi_event_handler[1].events_available(); + } + + if (update_dist) { + continue; + } + + const u64 wait_time = [&]() { + if (hdmi_event_dist[0] != 0 && hdmi_event_dist[1] != 0) + return std::min(hdmi_event_dist[0], hdmi_event_dist[1]); + else + return std::max(hdmi_event_dist[0], hdmi_event_dist[1]); + }(); + + std::unique_lock lock(tx_wake_m); + if (!tx_buf.get_used_size()) { + tx_wake_c.wait_unlock(wait_time ? wait_time : -1, lock); + } else { + lock.unlock(); + } + + if (u32 read_size = read_tx_data(temp_tx_buf, PS3AV_TX_BUF_SIZE)) { + parse_tx_buffer(read_size); + + // Give vsh some time + thread_ctrl::wait_for(1000 * 100 / g_cfg.core.clocks_scale); + + commit_rx_buf(false); + commit_rx_buf(true); + } + } +} + +void vuart_av_thread::parse_tx_buffer(u32 buf_size) { + if (buf_size >= PS3AV_TX_BUF_SIZE) { + while (read_tx_data(temp_tx_buf, PS3AV_TX_BUF_SIZE) >= PS3AV_TX_BUF_SIZE) + ; + write_resp(reinterpret_cast *>(temp_tx_buf)[3], + PS3AV_STATUS_BUFFER_OVERFLOW); + return; + } + + u32 read_ptr = 0; + + while (buf_size) { + const ps3av_header *const hdr = + reinterpret_cast(&temp_tx_buf[read_ptr]); + const u16 pkt_size = hdr->length + 4; + + if (hdr->length == 0xFFFCU) { + write_resp(0xDEAD, PS3AV_STATUS_FAILURE); + return; + } + + if (hdr->version != PS3AV_VERSION) { + if (hdr->version >= 0x100 && hdr->version < PS3AV_VERSION) { + sys_uart.todo("Unimplemented AV version: 0x%04x", hdr->version); + } + + write_resp(static_cast(hdr->cid.get()), + PS3AV_STATUS_INVALID_COMMAND); + return; + } + + const void *const pkt_storage = &temp_tx_buf[read_ptr]; + read_ptr += pkt_size; + buf_size = buf_size < pkt_size ? 0 : buf_size - pkt_size; + + auto cmd = get_cmd(hdr->cid); + + if (!cmd.get()) { + sys_uart.error("Unknown AV cmd: 0x%08x", hdr->cid); + continue; + } + + const auto cmd_size = cmd->get_size(*this, pkt_storage); + + if (cmd_size != pkt_size && cmd_size) { + sys_uart.error("Invalid size for cid=0x%x, expected=0x%x, got=0x%x", + static_cast(pkt_storage)->cid, + cmd_size, pkt_size); + write_resp(static_cast(hdr->cid.get()), + PS3AV_STATUS_INVALID_SAMPLE_SIZE); + return; + } + + cmd->execute(*this, pkt_storage); + } +} + +vuart_av_thread &vuart_av_thread::operator=(thread_state) { + { + std::lock_guard lock(tx_wake_m); + } + tx_wake_c.notify_all(); + return *this; +} + +u32 vuart_av_thread::enque_tx_data(const void *data, u32 data_sz) { + std::unique_lock lock(tx_wake_m); + if (u32 size = static_cast(tx_buf.push(data, data_sz, true))) { + lock.unlock(); + tx_wake_c.notify_all(); + return size; + } + + return 0; +} + +u32 vuart_av_thread::get_tx_bytes() { + return static_cast(tx_buf.get_used_size()); +} + +u32 vuart_av_thread::read_rx_data(void *data, u32 data_sz) { + return static_cast(rx_buf.pop(data, data_sz, true)); +} + +u32 vuart_av_thread::read_tx_data(void *data, u32 data_sz) { + std::unique_lock lock(tx_rdy_m); + if (u32 size = static_cast(tx_buf.pop(data, data_sz, true))) { + lock.unlock(); + tx_rdy_c.notify_all(); + return size; + } + return 0; +} + +u32 vuart_av_thread::get_reply_buf_free_size() { + return sizeof(temp_rx_buf.buf) - temp_rx_buf.crnt_size; +} + +template +void vuart_av_thread::write_resp(u32 cid, u32 status, const void *data, + u16 data_size) { + const ps3av_pkt_reply_hdr pkt_hdr = {PS3AV_VERSION, data_size + 8U, + cid | PS3AV_REPLY_BIT, status}; + + if (status != PS3AV_STATUS_SUCCESS) { + sys_uart.error("Packet failed cid=0x%08x status=0x%02x", cid, status); + } + + temp_buf &buf = UseScBuffer ? temp_rx_sc_buf : temp_rx_buf; + const u32 total_size = sizeof(pkt_hdr) + data_size; + + if (buf.crnt_size + total_size <= sizeof(buf.buf)) { + memcpy(&buf.buf[buf.crnt_size], &pkt_hdr, sizeof(pkt_hdr)); + memcpy(&buf.buf[buf.crnt_size + sizeof(pkt_hdr)], data, data_size); + buf.crnt_size += total_size; + } +} + +std::shared_ptr vuart_av_thread::get_cmd(u32 cid) { + switch (cid) { + case PS3AV_CID_AV_CEC_MESSAGE: + case PS3AV_CID_AV_UNK11: + case PS3AV_CID_AV_UNK12: + return std::make_shared(); + + // AV commands + case PS3AV_CID_AV_INIT: + return std::make_shared(); + case PS3AV_CID_AV_FIN: + return std::make_shared(); + case PS3AV_CID_AV_GET_HW_CONF: + return std::make_shared(); + case PS3AV_CID_AV_GET_MONITOR_INFO: + return std::make_shared(); + case PS3AV_CID_AV_GET_BKSV_LIST: + return std::make_shared(); + case PS3AV_CID_AV_ENABLE_EVENT: + return std::make_shared(); + case PS3AV_CID_AV_DISABLE_EVENT: + return std::make_shared(); + case PS3AV_CID_AV_TV_MUTE: + return std::make_shared(); + case PS3AV_CID_AV_NULL_CMD: + return std::make_shared(); + case PS3AV_CID_AV_GET_AKSV: + return std::make_shared(); + case PS3AV_CID_AV_VIDEO_DISABLE_SIG: + return std::make_shared(); + case PS3AV_CID_AV_VIDEO_YTRAPCONTROL: + return std::make_shared(); + case PS3AV_CID_AV_AUDIO_MUTE: + return std::make_shared(); + case PS3AV_CID_AV_ACP_CTRL: + return std::make_shared(); + case PS3AV_CID_AV_SET_ACP_PACKET: + return std::make_shared(); + case PS3AV_CID_AV_ADD_SIGNAL_CTL: + return std::make_shared(); + case PS3AV_CID_AV_SET_CGMS_WSS: + return std::make_shared(); + case PS3AV_CID_AV_HDMI_MODE: + return std::make_shared(); + case PS3AV_CID_AV_GET_CEC_CONFIG: + return std::make_shared(); + + // Video commands + case PS3AV_CID_VIDEO_INIT: + return std::make_shared(); + case PS3AV_CID_VIDEO_ROUTE: + return std::make_shared(); + case PS3AV_CID_VIDEO_FORMAT: + return std::make_shared(); + case PS3AV_CID_VIDEO_PITCH: + return std::make_shared(); + case PS3AV_CID_VIDEO_GET_HW_CONF: + return std::make_shared(); + + // Audio commands + case PS3AV_CID_AUDIO_INIT: + return std::make_shared(); + case PS3AV_CID_AUDIO_MODE: + return std::make_shared(); + case PS3AV_CID_AUDIO_MUTE: + return std::make_shared(); + case PS3AV_CID_AUDIO_ACTIVE: + return std::make_shared(); + case PS3AV_CID_AUDIO_INACTIVE: + return std::make_shared(); + case PS3AV_CID_AUDIO_SPDIF_BIT: + return std::make_shared(); + case PS3AV_CID_AUDIO_CTRL: + return std::make_shared(); + + // Multipacket + case PS3AV_CID_AVB_PARAM: + return std::make_shared(); + + default: + return {}; + } +} + +void vuart_av_thread::commit_rx_buf(bool syscon_buf) { + temp_buf &buf = syscon_buf ? temp_rx_sc_buf : temp_rx_buf; + + std::unique_lock lock(rx_wake_m); + rx_buf.push(buf.buf, buf.crnt_size, true); + buf.crnt_size = 0; + + if (rx_buf.get_used_size()) { + lock.unlock(); + rx_wake_c.notify_all(); + } +} + +void vuart_av_thread::add_hdmi_events(UartHdmiEvent first_event, + UartHdmiEvent last_event, bool hdmi_0, + bool hdmi_1) { + if (hdmi_0) { + hdmi_event_handler[0].set_target_state(first_event, last_event); + } + + if (hdmi_1 && g_cfg.core.debug_console_mode) { + hdmi_event_handler[1].set_target_state(first_event, last_event); + } +} + +void vuart_av_thread::add_hdmi_events(UartHdmiEvent last_event, bool hdmi_0, + bool hdmi_1) { + add_hdmi_events(last_event, last_event, hdmi_0, hdmi_1); +} + +void vuart_av_thread::dispatch_hdmi_event(UartHdmiEvent event, + UartAudioAvport hdmi) { + const bool hdmi_0 = hdmi == UartAudioAvport::HDMI_0; + const bool hdmi_1 = hdmi == UartAudioAvport::HDMI_1; + + switch (event) { + case UartHdmiEvent::UNPLUGGED: { + add_unplug_event(hdmi_0, hdmi_1); + break; + } + case UartHdmiEvent::PLUGGED: { + add_plug_event(hdmi_0, hdmi_1); + break; + } + case UartHdmiEvent::HDCP_DONE: { + add_hdcp_done_event(hdmi_0, hdmi_1); + break; + } + default: + break; + } +} + +RsxaudioAvportIdx vuart_av_thread::avport_to_idx(UartAudioAvport avport) { + switch (avport) { + case UartAudioAvport::HDMI_0: + return RsxaudioAvportIdx::HDMI_0; + case UartAudioAvport::HDMI_1: + return RsxaudioAvportIdx::HDMI_1; + case UartAudioAvport::AVMULTI_0: + return RsxaudioAvportIdx::AVMULTI; + case UartAudioAvport::SPDIF_0: + return RsxaudioAvportIdx::SPDIF_0; + case UartAudioAvport::SPDIF_1: + return RsxaudioAvportIdx::SPDIF_1; + default: + ensure(false); + return RsxaudioAvportIdx::HDMI_0; + } +} + +void vuart_av_thread::add_unplug_event(bool hdmi_0, bool hdmi_1) { + if ((hdmi_events_bitmask & PS3AV_EVENT_BIT_UNPLUGGED) == 0) + return; + + ps3av_header pkt{}; + pkt.version = av_cmd_ver; + pkt.length = sizeof(ps3av_header) - 4; + pkt.cid = PS3AV_CID_EVENT_UNPLUGGED; + + if (hdmi_0) { + g_fxo->get().update_av_mute_state(RsxaudioAvportIdx::HDMI_0, + false, true); + hdcp_first_auth[0] = true; + commit_event_data(&pkt, sizeof(pkt)); + } + + if (hdmi_1) { + g_fxo->get().update_av_mute_state(RsxaudioAvportIdx::HDMI_1, + false, true); + hdcp_first_auth[1] = true; + pkt.cid |= 0x10000; + commit_event_data(&pkt, sizeof(pkt)); + } +} + +void vuart_av_thread::add_plug_event(bool hdmi_0, bool hdmi_1) { + if ((hdmi_events_bitmask & PS3AV_EVENT_BIT_PLUGGED) == 0) + return; + + ps3av_pkt_hdmi_plugged_event pkt{}; + pkt.hdr.version = av_cmd_ver; + pkt.hdr.length = sizeof(ps3av_pkt_hdmi_plugged_event) - 8; + pkt.hdr.cid = PS3AV_CID_EVENT_PLUGGED; + + if (hdmi_0) { + g_fxo->get().update_av_mute_state(RsxaudioAvportIdx::HDMI_0, + false, true); + av_get_monitor_info_cmd::set_hdmi_display_cfg( + *this, pkt.minfo, static_cast(UartAudioAvport::HDMI_0)); + commit_event_data(&pkt, sizeof(pkt) - 4); + } + + if (hdmi_1) { + g_fxo->get().update_av_mute_state(RsxaudioAvportIdx::HDMI_1, + false, true); + memset(&pkt.minfo, 0, sizeof(pkt.minfo)); + pkt.hdr.cid |= 0x10000; + av_get_monitor_info_cmd::set_hdmi_display_cfg( + *this, pkt.minfo, static_cast(UartAudioAvport::HDMI_1)); + commit_event_data(&pkt, sizeof(pkt) - 4); + } +} + +void vuart_av_thread::add_hdcp_done_event(bool hdmi_0, bool hdmi_1) { + u16 pkt_size = offsetof(ps3av_pkt_hdmi_hdcp_done_event, ksv_arr); + ps3av_pkt_hdmi_hdcp_done_event pkt{}; + pkt.hdr.version = av_cmd_ver; + + if (hdmi_behavior_mode == PS3AV_HDMI_BEHAVIOR_NORMAL || + !(hdmi_behavior_mode & PS3AV_HDMI_BEHAVIOR_HDCP_OFF)) { + pkt.ksv_cnt = 1; + memcpy(&pkt.ksv_arr[0], PS3AV_BKSV_VALUE, sizeof(PS3AV_BKSV_VALUE)); + pkt_size = (pkt_size + 5 * pkt.ksv_cnt + 3) & 0xFFFC; + } + + pkt.hdr.length = pkt_size - 4; + + if (hdmi_0) { + g_fxo->get().update_av_mute_state(RsxaudioAvportIdx::HDMI_0, + false, true, false); + + if (hdcp_first_auth[0]) { + if (hdmi_events_bitmask & PS3AV_EVENT_BIT_HDCP_DONE) { + hdcp_first_auth[0] = false; + pkt.hdr.cid = PS3AV_CID_EVENT_HDCP_DONE; + commit_event_data(&pkt, pkt_size); + } + } else if (hdmi_events_bitmask & PS3AV_EVENT_BIT_HDCP_REAUTH) { + pkt.hdr.cid = PS3AV_CID_EVENT_HDCP_REAUTH; + commit_event_data(&pkt, pkt_size); + } + } + + if (hdmi_1) { + g_fxo->get().update_av_mute_state(RsxaudioAvportIdx::HDMI_1, + false, true, false); + + if (hdcp_first_auth[1]) { + if (hdmi_events_bitmask & PS3AV_EVENT_BIT_HDCP_DONE) { + hdcp_first_auth[1] = false; + pkt.hdr.cid = PS3AV_CID_EVENT_HDCP_DONE | 0x10000; + commit_event_data(&pkt, pkt_size); + } + } else if (hdmi_events_bitmask & PS3AV_EVENT_BIT_HDCP_REAUTH) { + pkt.hdr.cid = PS3AV_CID_EVENT_HDCP_REAUTH | 0x10000; + commit_event_data(&pkt, pkt_size); + } + } +} + +void vuart_av_thread::commit_event_data(const void *data, u16 data_size) { + std::unique_lock lock(rx_wake_m); + rx_buf.push(data, data_size, true); + + if (rx_buf.get_used_size()) { + lock.unlock(); + rx_wake_c.notify_all(); + } +} + +vuart_hdmi_event_handler::vuart_hdmi_event_handler(u64 time_offset) + : time_offset(time_offset) {} + +void vuart_hdmi_event_handler::set_target_state(UartHdmiEvent start_state, + UartHdmiEvent end_state) { + ensure(start_state != UartHdmiEvent::NONE && + static_cast(start_state) <= static_cast(end_state)); + + base_state = static_cast(std::min( + static_cast(start_state) - 1, static_cast(current_to_state))); + target_state = end_state; + + if (!events_available()) { + advance_state(); + } +} + +bool vuart_hdmi_event_handler::events_available() { + return time_of_next_event != 0; +} + +u64 vuart_hdmi_event_handler::time_until_next() { + const u64 current_time = get_system_time(); + + if (!events_available() || + current_time + EVENT_TIME_THRESHOLD >= time_of_next_event) { + return 0; + } + + return time_of_next_event - current_time; +} + +UartHdmiEvent vuart_hdmi_event_handler::get_occured_event() { + if (events_available() && time_until_next() == 0) { + const UartHdmiEvent occured = current_to_state; + advance_state(); + + return occured; + } + + return UartHdmiEvent::NONE; +} + +void vuart_hdmi_event_handler::schedule_next() { + time_of_next_event = get_system_time() + (EVENT_TIME_DURATION + time_offset) * + 100 / g_cfg.core.clocks_scale; +} + +void vuart_hdmi_event_handler::advance_state() { + current_state = current_to_state; + + while (base_state != target_state) { + base_state = static_cast(static_cast(base_state) + 1); + + if (current_state == UartHdmiEvent::UNPLUGGED && + base_state == UartHdmiEvent::UNPLUGGED) { + continue; + } + + if (current_state == UartHdmiEvent::PLUGGED && + base_state == UartHdmiEvent::PLUGGED) { + continue; + } + + if (current_state == UartHdmiEvent::HDCP_DONE && + base_state == UartHdmiEvent::PLUGGED) { + continue; + } + + current_to_state = base_state; + schedule_next(); + return; + } + + time_of_next_event = 0; +} diff --git a/kernel/cellos/src/sys_usbd.cpp b/kernel/cellos/src/sys_usbd.cpp new file mode 100644 index 000000000..977208c0f --- /dev/null +++ b/kernel/cellos/src/sys_usbd.cpp @@ -0,0 +1,1666 @@ +#include "stdafx.h" + +#include "sys_ppu_thread.h" +#include "sys_sync.h" +#include "sys_usbd.h" + +#include "Emu/IdManager.h" +#include "Emu/System.h" +#include "Emu/system_config.h" +#include "Emu/vfs_config.h" +#include + +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/Cell/PPUThread.h" +#include "Emu/Cell/timers.hpp" + +// FIXME: following includes should be removed from kernel +#include "Emu/Io/Buzz.h" +#include "Emu/Io/Dimensions.h" +#include "Emu/Io/GHLtar.h" +#include "Emu/Io/GameTablet.h" +#include "Emu/Io/GunCon3.h" +#include "Emu/Io/Infinity.h" +#include "Emu/Io/RB3MidiDrums.h" +#include "Emu/Io/RB3MidiGuitar.h" +#include "Emu/Io/RB3MidiKeyboard.h" +#include "Emu/Io/Skylander.h" +#include "Emu/Io/TopShotElite.h" +#include "Emu/Io/TopShotFearmaster.h" +#include "Emu/Io/Turntable.h" +#include "Emu/Io/buzz_config.h" +#include "Emu/Io/ghltar_config.h" +#include "Emu/Io/guncon3_config.h" +#include "Emu/Io/midi_config_types.h" +#include "Emu/Io/rb3drums_config.h" +#include "Emu/Io/topshotelite_config.h" +#include "Emu/Io/topshotfearmaster_config.h" +#include "Emu/Io/turntable_config.h" +#include "Emu/Io/usb_device.h" +#include "Emu/Io/usb_vfs.h" +#include "Emu/Io/usio.h" +#include "Emu/Io/usio_config.h" + +#include + +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::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 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 (*make_instance)(u32, const std::array &); + 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(); + } + + 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 &arg1, vm::ptr &arg2, vm::ptr &arg3); + void add_event(u64 arg1, u64 arg2, u64 arg3); + + // Transfers related functions + std::pair get_free_transfer(); + std::pair get_transfer_status(u32 transfer_id); + std::pair + get_isochronous_transfer_status(u32 transfer_id); + void push_fake_transfer(UsbTransfer *transfer); + + const std::array &get_new_location(); + void connect_usb_device(std::shared_ptr dev, + bool update_usb_devices = false); + void disconnect_usb_device(std::shared_ptr dev, + bool update_usb_devices = false); + + // Map of devices actively handled by the ps3(device_id, device) + std::map>> + handled_devices; + std::map>> + pad_to_usb; + + shared_mutex mutex; + atomic_t is_init = false; + + // sys_usbd_receive_event PPU Threads + shared_mutex mutex_sq; + ppu_thread *sq{}; + + atomic_t 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 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> + ldds; + + const std::vector 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 + {0x046D, 0xC283, 0xC29B, "lgFF_c283_c29b", nullptr, nullptr}, + {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 open_pipes; + // Transfers infos + shared_mutex mutex_transfers; + std::array transfers; + std::vector fake_transfers; + + // Queue of pending usbd events + std::queue> usbd_events; + + // List of devices "connected" to the ps3 + std::array location{}; + std::vector> usb_devices; + std::unordered_map> + usb_passthrough_devices; + + libusb_context *ctx = nullptr; + +#if LIBUSB_API_VERSION >= 0x01000102 + libusb_hotplug_callback_handle callback_handle{}; +#endif + + bool hotplug_supported = false; +}; + +void LIBUSB_CALL callback_transfer(struct libusb_transfer *transfer) { + auto &usbh = g_fxo->get>(); + + if (!usbh.is_init) + return; + + usbh.transfer_complete(transfer); +} + +#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 + +#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 seen_usb_devices; + + if (ndev < 0) { + sys_usbd.error("Failed to get device list: %s", + libusb_error_name(static_cast(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(desc.idVendor) << 48) | + (static_cast(desc.idProduct) << 32) | + (static_cast(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_dev = + std::make_shared(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 options = {std::move(log_lv_opt), + std::move(log_cb_opt)}; + + if (int res = libusb_init_context(&ctx, options.data(), + static_cast(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_DEVICE_ARRIVED | + LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT), + static_cast(0), LIBUSB_HOTPLUG_MATCH_ANY, + LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY, + static_cast(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 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_info, get_new_location())); + } + +#ifndef WITHOUT_RTMIDI + const std::vector 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( + get_new_location(), device.name, false)); + break; + case midi_device_type::guitar_22fret: + usb_devices.push_back(std::make_shared( + get_new_location(), device.name, true)); + break; + case midi_device_type::keyboard: + usb_devices.push_back(std::make_shared( + 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( + get_new_location(), device.name)); + + sys_usbd.notice("RB3 drums config=\n", g_cfg_rb3drums.to_string()); + break; + } + } +#endif +} + +usb_handler_thread::~usb_handler_thread() { + // Ensures shared_ptr 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); + } + +#if LIBUSB_API_VERSION >= 0x01000102 + if (ctx && hotplug_supported) + libusb_hotplug_deregister_callback(ctx, callback_handle); +#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(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 &arg1, vm::ptr &arg2, + vm::ptr &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(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 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 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 +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 &usb_handler_thread::get_new_location() { + location[0]++; + return location; +} + +void usb_handler_thread::connect_usb_device(std::shared_ptr 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(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 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>(); + 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 dev = + std::make_shared(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 dev = + std::make_shared(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 dev = + std::make_shared( + 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 dev = std::make_shared( + 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>()) { + usbh->usb_hotplug_timeout = + get_system_time() + (connected ? 1'000'000ull : 0); + } +} + +error_code sys_usbd_initialize(ppu_thread &ppu, vm::ptr handle) { + ppu.state += cpu_flag::wait; + + sys_usbd.warning("sys_usbd_initialize(handle=*0x%x)", handle); + + auto &usbh = g_fxo->get>(); + + { + 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>(); + + std::lock_guard lock(usbh.mutex); + usbh.is_init = false; + + // Forcefully awake all waiters + while (auto cpu = lv2_obj::schedule(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 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>(); + + std::lock_guard lock(usbh.mutex); + if (!usbh.is_init) + return CELL_EINVAL; + + // TODO: was std::min + u32 i_tocopy = std::min(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 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>(); + + 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 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>(); + + 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>(); + + 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 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>(); + + 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(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 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> + 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 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>(); + + 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(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>(); + + 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>(); + + 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 arg1, vm::ptr arg2, + vm::ptr 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>(); + + { + 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 buf, u32 buf_size, + vm::ptr 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>(); + + 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(+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 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>(); + + 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 result, + vm::ptr 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>(); + + 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 request, vm::ptr 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>(); + + 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 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>(); + + 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>(); + + 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; +} diff --git a/kernel/cellos/src/sys_vm.cpp b/kernel/cellos/src/sys_vm.cpp new file mode 100644 index 000000000..e8ec069d0 --- /dev/null +++ b/kernel/cellos/src/sys_vm.cpp @@ -0,0 +1,401 @@ +#include "stdafx.h" + +#include "sys_process.h" +#include "sys_vm.h" + +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/Cell/PPUThread.h" +#include "Emu/Cell/timers.hpp" +#include "Emu/IdManager.h" + +sys_vm_t::sys_vm_t(u32 _addr, u32 vsize, lv2_memory_container *ct, u32 psize) + : ct(ct), addr(_addr), size(vsize), psize(psize) { + // Write ID + g_ids[addr >> 28].release(idm::last_id()); +} + +void sys_vm_t::save(utils::serial &ar) { + USING_SERIALIZATION_VERSION(lv2_vm); + ar(ct->id, addr, size, psize); +} + +sys_vm_t::~sys_vm_t() { + // Free ID + g_ids[addr >> 28].release(id_manager::id_traits::invalid); +} + +LOG_CHANNEL(sys_vm); + +struct sys_vm_global_t { + atomic_t total_vsize = 0; +}; + +sys_vm_t::sys_vm_t(utils::serial &ar) + : ct(lv2_memory_container::search(ar)), addr(ar), size(ar), psize(ar) { + g_ids[addr >> 28].release(idm::last_id()); + g_fxo->need(); + g_fxo->get().total_vsize += size; +} + +error_code sys_vm_memory_map(ppu_thread &ppu, u64 vsize, u64 psize, u32 cid, + u64 flag, u64 policy, vm::ptr addr) { + ppu.state += cpu_flag::wait; + + sys_vm.warning("sys_vm_memory_map(vsize=0x%x, psize=0x%x, cid=0x%x, " + "flags=0x%x, policy=0x%x, addr=*0x%x)", + vsize, psize, cid, flag, policy, addr); + + if (!vsize || !psize || vsize % 0x200'0000 || vsize > 0x1000'0000 || + psize % 0x1'0000 || policy != SYS_VM_POLICY_AUTO_RECOMMENDED) { + return CELL_EINVAL; + } + + if (ppu.gpr[11] == 300 && psize < 0x10'0000) { + return CELL_EINVAL; + } + + const auto idm_ct = idm::get_unlocked(cid); + + const auto ct = cid == SYS_MEMORY_CONTAINER_ID_INVALID + ? &g_fxo->get() + : idm_ct.get(); + + if (!ct) { + return CELL_ESRCH; + } + + if (!g_fxo->get() + .total_vsize + .fetch_op([vsize, has_root = g_ps3_process_info.has_root_perm()]( + u32 &size) { + // A single process can hold up to 256MB of virtual memory, even on + // DECR + // VSH can hold more + if ((has_root ? 0x1E000000 : 0x10000000) - size < vsize) { + return false; + } + + size += static_cast(vsize); + return true; + }) + .second) { + return CELL_EBUSY; + } + + if (!ct->take(psize)) { + g_fxo->get().total_vsize -= static_cast(vsize); + return CELL_ENOMEM; + } + + // Look for unmapped space + if (const auto area = vm::find_map(0x10000000, 0x10000000, + 2 | (flag & SYS_MEMORY_PAGE_SIZE_MASK))) { + sys_vm.warning("sys_vm_memory_map(): Found VM 0x%x area (vsize=0x%x)", addr, + vsize); + + // Alloc all memory (shall not fail) + ensure(area->alloc(static_cast(vsize))); + vm::lock_sudo(area->addr, static_cast(vsize)); + + idm::make(area->addr, static_cast(vsize), ct, + static_cast(psize)); + + // Write a pointer for the allocated memory + ppu.check_state(); + *addr = area->addr; + return CELL_OK; + } + + ct->free(psize); + g_fxo->get().total_vsize -= static_cast(vsize); + return CELL_ENOMEM; +} + +error_code sys_vm_memory_map_different(ppu_thread &ppu, u64 vsize, u64 psize, + u32 cid, u64 flag, u64 policy, + vm::ptr addr) { + ppu.state += cpu_flag::wait; + + sys_vm.warning("sys_vm_memory_map_different(vsize=0x%x, psize=0x%x, " + "cid=0x%x, flags=0x%llx, policy=0x%llx, addr=*0x%x)", + vsize, psize, cid, flag, policy, addr); + // TODO: if needed implement different way to map memory, unconfirmed. + + return sys_vm_memory_map(ppu, vsize, psize, cid, flag, policy, addr); +} + +error_code sys_vm_unmap(ppu_thread &ppu, u32 addr) { + ppu.state += cpu_flag::wait; + + sys_vm.warning("sys_vm_unmap(addr=0x%x)", addr); + + // Special case, check if its a start address by alignment + if (addr % 0x10000000) { + return CELL_EINVAL; + } + + // Free block and info + const auto vmo = + idm::withdraw(sys_vm_t::find_id(addr), [&](sys_vm_t &vmo) { + // Free block + ensure(vm::unmap(addr).second); + + // Return memory + vmo.ct->free(vmo.psize); + g_fxo->get().total_vsize -= vmo.size; + }); + + if (!vmo) { + return CELL_EINVAL; + } + + return CELL_OK; +} + +error_code sys_vm_append_memory(ppu_thread &ppu, u32 addr, u64 size) { + ppu.state += cpu_flag::wait; + + sys_vm.warning("sys_vm_append_memory(addr=0x%x, size=0x%x)", addr, size); + + if (!size || size % 0x100000) { + return CELL_EINVAL; + } + + const auto block = idm::check(sys_vm_t::find_id(addr), + [&](sys_vm_t &vmo) -> CellError { + if (vmo.addr != addr) { + return CELL_EINVAL; + } + + if (!vmo.ct->take(size)) { + return CELL_ENOMEM; + } + + vmo.psize += static_cast(size); + return {}; + }); + + if (!block) { + return CELL_EINVAL; + } + + if (block.ret) { + return block.ret; + } + + return CELL_OK; +} + +error_code sys_vm_return_memory(ppu_thread &ppu, u32 addr, u64 size) { + ppu.state += cpu_flag::wait; + + sys_vm.warning("sys_vm_return_memory(addr=0x%x, size=0x%x)", addr, size); + + if (!size || size % 0x100000) { + return CELL_EINVAL; + } + + const auto block = idm::check( + sys_vm_t::find_id(addr), [&](sys_vm_t &vmo) -> CellError { + if (vmo.addr != addr) { + return CELL_EINVAL; + } + + auto [_, ok] = vmo.psize.fetch_op([&](u32 &value) { + if (value <= size || value - size < 0x100000ull) { + return false; + } + + value -= static_cast(size); + return true; + }); + + if (!ok) { + return CELL_EBUSY; + } + + vmo.ct->free(size); + return {}; + }); + + if (!block) { + return CELL_EINVAL; + } + + if (block.ret) { + return block.ret; + } + + return CELL_OK; +} + +error_code sys_vm_lock(ppu_thread &ppu, u32 addr, u32 size) { + ppu.state += cpu_flag::wait; + + sys_vm.warning("sys_vm_lock(addr=0x%x, size=0x%x)", addr, size); + + if (!size) { + return CELL_EINVAL; + } + + const auto block = idm::get_unlocked(sys_vm_t::find_id(addr)); + + if (!block || u64{addr} + size > u64{block->addr} + block->size) { + return CELL_EINVAL; + } + + return CELL_OK; +} + +error_code sys_vm_unlock(ppu_thread &ppu, u32 addr, u32 size) { + ppu.state += cpu_flag::wait; + + sys_vm.warning("sys_vm_unlock(addr=0x%x, size=0x%x)", addr, size); + + if (!size) { + return CELL_EINVAL; + } + + const auto block = idm::get_unlocked(sys_vm_t::find_id(addr)); + + if (!block || u64{addr} + size > u64{block->addr} + block->size) { + return CELL_EINVAL; + } + + return CELL_OK; +} + +error_code sys_vm_touch(ppu_thread &ppu, u32 addr, u32 size) { + ppu.state += cpu_flag::wait; + + sys_vm.warning("sys_vm_touch(addr=0x%x, size=0x%x)", addr, size); + + if (!size) { + return CELL_EINVAL; + } + + const auto block = idm::get_unlocked(sys_vm_t::find_id(addr)); + + if (!block || u64{addr} + size > u64{block->addr} + block->size) { + return CELL_EINVAL; + } + + return CELL_OK; +} + +error_code sys_vm_flush(ppu_thread &ppu, u32 addr, u32 size) { + ppu.state += cpu_flag::wait; + + sys_vm.warning("sys_vm_flush(addr=0x%x, size=0x%x)", addr, size); + + if (!size) { + return CELL_EINVAL; + } + + const auto block = idm::get_unlocked(sys_vm_t::find_id(addr)); + + if (!block || u64{addr} + size > u64{block->addr} + block->size) { + return CELL_EINVAL; + } + + return CELL_OK; +} + +error_code sys_vm_invalidate(ppu_thread &ppu, u32 addr, u32 size) { + ppu.state += cpu_flag::wait; + + sys_vm.warning("sys_vm_invalidate(addr=0x%x, size=0x%x)", addr, size); + + if (!size) { + return CELL_EINVAL; + } + + const auto block = idm::get_unlocked(sys_vm_t::find_id(addr)); + + if (!block || u64{addr} + size > u64{block->addr} + block->size) { + return CELL_EINVAL; + } + + return CELL_OK; +} + +error_code sys_vm_store(ppu_thread &ppu, u32 addr, u32 size) { + ppu.state += cpu_flag::wait; + + sys_vm.warning("sys_vm_store(addr=0x%x, size=0x%x)", addr, size); + + if (!size) { + return CELL_EINVAL; + } + + const auto block = idm::get_unlocked(sys_vm_t::find_id(addr)); + + if (!block || u64{addr} + size > u64{block->addr} + block->size) { + return CELL_EINVAL; + } + + return CELL_OK; +} + +error_code sys_vm_sync(ppu_thread &ppu, u32 addr, u32 size) { + ppu.state += cpu_flag::wait; + + sys_vm.warning("sys_vm_sync(addr=0x%x, size=0x%x)", addr, size); + + if (!size) { + return CELL_EINVAL; + } + + const auto block = idm::get_unlocked(sys_vm_t::find_id(addr)); + + if (!block || u64{addr} + size > u64{block->addr} + block->size) { + return CELL_EINVAL; + } + + return CELL_OK; +} + +error_code sys_vm_test(ppu_thread &ppu, u32 addr, u32 size, + vm::ptr result) { + ppu.state += cpu_flag::wait; + + sys_vm.warning("sys_vm_test(addr=0x%x, size=0x%x, result=*0x%x)", addr, size, + result); + + const auto block = idm::get_unlocked(sys_vm_t::find_id(addr)); + + if (!block || u64{addr} + size > u64{block->addr} + block->size) { + return CELL_EINVAL; + } + + ppu.check_state(); + *result = SYS_VM_STATE_ON_MEMORY; + + return CELL_OK; +} + +error_code sys_vm_get_statistics(ppu_thread &ppu, u32 addr, + vm::ptr stat) { + ppu.state += cpu_flag::wait; + + sys_vm.warning("sys_vm_get_statistics(addr=0x%x, stat=*0x%x)", addr, stat); + + const auto block = idm::get_unlocked(sys_vm_t::find_id(addr)); + + if (!block || block->addr != addr) { + return CELL_EINVAL; + } + + ppu.check_state(); + stat->page_fault_ppu = 0; + stat->page_fault_spu = 0; + stat->page_in = 0; + stat->page_out = 0; + stat->pmem_total = block->psize; + stat->pmem_used = 0; + stat->timestamp = get_timebased_time(); + + return CELL_OK; +} + +DECLARE(sys_vm_t::g_ids){}; diff --git a/ps3fw/HLE_PATCHES.cpp b/ps3fw/HLE_PATCHES.cpp index c80d993ba..6635fa7b6 100644 --- a/ps3fw/HLE_PATCHES.cpp +++ b/ps3fw/HLE_PATCHES.cpp @@ -3,7 +3,7 @@ #include "stdafx.h" #include "util/Thread.h" -#include "Emu/Cell/lv2/sys_spu.h" +#include "cellos/sys_spu.h" #include diff --git a/ps3fw/cellAdec.cpp b/ps3fw/cellAdec.cpp index 232993d10..3805e7072 100644 --- a/ps3fw/cellAdec.cpp +++ b/ps3fw/cellAdec.cpp @@ -1,8 +1,8 @@ #include "stdafx.h" #include "Emu/perf_meter.hpp" #include "Emu/Cell/PPUModule.h" -#include "Emu/Cell/lv2/sys_sync.h" -#include "Emu/Cell/lv2/sys_ppu_thread.h" +#include "cellos/sys_sync.h" +#include "cellos/sys_ppu_thread.h" #include "Emu/savestate_utils.hpp" #include "sysPrxForUser.h" diff --git a/ps3fw/cellAtracXdec.cpp b/ps3fw/cellAtracXdec.cpp index 1b2a58f1d..4b856e044 100644 --- a/ps3fw/cellAtracXdec.cpp +++ b/ps3fw/cellAtracXdec.cpp @@ -1,8 +1,8 @@ #include "stdafx.h" #include "Emu/perf_meter.hpp" #include "Emu/Cell/PPUModule.h" -#include "Emu/Cell/lv2/sys_sync.h" -#include "Emu/Cell/lv2/sys_ppu_thread.h" +#include "cellos/sys_sync.h" +#include "cellos/sys_ppu_thread.h" #include "Emu/savestate_utils.hpp" #include "sysPrxForUser.h" #include "util/asm.hpp" diff --git a/ps3fw/cellAudio.cpp b/ps3fw/cellAudio.cpp index 3800385db..eae15eee2 100644 --- a/ps3fw/cellAudio.cpp +++ b/ps3fw/cellAudio.cpp @@ -4,8 +4,8 @@ #include "Emu/Audio/audio_utils.h" #include "Emu/Cell/PPUModule.h" #include "Emu/Cell/timers.hpp" -#include "Emu/Cell/lv2/sys_process.h" -#include "Emu/Cell/lv2/sys_event.h" +#include "cellos/sys_process.h" +#include "cellos/sys_event.h" #include "cellAudio.h" #include "util/video_provider.h" diff --git a/ps3fw/cellAudioOut.cpp b/ps3fw/cellAudioOut.cpp index 23519a54e..7dc80c887 100644 --- a/ps3fw/cellAudioOut.cpp +++ b/ps3fw/cellAudioOut.cpp @@ -1,6 +1,6 @@ #include "stdafx.h" #include "Emu/Cell/PPUModule.h" -#include "Emu/Cell/lv2/sys_rsxaudio.h" +#include "cellos/sys_rsxaudio.h" #include "Emu/IdManager.h" #include "Emu/System.h" #include "Emu/system_config.h" diff --git a/ps3fw/cellCamera.cpp b/ps3fw/cellCamera.cpp index 4077cfc87..c38804388 100644 --- a/ps3fw/cellCamera.cpp +++ b/ps3fw/cellCamera.cpp @@ -4,7 +4,7 @@ #include "Emu/System.h" #include "Emu/system_config.h" #include "Emu/Cell/PPUModule.h" -#include "Emu/Cell/lv2/sys_event.h" +#include "cellos/sys_event.h" #include "Emu/IdManager.h" #include "Emu/Cell/timers.hpp" diff --git a/ps3fw/cellDmux.cpp b/ps3fw/cellDmux.cpp index 4dec25d67..095aa643d 100644 --- a/ps3fw/cellDmux.cpp +++ b/ps3fw/cellDmux.cpp @@ -2,7 +2,7 @@ #include "Emu/System.h" #include "Emu/IdManager.h" #include "Emu/Cell/PPUModule.h" -#include "Emu/Cell/lv2/sys_sync.h" +#include "cellos/sys_sync.h" #include "cellPamf.h" #include "cellDmux.h" diff --git a/ps3fw/cellFs.cpp b/ps3fw/cellFs.cpp index ae10550f7..a08b432fc 100644 --- a/ps3fw/cellFs.cpp +++ b/ps3fw/cellFs.cpp @@ -3,8 +3,8 @@ #include "Emu/IdManager.h" #include "Emu/Cell/PPUModule.h" -#include "Emu/Cell/lv2/sys_fs.h" -#include "Emu/Cell/lv2/sys_sync.h" +#include "cellos/sys_fs.h" +#include "cellos/sys_sync.h" #include "cellFs.h" #include diff --git a/ps3fw/cellGame.cpp b/ps3fw/cellGame.cpp index edbd31561..27b1f0048 100644 --- a/ps3fw/cellGame.cpp +++ b/ps3fw/cellGame.cpp @@ -2,8 +2,8 @@ #include "Emu/Cell/ErrorCodes.h" #include "Emu/Cell/PPUModule.h" -#include "Emu/Cell/lv2/sys_fs.h" -#include "Emu/Cell/lv2/sys_sync.h" +#include "cellos/sys_fs.h" +#include "cellos/sys_sync.h" #include "Emu/Cell/timers.hpp" #include "Emu/IdManager.h" #include "Emu/System.h" diff --git a/ps3fw/cellGcmSys.cpp b/ps3fw/cellGcmSys.cpp index 31b914a76..d91e03223 100644 --- a/ps3fw/cellGcmSys.cpp +++ b/ps3fw/cellGcmSys.cpp @@ -3,8 +3,8 @@ #include "Emu/Cell/PPUModule.h" #include "Emu/Memory/vm.h" -#include "Emu/Cell/lv2/sys_ppu_thread.h" -#include "Emu/Cell/lv2/sys_rsx.h" +#include "cellos/sys_ppu_thread.h" +#include "cellos/sys_rsx.h" #include "Emu/RSX/RSXThread.h" #include "cellGcmSys.h" diff --git a/ps3fw/cellGem.cpp b/ps3fw/cellGem.cpp index 15cfbd9cf..a1defdbea 100644 --- a/ps3fw/cellGem.cpp +++ b/ps3fw/cellGem.cpp @@ -2,8 +2,8 @@ #include "cellGem.h" #include "cellCamera.h" -#include "Emu/Cell/lv2/sys_event.h" -#include "Emu/Cell/lv2/sys_memory.h" +#include "cellos/sys_event.h" +#include "cellos/sys_memory.h" #include "Emu/Cell/PPUModule.h" #include "Emu/Cell/timers.hpp" #include "Emu/Io/MouseHandler.h" diff --git a/ps3fw/cellGifDec.cpp b/ps3fw/cellGifDec.cpp index f2c397096..ac65212c4 100644 --- a/ps3fw/cellGifDec.cpp +++ b/ps3fw/cellGifDec.cpp @@ -6,7 +6,7 @@ // STB_IMAGE_IMPLEMENTATION is already defined in stb_image.cpp #include -#include "Emu/Cell/lv2/sys_fs.h" +#include "cellos/sys_fs.h" #include "cellGifDec.h" LOG_CHANNEL(cellGifDec); diff --git a/ps3fw/cellImeJp.cpp b/ps3fw/cellImeJp.cpp index 60d888964..60be3732b 100644 --- a/ps3fw/cellImeJp.cpp +++ b/ps3fw/cellImeJp.cpp @@ -1,6 +1,6 @@ #include "stdafx.h" #include "Emu/Cell/PPUModule.h" -#include "Emu/Cell/lv2/sys_process.h" +#include "cellos/sys_process.h" #include "Emu/IdManager.h" #include "cellImeJp.h" diff --git a/ps3fw/cellJpgDec.cpp b/ps3fw/cellJpgDec.cpp index 559fcefe1..7c007ebed 100644 --- a/ps3fw/cellJpgDec.cpp +++ b/ps3fw/cellJpgDec.cpp @@ -6,7 +6,7 @@ // STB_IMAGE_IMPLEMENTATION is already defined in stb_image.cpp #include -#include "Emu/Cell/lv2/sys_fs.h" +#include "cellos/sys_fs.h" #include "cellJpgDec.h" LOG_CHANNEL(cellJpgDec); diff --git a/ps3fw/cellMic.cpp b/ps3fw/cellMic.cpp index c1bdf6b05..b5d3dfcc6 100644 --- a/ps3fw/cellMic.cpp +++ b/ps3fw/cellMic.cpp @@ -4,7 +4,7 @@ #include "util/StrUtil.h" #include "cellMic.h" -#include +#include #include #include diff --git a/ps3fw/cellMsgDialog.cpp b/ps3fw/cellMsgDialog.cpp index 91ee21025..4ef4b1acf 100644 --- a/ps3fw/cellMsgDialog.cpp +++ b/ps3fw/cellMsgDialog.cpp @@ -2,7 +2,7 @@ #include "Emu/System.h" #include "Emu/Cell/PPUModule.h" #include "Emu/Cell/PPUThread.h" -#include "Emu/Cell/lv2/sys_sync.h" +#include "cellos/sys_sync.h" #include "Emu/Cell/timers.hpp" #include "Emu/Io/interception.h" diff --git a/ps3fw/cellMusic.cpp b/ps3fw/cellMusic.cpp index c48fc8893..7f3181c23 100644 --- a/ps3fw/cellMusic.cpp +++ b/ps3fw/cellMusic.cpp @@ -1,8 +1,8 @@ #include "stdafx.h" #include "Emu/Cell/PPUModule.h" -#include "Emu/Cell/lv2/sys_lwmutex.h" -#include "Emu/Cell/lv2/sys_lwcond.h" -#include "Emu/Cell/lv2/sys_spu.h" +#include "cellos/sys_lwmutex.h" +#include "cellos/sys_lwcond.h" +#include "cellos/sys_spu.h" #include "Emu/Io/music_handler_base.h" #include "Emu/System.h" #include "Emu/VFS.h" diff --git a/ps3fw/cellMusicDecode.cpp b/ps3fw/cellMusicDecode.cpp index aa1bd0903..55fddb8fb 100644 --- a/ps3fw/cellMusicDecode.cpp +++ b/ps3fw/cellMusicDecode.cpp @@ -1,8 +1,8 @@ #include "stdafx.h" #include "Emu/Cell/PPUModule.h" -#include "Emu/Cell/lv2/sys_lwmutex.h" -#include "Emu/Cell/lv2/sys_lwcond.h" -#include "Emu/Cell/lv2/sys_spu.h" +#include "cellos/sys_lwmutex.h" +#include "cellos/sys_lwcond.h" +#include "cellos/sys_spu.h" #include "Emu/RSX/Overlays/overlay_media_list_dialog.h" #include "Emu/VFS.h" #include "cellMusicDecode.h" diff --git a/ps3fw/cellOvis.cpp b/ps3fw/cellOvis.cpp index 8047589c0..37ba8bacd 100644 --- a/ps3fw/cellOvis.cpp +++ b/ps3fw/cellOvis.cpp @@ -1,6 +1,6 @@ #include "stdafx.h" #include "Emu/Cell/PPUModule.h" -#include "Emu/Cell/lv2/sys_spu.h" +#include "cellos/sys_spu.h" LOG_CHANNEL(cellOvis); diff --git a/ps3fw/cellPad.cpp b/ps3fw/cellPad.cpp index 6c568bf67..87082c791 100644 --- a/ps3fw/cellPad.cpp +++ b/ps3fw/cellPad.cpp @@ -2,7 +2,7 @@ #include "Emu/IdManager.h" #include "Emu/system_config.h" #include "Emu/Cell/PPUModule.h" -#include "Emu/Cell/lv2/sys_process.h" +#include "cellos/sys_process.h" #include "Emu/Io/pad_types.h" #include "Emu/RSX/Overlays/overlay_debug_overlay.h" #include "Input/pad_thread.h" diff --git a/ps3fw/cellPhotoImport.cpp b/ps3fw/cellPhotoImport.cpp index a8a634632..e44494c06 100644 --- a/ps3fw/cellPhotoImport.cpp +++ b/ps3fw/cellPhotoImport.cpp @@ -1,5 +1,5 @@ #include "Emu/Cell/PPUModule.h" -#include "Emu/Cell/lv2/sys_fs.h" +#include "cellos/sys_fs.h" #include "Emu/IdManager.h" #include "Emu/RSX/Overlays/overlay_media_list_dialog.h" #include "Emu/System.h" diff --git a/ps3fw/cellPngDec.cpp b/ps3fw/cellPngDec.cpp index c60803eef..1ef3838e9 100644 --- a/ps3fw/cellPngDec.cpp +++ b/ps3fw/cellPngDec.cpp @@ -2,7 +2,7 @@ #include "Emu/VFS.h" #include "Emu/IdManager.h" #include "Emu/Cell/PPUModule.h" -#include "Emu/Cell/lv2/sys_fs.h" +#include "cellos/sys_fs.h" #include "png.h" #include "cellPng.h" #include "cellPngDec.h" diff --git a/ps3fw/cellRec.cpp b/ps3fw/cellRec.cpp index 5b9352d02..0513986a4 100644 --- a/ps3fw/cellRec.cpp +++ b/ps3fw/cellRec.cpp @@ -1,5 +1,5 @@ #include "stdafx.h" -#include "Emu/Cell/lv2/sys_memory.h" +#include "cellos/sys_memory.h" #include "Emu/Cell/timers.hpp" #include "Emu/Cell/PPUModule.h" #include "Emu/IdManager.h" diff --git a/ps3fw/cellRtc.cpp b/ps3fw/cellRtc.cpp index c34a0e6d7..08c7a7c05 100644 --- a/ps3fw/cellRtc.cpp +++ b/ps3fw/cellRtc.cpp @@ -3,9 +3,9 @@ #include "cellRtc.h" -#include "Emu/Cell/lv2/sys_time.h" -#include "Emu/Cell/lv2/sys_memory.h" -#include "Emu/Cell/lv2/sys_ss.h" +#include "cellos/sys_time.h" +#include "cellos/sys_memory.h" +#include "cellos/sys_ss.h" LOG_CHANNEL(cellRtc); diff --git a/ps3fw/cellSaveData.cpp b/ps3fw/cellSaveData.cpp index a2c5b5bda..73503fce7 100644 --- a/ps3fw/cellSaveData.cpp +++ b/ps3fw/cellSaveData.cpp @@ -1,9 +1,9 @@ #include "cellSysutil.h" #include "cellUserInfo.h" #include "Emu/Cell/PPUModule.h" -#include "Emu/Cell/lv2/sys_fs.h" -#include "Emu/Cell/lv2/sys_process.h" -#include "Emu/Cell/lv2/sys_sync.h" +#include "cellos/sys_fs.h" +#include "cellos/sys_process.h" +#include "cellos/sys_sync.h" #include "Emu/Cell/timers.hpp" #include "Emu/IdManager.h" #include "Emu/RSX/Overlays/overlay_message.h" diff --git a/ps3fw/cellSpurs.cpp b/ps3fw/cellSpurs.cpp index f0bb92b77..ba1ec29bd 100644 --- a/ps3fw/cellSpurs.cpp +++ b/ps3fw/cellSpurs.cpp @@ -4,14 +4,14 @@ #include "Emu/Memory/vm_reservation.h" #include "Emu/Cell/PPUModule.h" #include "Emu/Cell/SPUThread.h" -#include "Emu/Cell/lv2/sys_lwmutex.h" -#include "Emu/Cell/lv2/sys_lwcond.h" -#include "Emu/Cell/lv2/sys_spu.h" -#include "Emu/Cell/lv2/sys_ppu_thread.h" -#include "Emu/Cell/lv2/sys_memory.h" -#include "Emu/Cell/lv2/sys_process.h" -#include "Emu/Cell/lv2/sys_semaphore.h" -#include "Emu/Cell/lv2/sys_event.h" +#include "cellos/sys_lwmutex.h" +#include "cellos/sys_lwcond.h" +#include "cellos/sys_spu.h" +#include "cellos/sys_ppu_thread.h" +#include "cellos/sys_memory.h" +#include "cellos/sys_process.h" +#include "cellos/sys_semaphore.h" +#include "cellos/sys_event.h" #include "sysPrxForUser.h" #include "cellSpurs.h" diff --git a/ps3fw/cellSync.cpp b/ps3fw/cellSync.cpp index 71561c67c..129993ebe 100644 --- a/ps3fw/cellSync.cpp +++ b/ps3fw/cellSync.cpp @@ -1,7 +1,7 @@ #include "stdafx.h" #include "Emu/Cell/PPUModule.h" -#include "Emu/Cell/lv2/sys_event.h" -#include "Emu/Cell/lv2/sys_process.h" +#include "cellos/sys_event.h" +#include "cellos/sys_process.h" #include "cellSync.h" LOG_CHANNEL(cellSync); diff --git a/ps3fw/cellSysCache.cpp b/ps3fw/cellSysCache.cpp index bd3f58a6e..498fc07a3 100644 --- a/ps3fw/cellSysCache.cpp +++ b/ps3fw/cellSysCache.cpp @@ -5,7 +5,7 @@ #include "Emu/system_utils.hpp" #include "stdafx.h" -#include "Emu/Cell/lv2/sys_fs.h" +#include "cellos/sys_fs.h" #include "cellSysutil.h" #include "util/StrUtil.h" #include "util/init_mutex.hpp" diff --git a/ps3fw/cellSysutil.cpp b/ps3fw/cellSysutil.cpp index 1dd124d96..dbeaef32c 100644 --- a/ps3fw/cellSysutil.cpp +++ b/ps3fw/cellSysutil.cpp @@ -6,8 +6,8 @@ #include "Emu/VFS.h" #include "Emu/system_config.h" -#include "Emu/Cell/lv2/sys_game.h" -#include "Emu/Cell/lv2/sys_process.h" +#include "cellos/sys_game.h" +#include "cellos/sys_process.h" #include "cellSysutil.h" #include "util/StrUtil.h" diff --git a/ps3fw/cellVdec.cpp b/ps3fw/cellVdec.cpp index c4f679688..202ab126e 100644 --- a/ps3fw/cellVdec.cpp +++ b/ps3fw/cellVdec.cpp @@ -1,7 +1,7 @@ #include "Emu/Cell/PPUModule.h" -#include "Emu/Cell/lv2/sys_ppu_thread.h" -#include "Emu/Cell/lv2/sys_process.h" -#include "Emu/Cell/lv2/sys_sync.h" +#include "cellos/sys_ppu_thread.h" +#include "cellos/sys_process.h" +#include "cellos/sys_sync.h" #include "Emu/IdManager.h" #include "Emu/perf_meter.hpp" #include "Emu/savestate_utils.hpp" diff --git a/ps3fw/cellVoice.cpp b/ps3fw/cellVoice.cpp index f7642f3e7..01eb7be38 100644 --- a/ps3fw/cellVoice.cpp +++ b/ps3fw/cellVoice.cpp @@ -1,8 +1,8 @@ #include "stdafx.h" #include "Emu/Cell/PPUModule.h" -#include "Emu/Cell/lv2/sys_event.h" -#include "Emu/Cell/lv2/sys_process.h" +#include "cellos/sys_event.h" +#include "cellos/sys_process.h" #include "cellVoice.h" LOG_CHANNEL(cellVoice); diff --git a/ps3fw/include/rpcsx/fw/ps3/cellAdec.h b/ps3fw/include/rpcsx/fw/ps3/cellAdec.h index ca6f36730..f8a8309b8 100644 --- a/ps3fw/include/rpcsx/fw/ps3/cellAdec.h +++ b/ps3fw/include/rpcsx/fw/ps3/cellAdec.h @@ -1,8 +1,8 @@ #pragma once #include "cellPamf.h" // CellCodecTimeStamp -#include "Emu/Cell/lv2/sys_mutex.h" -#include "Emu/Cell/lv2/sys_cond.h" +#include "cellos/sys_mutex.h" +#include "cellos/sys_cond.h" // Error Codes enum CellAdecError : u32 diff --git a/ps3fw/include/rpcsx/fw/ps3/cellSpurs.h b/ps3fw/include/rpcsx/fw/ps3/cellSpurs.h index f843a2f9b..cdbca102f 100644 --- a/ps3fw/include/rpcsx/fw/ps3/cellSpurs.h +++ b/ps3fw/include/rpcsx/fw/ps3/cellSpurs.h @@ -4,9 +4,9 @@ #include "util/v128.hpp" -#include "Emu/Cell/lv2/sys_lwmutex.h" -#include "Emu/Cell/lv2/sys_lwcond.h" -#include "Emu/Cell/lv2/sys_spu.h" +#include "cellos/sys_lwmutex.h" +#include "cellos/sys_lwcond.h" +#include "cellos/sys_spu.h" struct CellSpurs; struct CellSpursTaskset; diff --git a/ps3fw/include/rpcsx/fw/ps3/sys_lv2dbg.h b/ps3fw/include/rpcsx/fw/ps3/sys_lv2dbg.h index 1e61def4f..3b04db7b9 100644 --- a/ps3fw/include/rpcsx/fw/ps3/sys_lv2dbg.h +++ b/ps3fw/include/rpcsx/fw/ps3/sys_lv2dbg.h @@ -1,13 +1,13 @@ #pragma once -#include "Emu/Cell/lv2/sys_mutex.h" -#include "Emu/Cell/lv2/sys_cond.h" -#include "Emu/Cell/lv2/sys_rwlock.h" -#include "Emu/Cell/lv2/sys_event.h" -#include "Emu/Cell/lv2/sys_semaphore.h" -#include "Emu/Cell/lv2/sys_lwmutex.h" -#include "Emu/Cell/lv2/sys_lwcond.h" -#include "Emu/Cell/lv2/sys_event_flag.h" +#include "cellos/sys_mutex.h" +#include "cellos/sys_cond.h" +#include "cellos/sys_rwlock.h" +#include "cellos/sys_event.h" +#include "cellos/sys_semaphore.h" +#include "cellos/sys_lwmutex.h" +#include "cellos/sys_lwcond.h" +#include "cellos/sys_event_flag.h" #include "Emu/Memory/vm_ptr.h" diff --git a/ps3fw/include/rpcsx/fw/ps3/sys_net_.h b/ps3fw/include/rpcsx/fw/ps3/sys_net_.h index 9ee344b97..e3198e718 100644 --- a/ps3fw/include/rpcsx/fw/ps3/sys_net_.h +++ b/ps3fw/include/rpcsx/fw/ps3/sys_net_.h @@ -1,6 +1,6 @@ #pragma once -#include "Emu/Cell/lv2/sys_net.h" +#include "cellos/sys_net.h" struct sys_net_sockinfo_t { diff --git a/ps3fw/libmixer.cpp b/ps3fw/libmixer.cpp index f60cb193b..72ab83fbe 100644 --- a/ps3fw/libmixer.cpp +++ b/ps3fw/libmixer.cpp @@ -1,6 +1,6 @@ #include "stdafx.h" #include "Emu/Cell/PPUModule.h" -#include "Emu/Cell/lv2/sys_sync.h" +#include "cellos/sys_sync.h" #include "cellAudio.h" #include "libmixer.h" diff --git a/ps3fw/sceNp.cpp b/ps3fw/sceNp.cpp index 39b6a5de6..8ee9043a8 100644 --- a/ps3fw/sceNp.cpp +++ b/ps3fw/sceNp.cpp @@ -14,9 +14,9 @@ #include "sceNp.h" #include "sysPrxForUser.h" -#include "Emu/Cell/lv2/sys_fs.h" -#include "Emu/Cell/lv2/sys_sync.h" -#include "Emu/Cell/lv2/sys_time.h" +#include "cellos/sys_fs.h" +#include "cellos/sys_sync.h" +#include "cellos/sys_time.h" #include "Emu/Cell/timers.hpp" #include "Emu/NP/np_contexts.h" #include "Emu/NP/np_handler.h" diff --git a/ps3fw/sceNpCommerce2.cpp b/ps3fw/sceNpCommerce2.cpp index 799f4d242..080c6bf0e 100644 --- a/ps3fw/sceNpCommerce2.cpp +++ b/ps3fw/sceNpCommerce2.cpp @@ -6,7 +6,7 @@ #include "sceNp.h" #include "cellSysutil.h" -#include "Emu/Cell/lv2/sys_process.h" +#include "cellos/sys_process.h" #include "Emu/NP/np_handler.h" #include "Emu/NP/np_contexts.h" diff --git a/ps3fw/sceNpTrophy.cpp b/ps3fw/sceNpTrophy.cpp index 84217e041..7bf6cd263 100644 --- a/ps3fw/sceNpTrophy.cpp +++ b/ps3fw/sceNpTrophy.cpp @@ -17,8 +17,8 @@ #include "util/StrUtil.h" -#include "Emu/Cell/lv2/sys_event.h" -#include "Emu/Cell/lv2/sys_fs.h" +#include "cellos/sys_event.h" +#include "cellos/sys_fs.h" #include "util/asm.hpp" #include diff --git a/ps3fw/sysPrxForUser.cpp b/ps3fw/sysPrxForUser.cpp index 313801bc4..789d59fcf 100644 --- a/ps3fw/sysPrxForUser.cpp +++ b/ps3fw/sysPrxForUser.cpp @@ -2,11 +2,11 @@ #include "Emu/Cell/PPUModule.h" #include "Emu/Cell/timers.hpp" -#include "Emu/Cell/lv2/sys_mutex.h" -#include "Emu/Cell/lv2/sys_interrupt.h" -#include "Emu/Cell/lv2/sys_process.h" -#include "Emu/Cell/lv2/sys_ss.h" -#include "Emu/Cell/lv2/sys_tty.h" +#include "cellos/sys_mutex.h" +#include "cellos/sys_interrupt.h" +#include "cellos/sys_process.h" +#include "cellos/sys_ss.h" +#include "cellos/sys_tty.h" #include "sysPrxForUser.h" LOG_CHANNEL(sysPrxForUser); diff --git a/ps3fw/sys_game_.cpp b/ps3fw/sys_game_.cpp index af13307bd..a5f58ea88 100644 --- a/ps3fw/sys_game_.cpp +++ b/ps3fw/sys_game_.cpp @@ -1,8 +1,8 @@ #include "stdafx.h" #include "Emu/Cell/PPUModule.h" -#include "Emu/Cell/lv2/sys_mutex.h" -#include "Emu/Cell/lv2/sys_process.h" +#include "cellos/sys_mutex.h" +#include "cellos/sys_process.h" #include "sysPrxForUser.h" LOG_CHANNEL(sysPrxForUser); diff --git a/ps3fw/sys_io_.cpp b/ps3fw/sys_io_.cpp index 037351cfc..d5ee6a8be 100644 --- a/ps3fw/sys_io_.cpp +++ b/ps3fw/sys_io_.cpp @@ -3,8 +3,8 @@ #include "Emu/IdManager.h" #include "Emu/Cell/PPUModule.h" -#include "Emu/Cell/lv2/sys_event.h" -#include "Emu/Cell/lv2/sys_ppu_thread.h" +#include "cellos/sys_event.h" +#include "cellos/sys_ppu_thread.h" #include "sysPrxForUser.h" LOG_CHANNEL(sys_io); diff --git a/ps3fw/sys_libc_.cpp b/ps3fw/sys_libc_.cpp index e9fee8d1d..c573d226e 100644 --- a/ps3fw/sys_libc_.cpp +++ b/ps3fw/sys_libc_.cpp @@ -1,5 +1,5 @@ #include "Emu/Cell/PPUModule.h" -#include "Emu/Cell/lv2/sys_tty.h" +#include "cellos/sys_tty.h" #include "stdafx.h" #include "util/cfmt.h" diff --git a/ps3fw/sys_lwcond_.cpp b/ps3fw/sys_lwcond_.cpp index 2b329f15c..a13babfa0 100644 --- a/ps3fw/sys_lwcond_.cpp +++ b/ps3fw/sys_lwcond_.cpp @@ -1,9 +1,9 @@ #include "stdafx.h" #include "Emu/system_config.h" #include "Emu/Cell/PPUModule.h" -#include "Emu/Cell/lv2/sys_lwmutex.h" -#include "Emu/Cell/lv2/sys_lwcond.h" -#include "Emu/Cell/lv2/sys_cond.h" +#include "cellos/sys_lwmutex.h" +#include "cellos/sys_lwcond.h" +#include "cellos/sys_cond.h" #include "sysPrxForUser.h" LOG_CHANNEL(sysPrxForUser); diff --git a/ps3fw/sys_lwmutex_.cpp b/ps3fw/sys_lwmutex_.cpp index 07458dbd3..ff64d2e90 100644 --- a/ps3fw/sys_lwmutex_.cpp +++ b/ps3fw/sys_lwmutex_.cpp @@ -3,8 +3,8 @@ #include "Emu/system_config.h" #include "Emu/Cell/PPUModule.h" #include "Emu/Cell/timers.hpp" -#include "Emu/Cell/lv2/sys_lwmutex.h" -#include "Emu/Cell/lv2/sys_mutex.h" +#include "cellos/sys_lwmutex.h" +#include "cellos/sys_mutex.h" #include "sysPrxForUser.h" #include "util/asm.hpp" diff --git a/ps3fw/sys_mempool.cpp b/ps3fw/sys_mempool.cpp index 3868eb4aa..7f8be0e9f 100644 --- a/ps3fw/sys_mempool.cpp +++ b/ps3fw/sys_mempool.cpp @@ -4,8 +4,8 @@ #include "Emu/Cell/PPUModule.h" -#include "Emu/Cell/lv2/sys_cond.h" -#include "Emu/Cell/lv2/sys_mutex.h" +#include "cellos/sys_cond.h" +#include "cellos/sys_mutex.h" LOG_CHANNEL(sysPrxForUser); diff --git a/ps3fw/sys_mmapper_.cpp b/ps3fw/sys_mmapper_.cpp index cd093ba1c..fd2d790aa 100644 --- a/ps3fw/sys_mmapper_.cpp +++ b/ps3fw/sys_mmapper_.cpp @@ -1,6 +1,6 @@ #include "stdafx.h" #include "Emu/Cell/PPUModule.h" -#include "Emu/Cell/lv2/sys_mmapper.h" +#include "cellos/sys_mmapper.h" LOG_CHANNEL(sysPrxForUser); diff --git a/ps3fw/sys_ppu_thread_.cpp b/ps3fw/sys_ppu_thread_.cpp index 16362f9f2..e16c9c8b2 100644 --- a/ps3fw/sys_ppu_thread_.cpp +++ b/ps3fw/sys_ppu_thread_.cpp @@ -1,9 +1,9 @@ #include "stdafx.h" #include "Emu/Cell/PPUModule.h" -#include "Emu/Cell/lv2/sys_ppu_thread.h" -#include "Emu/Cell/lv2/sys_interrupt.h" -#include "Emu/Cell/lv2/sys_lwmutex.h" -#include "Emu/Cell/lv2/sys_mutex.h" +#include "cellos/sys_ppu_thread.h" +#include "cellos/sys_interrupt.h" +#include "cellos/sys_lwmutex.h" +#include "cellos/sys_mutex.h" #include "sysPrxForUser.h" LOG_CHANNEL(sysPrxForUser); diff --git a/ps3fw/sys_prx_.cpp b/ps3fw/sys_prx_.cpp index a43d6b536..7a192b19b 100644 --- a/ps3fw/sys_prx_.cpp +++ b/ps3fw/sys_prx_.cpp @@ -1,8 +1,8 @@ #include "stdafx.h" #include "Emu/Cell/PPUModule.h" -#include "Emu/Cell/lv2/sys_lwmutex.h" -#include "Emu/Cell/lv2/sys_prx.h" +#include "cellos/sys_lwmutex.h" +#include "cellos/sys_prx.h" #include "sysPrxForUser.h" LOG_CHANNEL(sysPrxForUser); diff --git a/ps3fw/sys_spu_.cpp b/ps3fw/sys_spu_.cpp index bfe90c141..a75b6b769 100644 --- a/ps3fw/sys_spu_.cpp +++ b/ps3fw/sys_spu_.cpp @@ -2,7 +2,7 @@ #include "Emu/VFS.h" #include "Emu/Cell/PPUModule.h" -#include "Emu/Cell/lv2/sys_spu.h" +#include "cellos/sys_spu.h" #include "Loader/ELF.h" #include "sysPrxForUser.h" diff --git a/rpcs3/CMakeLists.txt b/rpcs3/CMakeLists.txt index 43990629e..f170d4526 100644 --- a/rpcs3/CMakeLists.txt +++ b/rpcs3/CMakeLists.txt @@ -33,7 +33,7 @@ include(${CMAKE_CURRENT_SOURCE_DIR}/git-version.cmake) include(ConfigureCompiler) include(CheckFunctionExists) -set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD 23) set(ADDITIONAL_LIBS "") if(CMAKE_SYSTEM_NAME STREQUAL "Linux") @@ -64,13 +64,7 @@ endif() gen_git_version(${CMAKE_CURRENT_SOURCE_DIR}) -add_library(rpcs3 STATIC - rpcs3_version.cpp - module_verifier.cpp - stb_image.cpp - - dev/iso.cpp - +add_library(rpcs3_core STATIC util/atomic.cpp util/console.cpp util/emu_utils.cpp @@ -100,6 +94,39 @@ add_library(rpcs3 STATIC util/StrFmt.cpp util/Thread.cpp util/version.cpp +) + +target_include_directories(rpcs3_core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_link_libraries(rpcs3_core PUBLIC rx) + +target_link_libraries(rpcs3_core PUBLIC + rpcsx::cpu::cell::ppu # FIXME: remove + rpcsx::fw::ps3::api # FIXME: remove + cellos-kernel # FIXME: remove + 3rdparty::stblib + 3rdparty::ffmpeg + 3rdparty::asmjit + 3rdparty::zlib + 3rdparty::pugixml + 3rdparty::yaml-cpp +) + +if(APPLE) + target_sources(rpcs3_core PRIVATE + util/darwin/sysinfo_darwin.mm + ) +endif() + +if(USE_PRECOMPILED_HEADERS) + target_precompile_headers(rpcs3_core PUBLIC stdafx.h) +endif() + +add_library(rpcs3 STATIC + rpcs3_version.cpp + module_verifier.cpp + stb_image.cpp + + dev/iso.cpp Crypto/aes.cpp Crypto/aesni.cpp @@ -146,14 +173,6 @@ add_library(rpcs3 STATIC Input/virtual_pad_handler.cpp ) -target_include_directories(rpcs3 PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) - -if(APPLE) - target_sources(rpcs3 PRIVATE - util/darwin/sysinfo_darwin.mm - ) -endif() - set_source_files_properties("util/JITLLVM.cpp" "util/JITASM.cpp" PROPERTIES COMPILE_FLAGS "$,/GR-,-fno-rtti>" SKIP_PRECOMPILE_HEADERS ON @@ -166,8 +185,7 @@ set_source_files_properties("util/yaml.cpp" PROPERTIES target_link_libraries(rpcs3 PUBLIC rpcs3_emu - 3rdparty::zlib - 3rdparty::pugixml + rpcs3_core 3rdparty::discordRPC 3rdparty::hidapi 3rdparty::libusb @@ -178,9 +196,6 @@ target_link_libraries(rpcs3 PUBLIC 3rdparty::opencv 3rdparty::fusion 3rdparty::rtmidi - 3rdparty::stblib - 3rdparty::ffmpeg - 3rdparty::yaml-cpp 3rdparty::zstd ${ADDITIONAL_LIBS} ) diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt index ee8f4cd03..a58ab0694 100644 --- a/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/Emu/CMakeLists.txt @@ -124,56 +124,6 @@ target_sources(rpcs3_emu PRIVATE Cell/SPUCommonRecompiler.cpp Cell/SPULLVMRecompiler.cpp Cell/SPUThread.cpp - Cell/lv2/lv2.cpp - Cell/lv2/sys_bdemu.cpp - Cell/lv2/sys_btsetting.cpp - Cell/lv2/sys_cond.cpp - Cell/lv2/sys_console.cpp - Cell/lv2/sys_crypto_engine.cpp - Cell/lv2/sys_config.cpp - Cell/lv2/sys_dbg.cpp - Cell/lv2/sys_event.cpp - Cell/lv2/sys_event_flag.cpp - Cell/lv2/sys_fs.cpp - Cell/lv2/sys_game.cpp - Cell/lv2/sys_gamepad.cpp - Cell/lv2/sys_gpio.cpp - Cell/lv2/sys_hid.cpp - Cell/lv2/sys_interrupt.cpp - Cell/lv2/sys_io.cpp - Cell/lv2/sys_lwcond.cpp - Cell/lv2/sys_lwmutex.cpp - Cell/lv2/sys_memory.cpp - Cell/lv2/sys_mmapper.cpp - Cell/lv2/sys_mutex.cpp - Cell/lv2/sys_net.cpp - Cell/lv2/sys_net/lv2_socket.cpp - Cell/lv2/sys_net/lv2_socket_native.cpp - Cell/lv2/sys_net/lv2_socket_raw.cpp - Cell/lv2/sys_net/lv2_socket_p2p.cpp - Cell/lv2/sys_net/lv2_socket_p2ps.cpp - Cell/lv2/sys_net/network_context.cpp - Cell/lv2/sys_net/nt_p2p_port.cpp - Cell/lv2/sys_net/sys_net_helpers.cpp - Cell/lv2/sys_overlay.cpp - Cell/lv2/sys_ppu_thread.cpp - Cell/lv2/sys_process.cpp - Cell/lv2/sys_prx.cpp - Cell/lv2/sys_rsx.cpp - Cell/lv2/sys_rsxaudio.cpp - Cell/lv2/sys_rwlock.cpp - Cell/lv2/sys_semaphore.cpp - Cell/lv2/sys_spu.cpp - Cell/lv2/sys_sm.cpp - Cell/lv2/sys_ss.cpp - Cell/lv2/sys_storage.cpp - Cell/lv2/sys_time.cpp - Cell/lv2/sys_timer.cpp - Cell/lv2/sys_trace.cpp - Cell/lv2/sys_tty.cpp - Cell/lv2/sys_uart.cpp - Cell/lv2/sys_usbd.cpp - Cell/lv2/sys_vm.cpp ) if(NOT MSVC) @@ -205,7 +155,8 @@ if(CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64|arm64|aarch64") endif() target_link_libraries(rpcs3_emu - PUBLIC 3rdparty::llvm 3rdparty::asmjit) + PUBLIC 3rdparty::llvm 3rdparty::asmjit cellos-kernel +) # Io @@ -446,6 +397,7 @@ target_link_libraries(rpcs3_emu rpcsx::fw::ps3::api rpcsx::cpu::cell::ppu rpcsx::cpu::cell::ppu::semantic + cellos-kernel PRIVATE 3rdparty::glslang diff --git a/rpcs3/Emu/CPU/CPUThread.cpp b/rpcs3/Emu/CPU/CPUThread.cpp index 079d2824c..4eb39457d 100644 --- a/rpcs3/Emu/CPU/CPUThread.cpp +++ b/rpcs3/Emu/CPU/CPUThread.cpp @@ -8,7 +8,7 @@ #include "Emu/Memory/vm_reservation.h" #include "Emu/IdManager.h" #include "Emu/GDB.h" -#include "Emu/Cell/lv2/sys_spu.h" +#include "cellos/sys_spu.h" #include "Emu/Cell/PPUThread.h" #include "Emu/Cell/SPUThread.h" #include "Emu/RSX/RSXThread.h" diff --git a/rpcs3/Emu/Cell/PPUAnalyser.cpp b/rpcs3/Emu/Cell/PPUAnalyser.cpp index c53622397..8d5d3c679 100644 --- a/rpcs3/Emu/Cell/PPUAnalyser.cpp +++ b/rpcs3/Emu/Cell/PPUAnalyser.cpp @@ -1,7 +1,7 @@ #include "stdafx.h" #include "PPUAnalyser.h" -#include "lv2/sys_sync.h" +#include "cellos/sys_sync.h" #include "PPUOpcodes.h" #include "PPUThread.h" diff --git a/rpcs3/Emu/Cell/PPUModule.cpp b/rpcs3/Emu/Cell/PPUModule.cpp index 4fca8778f..33e46ed2a 100644 --- a/rpcs3/Emu/Cell/PPUModule.cpp +++ b/rpcs3/Emu/Cell/PPUModule.cpp @@ -17,10 +17,10 @@ #include "Emu/Cell/PPUAnalyser.h" #include "Emu/Cell/timers.hpp" -#include "Emu/Cell/lv2/sys_process.h" -#include "Emu/Cell/lv2/sys_prx.h" -#include "Emu/Cell/lv2/sys_memory.h" -#include "Emu/Cell/lv2/sys_overlay.h" +#include "cellos/sys_process.h" +#include "cellos/sys_prx.h" +#include "cellos/sys_memory.h" +#include "cellos/sys_overlay.h" #include "rpcsx/fw/ps3/StaticHLE.h" diff --git a/rpcs3/Emu/Cell/PPUThread.cpp b/rpcs3/Emu/Cell/PPUThread.cpp index e13a927f9..6854e299c 100644 --- a/rpcs3/Emu/Cell/PPUThread.cpp +++ b/rpcs3/Emu/Cell/PPUThread.cpp @@ -23,11 +23,11 @@ #include "PPUDisAsm.h" #include "SPURecompiler.h" #include "timers.hpp" -#include "lv2/sys_sync.h" -#include "lv2/sys_prx.h" -#include "lv2/sys_overlay.h" -#include "lv2/sys_process.h" -#include "lv2/sys_spu.h" +#include "cellos/sys_sync.h" +#include "cellos/sys_prx.h" +#include "cellos/sys_overlay.h" +#include "cellos/sys_process.h" +#include "cellos/sys_spu.h" #include #include diff --git a/rpcs3/Emu/Cell/PPUTranslator.cpp b/rpcs3/Emu/Cell/PPUTranslator.cpp index 819498292..419c4a8dd 100644 --- a/rpcs3/Emu/Cell/PPUTranslator.cpp +++ b/rpcs3/Emu/Cell/PPUTranslator.cpp @@ -2,7 +2,7 @@ #include "Emu/system_config.h" #include "Emu/Cell/Common.h" -#include "Emu/Cell/lv2/sys_sync.h" +#include "cellos/sys_sync.h" #include "PPUTranslator.h" #include "PPUThread.h" #include "SPUThread.h" diff --git a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp index dfd1243f3..57aa5492c 100644 --- a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp @@ -5,7 +5,7 @@ #include "Emu/system_config.h" #include "Emu/IdManager.h" #include "Emu/Cell/timers.hpp" -#include "Emu/Cell/lv2/sys_time.h" +#include "cellos/sys_time.h" #include "Emu/Memory/vm_reservation.h" #include "Emu/RSX/Core/RSXReservationLock.hpp" #include "Crypto/sha1.h" diff --git a/rpcs3/Emu/Cell/SPUThread.cpp b/rpcs3/Emu/Cell/SPUThread.cpp index e6d8c0a70..a9fd8ff28 100644 --- a/rpcs3/Emu/Cell/SPUThread.cpp +++ b/rpcs3/Emu/Cell/SPUThread.cpp @@ -12,10 +12,10 @@ #include "Emu/perf_meter.hpp" #include "Emu/Cell/PPUThread.h" #include "Emu/Cell/ErrorCodes.h" -#include "Emu/Cell/lv2/sys_spu.h" -#include "Emu/Cell/lv2/sys_event_flag.h" -#include "Emu/Cell/lv2/sys_event.h" -#include "Emu/Cell/lv2/sys_interrupt.h" +#include "cellos/sys_spu.h" +#include "cellos/sys_event_flag.h" +#include "cellos/sys_event.h" +#include "cellos/sys_interrupt.h" #include "Emu/Cell/SPUDisAsm.h" #include "Emu/Cell/SPUAnalyser.h" diff --git a/rpcs3/Emu/Cell/lv2/lv2.cpp b/rpcs3/Emu/Cell/lv2/lv2.cpp deleted file mode 100644 index c25c39906..000000000 --- a/rpcs3/Emu/Cell/lv2/lv2.cpp +++ /dev/null @@ -1,2354 +0,0 @@ -#include "stdafx.h" -#include "Emu/System.h" -#include "Emu/system_config.h" -#include "Emu/Memory/vm_ptr.h" -#include "Emu/Memory/vm_reservation.h" -#include "Emu/Memory/vm_locking.h" - -#include "Emu/Cell/PPUFunction.h" -#include "Emu/Cell/PPUThread.h" -#include "Emu/Cell/SPUThread.h" -#include "Emu/Cell/ErrorCodes.h" -#include "sys_sync.h" -#include "sys_lwmutex.h" -#include "sys_lwcond.h" -#include "sys_mutex.h" -#include "sys_cond.h" -#include "sys_event.h" -#include "sys_event_flag.h" -#include "sys_game.h" -#include "sys_interrupt.h" -#include "sys_memory.h" -#include "sys_mmapper.h" -#include "sys_net.h" -#include "sys_overlay.h" -#include "sys_ppu_thread.h" -#include "sys_process.h" -#include "sys_prx.h" -#include "sys_rsx.h" -#include "sys_rwlock.h" -#include "sys_semaphore.h" -#include "sys_spu.h" -#include "sys_time.h" -#include "sys_timer.h" -#include "sys_trace.h" -#include "sys_tty.h" -#include "sys_usbd.h" -#include "sys_vm.h" -#include "sys_fs.h" -#include "sys_dbg.h" -#include "sys_gamepad.h" -#include "sys_ss.h" -#include "sys_gpio.h" -#include "sys_config.h" -#include "sys_bdemu.h" -#include "sys_btsetting.h" -#include "sys_console.h" -#include "sys_hid.h" -#include "sys_io.h" -#include "sys_rsxaudio.h" -#include "sys_sm.h" -#include "sys_storage.h" -#include "sys_uart.h" -#include "sys_crypto_engine.h" - -#include -#include -#include -#include -#include "util/tsc.hpp" -#include "util/sysinfo.hpp" -#include "util/init_mutex.hpp" - -#if defined(ARCH_X64) -#ifdef _MSC_VER -#include -#include -#else -#include -#endif -#endif - -extern std::string ppu_get_syscall_name(u64 code); - -namespace rsx -{ - void set_rsx_yield_flag() noexcept; -} - -using spu_rdata_t = decltype(spu_thread::rdata); -extern u32 compute_rdata_hash32(const spu_rdata_t& _src); - -template <> -void fmt_class_string::format(std::string& out, u64 arg) -{ - out += ppu_get_syscall_name(arg); -} - -template <> -void fmt_class_string::format(std::string& out, u64 arg) -{ - format_enum(out, arg, [](auto value) - { - switch (value) - { - case SYS_SYNC_FIFO: return "FIFO"; - case SYS_SYNC_PRIORITY: return "PRIO"; - case SYS_SYNC_PRIORITY_INHERIT: return "PRIO-INHER"; - case SYS_SYNC_RETRY: return "RETRY"; - } - - return unknown; - }); -} - -template <> -void fmt_class_string::format(std::string& out, u64 arg) -{ - out += lv2_obj::name64(get_object(arg).data); -} - -static void null_func_(ppu_thread& ppu, ppu_opcode_t, be_t* this_op, ppu_intrp_func*) -{ - ppu_log.todo("Unimplemented syscall %s -> CELL_OK (r3=0x%llx, r4=0x%llx, r5=0x%llx, r6=0x%llx, r7=0x%llx, r8=0x%llx, r9=0x%llx, r10=0x%llx)", ppu_syscall_code(ppu.gpr[11]), - ppu.gpr[3], ppu.gpr[4], ppu.gpr[5], ppu.gpr[6], ppu.gpr[7], ppu.gpr[8], ppu.gpr[9], ppu.gpr[10]); - - ppu.gpr[3] = 0; - ppu.cia = vm::get_addr(this_op) + 4; -} - -static void uns_func_(ppu_thread& ppu, ppu_opcode_t, be_t* this_op, ppu_intrp_func*) -{ - ppu_log.trace("Unused syscall %d -> ENOSYS", ppu.gpr[11]); - ppu.gpr[3] = CELL_ENOSYS; - ppu.cia = vm::get_addr(this_op) + 4; -} - -// Bind Syscall -#define BIND_SYSC(func) {BIND_FUNC(func), #func} -#define NULL_FUNC(name) {null_func_, #name} - -constexpr std::pair null_func{null_func_, ""}; -constexpr std::pair uns_func{uns_func_, ""}; - -// UNS = Unused -// ROOT = Root -// DBG = Debug -// DEX..DECR = Unavailable on retail consoles -// PM = Product Mode -// AuthID = Authentication ID -const std::array, 1024> g_ppu_syscall_table{ - null_func, - BIND_SYSC(sys_process_getpid), // 1 (0x001) - BIND_SYSC(sys_process_wait_for_child), // 2 (0x002) ROOT - BIND_SYSC(sys_process_exit3), // 3 (0x003) - BIND_SYSC(sys_process_get_status), // 4 (0x004) DBG - BIND_SYSC(sys_process_detach_child), // 5 (0x005) DBG - - uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, // 6-11 UNS - - BIND_SYSC(sys_process_get_number_of_object), // 12 (0x00C) - BIND_SYSC(sys_process_get_id), // 13 (0x00D) - BIND_SYSC(sys_process_is_spu_lock_line_reservation_address), // 14 (0x00E) - - uns_func, uns_func, uns_func, // 15-17 UNS - - BIND_SYSC(sys_process_getppid), // 18 (0x012) - BIND_SYSC(sys_process_kill), // 19 (0x013) - uns_func, // 20 (0x014) UNS - NULL_FUNC(_sys_process_spawn), // 21 (0x015) DBG - BIND_SYSC(_sys_process_exit), // 22 (0x016) - BIND_SYSC(sys_process_wait_for_child2), // 23 (0x017) DBG - null_func, // BIND_SYSC(), //24 (0x018) DBG - BIND_SYSC(sys_process_get_sdk_version), // 25 (0x019) - BIND_SYSC(_sys_process_exit2), // 26 (0x01A) - BIND_SYSC(sys_process_spawns_a_self2), // 27 (0x01B) DBG - NULL_FUNC(_sys_process_get_number_of_object), // 28 (0x01C) ROOT - BIND_SYSC(sys_process_get_id2), // 29 (0x01D) ROOT - BIND_SYSC(_sys_process_get_paramsfo), // 30 (0x01E) - NULL_FUNC(sys_process_get_ppu_guid), // 31 (0x01F) - - uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, // 32-40 UNS - - BIND_SYSC(_sys_ppu_thread_exit), // 41 (0x029) - uns_func, // 42 (0x02A) UNS - BIND_SYSC(sys_ppu_thread_yield), // 43 (0x02B) - BIND_SYSC(sys_ppu_thread_join), // 44 (0x02C) - BIND_SYSC(sys_ppu_thread_detach), // 45 (0x02D) - BIND_SYSC(sys_ppu_thread_get_join_state), // 46 (0x02E) - BIND_SYSC(sys_ppu_thread_set_priority), // 47 (0x02F) DBG - BIND_SYSC(sys_ppu_thread_get_priority), // 48 (0x030) - BIND_SYSC(sys_ppu_thread_get_stack_information), // 49 (0x031) - BIND_SYSC(sys_ppu_thread_stop), // 50 (0x032) ROOT - BIND_SYSC(sys_ppu_thread_restart), // 51 (0x033) ROOT - BIND_SYSC(_sys_ppu_thread_create), // 52 (0x034) DBG - BIND_SYSC(sys_ppu_thread_start), // 53 (0x035) - null_func, // BIND_SYSC(sys_ppu_...), //54 (0x036) ROOT - null_func, // BIND_SYSC(sys_ppu_...), //55 (0x037) ROOT - BIND_SYSC(sys_ppu_thread_rename), // 56 (0x038) - BIND_SYSC(sys_ppu_thread_recover_page_fault), // 57 (0x039) - BIND_SYSC(sys_ppu_thread_get_page_fault_context), // 58 (0x03A) - uns_func, // 59 (0x03B) UNS - BIND_SYSC(sys_trace_create), // 60 (0x03C) - BIND_SYSC(sys_trace_start), // 61 (0x03D) - BIND_SYSC(sys_trace_stop), // 62 (0x03E) - BIND_SYSC(sys_trace_update_top_index), // 63 (0x03F) - BIND_SYSC(sys_trace_destroy), // 64 (0x040) - BIND_SYSC(sys_trace_drain), // 65 (0x041) - BIND_SYSC(sys_trace_attach_process), // 66 (0x042) - BIND_SYSC(sys_trace_allocate_buffer), // 67 (0x043) - BIND_SYSC(sys_trace_free_buffer), // 68 (0x044) - BIND_SYSC(sys_trace_create2), // 69 (0x045) - BIND_SYSC(sys_timer_create), // 70 (0x046) - BIND_SYSC(sys_timer_destroy), // 71 (0x047) - BIND_SYSC(sys_timer_get_information), // 72 (0x048) - BIND_SYSC(_sys_timer_start), // 73 (0x049) - BIND_SYSC(sys_timer_stop), // 74 (0x04A) - BIND_SYSC(sys_timer_connect_event_queue), // 75 (0x04B) - BIND_SYSC(sys_timer_disconnect_event_queue), // 76 (0x04C) - NULL_FUNC(sys_trace_create2_in_cbepm), // 77 (0x04D) - null_func, // BIND_SYSC(sys_trace_...), //78 (0x04E) - uns_func, // 79 (0x04F) UNS - NULL_FUNC(sys_interrupt_tag_create), // 80 (0x050) - BIND_SYSC(sys_interrupt_tag_destroy), // 81 (0x051) - BIND_SYSC(sys_event_flag_create), // 82 (0x052) - BIND_SYSC(sys_event_flag_destroy), // 83 (0x053) - BIND_SYSC(_sys_interrupt_thread_establish), // 84 (0x054) - BIND_SYSC(sys_event_flag_wait), // 85 (0x055) - BIND_SYSC(sys_event_flag_trywait), // 86 (0x056) - BIND_SYSC(sys_event_flag_set), // 87 (0x057) - BIND_SYSC(sys_interrupt_thread_eoi), // 88 (0x058) - BIND_SYSC(_sys_interrupt_thread_disestablish), // 89 (0x059) - BIND_SYSC(sys_semaphore_create), // 90 (0x05A) - BIND_SYSC(sys_semaphore_destroy), // 91 (0x05B) - BIND_SYSC(sys_semaphore_wait), // 92 (0x05C) - BIND_SYSC(sys_semaphore_trywait), // 93 (0x05D) - BIND_SYSC(sys_semaphore_post), // 94 (0x05E) - BIND_SYSC(_sys_lwmutex_create), // 95 (0x05F) - BIND_SYSC(_sys_lwmutex_destroy), // 96 (0x060) - BIND_SYSC(_sys_lwmutex_lock), // 97 (0x061) - BIND_SYSC(_sys_lwmutex_unlock), // 98 (0x062) - BIND_SYSC(_sys_lwmutex_trylock), // 99 (0x063) - BIND_SYSC(sys_mutex_create), // 100 (0x064) - BIND_SYSC(sys_mutex_destroy), // 101 (0x065) - BIND_SYSC(sys_mutex_lock), // 102 (0x066) - BIND_SYSC(sys_mutex_trylock), // 103 (0x067) - BIND_SYSC(sys_mutex_unlock), // 104 (0x068) - BIND_SYSC(sys_cond_create), // 105 (0x069) - BIND_SYSC(sys_cond_destroy), // 106 (0x06A) - BIND_SYSC(sys_cond_wait), // 107 (0x06B) - BIND_SYSC(sys_cond_signal), // 108 (0x06C) - BIND_SYSC(sys_cond_signal_all), // 109 (0x06D) - BIND_SYSC(sys_cond_signal_to), // 110 (0x06E) - BIND_SYSC(_sys_lwcond_create), // 111 (0x06F) - BIND_SYSC(_sys_lwcond_destroy), // 112 (0x070) - BIND_SYSC(_sys_lwcond_queue_wait), // 113 (0x071) - BIND_SYSC(sys_semaphore_get_value), // 114 (0x072) - BIND_SYSC(_sys_lwcond_signal), // 115 (0x073) - BIND_SYSC(_sys_lwcond_signal_all), // 116 (0x074) - BIND_SYSC(_sys_lwmutex_unlock2), // 117 (0x075) - BIND_SYSC(sys_event_flag_clear), // 118 (0x076) - BIND_SYSC(sys_time_get_rtc), // 119 (0x077) ROOT - BIND_SYSC(sys_rwlock_create), // 120 (0x078) - BIND_SYSC(sys_rwlock_destroy), // 121 (0x079) - BIND_SYSC(sys_rwlock_rlock), // 122 (0x07A) - BIND_SYSC(sys_rwlock_tryrlock), // 123 (0x07B) - BIND_SYSC(sys_rwlock_runlock), // 124 (0x07C) - BIND_SYSC(sys_rwlock_wlock), // 125 (0x07D) - BIND_SYSC(sys_rwlock_trywlock), // 126 (0x07E) - BIND_SYSC(sys_rwlock_wunlock), // 127 (0x07F) - BIND_SYSC(sys_event_queue_create), // 128 (0x080) - BIND_SYSC(sys_event_queue_destroy), // 129 (0x081) - BIND_SYSC(sys_event_queue_receive), // 130 (0x082) - BIND_SYSC(sys_event_queue_tryreceive), // 131 (0x083) - BIND_SYSC(sys_event_flag_cancel), // 132 (0x084) - BIND_SYSC(sys_event_queue_drain), // 133 (0x085) - BIND_SYSC(sys_event_port_create), // 134 (0x086) - BIND_SYSC(sys_event_port_destroy), // 135 (0x087) - BIND_SYSC(sys_event_port_connect_local), // 136 (0x088) - BIND_SYSC(sys_event_port_disconnect), // 137 (0x089) - BIND_SYSC(sys_event_port_send), // 138 (0x08A) - BIND_SYSC(sys_event_flag_get), // 139 (0x08B) - BIND_SYSC(sys_event_port_connect_ipc), // 140 (0x08C) - BIND_SYSC(sys_timer_usleep), // 141 (0x08D) - BIND_SYSC(sys_timer_sleep), // 142 (0x08E) - BIND_SYSC(sys_time_set_timezone), // 143 (0x08F) ROOT - BIND_SYSC(sys_time_get_timezone), // 144 (0x090) - BIND_SYSC(sys_time_get_current_time), // 145 (0x091) - BIND_SYSC(sys_time_set_current_time), // 146 (0x092) ROOT - BIND_SYSC(sys_time_get_timebase_frequency), // 147 (0x093) - BIND_SYSC(_sys_rwlock_trywlock), // 148 (0x094) - NULL_FUNC(sys_time_get_system_time), // 149 (0x095) - BIND_SYSC(sys_raw_spu_create_interrupt_tag), // 150 (0x096) - BIND_SYSC(sys_raw_spu_set_int_mask), // 151 (0x097) - BIND_SYSC(sys_raw_spu_get_int_mask), // 152 (0x098) - BIND_SYSC(sys_raw_spu_set_int_stat), // 153 (0x099) - BIND_SYSC(sys_raw_spu_get_int_stat), // 154 (0x09A) - BIND_SYSC(_sys_spu_image_get_information), // 155 (0x09B) - BIND_SYSC(sys_spu_image_open), // 156 (0x09C) - BIND_SYSC(_sys_spu_image_import), // 157 (0x09D) - BIND_SYSC(_sys_spu_image_close), // 158 (0x09E) - BIND_SYSC(_sys_spu_image_get_segments), // 159 (0x09F) - BIND_SYSC(sys_raw_spu_create), // 160 (0x0A0) - BIND_SYSC(sys_raw_spu_destroy), // 161 (0x0A1) - uns_func, // 162 (0x0A2) UNS - BIND_SYSC(sys_raw_spu_read_puint_mb), // 163 (0x0A3) - uns_func, // 164 (0x0A4) UNS - BIND_SYSC(sys_spu_thread_get_exit_status), // 165 (0x0A5) - BIND_SYSC(sys_spu_thread_set_argument), // 166 (0x0A6) - NULL_FUNC(sys_spu_thread_group_start_on_exit), // 167 (0x0A7) - uns_func, // 168 (0x0A8) UNS - BIND_SYSC(sys_spu_initialize), // 169 (0x0A9) - BIND_SYSC(sys_spu_thread_group_create), // 170 (0x0AA) - BIND_SYSC(sys_spu_thread_group_destroy), // 171 (0x0AB) - BIND_SYSC(sys_spu_thread_initialize), // 172 (0x0AC) - BIND_SYSC(sys_spu_thread_group_start), // 173 (0x0AD) - BIND_SYSC(sys_spu_thread_group_suspend), // 174 (0x0AE) - BIND_SYSC(sys_spu_thread_group_resume), // 175 (0x0AF) - BIND_SYSC(sys_spu_thread_group_yield), // 176 (0x0B0) - BIND_SYSC(sys_spu_thread_group_terminate), // 177 (0x0B1) - BIND_SYSC(sys_spu_thread_group_join), // 178 (0x0B2) - BIND_SYSC(sys_spu_thread_group_set_priority), // 179 (0x0B3) - BIND_SYSC(sys_spu_thread_group_get_priority), // 180 (0x0B4) - BIND_SYSC(sys_spu_thread_write_ls), // 181 (0x0B5) - BIND_SYSC(sys_spu_thread_read_ls), // 182 (0x0B6) - uns_func, // 183 (0x0B7) UNS - BIND_SYSC(sys_spu_thread_write_snr), // 184 (0x0B8) - BIND_SYSC(sys_spu_thread_group_connect_event), // 185 (0x0B9) - BIND_SYSC(sys_spu_thread_group_disconnect_event), // 186 (0x0BA) - BIND_SYSC(sys_spu_thread_set_spu_cfg), // 187 (0x0BB) - BIND_SYSC(sys_spu_thread_get_spu_cfg), // 188 (0x0BC) - uns_func, // 189 (0x0BD) UNS - BIND_SYSC(sys_spu_thread_write_spu_mb), // 190 (0x0BE) - BIND_SYSC(sys_spu_thread_connect_event), // 191 (0x0BF) - BIND_SYSC(sys_spu_thread_disconnect_event), // 192 (0x0C0) - BIND_SYSC(sys_spu_thread_bind_queue), // 193 (0x0C1) - BIND_SYSC(sys_spu_thread_unbind_queue), // 194 (0x0C2) - uns_func, // 195 (0x0C3) UNS - BIND_SYSC(sys_raw_spu_set_spu_cfg), // 196 (0x0C4) - BIND_SYSC(sys_raw_spu_get_spu_cfg), // 197 (0x0C5) - BIND_SYSC(sys_spu_thread_recover_page_fault), // 198 (0x0C6) - BIND_SYSC(sys_raw_spu_recover_page_fault), // 199 (0x0C7) - - null_func, null_func, null_func, null_func, null_func, // 204 UNS? - null_func, null_func, null_func, null_func, null_func, // 209 UNS? - null_func, null_func, null_func, // 212 UNS? - BIND_SYSC(sys_console_write2), // 213 (0x0D5) - null_func, // 214 UNS? - - NULL_FUNC(sys_dbg_mat_set_condition), // 215 (0x0D7) - NULL_FUNC(sys_dbg_mat_get_condition), // 216 (0x0D8) - uns_func, // BIND_SYSC(sys_dbg_...), //217 (0x0D9) DBG UNS? - uns_func, // BIND_SYSC(sys_dbg_...), //218 (0x0DA) DBG UNS? - uns_func, // BIND_SYSC(sys_dbg_...), //219 (0x0DB) DBG UNS? - - null_func, null_func, null_func, null_func, null_func, // 224 UNS - null_func, null_func, null_func, null_func, null_func, // 229 UNS? - - BIND_SYSC(sys_isolated_spu_create), // 230 (0x0E6) ROOT - BIND_SYSC(sys_isolated_spu_destroy), // 231 (0x0E7) ROOT - BIND_SYSC(sys_isolated_spu_start), // 232 (0x0E8) ROOT - BIND_SYSC(sys_isolated_spu_create_interrupt_tag), // 233 (0x0E9) ROOT - BIND_SYSC(sys_isolated_spu_set_int_mask), // 234 (0x0EA) ROOT - BIND_SYSC(sys_isolated_spu_get_int_mask), // 235 (0x0EB) ROOT - BIND_SYSC(sys_isolated_spu_set_int_stat), // 236 (0x0EC) ROOT - BIND_SYSC(sys_isolated_spu_get_int_stat), // 237 (0x0ED) ROOT - BIND_SYSC(sys_isolated_spu_set_spu_cfg), // 238 (0x0EE) ROOT - BIND_SYSC(sys_isolated_spu_get_spu_cfg), // 239 (0x0EF) ROOT - BIND_SYSC(sys_isolated_spu_read_puint_mb), // 240 (0x0F0) ROOT - uns_func, uns_func, uns_func, // 241-243 ROOT UNS - NULL_FUNC(sys_spu_thread_group_system_set_next_group), // 244 (0x0F4) ROOT - NULL_FUNC(sys_spu_thread_group_system_unset_next_group), // 245 (0x0F5) ROOT - NULL_FUNC(sys_spu_thread_group_system_set_switch_group), // 246 (0x0F6) ROOT - NULL_FUNC(sys_spu_thread_group_system_unset_switch_group), // 247 (0x0F7) ROOT - null_func, // BIND_SYSC(sys_spu_thread_group...), //248 (0x0F8) ROOT - null_func, // BIND_SYSC(sys_spu_thread_group...), //249 (0x0F9) ROOT - BIND_SYSC(sys_spu_thread_group_set_cooperative_victims), // 250 (0x0FA) - BIND_SYSC(sys_spu_thread_group_connect_event_all_threads), // 251 (0x0FB) - BIND_SYSC(sys_spu_thread_group_disconnect_event_all_threads), // 252 (0x0FC) - BIND_SYSC(sys_spu_thread_group_syscall_253), // 253 (0x0FD) - BIND_SYSC(sys_spu_thread_group_log), // 254 (0x0FE) - - uns_func, uns_func, uns_func, uns_func, uns_func, // 255-259 UNS - - NULL_FUNC(sys_spu_image_open_by_fd), // 260 (0x104) - - uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, // 261-269 UNS - uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, // 270-279 UNS - uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, // 280-289 UNS - uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, // 290-299 UNS - - BIND_SYSC(sys_vm_memory_map), // 300 (0x12C) - BIND_SYSC(sys_vm_unmap), // 301 (0x12D) - BIND_SYSC(sys_vm_append_memory), // 302 (0x12E) - BIND_SYSC(sys_vm_return_memory), // 303 (0x12F) - BIND_SYSC(sys_vm_lock), // 304 (0x130) - BIND_SYSC(sys_vm_unlock), // 305 (0x131) - BIND_SYSC(sys_vm_touch), // 306 (0x132) - BIND_SYSC(sys_vm_flush), // 307 (0x133) - BIND_SYSC(sys_vm_invalidate), // 308 (0x134) - BIND_SYSC(sys_vm_store), // 309 (0x135) - BIND_SYSC(sys_vm_sync), // 310 (0x136) - BIND_SYSC(sys_vm_test), // 311 (0x137) - BIND_SYSC(sys_vm_get_statistics), // 312 (0x138) - BIND_SYSC(sys_vm_memory_map_different), // 313 (0x139) - null_func, // BIND_SYSC(sys_...), //314 (0x13A) - null_func, // BIND_SYSC(sys_...), //315 (0x13B) - - uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, // 316-323 UNS - - BIND_SYSC(sys_memory_container_create), // 324 (0x144) DBG - BIND_SYSC(sys_memory_container_destroy), // 325 (0x145) DBG - BIND_SYSC(sys_mmapper_allocate_fixed_address), // 326 (0x146) - BIND_SYSC(sys_mmapper_enable_page_fault_notification), // 327 (0x147) - BIND_SYSC(sys_mmapper_allocate_shared_memory_from_container_ext), // 328 (0x148) - BIND_SYSC(sys_mmapper_free_shared_memory), // 329 (0x149) - BIND_SYSC(sys_mmapper_allocate_address), // 330 (0x14A) - BIND_SYSC(sys_mmapper_free_address), // 331 (0x14B) - BIND_SYSC(sys_mmapper_allocate_shared_memory), // 332 (0x14C) - NULL_FUNC(sys_mmapper_set_shared_memory_flag), // 333(0x14D) - BIND_SYSC(sys_mmapper_map_shared_memory), // 334 (0x14E) - BIND_SYSC(sys_mmapper_unmap_shared_memory), // 335 (0x14F) - BIND_SYSC(sys_mmapper_change_address_access_right), // 336 (0x150) - BIND_SYSC(sys_mmapper_search_and_map), // 337 (0x151) - NULL_FUNC(sys_mmapper_get_shared_memory_attribute), // 338 (0x152) - BIND_SYSC(sys_mmapper_allocate_shared_memory_ext), // 339 (0x153) - null_func, // BIND_SYSC(sys_...), //340 (0x154) - BIND_SYSC(sys_memory_container_create), // 341 (0x155) - BIND_SYSC(sys_memory_container_destroy), // 342 (0x156) - BIND_SYSC(sys_memory_container_get_size), // 343 (0x157) - NULL_FUNC(sys_memory_budget_set), // 344 (0x158) - BIND_SYSC(sys_memory_container_destroy_parent_with_childs), // 345 (0x159) - null_func, // BIND_SYSC(sys_memory_...), //346 (0x15A) - uns_func, // 347 (0x15B) UNS - BIND_SYSC(sys_memory_allocate), // 348 (0x15C) - BIND_SYSC(sys_memory_free), // 349 (0x15D) - BIND_SYSC(sys_memory_allocate_from_container), // 350 (0x15E) - BIND_SYSC(sys_memory_get_page_attribute), // 351 (0x15F) - BIND_SYSC(sys_memory_get_user_memory_size), // 352 (0x160) - BIND_SYSC(sys_memory_get_user_memory_stat), // 353 (0x161) - null_func, // BIND_SYSC(sys_memory_...), //354 (0x162) - null_func, // BIND_SYSC(sys_memory_...), //355 (0x163) - NULL_FUNC(sys_memory_allocate_colored), // 356 (0x164) - null_func, // BIND_SYSC(sys_memory_...), //357 (0x165) - null_func, // BIND_SYSC(sys_memory_...), //358 (0x166) - null_func, // BIND_SYSC(sys_memory_...), //359 (0x167) - null_func, // BIND_SYSC(sys_memory_...), //360 (0x168) - NULL_FUNC(sys_memory_allocate_from_container_colored), // 361 (0x169) - BIND_SYSC(sys_mmapper_allocate_shared_memory_from_container), // 362 (0x16A) - null_func, // BIND_SYSC(sys_mmapper_...), //363 (0x16B) - null_func, // BIND_SYSC(sys_mmapper_...), //364 (0x16C) - uns_func, uns_func, // 366 (0x16E) UNS - BIND_SYSC(sys_uart_initialize), // 367 (0x16F) ROOT - BIND_SYSC(sys_uart_receive), // 368 (0x170) ROOT - BIND_SYSC(sys_uart_send), // 369 (0x171) ROOT - BIND_SYSC(sys_uart_get_params), // 370 (0x172) ROOT - uns_func, // 371 (0x173) UNS - BIND_SYSC(_sys_game_watchdog_start), // 372 (0x174) - BIND_SYSC(_sys_game_watchdog_stop), // 373 (0x175) - BIND_SYSC(_sys_game_watchdog_clear), // 374 (0x176) - BIND_SYSC(_sys_game_set_system_sw_version), // 375 (0x177) ROOT - BIND_SYSC(_sys_game_get_system_sw_version), // 376 (0x178) ROOT - BIND_SYSC(sys_sm_set_shop_mode), // 377 (0x179) ROOT - BIND_SYSC(sys_sm_get_ext_event2), // 378 (0x17A) ROOT - BIND_SYSC(sys_sm_shutdown), // 379 (0x17B) ROOT - BIND_SYSC(sys_sm_get_params), // 380 (0x17C) DBG - NULL_FUNC(sys_sm_get_inter_lpar_parameter), // 381 (0x17D) ROOT - NULL_FUNC(sys_sm_initialize), // 382 (0x17E) ROOT - NULL_FUNC(sys_game_get_temperature), // 383 (0x17F) ROOT - NULL_FUNC(sys_sm_get_tzpb), // 384 (0x180) ROOT - NULL_FUNC(sys_sm_request_led), // 385 (0x181) ROOT - BIND_SYSC(sys_sm_control_led), // 386 (0x182) ROOT - NULL_FUNC(sys_sm_get_system_info), // 387 (0x183) DBG - BIND_SYSC(sys_sm_ring_buzzer2), // 388 (0x184) ROOT - NULL_FUNC(sys_sm_set_fan_policy), // 389 (0x185) PM - NULL_FUNC(sys_sm_request_error_log), // 390 (0x186) ROOT - NULL_FUNC(sys_sm_request_be_count), // 391 (0x187) ROOT - BIND_SYSC(sys_sm_ring_buzzer), // 392 (0x188) ROOT - NULL_FUNC(sys_sm_get_hw_config), // 393 (0x189) ROOT - NULL_FUNC(sys_sm_request_scversion), // 394 (0x18A) ROOT - NULL_FUNC(sys_sm_request_system_event_log), // 395 (0x18B) PM - NULL_FUNC(sys_sm_set_rtc_alarm), // 396 (0x18C) ROOT - NULL_FUNC(sys_sm_get_rtc_alarm), // 397 (0x18D) ROOT - BIND_SYSC(sys_console_write), // 398 (0x18E) ROOT - uns_func, // 399 (0x18F) UNS - null_func, // BIND_SYSC(sys_sm_...), //400 (0x190) PM - null_func, // BIND_SYSC(sys_sm_...), //401 (0x191) ROOT - BIND_SYSC(sys_tty_read), // 402 (0x192) - BIND_SYSC(sys_tty_write), // 403 (0x193) - null_func, // BIND_SYSC(sys_...), //404 (0x194) ROOT - null_func, // BIND_SYSC(sys_...), //405 (0x195) PM - null_func, // BIND_SYSC(sys_...), //406 (0x196) PM - null_func, // BIND_SYSC(sys_...), //407 (0x197) PM - NULL_FUNC(sys_sm_get_tzpb), // 408 (0x198) PM - NULL_FUNC(sys_sm_get_fan_policy), // 409 (0x199) PM - BIND_SYSC(_sys_game_board_storage_read), // 410 (0x19A) - BIND_SYSC(_sys_game_board_storage_write), // 411 (0x19B) - BIND_SYSC(_sys_game_get_rtc_status), // 412 (0x19C) - null_func, // BIND_SYSC(sys_...), //413 (0x19D) ROOT - null_func, // BIND_SYSC(sys_...), //414 (0x19E) ROOT - null_func, // BIND_SYSC(sys_...), //415 (0x19F) ROOT - - uns_func, uns_func, uns_func, uns_func, // 416-419 UNS - uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, // 420-429 UNS - uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, // 430-439 UNS - uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, // 440-449 UNS - - BIND_SYSC(sys_overlay_load_module), // 450 (0x1C2) - BIND_SYSC(sys_overlay_unload_module), // 451 (0x1C3) - NULL_FUNC(sys_overlay_get_module_list), // 452 (0x1C4) - NULL_FUNC(sys_overlay_get_module_info), // 453 (0x1C5) - BIND_SYSC(sys_overlay_load_module_by_fd), // 454 (0x1C6) - NULL_FUNC(sys_overlay_get_module_info2), // 455 (0x1C7) - NULL_FUNC(sys_overlay_get_sdk_version), // 456 (0x1C8) - NULL_FUNC(sys_overlay_get_module_dbg_info), // 457 (0x1C9) - NULL_FUNC(sys_overlay_get_module_dbg_info), // 458 (0x1CA) - uns_func, // 459 (0x1CB) UNS - NULL_FUNC(sys_prx_dbg_get_module_id_list), // 460 (0x1CC) ROOT - BIND_SYSC(_sys_prx_get_module_id_by_address), // 461 (0x1CD) - uns_func, // 462 (0x1CE) DEX - BIND_SYSC(_sys_prx_load_module_by_fd), // 463 (0x1CF) - BIND_SYSC(_sys_prx_load_module_on_memcontainer_by_fd), // 464 (0x1D0) - BIND_SYSC(_sys_prx_load_module_list), // 465 (0x1D1) - BIND_SYSC(_sys_prx_load_module_list_on_memcontainer), // 466 (0x1D2) - BIND_SYSC(sys_prx_get_ppu_guid), // 467 (0x1D3) - null_func, // BIND_SYSC(sys_...), //468 (0x1D4) ROOT - uns_func, // 469 (0x1D5) UNS - NULL_FUNC(sys_npdrm_check_ekc), // 470 (0x1D6) ROOT - NULL_FUNC(sys_npdrm_regist_ekc), // 471 (0x1D7) ROOT - null_func, // BIND_SYSC(sys_...), //472 (0x1D8) ROOT - null_func, // BIND_SYSC(sys_...), //473 (0x1D9) - null_func, // BIND_SYSC(sys_...), //474 (0x1DA) - null_func, // BIND_SYSC(sys_...), //475 (0x1DB) ROOT - null_func, // BIND_SYSC(sys_...), //476 (0x1DC) ROOT - - uns_func, uns_func, uns_func, // 477-479 UNS - - BIND_SYSC(_sys_prx_load_module), // 480 (0x1E0) - BIND_SYSC(_sys_prx_start_module), // 481 (0x1E1) - BIND_SYSC(_sys_prx_stop_module), // 482 (0x1E2) - BIND_SYSC(_sys_prx_unload_module), // 483 (0x1E3) - BIND_SYSC(_sys_prx_register_module), // 484 (0x1E4) - BIND_SYSC(_sys_prx_query_module), // 485 (0x1E5) - BIND_SYSC(_sys_prx_register_library), // 486 (0x1E6) - BIND_SYSC(_sys_prx_unregister_library), // 487 (0x1E7) - BIND_SYSC(_sys_prx_link_library), // 488 (0x1E8) - BIND_SYSC(_sys_prx_unlink_library), // 489 (0x1E9) - BIND_SYSC(_sys_prx_query_library), // 490 (0x1EA) - uns_func, // 491 (0x1EB) UNS - NULL_FUNC(sys_prx_dbg_get_module_list), // 492 (0x1EC) DBG - NULL_FUNC(sys_prx_dbg_get_module_info), // 493 (0x1ED) DBG - BIND_SYSC(_sys_prx_get_module_list), // 494 (0x1EE) - BIND_SYSC(_sys_prx_get_module_info), // 495 (0x1EF) - BIND_SYSC(_sys_prx_get_module_id_by_name), // 496 (0x1F0) - BIND_SYSC(_sys_prx_load_module_on_memcontainer), // 497 (0x1F1) - BIND_SYSC(_sys_prx_start), // 498 (0x1F2) - BIND_SYSC(_sys_prx_stop), // 499 (0x1F3) - BIND_SYSC(sys_hid_manager_open), // 500 (0x1F4) - NULL_FUNC(sys_hid_manager_close), // 501 (0x1F5) - BIND_SYSC(sys_hid_manager_read), // 502 (0x1F6) ROOT - BIND_SYSC(sys_hid_manager_ioctl), // 503 (0x1F7) - NULL_FUNC(sys_hid_manager_map_logical_id_to_port_id), // 504 (0x1F8) ROOT - NULL_FUNC(sys_hid_manager_unmap_logical_id_to_port_id), // 505 (0x1F9) ROOT - BIND_SYSC(sys_hid_manager_add_hot_key_observer), // 506 (0x1FA) ROOT - NULL_FUNC(sys_hid_manager_remove_hot_key_observer), // 507 (0x1FB) ROOT - NULL_FUNC(sys_hid_manager_grab_focus), // 508 (0x1FC) ROOT - NULL_FUNC(sys_hid_manager_release_focus), // 509 (0x1FD) ROOT - BIND_SYSC(sys_hid_manager_check_focus), // 510 (0x1FE) - NULL_FUNC(sys_hid_manager_set_master_process), // 511 (0x1FF) ROOT - BIND_SYSC(sys_hid_manager_is_process_permission_root), // 512 (0x200) ROOT - BIND_SYSC(sys_hid_manager_513), // 513 (0x201) - BIND_SYSC(sys_hid_manager_514), // 514 (0x202) - uns_func, // 515 (0x203) UNS - BIND_SYSC(sys_config_open), // 516 (0x204) - BIND_SYSC(sys_config_close), // 517 (0x205) - BIND_SYSC(sys_config_get_service_event), // 518 (0x206) - BIND_SYSC(sys_config_add_service_listener), // 519 (0x207) - BIND_SYSC(sys_config_remove_service_listener), // 520 (0x208) - BIND_SYSC(sys_config_register_service), // 521 (0x209) - BIND_SYSC(sys_config_unregister_service), // 522 (0x20A) - BIND_SYSC(sys_config_get_io_event), // 523 (0x20B) - BIND_SYSC(sys_config_register_io_error_listener), // 524 (0x20C) - BIND_SYSC(sys_config_unregister_io_error_listener), // 525 (0x20D) - uns_func, uns_func, uns_func, uns_func, // 526-529 UNS - BIND_SYSC(sys_usbd_initialize), // 530 (0x212) - BIND_SYSC(sys_usbd_finalize), // 531 (0x213) - BIND_SYSC(sys_usbd_get_device_list), // 532 (0x214) - BIND_SYSC(sys_usbd_get_descriptor_size), // 533 (0x215) - BIND_SYSC(sys_usbd_get_descriptor), // 534 (0x216) - BIND_SYSC(sys_usbd_register_ldd), // 535 (0x217) - BIND_SYSC(sys_usbd_unregister_ldd), // 536 (0x218) - BIND_SYSC(sys_usbd_open_pipe), // 537 (0x219) - BIND_SYSC(sys_usbd_open_default_pipe), // 538 (0x21A) - BIND_SYSC(sys_usbd_close_pipe), // 539 (0x21B) - BIND_SYSC(sys_usbd_receive_event), // 540 (0x21C) - BIND_SYSC(sys_usbd_detect_event), // 541 (0x21D) - BIND_SYSC(sys_usbd_attach), // 542 (0x21E) - BIND_SYSC(sys_usbd_transfer_data), // 543 (0x21F) - BIND_SYSC(sys_usbd_isochronous_transfer_data), // 544 (0x220) - BIND_SYSC(sys_usbd_get_transfer_status), // 545 (0x221) - BIND_SYSC(sys_usbd_get_isochronous_transfer_status), // 546 (0x222) - BIND_SYSC(sys_usbd_get_device_location), // 547 (0x223) - BIND_SYSC(sys_usbd_send_event), // 548 (0x224) - BIND_SYSC(sys_usbd_event_port_send), // 549 (0x225) - BIND_SYSC(sys_usbd_allocate_memory), // 550 (0x226) - BIND_SYSC(sys_usbd_free_memory), // 551 (0x227) - null_func, // BIND_SYSC(sys_usbd_...), //552 (0x228) - null_func, // BIND_SYSC(sys_usbd_...), //553 (0x229) - null_func, // BIND_SYSC(sys_usbd_...), //554 (0x22A) - null_func, // BIND_SYSC(sys_usbd_...), //555 (0x22B) - BIND_SYSC(sys_usbd_get_device_speed), // 556 (0x22C) - null_func, // BIND_SYSC(sys_usbd_...), //557 (0x22D) - BIND_SYSC(sys_usbd_unregister_extra_ldd), // 558 (0x22E) - BIND_SYSC(sys_usbd_register_extra_ldd), // 559 (0x22F) - null_func, // BIND_SYSC(sys_...), //560 (0x230) ROOT - null_func, // BIND_SYSC(sys_...), //561 (0x231) ROOT - null_func, // BIND_SYSC(sys_...), //562 (0x232) ROOT - null_func, // BIND_SYSC(sys_...), //563 (0x233) - null_func, // BIND_SYSC(sys_...), //564 (0x234) - null_func, // BIND_SYSC(sys_...), //565 (0x235) - null_func, // BIND_SYSC(sys_...), //566 (0x236) - null_func, // BIND_SYSC(sys_...), //567 (0x237) - null_func, // BIND_SYSC(sys_...), //568 (0x238) - null_func, // BIND_SYSC(sys_...), //569 (0x239) - NULL_FUNC(sys_pad_ldd_register_controller), // 570 (0x23A) - NULL_FUNC(sys_pad_ldd_unregister_controller), // 571 (0x23B) - NULL_FUNC(sys_pad_ldd_data_insert), // 572 (0x23C) - NULL_FUNC(sys_pad_dbg_ldd_set_data_insert_mode), // 573 (0x23D) - NULL_FUNC(sys_pad_ldd_register_controller), // 574 (0x23E) - NULL_FUNC(sys_pad_ldd_get_port_no), // 575 (0x23F) - uns_func, // 576 (0x240) UNS - null_func, // BIND_SYSC(sys_pad_manager_...), //577 (0x241) ROOT PM - null_func, // BIND_SYSC(sys_bluetooth_...), //578 (0x242) - null_func, // BIND_SYSC(sys_bluetooth_aud_serial_...), //579 (0x243) - null_func, // BIND_SYSC(sys_bluetooth_...), //580 (0x244) ROOT - null_func, // BIND_SYSC(sys_bluetooth_...), //581 (0x245) ROOT - null_func, // BIND_SYSC(sys_bluetooth_...), //582 (0x246) ROOT - NULL_FUNC(sys_bt_read_firmware_version), // 583 (0x247) ROOT - NULL_FUNC(sys_bt_complete_wake_on_host), // 584 (0x248) ROOT - NULL_FUNC(sys_bt_disable_bluetooth), // 585 (0x249) - NULL_FUNC(sys_bt_enable_bluetooth), // 586 (0x24A) - NULL_FUNC(sys_bt_bccmd), // 587 (0x24B) ROOT - NULL_FUNC(sys_bt_read_hq), // 588 (0x24C) - NULL_FUNC(sys_bt_hid_get_remote_status), // 589 (0x24D) - NULL_FUNC(sys_bt_register_controller), // 590 (0x24E) ROOT - NULL_FUNC(sys_bt_clear_registered_contoller), // 591 (0x24F) - NULL_FUNC(sys_bt_connect_accept_controller), // 592 (0x250) - NULL_FUNC(sys_bt_get_local_bdaddress), // 593 (0x251) ROOT - NULL_FUNC(sys_bt_hid_get_data), // 594 (0x252) - NULL_FUNC(sys_bt_hid_set_report), // 595 (0x253) - NULL_FUNC(sys_bt_sched_log), // 596 (0x254) - NULL_FUNC(sys_bt_cancel_connect_accept_controller), // 597 (0x255) - null_func, // BIND_SYSC(sys_bluetooth_...), //598 (0x256) ROOT - null_func, // BIND_SYSC(sys_bluetooth_...), //599 (0x257) ROOT - BIND_SYSC(sys_storage_open), // 600 (0x258) ROOT - BIND_SYSC(sys_storage_close), // 601 (0x259) - BIND_SYSC(sys_storage_read), // 602 (0x25A) - BIND_SYSC(sys_storage_write), // 603 (0x25B) - BIND_SYSC(sys_storage_send_device_command), // 604 (0x25C) - BIND_SYSC(sys_storage_async_configure), // 605 (0x25D) - BIND_SYSC(sys_storage_async_read), // 606 (0x25E) - BIND_SYSC(sys_storage_async_write), // 607 (0x25F) - BIND_SYSC(sys_storage_async_cancel), // 608 (0x260) - BIND_SYSC(sys_storage_get_device_info), // 609 (0x261) ROOT - BIND_SYSC(sys_storage_get_device_config), // 610 (0x262) ROOT - BIND_SYSC(sys_storage_report_devices), // 611 (0x263) ROOT - BIND_SYSC(sys_storage_configure_medium_event), // 612 (0x264) ROOT - BIND_SYSC(sys_storage_set_medium_polling_interval), // 613 (0x265) - BIND_SYSC(sys_storage_create_region), // 614 (0x266) - BIND_SYSC(sys_storage_delete_region), // 615 (0x267) - BIND_SYSC(sys_storage_execute_device_command), // 616 (0x268) - BIND_SYSC(sys_storage_check_region_acl), // 617 (0x269) - BIND_SYSC(sys_storage_set_region_acl), // 618 (0x26A) - BIND_SYSC(sys_storage_async_send_device_command), // 619 (0x26B) - null_func, // BIND_SYSC(sys_...), //620 (0x26C) ROOT - BIND_SYSC(sys_gamepad_ycon_if), // 621 (0x26D) - BIND_SYSC(sys_storage_get_region_offset), // 622 (0x26E) - BIND_SYSC(sys_storage_set_emulated_speed), // 623 (0x26F) - BIND_SYSC(sys_io_buffer_create), // 624 (0x270) - BIND_SYSC(sys_io_buffer_destroy), // 625 (0x271) - BIND_SYSC(sys_io_buffer_allocate), // 626 (0x272) - BIND_SYSC(sys_io_buffer_free), // 627 (0x273) - uns_func, uns_func, // 629 (0x275) UNS - BIND_SYSC(sys_gpio_set), // 630 (0x276) - BIND_SYSC(sys_gpio_get), // 631 (0x277) - uns_func, // 632 (0x278) UNS - NULL_FUNC(sys_fsw_connect_event), // 633 (0x279) - NULL_FUNC(sys_fsw_disconnect_event), // 634 (0x27A) - BIND_SYSC(sys_btsetting_if), // 635 (0x27B) - null_func, // BIND_SYSC(sys_...), //636 (0x27C) - null_func, // BIND_SYSC(sys_...), //637 (0x27D) - null_func, // BIND_SYSC(sys_...), //638 (0x27E) - - null_func, // BIND_SYSC(sys...), //639 DEPRECATED - NULL_FUNC(sys_usbbtaudio_initialize), // 640 DEPRECATED - NULL_FUNC(sys_usbbtaudio_finalize), // 641 DEPRECATED - NULL_FUNC(sys_usbbtaudio_discovery), // 642 DEPRECATED - NULL_FUNC(sys_usbbtaudio_cancel_discovery), // 643 DEPRECATED - NULL_FUNC(sys_usbbtaudio_pairing), // 644 DEPRECATED - NULL_FUNC(sys_usbbtaudio_set_passkey), // 645 DEPRECATED - NULL_FUNC(sys_usbbtaudio_connect), // 646 DEPRECATED - NULL_FUNC(sys_usbbtaudio_disconnect), // 647 DEPRECATED - null_func, // BIND_SYSC(sys_...), //648 DEPRECATED - null_func, // BIND_SYSC(sys_...), //649 DEPRECATED - - BIND_SYSC(sys_rsxaudio_initialize), // 650 (0x28A) - BIND_SYSC(sys_rsxaudio_finalize), // 651 (0x28B) - BIND_SYSC(sys_rsxaudio_import_shared_memory), // 652 (0x28C) - BIND_SYSC(sys_rsxaudio_unimport_shared_memory), // 653 (0x28D) - BIND_SYSC(sys_rsxaudio_create_connection), // 654 (0x28E) - BIND_SYSC(sys_rsxaudio_close_connection), // 655 (0x28F) - BIND_SYSC(sys_rsxaudio_prepare_process), // 656 (0x290) - BIND_SYSC(sys_rsxaudio_start_process), // 657 (0x291) - BIND_SYSC(sys_rsxaudio_stop_process), // 658 (0x292) - BIND_SYSC(sys_rsxaudio_get_dma_param), // 659 (0x293) - - uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, // 660-665 UNS - - BIND_SYSC(sys_rsx_device_open), // 666 (0x29A) - BIND_SYSC(sys_rsx_device_close), // 667 (0x29B) - BIND_SYSC(sys_rsx_memory_allocate), // 668 (0x29C) - BIND_SYSC(sys_rsx_memory_free), // 669 (0x29D) - BIND_SYSC(sys_rsx_context_allocate), // 670 (0x29E) - BIND_SYSC(sys_rsx_context_free), // 671 (0x29F) - BIND_SYSC(sys_rsx_context_iomap), // 672 (0x2A0) - BIND_SYSC(sys_rsx_context_iounmap), // 673 (0x2A1) - BIND_SYSC(sys_rsx_context_attribute), // 674 (0x2A2) - BIND_SYSC(sys_rsx_device_map), // 675 (0x2A3) - BIND_SYSC(sys_rsx_device_unmap), // 676 (0x2A4) - BIND_SYSC(sys_rsx_attribute), // 677 (0x2A5) - null_func, // BIND_SYSC(sys_...), //678 (0x2A6) - null_func, // BIND_SYSC(sys_...), //679 (0x2A7) ROOT - null_func, // BIND_SYSC(sys_...), //680 (0x2A8) ROOT - null_func, // BIND_SYSC(sys_...), //681 (0x2A9) ROOT - null_func, // BIND_SYSC(sys_...), //682 (0x2AA) ROOT - null_func, // BIND_SYSC(sys_...), //683 (0x2AB) ROOT - null_func, // BIND_SYSC(sys_...), //684 (0x2AC) ROOT - null_func, // BIND_SYSC(sys_...), //685 (0x2AD) ROOT - null_func, // BIND_SYSC(sys_...), //686 (0x2AE) ROOT - null_func, // BIND_SYSC(sys_...), //687 (0x2AF) ROOT - null_func, // BIND_SYSC(sys_...), //688 (0x2B0) ROOT - null_func, // BIND_SYSC(sys_...), //689 (0x2B1) ROOT - null_func, // BIND_SYSC(sys_...), //690 (0x2B2) ROOT - null_func, // BIND_SYSC(sys_...), //691 (0x2B3) ROOT - null_func, // BIND_SYSC(sys_...), //692 (0x2B4) ROOT - null_func, // BIND_SYSC(sys_...), //693 (0x2B5) ROOT - null_func, // BIND_SYSC(sys_...), //694 (0x2B6) DEPRECATED - null_func, // BIND_SYSC(sys_...), //695 (0x2B7) DEPRECATED - null_func, // BIND_SYSC(sys_...), //696 (0x2B8) ROOT - uns_func, // BIND_SYSC(sys_...), //697 (0x2B9) UNS - uns_func, // BIND_SYSC(sys_...), //698 (0x2BA) UNS - BIND_SYSC(sys_bdemu_send_command), // 699 (0x2BB) - BIND_SYSC(sys_net_bnet_accept), // 700 (0x2BC) - BIND_SYSC(sys_net_bnet_bind), // 701 (0x2BD) - BIND_SYSC(sys_net_bnet_connect), // 702 (0x2BE) - BIND_SYSC(sys_net_bnet_getpeername), // 703 (0x2BF) - BIND_SYSC(sys_net_bnet_getsockname), // 704 (0x2C0) - BIND_SYSC(sys_net_bnet_getsockopt), // 705 (0x2C1) - BIND_SYSC(sys_net_bnet_listen), // 706 (0x2C2) - BIND_SYSC(sys_net_bnet_recvfrom), // 707 (0x2C3) - BIND_SYSC(sys_net_bnet_recvmsg), // 708 (0x2C4) - BIND_SYSC(sys_net_bnet_sendmsg), // 709 (0x2C5) - BIND_SYSC(sys_net_bnet_sendto), // 710 (0x2C6) - BIND_SYSC(sys_net_bnet_setsockopt), // 711 (0x2C7) - BIND_SYSC(sys_net_bnet_shutdown), // 712 (0x2C8) - BIND_SYSC(sys_net_bnet_socket), // 713 (0x2C9) - BIND_SYSC(sys_net_bnet_close), // 714 (0x2CA) - BIND_SYSC(sys_net_bnet_poll), // 715 (0x2CB) - BIND_SYSC(sys_net_bnet_select), // 716 (0x2CC) - BIND_SYSC(_sys_net_open_dump), // 717 (0x2CD) - BIND_SYSC(_sys_net_read_dump), // 718 (0x2CE) - BIND_SYSC(_sys_net_close_dump), // 719 (0x2CF) - BIND_SYSC(_sys_net_write_dump), // 720 (0x2D0) - BIND_SYSC(sys_net_abort), // 721 (0x2D1) - BIND_SYSC(sys_net_infoctl), // 722 (0x2D2) - BIND_SYSC(sys_net_control), // 723 (0x2D3) - BIND_SYSC(sys_net_bnet_ioctl), // 724 (0x2D4) - BIND_SYSC(sys_net_bnet_sysctl), // 725 (0x2D5) - BIND_SYSC(sys_net_eurus_post_command), // 726 (0x2D6) - - uns_func, uns_func, uns_func, // 727-729 UNS - uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, // 730-739 UNS - uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, // 740-749 UNS - uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, // 750-759 UNS - uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, // 760-769 UNS - uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, // 770-779 UNS - uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, // 780-789 UNS - uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, // 790-799 UNS - - BIND_SYSC(sys_fs_test), // 800 (0x320) - BIND_SYSC(sys_fs_open), // 801 (0x321) - BIND_SYSC(sys_fs_read), // 802 (0x322) - BIND_SYSC(sys_fs_write), // 803 (0x323) - BIND_SYSC(sys_fs_close), // 804 (0x324) - BIND_SYSC(sys_fs_opendir), // 805 (0x325) - BIND_SYSC(sys_fs_readdir), // 806 (0x326) - BIND_SYSC(sys_fs_closedir), // 807 (0x327) - BIND_SYSC(sys_fs_stat), // 808 (0x328) - BIND_SYSC(sys_fs_fstat), // 809 (0x329) - BIND_SYSC(sys_fs_link), // 810 (0x32A) - BIND_SYSC(sys_fs_mkdir), // 811 (0x32B) - BIND_SYSC(sys_fs_rename), // 812 (0x32C) - BIND_SYSC(sys_fs_rmdir), // 813 (0x32D) - BIND_SYSC(sys_fs_unlink), // 814 (0x32E) - BIND_SYSC(sys_fs_utime), // 815 (0x32F) - BIND_SYSC(sys_fs_access), // 816 (0x330) - BIND_SYSC(sys_fs_fcntl), // 817 (0x331) - BIND_SYSC(sys_fs_lseek), // 818 (0x332) - BIND_SYSC(sys_fs_fdatasync), // 819 (0x333) - BIND_SYSC(sys_fs_fsync), // 820 (0x334) - BIND_SYSC(sys_fs_fget_block_size), // 821 (0x335) - BIND_SYSC(sys_fs_get_block_size), // 822 (0x336) - BIND_SYSC(sys_fs_acl_read), // 823 (0x337) - BIND_SYSC(sys_fs_acl_write), // 824 (0x338) - BIND_SYSC(sys_fs_lsn_get_cda_size), // 825 (0x339) - BIND_SYSC(sys_fs_lsn_get_cda), // 826 (0x33A) - BIND_SYSC(sys_fs_lsn_lock), // 827 (0x33B) - BIND_SYSC(sys_fs_lsn_unlock), // 828 (0x33C) - BIND_SYSC(sys_fs_lsn_read), // 829 (0x33D) - BIND_SYSC(sys_fs_lsn_write), // 830 (0x33E) - BIND_SYSC(sys_fs_truncate), // 831 (0x33F) - BIND_SYSC(sys_fs_ftruncate), // 832 (0x340) - BIND_SYSC(sys_fs_symbolic_link), // 833 (0x341) - BIND_SYSC(sys_fs_chmod), // 834 (0x342) - BIND_SYSC(sys_fs_chown), // 835 (0x343) - BIND_SYSC(sys_fs_newfs), // 836 (0x344) - BIND_SYSC(sys_fs_mount), // 837 (0x345) - BIND_SYSC(sys_fs_unmount), // 838 (0x346) - NULL_FUNC(sys_fs_sync), // 839 (0x347) - BIND_SYSC(sys_fs_disk_free), // 840 (0x348) - BIND_SYSC(sys_fs_get_mount_info_size), // 841 (0x349) - BIND_SYSC(sys_fs_get_mount_info), // 842 (0x34A) - NULL_FUNC(sys_fs_get_fs_info_size), // 843 (0x34B) - NULL_FUNC(sys_fs_get_fs_info), // 844 (0x34C) - BIND_SYSC(sys_fs_mapped_allocate), // 845 (0x34D) - BIND_SYSC(sys_fs_mapped_free), // 846 (0x34E) - BIND_SYSC(sys_fs_truncate2), // 847 (0x34F) - - uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, // 848-853 UNS - uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, // 854-859 UNS - - NULL_FUNC(sys_ss_get_cache_of_analog_sunset_flag), // 860 (0x35C) AUTHID - NULL_FUNC(sys_ss_protected_file_db), // 861 ROOT - BIND_SYSC(sys_ss_virtual_trm_manager), // 862 ROOT - BIND_SYSC(sys_ss_update_manager), // 863 ROOT - NULL_FUNC(sys_ss_sec_hw_framework), // 864 DBG - BIND_SYSC(sys_ss_random_number_generator), // 865 (0x361) - BIND_SYSC(sys_ss_secure_rtc), // 866 ROOT - BIND_SYSC(sys_ss_appliance_info_manager), // 867 ROOT - BIND_SYSC(sys_ss_individual_info_manager), // 868 ROOT / DBG AUTHID - NULL_FUNC(sys_ss_factory_data_manager), // 869 ROOT - BIND_SYSC(sys_ss_get_console_id), // 870 (0x366) - BIND_SYSC(sys_ss_access_control_engine), // 871 (0x367) DBG - BIND_SYSC(sys_ss_get_open_psid), // 872 (0x368) - BIND_SYSC(sys_ss_get_cache_of_product_mode), // 873 (0x369) - BIND_SYSC(sys_ss_get_cache_of_flash_ext_flag), // 874 (0x36A) - BIND_SYSC(sys_ss_get_boot_device), // 875 (0x36B) - NULL_FUNC(sys_ss_disc_access_control), // 876 (0x36C) - null_func, // BIND_SYSC(sys_ss_~utoken_if), //877 (0x36D) ROOT - NULL_FUNC(sys_ss_ad_sign), // 878 (0x36E) - NULL_FUNC(sys_ss_media_id), // 879 (0x36F) - NULL_FUNC(sys_deci3_open), // 880 (0x370) - NULL_FUNC(sys_deci3_create_event_path), // 881 (0x371) - NULL_FUNC(sys_deci3_close), // 882 (0x372) - NULL_FUNC(sys_deci3_send), // 883 (0x373) - NULL_FUNC(sys_deci3_receive), // 884 (0x374) - NULL_FUNC(sys_deci3_open2), // 885 (0x375) - uns_func, uns_func, uns_func, // 886-888 UNS - null_func, // BIND_SYSC(sys_...), //889 (0x379) ROOT - NULL_FUNC(sys_deci3_initialize), // 890 (0x37A) - NULL_FUNC(sys_deci3_terminate), // 891 (0x37B) - NULL_FUNC(sys_deci3_debug_mode), // 892 (0x37C) - NULL_FUNC(sys_deci3_show_status), // 893 (0x37D) - NULL_FUNC(sys_deci3_echo_test), // 894 (0x37E) - NULL_FUNC(sys_deci3_send_dcmp_packet), // 895 (0x37F) - NULL_FUNC(sys_deci3_dump_cp_register), // 896 (0x380) - NULL_FUNC(sys_deci3_dump_cp_buffer), // 897 (0x381) - uns_func, // 898 (0x382) UNS - NULL_FUNC(sys_deci3_test), // 899 (0x383) - NULL_FUNC(sys_dbg_stop_processes), // 900 (0x384) - NULL_FUNC(sys_dbg_continue_processes), // 901 (0x385) - NULL_FUNC(sys_dbg_stop_threads), // 902 (0x386) - NULL_FUNC(sys_dbg_continue_threads), // 903 (0x387) - BIND_SYSC(sys_dbg_read_process_memory), // 904 (0x388) - BIND_SYSC(sys_dbg_write_process_memory), // 905 (0x389) - NULL_FUNC(sys_dbg_read_thread_register), // 906 (0x38A) - NULL_FUNC(sys_dbg_write_thread_register), // 907 (0x38B) - NULL_FUNC(sys_dbg_get_process_list), // 908 (0x38C) - NULL_FUNC(sys_dbg_get_thread_list), // 909 (0x38D) - NULL_FUNC(sys_dbg_get_thread_info), // 910 (0x38E) - NULL_FUNC(sys_dbg_spu_thread_read_from_ls), // 911 (0x38F) - NULL_FUNC(sys_dbg_spu_thread_write_to_ls), // 912 (0x390) - NULL_FUNC(sys_dbg_kill_process), // 913 (0x391) - NULL_FUNC(sys_dbg_get_process_info), // 914 (0x392) - NULL_FUNC(sys_dbg_set_run_control_bit_to_spu), // 915 (0x393) - NULL_FUNC(sys_dbg_spu_thread_get_exception_cause), // 916 (0x394) - NULL_FUNC(sys_dbg_create_kernel_event_queue), // 917 (0x395) - NULL_FUNC(sys_dbg_read_kernel_event_queue), // 918 (0x396) - NULL_FUNC(sys_dbg_destroy_kernel_event_queue), // 919 (0x397) - NULL_FUNC(sys_dbg_get_process_event_ctrl_flag), // 920 (0x398) - NULL_FUNC(sys_dbg_set_process_event_cntl_flag), // 921 (0x399) - NULL_FUNC(sys_dbg_get_spu_thread_group_event_cntl_flag), // 922 (0x39A) - NULL_FUNC(sys_dbg_set_spu_thread_group_event_cntl_flag), // 923 (0x39B) - NULL_FUNC(sys_dbg_get_module_list), // 924 (0x39C) - NULL_FUNC(sys_dbg_get_raw_spu_list), // 925 (0x39D) - NULL_FUNC(sys_dbg_initialize_scratch_executable_area), // 926 (0x39E) - NULL_FUNC(sys_dbg_terminate_scratch_executable_area), // 927 (0x3A0) - NULL_FUNC(sys_dbg_initialize_scratch_data_area), // 928 (0x3A1) - NULL_FUNC(sys_dbg_terminate_scratch_data_area), // 929 (0x3A2) - NULL_FUNC(sys_dbg_get_user_memory_stat), // 930 (0x3A3) - NULL_FUNC(sys_dbg_get_shared_memory_attribute_list), // 931 (0x3A4) - NULL_FUNC(sys_dbg_get_mutex_list), // 932 (0x3A4) - NULL_FUNC(sys_dbg_get_mutex_information), // 933 (0x3A5) - NULL_FUNC(sys_dbg_get_cond_list), // 934 (0x3A6) - NULL_FUNC(sys_dbg_get_cond_information), // 935 (0x3A7) - NULL_FUNC(sys_dbg_get_rwlock_list), // 936 (0x3A8) - NULL_FUNC(sys_dbg_get_rwlock_information), // 937 (0x3A9) - NULL_FUNC(sys_dbg_get_lwmutex_list), // 938 (0x3AA) - NULL_FUNC(sys_dbg_get_address_from_dabr), // 939 (0x3AB) - NULL_FUNC(sys_dbg_set_address_to_dabr), // 940 (0x3AC) - NULL_FUNC(sys_dbg_get_lwmutex_information), // 941 (0x3AD) - NULL_FUNC(sys_dbg_get_event_queue_list), // 942 (0x3AE) - NULL_FUNC(sys_dbg_get_event_queue_information), // 943 (0x3AF) - NULL_FUNC(sys_dbg_initialize_ppu_exception_handler), // 944 (0x3B0) - NULL_FUNC(sys_dbg_finalize_ppu_exception_handler), // 945 (0x3B1) DBG - NULL_FUNC(sys_dbg_get_semaphore_list), // 946 (0x3B2) - NULL_FUNC(sys_dbg_get_semaphore_information), // 947 (0x3B3) - NULL_FUNC(sys_dbg_get_kernel_thread_list), // 948 (0x3B4) - NULL_FUNC(sys_dbg_get_kernel_thread_info), // 949 (0x3B5) - NULL_FUNC(sys_dbg_get_lwcond_list), // 950 (0x3B6) - NULL_FUNC(sys_dbg_get_lwcond_information), // 951 (0x3B7) - NULL_FUNC(sys_dbg_create_scratch_data_area_ext), // 952 (0x3B8) - NULL_FUNC(sys_dbg_vm_get_page_information), // 953 (0x3B9) - NULL_FUNC(sys_dbg_vm_get_info), // 954 (0x3BA) - NULL_FUNC(sys_dbg_enable_floating_point_enabled_exception), // 955 (0x3BB) - NULL_FUNC(sys_dbg_disable_floating_point_enabled_exception), // 956 (0x3BC) - NULL_FUNC(sys_dbg_get_process_memory_container_information), // 957 (0x3BD) - uns_func, // 958 (0x3BE) UNS - null_func, // BIND_SYSC(sys_dbg_...), //959 (0x3BF) - NULL_FUNC(sys_control_performance_monitor), // 960 (0x3C0) - NULL_FUNC(sys_performance_monitor_hidden), // 961 (0x3C1) - NULL_FUNC(sys_performance_monitor_bookmark), // 962 (0x3C2) - NULL_FUNC(sys_lv1_pc_trace_create), // 963 (0x3C3) - NULL_FUNC(sys_lv1_pc_trace_start), // 964 (0x3C4) - NULL_FUNC(sys_lv1_pc_trace_stop), // 965 (0x3C5) - NULL_FUNC(sys_lv1_pc_trace_get_status), // 966 (0x3C6) - NULL_FUNC(sys_lv1_pc_trace_destroy), // 967 (0x3C7) - NULL_FUNC(sys_rsx_trace_ioctl), // 968 (0x3C8) - null_func, // BIND_SYSC(sys_dbg_...), //969 (0x3C9) - NULL_FUNC(sys_dbg_get_event_flag_list), // 970 (0x3CA) - NULL_FUNC(sys_dbg_get_event_flag_information), // 971 (0x3CB) - null_func, // BIND_SYSC(sys_dbg_...), //972 (0x3CC) - uns_func, // BIND_SYSC(sys_dbg_...), //973 (0x3CD) - null_func, // BIND_SYSC(sys_dbg_...), //974 (0x3CE) - NULL_FUNC(sys_dbg_read_spu_thread_context2), // 975 (0x3CF) - BIND_SYSC(sys_crypto_engine_create), // 976 (0x3D0) - BIND_SYSC(sys_crypto_engine_destroy), // 977 (0x3D1) - NULL_FUNC(sys_crypto_engine_hasher_prepare), // 978 (0x3D2) ROOT - NULL_FUNC(sys_crypto_engine_hasher_run), // 979 (0x3D3) - NULL_FUNC(sys_crypto_engine_hasher_get_hash), // 980 (0x3D4) - NULL_FUNC(sys_crypto_engine_cipher_prepare), // 981 (0x3D5) ROOT - NULL_FUNC(sys_crypto_engine_cipher_run), // 982 (0x3D6) - NULL_FUNC(sys_crypto_engine_cipher_get_hash), // 983 (0x3D7) - BIND_SYSC(sys_crypto_engine_random_generate), // 984 (0x3D8) - NULL_FUNC(sys_dbg_get_console_type), // 985 (0x3D9) ROOT - null_func, // BIND_SYSC(sys_dbg_...), //986 (0x3DA) ROOT DBG - null_func, // BIND_SYSC(sys_dbg_...), //987 (0x3DB) ROOT - null_func, // BIND_SYSC(sys_dbg_..._ppu_exception_handler) //988 (0x3DC) - null_func, // BIND_SYSC(sys_dbg_...), //989 (0x3DD) - - uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, // 990-998 UNS - uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, // 999-1007 UNS - uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, // 1008-1016 UNS - uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, uns_func, // 1020-1023 UNS -}; - -#undef BIND_SYSC -#undef NULL_FUNC - -// TODO: more enums -enum CellAdecError : u32; -enum CellAtracError : u32; -enum CellAtracMultiError : u32; -enum CellAudioError : u32; -enum CellAudioOutError : u32; -enum CellAudioInError : u32; - -enum CellVideoOutError : u32; - -enum CellSpursCoreError : u32; -enum CellSpursPolicyModuleError : u32; -enum CellSpursTaskError : u32; -enum CellSpursJobError : u32; -enum CellSyncError : u32; - -enum CellGameError : u32; -enum CellGameDataError : u32; -enum CellDiscGameError : u32; -enum CellHddGameError : u32; - -enum SceNpTrophyError : u32; -enum SceNpError : u32; - -template -constexpr auto formatter_of = std::make_pair(EnumMin, &fmt_class_string::format); - -const std::map s_error_codes_formatting_by_type{ - formatter_of<0x80610000, CellAdecError>, - formatter_of<0x80612100, CellAdecError>, - formatter_of<0x80610300, CellAtracError>, - formatter_of<0x80610b00, CellAtracMultiError>, - formatter_of<0x80310700, CellAudioError>, - formatter_of<0x8002b240, CellAudioOutError>, - formatter_of<0x8002b260, CellAudioInError>, - formatter_of<0x8002b220, CellVideoOutError>, - - formatter_of<0x80410100, CellSyncError>, - formatter_of<0x80410700, CellSpursCoreError>, - formatter_of<0x80410800, CellSpursPolicyModuleError>, - formatter_of<0x80410900, CellSpursTaskError>, - formatter_of<0x80410A00, CellSpursJobError>, - - formatter_of<0x8002cb00, CellGameError>, - formatter_of<0x8002b600, CellGameDataError>, - formatter_of<0x8002bd00, CellDiscGameError>, - formatter_of<0x8002ba00, CellHddGameError>, - - formatter_of<0x80022900, SceNpTrophyError>, - formatter_of<0x80029500, SceNpError>, -}; - -template <> -void fmt_class_string::format(std::string& out, u64 arg) -{ - // Test if can be formatted by this formatter - const bool lv2_cell_error = (arg >> 8) == 0x800100u; - - if (!lv2_cell_error) - { - // Format by external enum formatters - auto upper = s_error_codes_formatting_by_type.upper_bound(arg); - - if (upper == s_error_codes_formatting_by_type.begin()) - { - // Format as unknown by another enum formatter - upper->second(out, arg); - return; - } - - // Find the formatter whose base is the highest that is not more than arg - const auto found = std::prev(upper); - found->second(out, arg); - return; - } - - format_enum(out, arg, [](auto error) - { - switch (error) - { - STR_CASE(CELL_EAGAIN); - STR_CASE(CELL_EINVAL); - STR_CASE(CELL_ENOSYS); - STR_CASE(CELL_ENOMEM); - STR_CASE(CELL_ESRCH); - STR_CASE(CELL_ENOENT); - STR_CASE(CELL_ENOEXEC); - STR_CASE(CELL_EDEADLK); - STR_CASE(CELL_EPERM); - STR_CASE(CELL_EBUSY); - STR_CASE(CELL_ETIMEDOUT); - STR_CASE(CELL_EABORT); - STR_CASE(CELL_EFAULT); - STR_CASE(CELL_ENOCHILD); - STR_CASE(CELL_ESTAT); - STR_CASE(CELL_EALIGN); - STR_CASE(CELL_EKRESOURCE); - STR_CASE(CELL_EISDIR); - STR_CASE(CELL_ECANCELED); - STR_CASE(CELL_EEXIST); - STR_CASE(CELL_EISCONN); - STR_CASE(CELL_ENOTCONN); - STR_CASE(CELL_EAUTHFAIL); - STR_CASE(CELL_ENOTMSELF); - STR_CASE(CELL_ESYSVER); - STR_CASE(CELL_EAUTHFATAL); - STR_CASE(CELL_EDOM); - STR_CASE(CELL_ERANGE); - STR_CASE(CELL_EILSEQ); - STR_CASE(CELL_EFPOS); - STR_CASE(CELL_EINTR); - STR_CASE(CELL_EFBIG); - STR_CASE(CELL_EMLINK); - STR_CASE(CELL_ENFILE); - STR_CASE(CELL_ENOSPC); - STR_CASE(CELL_ENOTTY); - STR_CASE(CELL_EPIPE); - STR_CASE(CELL_EROFS); - STR_CASE(CELL_ESPIPE); - STR_CASE(CELL_E2BIG); - STR_CASE(CELL_EACCES); - STR_CASE(CELL_EBADF); - STR_CASE(CELL_EIO); - STR_CASE(CELL_EMFILE); - STR_CASE(CELL_ENODEV); - STR_CASE(CELL_ENOTDIR); - STR_CASE(CELL_ENXIO); - STR_CASE(CELL_EXDEV); - STR_CASE(CELL_EBADMSG); - STR_CASE(CELL_EINPROGRESS); - STR_CASE(CELL_EMSGSIZE); - STR_CASE(CELL_ENAMETOOLONG); - STR_CASE(CELL_ENOLCK); - STR_CASE(CELL_ENOTEMPTY); - STR_CASE(CELL_ENOTSUP); - STR_CASE(CELL_EFSSPECIFIC); - STR_CASE(CELL_EOVERFLOW); - STR_CASE(CELL_ENOTMOUNTED); - STR_CASE(CELL_ENOTSDATA); - STR_CASE(CELL_ESDKVER); - STR_CASE(CELL_ENOLICDISC); - STR_CASE(CELL_ENOLICENT); - } - - return unknown; - }); -} - -stx::init_lock acquire_lock(stx::init_mutex& mtx, ppu_thread* ppu) -{ - if (!ppu) - { - ppu = ensure(cpu_thread::get_current()); - } - - return mtx.init([](int invoke_count, const stx::init_lock&, ppu_thread* ppu) - { - if (!invoke_count) - { - // Sleep before waiting on lock - lv2_obj::sleep(*ppu); - } - else - { - // Wake up after acquistion or failure to acquire - ppu->check_state(); - } - }, - ppu); -} - -stx::access_lock acquire_access_lock(stx::init_mutex& mtx, ppu_thread* ppu) -{ - if (!ppu) - { - ppu = ensure(cpu_thread::get_current()); - } - - // TODO: Check if needs to wait - return mtx.access(); -} - -stx::reset_lock acquire_reset_lock(stx::init_mutex& mtx, ppu_thread* ppu) -{ - if (!ppu) - { - ppu = ensure(cpu_thread::get_current()); - } - - return mtx.reset([](int invoke_count, const stx::init_lock&, ppu_thread* ppu) - { - if (!invoke_count) - { - // Sleep before waiting on lock - lv2_obj::sleep(*ppu); - } - else - { - // Wake up after acquistion or failure to acquire - ppu->check_state(); - } - }, - ppu); -} - -class ppu_syscall_usage -{ - // Internal buffer - std::string m_stats; - u64 m_old_stat[1024]{}; - -public: - // Public info collection buffers - atomic_t stat[1024]{}; - - void print_stats(bool force_print) noexcept - { - std::multimap> usage; - - for (u32 i = 0; i < 1024; i++) - { - if (u64 v = stat[i]; m_old_stat[i] != v || (force_print && v)) - { - // Only add syscalls with non-zero usage counter and only if caught new calls since last print - usage.emplace(v, i); - m_old_stat[i] = v; - } - } - - m_stats.clear(); - - for (auto&& pair : usage) - { - fmt::append(m_stats, u8"\n\t⁂ %s [%u]", ppu_get_syscall_name(pair.second), pair.first); - } - - if (!m_stats.empty()) - { - ppu_log.notice("PPU Syscall Usage Stats:%s", m_stats); - } - } - - void operator()() - { - bool was_paused = false; - u64 sleep_until = get_system_time(); - - for (u32 i = 1; thread_ctrl::state() != thread_state::aborting; i++) - { - thread_ctrl::wait_until(&sleep_until, 1'000'000); - - const bool is_paused = Emu.IsPaused(); - - // Force-print all if paused - const bool force_print = is_paused && !was_paused; - - if (force_print || i % 10 == 0) - { - was_paused = is_paused; - print_stats(force_print); - } - } - } - - ~ppu_syscall_usage() - { - print_stats(true); - } - - static constexpr auto thread_name = "PPU Syscall Usage Thread"sv; -}; - -extern void ppu_execute_syscall(ppu_thread& ppu, u64 code) -{ - if (g_cfg.core.ppu_decoder == ppu_decoder_type::llvm_legacy) - { - code = ppu.gpr[11]; - } - - if (code < g_ppu_syscall_table.size()) - { - g_fxo->get>().stat[code]++; - - if (const auto func = g_ppu_syscall_table[code].first) - { -#ifdef __APPLE__ - pthread_jit_write_protect_np(false); -#endif - func(ppu, {}, vm::_ptr(ppu.cia), nullptr); - ppu_log.trace("Syscall '%s' (%llu) finished, r3=0x%llx", ppu_syscall_code(code), code, ppu.gpr[3]); - -#ifdef __APPLE__ - pthread_jit_write_protect_np(true); - // No need to flush cache lines after a syscall, since we didn't generate any code. -#endif - return; - } - } - - fmt::throw_exception("Invalid syscall number (%llu)", code); -} - -extern ppu_intrp_func_t ppu_get_syscall(u64 code) -{ - if (code < g_ppu_syscall_table.size()) - { - return g_ppu_syscall_table[code].first; - } - - return nullptr; -} - -std::string ppu_get_syscall_name(u64 code) -{ - if (code < g_ppu_syscall_table.size() && !g_ppu_syscall_table[code].second.empty()) - { - return std::string(g_ppu_syscall_table[code].second); - } - - return fmt::format("syscall_%u", code); -} - -DECLARE(lv2_obj::g_mutex); -DECLARE(lv2_obj::g_ppu){}; -DECLARE(lv2_obj::g_pending){}; -DECLARE(lv2_obj::g_priority_order_tag){}; - -thread_local DECLARE(lv2_obj::g_to_notify){}; -thread_local DECLARE(lv2_obj::g_postpone_notify_barrier){}; -thread_local DECLARE(lv2_obj::g_to_awake); - -// Scheduler queue for timeouts (wait until -> thread) -static std::deque> g_waiting; - -// Threads which must call lv2_obj::sleep before the scheduler starts -static std::deque g_to_sleep; -static atomic_t g_scheduler_ready = false; -static atomic_t s_yield_frequency = 0; -static atomic_t s_max_allowed_yield_tsc = 0; -static u64 s_last_yield_tsc = 0; -atomic_t g_lv2_preempts_taken = 0; - -namespace cpu_counter -{ - void remove(cpu_thread*) noexcept; -} - -std::string lv2_obj::name64(u64 name_u64) -{ - const auto ptr = reinterpret_cast(&name_u64); - - // NTS string, ignore invalid/newline characters - // Example: "lv2\n\0tx" will be printed as "lv2" - std::string str{ptr, std::find(ptr, ptr + 7, '\0')}; - str.erase(std::remove_if(str.begin(), str.end(), [](uchar c) - { - return !std::isprint(c); - }), - str.end()); - - return str; -} - -bool lv2_obj::sleep(cpu_thread& cpu, const u64 timeout) -{ - // Should already be performed when using this flag - if (!g_postpone_notify_barrier) - { - prepare_for_sleep(cpu); - } - - if (cpu.get_class() == thread_class::ppu) - { - if (u32 addr = static_cast(cpu).res_notify) - { - static_cast(cpu).res_notify = 0; - - if (static_cast(cpu).res_notify_time != vm::reservation_notifier_count_index(addr).second) - { - // Ignore outdated notification request - } - else if (auto it = std::find(g_to_notify, std::end(g_to_notify), std::add_pointer_t{}); it != std::end(g_to_notify)) - { - *it++ = vm::reservation_notifier_notify(addr, true); - - if (it < std::end(g_to_notify)) - { - // Null-terminate the list if it ends before last slot - *it = nullptr; - } - } - else - { - vm::reservation_notifier_notify(addr); - } - } - } - - bool result = false; - const u64 current_time = get_guest_system_time(); - { - std::lock_guard lock{g_mutex}; - result = sleep_unlocked(cpu, timeout, current_time); - - if (!g_to_awake.empty()) - { - // Schedule pending entries - awake_unlocked({}); - } - - schedule_all(current_time); - } - - if (!g_postpone_notify_barrier) - { - notify_all(); - } - - g_to_awake.clear(); - return result; -} - -bool lv2_obj::awake(cpu_thread* thread, s32 prio) -{ - if (ppu_thread* ppu = cpu_thread::get_current()) - { - if (u32 addr = ppu->res_notify) - { - ppu->res_notify = 0; - - if (ppu->res_notify_time != vm::reservation_notifier_count_index(addr).second) - { - // Ignore outdated notification request - } - else if (auto it = std::find(g_to_notify, std::end(g_to_notify), std::add_pointer_t{}); it != std::end(g_to_notify)) - { - *it++ = vm::reservation_notifier_notify(addr, true); - - if (it < std::end(g_to_notify)) - { - // Null-terminate the list if it ends before last slot - *it = nullptr; - } - } - else - { - vm::reservation_notifier_notify(addr); - } - } - } - - bool result = false; - { - std::lock_guard lock(g_mutex); - result = awake_unlocked(thread, prio); - schedule_all(); - } - - if (result) - { - if (auto cpu = cpu_thread::get_current(); cpu && cpu->is_paused()) - { - vm::temporary_unlock(); - } - } - - if (!g_postpone_notify_barrier) - { - notify_all(); - } - - return result; -} - -bool lv2_obj::yield(cpu_thread& thread) -{ - if (auto ppu = thread.try_get()) - { - ppu->raddr = 0; // Clear reservation - - if (!atomic_storage::load(ppu->next_ppu)) - { - // Nothing to do - return false; - } - } - - return awake(&thread, yield_cmd); -} - -bool lv2_obj::sleep_unlocked(cpu_thread& thread, u64 timeout, u64 current_time) -{ - const u64 start_time = current_time; - - auto on_to_sleep_update = [&]() - { - if (g_to_sleep.size() > 5u) - { - ppu_log.warning("Threads (%d)", g_to_sleep.size()); - } - else if (!g_to_sleep.empty()) - { - // In case there is a deadlock (PPU threads not sleeping) - // Print-out their IDs for further inspection (focus at 5 at max for now to avoid log spam) - std::string out = fmt::format("Threads (%d):", g_to_sleep.size()); - for (auto thread : g_to_sleep) - { - fmt::append(out, " 0x%x,", thread->id); - } - - out.resize(out.size() - 1); - - ppu_log.warning("%s", out); - } - else - { - ppu_log.warning("Final Thread"); - - // All threads are ready, wake threads - Emu.CallFromMainThread([] - { - if (Emu.IsStarting()) - { - // It uses lv2_obj::g_mutex, run it on main thread - Emu.FinalizeRunRequest(); - } - }); - } - }; - - bool return_val = true; - - if (auto ppu = thread.try_get()) - { - ppu_log.trace("sleep() - waiting (%zu)", g_pending); - - if (ppu->ack_suspend) - { - ppu->ack_suspend = false; - g_pending--; - } - - if (std::exchange(ppu->cancel_sleep, 0) == 2) - { - // Signal that the underlying LV2 operation has been cancelled and replaced with a short yield - return_val = false; - } - - const auto [_, ok] = ppu->state.fetch_op([&](bs_t& val) - { - if (!(val & cpu_flag::signal)) - { - val += cpu_flag::suspend; - - // Flag used for forced timeout notification - ensure(!timeout || !(val & cpu_flag::notify)); - return true; - } - - return false; - }); - - if (!ok) - { - ppu_log.fatal("sleep() failed (signaled) (%s)", ppu->current_function); - return false; - } - - // Find and remove the thread - if (!unqueue(g_ppu, ppu, &ppu_thread::next_ppu)) - { - if (auto it = std::find(g_to_sleep.begin(), g_to_sleep.end(), ppu); it != g_to_sleep.end()) - { - g_to_sleep.erase(it); - ppu->start_time = start_time; - on_to_sleep_update(); - return true; - } - - // Already sleeping - ppu_log.trace("sleep(): called on already sleeping thread."); - return false; - } - - ppu->raddr = 0; // Clear reservation - ppu->start_time = start_time; - ppu->end_time = timeout ? start_time + std::min(timeout, ~start_time) : u64{umax}; - } - else if (auto spu = thread.try_get()) - { - if (auto it = std::find(g_to_sleep.begin(), g_to_sleep.end(), spu); it != g_to_sleep.end()) - { - g_to_sleep.erase(it); - on_to_sleep_update(); - return true; - } - - return false; - } - - if (timeout) - { - const u64 wait_until = start_time + std::min(timeout, ~start_time); - - // Register timeout if necessary - for (auto it = g_waiting.cbegin(), end = g_waiting.cend();; it++) - { - if (it == end || it->first > wait_until) - { - g_waiting.emplace(it, wait_until, &thread); - break; - } - } - } - - return return_val; -} - -bool lv2_obj::awake_unlocked(cpu_thread* cpu, s32 prio) -{ - // Check thread type - AUDIT(!cpu || cpu->get_class() == thread_class::ppu); - - bool push_first = false; - - switch (prio) - { - default: - { - // Priority set - auto set_prio = [](atomic_t& prio, s32 value, bool increment_order_last, bool increment_order_first) - { - s64 tag = 0; - - if (increment_order_first || increment_order_last) - { - tag = ++g_priority_order_tag; - } - - prio.atomic_op([&](ppu_thread::ppu_prio_t& prio) - { - prio.prio = value; - - if (increment_order_first) - { - prio.order = ~tag; - } - else if (increment_order_last) - { - prio.order = tag; - } - }); - }; - - const s64 old_prio = static_cast(cpu)->prio.load().prio; - - // If priority is the same, push ONPROC/RUNNABLE thread to the back of the priority list if it is not the current thread - if (old_prio == prio && cpu == cpu_thread::get_current()) - { - set_prio(static_cast(cpu)->prio, prio, false, false); - return true; - } - - if (!unqueue(g_ppu, static_cast(cpu), &ppu_thread::next_ppu)) - { - set_prio(static_cast(cpu)->prio, prio, old_prio > prio, old_prio < prio); - return true; - } - - set_prio(static_cast(cpu)->prio, prio, false, false); - break; - } - case yield_cmd: - { - usz i = 0; - - // Yield command - for (auto ppu_next = &g_ppu;; i++) - { - const auto ppu = +*ppu_next; - - if (!ppu) - { - return false; - } - - if (ppu == cpu) - { - auto ppu2 = ppu->next_ppu; - - if (!ppu2 || ppu2->prio.load().prio != ppu->prio.load().prio) - { - // Empty 'same prio' threads list - return false; - } - - for (i++;; i++) - { - const auto next = ppu2->next_ppu; - - if (!next || next->prio.load().prio != ppu->prio.load().prio) - { - break; - } - - ppu2 = next; - } - - // Rotate current thread to the last position of the 'same prio' threads list - // Exchange forward pointers - *ppu_next = std::exchange(ppu->next_ppu, std::exchange(ppu2->next_ppu, ppu)); - - if (i < g_cfg.core.ppu_threads + 0u) - { - // Threads were rotated, but no context switch was made - return false; - } - - ppu->start_time = get_guest_system_time(); - break; - } - - ppu_next = &ppu->next_ppu; - } - - break; - } - case enqueue_cmd: - { - break; - } - } - - const auto emplace_thread = [push_first](cpu_thread* const cpu) - { - for (auto it = &g_ppu;;) - { - const auto next = +*it; - - if (next == cpu) - { - ppu_log.trace("sleep() - suspended (p=%zu)", g_pending); - - if (static_cast(cpu)->cancel_sleep == 1) - { - // The next sleep call of the thread is cancelled - static_cast(cpu)->cancel_sleep = 2; - } - - return false; - } - - // Use priority, also preserve FIFO order - if (!next || (push_first ? next->prio.load().prio >= static_cast(cpu)->prio.load().prio : next->prio.load().prio > static_cast(cpu)->prio.load().prio)) - { - atomic_storage::release(static_cast(cpu)->next_ppu, next); - atomic_storage::release(*it, static_cast(cpu)); - break; - } - - it = &next->next_ppu; - } - - // Unregister timeout if necessary - for (auto it = g_waiting.cbegin(), end = g_waiting.cend(); it != end; it++) - { - if (it->second == cpu) - { - g_waiting.erase(it); - break; - } - } - - ppu_log.trace("awake(): %s", cpu->id); - return true; - }; - - // Yield changed the queue before - bool changed_queue = prio == yield_cmd; - - s32 lowest_new_priority = smax; - const bool has_free_hw_thread_space = count_non_sleeping_threads().onproc_count < g_cfg.core.ppu_threads + 0u; - - if (cpu && prio != yield_cmd) - { - // Emplace current thread - if (emplace_thread(cpu)) - { - changed_queue = true; - lowest_new_priority = std::min(static_cast(cpu)->prio.load().prio, lowest_new_priority); - } - } - else - for (const auto _cpu : g_to_awake) - { - // Emplace threads from list - if (emplace_thread(_cpu)) - { - changed_queue = true; - lowest_new_priority = std::min(static_cast(_cpu)->prio.load().prio, lowest_new_priority); - } - } - - auto target = +g_ppu; - usz i = 0; - - // Suspend threads if necessary - for (usz thread_count = g_cfg.core.ppu_threads; target; target = target->next_ppu, i++) - { - if (i >= thread_count && cpu_flag::suspend - target->state) - { - ppu_log.trace("suspend(): %s", target->id); - target->ack_suspend = true; - g_pending++; - ensure(!target->state.test_and_set(cpu_flag::suspend)); - - if (is_paused(target->state - cpu_flag::suspend)) - { - target->state.notify_one(); - } - } - } - - const auto current_ppu = cpu_thread::get_current(); - - // Remove pending if necessary - if (current_ppu) - { - if (std::exchange(current_ppu->ack_suspend, false)) - { - ensure(g_pending)--; - } - } - - // In real PS3 (it seems), when a thread with a higher priority than the caller is signaled and - - // - that there is available space on the running queue for the other hardware thread to start - // It prioritizes signaled thread - caller's hardware thread switches instantly to the new thread code - // While signaling to the other hardware thread to execute the caller's code. - // Resulting in a delay to the caller after such thread is signaled - - if (current_ppu && changed_queue && has_free_hw_thread_space) - { - if (current_ppu->prio.load().prio > lowest_new_priority) - { - const bool is_create_thread = current_ppu->gpr[11] == 0x35; - - // When not being set to All timers - activate only for sys_ppu_thread_start - if (is_create_thread || g_cfg.core.sleep_timers_accuracy == sleep_timers_accuracy_level::_all_timers) - { - if (!current_ppu->state.test_and_set(cpu_flag::yield) || current_ppu->hw_sleep_time != 0) - { - current_ppu->hw_sleep_time += (is_create_thread ? 51 : 35); - } - else - { - current_ppu->hw_sleep_time = 30000; // In addition to another flag's use (TODO: Refactor and clean this) - } - } - } - } - - return changed_queue; -} - -void lv2_obj::cleanup() -{ - g_ppu = nullptr; - g_scheduler_ready = false; - g_to_sleep.clear(); - g_waiting.clear(); - g_pending = 0; - s_yield_frequency = 0; -} - -void lv2_obj::schedule_all(u64 current_time) -{ - auto it = std::find(g_to_notify, std::end(g_to_notify), std::add_pointer_t{}); - - if (!g_pending && g_scheduler_ready) - { - auto target = +g_ppu; - - // Wake up threads - for (usz x = g_cfg.core.ppu_threads; target && x; target = target->next_ppu, x--) - { - if (target->state & cpu_flag::suspend) - { - ppu_log.trace("schedule(): %s", target->id); - - // Remove yield if it was sleeping until now - const bs_t remove_yield = target->start_time == 0 ? +cpu_flag::suspend : (cpu_flag::yield + cpu_flag::preempt); - - target->start_time = 0; - - if ((target->state.fetch_op(AOFN(x += cpu_flag::signal, x -= cpu_flag::suspend, x -= remove_yield, void())) & (cpu_flag::wait + cpu_flag::signal)) != cpu_flag::wait) - { - continue; - } - - if (it == std::end(g_to_notify)) - { - // Out of notification slots, notify locally (resizable container is not worth it) - target->state.notify_one(); - } - else - { - *it++ = &target->state; - } - } - } - } - - // Check registered timeouts - while (!g_waiting.empty()) - { - const auto pair = &g_waiting.front(); - - if (!current_time) - { - current_time = get_guest_system_time(); - } - - if (pair->first <= current_time) - { - const auto target = pair->second; - g_waiting.pop_front(); - - if (target != cpu_thread::get_current()) - { - // Change cpu_thread::state for the lightweight notification to work - ensure(!target->state.test_and_set(cpu_flag::notify)); - - // Otherwise notify it to wake itself - if (it == std::end(g_to_notify)) - { - // Out of notification slots, notify locally (resizable container is not worth it) - target->state.notify_one(); - } - else - { - *it++ = &target->state; - } - } - } - else - { - // The list is sorted so assume no more timeouts - break; - } - } - - if (it < std::end(g_to_notify)) - { - // Null-terminate the list if it ends before last slot - *it = nullptr; - } - - if (const u64 freq = s_yield_frequency) - { - const u64 tsc = utils::get_tsc(); - const u64 last_tsc = s_last_yield_tsc; - - if (tsc >= last_tsc && tsc <= s_max_allowed_yield_tsc && tsc - last_tsc >= freq) - { - auto target = +g_ppu; - cpu_thread* cpu = nullptr; - - for (usz x = g_cfg.core.ppu_threads;; target = target->next_ppu, x--) - { - if (!target || !x) - { - if (g_ppu && cpu_flag::preempt - g_ppu->state) - { - // Don't be picky, pick up any running PPU thread even it has a wait flag - cpu = g_ppu; - } - // TODO: If this case is common enough it may be valuable to iterate over all CPU threads to find a perfect candidate (one without a wait or suspend flag) - else if (auto current = cpu_thread::get_current(); current && cpu_flag::suspend - current->state) - { - // May be an SPU or RSX thread, use them as a last resort - cpu = current; - } - - break; - } - - if (target->state.none_of(cpu_flag::preempt + cpu_flag::wait)) - { - cpu = target; - break; - } - } - - if (cpu && cpu_flag::preempt - cpu->state && !cpu->state.test_and_set(cpu_flag::preempt)) - { - s_last_yield_tsc = tsc; - g_lv2_preempts_taken.release(g_lv2_preempts_taken.load() + 1); // Has a minor race but performance is more important - rsx::set_rsx_yield_flag(); - } - } - } -} - -void lv2_obj::make_scheduler_ready() -{ - g_scheduler_ready.release(true); - lv2_obj::awake_all(); -} - -std::pair lv2_obj::ppu_state(ppu_thread* ppu, bool lock_idm, bool lock_lv2) -{ - std::optional opt_lock[2]; - - if (lock_idm) - { - opt_lock[0].emplace(id_manager::g_mutex); - } - - if (!Emu.IsReady() ? ppu->state.all_of(cpu_flag::stop) : ppu->stop_flag_removal_protection) - { - return {PPU_THREAD_STATUS_IDLE, 0}; - } - - switch (ppu->joiner) - { - case ppu_join_status::zombie: return {PPU_THREAD_STATUS_ZOMBIE, 0}; - case ppu_join_status::exited: return {PPU_THREAD_STATUS_DELETED, 0}; - default: break; - } - - if (lock_lv2) - { - opt_lock[1].emplace(lv2_obj::g_mutex); - } - - u32 pos = umax; - u32 i = 0; - - for (auto target = +g_ppu; target; target = target->next_ppu, i++) - { - if (target == ppu) - { - pos = i; - break; - } - } - - if (pos == umax) - { - if (!ppu->interrupt_thread_executing) - { - return {PPU_THREAD_STATUS_STOP, 0}; - } - - return {PPU_THREAD_STATUS_SLEEP, 0}; - } - - if (pos >= g_cfg.core.ppu_threads + 0u) - { - return {PPU_THREAD_STATUS_RUNNABLE, pos}; - } - - return {PPU_THREAD_STATUS_ONPROC, pos}; -} - -void lv2_obj::set_future_sleep(cpu_thread* cpu) -{ - g_to_sleep.emplace_back(cpu); -} - -bool lv2_obj::is_scheduler_ready() -{ - reader_lock lock(g_mutex); - return g_to_sleep.empty(); -} - -ppu_non_sleeping_count_t lv2_obj::count_non_sleeping_threads() -{ - ppu_non_sleeping_count_t total{}; - - auto target = atomic_storage::load(g_ppu); - - for (usz thread_count = g_cfg.core.ppu_threads; target; target = atomic_storage::load(target->next_ppu)) - { - if (total.onproc_count == thread_count) - { - total.has_running = true; - break; - } - - total.onproc_count++; - } - - return total; -} - -void lv2_obj::set_yield_frequency(u64 freq, u64 max_allowed_tsc) -{ - s_yield_frequency.release(freq); - s_max_allowed_yield_tsc.release(max_allowed_tsc); - g_lv2_preempts_taken.release(0); -} - -#if defined(_MSC_VER) -#define mwaitx_func -#define waitpkg_func -#else -#define mwaitx_func __attribute__((__target__("mwaitx"))) -#define waitpkg_func __attribute__((__target__("waitpkg"))) -#endif - -#if defined(ARCH_X64) -// Waits for a number of TSC clock cycles in power optimized state -// Cstate is represented in bits [7:4]+1 cstate. So C0 requires bits [7:4] to be set to 0xf, C1 requires bits [7:4] to be set to 0. -mwaitx_func static void __mwaitx(u32 cycles, u32 cstate) -{ - constexpr u32 timer_enable = 0x2; - - // monitorx will wake if the cache line is written to. We don't want this, so place the monitor value on it's own cache line. - alignas(64) u64 monitor_var{}; - _mm_monitorx(&monitor_var, 0, 0); - _mm_mwaitx(timer_enable, cstate, cycles); -} - -// First bit indicates cstate, 0x0 for C.02 state (lower power) or 0x1 for C.01 state (higher power) -waitpkg_func static void __tpause(u32 cycles, u32 cstate) -{ - const u64 tsc = utils::get_tsc() + cycles; - _tpause(cstate, tsc); -} -#endif - -bool lv2_obj::wait_timeout(u64 usec, ppu_thread* cpu, bool scale, bool is_usleep) -{ - static_assert(u64{umax} / max_timeout >= 100, "max timeout is not valid for scaling"); - - const u64 start_time = get_system_time(); - - if (cpu) - { - if (u64 end_time = cpu->end_time; end_time != umax) - { - const u64 guest_start = get_guest_system_time(start_time); - - if (end_time <= guest_start) - { - return true; - } - - usec = end_time - guest_start; - scale = true; - } - } - - if (scale) - { - // Scale time - usec = std::min(usec, u64{umax} / 100) * 100 / g_cfg.core.clocks_scale; - } - - // Clamp - usec = std::min(usec, max_timeout); - - u64 passed = 0; - - atomic_bs_t dummy{}; - const auto& state = cpu ? cpu->state : dummy; - auto old_state = +state; - - auto wait_for = [&](u64 timeout) - { - thread_ctrl::wait_on(state, old_state, timeout); - }; - - for (;; old_state = state) - { - if (old_state & cpu_flag::notify) - { - // Timeout notification has been forced - break; - } - - if (old_state & cpu_flag::signal) - { - return false; - } - - if (::is_stopped(old_state) || thread_ctrl::state() == thread_state::aborting) - { - return passed >= usec; - } - - if (passed >= usec) - { - break; - } - - u64 remaining = usec - passed; -#ifdef __linux__ - // NOTE: Assumption that timer initialization has succeeded - constexpr u64 host_min_quantum = 10; -#else - // Host scheduler quantum for windows (worst case) - // NOTE: On ps3 this function has very high accuracy - constexpr u64 host_min_quantum = 500; -#endif - // TODO: Tune for other non windows operating sytems - - if (g_cfg.core.sleep_timers_accuracy < (is_usleep ? sleep_timers_accuracy_level::_usleep : sleep_timers_accuracy_level::_all_timers)) - { - wait_for(remaining); - } - else - { - if (remaining > host_min_quantum) - { -#ifdef __linux__ - // With timerslack set low, Linux is precise for all values above - wait_for(remaining); -#else - // Wait on multiple of min quantum for large durations to avoid overloading low thread cpus - wait_for(remaining - (remaining % host_min_quantum)); -#endif - } - // TODO: Determine best value for yield delay -#if defined(ARCH_X64) - else if (utils::has_appropriate_um_wait()) - { - const u32 us_in_tsc_clocks = ::narrow(remaining * (utils::get_tsc_freq() / 1000000ULL)); - - if (utils::has_waitpkg()) - { - __tpause(us_in_tsc_clocks, 0x1); - } - else - { - __mwaitx(us_in_tsc_clocks, 0xf0); - } - } -#endif - else - { - // Try yielding. May cause long wake latency but helps weaker CPUs a lot by alleviating resource pressure - std::this_thread::yield(); - } - } - - passed = get_system_time() - start_time; - } - - return true; -} - -void lv2_obj::prepare_for_sleep(cpu_thread& cpu) -{ - vm::temporary_unlock(cpu); - cpu_counter::remove(&cpu); -} - -void lv2_obj::notify_all() noexcept -{ - for (auto cpu : g_to_notify) - { - if (!cpu) - { - break; - } - - if (cpu != &g_to_notify) - { - const auto res_start = vm::reservation_notifier(0).second; - const auto res_end = vm::reservation_notifier(umax).second; - - if (cpu >= res_start && cpu <= res_end) - { - atomic_wait_engine::notify_all(cpu); - } - else - { - // Note: by the time of notification the thread could have been deallocated which is why the direct function is used - atomic_wait_engine::notify_one(cpu); - } - } - } - - g_to_notify[0] = nullptr; - g_postpone_notify_barrier = false; - - const auto cpu = cpu_thread::get_current(); - - if (!cpu) - { - return; - } - - if (cpu->get_class() != thread_class::spu && cpu->state.none_of(cpu_flag::suspend)) - { - return; - } - - std::optional lock; - - constexpr usz total_waiters = std::size(spu_thread::g_spu_waiters_by_value); - - u32 notifies[total_waiters]{}; - - // There may be 6 waiters, but checking them all may be performance expensive - // Instead, check 2 at max, but use the CPU ID index to tell which index to start checking so the work would be distributed across all threads - - atomic_t* range_lock = nullptr; - - for (usz i = 0, checked = 0; checked < 3 && i < total_waiters; i++) - { - auto& waiter = spu_thread::g_spu_waiters_by_value[(i + cpu->id) % total_waiters]; - const u64 value = waiter.load(); - u32 raddr = static_cast(value) & -128; - - if (vm::check_addr(raddr)) - { - if (((raddr >> 28) < 2 || (raddr >> 28) == 0xd)) - { - checked++; - - if (compute_rdata_hash32(*vm::get_super_ptr(raddr)) != static_cast(value >> 32)) - { - // Clear address to avoid a race, keep waiter counter - if (waiter.fetch_op([&](u64& x) - { - if ((x & -128) == (value & -128)) - { - x &= 127; - return true; - } - - return false; - }) - .second) - { - notifies[i] = raddr; - } - } - - continue; - } - - if (!range_lock) - { - range_lock = vm::alloc_range_lock(); - } - - checked++; - - if (spu_thread::reservation_check(raddr, static_cast(value >> 32), range_lock)) - { - // Clear address to avoid a race, keep waiter counter - if (waiter.fetch_op([&](u64& x) - { - if ((x & -128) == (value & -128)) - { - x &= 127; - return true; - } - - return false; - }) - .second) - { - notifies[i] = raddr; - } - } - } - } - - if (range_lock) - { - vm::free_range_lock(range_lock); - } - - for (u32 addr : notifies) - { - if (addr) - { - vm::reservation_notifier_notify(addr); - } - } -} diff --git a/rpcs3/Emu/Cell/lv2/sys_bdemu.cpp b/rpcs3/Emu/Cell/lv2/sys_bdemu.cpp deleted file mode 100644 index 51d8e4884..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_bdemu.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include "stdafx.h" - -#include "Emu/Cell/ErrorCodes.h" - -#include "sys_bdemu.h" - -LOG_CHANNEL(sys_bdemu); - -error_code sys_bdemu_send_command(u64 cmd, u64 a2, u64 a3, vm::ptr buf, u64 buf_len) -{ - sys_bdemu.todo("sys_bdemu_send_command(cmd=0%llx, a2=0x%x, a3=0x%x, buf=0x%x, buf_len=0x%x)", cmd, a2, a3, buf, buf_len); - - return CELL_OK; -} diff --git a/rpcs3/Emu/Cell/lv2/sys_btsetting.cpp b/rpcs3/Emu/Cell/lv2/sys_btsetting.cpp deleted file mode 100644 index b5ecd1d18..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_btsetting.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include "stdafx.h" - -#include "sys_btsetting.h" -#include "Emu/Cell/ErrorCodes.h" - -LOG_CHANNEL(sys_btsetting); - -error_code sys_btsetting_if(u64 cmd, vm::ptr msg) -{ - sys_btsetting.todo("sys_btsetting_if(cmd=0x%llx, msg=*0x%x)", cmd, msg); - - return CELL_OK; -} diff --git a/rpcs3/Emu/Cell/lv2/sys_cond.cpp b/rpcs3/Emu/Cell/lv2/sys_cond.cpp deleted file mode 100644 index 0d43554c8..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_cond.cpp +++ /dev/null @@ -1,581 +0,0 @@ -#include "stdafx.h" - -#include "util/serialization.hpp" -#include "Emu/IdManager.h" -#include "Emu/System.h" - -#include "Emu/Cell/ErrorCodes.h" -#include "Emu/Cell/PPUThread.h" - -#include "sys_cond.h" - -#include "util/asm.hpp" - -LOG_CHANNEL(sys_cond); - -lv2_cond::lv2_cond(utils::serial& ar) noexcept - : key(ar), name(ar), mtx_id(ar), mutex(idm::check_unlocked(mtx_id)), _mutex(idm::get_unlocked(mtx_id)) // May be nullptr -{ -} - -lv2_cond::lv2_cond(u64 key, u64 name, u32 mtx_id, shared_ptr mutex0) noexcept - : key(key), name(name), mtx_id(mtx_id), mutex(static_cast(mutex0.get())), _mutex(mutex0) -{ -} - -CellError lv2_cond::on_id_create() -{ - exists++; - - static auto do_it = [](lv2_cond* _this) -> CellError - { - if (lv2_obj::check(_this->mutex)) - { - _this->mutex->cond_count++; - return {}; - } - - // Mutex has been destroyed, cannot create conditional variable - return CELL_ESRCH; - }; - - if (mutex) - { - return do_it(this); - } - - ensure(!!Emu.DeserialManager()); - - Emu.PostponeInitCode([this]() - { - if (!mutex) - { - _mutex = static_cast>(ensure(idm::get_unlocked(mtx_id))); - } - - // Defer function - ensure(CellError{} == do_it(this)); - }); - - return {}; -} - -std::function lv2_cond::load(utils::serial& ar) -{ - return load_func(make_shared(exact_t(ar))); -} - -void lv2_cond::save(utils::serial& ar) -{ - ar(key, name, mtx_id); -} - -error_code sys_cond_create(ppu_thread& ppu, vm::ptr cond_id, u32 mutex_id, vm::ptr attr) -{ - ppu.state += cpu_flag::wait; - - sys_cond.trace("sys_cond_create(cond_id=*0x%x, mutex_id=0x%x, attr=*0x%x)", cond_id, mutex_id, attr); - - auto mutex = idm::get_unlocked(mutex_id); - - if (!mutex) - { - return CELL_ESRCH; - } - - const auto _attr = *attr; - - const u64 ipc_key = lv2_obj::get_key(_attr); - - if (ipc_key) - { - sys_cond.warning("sys_cond_create(cond_id=*0x%x, attr=*0x%x): IPC=0x%016x", cond_id, attr, ipc_key); - } - - if (const auto error = lv2_obj::create(_attr.pshared, ipc_key, _attr.flags, [&] - { - return make_single( - ipc_key, - _attr.name_u64, - mutex_id, - std::move(mutex)); - })) - { - return error; - } - - ppu.check_state(); - *cond_id = idm::last_id(); - return CELL_OK; -} - -error_code sys_cond_destroy(ppu_thread& ppu, u32 cond_id) -{ - ppu.state += cpu_flag::wait; - - sys_cond.trace("sys_cond_destroy(cond_id=0x%x)", cond_id); - - const auto cond = idm::withdraw(cond_id, [&](lv2_cond& cond) -> CellError - { - std::lock_guard lock(cond.mutex->mutex); - - if (atomic_storage::load(cond.sq)) - { - return CELL_EBUSY; - } - - cond.mutex->cond_count--; - lv2_obj::on_id_destroy(cond, cond.key); - return {}; - }); - - if (!cond) - { - return CELL_ESRCH; - } - - if (cond->key) - { - sys_cond.warning("sys_cond_destroy(cond_id=0x%x): IPC=0x%016x", cond_id, cond->key); - } - - if (cond.ret) - { - return cond.ret; - } - - return CELL_OK; -} - -error_code sys_cond_signal(ppu_thread& ppu, u32 cond_id) -{ - ppu.state += cpu_flag::wait; - - sys_cond.trace("sys_cond_signal(cond_id=0x%x)", cond_id); - - while (true) - { - if (ppu.test_stopped()) - { - ppu.state += cpu_flag::again; - return {}; - } - - bool finished = true; - - ppu.state += cpu_flag::wait; - - const auto cond = idm::check(cond_id, [&, notify = lv2_obj::notify_all_t()](lv2_cond& cond) - { - if (atomic_storage::load(cond.sq)) - { - std::lock_guard lock(cond.mutex->mutex); - - if (ppu.state & cpu_flag::suspend) - { - // Test if another signal caused the current thread to be suspended, in which case it needs to wait until the thread wakes up (otherwise the signal may cause unexpected results) - finished = false; - return; - } - - if (const auto cpu = cond.schedule(cond.sq, cond.mutex->protocol)) - { - if (static_cast(cpu)->state & cpu_flag::again) - { - ppu.state += cpu_flag::again; - return; - } - - // TODO: Is EBUSY returned after reqeueing, on sys_cond_destroy? - - if (cond.mutex->try_own(*cpu)) - { - cond.awake(cpu); - } - } - } - else - { - cond.mutex->mutex.lock_unlock(); - - if (ppu.state & cpu_flag::suspend) - { - finished = false; - } - } - }); - - if (!finished) - { - continue; - } - - if (!cond) - { - return CELL_ESRCH; - } - - return CELL_OK; - } -} - -error_code sys_cond_signal_all(ppu_thread& ppu, u32 cond_id) -{ - ppu.state += cpu_flag::wait; - - sys_cond.trace("sys_cond_signal_all(cond_id=0x%x)", cond_id); - - while (true) - { - if (ppu.test_stopped()) - { - ppu.state += cpu_flag::again; - return {}; - } - - bool finished = true; - - ppu.state += cpu_flag::wait; - - const auto cond = idm::check(cond_id, [&, notify = lv2_obj::notify_all_t()](lv2_cond& cond) - { - if (atomic_storage::load(cond.sq)) - { - std::lock_guard lock(cond.mutex->mutex); - - if (ppu.state & cpu_flag::suspend) - { - // Test if another signal caused the current thread to be suspended, in which case it needs to wait until the thread wakes up (otherwise the signal may cause unexpected results) - finished = false; - return; - } - - for (auto cpu = +cond.sq; cpu; cpu = cpu->next_cpu) - { - if (cpu->state & cpu_flag::again) - { - ppu.state += cpu_flag::again; - return; - } - } - - cpu_thread* result = nullptr; - auto sq = cond.sq; - atomic_storage::release(cond.sq, nullptr); - - while (const auto cpu = cond.schedule(sq, SYS_SYNC_PRIORITY)) - { - if (cond.mutex->try_own(*cpu)) - { - ensure(!std::exchange(result, cpu)); - } - } - - if (result) - { - cond.awake(result); - } - } - else - { - cond.mutex->mutex.lock_unlock(); - - if (ppu.state & cpu_flag::suspend) - { - finished = false; - } - } - }); - - if (!finished) - { - continue; - } - - if (!cond) - { - return CELL_ESRCH; - } - - return CELL_OK; - } -} - -error_code sys_cond_signal_to(ppu_thread& ppu, u32 cond_id, u32 thread_id) -{ - ppu.state += cpu_flag::wait; - - sys_cond.trace("sys_cond_signal_to(cond_id=0x%x, thread_id=0x%x)", cond_id, thread_id); - - while (true) - { - if (ppu.test_stopped()) - { - ppu.state += cpu_flag::again; - return {}; - } - - bool finished = true; - - ppu.state += cpu_flag::wait; - - const auto cond = idm::check(cond_id, [&, notify = lv2_obj::notify_all_t()](lv2_cond& cond) - { - if (!idm::check_unlocked>(thread_id)) - { - return -1; - } - - if (atomic_storage::load(cond.sq)) - { - std::lock_guard lock(cond.mutex->mutex); - - if (ppu.state & cpu_flag::suspend) - { - // Test if another signal caused the current thread to be suspended, in which case it needs to wait until the thread wakes up (otherwise the signal may cause unexpected results) - finished = false; - return 0; - } - - for (auto cpu = +cond.sq; cpu; cpu = cpu->next_cpu) - { - if (cpu->id == thread_id) - { - if (static_cast(cpu)->state & cpu_flag::again) - { - ppu.state += cpu_flag::again; - return 0; - } - - ensure(cond.unqueue(cond.sq, cpu)); - - if (cond.mutex->try_own(*cpu)) - { - cond.awake(cpu); - } - - return 1; - } - } - } - else - { - cond.mutex->mutex.lock_unlock(); - - if (ppu.state & cpu_flag::suspend) - { - finished = false; - return 0; - } - } - - return 0; - }); - - if (!finished) - { - continue; - } - - if (!cond || cond.ret == -1) - { - return CELL_ESRCH; - } - - if (!cond.ret) - { - return not_an_error(CELL_EPERM); - } - - return CELL_OK; - } -} - -error_code sys_cond_wait(ppu_thread& ppu, u32 cond_id, u64 timeout) -{ - ppu.state += cpu_flag::wait; - - sys_cond.trace("sys_cond_wait(cond_id=0x%x, timeout=%lld)", cond_id, timeout); - - // Further function result - ppu.gpr[3] = CELL_OK; - - auto& sstate = *ppu.optional_savestate_state; - - const auto cond = idm::get(cond_id, [&, notify = lv2_obj::notify_all_t()](lv2_cond& cond) -> s64 - { - if (!ppu.loaded_from_savestate && atomic_storage::load(cond.mutex->control.raw().owner) != ppu.id) - { - return -1; - } - - lv2_obj::prepare_for_sleep(ppu); - - std::lock_guard lock(cond.mutex->mutex); - - const u64 syscall_state = sstate.try_read().second; - sstate.clear(); - - if (ppu.loaded_from_savestate) - { - if (syscall_state & 1) - { - // Mutex sleep - ensure(!cond.mutex->try_own(ppu)); - } - else - { - lv2_obj::emplace(cond.sq, &ppu); - } - - cond.sleep(ppu, timeout); - return static_cast(syscall_state >> 32); - } - - // Register waiter - lv2_obj::emplace(cond.sq, &ppu); - - // Unlock the mutex - const u32 count = cond.mutex->lock_count.exchange(0); - - if (const auto cpu = cond.mutex->reown()) - { - if (cpu->state & cpu_flag::again) - { - ensure(cond.unqueue(cond.sq, &ppu)); - ppu.state += cpu_flag::again; - return 0; - } - - cond.mutex->append(cpu); - } - - // Sleep current thread and schedule mutex waiter - cond.sleep(ppu, timeout); - - // Save the recursive value - return count; - }); - - if (!cond) - { - return CELL_ESRCH; - } - - if (ppu.state & cpu_flag::again) - { - return {}; - } - - if (cond.ret < 0) - { - return CELL_EPERM; - } - - while (auto state = +ppu.state) - { - if (state & cpu_flag::signal && ppu.state.test_and_reset(cpu_flag::signal)) - { - break; - } - - if (is_stopped(state)) - { - std::lock_guard lock(cond->mutex->mutex); - - bool mutex_sleep = false; - bool cond_sleep = false; - - for (auto cpu = atomic_storage::load(cond->sq); cpu; cpu = cpu->next_cpu) - { - if (cpu == &ppu) - { - cond_sleep = true; - break; - } - } - - for (auto cpu = atomic_storage::load(cond->mutex->control.raw().sq); cpu; cpu = cpu->next_cpu) - { - if (cpu == &ppu) - { - mutex_sleep = true; - break; - } - } - - if (!cond_sleep && !mutex_sleep) - { - break; - } - - const u64 optional_syscall_state = u32{mutex_sleep} | (u64{static_cast(cond.ret)} << 32); - sstate(optional_syscall_state); - - ppu.state += cpu_flag::again; - return {}; - } - - for (usz i = 0; cpu_flag::signal - ppu.state && i < 50; i++) - { - busy_wait(500); - } - - if (ppu.state & cpu_flag::signal) - { - continue; - } - - if (timeout) - { - if (lv2_obj::wait_timeout(timeout, &ppu)) - { - const u64 start_time = ppu.start_time; - - // Wait for rescheduling - if (ppu.check_state()) - { - continue; - } - - ppu.state += cpu_flag::wait; - - std::lock_guard lock(cond->mutex->mutex); - - // Try to cancel the waiting - if (cond->unqueue(cond->sq, &ppu)) - { - // TODO: Is EBUSY returned after reqeueing, on sys_cond_destroy? - ppu.gpr[3] = CELL_ETIMEDOUT; - - // Own or requeue - if (cond->mutex->try_own(ppu)) - { - break; - } - } - else if (atomic_storage::load(cond->mutex->control.raw().owner) == ppu.id) - { - break; - } - - cond->mutex->sleep(ppu); - ppu.start_time = start_time; // Restore start time because awake has been called - timeout = 0; - continue; - } - } - else - { - ppu.state.wait(state); - } - } - - // Verify ownership - ensure(atomic_storage::load(cond->mutex->control.raw().owner) == ppu.id); - - // Restore the recursive value - cond->mutex->lock_count.release(static_cast(cond.ret)); - - return not_an_error(ppu.gpr[3]); -} diff --git a/rpcs3/Emu/Cell/lv2/sys_cond.h b/rpcs3/Emu/Cell/lv2/sys_cond.h deleted file mode 100644 index 60e7c24e6..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_cond.h +++ /dev/null @@ -1,51 +0,0 @@ -#pragma once - -#include "sys_sync.h" -#include "sys_mutex.h" - -struct lv2_mutex; - -struct sys_cond_attribute_t -{ - be_t pshared; - be_t flags; - be_t ipc_key; - - union - { - nse_t name_u64; - char name[sizeof(u64)]; - }; -}; - -struct lv2_cond final : lv2_obj -{ - static const u32 id_base = 0x86000000; - - const u64 key; - const u64 name; - const u32 mtx_id; - - lv2_mutex* mutex; // Associated Mutex - shared_ptr _mutex; - ppu_thread* sq{}; - - lv2_cond(u64 key, u64 name, u32 mtx_id, shared_ptr mutex0) noexcept; - - lv2_cond(utils::serial& ar) noexcept; - static std::function load(utils::serial& ar); - void save(utils::serial& ar); - - CellError on_id_create(); -}; - -class ppu_thread; - -// Syscalls - -error_code sys_cond_create(ppu_thread& ppu, vm::ptr cond_id, u32 mutex_id, vm::ptr attr); -error_code sys_cond_destroy(ppu_thread& ppu, u32 cond_id); -error_code sys_cond_wait(ppu_thread& ppu, u32 cond_id, u64 timeout); -error_code sys_cond_signal(ppu_thread& ppu, u32 cond_id); -error_code sys_cond_signal_all(ppu_thread& ppu, u32 cond_id); -error_code sys_cond_signal_to(ppu_thread& ppu, u32 cond_id, u32 thread_id); diff --git a/rpcs3/Emu/Cell/lv2/sys_config.cpp b/rpcs3/Emu/Cell/lv2/sys_config.cpp deleted file mode 100644 index 7a95fe158..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_config.cpp +++ /dev/null @@ -1,466 +0,0 @@ -#include "stdafx.h" -#include "Emu/IdManager.h" - -#include "Emu/Cell/lv2/sys_event.h" -#include "Emu/Cell/ErrorCodes.h" - -#include "sys_config.h" - -LOG_CHANNEL(sys_config); - -// Enums -template <> -void fmt_class_string::format(std::string& out, u64 id) -{ - const s64 s_id = static_cast(id); - - switch (s_id) - { - case SYS_CONFIG_SERVICE_PADMANAGER: out += "SYS_CONFIG_SERVICE_PADMANAGER"; return; - case SYS_CONFIG_SERVICE_PADMANAGER2: out += "SYS_CONFIG_SERVICE_PADMANAGER2"; return; - case SYS_CONFIG_SERVICE_USER_LIBPAD: out += "SYS_CONFIG_SERVICE_USER_LIBPAD"; return; - case SYS_CONFIG_SERVICE_USER_LIBKB: out += "SYS_CONFIG_SERVICE_USER_LIBKB"; return; - case SYS_CONFIG_SERVICE_USER_LIBMOUSE: out += "SYS_CONFIG_SERVICE_USER_LIBMOUSE"; return; - } - - if (s_id < 0) - { - fmt::append(out, "SYS_CONFIG_SERVICE_USER_%llx", id & ~(1ull << 63)); - } - else - { - fmt::append(out, "SYS_CONFIG_SERVICE_%llx", id); - } -} - -template <> -void fmt_class_string::format(std::string& out, u64 arg) -{ - format_enum(out, arg, [](auto value) - { - switch (value) - { - STR_CASE(SYS_CONFIG_SERVICE_LISTENER_ONCE); - STR_CASE(SYS_CONFIG_SERVICE_LISTENER_REPEATING); - } - - return unknown; - }); -} - -// Utilities -void dump_buffer(std::string& out, const std::vector& buffer) -{ - if (!buffer.empty()) - { - out.reserve(out.size() + buffer.size() * 2 + 1); - fmt::append(out, "0x"); - - for (u8 x : buffer) - { - fmt::append(out, "%02x", x); - } - } - else - { - fmt::append(out, "EMPTY"); - } -} - -// LV2 Config -void lv2_config::initialize() -{ - if (m_state || !m_state.compare_and_swap_test(0, 1)) - { - return; - } - - // Register padmanager service, notifying vsh that a controller is connected - static const u8 hid_info[0x1a] = { - 0x01, 0x01, // 2 unk - 0x02, 0x02, // 4 - 0x00, 0x00, // 6 - 0x00, 0x00, // 8 - 0x00, 0x00, // 10 - 0x05, 0x4c, // 12 vid - 0x02, 0x68, // 14 pid - 0x00, 0x10, // 16 unk2 - 0x91, 0x88, // 18 - 0x04, 0x00, // 20 - 0x00, 0x07, // 22 - 0x00, 0x00, // 24 - 0x00, 0x00 // 26 - }; - - // user_id for the padmanager seems to signify the controller port number, and the buffer contains some sort of HID descriptor - lv2_config_service::create(SYS_CONFIG_SERVICE_PADMANAGER, 0, 1, 0, hid_info, 0x1a)->notify(); - lv2_config_service::create(SYS_CONFIG_SERVICE_PADMANAGER2, 0, 1, 0, hid_info, 0x1a)->notify(); -} - -void lv2_config::add_service_event(shared_ptr event) -{ - std::lock_guard lock(m_mutex); - events.emplace(event->id, std::move(event)); -} - -void lv2_config::remove_service_event(u32 id) -{ - shared_ptr ptr; - - std::lock_guard lock(m_mutex); - - if (auto it = events.find(id); it != events.end()) - { - ptr = std::move(it->second); - events.erase(it); - } -} - -lv2_config_service_event& lv2_config_service_event::operator=(thread_state s) noexcept -{ - if (s == thread_state::destroying_context && !m_destroyed.exchange(true)) - { - if (auto global = g_fxo->try_get()) - { - global->remove_service_event(id); - } - } - - return *this; -} - -lv2_config_service_event::~lv2_config_service_event() noexcept -{ - operator=(thread_state::destroying_context); -} - -lv2_config::~lv2_config() noexcept -{ - for (auto& [key, event] : events) - { - if (event) - { - // Avoid collision with lv2_config_service_event destructor - event->m_destroyed = true; - } - } -} - -// LV2 Config Service Listener -bool lv2_config_service_listener::check_service(const lv2_config_service& service) const -{ - // Filter by type - if (type == SYS_CONFIG_SERVICE_LISTENER_ONCE && !service_events.empty()) - { - return false; - } - - // Filter by service ID or verbosity - if (service_id != service.id || min_verbosity > service.verbosity) - { - return false; - } - - // realhw only seems to send the pad connected events to the listeners that provided 0x01 as the first byte of their data buffer - // TODO: Figure out how this filter works more properly - if (service_id == SYS_CONFIG_SERVICE_PADMANAGER && (data.empty() || data[0] != 0x01)) - { - return false; - } - - // Event applies to this listener! - return true; -} - -bool lv2_config_service_listener::notify(const shared_ptr& event) -{ - service_events.emplace_back(event); - return event->notify(); -} - -bool lv2_config_service_listener::notify(const shared_ptr& service) -{ - if (!check_service(*service)) - return false; - - // Create service event and notify queue! - const auto event = lv2_config_service_event::create(handle, service, *this); - return notify(event); -} - -void lv2_config_service_listener::notify_all() -{ - std::vector> services; - - // Grab all events - idm::select([&](u32 /*id*/, lv2_config_service& service) - { - if (check_service(service)) - { - services.push_back(service.get_shared_ptr()); - } - }); - - // Sort services by timestamp - sort(services.begin(), services.end(), [](const shared_ptr& s1, const shared_ptr& s2) - { - return s1->timestamp < s2->timestamp; - }); - - // Notify listener (now with services in sorted order) - for (auto& service : services) - { - this->notify(service); - } -} - -// LV2 Config Service -void lv2_config_service::unregister() -{ - registered = false; - - // Notify listeners - notify(); - - // Allow this object to be destroyed by withdrawing it from the IDM - // Note that it won't be destroyed while there are service events that hold a reference to it - idm::remove(idm_id); -} - -void lv2_config_service::notify() const -{ - std::vector> listeners; - - const shared_ptr sptr = get_shared_ptr(); - - idm::select([&](u32 /*id*/, lv2_config_service_listener& listener) - { - if (listener.check_service(*sptr)) - listeners.push_back(listener.get_shared_ptr()); - }); - - for (auto& listener : listeners) - { - listener->notify(sptr); - } -} - -bool lv2_config_service_event::notify() const -{ - const auto _handle = handle; - - if (!_handle) - { - return false; - } - - // Send event - return _handle->notify(SYS_CONFIG_EVENT_SOURCE_SERVICE, (static_cast(service->is_registered()) << 32) | id, service->get_size()); -} - -// LV2 Config Service Event -void lv2_config_service_event::write(sys_config_service_event_t* dst) const -{ - const auto registered = service->is_registered(); - - dst->service_listener_handle = listener.get_id(); - dst->registered = registered; - dst->service_id = service->id; - dst->user_id = service->user_id; - - if (registered) - { - dst->verbosity = service->verbosity; - dst->padding = service->padding; - - const auto size = service->data.size(); - dst->data_size = static_cast(size); - memcpy(dst->data, service->data.data(), size); - } -} - -/* - * Syscalls - */ -error_code sys_config_open(u32 equeue_hdl, vm::ptr out_config_hdl) -{ - sys_config.trace("sys_config_open(equeue_hdl=0x%x, out_config_hdl=*0x%x)", equeue_hdl, out_config_hdl); - - // Find queue with the given ID - const auto queue = idm::get_unlocked(equeue_hdl); - if (!queue) - { - return CELL_ESRCH; - } - - // Initialize lv2_config global state - auto& global = g_fxo->get(); - if (true) - { - global.initialize(); - } - - // Create a lv2_config_handle object - const auto config = lv2_config_handle::create(std::move(queue)); - if (config) - { - *out_config_hdl = idm::last_id(); - return CELL_OK; - } - - // Failed to allocate sys_config object - return CELL_EAGAIN; -} - -error_code sys_config_close(u32 config_hdl) -{ - sys_config.trace("sys_config_close(config_hdl=0x%x)", config_hdl); - - if (!idm::remove(config_hdl)) - { - return CELL_ESRCH; - } - - return CELL_OK; -} - -error_code sys_config_get_service_event(u32 config_hdl, u32 event_id, vm::ptr dst, u64 size) -{ - sys_config.trace("sys_config_get_service_event(config_hdl=0x%x, event_id=0x%llx, dst=*0x%llx, size=0x%llx)", config_hdl, event_id, dst, size); - - // Find sys_config handle object with the given ID - const auto cfg = idm::get_unlocked(config_hdl); - if (!cfg) - { - return CELL_ESRCH; - } - - // Find service_event object - const auto event = g_fxo->get().find_event(event_id); - if (!event) - { - return CELL_ESRCH; - } - - // Check buffer fits - if (!event->check_buffer_size(size)) - { - return CELL_EAGAIN; - } - - // Write event to buffer - event->write(dst.get_ptr()); - - return CELL_OK; -} - -error_code sys_config_add_service_listener(u32 config_hdl, sys_config_service_id service_id, u64 min_verbosity, vm::ptr in, u64 size, sys_config_service_listener_type type, vm::ptr out_listener_hdl) -{ - sys_config.trace("sys_config_add_service_listener(config_hdl=0x%x, service_id=0x%llx, min_verbosity=0x%llx, in=*0x%x, size=%lld, type=0x%llx, out_listener_hdl=*0x%x)", config_hdl, service_id, min_verbosity, in, size, type, out_listener_hdl); - - // Find sys_config handle object with the given ID - auto cfg = idm::get_unlocked(config_hdl); - if (!cfg) - { - return CELL_ESRCH; - } - - // Create service listener - const auto listener = lv2_config_service_listener::create(cfg, service_id, min_verbosity, type, static_cast(in.get_ptr()), size); - if (!listener) - { - return CELL_EAGAIN; - } - - if (size > 0) - { - std::string buf_str; - dump_buffer(buf_str, listener->data); - sys_config.todo("Registered service listener for service %llx with non-zero buffer: %s", service_id, buf_str.c_str()); - } - - // Notify listener with all past events - listener->notify_all(); - - // Done! - *out_listener_hdl = listener->get_id(); - return CELL_OK; -} - -error_code sys_config_remove_service_listener(u32 config_hdl, u32 listener_hdl) -{ - sys_config.trace("sys_config_remove_service_listener(config_hdl=0x%x, listener_hdl=0x%x)", config_hdl, listener_hdl); - - // Remove listener from IDM - if (!idm::remove(listener_hdl)) - { - return CELL_ESRCH; - } - - return CELL_OK; -} - -error_code sys_config_register_service(u32 config_hdl, sys_config_service_id service_id, u64 user_id, u64 verbosity, vm::ptr data_buf, u64 size, vm::ptr out_service_hdl) -{ - sys_config.trace("sys_config_register_service(config_hdl=0x%x, service_id=0x%llx, user_id=0x%llx, verbosity=0x%llx, data_but=*0x%llx, size=%lld, out_service_hdl=*0x%llx)", config_hdl, service_id, user_id, verbosity, data_buf, size, out_service_hdl); - - // Find sys_config handle object with the given ID - const auto cfg = idm::get_unlocked(config_hdl); - if (!cfg) - { - return CELL_ESRCH; - } - - // Create service - const auto service = lv2_config_service::create(service_id, user_id, verbosity, 0, data_buf.get_ptr(), size); - if (!service) - { - return CELL_EAGAIN; - } - - // Notify all listeners - service->notify(); - - // Done! - *out_service_hdl = service->get_id(); - return CELL_OK; -} - -error_code sys_config_unregister_service(u32 config_hdl, u32 service_hdl) -{ - sys_config.trace("sys_config_unregister_service(config_hdl=0x%x, service_hdl=0x%x)", config_hdl, service_hdl); - - // Remove listener from IDM - auto service = idm::withdraw(service_hdl); - if (!service) - { - return CELL_ESRCH; - } - - // Unregister service - service->unregister(); - - // Done! - return CELL_OK; -} - -/* - * IO Events - TODO - */ -error_code sys_config_get_io_event(u32 config_hdl, u32 event_id /*?*/, vm::ptr out_buf /*?*/, u64 size /*?*/) -{ - sys_config.todo("sys_config_get_io_event(config_hdl=0x%x, event_id=0x%x, out_buf=*0x%x, size=%lld)", config_hdl, event_id, out_buf, size); - return CELL_OK; -} - -error_code sys_config_register_io_error_listener(u32 config_hdl) -{ - sys_config.todo("sys_config_register_io_error_listener(config_hdl=0x%x)", config_hdl); - return CELL_OK; -} - -error_code sys_config_unregister_io_error_listener(u32 config_hdl) -{ - sys_config.todo("sys_config_unregister_io_error_listener(config_hdl=0x%x)", config_hdl); - return CELL_OK; -} diff --git a/rpcs3/Emu/Cell/lv2/sys_config.h b/rpcs3/Emu/Cell/lv2/sys_config.h deleted file mode 100644 index 33e23393f..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_config.h +++ /dev/null @@ -1,434 +0,0 @@ -#pragma once - -#include "util/atomic.hpp" -#include "util/shared_ptr.hpp" -#include "Emu/Cell/timers.hpp" - -/* - * sys_config is a "subscription-based data storage API" - * - * It has the concept of services and listeners. Services provide data, listeners subscribe to registration/unregistration events from specific services. - * - * Services are divided into two classes: LV2 services (positive service IDs) and User services (negative service IDs). - * LV2 services seem to be implictly "available", probably constructed on-demand with internal LV2 code generating the data. An example is PadManager (service ID 0x11). - * User services may be registered through a syscall, and have negative IDs. An example is libPad (service ID 0x8000'0000'0000'0001). - * Note that user-mode *cannot* register positive service IDs. - * - * To start with, you have to get a sys_config handle by calling sys_config_open and providing an event queue. - * This event queue will be used for sys_config notifications if a subscribed config event is registered. - * - * With a sys_config handle, listeners can be added to specific services using sys_config_add_service_listener. - * This syscall returns a service listener handle, which can be used to close the listener and stop further notifications. - * Once subscribed, any matching past service registrations will be automatically sent to the supplied queue (thus the "data storage"). - * - * Services exist "implicitly", and data may be registered *onto* a service by calling sys_config_register_service. - * You can remove config events by calling sys_config_unregister_service and providing the handle returned when registering a service. - * - * If a service is registered (or unregistered) and matches any active listener, that listener will get an event sent to the event queue provided in the call to sys_config_open. - * - * This event will contain the type of config event ("service event" or "IO event", in event.source), - * the corresponding sys_config handle (event.data1), the config event ID (event.data2 & 0xffff'ffff), - * whether the service was registered or unregistered ('data2 >> 32'), and what buffer size will be needed to read the corresponding service event (event.data3). - * - * NOTE: if multiple listeners exist, each gets a separate event ID even though all events are the same! - * - * After receiving such an event from the event queue, the user should allocate enough buffer and call sys_config_get_service_event - * (or sys_config_io_event) with the given event ID, in order to obtain a sys_config_service_event_t (or sys_config_io_event_t) structure - * with the contents of the service that was (un)registered. - */ - -class lv2_config_handle; -class lv2_config_service; -class lv2_config_service_listener; -class lv2_config_service_event; - -// Known sys_config service IDs -enum sys_config_service_id : s64 -{ - SYS_CONFIG_SERVICE_PADMANAGER = 0x11, - SYS_CONFIG_SERVICE_PADMANAGER2 = 0x12, // lv2 seems to send padmanager events to both 0x11 and 0x12 - SYS_CONFIG_SERVICE_0x20 = 0x20, - SYS_CONFIG_SERVICE_0x30 = 0x30, - - SYS_CONFIG_SERVICE_USER_BASE = static_cast(UINT64_C(0x8000'0000'0000'0000)), - SYS_CONFIG_SERVICE_USER_LIBPAD = SYS_CONFIG_SERVICE_USER_BASE + 1, - SYS_CONFIG_SERVICE_USER_LIBKB = SYS_CONFIG_SERVICE_USER_BASE + 2, - SYS_CONFIG_SERVICE_USER_LIBMOUSE = SYS_CONFIG_SERVICE_USER_BASE + 3, - SYS_CONFIG_SERVICE_USER_0x1000 = SYS_CONFIG_SERVICE_USER_BASE + 0x1000, - SYS_CONFIG_SERVICE_USER_0x1010 = SYS_CONFIG_SERVICE_USER_BASE + 0x1010, - SYS_CONFIG_SERVICE_USER_0x1011 = SYS_CONFIG_SERVICE_USER_BASE + 0x1011, - SYS_CONFIG_SERVICE_USER_0x1013 = SYS_CONFIG_SERVICE_USER_BASE + 0x1013, - SYS_CONFIG_SERVICE_USER_0x1020 = SYS_CONFIG_SERVICE_USER_BASE + 0x1020, - SYS_CONFIG_SERVICE_USER_0x1030 = SYS_CONFIG_SERVICE_USER_BASE + 0x1030, -}; - -enum sys_config_service_listener_type : u32 -{ - SYS_CONFIG_SERVICE_LISTENER_ONCE = 0, - SYS_CONFIG_SERVICE_LISTENER_REPEATING = 1 -}; - -enum sys_config_event_source : u64 -{ - SYS_CONFIG_EVENT_SOURCE_SERVICE = 1, - SYS_CONFIG_EVENT_SOURCE_IO = 2 -}; - -/* - * Dynamic-sized struct to describe a sys_config_service_event - * We never allocate it - the guest does it for us and provides a pointer - */ -struct sys_config_service_event_t -{ - // Handle to the service listener for whom this event is destined - be_t service_listener_handle; - - // 1 if this service is currently registered or unregistered - be_t registered; - - // Service ID that triggered this event - be_t service_id; - - // Custom ID provided by the user, used to uniquely identify service events (provided to sys_config_register_event) - // When a service is unregistered, this is the only value available to distinguish which service event was unregistered. - be_t user_id; - - /* if added==0, the structure ends here */ - - // Verbosity of this service event (provided to sys_config_register_event) - be_t verbosity; - - // Size of 'data' - be_t data_size; - - // Ignored, seems to be simply 32-bits of padding - be_t padding; - - // Buffer containing event data (copy of the buffer supplied to sys_config_register_service) - // NOTE: This buffer size is dynamic, according to 'data_size', and can be 0. Here it is set to 1 since zero-sized buffers are not standards-compliant - u8 data[1]; -}; - -/* - * Event data structure for SYS_CONFIG_SERVICE_PADMANAGER - * This is a guess - */ -struct sys_config_padmanager_data_t -{ - be_t unk[5]; // hid device type ? - be_t vid; - be_t pid; - be_t unk2[6]; // bluetooth address? -}; -static_assert(sizeof(sys_config_padmanager_data_t) == 26); - -/* - * Global sys_config state - */ - -class lv2_config -{ - atomic_t m_state = 0; - - // LV2 Config mutex - shared_mutex m_mutex; - - // Map of LV2 Service Events - std::unordered_map> events; - -public: - void initialize(); - - // Service Events - void add_service_event(shared_ptr event); - void remove_service_event(u32 id); - - shared_ptr find_event(u32 id) - { - reader_lock lock(m_mutex); - - const auto it = events.find(id); - - if (it == events.cend()) - return null_ptr; - - if (it->second) - { - return it->second; - } - - return null_ptr; - } - - ~lv2_config() noexcept; -}; - -/* - * LV2 Config Handle object, managed by IDM - */ -class lv2_config_handle -{ -public: - static const u32 id_base = 0x41000000; - static const u32 id_step = 0x100; - static const u32 id_count = 2048; - SAVESTATE_INIT_POS(37); - -private: - u32 idm_id; - - // queue for service/io event notifications - const shared_ptr queue; - - bool send_queue_event(u64 source, u64 d1, u64 d2, u64 d3) const - { - if (auto sptr = queue) - { - return sptr->send(source, d1, d2, d3) == 0; - } - - return false; - } - -public: - // Constructors (should not be used directly) - lv2_config_handle(shared_ptr _queue) noexcept - : queue(std::move(_queue)) - { - } - - // Factory - template - static shared_ptr create(Args&&... args) - { - if (auto cfg = idm::make_ptr(std::forward(args)...)) - { - cfg->idm_id = idm::last_id(); - return cfg; - } - return null_ptr; - } - - // Notify event queue for this handle - bool notify(u64 source, u64 data2, u64 data3) const - { - return send_queue_event(source, idm_id, data2, data3); - } -}; - -/* - * LV2 Service object, managed by IDM - */ -class lv2_config_service -{ -public: - static const u32 id_base = 0x43000000; - static const u32 id_step = 0x100; - static const u32 id_count = 2048; - SAVESTATE_INIT_POS(38); - -private: - // IDM data - u32 idm_id; - - // Whether this service is currently registered or not - bool registered = true; - -public: - const u64 timestamp; - const sys_config_service_id id; - - const u64 user_id; - const u64 verbosity; - const u32 padding; // not used, but stored here just in case - const std::vector data; - - // Constructors (should not be used directly) - lv2_config_service(sys_config_service_id _id, u64 _user_id, u64 _verbosity, u32 _padding, const u8* _data, usz size) noexcept - : timestamp(get_system_time()), id(_id), user_id(_user_id), verbosity(_verbosity), padding(_padding), data(&_data[0], &_data[size]) - { - } - - // Factory - template - static shared_ptr create(Args&&... args) - { - if (auto service = idm::make_ptr(std::forward(args)...)) - { - service->idm_id = idm::last_id(); - return service; - } - - return null_ptr; - } - - // Registration - bool is_registered() const - { - return registered; - } - void unregister(); - - // Notify listeners - void notify() const; - - // Utilities - usz get_size() const - { - return sizeof(sys_config_service_event_t) - 1 + data.size(); - } - shared_ptr get_shared_ptr() const - { - return stx::make_shared_from_this(this); - } - u32 get_id() const - { - return idm_id; - } -}; - -/* - * LV2 Service Event Listener object, managed by IDM - */ -class lv2_config_service_listener -{ -public: - static const u32 id_base = 0x42000000; - static const u32 id_step = 0x100; - static const u32 id_count = 2048; - SAVESTATE_INIT_POS(39); - -private: - // IDM data - u32 idm_id; - - // The service listener owns the service events - service events will not be freed as long as their corresponding listener exists - // This has been confirmed to be the case in realhw - std::vector> service_events; - shared_ptr handle; - - bool notify(const shared_ptr& event); - -public: - const sys_config_service_id service_id; - const u64 min_verbosity; - const sys_config_service_listener_type type; - - const std::vector data; - - // Constructors (should not be used directly) - lv2_config_service_listener(shared_ptr _handle, sys_config_service_id _service_id, u64 _min_verbosity, sys_config_service_listener_type _type, const u8* _data, usz size) noexcept - : handle(std::move(_handle)), service_id(_service_id), min_verbosity(_min_verbosity), type(_type), data(&_data[0], &_data[size]) - { - } - - // Factory - template - static shared_ptr create(Args&&... args) - { - if (auto listener = idm::make_ptr(std::forward(args)...)) - { - listener->idm_id = idm::last_id(); - return listener; - } - - return null_ptr; - } - - // Check whether service matches - bool check_service(const lv2_config_service& service) const; - - // Register new event, and notify queue - bool notify(const shared_ptr& service); - - // (Re-)notify about all still-registered past events - void notify_all(); - - // Utilities - u32 get_id() const - { - return idm_id; - } - shared_ptr get_shared_ptr() const - { - return stx::make_shared_from_this(this); - } -}; - -/* - * LV2 Service Event object (*not* managed by IDM) - */ -class lv2_config_service_event -{ - static u32 get_next_id() - { - struct service_event_id - { - atomic_t next_id = 0; - }; - - return g_fxo->get().next_id++; - } - - atomic_t m_destroyed = false; - - friend class lv2_config; - -public: - const u32 id; - - // Note: Events hold a shared_ptr to their corresponding service - services only get freed once there are no more pending service events - // This has been confirmed to be the case in realhw - const shared_ptr handle; - const shared_ptr service; - const lv2_config_service_listener& listener; - - // Constructors (should not be used directly) - lv2_config_service_event(shared_ptr _handle, shared_ptr _service, const lv2_config_service_listener& _listener) noexcept - : id(get_next_id()), handle(std::move(_handle)), service(std::move(_service)), listener(_listener) - { - } - - // Factory - template - static shared_ptr create(Args&&... args) - { - auto ev = make_shared(std::forward(args)...); - - g_fxo->get().add_service_event(ev); - - return ev; - } - - // Destructor - lv2_config_service_event& operator=(thread_state s) noexcept; - ~lv2_config_service_event() noexcept; - - // Notify queue that this event exists - bool notify() const; - - // Write event to buffer - void write(sys_config_service_event_t* dst) const; - - // Check if the buffer can fit the current event, return false otherwise - bool check_buffer_size(usz size) const - { - return service->get_size() <= size; - } -}; - -/* - * Syscalls - */ -/*516*/ error_code sys_config_open(u32 equeue_hdl, vm::ptr out_config_hdl); -/*517*/ error_code sys_config_close(u32 config_hdl); -/*518*/ error_code sys_config_get_service_event(u32 config_hdl, u32 event_id, vm::ptr dst, u64 size); -/*519*/ error_code sys_config_add_service_listener(u32 config_hdl, sys_config_service_id service_id, u64 min_verbosity, vm::ptr in, u64 size, sys_config_service_listener_type type, vm::ptr out_listener_hdl); -/*520*/ error_code sys_config_remove_service_listener(u32 config_hdl, u32 listener_hdl); -/*521*/ error_code sys_config_register_service(u32 config_hdl, sys_config_service_id service_id, u64 user_id, u64 verbosity, vm::ptr data_buf, u64 size, vm::ptr out_service_hdl); -/*522*/ error_code sys_config_unregister_service(u32 config_hdl, u32 service_hdl); - -// Following syscalls have not been REd yet -/*523*/ error_code sys_config_get_io_event(u32 config_hdl, u32 event_id /*?*/, vm::ptr out_buf /*?*/, u64 size /*?*/); -/*524*/ error_code sys_config_register_io_error_listener(u32 config_hdl); -/*525*/ error_code sys_config_unregister_io_error_listener(u32 config_hdl); diff --git a/rpcs3/Emu/Cell/lv2/sys_console.cpp b/rpcs3/Emu/Cell/lv2/sys_console.cpp deleted file mode 100644 index 1cbf7703a..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_console.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include "stdafx.h" -#include "Emu/Cell/ErrorCodes.h" - -#include "sys_console.h" - -LOG_CHANNEL(sys_console); - -error_code sys_console_write(vm::cptr buf, u32 len) -{ - sys_console.todo("sys_console_write(buf=*0x%x, len=0x%x)", buf, len); - - return CELL_OK; -} diff --git a/rpcs3/Emu/Cell/lv2/sys_crypto_engine.cpp b/rpcs3/Emu/Cell/lv2/sys_crypto_engine.cpp deleted file mode 100644 index 0c149e5eb..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_crypto_engine.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include "stdafx.h" - -#include "Emu/Cell/ErrorCodes.h" - -#include "sys_crypto_engine.h" - -LOG_CHANNEL(sys_crypto_engine); - -error_code sys_crypto_engine_create(vm::ptr id) -{ - sys_crypto_engine.todo("sys_crypto_engine_create(id=*0x%x)", id); - - return CELL_OK; -} - -error_code sys_crypto_engine_destroy(u32 id) -{ - sys_crypto_engine.todo("sys_crypto_engine_destroy(id=0x%x)", id); - - return CELL_OK; -} - -error_code sys_crypto_engine_random_generate(vm::ptr buffer, u64 buffer_size) -{ - sys_crypto_engine.todo("sys_crypto_engine_random_generate(buffer=*0x%x, buffer_size=0x%x", buffer, buffer_size); - - return CELL_OK; -} diff --git a/rpcs3/Emu/Cell/lv2/sys_dbg.cpp b/rpcs3/Emu/Cell/lv2/sys_dbg.cpp deleted file mode 100644 index 0c0dd4e38..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_dbg.cpp +++ /dev/null @@ -1,131 +0,0 @@ -#include "stdafx.h" -#include "sys_dbg.h" - -#include "Emu/Cell/ErrorCodes.h" - -#include "Emu/Cell/PPUInterpreter.h" -#include "rpcsx/fw/ps3/sys_lv2dbg.h" -#include "Emu/Memory/vm_locking.h" - -#include "util/asm.hpp" - -void ppu_register_function_at(u32 addr, u32 size, ppu_intrp_func_t ptr = nullptr); - -LOG_CHANNEL(sys_dbg); - -error_code sys_dbg_read_process_memory(s32 pid, u32 address, u32 size, vm::ptr data) -{ - sys_dbg.warning("sys_dbg_read_process_memory(pid=0x%x, address=0x%llx, size=0x%x, data=*0x%x)", pid, address, size, data); - - // Todo(TGEnigma): Process lookup (only 1 process exists right now) - if (pid != 1) - { - return CELL_LV2DBG_ERROR_DEINVALIDARGUMENTS; - } - - if (!size || !data) - { - return CELL_LV2DBG_ERROR_DEINVALIDARGUMENTS; - } - - vm::writer_lock lock; - - // Check if data destination is writable - if (!vm::check_addr(data.addr(), vm::page_writable, size)) - { - return CELL_EFAULT; - } - - // Check if the source is readable - if (!vm::check_addr(address, vm::page_readable, size)) - { - return CELL_EFAULT; - } - - std::memmove(data.get_ptr(), vm::base(address), size); - - return CELL_OK; -} - -error_code sys_dbg_write_process_memory(s32 pid, u32 address, u32 size, vm::cptr data) -{ - sys_dbg.warning("sys_dbg_write_process_memory(pid=0x%x, address=0x%llx, size=0x%x, data=*0x%x)", pid, address, size, data); - - // Todo(TGEnigma): Process lookup (only 1 process exists right now) - if (pid != 1) - { - return CELL_LV2DBG_ERROR_DEINVALIDARGUMENTS; - } - - if (!size || !data) - { - return CELL_LV2DBG_ERROR_DEINVALIDARGUMENTS; - } - - // Check if data source is readable - if (!vm::check_addr(data.addr(), vm::page_readable, size)) - { - return CELL_EFAULT; - } - - // Check destination (can be read-only actually) - if (!vm::check_addr(address, vm::page_readable, size)) - { - return CELL_EFAULT; - } - - vm::writer_lock lock; - - // Again - if (!vm::check_addr(data.addr(), vm::page_readable, size) || !vm::check_addr(address, vm::page_readable, size)) - { - return CELL_EFAULT; - } - - const u8* data_ptr = static_cast(data.get_ptr()); - - if ((address >> 28) == 0xDu) - { - // Stack pages (4k pages is the exception here) - std::memmove(vm::base(address), data_ptr, size); - return CELL_OK; - } - - const u32 end = address + size; - - for (u32 i = address, exec_update_size = 0; i < end;) - { - const u32 op_size = std::min(utils::align(i + 1, 0x10000), end) - i; - - const bool is_exec = vm::check_addr(i, vm::page_executable | vm::page_readable); - - if (is_exec) - { - exec_update_size += op_size; - i += op_size; - } - - if (!is_exec || i >= end) - { - // Commit executable data update - // The read memory is also super ptr so memmove can work correctly on all implementations - const u32 before_addr = i - exec_update_size; - std::memmove(vm::get_super_ptr(before_addr), vm::get_super_ptr(data.addr() + (before_addr - address)), exec_update_size); - ppu_register_function_at(before_addr, exec_update_size); - exec_update_size = 0; - - if (i >= end) - { - break; - } - } - - if (!is_exec) - { - std::memmove(vm::base(i), data_ptr + (i - address), op_size); - i += op_size; - } - } - - return CELL_OK; -} diff --git a/rpcs3/Emu/Cell/lv2/sys_event.cpp b/rpcs3/Emu/Cell/lv2/sys_event.cpp deleted file mode 100644 index c65efd62c..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_event.cpp +++ /dev/null @@ -1,792 +0,0 @@ -#include "stdafx.h" -#include "sys_event.h" - -#include "Emu/IdManager.h" -#include "Emu/IPC.h" -#include "Emu/System.h" - -#include "Emu/Cell/ErrorCodes.h" -#include "Emu/Cell/PPUThread.h" -#include "Emu/Cell/SPUThread.h" -#include "sys_process.h" - -#include "util/asm.hpp" - -LOG_CHANNEL(sys_event); - -lv2_event_queue::lv2_event_queue(u32 protocol, s32 type, s32 size, u64 name, u64 ipc_key) noexcept - : id(idm::last_id()), protocol{static_cast(protocol)}, type(static_cast(type)), size(static_cast(size)), name(name), key(ipc_key) -{ -} - -lv2_event_queue::lv2_event_queue(utils::serial& ar) noexcept - : id(idm::last_id()), protocol(ar), type(ar), size(ar), name(ar), key(ar) -{ - ar(events); -} - -std::function lv2_event_queue::load(utils::serial& ar) -{ - auto queue = make_shared(exact_t(ar)); - return [ptr = lv2_obj::load(queue->key, queue)](void* storage) - { - *static_cast*>(storage) = ptr; - }; -} - -void lv2_event_queue::save(utils::serial& ar) -{ - ar(protocol, type, size, name, key, events); -} - -void lv2_event_queue::save_ptr(utils::serial& ar, lv2_event_queue* q) -{ - if (!lv2_obj::check(q)) - { - ar(u32{0}); - return; - } - - ar(q->id); -} - -shared_ptr lv2_event_queue::load_ptr(utils::serial& ar, shared_ptr& queue, std::string_view msg) -{ - const u32 id = ar.pop(); - - if (!id) - { - return {}; - } - - if (auto q = idm::get_unlocked(id)) - { - // Already initialized - return q; - } - - if (id >> 24 != id_base >> 24) - { - fmt::throw_exception("Failed in event queue pointer deserialization (invalid ID): location: %s, id=0x%x", msg, id); - } - - Emu.PostponeInitCode([id, &queue, msg_str = std::string{msg}]() - { - // Defer resolving - queue = idm::get_unlocked(id); - - if (!queue) - { - fmt::throw_exception("Failed in event queue pointer deserialization (not found): location: %s, id=0x%x", msg_str, id); - } - }); - - // Null until resolved - return {}; -} - -lv2_event_port::lv2_event_port(utils::serial& ar) - : type(ar), name(ar), queue(lv2_event_queue::load_ptr(ar, queue, "eventport")) -{ -} - -void lv2_event_port::save(utils::serial& ar) -{ - ar(type, name); - - lv2_event_queue::save_ptr(ar, queue.get()); -} - -shared_ptr lv2_event_queue::find(u64 ipc_key) -{ - if (ipc_key == SYS_EVENT_QUEUE_LOCAL) - { - // Invalid IPC key - return {}; - } - - return g_fxo->get>().get(ipc_key); -} - -extern void resume_spu_thread_group_from_waiting(spu_thread& spu); - -CellError lv2_event_queue::send(lv2_event event, bool* notified_thread, lv2_event_port* port) -{ - if (notified_thread) - { - *notified_thread = false; - } - - std::lock_guard lock(mutex); - - if (!exists) - { - return CELL_ENOTCONN; - } - - if (!pq && !sq) - { - if (events.size() < this->size + 0u) - { - // Save event - events.emplace_back(event); - return {}; - } - - return CELL_EBUSY; - } - - if (type == SYS_PPU_QUEUE) - { - // Store event in registers - auto& ppu = static_cast(*schedule(pq, protocol)); - - if (ppu.state & cpu_flag::again) - { - if (auto cpu = get_current_cpu_thread()) - { - cpu->state += cpu_flag::again; - cpu->state += cpu_flag::exit; - } - - sys_event.warning("Ignored event!"); - - // Fake error for abort - return CELL_EAGAIN; - } - - std::tie(ppu.gpr[4], ppu.gpr[5], ppu.gpr[6], ppu.gpr[7]) = event; - - awake(&ppu); - - if (port && ppu.prio.load().prio < ensure(cpu_thread::get_current())->prio.load().prio) - { - // Block event port disconnection for the time being of sending events - // PPU -> lower prio PPU is the only case that can cause thread blocking - port->is_busy++; - ensure(notified_thread); - *notified_thread = true; - } - } - else - { - // Store event in In_MBox - auto& spu = static_cast(*schedule(sq, protocol)); - - if (spu.state & cpu_flag::again) - { - if (auto cpu = get_current_cpu_thread()) - { - cpu->state += cpu_flag::exit + cpu_flag::again; - } - - sys_event.warning("Ignored event!"); - - // Fake error for abort - return CELL_EAGAIN; - } - - const u32 data1 = static_cast(std::get<1>(event)); - const u32 data2 = static_cast(std::get<2>(event)); - const u32 data3 = static_cast(std::get<3>(event)); - spu.ch_in_mbox.set_values(4, CELL_OK, data1, data2, data3); - resume_spu_thread_group_from_waiting(spu); - } - - return {}; -} - -error_code sys_event_queue_create(cpu_thread& cpu, vm::ptr equeue_id, vm::ptr attr, u64 ipc_key, s32 size) -{ - cpu.state += cpu_flag::wait; - - sys_event.warning("sys_event_queue_create(equeue_id=*0x%x, attr=*0x%x, ipc_key=0x%llx, size=%d)", equeue_id, attr, ipc_key, size); - - if (size <= 0 || size > 127) - { - return CELL_EINVAL; - } - - const u32 protocol = attr->protocol; - - if (protocol != SYS_SYNC_FIFO && protocol != SYS_SYNC_PRIORITY) - { - sys_event.error("sys_event_queue_create(): unknown protocol (0x%x)", protocol); - return CELL_EINVAL; - } - - const u32 type = attr->type; - - if (type != SYS_PPU_QUEUE && type != SYS_SPU_QUEUE) - { - sys_event.error("sys_event_queue_create(): unknown type (0x%x)", type); - return CELL_EINVAL; - } - - const u32 pshared = ipc_key == SYS_EVENT_QUEUE_LOCAL ? SYS_SYNC_NOT_PROCESS_SHARED : SYS_SYNC_PROCESS_SHARED; - constexpr u32 flags = SYS_SYNC_NEWLY_CREATED; - const u64 name = attr->name_u64; - - if (const auto error = lv2_obj::create(pshared, ipc_key, flags, [&]() - { - return make_shared(protocol, type, size, name, ipc_key); - })) - { - return error; - } - - cpu.check_state(); - *equeue_id = idm::last_id(); - return CELL_OK; -} - -error_code sys_event_queue_destroy(ppu_thread& ppu, u32 equeue_id, s32 mode) -{ - ppu.state += cpu_flag::wait; - - sys_event.warning("sys_event_queue_destroy(equeue_id=0x%x, mode=%d)", equeue_id, mode); - - if (mode && mode != SYS_EVENT_QUEUE_DESTROY_FORCE) - { - return CELL_EINVAL; - } - - std::vector events; - - std::unique_lock qlock; - - cpu_thread* head{}; - - const auto queue = idm::withdraw(equeue_id, [&](lv2_event_queue& queue) -> CellError - { - qlock = std::unique_lock{queue.mutex}; - - head = queue.type == SYS_PPU_QUEUE ? static_cast(+queue.pq) : +queue.sq; - - if (!mode && head) - { - return CELL_EBUSY; - } - - if (!queue.events.empty()) - { - // Copy events for logging, does not empty - events.insert(events.begin(), queue.events.begin(), queue.events.end()); - } - - lv2_obj::on_id_destroy(queue, queue.key); - - if (!head) - { - qlock.unlock(); - } - else - { - for (auto cpu = head; cpu; cpu = cpu->get_next_cpu()) - { - if (cpu->state & cpu_flag::again) - { - ppu.state += cpu_flag::again; - return CELL_EAGAIN; - } - } - } - - return {}; - }); - - if (!queue) - { - return CELL_ESRCH; - } - - if (ppu.state & cpu_flag::again) - { - return {}; - } - - if (queue.ret) - { - return queue.ret; - } - - std::string lost_data; - - if (qlock.owns_lock()) - { - if (sys_event.warning) - { - u32 size = 0; - - for (auto cpu = head; cpu; cpu = cpu->get_next_cpu()) - { - size++; - } - - fmt::append(lost_data, "Forcefully awaken waiters (%u):\n", size); - - for (auto cpu = head; cpu; cpu = cpu->get_next_cpu()) - { - lost_data += cpu->get_name(); - lost_data += '\n'; - } - } - - if (queue->type == SYS_PPU_QUEUE) - { - for (auto cpu = +queue->pq; cpu; cpu = cpu->next_cpu) - { - cpu->gpr[3] = CELL_ECANCELED; - queue->append(cpu); - } - - atomic_storage::release(queue->pq, nullptr); - lv2_obj::awake_all(); - } - else - { - for (auto cpu = +queue->sq; cpu; cpu = cpu->next_cpu) - { - cpu->ch_in_mbox.set_values(1, CELL_ECANCELED); - resume_spu_thread_group_from_waiting(*cpu); - } - - atomic_storage::release(queue->sq, nullptr); - } - - qlock.unlock(); - } - - if (sys_event.warning) - { - if (!events.empty()) - { - fmt::append(lost_data, "Unread queue events (%u):\n", events.size()); - } - - for (const lv2_event& evt : events) - { - fmt::append(lost_data, "data0=0x%x, data1=0x%x, data2=0x%x, data3=0x%x\n", std::get<0>(evt), std::get<1>(evt), std::get<2>(evt), std::get<3>(evt)); - } - - if (!lost_data.empty()) - { - sys_event.warning("sys_event_queue_destroy(): %s", lost_data); - } - } - - return CELL_OK; -} - -error_code sys_event_queue_tryreceive(ppu_thread& ppu, u32 equeue_id, vm::ptr event_array, s32 size, vm::ptr number) -{ - ppu.state += cpu_flag::wait; - - sys_event.trace("sys_event_queue_tryreceive(equeue_id=0x%x, event_array=*0x%x, size=%d, number=*0x%x)", equeue_id, event_array, size, number); - - const auto queue = idm::get_unlocked(equeue_id); - - if (!queue) - { - return CELL_ESRCH; - } - - if (queue->type != SYS_PPU_QUEUE) - { - return CELL_EINVAL; - } - - std::array events; - - std::unique_lock lock(queue->mutex); - - if (!queue->exists) - { - return CELL_ESRCH; - } - - s32 count = 0; - - while (count < size && !queue->events.empty()) - { - auto& dest = events[count++]; - std::tie(dest.source, dest.data1, dest.data2, dest.data3) = queue->events.front(); - queue->events.pop_front(); - } - - lock.unlock(); - ppu.check_state(); - - std::copy_n(events.begin(), count, event_array.get_ptr()); - *number = count; - - return CELL_OK; -} - -error_code sys_event_queue_receive(ppu_thread& ppu, u32 equeue_id, vm::ptr dummy_event, u64 timeout) -{ - ppu.state += cpu_flag::wait; - - sys_event.trace("sys_event_queue_receive(equeue_id=0x%x, *0x%x, timeout=0x%llx)", equeue_id, dummy_event, timeout); - - ppu.gpr[3] = CELL_OK; - - const auto queue = idm::get(equeue_id, [&, notify = lv2_obj::notify_all_t()](lv2_event_queue& queue) -> CellError - { - if (queue.type != SYS_PPU_QUEUE) - { - return CELL_EINVAL; - } - - lv2_obj::prepare_for_sleep(ppu); - - std::lock_guard lock(queue.mutex); - - // "/dev_flash/vsh/module/msmw2.sprx" seems to rely on some cryptic shared memory behaviour that we don't emulate correctly - // This is a hack to avoid waiting for 1m40s every time we boot vsh - if (queue.key == 0x8005911000000012 && Emu.IsVsh()) - { - sys_event.todo("sys_event_queue_receive(equeue_id=0x%x, *0x%x, timeout=0x%llx) Bypassing timeout for msmw2.sprx", equeue_id, dummy_event, timeout); - timeout = 1; - } - - if (queue.events.empty()) - { - queue.sleep(ppu, timeout); - lv2_obj::emplace(queue.pq, &ppu); - return CELL_EBUSY; - } - - std::tie(ppu.gpr[4], ppu.gpr[5], ppu.gpr[6], ppu.gpr[7]) = queue.events.front(); - queue.events.pop_front(); - return {}; - }); - - if (!queue) - { - return CELL_ESRCH; - } - - if (queue.ret) - { - if (queue.ret != CELL_EBUSY) - { - return queue.ret; - } - } - else - { - return CELL_OK; - } - - // If cancelled, gpr[3] will be non-zero. Other registers must contain event data. - while (auto state = +ppu.state) - { - if (state & cpu_flag::signal && ppu.state.test_and_reset(cpu_flag::signal)) - { - break; - } - - if (is_stopped(state)) - { - std::lock_guard lock_rsx(queue->mutex); - - for (auto cpu = +queue->pq; cpu; cpu = cpu->next_cpu) - { - if (cpu == &ppu) - { - ppu.state += cpu_flag::again; - return {}; - } - } - - break; - } - - for (usz i = 0; cpu_flag::signal - ppu.state && i < 50; i++) - { - busy_wait(500); - } - - if (ppu.state & cpu_flag::signal) - { - continue; - } - - if (timeout) - { - if (lv2_obj::wait_timeout(timeout, &ppu)) - { - // Wait for rescheduling - if (ppu.check_state()) - { - continue; - } - - ppu.state += cpu_flag::wait; - - if (!atomic_storage::load(queue->pq)) - { - // Waiters queue is empty, so the thread must have been signaled - queue->mutex.lock_unlock(); - break; - } - - std::lock_guard lock(queue->mutex); - - if (!queue->unqueue(queue->pq, &ppu)) - { - break; - } - - ppu.gpr[3] = CELL_ETIMEDOUT; - break; - } - } - else - { - ppu.state.wait(state); - } - } - - return not_an_error(ppu.gpr[3]); -} - -error_code sys_event_queue_drain(ppu_thread& ppu, u32 equeue_id) -{ - ppu.state += cpu_flag::wait; - - sys_event.trace("sys_event_queue_drain(equeue_id=0x%x)", equeue_id); - - const auto queue = idm::check(equeue_id, [&](lv2_event_queue& queue) - { - std::lock_guard lock(queue.mutex); - - queue.events.clear(); - }); - - if (!queue) - { - return CELL_ESRCH; - } - - return CELL_OK; -} - -error_code sys_event_port_create(cpu_thread& cpu, vm::ptr eport_id, s32 port_type, u64 name) -{ - cpu.state += cpu_flag::wait; - - sys_event.warning("sys_event_port_create(eport_id=*0x%x, port_type=%d, name=0x%llx)", eport_id, port_type, name); - - if (port_type != SYS_EVENT_PORT_LOCAL && port_type != 3) - { - sys_event.error("sys_event_port_create(): unknown port type (%d)", port_type); - return CELL_EINVAL; - } - - if (const u32 id = idm::make(port_type, name)) - { - cpu.check_state(); - *eport_id = id; - return CELL_OK; - } - - return CELL_EAGAIN; -} - -error_code sys_event_port_destroy(ppu_thread& ppu, u32 eport_id) -{ - ppu.state += cpu_flag::wait; - - sys_event.warning("sys_event_port_destroy(eport_id=0x%x)", eport_id); - - const auto port = idm::withdraw(eport_id, [](lv2_event_port& port) -> CellError - { - if (lv2_obj::check(port.queue)) - { - return CELL_EISCONN; - } - - return {}; - }); - - if (!port) - { - return CELL_ESRCH; - } - - if (port.ret) - { - return port.ret; - } - - return CELL_OK; -} - -error_code sys_event_port_connect_local(cpu_thread& cpu, u32 eport_id, u32 equeue_id) -{ - cpu.state += cpu_flag::wait; - - sys_event.warning("sys_event_port_connect_local(eport_id=0x%x, equeue_id=0x%x)", eport_id, equeue_id); - - std::lock_guard lock(id_manager::g_mutex); - - const auto port = idm::check_unlocked(eport_id); - - if (!port || !idm::check_unlocked(equeue_id)) - { - return CELL_ESRCH; - } - - if (port->type != SYS_EVENT_PORT_LOCAL) - { - return CELL_EINVAL; - } - - if (lv2_obj::check(port->queue)) - { - return CELL_EISCONN; - } - - port->queue = idm::get_unlocked(equeue_id); - - return CELL_OK; -} - -error_code sys_event_port_connect_ipc(ppu_thread& ppu, u32 eport_id, u64 ipc_key) -{ - ppu.state += cpu_flag::wait; - - sys_event.warning("sys_event_port_connect_ipc(eport_id=0x%x, ipc_key=0x%x)", eport_id, ipc_key); - - if (ipc_key == 0) - { - return CELL_EINVAL; - } - - auto queue = lv2_event_queue::find(ipc_key); - - std::lock_guard lock(id_manager::g_mutex); - - const auto port = idm::check_unlocked(eport_id); - - if (!port || !queue) - { - return CELL_ESRCH; - } - - if (port->type != SYS_EVENT_PORT_IPC) - { - return CELL_EINVAL; - } - - if (lv2_obj::check(port->queue)) - { - return CELL_EISCONN; - } - - port->queue = std::move(queue); - - return CELL_OK; -} - -error_code sys_event_port_disconnect(ppu_thread& ppu, u32 eport_id) -{ - ppu.state += cpu_flag::wait; - - sys_event.warning("sys_event_port_disconnect(eport_id=0x%x)", eport_id); - - std::lock_guard lock(id_manager::g_mutex); - - const auto port = idm::check_unlocked(eport_id); - - if (!port) - { - return CELL_ESRCH; - } - - if (!lv2_obj::check(port->queue)) - { - return CELL_ENOTCONN; - } - - if (port->is_busy) - { - return CELL_EBUSY; - } - - port->queue.reset(); - - return CELL_OK; -} - -error_code sys_event_port_send(u32 eport_id, u64 data1, u64 data2, u64 data3) -{ - const auto cpu = cpu_thread::get_current(); - const auto ppu = cpu ? cpu->try_get() : nullptr; - - if (cpu) - { - cpu->state += cpu_flag::wait; - } - - sys_event.trace("sys_event_port_send(eport_id=0x%x, data1=0x%llx, data2=0x%llx, data3=0x%llx)", eport_id, data1, data2, data3); - - bool notified_thread = false; - - const auto port = idm::check(eport_id, [&, notify = lv2_obj::notify_all_t()](lv2_event_port& port) -> CellError - { - if (ppu && ppu->loaded_from_savestate) - { - port.is_busy++; - notified_thread = true; - return {}; - } - - if (lv2_obj::check(port.queue)) - { - const u64 source = port.name ? port.name : (u64{process_getpid() + 0u} << 32) | u64{eport_id}; - - return port.queue->send(source, data1, data2, data3, ¬ified_thread, ppu && port.queue->type == SYS_PPU_QUEUE ? &port : nullptr); - } - - return CELL_ENOTCONN; - }); - - if (!port) - { - return CELL_ESRCH; - } - - if (ppu && notified_thread) - { - // Wait to be requeued - if (ppu->test_stopped()) - { - // Wait again on savestate load - ppu->state += cpu_flag::again; - } - - port->is_busy--; - return CELL_OK; - } - - if (port.ret) - { - if (port.ret == CELL_EAGAIN) - { - // Not really an error code exposed to games (thread has raised cpu_flag::again) - return not_an_error(CELL_EAGAIN); - } - - if (port.ret == CELL_EBUSY) - { - return not_an_error(CELL_EBUSY); - } - - return port.ret; - } - - return CELL_OK; -} diff --git a/rpcs3/Emu/Cell/lv2/sys_event.h b/rpcs3/Emu/Cell/lv2/sys_event.h deleted file mode 100644 index 610bd051f..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_event.h +++ /dev/null @@ -1,153 +0,0 @@ -#pragma once - -#include "sys_sync.h" - -#include "Emu/Memory/vm_ptr.h" - -#include - -class cpu_thread; -class spu_thrread; - -// Event Queue Type -enum : u32 -{ - SYS_PPU_QUEUE = 1, - SYS_SPU_QUEUE = 2, -}; - -// Event Queue Destroy Mode -enum : s32 -{ - SYS_EVENT_QUEUE_DESTROY_FORCE = 1, -}; - -// Event Queue Ipc Key -enum : u64 -{ - SYS_EVENT_QUEUE_LOCAL = 0, -}; - -// Event Port Type -enum : s32 -{ - SYS_EVENT_PORT_LOCAL = 1, - SYS_EVENT_PORT_IPC = 3, // Unofficial name -}; - -// Event Port Name -enum : u64 -{ - SYS_EVENT_PORT_NO_NAME = 0, -}; - -// Event Source Type -enum : u32 -{ - SYS_SPU_THREAD_EVENT_USER = 1, - SYS_SPU_THREAD_EVENT_DMA = 2, // not supported -}; - -// Event Source Key -enum : u64 -{ - SYS_SPU_THREAD_EVENT_USER_KEY = 0xFFFFFFFF53505501ull, - SYS_SPU_THREAD_EVENT_DMA_KEY = 0xFFFFFFFF53505502ull, - SYS_SPU_THREAD_EVENT_EXCEPTION_KEY = 0xFFFFFFFF53505503ull, -}; - -struct sys_event_queue_attribute_t -{ - be_t protocol; // SYS_SYNC_PRIORITY or SYS_SYNC_FIFO - be_t type; // SYS_PPU_QUEUE or SYS_SPU_QUEUE - - union - { - nse_t name_u64; - char name[sizeof(u64)]; - }; -}; - -struct sys_event_t -{ - be_t source; - be_t data1; - be_t data2; - be_t data3; -}; - -// Source, data1, data2, data3 -using lv2_event = std::tuple; - -struct lv2_event_port; - -struct lv2_event_queue final : public lv2_obj -{ - static const u32 id_base = 0x8d000000; - - const u32 id; - const lv2_protocol protocol; - const u8 type; - const u8 size; - const u64 name; - const u64 key; - - shared_mutex mutex; - std::deque events; - spu_thread* sq{}; - ppu_thread* pq{}; - - lv2_event_queue(u32 protocol, s32 type, s32 size, u64 name, u64 ipc_key) noexcept; - - lv2_event_queue(utils::serial& ar) noexcept; - static std::function load(utils::serial& ar); - void save(utils::serial& ar); - static void save_ptr(utils::serial&, lv2_event_queue*); - static shared_ptr load_ptr(utils::serial& ar, shared_ptr& queue, std::string_view msg = {}); - - CellError send(lv2_event event, bool* notified_thread = nullptr, lv2_event_port* port = nullptr); - - CellError send(u64 source, u64 d1, u64 d2, u64 d3, bool* notified_thread = nullptr, lv2_event_port* port = nullptr) - { - return send(std::make_tuple(source, d1, d2, d3), notified_thread, port); - } - - // Get event queue by its global key - static shared_ptr find(u64 ipc_key); -}; - -struct lv2_event_port final : lv2_obj -{ - static const u32 id_base = 0x0e000000; - - const s32 type; // Port type, either IPC or local - const u64 name; // Event source (generated from id and process id if not set) - - atomic_t is_busy = 0; // Counts threads waiting on event sending - shared_ptr queue; // Event queue this port is connected to - - lv2_event_port(s32 type, u64 name) - : type(type), name(name) - { - } - - lv2_event_port(utils::serial& ar); - void save(utils::serial& ar); -}; - -class ppu_thread; - -// Syscalls - -error_code sys_event_queue_create(cpu_thread& cpu, vm::ptr equeue_id, vm::ptr attr, u64 event_queue_key, s32 size); -error_code sys_event_queue_destroy(ppu_thread& ppu, u32 equeue_id, s32 mode); -error_code sys_event_queue_receive(ppu_thread& ppu, u32 equeue_id, vm::ptr dummy_event, u64 timeout); -error_code sys_event_queue_tryreceive(ppu_thread& ppu, u32 equeue_id, vm::ptr event_array, s32 size, vm::ptr number); -error_code sys_event_queue_drain(ppu_thread& ppu, u32 event_queue_id); - -error_code sys_event_port_create(cpu_thread& cpu, vm::ptr eport_id, s32 port_type, u64 name); -error_code sys_event_port_destroy(ppu_thread& ppu, u32 eport_id); -error_code sys_event_port_connect_local(cpu_thread& cpu, u32 event_port_id, u32 event_queue_id); -error_code sys_event_port_connect_ipc(ppu_thread& ppu, u32 eport_id, u64 ipc_key); -error_code sys_event_port_disconnect(ppu_thread& ppu, u32 eport_id); -error_code sys_event_port_send(u32 event_port_id, u64 data1, u64 data2, u64 data3); diff --git a/rpcs3/Emu/Cell/lv2/sys_event_flag.cpp b/rpcs3/Emu/Cell/lv2/sys_event_flag.cpp deleted file mode 100644 index 30c81ebf2..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_event_flag.cpp +++ /dev/null @@ -1,577 +0,0 @@ -#include "stdafx.h" -#include "sys_event_flag.h" - -#include "Emu/IdManager.h" - -#include "Emu/Cell/ErrorCodes.h" -#include "Emu/Cell/PPUThread.h" - -#include "util/asm.hpp" - -LOG_CHANNEL(sys_event_flag); - -lv2_event_flag::lv2_event_flag(utils::serial& ar) - : protocol(ar), key(ar), type(ar), name(ar) -{ - ar(pattern); -} - -std::function lv2_event_flag::load(utils::serial& ar) -{ - return load_func(make_shared(exact_t(ar))); -} - -void lv2_event_flag::save(utils::serial& ar) -{ - ar(protocol, key, type, name, pattern); -} - -error_code sys_event_flag_create(ppu_thread& ppu, vm::ptr id, vm::ptr attr, u64 init) -{ - ppu.state += cpu_flag::wait; - - sys_event_flag.warning("sys_event_flag_create(id=*0x%x, attr=*0x%x, init=0x%llx)", id, attr, init); - - if (!id || !attr) - { - return CELL_EFAULT; - } - - const auto _attr = *attr; - - const u32 protocol = _attr.protocol; - - if (protocol != SYS_SYNC_FIFO && protocol != SYS_SYNC_PRIORITY) - { - sys_event_flag.error("sys_event_flag_create(): unknown protocol (0x%x)", protocol); - return CELL_EINVAL; - } - - const u32 type = _attr.type; - - if (type != SYS_SYNC_WAITER_SINGLE && type != SYS_SYNC_WAITER_MULTIPLE) - { - sys_event_flag.error("sys_event_flag_create(): unknown type (0x%x)", type); - return CELL_EINVAL; - } - - const u64 ipc_key = lv2_obj::get_key(_attr); - - if (const auto error = lv2_obj::create(_attr.pshared, ipc_key, _attr.flags, [&] - { - return make_shared( - _attr.protocol, - ipc_key, - _attr.type, - _attr.name_u64, - init); - })) - { - return error; - } - - ppu.check_state(); - *id = idm::last_id(); - return CELL_OK; -} - -error_code sys_event_flag_destroy(ppu_thread& ppu, u32 id) -{ - ppu.state += cpu_flag::wait; - - sys_event_flag.warning("sys_event_flag_destroy(id=0x%x)", id); - - const auto flag = idm::withdraw(id, [&](lv2_event_flag& flag) -> CellError - { - if (flag.sq) - { - return CELL_EBUSY; - } - - lv2_obj::on_id_destroy(flag, flag.key); - return {}; - }); - - if (!flag) - { - return CELL_ESRCH; - } - - if (flag.ret) - { - return flag.ret; - } - - return CELL_OK; -} - -error_code sys_event_flag_wait(ppu_thread& ppu, u32 id, u64 bitptn, u32 mode, vm::ptr result, u64 timeout) -{ - ppu.state += cpu_flag::wait; - - sys_event_flag.trace("sys_event_flag_wait(id=0x%x, bitptn=0x%llx, mode=0x%x, result=*0x%x, timeout=0x%llx)", id, bitptn, mode, result, timeout); - - // Fix function arguments for external access - ppu.gpr[3] = -1; - ppu.gpr[4] = bitptn; - ppu.gpr[5] = mode; - ppu.gpr[6] = 0; - - // Always set result - struct store_result - { - vm::ptr ptr; - u64 val = 0; - - ~store_result() noexcept - { - if (ptr) - { - cpu_thread::get_current()->check_state(); - *ptr = val; - } - } - } store{result}; - - if (!lv2_event_flag::check_mode(mode)) - { - sys_event_flag.error("sys_event_flag_wait(): unknown mode (0x%x)", mode); - return CELL_EINVAL; - } - - const auto flag = idm::get(id, [&, notify = lv2_obj::notify_all_t()](lv2_event_flag& flag) -> CellError - { - if (flag.pattern.fetch_op([&](u64& pat) - { - return lv2_event_flag::check_pattern(pat, bitptn, mode, &ppu.gpr[6]); - }) - .second) - { - // TODO: is it possible to return EPERM in this case? - return {}; - } - - lv2_obj::prepare_for_sleep(ppu); - - std::lock_guard lock(flag.mutex); - - if (flag.pattern.fetch_op([&](u64& pat) - { - return lv2_event_flag::check_pattern(pat, bitptn, mode, &ppu.gpr[6]); - }) - .second) - { - return {}; - } - - if (flag.type == SYS_SYNC_WAITER_SINGLE && flag.sq) - { - return CELL_EPERM; - } - - flag.sleep(ppu, timeout); - lv2_obj::emplace(flag.sq, &ppu); - return CELL_EBUSY; - }); - - if (!flag) - { - return CELL_ESRCH; - } - - if (flag.ret) - { - if (flag.ret != CELL_EBUSY) - { - return flag.ret; - } - } - else - { - store.val = ppu.gpr[6]; - return CELL_OK; - } - - while (auto state = +ppu.state) - { - if (state & cpu_flag::signal && ppu.state.test_and_reset(cpu_flag::signal)) - { - break; - } - - if (is_stopped(state)) - { - std::lock_guard lock(flag->mutex); - - for (auto cpu = +flag->sq; cpu; cpu = cpu->next_cpu) - { - if (cpu == &ppu) - { - ppu.state += cpu_flag::again; - return {}; - } - } - - break; - } - - for (usz i = 0; cpu_flag::signal - ppu.state && i < 50; i++) - { - busy_wait(500); - } - - if (ppu.state & cpu_flag::signal) - { - continue; - } - - if (timeout) - { - if (lv2_obj::wait_timeout(timeout, &ppu)) - { - // Wait for rescheduling - if (ppu.check_state()) - { - continue; - } - - ppu.state += cpu_flag::wait; - - if (!atomic_storage::load(flag->sq)) - { - // Waiters queue is empty, so the thread must have been signaled - flag->mutex.lock_unlock(); - break; - } - - std::lock_guard lock(flag->mutex); - - if (!flag->unqueue(flag->sq, &ppu)) - { - break; - } - - ppu.gpr[3] = CELL_ETIMEDOUT; - ppu.gpr[6] = flag->pattern; - break; - } - } - else - { - ppu.state.wait(state); - } - } - - store.val = ppu.gpr[6]; - return not_an_error(ppu.gpr[3]); -} - -error_code sys_event_flag_trywait(ppu_thread& ppu, u32 id, u64 bitptn, u32 mode, vm::ptr result) -{ - ppu.state += cpu_flag::wait; - - sys_event_flag.trace("sys_event_flag_trywait(id=0x%x, bitptn=0x%llx, mode=0x%x, result=*0x%x)", id, bitptn, mode, result); - - // Always set result - struct store_result - { - vm::ptr ptr; - u64 val = 0; - - ~store_result() noexcept - { - if (ptr) - { - cpu_thread::get_current()->check_state(); - *ptr = val; - } - } - } store{result}; - - if (!lv2_event_flag::check_mode(mode)) - { - sys_event_flag.error("sys_event_flag_trywait(): unknown mode (0x%x)", mode); - return CELL_EINVAL; - } - - u64 pattern{}; - - const auto flag = idm::check(id, [&](lv2_event_flag& flag) - { - return flag.pattern.fetch_op([&](u64& pat) - { - return lv2_event_flag::check_pattern(pat, bitptn, mode, &pattern); - }) - .second; - }); - - if (!flag) - { - return CELL_ESRCH; - } - - if (!flag.ret) - { - return not_an_error(CELL_EBUSY); - } - - store.val = pattern; - return CELL_OK; -} - -error_code sys_event_flag_set(cpu_thread& cpu, u32 id, u64 bitptn) -{ - cpu.state += cpu_flag::wait; - - // Warning: may be called from SPU thread. - sys_event_flag.trace("sys_event_flag_set(id=0x%x, bitptn=0x%llx)", id, bitptn); - - const auto flag = idm::get_unlocked(id); - - if (!flag) - { - return CELL_ESRCH; - } - - if ((flag->pattern & bitptn) == bitptn) - { - return CELL_OK; - } - - if (lv2_obj::notify_all_t notify; true) - { - std::lock_guard lock(flag->mutex); - - for (auto ppu = +flag->sq; ppu; ppu = ppu->next_cpu) - { - if (ppu->state & cpu_flag::again) - { - cpu.state += cpu_flag::again; - - // Fake error for abort - return not_an_error(CELL_EAGAIN); - } - } - - u32 count = 0; - - // Process all waiters in single atomic op - for (u64 pattern = flag->pattern, to_write = pattern, dependant_mask = 0;; to_write = pattern, dependant_mask = 0) - { - count = 0; - to_write |= bitptn; - dependant_mask = 0; - - for (auto ppu = +flag->sq; ppu; ppu = ppu->next_cpu) - { - ppu->gpr[7] = 0; - } - - auto first = +flag->sq; - - auto get_next = [&]() -> ppu_thread* - { - s32 prio = smax; - ppu_thread* it{}; - - for (auto ppu = first; ppu; ppu = ppu->next_cpu) - { - if (!ppu->gpr[7] && (flag->protocol != SYS_SYNC_PRIORITY || ppu->prio.load().prio <= prio)) - { - it = ppu; - prio = ppu->prio.load().prio; - } - } - - if (it) - { - // Mark it so it won't reappear - it->gpr[7] = 1; - } - - return it; - }; - - while (auto it = get_next()) - { - auto& ppu = *it; - - const u64 pattern = ppu.gpr[4]; - const u64 mode = ppu.gpr[5]; - - // If it's OR mode, set bits must have waken up the thread therefore no - // dependency on old value - const u64 dependant_mask_or = ((mode & 0xf) == SYS_EVENT_FLAG_WAIT_OR || (bitptn & pattern & to_write) == pattern ? 0 : pattern); - - if (lv2_event_flag::check_pattern(to_write, pattern, mode, &ppu.gpr[6])) - { - dependant_mask |= dependant_mask_or; - ppu.gpr[3] = CELL_OK; - count++; - - if (!to_write) - { - break; - } - } - else - { - ppu.gpr[3] = -1; - } - } - - dependant_mask &= ~bitptn; - - auto [new_val, ok] = flag->pattern.fetch_op([&](u64& x) - { - if ((x ^ pattern) & dependant_mask) - { - return false; - } - - x |= bitptn; - - // Clear the bit-wise difference - x &= ~((pattern | bitptn) & ~to_write); - return true; - }); - - if (ok) - { - break; - } - - pattern = new_val; - } - - if (!count) - { - return CELL_OK; - } - - // Remove waiters - for (auto next_cpu = &flag->sq; *next_cpu;) - { - auto& ppu = **next_cpu; - - if (ppu.gpr[3] == CELL_OK) - { - atomic_storage::release(*next_cpu, ppu.next_cpu); - ppu.next_cpu = nullptr; - flag->append(&ppu); - continue; - } - - next_cpu = &ppu.next_cpu; - }; - - lv2_obj::awake_all(); - } - - return CELL_OK; -} - -error_code sys_event_flag_clear(ppu_thread& ppu, u32 id, u64 bitptn) -{ - ppu.state += cpu_flag::wait; - - sys_event_flag.trace("sys_event_flag_clear(id=0x%x, bitptn=0x%llx)", id, bitptn); - - const auto flag = idm::check(id, [&](lv2_event_flag& flag) - { - flag.pattern &= bitptn; - }); - - if (!flag) - { - return CELL_ESRCH; - } - - return CELL_OK; -} - -error_code sys_event_flag_cancel(ppu_thread& ppu, u32 id, vm::ptr num) -{ - ppu.state += cpu_flag::wait; - - sys_event_flag.trace("sys_event_flag_cancel(id=0x%x, num=*0x%x)", id, num); - - if (num) - *num = 0; - - const auto flag = idm::get_unlocked(id); - - if (!flag) - { - return CELL_ESRCH; - } - - u32 value = 0; - { - lv2_obj::notify_all_t notify; - - std::lock_guard lock(flag->mutex); - - for (auto cpu = +flag->sq; cpu; cpu = cpu->next_cpu) - { - if (cpu->state & cpu_flag::again) - { - ppu.state += cpu_flag::again; - return {}; - } - } - - // Get current pattern - const u64 pattern = flag->pattern; - - // Signal all threads to return CELL_ECANCELED (protocol does not matter) - while (auto ppu = flag->schedule(flag->sq, SYS_SYNC_FIFO)) - { - ppu->gpr[3] = CELL_ECANCELED; - ppu->gpr[6] = pattern; - - value++; - flag->append(ppu); - } - - if (value) - { - lv2_obj::awake_all(); - } - } - - static_cast(ppu.test_stopped()); - - if (num) - *num = value; - return CELL_OK; -} - -error_code sys_event_flag_get(ppu_thread& ppu, u32 id, vm::ptr flags) -{ - ppu.state += cpu_flag::wait; - - sys_event_flag.trace("sys_event_flag_get(id=0x%x, flags=*0x%x)", id, flags); - - const auto flag = idm::check(id, [](lv2_event_flag& flag) - { - return +flag.pattern; - }); - - ppu.check_state(); - - if (!flag) - { - if (flags) - *flags = 0; - return CELL_ESRCH; - } - - if (!flags) - { - return CELL_EFAULT; - } - - *flags = flag.ret; - return CELL_OK; -} diff --git a/rpcs3/Emu/Cell/lv2/sys_event_flag.h b/rpcs3/Emu/Cell/lv2/sys_event_flag.h deleted file mode 100644 index db7d4d43d..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_event_flag.h +++ /dev/null @@ -1,119 +0,0 @@ -#pragma once - -#include "sys_sync.h" - -#include "Emu/Memory/vm_ptr.h" - -enum -{ - SYS_SYNC_WAITER_SINGLE = 0x10000, - SYS_SYNC_WAITER_MULTIPLE = 0x20000, - - SYS_EVENT_FLAG_WAIT_AND = 0x01, - SYS_EVENT_FLAG_WAIT_OR = 0x02, - - SYS_EVENT_FLAG_WAIT_CLEAR = 0x10, - SYS_EVENT_FLAG_WAIT_CLEAR_ALL = 0x20, -}; - -struct sys_event_flag_attribute_t -{ - be_t protocol; - be_t pshared; - be_t ipc_key; - be_t flags; - be_t type; - - union - { - nse_t name_u64; - char name[sizeof(u64)]; - }; -}; - -struct lv2_event_flag final : lv2_obj -{ - static const u32 id_base = 0x98000000; - - const lv2_protocol protocol; - const u64 key; - const s32 type; - const u64 name; - - shared_mutex mutex; - atomic_t pattern; - ppu_thread* sq{}; - - lv2_event_flag(u32 protocol, u64 key, s32 type, u64 name, u64 pattern) noexcept - : protocol{static_cast(protocol)}, key(key), type(type), name(name), pattern(pattern) - { - } - - lv2_event_flag(utils::serial& ar); - static std::function load(utils::serial& ar); - void save(utils::serial& ar); - - // Check mode arg - static bool check_mode(u32 mode) - { - switch (mode & 0xf) - { - case SYS_EVENT_FLAG_WAIT_AND: break; - case SYS_EVENT_FLAG_WAIT_OR: break; - default: return false; - } - - switch (mode & ~0xf) - { - case 0: break; - case SYS_EVENT_FLAG_WAIT_CLEAR: break; - case SYS_EVENT_FLAG_WAIT_CLEAR_ALL: break; - default: return false; - } - - return true; - } - - // Check and clear pattern (must be atomic op) - static bool check_pattern(u64& pattern, u64 bitptn, u64 mode, u64* result) - { - // Write pattern - if (result) - { - *result = pattern; - } - - // Check pattern - if (((mode & 0xf) == SYS_EVENT_FLAG_WAIT_AND && (pattern & bitptn) != bitptn) || - ((mode & 0xf) == SYS_EVENT_FLAG_WAIT_OR && (pattern & bitptn) == 0)) - { - return false; - } - - // Clear pattern if necessary - if ((mode & ~0xf) == SYS_EVENT_FLAG_WAIT_CLEAR) - { - pattern &= ~bitptn; - } - else if ((mode & ~0xf) == SYS_EVENT_FLAG_WAIT_CLEAR_ALL) - { - pattern = 0; - } - - return true; - } -}; - -// Aux -class ppu_thread; - -// Syscalls - -error_code sys_event_flag_create(ppu_thread& ppu, vm::ptr id, vm::ptr attr, u64 init); -error_code sys_event_flag_destroy(ppu_thread& ppu, u32 id); -error_code sys_event_flag_wait(ppu_thread& ppu, u32 id, u64 bitptn, u32 mode, vm::ptr result, u64 timeout); -error_code sys_event_flag_trywait(ppu_thread& ppu, u32 id, u64 bitptn, u32 mode, vm::ptr result); -error_code sys_event_flag_set(cpu_thread& cpu, u32 id, u64 bitptn); -error_code sys_event_flag_clear(ppu_thread& ppu, u32 id, u64 bitptn); -error_code sys_event_flag_cancel(ppu_thread& ppu, u32 id, vm::ptr num); -error_code sys_event_flag_get(ppu_thread& ppu, u32 id, vm::ptr flags); diff --git a/rpcs3/Emu/Cell/lv2/sys_fs.cpp b/rpcs3/Emu/Cell/lv2/sys_fs.cpp deleted file mode 100644 index 97ced5c50..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_fs.cpp +++ /dev/null @@ -1,3391 +0,0 @@ -#include "stdafx.h" -#include "sys_sync.h" -#include "sys_fs.h" -#include "sys_memory.h" -#include "util/asm.hpp" - -#include "Emu/Cell/PPUThread.h" -#include "Crypto/unedat.h" -#include "Emu/System.h" -#include "Emu/system_config.h" -#include "Emu/VFS.h" -#include "Emu/vfs_config.h" -#include "Emu/IdManager.h" -#include "Emu/system_utils.hpp" -#include "Emu/Cell/lv2/sys_process.h" - -#include -#include -#include - -LOG_CHANNEL(sys_fs); - -lv2_fs_mount_point g_mp_sys_dev_usb{"/dev_usb", "CELL_FS_FAT", "CELL_FS_IOS:USB_MASS_STORAGE", 512, 0x100, 4096, lv2_mp_flag::no_uid_gid}; -lv2_fs_mount_point g_mp_sys_dev_dvd{"/dev_ps2disc", "CELL_FS_ISO9660", "CELL_FS_IOS:PATA1_BDVD_DRIVE", 2048, 0x100, 32768, lv2_mp_flag::read_only + lv2_mp_flag::no_uid_gid, &g_mp_sys_dev_usb}; -lv2_fs_mount_point g_mp_sys_dev_bdvd{"/dev_bdvd", "CELL_FS_ISO9660", "CELL_FS_IOS:PATA0_BDVD_DRIVE", 2048, 0x4D955, 2048, lv2_mp_flag::read_only + lv2_mp_flag::no_uid_gid, &g_mp_sys_dev_dvd}; -lv2_fs_mount_point g_mp_sys_dev_hdd1{"/dev_hdd1", "CELL_FS_FAT", "CELL_FS_UTILITY:HDD1", 512, 0x3FFFF8, 32768, lv2_mp_flag::no_uid_gid + lv2_mp_flag::cache, &g_mp_sys_dev_bdvd}; -lv2_fs_mount_point g_mp_sys_dev_hdd0{"/dev_hdd0", "CELL_FS_UFS", "CELL_FS_UTILITY:HDD0", 512, 0x24FAEA98, 4096, {}, &g_mp_sys_dev_hdd1}; -lv2_fs_mount_point g_mp_sys_dev_flash3{"/dev_flash3", "CELL_FS_FAT", "CELL_FS_IOS:BUILTIN_FLSH3", 512, 0x400, 8192, lv2_mp_flag::no_uid_gid, &g_mp_sys_dev_hdd0}; // TODO confirm -lv2_fs_mount_point g_mp_sys_dev_flash2{"/dev_flash2", "CELL_FS_FAT", "CELL_FS_IOS:BUILTIN_FLSH2", 512, 0x8000, 8192, lv2_mp_flag::no_uid_gid, &g_mp_sys_dev_flash3}; // TODO confirm -lv2_fs_mount_point g_mp_sys_dev_flash{"/dev_flash", "CELL_FS_FAT", "CELL_FS_IOS:BUILTIN_FLSH1", 512, 0x63E00, 8192, lv2_mp_flag::no_uid_gid, &g_mp_sys_dev_flash2}; -lv2_fs_mount_point g_mp_sys_host_root{"/host_root", "CELL_FS_DUMMYFS", "CELL_FS_DUMMY:/", 512, 0x100, 512, lv2_mp_flag::strict_get_block_size + lv2_mp_flag::no_uid_gid, &g_mp_sys_dev_flash}; -lv2_fs_mount_point g_mp_sys_app_home{"/app_home", "CELL_FS_DUMMYFS", "CELL_FS_DUMMY:", 512, 0x100, 512, lv2_mp_flag::strict_get_block_size + lv2_mp_flag::no_uid_gid, &g_mp_sys_host_root}; -lv2_fs_mount_point g_mp_sys_dev_root{"/", "CELL_FS_ADMINFS", "CELL_FS_ADMINFS:", 512, 0x100, 512, lv2_mp_flag::read_only + lv2_mp_flag::strict_get_block_size + lv2_mp_flag::no_uid_gid, &g_mp_sys_app_home}; -lv2_fs_mount_point g_mp_sys_no_device{}; -lv2_fs_mount_info g_mi_sys_not_found{}; // wrapper for &g_mp_sys_no_device - -template <> -void fmt_class_string::format(std::string& out, u64 arg) -{ - format_enum(out, arg, [](lv2_file_type type) - { - switch (type) - { - case lv2_file_type::regular: return "Regular file"; - case lv2_file_type::sdata: return "SDATA"; - case lv2_file_type::edata: return "EDATA"; - } - - return unknown; - }); -} - -template <> -void fmt_class_string::format(std::string& out, u64 arg) -{ - const auto& file = get_object(arg); - - auto get_size = [](u64 size) -> std::string - { - if (size == umax) - { - return "N/A"; - } - - std::string size_str; - switch (std::bit_width(size) / 10 * 10) - { - case 0: fmt::append(size_str, "%u", size); break; - case 10: fmt::append(size_str, "%gKB", size / 1024.); break; - case 20: fmt::append(size_str, "%gMB", size / (1024. * 1024)); break; - - default: - case 30: fmt::append(size_str, "%gGB", size / (1024. * 1024 * 1024)); break; - } - - return size_str; - }; - - const usz pos = file.file ? file.file.pos() : umax; - const usz size = file.file ? file.file.size() : umax; - - fmt::append(out, u8"%s, '%s', Mode: 0x%x, Flags: 0x%x, Pos/Size: %s/%s (0x%x/0x%x)", file.type, file.name.data(), file.mode, file.flags, get_size(pos), get_size(size), pos, size); -} - -template <> -void fmt_class_string::format(std::string& out, u64 arg) -{ - const auto& dir = get_object(arg); - - fmt::append(out, u8"Directory, '%s', Entries: %u/%u", dir.name.data(), std::min(dir.pos, dir.entries.size()), dir.entries.size()); -} - -bool has_fs_write_rights(std::string_view vpath) -{ - // VSH has access to everything - if (g_ps3_process_info.has_root_perm()) - return true; - - const auto norm_vpath = lv2_fs_object::get_normalized_path(vpath); - const auto parent_dir = fs::get_parent_dir_view(norm_vpath); - - // This is not exhaustive, PS3 has a unix filesystem with rights for each directory and files - // This is mostly meant to protect against games doing insane things(ie NPUB30003 => NPUB30008) - if (parent_dir == "/dev_hdd0" || parent_dir == "/dev_hdd0/game") - return false; - - return true; -} - -bool verify_mself(const fs::file& mself_file) -{ - FsMselfHeader mself_header; - if (!mself_file.read(mself_header)) - { - sys_fs.error("verify_mself: Didn't read expected bytes for header."); - return false; - } - - if (mself_header.m_magic != 0x4D534600u) - { - sys_fs.error("verify_mself: Header magic is incorrect."); - return false; - } - - if (mself_header.m_format_version != 1u) - { - sys_fs.error("verify_mself: Unexpected header format version."); - return false; - } - - // sanity check - if (mself_header.m_entry_size != sizeof(FsMselfEntry)) - { - sys_fs.error("verify_mself: Unexpected header entry size."); - return false; - } - - mself_file.seek(0); - - return true; -} - -lv2_fs_mount_info_map::lv2_fs_mount_info_map() -{ - for (auto mp = &g_mp_sys_dev_root; mp; mp = mp->next) // Scan and keep track of pre-mounted devices - { - if (mp == &g_mp_sys_dev_usb) - { - for (int i = 0; i < 8; i++) - { - if (!vfs::get(fmt::format("%s%03d", mp->root, i)).empty()) - { - add(fmt::format("%s%03d", mp->root, i), mp, fmt::format("%s%03d", mp->device, i), mp->file_system, false); - } - } - } - else if (mp == &g_mp_sys_dev_root || !vfs::get(mp->root).empty()) - { - add(std::string(mp->root), mp, mp->device, mp->file_system, mp == &g_mp_sys_dev_flash); // /dev_flash is mounted in read only mode initially - } - } -} - -lv2_fs_mount_info_map::~lv2_fs_mount_info_map() -{ - for (const auto& [path, info] : map) - vfs_unmount(path, false); // Do not remove the value from the map we are iterating over. -} - -bool lv2_fs_mount_info_map::remove(std::string_view path) -{ - if (const auto iterator = map.find(path); iterator != map.end()) - { - map.erase(iterator); - return true; - } - return false; -} - -const lv2_fs_mount_info& lv2_fs_mount_info_map::lookup(std::string_view path, bool no_cell_fs_path, std::string* mount_path) const -{ - if (path.starts_with("/"sv)) - { - constexpr std::string_view cell_fs_path = "CELL_FS_PATH:"sv; - const std::string normalized_path = lv2_fs_object::get_normalized_path(path); - std::string_view parent_dir; - u32 parent_level = 0; - - do - { - parent_dir = fs::get_parent_dir_view(normalized_path, parent_level++); - if (const auto iterator = map.find(parent_dir); iterator != map.end()) - { - if (iterator->second == &g_mp_sys_dev_root && parent_level > 1) - break; - if (no_cell_fs_path && iterator->second.device.starts_with(cell_fs_path)) - return lookup(iterator->second.device.substr(cell_fs_path.size()), no_cell_fs_path, mount_path); // Recursively look up the parent mount info - if (mount_path) - *mount_path = iterator->first; - return iterator->second; - } - } while (parent_dir.length() > 1); // Exit the loop when parent_dir == "/" or empty - } - - return g_mi_sys_not_found; -} - -u64 lv2_fs_mount_info_map::get_all(CellFsMountInfo* info, u64 len) const -{ - if (!info) - return map.size(); - - u64 count = 0; - - for (const auto& [path, mount_info] : map) - { - if (count >= len) - break; - - strcpy_trunc(info[count].mount_path, path); - strcpy_trunc(info[count].filesystem, mount_info.file_system); - strcpy_trunc(info[count].dev_name, mount_info.device); - if (mount_info.read_only) - info[count].unk[4] |= 0x10000000; - - count++; - } - - return count; -} - -bool lv2_fs_mount_info_map::is_device_mounted(std::string_view device_name) const -{ - return std::any_of(map.begin(), map.end(), [&](const decltype(map)::value_type& info) - { - return info.second.device == device_name; - }); -} - -bool lv2_fs_mount_info_map::vfs_unmount(std::string_view vpath, bool remove_from_map) -{ - const std::string local_path = vfs::get(vpath); - - if (local_path.empty()) - return false; - - if (fs::is_file(local_path)) - { - if (fs::remove_file(local_path)) - { - sys_fs.notice("Removed simplefs file \"%s\"", local_path); - } - else - { - sys_fs.error("Failed to remove simplefs file \"%s\"", local_path); - } - } - - const bool result = vfs::unmount(vpath); - - if (result && remove_from_map) - g_fxo->get().remove(vpath); - - return result; -} - -std::string lv2_fs_object::get_normalized_path(std::string_view path) -{ - std::string normalized_path = std::filesystem::path(path).lexically_normal().string(); - -#ifdef _WIN32 - std::replace(normalized_path.begin(), normalized_path.end(), '\\', '/'); -#endif - - if (normalized_path.ends_with('/')) - normalized_path.pop_back(); - - return normalized_path.empty() ? "/" : normalized_path; -} - -std::string lv2_fs_object::get_device_root(std::string_view filename) -{ - std::string path = get_normalized_path(filename); // Prevent getting fooled by ".." trick such as "/dev_usb000/../dev_flash" - - if (const auto first = path.find_first_not_of("/"sv); first != umax) - { - if (const auto pos = path.substr(first).find_first_of("/"sv); pos != umax) - path = path.substr(0, first + pos); - path = path.substr(std::max>(0, first - 1)); // Remove duplicate leading '/' while keeping only one - } - else - { - path = path.substr(0, 1); - } - - return path; -} - -lv2_fs_mount_point* lv2_fs_object::get_mp(std::string_view filename, std::string* vfs_path) -{ - constexpr std::string_view cell_fs_path = "CELL_FS_PATH:"sv; - const bool is_cell_fs_path = filename.starts_with(cell_fs_path); - - if (is_cell_fs_path) - filename.remove_prefix(cell_fs_path.size()); - - const bool is_path = filename.starts_with("/"sv); - std::string mp_name = is_path ? get_device_root(filename) : std::string(filename); - - const auto check_mp = [&]() - { - for (auto mp = &g_mp_sys_dev_root; mp; mp = mp->next) - { - const auto& device_alias_check = !is_path && ((mp == &g_mp_sys_dev_hdd0 && mp_name == "CELL_FS_IOS:PATA0_HDD_DRIVE"sv) || - (mp == &g_mp_sys_dev_hdd1 && mp_name == "CELL_FS_IOS:PATA1_HDD_DRIVE"sv) || - (mp == &g_mp_sys_dev_flash2 && mp_name == "CELL_FS_IOS:BUILTIN_FLASH"sv)); // TODO confirm - - if (mp == &g_mp_sys_dev_usb) - { - if (mp_name.starts_with(is_path ? mp->root : mp->device)) - { - if (!is_path) - mp_name = fmt::format("%s%s", mp->root, mp_name.substr(mp->device.size())); - return mp; - } - } - else if ((is_path ? mp->root : mp->device) == mp_name || device_alias_check) - { - if (!is_path) - mp_name = mp->root; - return mp; - } - } - return &g_mp_sys_no_device; // Default fallback - }; - - const auto result = check_mp(); - - if (vfs_path) - { - if (is_cell_fs_path) - *vfs_path = vfs::get(filename); - else if (result == &g_mp_sys_dev_hdd0) - *vfs_path = g_cfg_vfs.get(g_cfg_vfs.dev_hdd0, rpcs3::utils::get_emu_dir()); - else if (result == &g_mp_sys_dev_hdd1) - *vfs_path = g_cfg_vfs.get(g_cfg_vfs.dev_hdd1, rpcs3::utils::get_emu_dir()); - else if (result == &g_mp_sys_dev_usb) - *vfs_path = g_cfg_vfs.get_device(g_cfg_vfs.dev_usb, mp_name, rpcs3::utils::get_emu_dir()).path; - else if (result == &g_mp_sys_dev_bdvd) - *vfs_path = g_cfg_vfs.get(g_cfg_vfs.dev_bdvd, rpcs3::utils::get_emu_dir()); - else if (result == &g_mp_sys_dev_dvd) - *vfs_path = g_cfg_vfs.get(g_cfg_vfs.dev_bdvd, rpcs3::utils::get_emu_dir()); // For compatibility - else if (result == &g_mp_sys_app_home) - *vfs_path = g_cfg_vfs.get(g_cfg_vfs.app_home, rpcs3::utils::get_emu_dir()); - else if (result == &g_mp_sys_host_root && g_cfg.vfs.host_root) - *vfs_path = "/"; - else if (result == &g_mp_sys_dev_flash) - *vfs_path = g_cfg_vfs.get_dev_flash(); - else if (result == &g_mp_sys_dev_flash2) - *vfs_path = g_cfg_vfs.get_dev_flash2(); - else if (result == &g_mp_sys_dev_flash3) - *vfs_path = g_cfg_vfs.get_dev_flash3(); - else - *vfs_path = {}; - - if (is_path && !is_cell_fs_path && !vfs_path->empty()) - vfs_path->append(filename.substr(mp_name.size())); - } - - return result; -} - -lv2_fs_object::lv2_fs_object(std::string_view filename) - : name(get_name(filename)), mp(g_fxo->get().lookup(name.data())) -{ -} - -lv2_fs_object::lv2_fs_object(utils::serial& ar, bool) - : name(ar), mp(g_fxo->get().lookup(name.data())) -{ -} - -u64 lv2_file::op_read(const fs::file& file, vm::ptr buf, u64 size, u64 opt_pos) -{ - if (u64 region = buf.addr() >> 28, region_end = (buf.addr() & 0xfff'ffff) + (size & 0xfff'ffff); region == region_end && ((region >> 28) == 0 || region >= 0xC)) - { - // Optimize reads from safe memory - return (opt_pos == umax ? file.read(buf.get_ptr(), size) : file.read_at(opt_pos, buf.get_ptr(), size)); - } - - // Copy data from intermediate buffer (avoid passing vm pointer to a native API) - std::vector local_buf(std::min(size, 65536)); - - u64 result = 0; - - while (result < size) - { - const u64 block = std::min(size - result, local_buf.size()); - const u64 nread = (opt_pos == umax ? file.read(local_buf.data(), block) : file.read_at(opt_pos + result, local_buf.data(), block)); - - std::memcpy(static_cast(buf.get_ptr()) + result, local_buf.data(), nread); - result += nread; - - if (nread < block) - { - break; - } - } - - return result; -} - -u64 lv2_file::op_write(const fs::file& file, vm::cptr buf, u64 size) -{ - // Copy data to intermediate buffer (avoid passing vm pointer to a native API) - std::vector local_buf(std::min(size, 65536)); - - u64 result = 0; - - while (result < size) - { - const u64 block = std::min(size - result, local_buf.size()); - std::memcpy(local_buf.data(), static_cast(buf.get_ptr()) + result, block); - const u64 nwrite = file.write(+local_buf.data(), block); - result += nwrite; - - if (nwrite < block) - { - break; - } - } - - return result; -} - -lv2_file::lv2_file(utils::serial& ar) - : lv2_fs_object(ar, false), mode(ar), flags(ar), type(ar) -{ - [[maybe_unused]] const s32 version = GET_SERIALIZATION_VERSION(lv2_fs); - - ar(lock); - - be_t arg = 0; - u64 size = 0; - - switch (type) - { - case lv2_file_type::regular: break; - case lv2_file_type::sdata: arg = 0x18000000010, size = 8; break; // TODO: Fix - case lv2_file_type::edata: arg = 0x2, size = 8; break; - } - - const std::string retrieve_real = ar.pop(); - - if (type == lv2_file_type::edata && version >= 2) - { - ar(g_fxo->get().one_time_key); - } - - open_result_t res = lv2_file::open(retrieve_real, flags & CELL_FS_O_ACCMODE, mode, size ? &arg : nullptr, size); - file = std::move(res.file); - real_path = std::move(res.real_path); - - g_fxo->get().npdrm_fds.raw() += type != lv2_file_type::regular; - g_fxo->get().one_time_key = {}; - - if (ar.pop()) // see lv2_file::save in_mem - { - const fs::stat_t stat = ar; - - std::vector buf(stat.size); - ar(std::span(buf.data(), buf.size())); - - file = fs::make_stream>(std::move(buf), stat); - } - - if (!file) - { - sys_fs.error("Failed to load \'%s\' file for savestates (res=%s, vpath=\'%s\', real-path=\'%s\', type=%s, flags=0x%x)", name.data(), res.error, retrieve_real, real_path, type, flags); - ar.pos += sizeof(u64); - ensure(!!g_cfg.savestate.state_inspection_mode); - return; - } - else - { - sys_fs.success("Loaded file descriptor \'%s\' file for savestates (vpath=\'%s\', type=%s, flags=0x%x, id=%d)", name.data(), retrieve_real, type, flags, idm::last_id()); - } - - file.seek(ar); -} - -void lv2_file::save(utils::serial& ar) -{ - USING_SERIALIZATION_VERSION(lv2_fs); - ar(name, mode, flags, type, lock, ensure(vfs::retrieve(real_path), FN(!x.empty()))); - - if (type == lv2_file_type::edata) - { - auto file_ptr = file.release(); - ar(static_cast(file_ptr.get())->get_key()); - file.reset(std::move(file_ptr)); - } - - if (!mp.read_only && flags & CELL_FS_O_ACCMODE) - { - // Ensure accurate timestamps and content on disk - file.sync(); - } - - // UNIX allows deletion of files while descriptors are still opened - // descriptors shall keep the data in memory in this case - const bool in_mem = [&]() - { - if (mp.read_only) - { - return false; - } - - fs::file test{real_path}; - - if (!test) - { - if (fs::is_file(real_path + ".66600")) - { - // May be a split-files descriptor, don't even bother - return false; - } - - return true; - } - - fs::file_id test_s = test.get_id(); - fs::file_id file_s = file.get_id(); - - return !test_s.is_coherent_with(file_s); - }(); - - ar(in_mem); - - if (in_mem) - { - fs::stat_t stats = file.get_stat(); - - sys_fs.error("Saving \'%s\' LV2 file descriptor in memory! (exists=%s, type=%s, flags=0x%x, size=0x%x)", name.data(), fs::is_file(real_path), type, flags, stats.size); - - const usz patch_stats_pos = ar.seek_end(); - - ar(stats); - - const usz old_end = ar.pad_from_end(stats.size); - - if (usz read_size = file.read_at(0, &ar.data[old_end], stats.size); read_size != stats.size) - { - ensure(read_size < stats.size); - sys_fs.error("Read less than expected! (new-size=0x%x)", read_size); - stats.size = read_size; - ar.data.resize(old_end + stats.size); - write_to_ptr(&ar.data[patch_stats_pos], stats); - } - } - - ar(file.pos()); -} - -lv2_dir::lv2_dir(utils::serial& ar) - : lv2_fs_object(ar, false), entries([&] - { - std::vector entries; - - u64 size = 0; - ar.deserialize_vle(size); - entries.resize(size); - - for (auto& entry : entries) - { - ar(entry.name, static_cast(entry)); - } - - return entries; - }()), - pos(ar) -{ -} - -void lv2_dir::save(utils::serial& ar) -{ - USING_SERIALIZATION_VERSION(lv2_fs); - - ar(name); - - ar.serialize_vle(entries.size()); - - for (auto& entry : entries) - { - ar(entry.name, static_cast(entry)); - } - - ar(pos); -} - -loaded_npdrm_keys::loaded_npdrm_keys(utils::serial& ar) -{ - save(ar); -} - -void loaded_npdrm_keys::save(utils::serial& ar) -{ - ar(dec_keys_pos); - ar(std::span(dec_keys, std::min(std::size(dec_keys), dec_keys_pos))); -} - -struct lv2_file::file_view : fs::file_base -{ - const shared_ptr m_file; - const u64 m_off; - u64 m_pos; - - explicit file_view(const shared_ptr& _file, u64 offset) - : m_file(_file), m_off(offset), m_pos(0) - { - } - - ~file_view() override - { - } - - fs::stat_t get_stat() override - { - fs::stat_t stat = m_file->file.get_stat(); - - // TODO: Check this on realhw - // stat.size = utils::sub_saturate(stat.size, m_off); - - stat.is_writable = false; - return stat; - } - - bool trunc(u64) override - { - return false; - } - - u64 read(void* buffer, u64 size) override - { - const u64 result = file_view::read_at(m_pos, buffer, size); - - m_pos += result; - return result; - } - - u64 read_at(u64 offset, void* buffer, u64 size) override - { - return m_file->file.read_at(m_off + offset, buffer, size); - } - - u64 write(const void*, u64) override - { - return 0; - } - - u64 seek(s64 offset, fs::seek_mode whence) override - { - const s64 new_pos = - whence == fs::seek_set ? offset : - whence == fs::seek_cur ? offset + m_pos : - whence == fs::seek_end ? offset + size() : - -1; - - if (new_pos < 0) - { - fs::g_tls_error = fs::error::inval; - return -1; - } - - m_pos = new_pos; - return m_pos; - } - - u64 size() override - { - return utils::sub_saturate(m_file->file.size(), m_off); - } - - fs::file_id get_id() override - { - fs::file_id id = m_file->file.get_id(); - - be_t off = m_off; - const auto ptr = reinterpret_cast(&off); - - id.data.insert(id.data.end(), ptr, ptr + sizeof(off)); - id.type.insert(0, "lv2_file::file_view: "sv); - return id; - } -}; - -fs::file lv2_file::make_view(const shared_ptr& _file, u64 offset) -{ - fs::file result; - result.reset(std::make_unique(_file, offset)); - return result; -} - -std::pair translate_to_str(vm::cptr ptr, bool is_path = true) -{ - constexpr usz max_length = CELL_FS_MAX_FS_PATH_LENGTH + 1; - - std::string path; - - if (!vm::read_string(ptr.addr(), max_length, path, true)) - { - // Null character lookup has ended whilst pointing at invalid memory - return {CELL_EFAULT, std::move(path)}; - } - - if (path.size() == max_length) - { - return {CELL_ENAMETOOLONG, {}}; - } - - if (is_path && !path.starts_with("/"sv)) - { - return {CELL_ENOENT, std::move(path)}; - } - - return {{}, std::move(path)}; -} - -error_code sys_fs_test(ppu_thread&, u32 arg1, u32 arg2, vm::ptr arg3, u32 arg4, vm::ptr buf, u32 buf_size) -{ - sys_fs.trace("sys_fs_test(arg1=0x%x, arg2=0x%x, arg3=*0x%x, arg4=0x%x, buf=*0x%x, buf_size=0x%x)", arg1, arg2, arg3, arg4, buf, buf_size); - - if (arg1 != 6 || arg2 != 0 || arg4 != sizeof(u32)) - { - sys_fs.todo("sys_fs_test: unknown arguments (arg1=0x%x, arg2=0x%x, arg3=*0x%x, arg4=0x%x)", arg1, arg2, arg3, arg4); - } - - if (!arg3) - { - return CELL_EFAULT; - } - - const auto file = idm::get_unlocked(*arg3); - - if (!file) - { - return CELL_EBADF; - } - - for (u32 i = 0; i < buf_size; i++) - { - if (!(buf[i] = file->name[i])) - { - return CELL_OK; - } - } - - buf[buf_size - 1] = 0; - return CELL_OK; -} - -lv2_file::open_raw_result_t lv2_file::open_raw(const std::string& local_path, s32 flags, s32 /*mode*/, lv2_file_type type, const lv2_fs_mount_info& mp) -{ - // TODO: other checks for path - - if (fs::is_dir(local_path)) - { - return {CELL_EISDIR}; - } - - bs_t open_mode{}; - - switch (flags & CELL_FS_O_ACCMODE) - { - case CELL_FS_O_RDONLY: open_mode += fs::read; break; - case CELL_FS_O_WRONLY: open_mode += fs::write; break; - case CELL_FS_O_RDWR: open_mode += fs::read + fs::write; break; - default: break; - } - - if (mp.read_only) - { - if ((flags & CELL_FS_O_ACCMODE) != CELL_FS_O_RDONLY && fs::is_file(local_path)) - { - return {CELL_EPERM}; - } - } - - if (flags & CELL_FS_O_CREAT) - { - open_mode += fs::create; - - if (flags & CELL_FS_O_EXCL) - { - open_mode += fs::excl; - } - } - - if (flags & CELL_FS_O_TRUNC) - { - open_mode += fs::trunc; - } - - if (flags & CELL_FS_O_MSELF) - { - open_mode = fs::read; - // mself can be mself or mself | rdonly - if (flags & ~(CELL_FS_O_MSELF | CELL_FS_O_RDONLY)) - { - open_mode = {}; - } - } - - if (flags & CELL_FS_O_UNK) - { - sys_fs.warning("lv2_file::open() called with CELL_FS_O_UNK flag enabled. FLAGS: %#o", flags); - } - - if (mp.read_only) - { - // Deactivate mutating flags on read-only FS - open_mode = fs::read; - } - - // Tests have shown that invalid combinations get resolved internally (without exceptions), but that would complicate code with minimal accuracy gains. - // For example, no games are known to try and call TRUNCATE | APPEND | RW, or APPEND | READ, which currently would cause an exception. - if (flags & ~(CELL_FS_O_UNK | CELL_FS_O_ACCMODE | CELL_FS_O_CREAT | CELL_FS_O_TRUNC | CELL_FS_O_APPEND | CELL_FS_O_EXCL | CELL_FS_O_MSELF)) - { - open_mode = {}; // error - } - - if ((flags & CELL_FS_O_ACCMODE) == CELL_FS_O_ACCMODE) - { - open_mode = {}; // error - } - - if (!open_mode) - { - fmt::throw_exception("lv2_file::open_raw(): Invalid or unimplemented flags: %#o", flags); - } - - std::lock_guard lock(mp->mutex); - - fs::file file(local_path, open_mode); - - if (!file && open_mode == fs::read && fs::g_tls_error == fs::error::noent) - { - // Try to gather split file (TODO) - std::vector fragments; - - for (u32 i = 66600; i <= 66699; i++) - { - if (fs::file fragment{fmt::format("%s.%u", local_path, i)}) - { - fragments.emplace_back(std::move(fragment)); - } - else - { - break; - } - } - - if (!fragments.empty()) - { - file = fs::make_gather(std::move(fragments)); - } - } - - if (!file) - { - if (mp.read_only) - { - // Failed to create file on read-only FS (file doesn't exist) - if (flags & CELL_FS_O_ACCMODE && flags & CELL_FS_O_CREAT) - { - return {CELL_EPERM}; - } - } - - if (open_mode & fs::excl && fs::g_tls_error == fs::error::exist) - { - return {CELL_EEXIST}; - } - - switch (auto error = fs::g_tls_error) - { - case fs::error::noent: return {CELL_ENOENT}; - default: sys_fs.error("lv2_file::open(): unknown error %s", error); - } - - return {CELL_EIO}; - } - - if (flags & CELL_FS_O_MSELF && !verify_mself(file)) - { - return {CELL_ENOTMSELF}; - } - - if (type >= lv2_file_type::sdata) - { - // check for sdata - switch (type) - { - case lv2_file_type::sdata: - { - // check if the file has the NPD header, or else assume its not encrypted - u32 magic; - file.read(magic); - file.seek(0); - if (magic == "NPD\0"_u32) - { - auto sdata_file = std::make_unique(std::move(file)); - if (!sdata_file->ReadHeader()) - { - return {CELL_EFSSPECIFIC}; - } - - file.reset(std::move(sdata_file)); - } - - break; - } - // edata - case lv2_file_type::edata: - { - // check if the file has the NPD header, or else assume its not encrypted - u32 magic; - file.read(magic); - file.seek(0); - if (magic == "NPD\0"_u32) - { - auto& edatkeys = g_fxo->get(); - - const u64 init_pos = edatkeys.dec_keys_pos; - const auto& dec_keys = edatkeys.dec_keys; - const u64 max_i = std::min(std::size(dec_keys), init_pos); - - if (edatkeys.one_time_key) - { - auto edata_file = std::make_unique(std::move(file), edatkeys.one_time_key); - edatkeys.one_time_key = {}; - - if (!edata_file->ReadHeader()) - { - // Read failure - return {CELL_EFSSPECIFIC}; - } - - file.reset(std::move(edata_file)); - break; - } - - for (u64 i = 0;; i++) - { - if (i == max_i) - { - // Run out of keys to try - return {CELL_EFSSPECIFIC}; - } - - // Try all registered keys - auto edata_file = std::make_unique(std::move(file), dec_keys[(init_pos - i - 1) % std::size(dec_keys)].load()); - if (!edata_file->ReadHeader()) - { - // Prepare file for the next iteration - file = std::move(edata_file->m_edata_file); - continue; - } - - file.reset(std::move(edata_file)); - break; - } - } - - break; - } - default: break; - } - } - - return {.error = {}, .file = std::move(file)}; -} - -lv2_file::open_result_t lv2_file::open(std::string_view vpath, s32 flags, s32 mode, const void* arg, u64 size) -{ - if (vpath.empty()) - { - return {CELL_ENOENT}; - } - - std::string path; - std::string local_path = vfs::get(vpath, nullptr, &path); - - const auto& mp = g_fxo->get().lookup(vpath); - - if (mp == &g_mp_sys_dev_root) - { - return {CELL_EISDIR, path}; - } - - if (local_path.empty()) - { - return {CELL_ENOTMOUNTED, path}; - } - - if (flags & CELL_FS_O_CREAT && !has_fs_write_rights(vpath) && !fs::is_dir(local_path)) - { - return {CELL_EACCES}; - } - - lv2_file_type type = lv2_file_type::regular; - - if (size == 8) - { - // see lv2_file::open_raw - switch (*static_cast*>(arg)) - { - case 0x18000000010: type = lv2_file_type::sdata; break; - case 0x2: type = lv2_file_type::edata; break; - default: - break; - } - } - - auto [error, file] = open_raw(local_path, flags, mode, type, mp); - - return {.error = error, .ppath = std::move(path), .real_path = std::move(local_path), .file = std::move(file), .type = type}; -} - -error_code sys_fs_open(ppu_thread& ppu, vm::cptr path, s32 flags, vm::ptr fd, s32 mode, vm::cptr arg, u64 size) -{ - ppu.state += cpu_flag::wait; - lv2_obj::sleep(ppu); - - sys_fs.warning("sys_fs_open(path=%s, flags=%#o, fd=*0x%x, mode=%#o, arg=*0x%x, size=0x%llx)", path, flags, fd, mode, arg, size); - - const auto [path_error, vpath] = translate_to_str(path); - - if (path_error) - { - return {path_error, vpath}; - } - - auto [error, ppath, real, file, type] = lv2_file::open(vpath, flags, mode, arg.get_ptr(), size); - - if (error) - { - if (error == CELL_EEXIST) - { - return not_an_error(CELL_EEXIST); - } - - return {g_fxo->get().lookup(vpath) == &g_mp_sys_dev_hdd1 ? sys_fs.warning : sys_fs.error, error, path}; - } - - if (const u32 id = idm::import ([&ppath = ppath, &file = file, mode, flags, &real = real, &type = type]() -> shared_ptr - { - shared_ptr result; - - if (type >= lv2_file_type::sdata && !g_fxo->get().npdrm_fds.try_inc(16)) - { - return result; - } - - result = stx::make_shared(ppath, std::move(file), mode, flags, real, type); - sys_fs.warning("sys_fs_open(): fd=%u, %s", idm::last_id(), *result); - return result; - })) - { - ppu.check_state(); - *fd = id; - return CELL_OK; - } - - // Out of file descriptors - return {CELL_EMFILE, path}; -} - -error_code sys_fs_read(ppu_thread& ppu, u32 fd, vm::ptr buf, u64 nbytes, vm::ptr nread) -{ - ppu.state += cpu_flag::wait; - lv2_obj::sleep(ppu); - - sys_fs.trace("sys_fs_read(fd=%d, buf=*0x%x, nbytes=0x%llx, nread=*0x%x)", fd, buf, nbytes, nread); - - if (!nread) - { - return CELL_EFAULT; - } - - if (!buf) - { - nread.try_write(0); - return CELL_EFAULT; - } - - const auto file = idm::get_unlocked(fd); - - if (!file || (nbytes && file->flags & CELL_FS_O_WRONLY)) - { - nread.try_write(0); // nread writing is allowed to fail, error code is unchanged - return CELL_EBADF; - } - - if (!nbytes) - { - // Whole function is skipped, only EBADF and EBUSY are checked - if (file->lock == 1) - { - nread.try_write(0); - return CELL_EBUSY; - } - - ppu.check_state(); - *nread = 0; - return CELL_OK; - } - - std::unique_lock lock(file->mp->mutex); - - if (!file->file) - { - return CELL_EBADF; - } - - if (file->lock == 2) - { - nread.try_write(0); - return CELL_EIO; - } - - const u64 read_bytes = file->op_read(buf, nbytes); - const bool failure = !read_bytes && file->file.pos() < file->file.size(); - lock.unlock(); - ppu.check_state(); - - *nread = read_bytes; - - if (failure) - { - // EDATA corruption perhaps - return CELL_EFSSPECIFIC; - } - - return CELL_OK; -} - -error_code sys_fs_write(ppu_thread& ppu, u32 fd, vm::cptr buf, u64 nbytes, vm::ptr nwrite) -{ - ppu.state += cpu_flag::wait; - lv2_obj::sleep(ppu); - - sys_fs.trace("sys_fs_write(fd=%d, buf=*0x%x, nbytes=0x%llx, nwrite=*0x%x)", fd, buf, nbytes, nwrite); - - if (!nwrite) - { - return CELL_EFAULT; - } - - if (!buf) - { - nwrite.try_write(0); - return CELL_EFAULT; - } - - const auto file = idm::get_unlocked(fd); - - if (!file || (nbytes && !(file->flags & CELL_FS_O_ACCMODE))) - { - nwrite.try_write(0); // nwrite writing is allowed to fail, error code is unchanged - return CELL_EBADF; - } - - if (!nbytes) - { - // Whole function is skipped, only EBADF and EBUSY are checked - if (file->lock == 1) - { - nwrite.try_write(0); - return CELL_EBUSY; - } - - ppu.check_state(); - *nwrite = 0; - return CELL_OK; - } - - if (file->type != lv2_file_type::regular) - { - sys_fs.error("%s type: Writing %u bytes to FD=%d (path=%s)", file->type, nbytes, file->name.data()); - } - - if (file->mp.read_only) - { - nwrite.try_write(0); - return CELL_EROFS; - } - - std::unique_lock lock(file->mp->mutex); - - if (!file->file) - { - return CELL_EBADF; - } - - if (file->lock) - { - if (file->lock == 2) - { - nwrite.try_write(0); - return CELL_EIO; - } - - nwrite.try_write(0); - return CELL_EBUSY; - } - - if (file->flags & CELL_FS_O_APPEND) - { - file->file.seek(0, fs::seek_end); - } - - const u64 written = file->op_write(buf, nbytes); - lock.unlock(); - ppu.check_state(); - - *nwrite = written; - return CELL_OK; -} - -error_code sys_fs_close(ppu_thread& ppu, u32 fd) -{ - ppu.state += cpu_flag::wait; - lv2_obj::sleep(ppu); - - const auto file = idm::get_unlocked(fd); - - if (!file) - { - return {CELL_EBADF, fd}; - } - - std::string FD_state_log; - - if (sys_fs.warning) - { - FD_state_log = fmt::format("sys_fs_close(fd=%u)", fd); - } - - { - std::lock_guard lock(file->mp->mutex); - - if (!file->file) - { - sys_fs.warning("%s", FD_state_log); - return {CELL_EBADF, fd}; - } - - if (!(file->mp.read_only && file->mp->flags & lv2_mp_flag::cache) && file->flags & CELL_FS_O_ACCMODE) - { - // Special: Ensure temporary directory for gamedata writes will remain on disk before final gamedata commitment - file->file.sync(); // For cellGameContentPermit atomicity - } - - if (!FD_state_log.empty()) - { - sys_fs.warning("%s: %s", FD_state_log, *file); - } - - // Free memory associated with fd if any - if (file->ct_id && file->ct_used) - { - auto& default_container = g_fxo->get(); - std::lock_guard lock(default_container.mutex); - - if (auto ct = idm::get_unlocked(file->ct_id)) - { - ct->free(file->ct_used); - if (default_container.id == file->ct_id) - { - default_container.used -= file->ct_used; - } - } - } - - // Ensure Host file handle won't be kept open after this syscall - file->file.close(); - } - - ensure(idm::withdraw(fd, [&](lv2_file& _file) -> CellError - { - if (_file.type >= lv2_file_type::sdata) - { - g_fxo->get().npdrm_fds--; - } - - return {}; - })); - - if (file->lock == 1) - { - return {CELL_EBUSY, fd}; - } - - return CELL_OK; -} - -error_code sys_fs_opendir(ppu_thread& ppu, vm::cptr path, vm::ptr fd) -{ - ppu.state += cpu_flag::wait; - lv2_obj::sleep(ppu); - - sys_fs.warning("sys_fs_opendir(path=%s, fd=*0x%x)", path, fd); - - const auto [path_error, vpath] = translate_to_str(path); - - if (path_error) - { - return {path_error, vpath}; - } - - std::string processed_path; - std::vector ext; - const std::string local_path = vfs::get(vpath, &ext, &processed_path); - - processed_path += "/"; - - const auto& mp = g_fxo->get().lookup(vpath); - - if (local_path.empty() && ext.empty()) - { - return {CELL_ENOTMOUNTED, path}; - } - - // TODO: other checks for path - - if (fs::is_file(local_path)) - { - return {CELL_ENOTDIR, path}; - } - - std::unique_lock lock(mp->mutex); - - const fs::dir dir(local_path); - - if (!dir) - { - switch (const auto error = fs::g_tls_error) - { - case fs::error::noent: - { - if (ext.empty()) - { - return {mp == &g_mp_sys_dev_hdd1 ? sys_fs.warning : sys_fs.error, CELL_ENOENT, path}; - } - - break; - } - default: - { - sys_fs.error("sys_fs_opendir(): unknown error %s", error); - return {CELL_EIO, path}; - } - } - } - - // Build directory as a vector of entries - std::vector data; - - if (dir) - { - // Add real directories - while (dir.read(data.emplace_back())) - { - // Preprocess entries - data.back().name = vfs::unescape(data.back().name); - - if (!data.back().is_directory && data.back().name == "."sv) - { - // Files hidden from emulation - data.resize(data.size() - 1); - continue; - } - - // Add additional entries for split file candidates (while ends with .66600) - while (data.back().name.ends_with(".66600")) - { - data.emplace_back(data.back()).name.resize(data.back().name.size() - 6); - } - } - - data.resize(data.size() - 1); - } - else - { - data.emplace_back().name += '.'; - data.back().is_directory = true; - data.emplace_back().name = ".."; - data.back().is_directory = true; - } - - // Add mount points (TODO) - for (auto&& ex : ext) - { - data.emplace_back().name = std::move(ex); - data.back().is_directory = true; - } - - // Sort files, keeping . and .. - std::stable_sort(data.begin() + 2, data.end(), FN(x.name < y.name)); - - // Remove duplicates - data.erase(std::unique(data.begin(), data.end(), FN(x.name == y.name)), data.end()); - - if (const u32 id = idm::make(processed_path, std::move(data))) - { - lock.unlock(); - ppu.check_state(); - - *fd = id; - return CELL_OK; - } - - // Out of file descriptors - return CELL_EMFILE; -} - -error_code sys_fs_readdir(ppu_thread& ppu, u32 fd, vm::ptr dir, vm::ptr nread) -{ - ppu.state += cpu_flag::wait; - - sys_fs.warning("sys_fs_readdir(fd=%d, dir=*0x%x, nread=*0x%x)", fd, dir, nread); - - if (!dir || !nread) - { - return CELL_EFAULT; - } - - const auto directory = idm::get_unlocked(fd); - - if (!directory) - { - return CELL_EBADF; - } - - ppu.check_state(); - - auto* info = directory->dir_read(); - - u64 nread_to_write = 0; - - if (info) - { - nread_to_write = sizeof(CellFsDirent); - } - else - { - // It does actually write polling the last entry. Seems consistent across HDD0 and HDD1 (TODO: check more partitions) - info = &directory->entries.back(); - nread_to_write = 0; - } - - CellFsDirent dir_write{}; - - dir_write.d_type = info->is_directory ? CELL_FS_TYPE_DIRECTORY : CELL_FS_TYPE_REGULAR; - dir_write.d_namlen = u8(std::min(info->name.size(), CELL_FS_MAX_FS_FILE_NAME_LENGTH)); - strcpy_trunc(dir_write.d_name, info->name); - - // TODO: Check more partitions (HDD1 is known to differ in actual filesystem implementation) - if (directory->mp != &g_mp_sys_dev_hdd1 && nread_to_write == 0) - { - // First 3 bytes are being set to 0 here - dir_write.d_type = 0; - dir_write.d_namlen = 0; - dir_write.d_name[0] = '\0'; - } - - *dir = dir_write; - - // Write after dir - *nread = nread_to_write; - return CELL_OK; -} - -error_code sys_fs_closedir(ppu_thread& ppu, u32 fd) -{ - ppu.state += cpu_flag::wait; - lv2_obj::sleep(ppu); - - sys_fs.warning("sys_fs_closedir(fd=%d)", fd); - - if (!idm::remove(fd)) - { - return CELL_EBADF; - } - - return CELL_OK; -} - -error_code sys_fs_stat(ppu_thread& ppu, vm::cptr path, vm::ptr sb) -{ - ppu.state += cpu_flag::wait; - lv2_obj::sleep(ppu); - - sys_fs.warning("sys_fs_stat(path=%s, sb=*0x%x)", path, sb); - - const auto [path_error, vpath] = translate_to_str(path); - - if (path_error) - { - return {path_error, vpath}; - } - - const std::string local_path = vfs::get(vpath); - - const auto& mp = g_fxo->get().lookup(vpath); - - if (mp == &g_mp_sys_dev_root) - { - sb->mode = CELL_FS_S_IFDIR | 0711; - sb->uid = -1; - sb->gid = -1; - sb->atime = -1; - sb->mtime = -1; - sb->ctime = -1; - sb->size = 258; - sb->blksize = 512; - return CELL_OK; - } - - if (local_path.empty()) - { - // This syscall can be used by games and VSH to test the presence of dev_usb000 ~ dev_usb127 - // Thus there is no need to fuss about CELL_ENOTMOUNTED in this case - return {sys_fs.warning, CELL_ENOTMOUNTED, path}; - } - - std::unique_lock lock(mp->mutex); - - fs::stat_t info{}; - - if (!fs::get_stat(local_path, info)) - { - switch (auto error = fs::g_tls_error) - { - case fs::error::noent: - { - // Try to analyse split file (TODO) - u64 total_size = 0; - - for (u32 i = 66601; i <= 66699; i++) - { - if (fs::get_stat(fmt::format("%s.%u", local_path, i), info) && !info.is_directory) - { - total_size += info.size; - } - else - { - break; - } - } - - // Use attributes from the first fragment (consistently with sys_fs_open+fstat) - if (fs::get_stat(local_path + ".66600", info) && !info.is_directory) - { - // Success - info.size += total_size; - break; - } - - return {mp == &g_mp_sys_dev_hdd1 ? sys_fs.warning : sys_fs.error, CELL_ENOENT, path}; - } - default: - { - sys_fs.error("sys_fs_stat(): unknown error %s", error); - return {CELL_EIO, path}; - } - } - } - - lock.unlock(); - ppu.check_state(); - - s32 mode = info.is_directory ? CELL_FS_S_IFDIR | 0777 : CELL_FS_S_IFREG | 0666; - - if (mp.read_only) - { - // Remove write permissions - mode &= ~0222; - } - - sb->mode = mode; - sb->uid = mp->flags & lv2_mp_flag::no_uid_gid ? -1 : 0; - sb->gid = mp->flags & lv2_mp_flag::no_uid_gid ? -1 : 0; - sb->atime = info.atime; - sb->mtime = info.mtime; - sb->ctime = info.ctime; - sb->size = info.is_directory ? mp->block_size : info.size; - sb->blksize = mp->block_size; - - return CELL_OK; -} - -error_code sys_fs_fstat(ppu_thread& ppu, u32 fd, vm::ptr sb) -{ - ppu.state += cpu_flag::wait; - lv2_obj::sleep(ppu); - - sys_fs.warning("sys_fs_fstat(fd=%d, sb=*0x%x)", fd, sb); - - const auto file = idm::get_unlocked(fd); - - if (!file) - { - return CELL_EBADF; - } - - std::unique_lock lock(file->mp->mutex); - - if (!file->file) - { - return CELL_EBADF; - } - - if (file->lock == 2) - { - return CELL_EIO; - } - - const fs::stat_t info = file->file.get_stat(); - lock.unlock(); - ppu.check_state(); - - s32 mode = info.is_directory ? CELL_FS_S_IFDIR | 0777 : CELL_FS_S_IFREG | 0666; - - if (file->mp.read_only) - { - // Remove write permissions - mode &= ~0222; - } - - sb->mode = mode; - sb->uid = file->mp->flags & lv2_mp_flag::no_uid_gid ? -1 : 0; - sb->gid = file->mp->flags & lv2_mp_flag::no_uid_gid ? -1 : 0; - sb->atime = info.atime; - sb->mtime = info.mtime; - sb->ctime = info.ctime; // ctime may be incorrect - sb->size = info.size; - sb->blksize = file->mp->block_size; - return CELL_OK; -} - -error_code sys_fs_link(ppu_thread&, vm::cptr from, vm::cptr to) -{ - sys_fs.todo("sys_fs_link(from=%s, to=%s)", from, to); - - return CELL_OK; -} - -error_code sys_fs_mkdir(ppu_thread& ppu, vm::cptr path, s32 mode) -{ - ppu.state += cpu_flag::wait; - lv2_obj::sleep(ppu); - - sys_fs.warning("sys_fs_mkdir(path=%s, mode=%#o)", path, mode); - - const auto [path_error, vpath] = translate_to_str(path); - - if (path_error) - { - return {path_error, vpath}; - } - - const std::string local_path = vfs::get(vpath); - - const auto& mp = g_fxo->get().lookup(vpath); - - if (mp == &g_mp_sys_dev_root) - { - return {CELL_EEXIST, path}; - } - - if (local_path.empty()) - { - return {CELL_ENOTMOUNTED, path}; - } - - if (mp.read_only) - { - return {CELL_EROFS, path}; - } - - if (!fs::exists(local_path) && !has_fs_write_rights(path.get_ptr())) - { - return {CELL_EACCES, path}; - } - - std::lock_guard lock(mp->mutex); - - if (!fs::create_dir(local_path)) - { - switch (auto error = fs::g_tls_error) - { - case fs::error::noent: - { - return {mp == &g_mp_sys_dev_hdd1 ? sys_fs.warning : sys_fs.error, CELL_ENOENT, path}; - } - case fs::error::exist: - { - return {sys_fs.warning, CELL_EEXIST, path}; - } - default: sys_fs.error("sys_fs_mkdir(): unknown error %s", error); - } - - return {CELL_EIO, path}; // ??? - } - - sys_fs.notice("sys_fs_mkdir(): directory %s created", path); - return CELL_OK; -} - -error_code sys_fs_rename(ppu_thread& ppu, vm::cptr from, vm::cptr to) -{ - ppu.state += cpu_flag::wait; - lv2_obj::sleep(ppu); - - sys_fs.warning("sys_fs_rename(from=%s, to=%s)", from, to); - - const auto [from_error, vfrom] = translate_to_str(from); - - if (from_error) - { - return {from_error, vfrom}; - } - - const auto [to_error, vto] = translate_to_str(to); - - if (to_error) - { - return {to_error, vto}; - } - - const std::string local_from = vfs::get(vfrom); - const std::string local_to = vfs::get(vto); - - const auto& mp = g_fxo->get().lookup(vfrom); - const auto& mp_to = g_fxo->get().lookup(vto); - - if (mp == &g_mp_sys_dev_root || mp_to == &g_mp_sys_dev_root) - { - return CELL_EPERM; - } - - if (local_from.empty() || local_to.empty()) - { - return CELL_ENOTMOUNTED; - } - - if (mp != mp_to) - { - return CELL_EXDEV; - } - - if (mp.read_only) - { - return CELL_EROFS; - } - - // Done in vfs::host::rename - // std::lock_guard lock(mp->mutex); - - if (!vfs::host::rename(local_from, local_to, mp.mp, false)) - { - switch (auto error = fs::g_tls_error) - { - case fs::error::noent: return {CELL_ENOENT, from}; - case fs::error::exist: return {CELL_EEXIST, to}; - default: sys_fs.error("sys_fs_rename(): unknown error %s", error); - } - - return {CELL_EIO, from}; // ??? - } - - sys_fs.notice("sys_fs_rename(): %s renamed to %s", from, to); - return CELL_OK; -} - -error_code sys_fs_rmdir(ppu_thread& ppu, vm::cptr path) -{ - ppu.state += cpu_flag::wait; - lv2_obj::sleep(ppu); - - sys_fs.warning("sys_fs_rmdir(path=%s)", path); - - const auto [path_error, vpath] = translate_to_str(path); - - if (path_error) - { - return {path_error, vpath}; - } - - const std::string local_path = vfs::get(vpath); - - const auto& mp = g_fxo->get().lookup(vpath); - - if (mp == &g_mp_sys_dev_root) - { - return {CELL_EPERM, path}; - } - - if (local_path.empty()) - { - return {CELL_ENOTMOUNTED, path}; - } - - if (mp.read_only) - { - return {CELL_EROFS, path}; - } - - if (fs::is_dir(local_path) && !has_fs_write_rights(path.get_ptr())) - { - return {CELL_EACCES}; - } - - std::lock_guard lock(mp->mutex); - - if (!fs::remove_dir(local_path)) - { - switch (auto error = fs::g_tls_error) - { - case fs::error::noent: return {CELL_ENOENT, path}; - case fs::error::notempty: return {CELL_ENOTEMPTY, path}; - default: sys_fs.error("sys_fs_rmdir(): unknown error %s", error); - } - - return {CELL_EIO, path}; // ??? - } - - sys_fs.notice("sys_fs_rmdir(): directory %s removed", path); - return CELL_OK; -} - -error_code sys_fs_unlink(ppu_thread& ppu, vm::cptr path) -{ - ppu.state += cpu_flag::wait; - lv2_obj::sleep(ppu); - - sys_fs.warning("sys_fs_unlink(path=%s)", path); - - const auto [path_error, vpath] = translate_to_str(path); - - if (path_error) - { - return {path_error, vpath}; - } - - const std::string local_path = vfs::get(vpath); - - std::string mount_path = fs::get_parent_dir(vpath); // Use its parent directory as fallback - const auto& mp = g_fxo->get().lookup(vpath, true, &mount_path); - - if (mp == &g_mp_sys_dev_root) - { - return {CELL_EISDIR, path}; - } - - if (local_path.empty()) - { - return {CELL_ENOTMOUNTED, path}; - } - - if (fs::is_dir(local_path)) - { - return {CELL_EISDIR, path}; - } - - if (mp.read_only) - { - return {CELL_EROFS, path}; - } - - std::lock_guard lock(mp->mutex); - - if (!vfs::host::unlink(local_path, vfs::get(mount_path))) - { - switch (auto error = fs::g_tls_error) - { - case fs::error::noent: - { - return {mp == &g_mp_sys_dev_hdd1 ? sys_fs.warning : sys_fs.error, CELL_ENOENT, path}; - } - default: sys_fs.error("sys_fs_unlink(): unknown error %s", error); - } - - return {CELL_EIO, path}; // ??? - } - - sys_fs.notice("sys_fs_unlink(): file %s deleted", path); - return CELL_OK; -} - -error_code sys_fs_access(ppu_thread&, vm::cptr path, s32 mode) -{ - sys_fs.todo("sys_fs_access(path=%s, mode=%#o)", path, mode); - - return CELL_OK; -} - -error_code sys_fs_fcntl(ppu_thread& ppu, u32 fd, u32 op, vm::ptr _arg, u32 _size) -{ - ppu.state += cpu_flag::wait; - - sys_fs.trace("sys_fs_fcntl(fd=%d, op=0x%x, arg=*0x%x, size=0x%x)", fd, op, _arg, _size); - - switch (op) - { - case 0x80000004: // Unknown - { - if (_size > 4) - { - return CELL_EINVAL; - } - - const auto arg = vm::static_ptr_cast(_arg); - *arg = 0; - break; - } - - case 0x80000006: // cellFsAllocateFileAreaByFdWithInitialData - { - break; - } - - case 0x80000007: // cellFsAllocateFileAreaByFdWithoutZeroFill - { - break; - } - - case 0x80000008: // cellFsChangeFileSizeByFdWithoutAllocation - { - break; - } - - case 0x8000000a: // cellFsReadWithOffset - case 0x8000000b: // cellFsWriteWithOffset - { - lv2_obj::sleep(ppu); - - const auto arg = vm::static_ptr_cast(_arg); - - if (_size < arg.size()) - { - return CELL_EINVAL; - } - - const auto file = idm::get_unlocked(fd); - - if (!file) - { - return CELL_EBADF; - } - - if (op == 0x8000000a && file->flags & CELL_FS_O_WRONLY) - { - return CELL_EBADF; - } - - if (op == 0x8000000b && !(file->flags & CELL_FS_O_ACCMODE)) - { - return CELL_EBADF; - } - - if (op == 0x8000000b && file->flags & CELL_FS_O_APPEND) - { - return CELL_EBADF; - } - - if (op == 0x8000000b && file->mp.read_only) - { - return CELL_EROFS; - } - - if (op == 0x8000000b && file->type != lv2_file_type::regular && arg->size) - { - sys_fs.error("%s type: Writing %u bytes to FD=%d (path=%s)", file->type, arg->size, file->name.data()); - } - - std::unique_lock wlock(file->mp->mutex, std::defer_lock); - std::shared_lock rlock(file->mp->mutex, std::defer_lock); - - if (op == 0x8000000b) - { - // Writer lock - wlock.lock(); - } - else - { - // Reader lock (not needing exclusivity in this special case because the state should not change) - rlock.lock(); - } - - if (!file->file) - { - return CELL_EBADF; - } - - if (file->lock == 2) - { - return CELL_EIO; - } - - if (op == 0x8000000b && file->lock) - { - return CELL_EBUSY; - } - - u64 old_pos = umax; - const u64 op_pos = arg->offset; - - if (op == 0x8000000b) - { - old_pos = file->file.pos(); - file->file.seek(op_pos); - } - - arg->out_size = op == 0x8000000a ? file->op_read(arg->buf, arg->size, op_pos) : file->op_write(arg->buf, arg->size); - - if (op == 0x8000000b) - { - ensure(old_pos == file->file.seek(old_pos)); - } - - // TODO: EDATA corruption detection - - arg->out_code = CELL_OK; - return CELL_OK; - } - - case 0x80000009: // cellFsSdataOpenByFd - { - lv2_obj::sleep(ppu); - - const auto arg = vm::static_ptr_cast(_arg); - - if (_size < arg.size()) - { - return CELL_EINVAL; - } - - const auto file = idm::get_unlocked(fd); - - if (!file) - { - return {CELL_EBADF, "fd=%u", fd}; - } - - sys_fs.warning("sys_fs_fcntl(0x80000009): fd=%d, arg->offset=0x%x, size=0x%x (file: %s)", fd, arg->offset, _size, *file); - - std::lock_guard lock(file->mp->mutex); - - if (!file->file) - { - return {CELL_EBADF, "fd=%u", fd}; - } - - auto sdata_file = std::make_unique(lv2_file::make_view(file, arg->offset)); - - if (!sdata_file->ReadHeader()) - { - return {CELL_EFSSPECIFIC, "fd=%u", fd}; - } - - fs::file stream; - stream.reset(std::move(sdata_file)); - if (const u32 id = idm::import ([&file = *file, &stream = stream]() -> shared_ptr - { - if (!g_fxo->get().npdrm_fds.try_inc(16)) - { - return null_ptr; - } - - return stx::make_shared(file, std::move(stream), file.mode, CELL_FS_O_RDONLY, file.real_path, lv2_file_type::sdata); - })) - { - arg->out_code = CELL_OK; - arg->out_fd = id; - return CELL_OK; - } - - // Out of file descriptors - return CELL_EMFILE; - } - - case 0xc0000002: // cellFsGetFreeSize (TODO) - { - lv2_obj::sleep(ppu); - - const auto arg = vm::static_ptr_cast(_arg); - - const auto& mp = g_fxo->get().lookup("/dev_hdd0"); - - arg->out_block_size = mp->block_size; - arg->out_block_count = (40ull * 1024 * 1024 * 1024 - 1) / mp->block_size; // Read explanation in cellHddGameCheck - return CELL_OK; - } - - case 0xc0000003: // cellFsUtilitySetFakeSize - { - break; - } - - case 0xc0000004: // cellFsUtilityGetFakeSize - { - break; - } - - case 0xc0000006: // Unknown - { - const auto arg = vm::static_ptr_cast(_arg); - - if (arg->size != 0x20u) - { - sys_fs.error("sys_fs_fcntl(0xc0000006): invalid size (0x%x)", arg->size); - break; - } - - if (arg->_x4 != 0x10u || arg->_x8 != 0x18u) - { - sys_fs.error("sys_fs_fcntl(0xc0000006): invalid args (0x%x, 0x%x)", arg->_x4, arg->_x8); - break; - } - - // Load mountpoint (doesn't support multiple // at the start) - std::string_view vpath{arg->name.get_ptr(), arg->name_size}; - - sys_fs.notice("sys_fs_fcntl(0xc0000006): %s", vpath); - - // Check only mountpoint - vpath = vpath.substr(0, vpath.find_first_of("/", 1)); - - // Some mountpoints seem to be handled specially - if (false) - { - // TODO: /dev_hdd1, /dev_usb000, /dev_flash - // arg->out_code = CELL_OK; - // arg->out_id = 0x1b5; - } - - arg->out_code = CELL_ENOTSUP; - arg->out_id = 0; - return CELL_OK; - } - - case 0xc0000007: // cellFsArcadeHddSerialNumber - { - const auto arg = vm::static_ptr_cast(_arg); - - arg->out_code = CELL_OK; - - if (const auto size = arg->model_size; size > 0) - strcpy_trunc(std::span(arg->model.get_ptr(), size), - fmt::format("%-*s", size - 1, g_cfg.sys.hdd_model.to_string())); // Example: "TOSHIBA MK3265GSX H " - - if (const auto size = arg->serial_size; size > 0) - strcpy_trunc(std::span(arg->serial.get_ptr(), size), - fmt::format("%*s", size - 1, g_cfg.sys.hdd_serial.to_string())); // Example: " 0A1B2C3D4" - else - return CELL_EFAULT; // CELL_EFAULT is returned only when arg->serial_size == 0 - - return CELL_OK; - } - - case 0xc0000008: // cellFsSetDefaultContainer, cellFsSetIoBuffer, cellFsSetIoBufferFromDefaultContainer - { - // Allocates memory from a container/default container to a specific fd or default IO processing - const auto arg = vm::static_ptr_cast(_arg); - auto& default_container = g_fxo->get(); - - std::lock_guard def_container_lock(default_container.mutex); - - if (fd == 0xFFFFFFFF) - { - // No check on container is done when setting default container - default_container.id = arg->size ? ::narrow(arg->container_id) : 0u; - default_container.cap = arg->size; - default_container.used = 0; - - arg->out_code = CELL_OK; - return CELL_OK; - } - - auto file = idm::get_unlocked(fd); - if (!file) - { - return CELL_EBADF; - } - - if (auto ct = idm::get_unlocked(file->ct_id)) - { - ct->free(file->ct_used); - if (default_container.id == file->ct_id) - { - default_container.used -= file->ct_used; - } - } - - file->ct_id = 0; - file->ct_used = 0; - - // Aligns on lower bound - u32 actual_size = arg->size - (arg->size % ((arg->page_type & CELL_FS_IO_BUFFER_PAGE_SIZE_64KB) ? 0x10000 : 0x100000)); - - if (!actual_size) - { - arg->out_code = CELL_OK; - return CELL_OK; - } - - u32 new_container_id = arg->container_id == 0xFFFFFFFF ? default_container.id : ::narrow(arg->container_id); - if (default_container.id == new_container_id && (default_container.used + actual_size) > default_container.cap) - { - return CELL_ENOMEM; - } - - const auto ct = idm::get(new_container_id, [&](lv2_memory_container& ct) -> CellError - { - if (!ct.take(actual_size)) - { - return CELL_ENOMEM; - } - - return {}; - }); - - if (!ct) - { - return CELL_ESRCH; - } - - if (ct.ret) - { - return ct.ret; - } - - if (default_container.id == new_container_id) - { - default_container.used += actual_size; - } - - file->ct_id = new_container_id; - file->ct_used = actual_size; - - arg->out_code = CELL_OK; - return CELL_OK; - } - - case 0xc0000015: // USB Vid/Pid query - case 0xc000001c: // USB Vid/Pid/Serial query - { - const auto arg = vm::static_ptr_cast(_arg); - const bool with_serial = op == 0xc000001c; - - if (arg->size != (with_serial ? sizeof(lv2_file_c000001c) : sizeof(lv2_file_c0000015))) - { - sys_fs.error("sys_fs_fcntl(0x%08x): invalid size (0x%x)", op, arg->size); - break; - } - - if (arg->_x4 != 0x10u || arg->_x8 != 0x18u) - { - sys_fs.error("sys_fs_fcntl(0x%08x): invalid args (0x%x, 0x%x)", op, arg->_x4, arg->_x8); - break; - } - - std::string_view vpath{arg->path.get_ptr(), arg->path_size}; - - if (vpath.size() == 0) - return CELL_ENOMEM; - - // Trim trailing '\0' - if (const auto trim_pos = vpath.find('\0'); trim_pos != umax) - vpath.remove_suffix(vpath.size() - trim_pos); - - arg->out_code = CELL_ENOTMOUNTED; // arg->out_code is set to CELL_ENOTMOUNTED on real hardware when the device doesn't exist or when the device isn't USB - - if (!vfs::get(vpath).empty()) - { - if (const auto& mp = g_fxo->get().lookup(vpath, true); mp == &g_mp_sys_dev_usb) - { - const cfg::device_info device = g_cfg_vfs.get_device(g_cfg_vfs.dev_usb, fmt::format("%s%s", mp->root, mp.device.substr(mp->device.size()))); - const auto usb_ids = device.get_usb_ids(); - std::tie(arg->vendorID, arg->productID) = usb_ids; - - if (with_serial) - { - const auto arg_c000001c = vm::static_ptr_cast(_arg); - const std::u16string serial = utf8_to_utf16(device.serial); // Serial needs to be encoded to utf-16 BE - std::copy_n(serial.begin(), std::min(serial.size(), sizeof(arg_c000001c->serial) / sizeof(u16)), arg_c000001c->serial); - } - - arg->out_code = CELL_OK; - sys_fs.trace("sys_fs_fcntl(0x%08x): found device \"%s\" (vid=0x%04x, pid=0x%04x, serial=\"%s\")", op, mp.device, usb_ids.first, usb_ids.second, device.serial); - } - } - - return CELL_OK; - } - - case 0xc0000016: // ps2disc_8160A811 - { - break; - } - - case 0xc000001a: // cellFsSetDiscReadRetrySetting, 5731DF45 - { - [[maybe_unused]] const auto arg = vm::static_ptr_cast(_arg); - return CELL_OK; - } - - case 0xc0000021: // 9FDBBA89 - { - break; - } - - case 0xe0000000: // Unknown (cellFsGetBlockSize) - { - break; - } - - case 0xe0000001: // Unknown (cellFsStat) - { - break; - } - - case 0xe0000003: // Unknown - { - break; - } - - case 0xe0000004: // Unknown - { - break; - } - - case 0xe0000005: // Unknown (cellFsMkdir) - { - break; - } - - case 0xe0000006: // Unknown - { - break; - } - - case 0xe0000007: // Unknown - { - break; - } - - case 0xe0000008: // Unknown (cellFsAclRead) - { - break; - } - - case 0xe0000009: // Unknown (cellFsAccess) - { - break; - } - - case 0xe000000a: // Unknown (E3D28395) - { - break; - } - - case 0xe000000b: // Unknown (cellFsRename, FF29F478) - { - break; - } - - case 0xe000000c: // Unknown (cellFsTruncate) - { - break; - } - - case 0xe000000d: // Unknown (cellFsUtime) - { - break; - } - - case 0xe000000e: // Unknown (cellFsAclWrite) - { - break; - } - - case 0xe000000f: // Unknown (cellFsChmod) - { - break; - } - - case 0xe0000010: // Unknown (cellFsChown) - { - break; - } - - case 0xe0000011: // Unknown - { - break; - } - - case 0xe0000012: // cellFsGetDirectoryEntries - { - lv2_obj::sleep(ppu); - - const auto arg = vm::static_ptr_cast(_arg); - - if (_size < arg.size()) - { - return CELL_EINVAL; - } - - const auto directory = idm::get_unlocked(fd); - - if (!directory) - { - return CELL_EBADF; - } - - ppu.check_state(); - - u32 read_count = 0; - - // NOTE: This function is actually capable of reading only one entry at a time - if (const u32 max = arg->max) - { - const auto arg_ptr = +arg->ptr; - - if (auto* info = directory->dir_read()) - { - auto& entry = arg_ptr[read_count++]; - - s32 mode = info->is_directory ? CELL_FS_S_IFDIR | 0777 : CELL_FS_S_IFREG | 0666; - - if (directory->mp.read_only) - { - // Remove write permissions - mode &= ~0222; - } - - entry.attribute.mode = mode; - entry.attribute.uid = directory->mp->flags & lv2_mp_flag::no_uid_gid ? -1 : 0; - entry.attribute.gid = directory->mp->flags & lv2_mp_flag::no_uid_gid ? -1 : 0; - entry.attribute.atime = info->atime; - entry.attribute.mtime = info->mtime; - entry.attribute.ctime = info->ctime; - entry.attribute.size = info->size; - entry.attribute.blksize = directory->mp->block_size; - - entry.entry_name.d_type = info->is_directory ? CELL_FS_TYPE_DIRECTORY : CELL_FS_TYPE_REGULAR; - entry.entry_name.d_namlen = u8(std::min(info->name.size(), CELL_FS_MAX_FS_FILE_NAME_LENGTH)); - strcpy_trunc(entry.entry_name.d_name, info->name); - } - - // Apparently all this function does to additional buffer elements is to zeroize them - std::memset(arg_ptr.get_ptr() + read_count, 0, (max - read_count) * arg->ptr.size()); - } - - arg->_size = read_count; - arg->_code = CELL_OK; - return CELL_OK; - } - - case 0xe0000015: // Unknown - { - break; - } - - case 0xe0000016: // cellFsAllocateFileAreaWithInitialData - { - break; - } - - case 0xe0000017: // cellFsAllocateFileAreaWithoutZeroFill - { - const auto arg = vm::static_ptr_cast(_arg); - - if (_size < arg->size || arg->_x4 != 0x10u || arg->_x8 != 0x20u) - { - return CELL_EINVAL; - } - - arg->out_code = sys_fs_truncate(ppu, arg->file_path, arg->file_size); - return CELL_OK; - } - - case 0xe0000018: // cellFsChangeFileSizeWithoutAllocation - { - break; - } - - case 0xe0000019: // Unknown - { - break; - } - - case 0xe000001b: // Unknown - { - break; - } - - case 0xe000001d: // Unknown - { - break; - } - - case 0xe000001e: // Unknown - { - break; - } - - case 0xe000001f: // Unknown - { - break; - } - - case 0xe0000020: // Unknown - { - break; - } - - case 0xe0000025: // cellFsSdataOpenWithVersion - { - const auto arg = vm::static_ptr_cast(_arg); - - if (arg->size != 0x30u) - { - sys_fs.error("sys_fs_fcntl(0xe0000025): invalid size (0x%x)", arg->size); - break; - } - - if (arg->_x4 != 0x10u || arg->_x8 != 0x28u) - { - sys_fs.error("sys_fs_fcntl(0xe0000025): invalid args (0x%x, 0x%x)", arg->_x4, arg->_x8); - break; - } - - std::string_view vpath{arg->name.get_ptr(), arg->name_size}; - vpath = vpath.substr(0, vpath.find_first_of('\0')); - - sys_fs.notice("sys_fs_fcntl(0xe0000025): %s", vpath); - - be_t sdata_identifier = 0x18000000010; - - lv2_file::open_result_t result = lv2_file::open(vpath, 0, 0, &sdata_identifier, 8); - - if (result.error) - { - return result.error; - } - - if (const u32 id = idm::import ([&]() -> shared_ptr - { - if (!g_fxo->get().npdrm_fds.try_inc(16)) - { - return null_ptr; - } - - return stx::make_shared(result.ppath, std::move(result.file), 0, 0, std::move(result.real_path), lv2_file_type::sdata); - })) - { - arg->out_code = CELL_OK; - arg->fd = id; - return CELL_OK; - } - - // Out of file descriptors - return CELL_EMFILE; - } - } - - sys_fs.error("sys_fs_fcntl(): Unknown operation 0x%08x (fd=%d, arg=*0x%x, size=0x%x)", op, fd, _arg, _size); - return CELL_OK; -} - -error_code sys_fs_lseek(ppu_thread& ppu, u32 fd, s64 offset, s32 whence, vm::ptr pos) -{ - ppu.state += cpu_flag::wait; - lv2_obj::sleep(ppu); - - sys_fs.trace("sys_fs_lseek(fd=%d, offset=0x%llx, whence=0x%x, pos=*0x%x)", fd, offset, whence, pos); - - const auto file = idm::get_unlocked(fd); - - if (!file) - { - return CELL_EBADF; - } - - std::unique_lock lock(file->mp->mutex); - - if (!file->file) - { - return CELL_EBADF; - } - - if (whence + 0u >= 3) - { - return {CELL_EINVAL, whence}; - } - - const u64 result = file->file.seek(offset, static_cast(whence)); - - if (result == umax) - { - switch (auto error = fs::g_tls_error) - { - case fs::error::inval: return {CELL_EINVAL, "fd=%u, offset=0x%x, whence=%d", fd, offset, whence}; - default: sys_fs.error("sys_fs_lseek(): unknown error %s", error); - } - - return CELL_EIO; // ??? - } - - lock.unlock(); - ppu.check_state(); - - *pos = result; - return CELL_OK; -} - -error_code sys_fs_fdatasync(ppu_thread& ppu, u32 fd) -{ - ppu.state += cpu_flag::wait; - lv2_obj::sleep(ppu); - - sys_fs.trace("sys_fs_fdadasync(fd=%d)", fd); - - const auto file = idm::get_unlocked(fd); - - if (!file || !(file->flags & CELL_FS_O_ACCMODE)) - { - return CELL_EBADF; - } - - std::lock_guard lock(file->mp->mutex); - - if (!file->file) - { - return CELL_EBADF; - } - - file->file.sync(); - return CELL_OK; -} - -error_code sys_fs_fsync(ppu_thread& ppu, u32 fd) -{ - ppu.state += cpu_flag::wait; - lv2_obj::sleep(ppu); - - sys_fs.trace("sys_fs_fsync(fd=%d)", fd); - - const auto file = idm::get_unlocked(fd); - - if (!file || !(file->flags & CELL_FS_O_ACCMODE)) - { - return CELL_EBADF; - } - - std::lock_guard lock(file->mp->mutex); - - if (!file->file) - { - return CELL_EBADF; - } - - file->file.sync(); - return CELL_OK; -} - -error_code sys_fs_fget_block_size(ppu_thread& ppu, u32 fd, vm::ptr sector_size, vm::ptr block_size, vm::ptr arg4, vm::ptr out_flags) -{ - ppu.state += cpu_flag::wait; - - sys_fs.warning("sys_fs_fget_block_size(fd=%d, sector_size=*0x%x, block_size=*0x%x, arg4=*0x%x, out_flags=*0x%x)", fd, sector_size, block_size, arg4, out_flags); - - const auto file = idm::get_unlocked(fd); - - if (!file) - { - return CELL_EBADF; - } - - static_cast(ppu.test_stopped()); - - // TODO - *sector_size = file->mp->sector_size; - *block_size = file->mp->block_size; - *arg4 = file->mp->sector_size; - *out_flags = file->flags; - - return CELL_OK; -} - -error_code sys_fs_get_block_size(ppu_thread& ppu, vm::cptr path, vm::ptr sector_size, vm::ptr block_size, vm::ptr arg4) -{ - ppu.state += cpu_flag::wait; - - sys_fs.warning("sys_fs_get_block_size(path=%s, sector_size=*0x%x, block_size=*0x%x, arg4=*0x%x)", path, sector_size, block_size, arg4); - - const auto [path_error, vpath] = translate_to_str(path); - - if (path_error) - { - return {path_error, vpath}; - } - - const std::string local_path = vfs::get(vpath); - - if (vpath.find_first_not_of('/') == umax) - { - return {CELL_EISDIR, path}; - } - - if (local_path.empty()) - { - return {CELL_ENOTMOUNTED, path}; - } - - const auto& mp = g_fxo->get().lookup(vpath); - - // It appears that /dev_hdd0 mount point is special in this function - if (mp != &g_mp_sys_dev_hdd0 && (mp->flags & lv2_mp_flag::strict_get_block_size ? !fs::is_file(local_path) : !fs::exists(local_path))) - { - switch (auto error = fs::g_tls_error) - { - case fs::error::exist: return {CELL_EISDIR, path}; - case fs::error::noent: return {CELL_ENOENT, path}; - default: sys_fs.error("sys_fs_get_block_size(): unknown error %s", error); - } - - return {CELL_EIO, path}; // ??? - } - - static_cast(ppu.test_stopped()); - - // TODO - *sector_size = mp->sector_size; - *block_size = mp->block_size; - *arg4 = mp->sector_size; - - return CELL_OK; -} - -error_code sys_fs_truncate(ppu_thread& ppu, vm::cptr path, u64 size) -{ - ppu.state += cpu_flag::wait; - lv2_obj::sleep(ppu); - - sys_fs.warning("sys_fs_truncate(path=%s, size=0x%llx)", path, size); - - const auto [path_error, vpath] = translate_to_str(path); - - if (path_error) - { - return {path_error, vpath}; - } - - const std::string local_path = vfs::get(vpath); - - const auto& mp = g_fxo->get().lookup(vpath); - - if (mp == &g_mp_sys_dev_root) - { - return {CELL_EISDIR, path}; - } - - if (local_path.empty()) - { - return {CELL_ENOTMOUNTED, path}; - } - - if (mp.read_only) - { - return {CELL_EROFS, path}; - } - - std::lock_guard lock(mp->mutex); - - if (!fs::truncate_file(local_path, size)) - { - switch (auto error = fs::g_tls_error) - { - case fs::error::noent: - { - return {mp == &g_mp_sys_dev_hdd1 ? sys_fs.warning : sys_fs.error, CELL_ENOENT, path}; - } - default: sys_fs.error("sys_fs_truncate(): unknown error %s", error); - } - - return {CELL_EIO, path}; // ??? - } - - return CELL_OK; -} - -error_code sys_fs_ftruncate(ppu_thread& ppu, u32 fd, u64 size) -{ - ppu.state += cpu_flag::wait; - lv2_obj::sleep(ppu); - - sys_fs.warning("sys_fs_ftruncate(fd=%d, size=0x%llx)", fd, size); - - const auto file = idm::get_unlocked(fd); - - if (!file || !(file->flags & CELL_FS_O_ACCMODE)) - { - return CELL_EBADF; - } - - if (file->mp.read_only) - { - return CELL_EROFS; - } - - std::lock_guard lock(file->mp->mutex); - - if (!file->file) - { - return CELL_EBADF; - } - - if (file->lock == 2) - { - return CELL_EIO; - } - - if (file->lock) - { - return CELL_EBUSY; - } - - if (!file->file.trunc(size)) - { - switch (auto error = fs::g_tls_error) - { - case fs::error::ok: - default: sys_fs.error("sys_fs_ftruncate(): unknown error %s", error); - } - - return CELL_EIO; // ??? - } - - return CELL_OK; -} - -error_code sys_fs_symbolic_link(ppu_thread&, vm::cptr target, vm::cptr linkpath) -{ - sys_fs.todo("sys_fs_symbolic_link(target=%s, linkpath=%s)", target, linkpath); - - return CELL_OK; -} - -error_code sys_fs_chmod(ppu_thread&, vm::cptr path, s32 mode) -{ - sys_fs.todo("sys_fs_chmod(path=%s, mode=%#o)", path, mode); - - const auto [path_error, vpath] = translate_to_str(path); - - if (path_error) - { - return {path_error, vpath}; - } - - const std::string local_path = vfs::get(vpath); - - const auto mp = lv2_fs_object::get_mp(vpath); - - if (local_path.empty()) - { - return {CELL_ENOTMOUNTED, path}; - } - - if (mp->flags & lv2_mp_flag::read_only) - { - return {CELL_EROFS, path}; - } - - std::unique_lock lock(mp->mutex); - - fs::stat_t info{}; - - if (!fs::get_stat(local_path, info)) - { - switch (auto error = fs::g_tls_error) - { - case fs::error::noent: - { - // Try to locate split files - - for (u32 i = 66601; i <= 66699; i++) - { - if (!fs::get_stat(fmt::format("%s.%u", local_path, i), info) && !info.is_directory) - { - break; - } - } - - if (fs::get_stat(local_path + ".66600", info) && !info.is_directory) - { - break; - } - - return {CELL_ENOENT, path}; - } - default: - { - sys_fs.error("sys_fs_chmod(): unknown error %s", error); - return {CELL_EIO, path}; - } - } - } - - return CELL_OK; -} - -error_code sys_fs_chown(ppu_thread&, vm::cptr path, s32 uid, s32 gid) -{ - sys_fs.todo("sys_fs_chown(path=%s, uid=%d, gid=%d)", path, uid, gid); - - return CELL_OK; -} - -error_code sys_fs_disk_free(ppu_thread& ppu, vm::cptr path, vm::ptr total_free, vm::ptr avail_free) -{ - ppu.state += cpu_flag::wait; - lv2_obj::sleep(ppu); - - sys_fs.warning("sys_fs_disk_free(path=%s total_free=*0x%x avail_free=*0x%x)", path, total_free, avail_free); - - if (!path) - return CELL_EFAULT; - - if (!path[0]) - return CELL_EINVAL; - - const std::string_view vpath = path.get_ptr(); - - if (vpath == "/"sv) - { - return CELL_ENOTSUP; - } - - // It seems max length is 31, and multiple / at the start aren't supported - if (vpath.size() > CELL_FS_MAX_MP_LENGTH) - { - return {CELL_ENAMETOOLONG, path}; - } - - if (vpath.find_first_not_of('/') != 1) - { - return {CELL_EINVAL, path}; - } - - // Get only device path - const std::string local_path = vfs::get(vpath.substr(0, vpath.find_first_of('/', 1))); - - if (local_path.empty()) - { - return {CELL_EINVAL, path}; - } - - const auto& mp = g_fxo->get().lookup(vpath); - - if (mp->flags & lv2_mp_flag::strict_get_block_size) - { - // TODO: - return {CELL_ENOTSUP, path}; - } - - if (mp.read_only) - { - // TODO: check /dev_bdvd - ppu.check_state(); - *total_free = 0; - *avail_free = 0; - return CELL_OK; - } - - u64 available = 0; - - // avail_free is the only value used by cellFsGetFreeSize - if (mp == &g_mp_sys_dev_hdd1) - { - available = (1u << 31) - mp->sector_size; // 2GB (TODO: Should be the total size) - } - else // if (mp == &g_mp_sys_dev_hdd0) - { - available = (40ull * 1024 * 1024 * 1024 - mp->sector_size); // Read explanation in cellHddGameCheck - } - - // HACK: Hopefully nothing uses this value or once at max because its hacked here: - // The total size can change based on the size of the directory - const u64 total = available + fs::get_dir_size(local_path, mp->sector_size); - - ppu.check_state(); - *total_free = total; - *avail_free = available; - - return CELL_OK; -} - -error_code sys_fs_utime(ppu_thread& ppu, vm::cptr path, vm::cptr timep) -{ - ppu.state += cpu_flag::wait; - lv2_obj::sleep(ppu); - - sys_fs.warning("sys_fs_utime(path=%s, timep=*0x%x)", path, timep); - sys_fs.warning("** actime=%u, modtime=%u", timep->actime, timep->modtime); - - const auto [path_error, vpath] = translate_to_str(path); - - if (path_error) - { - return {path_error, vpath}; - } - - const std::string local_path = vfs::get(vpath); - - const auto& mp = g_fxo->get().lookup(vpath); - - if (mp == &g_mp_sys_dev_root) - { - return {CELL_EISDIR, path}; - } - - if (local_path.empty()) - { - return {CELL_ENOTMOUNTED, path}; - } - - if (mp.read_only) - { - return {CELL_EROFS, path}; - } - - std::lock_guard lock(mp->mutex); - - if (!fs::utime(local_path, timep->actime, timep->modtime)) - { - switch (auto error = fs::g_tls_error) - { - case fs::error::noent: - { - return {mp == &g_mp_sys_dev_hdd1 ? sys_fs.warning : sys_fs.error, CELL_ENOENT, path}; - } - default: sys_fs.error("sys_fs_utime(): unknown error %s", error); - } - - return {CELL_EIO, path}; // ??? - } - - return CELL_OK; -} - -error_code sys_fs_acl_read(ppu_thread&, vm::cptr path, vm::ptr ptr) -{ - sys_fs.todo("sys_fs_acl_read(path=%s, ptr=*0x%x)", path, ptr); - - return CELL_OK; -} - -error_code sys_fs_acl_write(ppu_thread&, vm::cptr path, vm::ptr ptr) -{ - sys_fs.todo("sys_fs_acl_write(path=%s, ptr=*0x%x)", path, ptr); - - return CELL_OK; -} - -error_code sys_fs_lsn_get_cda_size(ppu_thread&, u32 fd, vm::ptr ptr) -{ - sys_fs.warning("sys_fs_lsn_get_cda_size(fd=%d, ptr=*0x%x)", fd, ptr); - - const auto file = idm::get_unlocked(fd); - - if (!file) - { - return CELL_EBADF; - } - - // TODO - *ptr = 0; - return CELL_OK; -} - -error_code sys_fs_lsn_get_cda(ppu_thread&, u32 fd, vm::ptr arg2, u64 arg3, vm::ptr arg4) -{ - sys_fs.todo("sys_fs_lsn_get_cda(fd=%d, arg2=*0x%x, arg3=0x%x, arg4=*0x%x)", fd, arg2, arg3, arg4); - - return CELL_OK; -} - -error_code sys_fs_lsn_lock(ppu_thread&, u32 fd) -{ - sys_fs.trace("sys_fs_lsn_lock(fd=%d)", fd); - - const auto file = idm::get_unlocked(fd); - - if (!file) - { - return CELL_EBADF; - } - - // TODO: seems to do nothing on /dev_hdd0 or /host_root - if (file->mp == &g_mp_sys_dev_hdd0 || file->mp->flags & lv2_mp_flag::strict_get_block_size) - { - return CELL_OK; - } - - std::lock_guard lock(file->mp->mutex); - file->lock.compare_and_swap(0, 1); - return CELL_OK; -} - -error_code sys_fs_lsn_unlock(ppu_thread&, u32 fd) -{ - sys_fs.trace("sys_fs_lsn_unlock(fd=%d)", fd); - - const auto file = idm::get_unlocked(fd); - - if (!file) - { - return CELL_EBADF; - } - - // See sys_fs_lsn_lock - if (file->mp == &g_mp_sys_dev_hdd0 || file->mp->flags & lv2_mp_flag::strict_get_block_size) - { - return CELL_OK; - } - - // Unlock unconditionally - std::lock_guard lock(file->mp->mutex); - file->lock.compare_and_swap(1, 0); - return CELL_OK; -} - -error_code sys_fs_lsn_read(ppu_thread&, u32 fd, vm::cptr ptr, u64 size) -{ - sys_fs.todo("sys_fs_lsn_read(fd=%d, ptr=*0x%x, size=0x%x)", fd, ptr, size); - - return CELL_OK; -} - -error_code sys_fs_lsn_write(ppu_thread&, u32 fd, vm::cptr ptr, u64 size) -{ - sys_fs.todo("sys_fs_lsn_write(fd=%d, ptr=*0x%x, size=0x%x)", fd, ptr, size); - - return CELL_OK; -} - -error_code sys_fs_mapped_allocate(ppu_thread&, u32 fd, u64 size, vm::pptr out_ptr) -{ - sys_fs.todo("sys_fs_mapped_allocate(fd=%d, arg2=0x%x, out_ptr=**0x%x)", fd, size, out_ptr); - - return CELL_OK; -} - -error_code sys_fs_mapped_free(ppu_thread&, u32 fd, vm::ptr ptr) -{ - sys_fs.todo("sys_fs_mapped_free(fd=%d, ptr=0x%#x)", fd, ptr); - - return CELL_OK; -} - -error_code sys_fs_truncate2(ppu_thread&, u32 fd, u64 size) -{ - sys_fs.todo("sys_fs_truncate2(fd=%d, size=0x%x)", fd, size); - - return CELL_OK; -} - -error_code sys_fs_get_mount_info_size(ppu_thread&, vm::ptr len) -{ - sys_fs.warning("sys_fs_get_mount_info_size(len=*0x%x)", len); - - if (!len) - { - return CELL_EFAULT; - } - - *len = g_fxo->get().get_all(); - - return CELL_OK; -} - -error_code sys_fs_get_mount_info(ppu_thread&, vm::ptr info, u64 len, vm::ptr out_len) -{ - sys_fs.warning("sys_fs_get_mount_info(info=*0x%x, len=0x%x, out_len=*0x%x)", info, len, out_len); - - if (!info || !out_len) - { - return CELL_EFAULT; - } - - *out_len = g_fxo->get().get_all(info.get_ptr(), len); - - return CELL_OK; -} - -error_code sys_fs_newfs(ppu_thread& ppu, vm::cptr dev_name, vm::cptr file_system, s32 unk1, vm::cptr str1) -{ - ppu.state += cpu_flag::wait; - - sys_fs.warning("sys_fs_newfs(dev_name=%s, file_system=%s, unk1=0x%x, str1=%s)", dev_name, file_system, unk1, str1); - - const auto [dev_error, device_name] = translate_to_str(dev_name, false); - - if (dev_error) - { - return {dev_error, device_name}; - } - - std::string vfs_path; - const auto mp = lv2_fs_object::get_mp(device_name, &vfs_path); - std::unique_lock lock(mp->mutex, std::defer_lock); - - if (!g_ps3_process_info.has_root_perm() && mp != &g_mp_sys_dev_usb) - return {CELL_EPERM, device_name}; - - if (mp == &g_mp_sys_no_device) - return {CELL_ENXIO, device_name}; - - if (g_fxo->get().is_device_mounted(device_name) || !lock.try_lock()) - return {CELL_EBUSY, device_name}; - - if (vfs_path.empty()) - return {CELL_ENOTSUP, device_name}; - - if (mp->flags & lv2_mp_flag::read_only) - return {CELL_EROFS, device_name}; - - if (mp == &g_mp_sys_dev_hdd1) - { - const std::string_view appname = g_ps3_process_info.get_cellos_appname(); - vfs_path = fmt::format("%s/caches/%s", vfs_path, appname.substr(0, appname.find_last_of('.'))); - } - - if (!fs::remove_all(vfs_path, false)) - { - sys_fs.error("sys_fs_newfs(): Failed to clear \"%s\" at \"%s\"", device_name, vfs_path); - return {CELL_EIO, vfs_path}; - } - - sys_fs.success("sys_fs_newfs(): Successfully cleared \"%s\" at \"%s\"", device_name, vfs_path); - return CELL_OK; -} - -error_code sys_fs_mount(ppu_thread& ppu, vm::cptr dev_name, vm::cptr file_system, vm::cptr path, s32 unk1, s32 prot, s32 unk2, vm::cptr str1, u32 str_len) -{ - ppu.state += cpu_flag::wait; - - sys_fs.warning("sys_fs_mount(dev_name=%s, file_system=%s, path=%s, unk1=0x%x, prot=%d, unk3=0x%x, str1=%s, str_len=%d)", dev_name, file_system, path, unk1, prot, unk2, str1, str_len); - - const auto [dev_error, device_name] = translate_to_str(dev_name, false); - - if (dev_error) - { - return {dev_error, device_name}; - } - - const auto [fs_error, filesystem] = translate_to_str(file_system, false); - - if (fs_error) - { - return {fs_error, filesystem}; - } - - const auto [path_error, path_sv] = translate_to_str(path); - - if (path_error) - { - return {path_error, path_sv}; - } - - const std::string vpath = lv2_fs_object::get_normalized_path(path_sv); - - std::string vfs_path; - const auto mp = lv2_fs_object::get_mp(device_name, &vfs_path); - std::unique_lock lock(mp->mutex, std::defer_lock); - - if (!g_ps3_process_info.has_root_perm() && mp != &g_mp_sys_dev_usb) - return {CELL_EPERM, device_name}; - - if (mp == &g_mp_sys_no_device) - return {CELL_ENXIO, device_name}; - - if (g_fxo->get().is_device_mounted(device_name) || !lock.try_lock()) - return {CELL_EBUSY, device_name}; - - if (vfs_path.empty()) - return {CELL_ENOTSUP, device_name}; - - if (vpath.find_first_not_of('/') == umax || !vfs::get(vpath).empty()) - return {CELL_EEXIST, vpath}; - - if (mp == &g_mp_sys_dev_hdd1) - { - const std::string_view appname = g_ps3_process_info.get_cellos_appname(); - vfs_path = fmt::format("%s/caches/%s", vfs_path, appname.substr(0, appname.find_last_of('.'))); - } - - if (!vfs_path.ends_with('/')) - vfs_path += '/'; - - if (!fs::is_dir(vfs_path) && !fs::create_dir(vfs_path)) - { - sys_fs.error("Failed to create directory \"%s\"", vfs_path); - return {CELL_EIO, vfs_path}; - } - - const bool is_simplefs = filesystem == "CELL_FS_SIMPLEFS"sv; - - if (is_simplefs) - { - vfs_path += "simplefs.tmp"; - if (fs::file simplefs_file; simplefs_file.open(vfs_path, fs::create + fs::read + fs::write + fs::trunc + fs::lock)) - { - const u64 file_size = mp->sector_size; // One sector's size is enough for VSH's simplefs check - simplefs_file.trunc(file_size); - sys_fs.notice("Created a simplefs file at \"%s\"", vfs_path); - } - else - { - sys_fs.error("Failed to create simplefs file \"%s\"", vfs_path); - return {CELL_EIO, vfs_path}; - } - } - - if (!vfs::mount(vpath, vfs_path, !is_simplefs)) - { - if (is_simplefs) - { - if (fs::remove_file(vfs_path)) - { - sys_fs.notice("Removed simplefs file \"%s\"", vfs_path); - } - else - { - sys_fs.error("Failed to remove simplefs file \"%s\"", vfs_path); - } - } - - return CELL_EIO; - } - - g_fxo->get().add(vpath, mp, device_name, filesystem, prot); - - return CELL_OK; -} - -error_code sys_fs_unmount(ppu_thread& ppu, vm::cptr path, s32 unk1, s32 force) -{ - ppu.state += cpu_flag::wait; - - sys_fs.warning("sys_fs_unmount(path=%s, unk1=0x%x, force=%d)", path, unk1, force); - - const auto [path_error, vpath] = translate_to_str(path); - - if (path_error) - { - return {path_error, vpath}; - } - - const auto& mp = g_fxo->get().lookup(vpath); - std::unique_lock lock(mp->mutex, std::defer_lock); - - if (!g_ps3_process_info.has_root_perm() && mp != &g_mp_sys_dev_usb) - return {CELL_EPERM, vpath}; - - if (mp == &g_mp_sys_no_device) - return {CELL_EINVAL, vpath}; - - if (mp == &g_mp_sys_dev_root || (!lock.try_lock() && !force)) - return {CELL_EBUSY, vpath}; - - if (!lv2_fs_mount_info_map::vfs_unmount(vpath)) - return {CELL_EIO, vpath}; - - return CELL_OK; -} diff --git a/rpcs3/Emu/Cell/lv2/sys_fs.h b/rpcs3/Emu/Cell/lv2/sys_fs.h deleted file mode 100644 index 9acf7fa18..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_fs.h +++ /dev/null @@ -1,671 +0,0 @@ -#pragma once - -#include "Emu/Memory/vm_ptr.h" -#include "Emu/Cell/ErrorCodes.h" -#include "util/File.h" -#include "util/StrUtil.h" -#include "util/mutex.h" - -#include - -// Open Flags -enum : s32 -{ - CELL_FS_O_RDONLY = 000000, - CELL_FS_O_WRONLY = 000001, - CELL_FS_O_RDWR = 000002, - CELL_FS_O_ACCMODE = 000003, - CELL_FS_O_CREAT = 000100, - CELL_FS_O_EXCL = 000200, - CELL_FS_O_TRUNC = 001000, - CELL_FS_O_APPEND = 002000, - CELL_FS_O_MSELF = 010000, - CELL_FS_O_UNK = 01000000, // Tests have shown this is independent of other flags. Only known to be called in Rockband games. -}; - -// Seek Mode -enum : s32 -{ - CELL_FS_SEEK_SET, - CELL_FS_SEEK_CUR, - CELL_FS_SEEK_END, -}; - -enum : s32 -{ - CELL_FS_MAX_FS_PATH_LENGTH = 1024, - CELL_FS_MAX_FS_FILE_NAME_LENGTH = 255, - CELL_FS_MAX_MP_LENGTH = 31, -}; - -enum : s32 -{ - CELL_FS_S_IFMT = 0170000, - CELL_FS_S_IFDIR = 0040000, // directory - CELL_FS_S_IFREG = 0100000, // regular - CELL_FS_S_IFLNK = 0120000, // symbolic link - CELL_FS_S_IFWHT = 0160000, // unknown - - CELL_FS_S_IRUSR = 0000400, // R for owner - CELL_FS_S_IWUSR = 0000200, // W for owner - CELL_FS_S_IXUSR = 0000100, // X for owner - - CELL_FS_S_IRGRP = 0000040, // R for group - CELL_FS_S_IWGRP = 0000020, // W for group - CELL_FS_S_IXGRP = 0000010, // X for group - - CELL_FS_S_IROTH = 0000004, // R for other - CELL_FS_S_IWOTH = 0000002, // W for other - CELL_FS_S_IXOTH = 0000001, // X for other -}; - -// CellFsDirent.d_type -enum : u8 -{ - CELL_FS_TYPE_UNKNOWN = 0, - CELL_FS_TYPE_DIRECTORY = 1, - CELL_FS_TYPE_REGULAR = 2, - CELL_FS_TYPE_SYMLINK = 3, -}; - -enum : u32 -{ - CELL_FS_IO_BUFFER_PAGE_SIZE_64KB = 0x0002, - CELL_FS_IO_BUFFER_PAGE_SIZE_1MB = 0x0004, -}; - -struct CellFsDirent -{ - u8 d_type; - u8 d_namlen; - char d_name[256]; -}; - -struct CellFsStat -{ - be_t mode; - be_t uid; - be_t gid; - be_t atime; - be_t mtime; - be_t ctime; - be_t size; - be_t blksize; -}; - -CHECK_SIZE_ALIGN(CellFsStat, 52, 4); - -struct CellFsDirectoryEntry -{ - CellFsStat attribute; - CellFsDirent entry_name; -}; - -struct CellFsUtimbuf -{ - be_t actime; - be_t modtime; -}; - -CHECK_SIZE_ALIGN(CellFsUtimbuf, 16, 4); - -// MSelf file structs -struct FsMselfHeader -{ - be_t m_magic; - be_t m_format_version; - be_t m_file_size; - be_t m_entry_num; - be_t m_entry_size; - u8 m_reserve[40]; -}; - -struct FsMselfEntry -{ - char m_name[32]; - be_t m_offset; - be_t m_size; - u8 m_reserve[16]; -}; - -enum class lv2_mp_flag -{ - read_only, - no_uid_gid, - strict_get_block_size, - cache, - - __bitset_enum_max -}; - -enum class lv2_file_type -{ - regular = 0, - sdata, - edata, -}; - -struct lv2_fs_mount_point -{ - const std::string_view root; - const std::string_view file_system; - const std::string_view device; - const u32 sector_size = 512; - const u64 sector_count = 256; - const u32 block_size = 4096; - const bs_t flags{}; - lv2_fs_mount_point* const next = nullptr; - - mutable shared_mutex mutex; -}; - -extern lv2_fs_mount_point g_mp_sys_dev_hdd0; -extern lv2_fs_mount_point g_mp_sys_no_device; - -struct lv2_fs_mount_info -{ - lv2_fs_mount_point* const mp; - const std::string device; - const std::string file_system; - const bool read_only; - - lv2_fs_mount_info(lv2_fs_mount_point* mp = nullptr, std::string_view device = {}, std::string_view file_system = {}, bool read_only = false) - : mp(mp ? mp : &g_mp_sys_no_device), device(device.empty() ? this->mp->device : device), file_system(file_system.empty() ? this->mp->file_system : file_system), read_only((this->mp->flags & lv2_mp_flag::read_only) || read_only) // respect the original flags of the mount point as well - { - } - - constexpr bool operator==(const lv2_fs_mount_info& rhs) const noexcept - { - return this == &rhs; - } - constexpr bool operator==(const lv2_fs_mount_point* const& rhs) const noexcept - { - return mp == rhs; - } - constexpr lv2_fs_mount_point* operator->() const noexcept - { - return mp; - } -}; - -extern lv2_fs_mount_info g_mi_sys_not_found; - -struct CellFsMountInfo; // Forward Declaration - -struct lv2_fs_mount_info_map -{ -public: - SAVESTATE_INIT_POS(40); - - lv2_fs_mount_info_map(); - lv2_fs_mount_info_map(const lv2_fs_mount_info_map&) = delete; - lv2_fs_mount_info_map& operator=(const lv2_fs_mount_info_map&) = delete; - ~lv2_fs_mount_info_map(); - - // Forwarding arguments to map.try_emplace(): refer to the constructor of lv2_fs_mount_info - template - bool add(Args&&... args) - { - return map.try_emplace(std::forward(args)...).second; - } - bool remove(std::string_view path); - const lv2_fs_mount_info& lookup(std::string_view path, bool no_cell_fs_path = false, std::string* mount_path = nullptr) const; - u64 get_all(CellFsMountInfo* info = nullptr, u64 len = 0) const; - bool is_device_mounted(std::string_view device_name) const; - - static bool vfs_unmount(std::string_view vpath, bool remove_from_map = true); - -private: - std::unordered_map> map; -}; - -struct lv2_fs_object -{ - static constexpr u32 id_base = 3; - static constexpr u32 id_step = 1; - static constexpr u32 id_count = 255 - id_base; - static constexpr bool id_lowest = true; - SAVESTATE_INIT_POS(49); - - // File Name (max 1055) - const std::array name; - - // Mount Info - const lv2_fs_mount_info& mp; - -protected: - lv2_fs_object(std::string_view filename); - lv2_fs_object(utils::serial& ar, bool dummy); - -public: - lv2_fs_object(const lv2_fs_object&) = delete; - - lv2_fs_object& operator=(const lv2_fs_object&) = delete; - - // Normalize a virtual path - static std::string get_normalized_path(std::string_view path); - - // Get the device's root path (e.g. "/dev_hdd0") from a given path - static std::string get_device_root(std::string_view filename); - - // Filename can be either a path starting with '/' or a CELL_FS device name - // This should be used only when handling devices that are not mounted - // Otherwise, use g_fxo->get().lookup() to look up mounted devices accurately - static lv2_fs_mount_point* get_mp(std::string_view filename, std::string* vfs_path = nullptr); - - static std::array get_name(std::string_view filename) - { - std::array name; - - if (filename.size() >= 0x420) - { - filename = filename.substr(0, 0x420 - 1); - } - - filename.copy(name.data(), filename.size()); - name[filename.size()] = 0; - return name; - } - - void save(utils::serial&) {} -}; - -struct lv2_file final : lv2_fs_object -{ - static constexpr u32 id_type = 1; - - fs::file file; - const s32 mode; - const s32 flags; - std::string real_path; - const lv2_file_type type; - - // IO Container - u32 ct_id{}, ct_used{}; - - // Stream lock - atomic_t lock{0}; - - // Some variables for convenience of data restoration - struct save_restore_t - { - u64 seek_pos; - u64 atime; - u64 mtime; - } restore_data{}; - - lv2_file(std::string_view filename, fs::file&& file, s32 mode, s32 flags, const std::string& real_path, lv2_file_type type = {}) - : lv2_fs_object(filename), file(std::move(file)), mode(mode), flags(flags), real_path(real_path), type(type) - { - } - - lv2_file(const lv2_file& host, fs::file&& file, s32 mode, s32 flags, const std::string& real_path, lv2_file_type type = {}) - : lv2_fs_object(host.name.data()), file(std::move(file)), mode(mode), flags(flags), real_path(real_path), type(type) - { - } - - lv2_file(utils::serial& ar); - void save(utils::serial& ar); - - struct open_raw_result_t - { - CellError error; - fs::file file; - }; - - struct open_result_t - { - CellError error; - std::string ppath; - std::string real_path; - fs::file file; - lv2_file_type type; - }; - - // Open a file with wrapped logic of sys_fs_open - static open_raw_result_t open_raw(const std::string& path, s32 flags, s32 mode, lv2_file_type type = lv2_file_type::regular, const lv2_fs_mount_info& mp = g_mi_sys_not_found); - static open_result_t open(std::string_view vpath, s32 flags, s32 mode, const void* arg = {}, u64 size = 0); - - // File reading with intermediate buffer - static u64 op_read(const fs::file& file, vm::ptr buf, u64 size, u64 opt_pos = umax); - - u64 op_read(vm::ptr buf, u64 size, u64 opt_pos = umax) const - { - return op_read(file, buf, size, opt_pos); - } - - // File writing with intermediate buffer - static u64 op_write(const fs::file& file, vm::cptr buf, u64 size); - - u64 op_write(vm::cptr buf, u64 size) const - { - return op_write(file, buf, size); - } - - // For MSELF support - struct file_view; - - // Make file view from lv2_file object (for MSELF support) - static fs::file make_view(const shared_ptr& _file, u64 offset); -}; - -struct lv2_dir final : lv2_fs_object -{ - static constexpr u32 id_type = 2; - - const std::vector entries; - - // Current reading position - atomic_t pos{0}; - - lv2_dir(std::string_view filename, std::vector&& entries) - : lv2_fs_object(filename), entries(std::move(entries)) - { - } - - lv2_dir(utils::serial& ar); - void save(utils::serial& ar); - - // Read next - const fs::dir_entry* dir_read() - { - const u64 old_pos = pos; - - if (const u64 cur = (old_pos < entries.size() ? pos++ : old_pos); cur < entries.size()) - { - return &entries[cur]; - } - - return nullptr; - } -}; - -// sys_fs_fcntl arg base class (left empty for PODness) -struct lv2_file_op -{ -}; - -namespace vtable -{ - struct lv2_file_op - { - // Speculation - vm::bptrb(vm::ptrb)> get_data; - vm::bptrb)> get_size; - vm::bptrb)> _dtor1; - vm::bptrb)> _dtor2; - }; -} // namespace vtable - -// sys_fs_fcntl: read with offset, write with offset -struct lv2_file_op_rw : lv2_file_op -{ - vm::bptrb _vtable; - - be_t op; - be_t _x8; // ??? - be_t _xc; // ??? - - be_t fd; // File descriptor (3..255) - vm::bptrb buf; // Buffer for data - be_t offset; // File offset - be_t size; // Access size - - be_t out_code; // Op result - be_t out_size; // Size processed -}; - -CHECK_SIZE(lv2_file_op_rw, 0x38); - -// sys_fs_fcntl: cellFsSdataOpenByFd -struct lv2_file_op_09 : lv2_file_op -{ - vm::bptrb _vtable; - - be_t op; - be_t _x8; - be_t _xc; - - be_t fd; - be_t offset; - be_t _vtabl2; - be_t arg1; // 0x180 - be_t arg2; // 0x10 - be_t arg_size; // 6th arg - be_t arg_ptr; // 5th arg - - be_t _x34; - be_t out_code; - be_t out_fd; -}; - -CHECK_SIZE(lv2_file_op_09, 0x40); - -struct lv2_file_e0000025 : lv2_file_op -{ - be_t size; // 0x30 - be_t _x4; // 0x10 - be_t _x8; // 0x28 - offset of out_code - be_t name_size; - vm::bcptr name; - be_t _x14; - be_t _x18; // 0 - be_t _x1c; // 0 - be_t _x20; // 16 - be_t _x24; // unk, seems to be memory location - be_t out_code; // out_code - be_t fd; // 0xffffffff - likely fd out -}; - -CHECK_SIZE(lv2_file_e0000025, 0x30); - -// sys_fs_fnctl: cellFsGetDirectoryEntries -struct lv2_file_op_dir : lv2_file_op -{ - struct dir_info : lv2_file_op - { - be_t _code; // Op result - be_t _size; // Number of entries written - vm::bptrb ptr; - be_t max; - }; - - CHECK_SIZE(dir_info, 0x10); - - vm::bptrb _vtable; - - be_t op; - be_t _x8; - dir_info arg; -}; - -CHECK_SIZE(lv2_file_op_dir, 0x1c); - -// sys_fs_fcntl: cellFsGetFreeSize (for dev_hdd0) -struct lv2_file_c0000002 : lv2_file_op -{ - vm::bptrb _vtable; - - be_t op; - be_t _x8; - vm::bcptr path; - be_t _x10; // 0 - be_t _x14; - - be_t out_code; // CELL_ENOSYS - be_t out_block_size; - be_t out_block_count; -}; - -CHECK_SIZE(lv2_file_c0000002, 0x28); - -// sys_fs_fcntl: unknown (called before cellFsOpen, for example) -struct lv2_file_c0000006 : lv2_file_op -{ - be_t size; // 0x20 - be_t _x4; // 0x10 - be_t _x8; // 0x18 - offset of out_code - be_t name_size; - vm::bcptr name; - be_t _x14; // 0 - be_t out_code; // 0x80010003 - be_t out_id; // set to 0, may return 0x1b5 -}; - -CHECK_SIZE(lv2_file_c0000006, 0x20); - -// sys_fs_fcntl: cellFsArcadeHddSerialNumber -struct lv2_file_c0000007 : lv2_file_op -{ - be_t out_code; // set to 0 - vm::bcptr device; // CELL_FS_IOS:ATA_HDD - be_t device_size; // 0x14 - vm::bptr model; - be_t model_size; // 0x29 - vm::bptr serial; - be_t serial_size; // 0x15 -}; - -CHECK_SIZE(lv2_file_c0000007, 0x1c); - -struct lv2_file_c0000008 : lv2_file_op -{ - u8 _x0[4]; - be_t op; // 0xC0000008 - u8 _x8[8]; - be_t container_id; - be_t size; - be_t page_type; // 0x4000 for cellFsSetDefaultContainer - // 0x4000 | page_type given by user, valid values seem to be: - // CELL_FS_IO_BUFFER_PAGE_SIZE_64KB 0x0002 - // CELL_FS_IO_BUFFER_PAGE_SIZE_1MB 0x0004 - be_t out_code; - u8 _x24[4]; -}; - -CHECK_SIZE(lv2_file_c0000008, 0x28); - -struct lv2_file_c0000015 : lv2_file_op -{ - be_t size; // 0x20 - be_t _x4; // 0x10 - be_t _x8; // 0x18 - offset of out_code - be_t path_size; - vm::bcptr path; - be_t _x14; // - be_t vendorID; - be_t productID; - be_t out_code; // set to 0 -}; - -CHECK_SIZE(lv2_file_c0000015, 0x20); - -struct lv2_file_c000001a : lv2_file_op -{ - be_t disc_retry_type; // CELL_FS_DISC_READ_RETRY_NONE results in a 0 here - // CELL_FS_DISC_READ_RETRY_DEFAULT results in a 0x63 here - be_t _x4; // 0 - be_t _x8; // 0x000186A0 - be_t _xC; // 0 - be_t _x10; // 0 - be_t _x14; // 0 -}; - -CHECK_SIZE(lv2_file_c000001a, 0x18); - -struct lv2_file_c000001c : lv2_file_op -{ - be_t size; // 0x60 - be_t _x4; // 0x10 - be_t _x8; // 0x18 - offset of out_code - be_t path_size; - vm::bcptr path; - be_t unk1; - be_t vendorID; - be_t productID; - be_t out_code; // set to 0 - be_t serial[32]; -}; - -CHECK_SIZE(lv2_file_c000001c, 0x60); - -// sys_fs_fcntl: cellFsAllocateFileAreaWithoutZeroFill -struct lv2_file_e0000017 : lv2_file_op -{ - be_t size; // 0x28 - be_t _x4; // 0x10, offset - be_t _x8; // 0x20, offset - be_t _xc; // - - vm::bcptr file_path; - be_t file_size; - be_t out_code; -}; - -CHECK_SIZE(lv2_file_e0000017, 0x28); - -struct CellFsMountInfo -{ - char mount_path[0x20]; // 0x0 - char filesystem[0x20]; // 0x20 - char dev_name[0x40]; // 0x40 - be_t unk[5]; // 0x80, probably attributes -}; - -CHECK_SIZE(CellFsMountInfo, 0x94); - -// Default IO container -struct default_sys_fs_container -{ - shared_mutex mutex; - u32 id = 0; - u32 cap = 0; - u32 used = 0; -}; - -// Syscalls - -error_code sys_fs_test(ppu_thread& ppu, u32 arg1, u32 arg2, vm::ptr arg3, u32 arg4, vm::ptr buf, u32 buf_size); -error_code sys_fs_open(ppu_thread& ppu, vm::cptr path, s32 flags, vm::ptr fd, s32 mode, vm::cptr arg, u64 size); -error_code sys_fs_read(ppu_thread& ppu, u32 fd, vm::ptr buf, u64 nbytes, vm::ptr nread); -error_code sys_fs_write(ppu_thread& ppu, u32 fd, vm::cptr buf, u64 nbytes, vm::ptr nwrite); -error_code sys_fs_close(ppu_thread& ppu, u32 fd); -error_code sys_fs_opendir(ppu_thread& ppu, vm::cptr path, vm::ptr fd); -error_code sys_fs_readdir(ppu_thread& ppu, u32 fd, vm::ptr dir, vm::ptr nread); -error_code sys_fs_closedir(ppu_thread& ppu, u32 fd); -error_code sys_fs_stat(ppu_thread& ppu, vm::cptr path, vm::ptr sb); -error_code sys_fs_fstat(ppu_thread& ppu, u32 fd, vm::ptr sb); -error_code sys_fs_link(ppu_thread& ppu, vm::cptr from, vm::cptr to); -error_code sys_fs_mkdir(ppu_thread& ppu, vm::cptr path, s32 mode); -error_code sys_fs_rename(ppu_thread& ppu, vm::cptr from, vm::cptr to); -error_code sys_fs_rmdir(ppu_thread& ppu, vm::cptr path); -error_code sys_fs_unlink(ppu_thread& ppu, vm::cptr path); -error_code sys_fs_access(ppu_thread& ppu, vm::cptr path, s32 mode); -error_code sys_fs_fcntl(ppu_thread& ppu, u32 fd, u32 op, vm::ptr arg, u32 size); -error_code sys_fs_lseek(ppu_thread& ppu, u32 fd, s64 offset, s32 whence, vm::ptr pos); -error_code sys_fs_fdatasync(ppu_thread& ppu, u32 fd); -error_code sys_fs_fsync(ppu_thread& ppu, u32 fd); -error_code sys_fs_fget_block_size(ppu_thread& ppu, u32 fd, vm::ptr sector_size, vm::ptr block_size, vm::ptr arg4, vm::ptr out_flags); -error_code sys_fs_get_block_size(ppu_thread& ppu, vm::cptr path, vm::ptr sector_size, vm::ptr block_size, vm::ptr arg4); -error_code sys_fs_truncate(ppu_thread& ppu, vm::cptr path, u64 size); -error_code sys_fs_ftruncate(ppu_thread& ppu, u32 fd, u64 size); -error_code sys_fs_symbolic_link(ppu_thread& ppu, vm::cptr target, vm::cptr linkpath); -error_code sys_fs_chmod(ppu_thread& ppu, vm::cptr path, s32 mode); -error_code sys_fs_chown(ppu_thread& ppu, vm::cptr path, s32 uid, s32 gid); -error_code sys_fs_disk_free(ppu_thread& ppu, vm::cptr path, vm::ptr total_free, vm::ptr avail_free); -error_code sys_fs_utime(ppu_thread& ppu, vm::cptr path, vm::cptr timep); -error_code sys_fs_acl_read(ppu_thread& ppu, vm::cptr path, vm::ptr); -error_code sys_fs_acl_write(ppu_thread& ppu, vm::cptr path, vm::ptr); -error_code sys_fs_lsn_get_cda_size(ppu_thread& ppu, u32 fd, vm::ptr ptr); -error_code sys_fs_lsn_get_cda(ppu_thread& ppu, u32 fd, vm::ptr, u64, vm::ptr); -error_code sys_fs_lsn_lock(ppu_thread& ppu, u32 fd); -error_code sys_fs_lsn_unlock(ppu_thread& ppu, u32 fd); -error_code sys_fs_lsn_read(ppu_thread& ppu, u32 fd, vm::cptr, u64); -error_code sys_fs_lsn_write(ppu_thread& ppu, u32 fd, vm::cptr, u64); -error_code sys_fs_mapped_allocate(ppu_thread& ppu, u32 fd, u64, vm::pptr out_ptr); -error_code sys_fs_mapped_free(ppu_thread& ppu, u32 fd, vm::ptr ptr); -error_code sys_fs_truncate2(ppu_thread& ppu, u32 fd, u64 size); -error_code sys_fs_newfs(ppu_thread& ppu, vm::cptr dev_name, vm::cptr file_system, s32 unk1, vm::cptr str1); -error_code sys_fs_mount(ppu_thread& ppu, vm::cptr dev_name, vm::cptr file_system, vm::cptr path, s32 unk1, s32 prot, s32 unk2, vm::cptr str1, u32 str_len); -error_code sys_fs_unmount(ppu_thread& ppu, vm::cptr path, s32 unk1, s32 force); -error_code sys_fs_get_mount_info_size(ppu_thread& ppu, vm::ptr len); -error_code sys_fs_get_mount_info(ppu_thread& ppu, vm::ptr info, u64 len, vm::ptr out_len); diff --git a/rpcs3/Emu/Cell/lv2/sys_game.cpp b/rpcs3/Emu/Cell/lv2/sys_game.cpp deleted file mode 100644 index 0bd23e0c7..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_game.cpp +++ /dev/null @@ -1,295 +0,0 @@ -#include "stdafx.h" -#include "util/sysinfo.hpp" -#include "util/v128.hpp" -#include "Emu/Cell/lv2/sys_process.h" -#include "Emu/Memory/vm_ptr.h" -#include "Emu/Cell/ErrorCodes.h" -#include "Emu/System.h" -#include "Emu/system_utils.hpp" -#include "Emu/IdManager.h" -#include "util/StrUtil.h" -#include "util/Thread.h" - -#include "Emu/Cell/timers.hpp" -#include "sys_game.h" - -LOG_CHANNEL(sys_game); - -struct system_sw_version -{ - system_sw_version() - { - f64 version_f = 0; - if (!try_to_float(&version_f, utils::get_firmware_version(), 0.0f, 99.9999f)) - sys_game.error("Error parsing firmware version"); - version = static_cast(version_f * 10000); - } - - system_sw_version(const system_sw_version&) = delete; - - system_sw_version& operator=(const system_sw_version&) = delete; - - ~system_sw_version() = default; - - atomic_t version; -}; - -struct board_storage -{ -public: - bool read(u8* buffer) - { - if (!buffer) - return false; - - const auto data = storage.load(); - memcpy(buffer, &data, size); - - return true; - } - - bool write(u8* buffer) - { - if (!buffer) - return false; - - storage.store(read_from_ptr>(buffer)); - written = true; - - return true; - } - - board_storage() - { - memset(&storage.raw(), -1, size); - if (fs::file file; file.open(file_path, fs::read)) - file.read(&storage.raw(), std::min(file.size(), size)); - } - - board_storage(const board_storage&) = delete; - - board_storage& operator=(const board_storage&) = delete; - - ~board_storage() - { - if (written) - { - if (fs::file file; file.open(file_path, fs::create + fs::write + fs::lock)) - { - file.write(&storage.raw(), size); - file.trunc(size); - } - } - } - -private: - atomic_be_t storage; - bool written = false; - const std::string file_path = rpcs3::utils::get_hdd1_dir() + "/caches/board_storage.bin"; - static constexpr u64 size = sizeof(v128); -}; - -struct watchdog_t -{ - struct alignas(8) control_t - { - bool needs_restart = false; - bool active = false; - char pad[sizeof(u32) - sizeof(bool) * 2]{}; - u32 timeout = 0; - }; - - atomic_t control; - - void operator()() - { - u64 start_time = get_system_time(); - u64 old_time = start_time; - u64 current_time = old_time; - - constexpr u64 sleep_time = 50'000; - - while (thread_ctrl::state() != thread_state::aborting) - { - if (Emu.GetStatus(false) == system_state::paused) - { - start_time += current_time - old_time; - old_time = current_time; - thread_ctrl::wait_for(sleep_time); - current_time = get_system_time(); - continue; - } - - old_time = std::exchange(current_time, get_system_time()); - - const auto old = control.fetch_op([&](control_t& data) - { - if (data.needs_restart) - { - data.needs_restart = false; - return true; - } - - return false; - }) - .first; - - if (old.active && old.needs_restart) - { - start_time = current_time; - old_time = current_time; - continue; - } - - if (old.active && current_time - start_time >= old.timeout) - { - sys_game.success("Watchdog timeout! Restarting the game..."); - - Emu.CallFromMainThread([]() - { - Emu.Restart(false); - }); - - return; - } - - thread_ctrl::wait_for(sleep_time); - } - } - - static constexpr auto thread_name = "LV2 Watchdog Thread"sv; -}; - -void abort_lv2_watchdog() -{ - if (auto thr = g_fxo->try_get>()) - { - sys_game.notice("Aborting %s...", thr->thread_name); - *thr = thread_state::aborting; - } -} - -error_code _sys_game_watchdog_start(u32 timeout) -{ - sys_game.trace("sys_game_watchdog_start(timeout=%d)", timeout); - - // According to disassembly - timeout *= 1'000'000; - timeout &= -64; - - if (!g_fxo->get>().control.fetch_op([&](watchdog_t::control_t& data) - { - if (data.active) - { - return false; - } - - data.needs_restart = true; - data.active = true; - data.timeout = timeout; - return true; - }) - .second) - { - return CELL_EABORT; - } - - return CELL_OK; -} - -error_code _sys_game_watchdog_stop() -{ - sys_game.trace("sys_game_watchdog_stop()"); - - g_fxo->get>().control.fetch_op([](watchdog_t::control_t& data) - { - if (!data.active) - { - return false; - } - - data.active = false; - return true; - }); - - return CELL_OK; -} - -error_code _sys_game_watchdog_clear() -{ - sys_game.trace("sys_game_watchdog_clear()"); - - g_fxo->get>().control.fetch_op([](watchdog_t::control_t& data) - { - if (!data.active || data.needs_restart) - { - return false; - } - - data.needs_restart = true; - return true; - }); - - return CELL_OK; -} - -error_code _sys_game_set_system_sw_version(u64 version) -{ - sys_game.trace("sys_game_set_system_sw_version(version=%d)", version); - - if (!g_ps3_process_info.has_root_perm()) - return CELL_ENOSYS; - - g_fxo->get().version = version; - - return CELL_OK; -} - -u64 _sys_game_get_system_sw_version() -{ - sys_game.trace("sys_game_get_system_sw_version()"); - - return g_fxo->get().version; -} - -error_code _sys_game_board_storage_read(vm::ptr buffer, vm::ptr status) -{ - sys_game.trace("sys_game_board_storage_read(buffer=*0x%x, status=*0x%x)", buffer, status); - - if (!buffer || !status) - { - return CELL_EFAULT; - } - - *status = g_fxo->get().read(buffer.get_ptr()) ? 0x00 : 0xFF; - - return CELL_OK; -} - -error_code _sys_game_board_storage_write(vm::ptr buffer, vm::ptr status) -{ - sys_game.trace("sys_game_board_storage_write(buffer=*0x%x, status=*0x%x)", buffer, status); - - if (!buffer || !status) - { - return CELL_EFAULT; - } - - *status = g_fxo->get().write(buffer.get_ptr()) ? 0x00 : 0xFF; - - return CELL_OK; -} - -error_code _sys_game_get_rtc_status(vm::ptr status) -{ - sys_game.trace("sys_game_get_rtc_status(status=*0x%x)", status); - - if (!status) - { - return CELL_EFAULT; - } - - *status = 0; - - return CELL_OK; -} diff --git a/rpcs3/Emu/Cell/lv2/sys_gamepad.cpp b/rpcs3/Emu/Cell/lv2/sys_gamepad.cpp deleted file mode 100644 index 26ffbc269..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_gamepad.cpp +++ /dev/null @@ -1,99 +0,0 @@ -#include "stdafx.h" -#include "sys_gamepad.h" -#include "Emu/Cell/ErrorCodes.h" - -LOG_CHANNEL(sys_gamepad); - -u32 sys_gamepad_ycon_initalize(vm::ptr in, vm::ptr out) -{ - sys_gamepad.todo("sys_gamepad_ycon_initalize(in=%d, out=%d) -> CELL_OK", in, out); - return CELL_OK; -} - -u32 sys_gamepad_ycon_finalize(vm::ptr in, vm::ptr out) -{ - sys_gamepad.todo("sys_gamepad_ycon_finalize(in=%d, out=%d) -> CELL_OK", in, out); - return CELL_OK; -} - -u32 sys_gamepad_ycon_has_input_ownership(vm::ptr in, vm::ptr out) -{ - sys_gamepad.todo("sys_gamepad_ycon_has_input_ownership(in=%d, out=%d) -> CELL_OK", in, out); - return CELL_OK; -} - -u32 sys_gamepad_ycon_enumerate_device(vm::ptr in, vm::ptr out) -{ - sys_gamepad.todo("sys_gamepad_ycon_enumerate_device(in=%d, out=%d) -> CELL_OK", in, out); - return CELL_OK; -} - -u32 sys_gamepad_ycon_get_device_info(vm::ptr in, vm::ptr out) -{ - sys_gamepad.todo("sys_gamepad_ycon_get_device_info(in=%d, out=%d) -> CELL_OK", in, out); - return CELL_OK; -} - -u32 sys_gamepad_ycon_read_raw_report(vm::ptr in, vm::ptr out) -{ - sys_gamepad.todo("sys_gamepad_ycon_read_raw_report(in=%d, out=%d) -> CELL_OK", in, out); - return CELL_OK; -} - -u32 sys_gamepad_ycon_write_raw_report(vm::ptr in, vm::ptr out) -{ - sys_gamepad.todo("sys_gamepad_ycon_write_raw_report(in=%d, out=%d) -> CELL_OK", in, out); - return CELL_OK; -} - -u32 sys_gamepad_ycon_get_feature(vm::ptr in, vm::ptr out) -{ - sys_gamepad.todo("sys_gamepad_ycon_get_feature(in=%d, out=%d) -> CELL_OK", in, out); - return CELL_OK; -} - -u32 sys_gamepad_ycon_set_feature(vm::ptr in, vm::ptr out) -{ - sys_gamepad.todo("sys_gamepad_ycon_set_feature(in=%d, out=%d) -> CELL_OK", in, out); - return CELL_OK; -} - -u32 sys_gamepad_ycon_is_gem(vm::ptr in, vm::ptr out) -{ - sys_gamepad.todo("sys_gamepad_ycon_is_gem(in=%d, out=%d) -> CELL_OK", in, out); - return CELL_OK; -} - -// syscall(621,packet_id,u8 *in,u8 *out) Talk:LV2_Functions_and_Syscalls#Syscall_621_.280x26D.29 gamepad_if usage -u32 sys_gamepad_ycon_if(u8 packet_id, vm::ptr in, vm::ptr out) -{ - - switch (packet_id) - { - case 0: - return sys_gamepad_ycon_initalize(in, out); - case 1: - return sys_gamepad_ycon_finalize(in, out); - case 2: - return sys_gamepad_ycon_has_input_ownership(in, out); - case 3: - return sys_gamepad_ycon_enumerate_device(in, out); - case 4: - return sys_gamepad_ycon_get_device_info(in, out); - case 5: - return sys_gamepad_ycon_read_raw_report(in, out); - case 6: - return sys_gamepad_ycon_write_raw_report(in, out); - case 7: - return sys_gamepad_ycon_get_feature(in, out); - case 8: - return sys_gamepad_ycon_set_feature(in, out); - case 9: - return sys_gamepad_ycon_is_gem(in, out); - default: - sys_gamepad.error("sys_gamepad_ycon_if(packet_id=*%d, in=%d, out=%d), unknown packet id", packet_id, in, out); - break; - } - - return CELL_OK; -} diff --git a/rpcs3/Emu/Cell/lv2/sys_gpio.cpp b/rpcs3/Emu/Cell/lv2/sys_gpio.cpp deleted file mode 100644 index 54eb84543..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_gpio.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "stdafx.h" -#include "sys_gpio.h" - -#include "Emu/Cell/ErrorCodes.h" - -LOG_CHANNEL(sys_gpio); - -error_code sys_gpio_get(u64 device_id, vm::ptr value) -{ - sys_gpio.trace("sys_gpio_get(device_id=0x%llx, value=*0x%x)", device_id, value); - - if (device_id != SYS_GPIO_LED_DEVICE_ID && device_id != SYS_GPIO_DIP_SWITCH_DEVICE_ID) - { - return CELL_ESRCH; - } - - // Retail consoles dont have LEDs or DIPs switches, hence always sets 0 in paramenter - if (!value.try_write(0)) - { - return CELL_EFAULT; - } - - return CELL_OK; -} - -error_code sys_gpio_set(u64 device_id, u64 mask, u64 value) -{ - sys_gpio.trace("sys_gpio_set(device_id=0x%llx, mask=0x%llx, value=0x%llx)", device_id, mask, value); - - // Retail consoles dont have LEDs or DIPs switches, hence the syscall can't modify devices's value - switch (device_id) - { - case SYS_GPIO_LED_DEVICE_ID: return CELL_OK; - case SYS_GPIO_DIP_SWITCH_DEVICE_ID: return CELL_EINVAL; - } - - return CELL_ESRCH; -} diff --git a/rpcs3/Emu/Cell/lv2/sys_hid.cpp b/rpcs3/Emu/Cell/lv2/sys_hid.cpp deleted file mode 100644 index 1cf90c7a5..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_hid.cpp +++ /dev/null @@ -1,190 +0,0 @@ -#include "stdafx.h" -#include "sys_hid.h" - -#include "Emu/Memory/vm_var.h" - -#include "Emu/Cell/PPUThread.h" -#include "Emu/Cell/ErrorCodes.h" -#include "rpcsx/fw/ps3/cellPad.h" - -#include "sys_process.h" - -LOG_CHANNEL(sys_hid); - -error_code sys_hid_manager_open(ppu_thread& ppu, u64 device_type, u64 port_no, vm::ptr handle) -{ - sys_hid.todo("sys_hid_manager_open(device_type=0x%llx, port_no=0x%llx, handle=*0x%llx)", device_type, port_no, handle); - - // device type == 1 = pad, 2 = kb, 3 = mouse - if (device_type > 3) - { - return CELL_EINVAL; - } - - if (!handle) - { - return CELL_EFAULT; - } - - // 'handle' starts at 0x100 in realhw, and increments every time sys_hid_manager_open is called - // however, sometimes the handle is reused when opening sys_hid_manager again (even when the previous one hasn't been closed yet) - maybe when processes/threads get killed/finish they also release their handles? - static u32 ctr = 0x100; - *handle = ctr++; - - if (device_type == 1) - { - cellPadInit(ppu, 7); - cellPadSetPortSetting(::narrow(port_no) /* 0 */, CELL_PAD_SETTING_LDD | CELL_PAD_SETTING_PRESS_ON | CELL_PAD_SETTING_SENSOR_ON); - } - - return CELL_OK; -} - -error_code sys_hid_manager_ioctl(u32 hid_handle, u32 pkg_id, vm::ptr buf, u64 buf_size) -{ - sys_hid.todo("sys_hid_manager_ioctl(hid_handle=0x%x, pkg_id=0x%llx, buf=*0x%x, buf_size=0x%llx)", hid_handle, pkg_id, buf, buf_size); - - // From realhw syscall dump when vsh boots - // SC count | handle | pkg_id | *buf (in) | *buf (out) | size -> ret - // ---------|--------|--------|---------------------------------------------------------------------------|---------------------------------------------------------------------------|------------ - // 28893 | 0x101 | 0x2 | 000000000000000000000000000000000000000000 | 054c02680102020000000000000008035000001c1f | 21 -> 0 - // 28894 | 0x101 | 0x3 | 00000000 | 00000000 | 4 -> 0 - // 28895 | 0x101 | 0x5 | 00000000 | 00000000 | 4 -> 0 - // 28896 | 0x101 | 0x68 | 01000000d0031cb020169e502006b7f80000000000606098000000000000000000000000d | 01000000d0031cb020169e502006b7f80000000000606098000000000000000000000000d | 64 -> 0 - // | | | 0031c90000000002006bac400000000d0031cb0000000002006b4d0 | 0031c90000000002006bac400000000d0031cb0000000002006b4d0 | - // 28898 | 0x102 | 0x2 | 000000000000000000000000000000000000000000 | 054c02680102020000000000000008035000001c1f | 21 -> 0 - // 28901 | 0x100 | 0x64 | 00000001 | 00000001 | 4 -> 0xffffffff80010002 # x3::hidportassign - // 2890 | 0x100 | 0x65 | 6b49d200 | 6b49d200 | 4 -> 0xffffffff80010002 # x3::hidportassign - // 28903 | 0x100 | 0x66 | 00000001 | 00000001 | 4 -> 0 # x3::hidportassign - // 28904 | 0x100 | 0x0 | 00000001000000ff000000ff000000ff000000ff000000010000000100000001000000010 | 00000001000000ff000000ff000000ff000000ff000000010000000100000001000000010 | 68 -> 0 # x3::hidportassign - // | | | 000000000000000000000000000000000000001000000010000000100000001 | 000000000000000000000000000000000000001000000010000000100000001 | - // 28907 | 0x101 | 0x3 | 00000001 | 00000001 | 4 -> 0 - // 28908 | 0x101 | 0x5 | 00000001 | 00000001 | 4 -> 0 - // 29404 | 0x100 | 0x4 | 00 | ee | 1 -> 0 - // *** repeats 30600, 31838, 33034, 34233, 35075 (35075 is x3::hidportassign) *** - // 35076 | 0x100 | 0x0 | 00000001000000ff000000ff000000ff000000ff000000320000003200000032000000320 | 00000001000000ff000000ff000000ff000000ff000000320000003200000032000000320 | 68 -> 0 - // | | | 000003200000032000000320000003200002710000027100000271000002710 | 000003200000032000000320000003200002710000027100000271000002710 | - // *** more 0x4 that have buf(in)=00 and buf(out)=ee *** - - if (pkg_id == 2) - { - // Return what realhw seems to return - // TODO: Figure out what this corresponds to - auto info = vm::static_ptr_cast(buf); - info->vid = 0x054C; - info->pid = 0x0268; - - u8 realhw[17] = {0x01, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x03, 0x50, 0x00, 0x00, 0x1c, 0x1f}; - memcpy(info->unk, &realhw, 17); - } - else if (pkg_id == 5) - { - auto info = vm::static_ptr_cast(buf); - info->vid = 0x054C; - info->pid = 0x0268; - } - // pkg_id == 6 == setpressmode? - else if (pkg_id == 0x68) - { - [[maybe_unused]] auto info = vm::static_ptr_cast(buf); - // info->unk2 = 0; - } - - return CELL_OK; -} - -error_code sys_hid_manager_check_focus() -{ - // spammy sys_hid.todo("sys_hid_manager_check_focus()"); - - return not_an_error(1); -} - -error_code sys_hid_manager_513(u64 a1, u64 a2, vm::ptr buf, u64 buf_size) -{ - sys_hid.todo("sys_hid_manager_513(%llx, %llx, buf=%llx, buf_size=%llx)", a1, a2, buf, buf_size); - - return CELL_OK; -} - -error_code sys_hid_manager_514(u32 pkg_id, vm::ptr buf, u64 buf_size) -{ - if (pkg_id == 0xE) - { - sys_hid.trace("sys_hid_manager_514(pkg_id=0x%x, buf=*0x%x, buf_size=0x%llx)", pkg_id, buf, buf_size); - } - else - { - sys_hid.todo("sys_hid_manager_514(pkg_id=0x%x, buf=*0x%x, buf_size=0x%llx)", pkg_id, buf, buf_size); - } - - if (pkg_id == 0xE) - { - // buf holds device_type - // auto device_type = vm::static_ptr_cast(buf); - // spammy sys_hid.todo("device_type: 0x%x", device_type[0]); - - // return 1 or 0? look like almost like another check_focus type check, returning 0 looks to keep system focus - } - else if (pkg_id == 0xD) - { - auto inf = vm::static_ptr_cast(buf); - // unk1 = (pad# << 24) | pad# | 0x100 - // return value doesn't seem to be used again - sys_hid.todo("unk1: 0x%x, unk2:0x%x", inf->unk1, inf->unk2); - } - - return CELL_OK; -} - -error_code sys_hid_manager_is_process_permission_root(u32 pid) -{ - sys_hid.todo("sys_hid_manager_is_process_permission_root(pid=0x%x)", pid); - - return not_an_error(g_ps3_process_info.has_root_perm()); -} - -error_code sys_hid_manager_add_hot_key_observer(u32 event_queue, vm::ptr unk) -{ - sys_hid.todo("sys_hid_manager_add_hot_key_observer(event_queue=0x%x, unk=*0x%x)", event_queue, unk); - - return CELL_OK; -} - -error_code sys_hid_manager_read(u32 handle, u32 pkg_id, vm::ptr buf, u64 buf_size) -{ - if (!buf) - { - return CELL_EFAULT; - } - - (pkg_id == 2 || pkg_id == 0x81 ? sys_hid.trace : sys_hid.todo)("sys_hid_manager_read(handle=0x%x, pkg_id=0x%x, buf=*0x%x, buf_size=0x%llx)", handle, pkg_id, buf, buf_size); - - if (pkg_id == 2) - { - // cellPadGetData - // it returns just button array from 'CellPadData' - // auto data = vm::static_ptr_cast(buf); - // todo: use handle and dont call cellpad here - vm::var tmpData; - if ((cellPadGetData(0, +tmpData) == CELL_OK) && tmpData->len > 0) - { - u64 cpySize = std::min(static_cast(tmpData->len) * sizeof(u16), buf_size * sizeof(u16)); - memcpy(buf.get_ptr(), &tmpData->button, cpySize); - return not_an_error(cpySize); - } - } - else if (pkg_id == 0x81) - { - // cellPadGetDataExtra? - vm::var tmpData; - if ((cellPadGetData(0, +tmpData) == CELL_OK) && tmpData->len > 0) - { - u64 cpySize = std::min(static_cast(tmpData->len) * sizeof(u16), buf_size * sizeof(u16)); - memcpy(buf.get_ptr(), &tmpData->button, cpySize); - return not_an_error(cpySize / 2); - } - } - - return CELL_OK; -} diff --git a/rpcs3/Emu/Cell/lv2/sys_hid.h b/rpcs3/Emu/Cell/lv2/sys_hid.h deleted file mode 100644 index 66acb7800..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_hid.h +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -#include "Emu/Memory/vm_ptr.h" -#include "Emu/Cell/ErrorCodes.h" - -// set sensor mode? also getinfo? -struct sys_hid_info_5 -{ - le_t vid; - le_t pid; - u8 status; - // todo: more in this, not sure what tho -}; - -struct sys_hid_info_2 -{ - be_t vid; - be_t pid; - u8 unk[17]; -}; - -struct sys_hid_ioctl_68 -{ - u8 unk; - u8 unk2; -}; - -// unk -struct sys_hid_manager_514_pkg_d -{ - be_t unk1; - u8 unk2; -}; - -// SysCalls - -error_code sys_hid_manager_open(ppu_thread& ppu, u64 device_type, u64 port_no, vm::ptr handle); -error_code sys_hid_manager_ioctl(u32 hid_handle, u32 pkg_id, vm::ptr buf, u64 buf_size); -error_code sys_hid_manager_add_hot_key_observer(u32 event_queue, vm::ptr unk); -error_code sys_hid_manager_check_focus(); -error_code sys_hid_manager_is_process_permission_root(u32 pid); -error_code sys_hid_manager_513(u64 a1, u64 a2, vm::ptr buf, u64 buf_size); -error_code sys_hid_manager_514(u32 pkg_id, vm::ptr buf, u64 buf_size); -error_code sys_hid_manager_read(u32 handle, u32 pkg_id, vm::ptr buf, u64 buf_size); diff --git a/rpcs3/Emu/Cell/lv2/sys_interrupt.cpp b/rpcs3/Emu/Cell/lv2/sys_interrupt.cpp deleted file mode 100644 index d78d17e89..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_interrupt.cpp +++ /dev/null @@ -1,283 +0,0 @@ -#include "stdafx.h" -#include "sys_interrupt.h" - -#include "Emu/IdManager.h" -#include "Emu/System.h" - -#include "Emu/Cell/ErrorCodes.h" -#include "Emu/Cell/PPUThread.h" -#include "Emu/Cell/SPUThread.h" -#include "Emu/Cell/PPUOpcodes.h" - -LOG_CHANNEL(sys_interrupt); - -lv2_int_tag::lv2_int_tag() noexcept - : lv2_obj(1), id(idm::last_id()) -{ -} - -lv2_int_tag::lv2_int_tag(utils::serial& ar) noexcept - : lv2_obj(1), id(idm::last_id()), handler([&]() - { - const u32 id = ar; - - auto ptr = idm::get_unlocked(id); - - if (!ptr && id) - { - Emu.PostponeInitCode([id, &handler = this->handler]() - { - handler = ensure(idm::get_unlocked(id)); - }); - } - - return ptr; - }()) -{ -} - -void lv2_int_tag::save(utils::serial& ar) -{ - ar(lv2_obj::check(handler) ? handler->id : 0); -} - -lv2_int_serv::lv2_int_serv(shared_ptr> thread, u64 arg1, u64 arg2) noexcept - : lv2_obj(1), id(idm::last_id()), thread(thread), arg1(arg1), arg2(arg2) -{ -} - -lv2_int_serv::lv2_int_serv(utils::serial& ar) noexcept - : lv2_obj(1), id(idm::last_id()), thread(idm::get_unlocked>(ar)), arg1(ar), arg2(ar) -{ -} - -void lv2_int_serv::save(utils::serial& ar) -{ - ar(thread && idm::check_unlocked>(thread->id) ? thread->id : 0, arg1, arg2); -} - -void ppu_interrupt_thread_entry(ppu_thread&, ppu_opcode_t, be_t*, struct ppu_intrp_func*); - -void lv2_int_serv::exec() const -{ - thread->cmd_list({{ppu_cmd::reset_stack, 0}, - {ppu_cmd::set_args, 2}, arg1, arg2, - {ppu_cmd::entry_call, 0}, - {ppu_cmd::sleep, 0}, - {ppu_cmd::ptr_call, 0}, - std::bit_cast(&ppu_interrupt_thread_entry)}); -} - -void ppu_thread_exit(ppu_thread&, ppu_opcode_t, be_t*, struct ppu_intrp_func*); - -void lv2_int_serv::join() const -{ - thread->cmd_list({{ppu_cmd::ptr_call, 0}, - std::bit_cast(&ppu_thread_exit)}); - - thread->cmd_notify.store(1); - thread->cmd_notify.notify_one(); - (*thread)(); - - idm::remove_verify>(thread->id, thread); -} - -error_code sys_interrupt_tag_destroy(ppu_thread& ppu, u32 intrtag) -{ - ppu.state += cpu_flag::wait; - - sys_interrupt.warning("sys_interrupt_tag_destroy(intrtag=0x%x)", intrtag); - - const auto tag = idm::withdraw(intrtag, [](lv2_int_tag& tag) -> CellError - { - if (lv2_obj::check(tag.handler)) - { - return CELL_EBUSY; - } - - tag.exists.release(0); - return {}; - }); - - if (!tag) - { - return CELL_ESRCH; - } - - if (tag.ret) - { - return tag.ret; - } - - return CELL_OK; -} - -error_code _sys_interrupt_thread_establish(ppu_thread& ppu, vm::ptr ih, u32 intrtag, u32 intrthread, u64 arg1, u64 arg2) -{ - ppu.state += cpu_flag::wait; - - sys_interrupt.warning("_sys_interrupt_thread_establish(ih=*0x%x, intrtag=0x%x, intrthread=0x%x, arg1=0x%llx, arg2=0x%llx)", ih, intrtag, intrthread, arg1, arg2); - - CellError error = CELL_EAGAIN; - - const u32 id = idm::import ([&]() - { - shared_ptr result; - - // Get interrupt tag - const auto tag = idm::check_unlocked(intrtag); - - if (!tag) - { - error = CELL_ESRCH; - return result; - } - - // Get interrupt thread - const auto it = idm::get_unlocked>(intrthread); - - if (!it) - { - error = CELL_ESRCH; - return result; - } - - // If interrupt thread is running, it's already established on another interrupt tag - if (cpu_flag::stop - it->state) - { - error = CELL_EAGAIN; - return result; - } - - // It's unclear if multiple handlers can be established on single interrupt tag - if (lv2_obj::check(tag->handler)) - { - error = CELL_ESTAT; - return result; - } - - result = make_shared(it, arg1, arg2); - tag->handler = result; - - it->cmd_list({{ppu_cmd::ptr_call, 0}, - std::bit_cast(&ppu_interrupt_thread_entry)}); - - it->state -= cpu_flag::stop; - it->state.notify_one(); - - return result; - }); - - if (id) - { - ppu.check_state(); - *ih = id; - return CELL_OK; - } - - return error; -} - -error_code _sys_interrupt_thread_disestablish(ppu_thread& ppu, u32 ih, vm::ptr r13) -{ - ppu.state += cpu_flag::wait; - - sys_interrupt.warning("_sys_interrupt_thread_disestablish(ih=0x%x, r13=*0x%x)", ih, r13); - - const auto handler = idm::withdraw(ih, [](lv2_obj& obj) - { - obj.exists.release(0); - }); - - if (!handler) - { - if (const auto thread = idm::withdraw>(ih)) - { - *r13 = thread->gpr[13]; - - // It is detached from IDM now so join must be done explicitly now - *thread = thread_state::finished; - return CELL_OK; - } - - return CELL_ESRCH; - } - - lv2_obj::sleep(ppu); - - // Wait for sys_interrupt_thread_eoi() and destroy interrupt thread - handler->join(); - - // Save TLS base - *r13 = handler->thread->gpr[13]; - - return CELL_OK; -} - -void sys_interrupt_thread_eoi(ppu_thread& ppu) -{ - ppu.state += cpu_flag::wait; - - sys_interrupt.trace("sys_interrupt_thread_eoi()"); - - ppu.state += cpu_flag::ret; - - lv2_obj::sleep(ppu); - - ppu.interrupt_thread_executing = false; -} - -void ppu_interrupt_thread_entry(ppu_thread& ppu, ppu_opcode_t, be_t*, struct ppu_intrp_func*) -{ - while (true) - { - shared_ptr serv = null_ptr; - - // Loop endlessly trying to invoke an interrupt if required - idm::select>([&](u32, spu_thread& spu) - { - if (spu.get_type() != spu_type::threaded) - { - auto& ctrl = spu.int_ctrl[2]; - - if (lv2_obj::check(ctrl.tag)) - { - auto& handler = ctrl.tag->handler; - - if (lv2_obj::check(handler)) - { - if (handler->thread.get() == &ppu) - { - if (spu.ch_out_intr_mbox.get_count() && ctrl.mask & SPU_INT2_STAT_MAILBOX_INT) - { - ctrl.stat |= SPU_INT2_STAT_MAILBOX_INT; - } - - if (ctrl.mask & ctrl.stat) - { - ensure(!serv); - serv = handler; - } - } - } - } - } - }); - - if (serv) - { - // Queue interrupt, after the interrupt has finished the PPU returns to this loop - serv->exec(); - return; - } - - const auto state = +ppu.state; - - if (::is_stopped(state) || ppu.cmd_notify.exchange(0)) - { - return; - } - - thread_ctrl::wait_on(ppu.cmd_notify, 0); - } -} diff --git a/rpcs3/Emu/Cell/lv2/sys_interrupt.h b/rpcs3/Emu/Cell/lv2/sys_interrupt.h deleted file mode 100644 index f32517e9a..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_interrupt.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#include "sys_sync.h" - -#include "Emu/Memory/vm_ptr.h" - -class ppu_thread; - -struct lv2_int_tag final : public lv2_obj -{ - static const u32 id_base = 0x0a000000; - - const u32 id; - shared_ptr handler; - - lv2_int_tag() noexcept; - lv2_int_tag(utils::serial& ar) noexcept; - void save(utils::serial& ar); -}; - -struct lv2_int_serv final : public lv2_obj -{ - static const u32 id_base = 0x0b000000; - - const u32 id; - const shared_ptr> thread; - const u64 arg1; - const u64 arg2; - - lv2_int_serv(shared_ptr> thread, u64 arg1, u64 arg2) noexcept; - lv2_int_serv(utils::serial& ar) noexcept; - void save(utils::serial& ar); - - void exec() const; - void join() const; -}; - -// Syscalls - -error_code sys_interrupt_tag_destroy(ppu_thread& ppu, u32 intrtag); -error_code _sys_interrupt_thread_establish(ppu_thread& ppu, vm::ptr ih, u32 intrtag, u32 intrthread, u64 arg1, u64 arg2); -error_code _sys_interrupt_thread_disestablish(ppu_thread& ppu, u32 ih, vm::ptr r13); -void sys_interrupt_thread_eoi(ppu_thread& ppu); diff --git a/rpcs3/Emu/Cell/lv2/sys_io.cpp b/rpcs3/Emu/Cell/lv2/sys_io.cpp deleted file mode 100644 index b1cec87a4..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_io.cpp +++ /dev/null @@ -1,75 +0,0 @@ -#include "stdafx.h" -#include "Emu/Memory/vm.h" -#include "Emu/IdManager.h" -#include "Emu/Cell/ErrorCodes.h" - -#include "sys_io.h" - -LOG_CHANNEL(sys_io); - -error_code sys_io_buffer_create(u32 block_count, u32 block_size, u32 blocks, u32 unk1, vm::ptr handle) -{ - sys_io.todo("sys_io_buffer_create(block_count=0x%x, block_size=0x%x, blocks=0x%x, unk1=0x%x, handle=*0x%x)", block_count, block_size, blocks, unk1, handle); - - if (!handle) - { - return CELL_EFAULT; - } - - if (auto io = idm::make(block_count, block_size, blocks, unk1)) - { - *handle = io; - return CELL_OK; - } - - return CELL_ESRCH; -} - -error_code sys_io_buffer_destroy(u32 handle) -{ - sys_io.todo("sys_io_buffer_destroy(handle=0x%x)", handle); - - idm::remove(handle); - - return CELL_OK; -} - -error_code sys_io_buffer_allocate(u32 handle, vm::ptr block) -{ - sys_io.todo("sys_io_buffer_allocate(handle=0x%x, block=*0x%x)", handle, block); - - if (!block) - { - return CELL_EFAULT; - } - - if (auto io = idm::get_unlocked(handle)) - { - // no idea what we actually need to allocate - if (u32 addr = vm::alloc(io->block_count * io->block_size, vm::main)) - { - *block = addr; - return CELL_OK; - } - - return CELL_ENOMEM; - } - - return CELL_ESRCH; -} - -error_code sys_io_buffer_free(u32 handle, u32 block) -{ - sys_io.todo("sys_io_buffer_free(handle=0x%x, block=0x%x)", handle, block); - - const auto io = idm::get_unlocked(handle); - - if (!io) - { - return CELL_ESRCH; - } - - vm::dealloc(block); - - return CELL_OK; -} diff --git a/rpcs3/Emu/Cell/lv2/sys_io.h b/rpcs3/Emu/Cell/lv2/sys_io.h deleted file mode 100644 index c60b18d90..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_io.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include "Emu/Memory/vm_ptr.h" - -struct lv2_io_buf -{ - static const u32 id_base = 0x44000000; - static const u32 id_step = 1; - static const u32 id_count = 2048; - SAVESTATE_INIT_POS(41); - - const u32 block_count; - const u32 block_size; - const u32 blocks; - const u32 unk1; - - lv2_io_buf(u32 block_count, u32 block_size, u32 blocks, u32 unk1) - : block_count(block_count), block_size(block_size), blocks(blocks), unk1(unk1) - { - } -}; - -// SysCalls - -error_code sys_io_buffer_create(u32 block_count, u32 block_size, u32 blocks, u32 unk1, vm::ptr handle); -error_code sys_io_buffer_destroy(u32 handle); -error_code sys_io_buffer_allocate(u32 handle, vm::ptr block); -error_code sys_io_buffer_free(u32 handle, u32 block); diff --git a/rpcs3/Emu/Cell/lv2/sys_lwcond.cpp b/rpcs3/Emu/Cell/lv2/sys_lwcond.cpp deleted file mode 100644 index e840cbeff..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_lwcond.cpp +++ /dev/null @@ -1,639 +0,0 @@ -#include "stdafx.h" -#include "sys_lwcond.h" - -#include "Emu/IdManager.h" - -#include "Emu/Cell/ErrorCodes.h" -#include "Emu/Cell/PPUThread.h" -#include "sys_lwmutex.h" - -#include "util/asm.hpp" - -LOG_CHANNEL(sys_lwcond); - -lv2_lwcond::lv2_lwcond(utils::serial& ar) - : name(ar.pop>()), lwid(ar), protocol(ar), control(ar.pop()) -{ -} - -void lv2_lwcond::save(utils::serial& ar) -{ - USING_SERIALIZATION_VERSION(lv2_sync); - ar(name, lwid, protocol, control); -} - -error_code _sys_lwcond_create(ppu_thread& ppu, vm::ptr lwcond_id, u32 lwmutex_id, vm::ptr control, u64 name) -{ - ppu.state += cpu_flag::wait; - - sys_lwcond.trace(u8"_sys_lwcond_create(lwcond_id=*0x%x, lwmutex_id=0x%x, control=*0x%x, name=0x%llx (“%s”))", lwcond_id, lwmutex_id, control, name, lv2_obj::name_64{std::bit_cast>(name)}); - - u32 protocol; - - // Extract protocol from lwmutex - if (!idm::check(lwmutex_id, [&protocol](lv2_lwmutex& mutex) - { - protocol = mutex.protocol; - })) - { - return CELL_ESRCH; - } - - if (protocol == SYS_SYNC_RETRY) - { - // Lwcond can't have SYS_SYNC_RETRY protocol - protocol = SYS_SYNC_PRIORITY; - } - - if (const u32 id = idm::make(name, lwmutex_id, protocol, control)) - { - ppu.check_state(); - *lwcond_id = id; - return CELL_OK; - } - - return CELL_EAGAIN; -} - -error_code _sys_lwcond_destroy(ppu_thread& ppu, u32 lwcond_id) -{ - ppu.state += cpu_flag::wait; - - sys_lwcond.trace("_sys_lwcond_destroy(lwcond_id=0x%x)", lwcond_id); - - shared_ptr _cond; - - while (true) - { - s32 old_val = 0; - - auto [ptr, ret] = idm::withdraw(lwcond_id, [&](lv2_lwcond& cond) -> CellError - { - // Ignore check on first iteration - if (_cond && std::addressof(cond) != _cond.get()) - { - // Other thread has destroyed the lwcond earlier - return CELL_ESRCH; - } - - std::lock_guard lock(cond.mutex); - - if (atomic_storage::load(cond.sq)) - { - return CELL_EBUSY; - } - - old_val = cond.lwmutex_waiters.or_fetch(smin); - - if (old_val != smin) - { - // De-schedule if waiters were found - lv2_obj::sleep(ppu); - - // Repeat loop: there are lwmutex waiters inside _sys_lwcond_queue_wait - return CELL_EAGAIN; - } - - return {}; - }); - - if (!ptr) - { - return CELL_ESRCH; - } - - if (ret) - { - if (ret != CELL_EAGAIN) - { - return ret; - } - } - else - { - break; - } - - _cond = std::move(ptr); - - // Wait for all lwcond waiters to quit - while (old_val + 0u > 1u << 31) - { - thread_ctrl::wait_on(_cond->lwmutex_waiters, old_val); - - if (ppu.is_stopped()) - { - ppu.state += cpu_flag::again; - return {}; - } - - old_val = _cond->lwmutex_waiters; - } - - // Wake up from sleep - ppu.check_state(); - } - - return CELL_OK; -} - -error_code _sys_lwcond_signal(ppu_thread& ppu, u32 lwcond_id, u32 lwmutex_id, u64 ppu_thread_id, u32 mode) -{ - ppu.state += cpu_flag::wait; - - sys_lwcond.trace("_sys_lwcond_signal(lwcond_id=0x%x, lwmutex_id=0x%x, ppu_thread_id=0x%llx, mode=%d)", lwcond_id, lwmutex_id, ppu_thread_id, mode); - - // Mode 1: lwmutex was initially owned by the calling thread - // Mode 2: lwmutex was not owned by the calling thread and waiter hasn't been increased - // Mode 3: lwmutex was forcefully owned by the calling thread - - if (mode < 1 || mode > 3) - { - fmt::throw_exception("Unknown mode (%d)", mode); - } - - while (true) - { - if (ppu.test_stopped()) - { - ppu.state += cpu_flag::again; - return {}; - } - - bool finished = true; - - ppu.state += cpu_flag::wait; - - const auto cond = idm::check(lwcond_id, [&, notify = lv2_obj::notify_all_t()](lv2_lwcond& cond) -> int - { - ppu_thread* cpu = nullptr; - - if (ppu_thread_id != u32{umax}) - { - cpu = idm::check_unlocked>(static_cast(ppu_thread_id)); - - if (!cpu) - { - return -1; - } - } - - lv2_lwmutex* mutex = nullptr; - - if (mode != 2) - { - mutex = idm::check_unlocked(lwmutex_id); - - if (!mutex) - { - return -1; - } - } - - if (atomic_storage::load(cond.sq)) - { - std::lock_guard lock(cond.mutex); - - if (ppu.state & cpu_flag::suspend) - { - // Test if another signal caused the current thread to be suspended, in which case it needs to wait until the thread wakes up (otherwise the signal may cause unexpected results) - finished = false; - return 0; - } - - if (cpu) - { - if (static_cast(cpu)->state & cpu_flag::again) - { - ppu.state += cpu_flag::again; - return 0; - } - } - - auto result = cpu ? cond.unqueue(cond.sq, cpu) : - cond.schedule(cond.sq, cond.protocol); - - if (result) - { - if (static_cast(result)->state & cpu_flag::again) - { - ppu.state += cpu_flag::again; - return 0; - } - - if (mode == 2) - { - static_cast(result)->gpr[3] = CELL_EBUSY; - } - else if (mode == 3 && mutex->load_sq()) [[unlikely]] - { - std::lock_guard lock(mutex->mutex); - - // Respect ordering of the sleep queue - mutex->try_own(result, true); - auto result2 = mutex->reown(); - - if (result2->state & cpu_flag::again) - { - ppu.state += cpu_flag::again; - return 0; - } - - if (result2 != result) - { - cond.awake(result2); - result = nullptr; - } - } - else if (mode == 1) - { - mutex->try_own(result, true); - result = nullptr; - } - - if (result) - { - cond.awake(result); - } - - return 1; - } - } - else - { - cond.mutex.lock_unlock(); - - if (ppu.state & cpu_flag::suspend) - { - finished = false; - return 0; - } - } - - return 0; - }); - - if (!finished) - { - continue; - } - - if (!cond || cond.ret == -1) - { - return CELL_ESRCH; - } - - if (!cond.ret) - { - if (ppu_thread_id == u32{umax}) - { - if (mode == 3) - { - return not_an_error(CELL_ENOENT); - } - else if (mode == 2) - { - return CELL_OK; - } - } - - return not_an_error(CELL_EPERM); - } - - return CELL_OK; - } -} - -error_code _sys_lwcond_signal_all(ppu_thread& ppu, u32 lwcond_id, u32 lwmutex_id, u32 mode) -{ - ppu.state += cpu_flag::wait; - - sys_lwcond.trace("_sys_lwcond_signal_all(lwcond_id=0x%x, lwmutex_id=0x%x, mode=%d)", lwcond_id, lwmutex_id, mode); - - // Mode 1: lwmutex was initially owned by the calling thread - // Mode 2: lwmutex was not owned by the calling thread and waiter hasn't been increased - - if (mode < 1 || mode > 2) - { - fmt::throw_exception("Unknown mode (%d)", mode); - } - - while (true) - { - if (ppu.test_stopped()) - { - ppu.state += cpu_flag::again; - return {}; - } - - bool finished = true; - - ppu.state += cpu_flag::wait; - - const auto cond = idm::check(lwcond_id, [&, notify = lv2_obj::notify_all_t()](lv2_lwcond& cond) -> int - { - lv2_lwmutex* mutex{}; - - if (mode != 2) - { - mutex = idm::check_unlocked(lwmutex_id); - - if (!mutex) - { - return -1; - } - } - - if (atomic_storage::load(cond.sq)) - { - std::lock_guard lock(cond.mutex); - - if (ppu.state & cpu_flag::suspend) - { - // Test if another signal caused the current thread to be suspended, in which case it needs to wait until the thread wakes up (otherwise the signal may cause unexpected results) - finished = false; - return 0; - } - - u32 result = 0; - - for (auto cpu = +cond.sq; cpu; cpu = cpu->next_cpu) - { - if (cpu->state & cpu_flag::again) - { - ppu.state += cpu_flag::again; - return 0; - } - } - - auto sq = cond.sq; - atomic_storage::release(cond.sq, nullptr); - - while (const auto cpu = cond.schedule(sq, cond.protocol)) - { - if (mode == 2) - { - static_cast(cpu)->gpr[3] = CELL_EBUSY; - } - - if (mode == 1) - { - mutex->try_own(cpu, true); - } - else - { - lv2_obj::append(cpu); - } - - result++; - } - - if (result && mode == 2) - { - lv2_obj::awake_all(); - } - - return result; - } - else - { - cond.mutex.lock_unlock(); - - if (ppu.state & cpu_flag::suspend) - { - finished = false; - return 0; - } - } - - return 0; - }); - - if (!finished) - { - continue; - } - - if (!cond || cond.ret == -1) - { - return CELL_ESRCH; - } - - if (mode == 1) - { - // Mode 1: return the amount of threads (TODO) - return not_an_error(cond.ret); - } - - return CELL_OK; - } -} - -error_code _sys_lwcond_queue_wait(ppu_thread& ppu, u32 lwcond_id, u32 lwmutex_id, u64 timeout) -{ - ppu.state += cpu_flag::wait; - - sys_lwcond.trace("_sys_lwcond_queue_wait(lwcond_id=0x%x, lwmutex_id=0x%x, timeout=0x%llx)", lwcond_id, lwmutex_id, timeout); - - ppu.gpr[3] = CELL_OK; - - shared_ptr mutex; - - auto& sstate = *ppu.optional_savestate_state; - - const auto cond = idm::get(lwcond_id, [&, notify = lv2_obj::notify_all_t()](lv2_lwcond& cond) - { - mutex = idm::get_unlocked(lwmutex_id); - - if (!mutex) - { - return; - } - - // Increment lwmutex's lwcond's waiters count - mutex->lwcond_waiters++; - - lv2_obj::prepare_for_sleep(ppu); - - std::lock_guard lock(cond.mutex); - - cond.lwmutex_waiters++; - - const bool mutex_sleep = sstate.try_read().second; - sstate.clear(); - - if (mutex_sleep) - { - // Special: loading state from the point of waiting on lwmutex sleep queue - mutex->try_own(&ppu, true); - } - else - { - // Add a waiter - lv2_obj::emplace(cond.sq, &ppu); - } - - if (!ppu.loaded_from_savestate && !mutex->try_unlock(false)) - { - std::lock_guard lock2(mutex->mutex); - - // Process lwmutex sleep queue - if (const auto cpu = mutex->reown()) - { - if (static_cast(cpu)->state & cpu_flag::again) - { - ensure(cond.unqueue(cond.sq, &ppu)); - ppu.state += cpu_flag::again; - return; - } - - // Put the current thread to sleep and schedule lwmutex waiter atomically - cond.append(cpu); - cond.sleep(ppu, timeout); - return; - } - } - - cond.sleep(ppu, timeout); - }); - - if (!cond || !mutex) - { - return CELL_ESRCH; - } - - if (ppu.state & cpu_flag::again) - { - return CELL_OK; - } - - while (auto state = +ppu.state) - { - if (state & cpu_flag::signal && ppu.state.test_and_reset(cpu_flag::signal)) - { - break; - } - - if (is_stopped(state)) - { - std::scoped_lock lock(cond->mutex, mutex->mutex); - - bool mutex_sleep = false; - bool cond_sleep = false; - - for (auto cpu = mutex->load_sq(); cpu; cpu = cpu->next_cpu) - { - if (cpu == &ppu) - { - mutex_sleep = true; - break; - } - } - - for (auto cpu = atomic_storage::load(cond->sq); cpu; cpu = cpu->next_cpu) - { - if (cpu == &ppu) - { - cond_sleep = true; - break; - } - } - - if (!cond_sleep && !mutex_sleep) - { - break; - } - - sstate(mutex_sleep); - ppu.state += cpu_flag::again; - break; - } - - for (usz i = 0; cpu_flag::signal - ppu.state && i < 50; i++) - { - busy_wait(500); - } - - if (ppu.state & cpu_flag::signal) - { - continue; - } - - if (timeout) - { - if (lv2_obj::wait_timeout(timeout, &ppu)) - { - // Wait for rescheduling - if (ppu.check_state()) - { - continue; - } - - ppu.state += cpu_flag::wait; - - std::lock_guard lock(cond->mutex); - - if (cond->unqueue(cond->sq, &ppu)) - { - ppu.gpr[3] = CELL_ETIMEDOUT; - break; - } - - std::lock_guard lock2(mutex->mutex); - - bool success = false; - - mutex->lv2_control.fetch_op([&](lv2_lwmutex::control_data_t& data) - { - success = false; - - ppu_thread* sq = static_cast(data.sq); - - const bool retval = &ppu == sq; - - if (!mutex->unqueue(sq, &ppu)) - { - return false; - } - - success = true; - - if (!retval) - { - return false; - } - - data.sq = sq; - return true; - }); - - if (success) - { - ppu.next_cpu = nullptr; - ppu.gpr[3] = CELL_ETIMEDOUT; - } - - break; - } - } - else - { - ppu.state.wait(state); - } - } - - if (--mutex->lwcond_waiters == smin) - { - // Notify the thread destroying lwmutex on last waiter - mutex->lwcond_waiters.notify_all(); - } - - if (--cond->lwmutex_waiters == smin) - { - // Notify the thread destroying lwcond on last waiter - cond->lwmutex_waiters.notify_all(); - } - - // Return cause - return not_an_error(ppu.gpr[3]); -} diff --git a/rpcs3/Emu/Cell/lv2/sys_lwcond.h b/rpcs3/Emu/Cell/lv2/sys_lwcond.h deleted file mode 100644 index 086964c90..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_lwcond.h +++ /dev/null @@ -1,56 +0,0 @@ -#pragma once - -#include "sys_sync.h" - -#include "Emu/Memory/vm_ptr.h" - -struct sys_lwmutex_t; - -struct sys_lwcond_attribute_t -{ - union - { - nse_t name_u64; - char name[sizeof(u64)]; - }; -}; - -struct sys_lwcond_t -{ - vm::bptr lwmutex; - be_t lwcond_queue; // lwcond pseudo-id -}; - -struct lv2_lwcond final : lv2_obj -{ - static const u32 id_base = 0x97000000; - - const be_t name; - const u32 lwid; - const lv2_protocol protocol; - vm::ptr control; - - shared_mutex mutex; - ppu_thread* sq{}; - - atomic_t lwmutex_waiters = 0; - - lv2_lwcond(u64 name, u32 lwid, u32 protocol, vm::ptr control) noexcept - : name(std::bit_cast>(name)), lwid(lwid), protocol{static_cast(protocol)}, control(control) - { - } - - lv2_lwcond(utils::serial& ar); - void save(utils::serial& ar); -}; - -// Aux -class ppu_thread; - -// Syscalls - -error_code _sys_lwcond_create(ppu_thread& ppu, vm::ptr lwcond_id, u32 lwmutex_id, vm::ptr control, u64 name); -error_code _sys_lwcond_destroy(ppu_thread& ppu, u32 lwcond_id); -error_code _sys_lwcond_signal(ppu_thread& ppu, u32 lwcond_id, u32 lwmutex_id, u64 ppu_thread_id, u32 mode); -error_code _sys_lwcond_signal_all(ppu_thread& ppu, u32 lwcond_id, u32 lwmutex_id, u32 mode); -error_code _sys_lwcond_queue_wait(ppu_thread& ppu, u32 lwcond_id, u32 lwmutex_id, u64 timeout); diff --git a/rpcs3/Emu/Cell/lv2/sys_lwmutex.cpp b/rpcs3/Emu/Cell/lv2/sys_lwmutex.cpp deleted file mode 100644 index 441a36bf5..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_lwmutex.cpp +++ /dev/null @@ -1,397 +0,0 @@ -#include "stdafx.h" -#include "sys_lwmutex.h" - -#include "Emu/IdManager.h" - -#include "Emu/Cell/ErrorCodes.h" -#include "Emu/Cell/PPUThread.h" - -#include "util/asm.hpp" - -LOG_CHANNEL(sys_lwmutex); - -lv2_lwmutex::lv2_lwmutex(utils::serial& ar) - : protocol(ar), control(ar.pop()), name(ar.pop>()) -{ - ar(lv2_control.raw().signaled); -} - -void lv2_lwmutex::save(utils::serial& ar) -{ - ar(protocol, control, name, lv2_control.raw().signaled); -} - -error_code _sys_lwmutex_create(ppu_thread& ppu, vm::ptr lwmutex_id, u32 protocol, vm::ptr control, s32 has_name, u64 name) -{ - ppu.state += cpu_flag::wait; - - sys_lwmutex.trace(u8"_sys_lwmutex_create(lwmutex_id=*0x%x, protocol=0x%x, control=*0x%x, has_name=0x%x, name=0x%llx (“%s”))", lwmutex_id, protocol, control, has_name, name, lv2_obj::name_64{std::bit_cast>(name)}); - - if (protocol != SYS_SYNC_FIFO && protocol != SYS_SYNC_RETRY && protocol != SYS_SYNC_PRIORITY) - { - sys_lwmutex.error("_sys_lwmutex_create(): unknown protocol (0x%x)", protocol); - return CELL_EINVAL; - } - - if (!(has_name < 0)) - { - name = 0; - } - - if (const u32 id = idm::make(protocol, control, name)) - { - ppu.check_state(); - *lwmutex_id = id; - return CELL_OK; - } - - return CELL_EAGAIN; -} - -error_code _sys_lwmutex_destroy(ppu_thread& ppu, u32 lwmutex_id) -{ - ppu.state += cpu_flag::wait; - - sys_lwmutex.trace("_sys_lwmutex_destroy(lwmutex_id=0x%x)", lwmutex_id); - - shared_ptr _mutex; - - while (true) - { - s32 old_val = 0; - - auto [ptr, ret] = idm::withdraw(lwmutex_id, [&](lv2_lwmutex& mutex) -> CellError - { - // Ignore check on first iteration - if (_mutex && std::addressof(mutex) != _mutex.get()) - { - // Other thread has destroyed the lwmutex earlier - return CELL_ESRCH; - } - - std::lock_guard lock(mutex.mutex); - - if (mutex.load_sq()) - { - return CELL_EBUSY; - } - - old_val = mutex.lwcond_waiters.or_fetch(smin); - - if (old_val != smin) - { - // Deschedule if waiters were found - lv2_obj::sleep(ppu); - - // Repeat loop: there are lwcond waiters - return CELL_EAGAIN; - } - - return {}; - }); - - if (!ptr) - { - return CELL_ESRCH; - } - - if (ret) - { - if (ret != CELL_EAGAIN) - { - return ret; - } - } - else - { - break; - } - - _mutex = std::move(ptr); - - // Wait for all lwcond waiters to quit - while (old_val + 0u > 1u << 31) - { - thread_ctrl::wait_on(_mutex->lwcond_waiters, old_val); - - if (ppu.is_stopped()) - { - ppu.state += cpu_flag::again; - return {}; - } - - old_val = _mutex->lwcond_waiters; - } - - // Wake up from sleep - ppu.check_state(); - } - - return CELL_OK; -} - -error_code _sys_lwmutex_lock(ppu_thread& ppu, u32 lwmutex_id, u64 timeout) -{ - ppu.state += cpu_flag::wait; - - sys_lwmutex.trace("_sys_lwmutex_lock(lwmutex_id=0x%x, timeout=0x%llx)", lwmutex_id, timeout); - - ppu.gpr[3] = CELL_OK; - - const auto mutex = idm::get(lwmutex_id, [&, notify = lv2_obj::notify_all_t()](lv2_lwmutex& mutex) - { - if (s32 signal = mutex.lv2_control.fetch_op([](lv2_lwmutex::control_data_t& data) - { - if (data.signaled) - { - data.signaled = 0; - return true; - } - - return false; - }) - .first.signaled) - { - if (~signal & 1) - { - ppu.gpr[3] = CELL_EBUSY; - } - - return true; - } - - lv2_obj::prepare_for_sleep(ppu); - - ppu.cancel_sleep = 1; - - if (s32 signal = mutex.try_own(&ppu)) - { - if (~signal & 1) - { - ppu.gpr[3] = CELL_EBUSY; - } - - ppu.cancel_sleep = 0; - return true; - } - - const bool finished = !mutex.sleep(ppu, timeout); - notify.cleanup(); - return finished; - }); - - if (!mutex) - { - return CELL_ESRCH; - } - - if (mutex.ret) - { - return not_an_error(ppu.gpr[3]); - } - - while (auto state = +ppu.state) - { - if (state & cpu_flag::signal && ppu.state.test_and_reset(cpu_flag::signal)) - { - break; - } - - if (is_stopped(state)) - { - std::lock_guard lock(mutex->mutex); - - for (auto cpu = mutex->load_sq(); cpu; cpu = cpu->next_cpu) - { - if (cpu == &ppu) - { - ppu.state += cpu_flag::again; - return {}; - } - } - - break; - } - - for (usz i = 0; cpu_flag::signal - ppu.state && i < 50; i++) - { - busy_wait(500); - } - - if (ppu.state & cpu_flag::signal) - { - continue; - } - - if (timeout) - { - if (lv2_obj::wait_timeout(timeout, &ppu)) - { - // Wait for rescheduling - if (ppu.check_state()) - { - continue; - } - - ppu.state += cpu_flag::wait; - - if (!mutex->load_sq()) - { - // Sleep queue is empty, so the thread must have been signaled - mutex->mutex.lock_unlock(); - break; - } - - std::lock_guard lock(mutex->mutex); - - bool success = false; - - mutex->lv2_control.fetch_op([&](lv2_lwmutex::control_data_t& data) - { - success = false; - - ppu_thread* sq = static_cast(data.sq); - - const bool retval = &ppu == sq; - - if (!mutex->unqueue(sq, &ppu)) - { - return false; - } - - success = true; - - if (!retval) - { - return false; - } - - data.sq = sq; - return true; - }); - - if (success) - { - ppu.next_cpu = nullptr; - ppu.gpr[3] = CELL_ETIMEDOUT; - } - - break; - } - } - else - { - ppu.state.wait(state); - } - } - - return not_an_error(ppu.gpr[3]); -} - -error_code _sys_lwmutex_trylock(ppu_thread& ppu, u32 lwmutex_id) -{ - ppu.state += cpu_flag::wait; - - sys_lwmutex.trace("_sys_lwmutex_trylock(lwmutex_id=0x%x)", lwmutex_id); - - const auto mutex = idm::check(lwmutex_id, [&](lv2_lwmutex& mutex) - { - auto [_, ok] = mutex.lv2_control.fetch_op([](lv2_lwmutex::control_data_t& data) - { - if (data.signaled & 1) - { - data.signaled = 0; - return true; - } - - return false; - }); - - return ok; - }); - - if (!mutex) - { - return CELL_ESRCH; - } - - if (!mutex.ret) - { - return not_an_error(CELL_EBUSY); - } - - return CELL_OK; -} - -error_code _sys_lwmutex_unlock(ppu_thread& ppu, u32 lwmutex_id) -{ - ppu.state += cpu_flag::wait; - - sys_lwmutex.trace("_sys_lwmutex_unlock(lwmutex_id=0x%x)", lwmutex_id); - - const auto mutex = idm::check(lwmutex_id, [&, notify = lv2_obj::notify_all_t()](lv2_lwmutex& mutex) - { - if (mutex.try_unlock(false)) - { - return; - } - - std::lock_guard lock(mutex.mutex); - - if (const auto cpu = mutex.reown()) - { - if (static_cast(cpu)->state & cpu_flag::again) - { - ppu.state += cpu_flag::again; - return; - } - - mutex.awake(cpu); - notify.cleanup(); // lv2_lwmutex::mutex is not really active 99% of the time, can be ignored - } - }); - - if (!mutex) - { - return CELL_ESRCH; - } - - return CELL_OK; -} - -error_code _sys_lwmutex_unlock2(ppu_thread& ppu, u32 lwmutex_id) -{ - ppu.state += cpu_flag::wait; - - sys_lwmutex.warning("_sys_lwmutex_unlock2(lwmutex_id=0x%x)", lwmutex_id); - - const auto mutex = idm::check(lwmutex_id, [&, notify = lv2_obj::notify_all_t()](lv2_lwmutex& mutex) - { - if (mutex.try_unlock(true)) - { - return; - } - - std::lock_guard lock(mutex.mutex); - - if (const auto cpu = mutex.reown(true)) - { - if (static_cast(cpu)->state & cpu_flag::again) - { - ppu.state += cpu_flag::again; - return; - } - - static_cast(cpu)->gpr[3] = CELL_EBUSY; - mutex.awake(cpu); - notify.cleanup(); // lv2_lwmutex::mutex is not really active 99% of the time, can be ignored - } - }); - - if (!mutex) - { - return CELL_ESRCH; - } - - return CELL_OK; -} diff --git a/rpcs3/Emu/Cell/lv2/sys_lwmutex.h b/rpcs3/Emu/Cell/lv2/sys_lwmutex.h deleted file mode 100644 index 2a5e1c901..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_lwmutex.h +++ /dev/null @@ -1,205 +0,0 @@ -#pragma once - -#include "sys_sync.h" - -#include "Emu/Memory/vm_ptr.h" - -struct sys_lwmutex_attribute_t -{ - be_t protocol; - be_t recursive; - - union - { - nse_t name_u64; - char name[sizeof(u64)]; - }; -}; - -enum : u32 -{ - lwmutex_free = 0xffffffffu, - lwmutex_dead = 0xfffffffeu, - lwmutex_reserved = 0xfffffffdu, -}; - -struct sys_lwmutex_t -{ - struct alignas(8) sync_var_t - { - be_t owner; - be_t waiter; - }; - - union - { - atomic_t lock_var; - - struct - { - atomic_be_t owner; - atomic_be_t waiter; - } vars; - - atomic_be_t all_info; - }; - - be_t attribute; - be_t recursive_count; - be_t sleep_queue; // lwmutex pseudo-id - be_t pad; -}; - -struct lv2_lwmutex final : lv2_obj -{ - static const u32 id_base = 0x95000000; - - const lv2_protocol protocol; - const vm::ptr control; - const be_t name; - - shared_mutex mutex; - atomic_t lwcond_waiters{0}; - - struct alignas(16) control_data_t - { - s32 signaled{0}; - u32 reserved{}; - ppu_thread* sq{}; - }; - - atomic_t lv2_control{}; - - lv2_lwmutex(u32 protocol, vm::ptr control, u64 name) noexcept - : protocol{static_cast(protocol)}, control(control), name(std::bit_cast>(name)) - { - } - - lv2_lwmutex(utils::serial& ar); - void save(utils::serial& ar); - - ppu_thread* load_sq() const - { - return atomic_storage::load(lv2_control.raw().sq); - } - - template - s32 try_own(T* cpu, bool wait_only = false) - { - const s32 signal = lv2_control.fetch_op([&](control_data_t& data) - { - if (!data.signaled) - { - cpu->prio.atomic_op([tag = ++g_priority_order_tag](std::common_type_t& prio) - { - prio.order = tag; - }); - - cpu->next_cpu = data.sq; - data.sq = cpu; - } - else - { - ensure(!wait_only); - data.signaled = 0; - } - }) - .signaled; - - if (signal) - { - cpu->next_cpu = nullptr; - } - else - { - const bool notify = lwcond_waiters.fetch_op([](s32& val) - { - if (val + 0u <= 1u << 31) - { - // Value was either positive or INT32_MIN - return false; - } - - // lwmutex was set to be destroyed, but there are lwcond waiters - // Turn off the "lwcond_waiters notification" bit as we are adding an lwmutex waiter - val &= 0x7fff'ffff; - return true; - }) - .second; - - if (notify) - { - // Notify lwmutex destroyer (may cause EBUSY to be returned for it) - lwcond_waiters.notify_all(); - } - } - - return signal; - } - - bool try_unlock(bool unlock2) - { - if (!load_sq()) - { - control_data_t old{}; - old.signaled = atomic_storage::load(lv2_control.raw().signaled); - control_data_t store = old; - store.signaled |= (unlock2 ? s32{smin} : 1); - - if (lv2_control.compare_exchange(old, store)) - { - return true; - } - } - - return false; - } - - template - T* reown(bool unlock2 = false) - { - T* res = nullptr; - - lv2_control.fetch_op([&](control_data_t& data) - { - res = nullptr; - - if (auto sq = static_cast(data.sq)) - { - res = schedule(data.sq, protocol, false); - - if (sq == data.sq) - { - return false; - } - - return true; - } - else - { - data.signaled |= (unlock2 ? s32{smin} : 1); - return true; - } - }); - - if (res && cpu_flag::again - res->state) - { - // Detach manually (fetch_op can fail, so avoid side-effects on the first node in this case) - res->next_cpu = nullptr; - } - - return res; - } -}; - -// Aux -class ppu_thread; - -// Syscalls - -error_code _sys_lwmutex_create(ppu_thread& ppu, vm::ptr lwmutex_id, u32 protocol, vm::ptr control, s32 has_name, u64 name); -error_code _sys_lwmutex_destroy(ppu_thread& ppu, u32 lwmutex_id); -error_code _sys_lwmutex_lock(ppu_thread& ppu, u32 lwmutex_id, u64 timeout); -error_code _sys_lwmutex_trylock(ppu_thread& ppu, u32 lwmutex_id); -error_code _sys_lwmutex_unlock(ppu_thread& ppu, u32 lwmutex_id); -error_code _sys_lwmutex_unlock2(ppu_thread& ppu, u32 lwmutex_id); diff --git a/rpcs3/Emu/Cell/lv2/sys_memory.cpp b/rpcs3/Emu/Cell/lv2/sys_memory.cpp deleted file mode 100644 index f5fc74321..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_memory.cpp +++ /dev/null @@ -1,426 +0,0 @@ -#include "stdafx.h" -#include "sys_memory.h" - -#include "Emu/Memory/vm_locking.h" -#include "Emu/CPU/CPUThread.h" -#include "Emu/Cell/ErrorCodes.h" -#include "Emu/Cell/SPUThread.h" -#include "Emu/IdManager.h" - -#include "util/asm.hpp" - -LOG_CHANNEL(sys_memory); - -// -static shared_mutex s_memstats_mtx; - -lv2_memory_container::lv2_memory_container(u32 size, bool from_idm) noexcept - : size(size), id{from_idm ? idm::last_id() : SYS_MEMORY_CONTAINER_ID_INVALID} -{ -} - -lv2_memory_container::lv2_memory_container(utils::serial& ar, bool from_idm) noexcept - : size(ar), id{from_idm ? idm::last_id() : SYS_MEMORY_CONTAINER_ID_INVALID}, used(ar) -{ -} - -std::function lv2_memory_container::load(utils::serial& ar) -{ - // Use idm::last_id() only for the instances at IDM - return [ptr = make_shared(exact_t(ar), true)](void* storage) - { - *static_cast*>(storage) = ptr; - }; -} - -void lv2_memory_container::save(utils::serial& ar) -{ - ar(size, used); -} - -lv2_memory_container* lv2_memory_container::search(u32 id) -{ - if (id != SYS_MEMORY_CONTAINER_ID_INVALID) - { - return idm::check_unlocked(id); - } - - return &g_fxo->get(); -} - -struct sys_memory_address_table -{ - atomic_t addrs[65536]{}; - - sys_memory_address_table() = default; - - SAVESTATE_INIT_POS(id_manager::id_map::savestate_init_pos + 0.1); - - sys_memory_address_table(utils::serial& ar) - { - // First: address, second: conatiner ID (SYS_MEMORY_CONTAINER_ID_INVALID for global FXO memory container) - std::unordered_map mm; - ar(mm); - - for (const auto& [addr, id] : mm) - { - addrs[addr] = ensure(lv2_memory_container::search(id)); - } - } - - void save(utils::serial& ar) - { - std::unordered_map mm; - - for (auto& ctr : addrs) - { - if (const auto ptr = +ctr) - { - mm[static_cast(&ctr - addrs)] = ptr->id; - } - } - - ar(mm); - } -}; - -std::shared_ptr reserve_map(u32 alloc_size, u32 align) -{ - return vm::reserve_map(align == 0x10000 ? vm::user64k : vm::user1m, 0, align == 0x10000 ? 0x20000000 : utils::align(alloc_size, 0x10000000), align == 0x10000 ? (vm::page_size_64k | vm::bf0_0x1) : (vm::page_size_1m | vm::bf0_0x1)); -} - -// Todo: fix order of error checks - -error_code sys_memory_allocate(cpu_thread& cpu, u64 size, u64 flags, vm::ptr alloc_addr) -{ - cpu.state += cpu_flag::wait; - - sys_memory.warning("sys_memory_allocate(size=0x%x, flags=0x%llx, alloc_addr=*0x%x)", size, flags, alloc_addr); - - if (!size) - { - return {CELL_EALIGN, size}; - } - - // Check allocation size - const u32 align = - flags == SYS_MEMORY_PAGE_SIZE_1M ? 0x100000 : - flags == SYS_MEMORY_PAGE_SIZE_64K ? 0x10000 : - flags == 0 ? 0x100000 : - 0; - - if (!align) - { - return {CELL_EINVAL, flags}; - } - - if (size % align) - { - return {CELL_EALIGN, size}; - } - - // Get "default" memory container - auto& dct = g_fxo->get(); - - // Try to get "physical memory" - if (!dct.take(size)) - { - return {CELL_ENOMEM, dct.size - dct.used}; - } - - if (const auto area = reserve_map(static_cast(size), align)) - { - if (const u32 addr = area->alloc(static_cast(size), nullptr, align)) - { - ensure(!g_fxo->get().addrs[addr >> 16].exchange(&dct)); - - if (alloc_addr) - { - sys_memory.notice("sys_memory_allocate(): Allocated 0x%x address (size=0x%x)", addr, size); - - vm::lock_sudo(addr, static_cast(size)); - cpu.check_state(); - *alloc_addr = addr; - return CELL_OK; - } - - // Dealloc using the syscall - sys_memory_free(cpu, addr); - return CELL_EFAULT; - } - } - - dct.free(size); - return CELL_ENOMEM; -} - -error_code sys_memory_allocate_from_container(cpu_thread& cpu, u64 size, u32 cid, u64 flags, vm::ptr alloc_addr) -{ - cpu.state += cpu_flag::wait; - - sys_memory.warning("sys_memory_allocate_from_container(size=0x%x, cid=0x%x, flags=0x%llx, alloc_addr=*0x%x)", size, cid, flags, alloc_addr); - - if (!size) - { - return {CELL_EALIGN, size}; - } - - // Check allocation size - const u32 align = - flags == SYS_MEMORY_PAGE_SIZE_1M ? 0x100000 : - flags == SYS_MEMORY_PAGE_SIZE_64K ? 0x10000 : - flags == 0 ? 0x100000 : - 0; - - if (!align) - { - return {CELL_EINVAL, flags}; - } - - if (size % align) - { - return {CELL_EALIGN, size}; - } - - const auto ct = idm::get(cid, [&](lv2_memory_container& ct) -> CellError - { - // Try to get "physical memory" - if (!ct.take(size)) - { - return CELL_ENOMEM; - } - - return {}; - }); - - if (!ct) - { - return CELL_ESRCH; - } - - if (ct.ret) - { - return {ct.ret, ct->size - ct->used}; - } - - if (const auto area = reserve_map(static_cast(size), align)) - { - if (const u32 addr = area->alloc(static_cast(size))) - { - ensure(!g_fxo->get().addrs[addr >> 16].exchange(ct.ptr.get())); - - if (alloc_addr) - { - vm::lock_sudo(addr, static_cast(size)); - cpu.check_state(); - *alloc_addr = addr; - return CELL_OK; - } - - // Dealloc using the syscall - sys_memory_free(cpu, addr); - return CELL_EFAULT; - } - } - - ct->free(size); - return CELL_ENOMEM; -} - -error_code sys_memory_free(cpu_thread& cpu, u32 addr) -{ - cpu.state += cpu_flag::wait; - - sys_memory.warning("sys_memory_free(addr=0x%x)", addr); - - const auto ct = addr % 0x10000 ? nullptr : g_fxo->get().addrs[addr >> 16].exchange(nullptr); - - if (!ct) - { - return {CELL_EINVAL, addr}; - } - - const auto size = (ensure(vm::dealloc(addr))); - reader_lock{id_manager::g_mutex}, ct->free(size); - return CELL_OK; -} - -error_code sys_memory_get_page_attribute(cpu_thread& cpu, u32 addr, vm::ptr attr) -{ - cpu.state += cpu_flag::wait; - - sys_memory.trace("sys_memory_get_page_attribute(addr=0x%x, attr=*0x%x)", addr, attr); - - vm::writer_lock rlock; - - if (!vm::check_addr(addr) || addr >= SPU_FAKE_BASE_ADDR) - { - return CELL_EINVAL; - } - - if (!vm::check_addr(attr.addr(), vm::page_readable, attr.size())) - { - return CELL_EFAULT; - } - - attr->attribute = 0x40000ull; // SYS_MEMORY_PROT_READ_WRITE (TODO) - attr->access_right = addr >> 28 == 0xdu ? SYS_MEMORY_ACCESS_RIGHT_PPU_THR : SYS_MEMORY_ACCESS_RIGHT_ANY; // (TODO) - - if (vm::check_addr(addr, vm::page_1m_size)) - { - attr->page_size = 0x100000; - } - else if (vm::check_addr(addr, vm::page_64k_size)) - { - attr->page_size = 0x10000; - } - else - { - attr->page_size = 4096; - } - - attr->pad = 0; // Always write 0 - return CELL_OK; -} - -error_code sys_memory_get_user_memory_size(cpu_thread& cpu, vm::ptr mem_info) -{ - cpu.state += cpu_flag::wait; - - sys_memory.warning("sys_memory_get_user_memory_size(mem_info=*0x%x)", mem_info); - - // Get "default" memory container - auto& dct = g_fxo->get(); - - sys_memory_info_t out{}; - { - ::reader_lock lock(s_memstats_mtx); - - out.total_user_memory = dct.size; - out.available_user_memory = dct.size - dct.used; - - // Scan other memory containers - idm::select([&](u32, lv2_memory_container& ct) - { - out.total_user_memory -= ct.size; - }); - } - - cpu.check_state(); - *mem_info = out; - return CELL_OK; -} - -error_code sys_memory_get_user_memory_stat(cpu_thread& cpu, vm::ptr mem_stat) -{ - cpu.state += cpu_flag::wait; - - sys_memory.todo("sys_memory_get_user_memory_stat(mem_stat=*0x%x)", mem_stat); - - return CELL_OK; -} - -error_code sys_memory_container_create(cpu_thread& cpu, vm::ptr cid, u64 size) -{ - cpu.state += cpu_flag::wait; - - sys_memory.warning("sys_memory_container_create(cid=*0x%x, size=0x%x)", cid, size); - - // Round down to 1 MB granularity - size &= ~0xfffff; - - if (!size) - { - return CELL_ENOMEM; - } - - auto& dct = g_fxo->get(); - - std::lock_guard lock(s_memstats_mtx); - - // Try to obtain "physical memory" from the default container - if (!dct.take(size)) - { - return CELL_ENOMEM; - } - - // Create the memory container - if (const u32 id = idm::make(static_cast(size), true)) - { - cpu.check_state(); - *cid = id; - return CELL_OK; - } - - dct.free(size); - return CELL_EAGAIN; -} - -error_code sys_memory_container_destroy(cpu_thread& cpu, u32 cid) -{ - cpu.state += cpu_flag::wait; - - sys_memory.warning("sys_memory_container_destroy(cid=0x%x)", cid); - - std::lock_guard lock(s_memstats_mtx); - - const auto ct = idm::withdraw(cid, [](lv2_memory_container& ct) -> CellError - { - // Check if some memory is not deallocated (the container cannot be destroyed in this case) - if (!ct.used.compare_and_swap_test(0, ct.size)) - { - return CELL_EBUSY; - } - - return {}; - }); - - if (!ct) - { - return CELL_ESRCH; - } - - if (ct.ret) - { - return ct.ret; - } - - // Return "physical memory" to the default container - g_fxo->get().free(ct->size); - - return CELL_OK; -} - -error_code sys_memory_container_get_size(cpu_thread& cpu, vm::ptr mem_info, u32 cid) -{ - cpu.state += cpu_flag::wait; - - sys_memory.warning("sys_memory_container_get_size(mem_info=*0x%x, cid=0x%x)", mem_info, cid); - - const auto ct = idm::get_unlocked(cid); - - if (!ct) - { - return CELL_ESRCH; - } - - cpu.check_state(); - mem_info->total_user_memory = ct->size; // Total container memory - mem_info->available_user_memory = ct->size - ct->used; // Available container memory - - return CELL_OK; -} - -error_code sys_memory_container_destroy_parent_with_childs(cpu_thread& cpu, u32 cid, u32 must_0, vm::ptr mc_child) -{ - sys_memory.warning("sys_memory_container_destroy_parent_with_childs(cid=0x%x, must_0=%d, mc_child=*0x%x)", cid, must_0, mc_child); - - if (must_0) - { - return CELL_EINVAL; - } - - // Multi-process is not supported yet so child containers mean nothing at the moment - // Simply destroy parent - return sys_memory_container_destroy(cpu, cid); -} diff --git a/rpcs3/Emu/Cell/lv2/sys_memory.h b/rpcs3/Emu/Cell/lv2/sys_memory.h deleted file mode 100644 index 94235fc2b..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_memory.h +++ /dev/null @@ -1,139 +0,0 @@ -#pragma once - -#include "Emu/Memory/vm_ptr.h" -#include "Emu/Cell/ErrorCodes.h" - -class cpu_thread; - -enum lv2_mem_container_id : u32 -{ - SYS_MEMORY_CONTAINER_ID_INVALID = 0xFFFFFFFF, -}; - -enum : u64 -{ - SYS_MEMORY_ACCESS_RIGHT_NONE = 0x00000000000000F0ULL, - SYS_MEMORY_ACCESS_RIGHT_ANY = 0x000000000000000FULL, - SYS_MEMORY_ACCESS_RIGHT_PPU_THR = 0x0000000000000008ULL, - SYS_MEMORY_ACCESS_RIGHT_HANDLER = 0x0000000000000004ULL, - SYS_MEMORY_ACCESS_RIGHT_SPU_THR = 0x0000000000000002ULL, - SYS_MEMORY_ACCESS_RIGHT_RAW_SPU = 0x0000000000000001ULL, - - SYS_MEMORY_ATTR_READ_ONLY = 0x0000000000080000ULL, - SYS_MEMORY_ATTR_READ_WRITE = 0x0000000000040000ULL, -}; - -enum : u64 -{ - SYS_MEMORY_PAGE_SIZE_4K = 0x100ull, - SYS_MEMORY_PAGE_SIZE_64K = 0x200ull, - SYS_MEMORY_PAGE_SIZE_1M = 0x400ull, - SYS_MEMORY_PAGE_SIZE_MASK = 0xf00ull, -}; - -enum : u64 -{ - SYS_MEMORY_GRANULARITY_64K = 0x0000000000000200, - SYS_MEMORY_GRANULARITY_1M = 0x0000000000000400, - SYS_MEMORY_GRANULARITY_MASK = 0x0000000000000f00, -}; - -enum : u64 -{ - SYS_MEMORY_PROT_READ_WRITE = 0x0000000000040000, - SYS_MEMORY_PROT_READ_ONLY = 0x0000000000080000, - SYS_MEMORY_PROT_MASK = 0x00000000000f0000, -}; - -struct sys_memory_info_t -{ - be_t total_user_memory; - be_t available_user_memory; -}; - -struct sys_page_attr_t -{ - be_t attribute; - be_t access_right; - be_t page_size; - be_t pad; -}; - -struct lv2_memory_container -{ - static const u32 id_base = 0x3F000000; - static const u32 id_step = 0x1; - static const u32 id_count = 16; - - const u32 size; // Amount of "physical" memory in this container - const lv2_mem_container_id id; // ID of the container in if placed at IDM, otherwise SYS_MEMORY_CONTAINER_ID_INVALID - atomic_t used{}; // Amount of "physical" memory currently used - - SAVESTATE_INIT_POS(1); - - lv2_memory_container(u32 size, bool from_idm = false) noexcept; - lv2_memory_container(utils::serial& ar, bool from_idm = false) noexcept; - static std::function load(utils::serial& ar); - void save(utils::serial& ar); - static lv2_memory_container* search(u32 id); - - // Try to get specified amount of "physical" memory - // Values greater than UINT32_MAX will fail - u32 take(u64 amount) - { - auto [_, result] = used.fetch_op([&](u32& value) -> u32 - { - if (size - value >= amount) - { - value += static_cast(amount); - return static_cast(amount); - } - - return 0; - }); - - return result; - } - - u32 free(u64 amount) - { - auto [_, result] = used.fetch_op([&](u32& value) -> u32 - { - if (value >= amount) - { - value -= static_cast(amount); - return static_cast(amount); - } - - return 0; - }); - - // Sanity check - ensure(result == amount); - - return result; - } -}; - -struct sys_memory_user_memory_stat_t -{ - be_t a; // 0x0 - be_t b; // 0x4 - be_t c; // 0x8 - be_t d; // 0xc - be_t e; // 0x10 - be_t f; // 0x14 - be_t g; // 0x18 -}; - -// SysCalls -error_code sys_memory_allocate(cpu_thread& cpu, u64 size, u64 flags, vm::ptr alloc_addr); -error_code sys_memory_allocate_from_container(cpu_thread& cpu, u64 size, u32 cid, u64 flags, vm::ptr alloc_addr); -error_code sys_memory_free(cpu_thread& cpu, u32 start_addr); -error_code sys_memory_get_page_attribute(cpu_thread& cpu, u32 addr, vm::ptr attr); -error_code sys_memory_get_user_memory_size(cpu_thread& cpu, vm::ptr mem_info); -error_code sys_memory_get_user_memory_stat(cpu_thread& cpu, vm::ptr mem_stat); -error_code sys_memory_container_create(cpu_thread& cpu, vm::ptr cid, u64 size); -error_code sys_memory_container_destroy(cpu_thread& cpu, u32 cid); -error_code sys_memory_container_get_size(cpu_thread& cpu, vm::ptr mem_info, u32 cid); -error_code sys_memory_container_destroy_parent_with_childs(cpu_thread& cpu, u32 cid, u32 must_0, vm::ptr mc_child); diff --git a/rpcs3/Emu/Cell/lv2/sys_mmapper.cpp b/rpcs3/Emu/Cell/lv2/sys_mmapper.cpp deleted file mode 100644 index c71fac708..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_mmapper.cpp +++ /dev/null @@ -1,859 +0,0 @@ -#include "stdafx.h" -#include "sys_mmapper.h" - -#include "Emu/Cell/PPUThread.h" -#include "Emu/Cell/lv2/sys_event.h" -#include "Emu/Memory/vm_var.h" -#include "sys_memory.h" -#include "sys_sync.h" -#include "sys_process.h" - -#include - -#include "util/vm.hpp" - -LOG_CHANNEL(sys_mmapper); - -template <> -void fmt_class_string::format(std::string& out, u64 arg) -{ - format_enum(out, arg, [](auto value) - { - switch (value) - { - case SYS_MEMORY_CONTAINER_ID_INVALID: return "Global"; - } - - // Resort to hex formatting for other values - return unknown; - }); -} - -lv2_memory::lv2_memory(u32 size, u32 align, u64 flags, u64 key, bool pshared, lv2_memory_container* ct) - : size(size), align(align), flags(flags), key(key), pshared(pshared), ct(ct), shm(std::make_shared(size, 1 /* shareable flag */)) -{ -#ifndef _WIN32 - // Optimization that's useless on Windows :puke: - utils::memory_lock(shm->map_self(), size); -#endif -} - -lv2_memory::lv2_memory(utils::serial& ar) - : size(ar), align(ar), flags(ar), key(ar), pshared(ar), ct(lv2_memory_container::search(ar.pop())), shm([&](u32 addr) - { - if (addr) - { - return ensure(vm::get(vm::any, addr)->peek(addr).second); - } - - const auto _shm = std::make_shared(size, 1); - ar(std::span(_shm->map_self(), size)); - return _shm; - }(ar.pop())), - counter(ar) -{ -#ifndef _WIN32 - // Optimization that's useless on Windows :puke: - utils::memory_lock(shm->map_self(), size); -#endif -} - -CellError lv2_memory::on_id_create() -{ - if (!exists && !ct->take(size)) - { - sys_mmapper.error("lv2_memory::on_id_create(): Cannot allocate 0x%x bytes (0x%x available)", size, ct->size - ct->used); - return CELL_ENOMEM; - } - - exists++; - return {}; -} - -std::function lv2_memory::load(utils::serial& ar) -{ - auto mem = make_shared(exact_t(ar)); - mem->exists++; // Disable on_id_create() - auto func = load_func(mem, +mem->pshared); - mem->exists--; - return func; -} - -void lv2_memory::save(utils::serial& ar) -{ - USING_SERIALIZATION_VERSION(lv2_memory); - - ar(size, align, flags, key, pshared, ct->id); - ar(counter ? vm::get_shm_addr(shm) : 0); - - if (!counter) - { - ar(std::span(shm->map_self(), size)); - } - - ar(counter); -} - -page_fault_notification_entries::page_fault_notification_entries(utils::serial& ar) -{ - ar(entries); -} - -void page_fault_notification_entries::save(utils::serial& ar) -{ - ar(entries); -} - -template -error_code create_lv2_shm(bool pshared, u64 ipc_key, u64 size, u32 align, u64 flags, lv2_memory_container* ct) -{ - const u32 _pshared = pshared ? SYS_SYNC_PROCESS_SHARED : SYS_SYNC_NOT_PROCESS_SHARED; - - if (!pshared) - { - ipc_key = 0; - } - - if (auto error = lv2_obj::create(_pshared, ipc_key, exclusive ? SYS_SYNC_NEWLY_CREATED : SYS_SYNC_NOT_CARE, [&]() - { - return make_shared( - static_cast(size), - align, - flags, - ipc_key, - pshared, - ct); - }, - false)) - { - return error; - } - - return CELL_OK; -} - -error_code sys_mmapper_allocate_address(ppu_thread& ppu, u64 size, u64 flags, u64 alignment, vm::ptr alloc_addr) -{ - ppu.state += cpu_flag::wait; - - sys_mmapper.warning("sys_mmapper_allocate_address(size=0x%x, flags=0x%x, alignment=0x%x, alloc_addr=*0x%x)", size, flags, alignment, alloc_addr); - - if (size % 0x10000000) - { - return CELL_EALIGN; - } - - if (size > u32{umax}) - { - return CELL_ENOMEM; - } - - // This is a workaround for psl1ght, which gives us an alignment of 0, which is technically invalid, but apparently is allowed on actual ps3 - // https://github.com/ps3dev/PSL1GHT/blob/534e58950732c54dc6a553910b653c99ba6e9edc/ppu/librt/sbrk.c#L71 - if (!alignment) - { - alignment = 0x10000000; - } - - switch (alignment) - { - case 0x10000000: - case 0x20000000: - case 0x40000000: - case 0x80000000: - { - if (const auto area = vm::find_map(static_cast(size), static_cast(alignment), flags & SYS_MEMORY_PAGE_SIZE_MASK)) - { - sys_mmapper.warning("sys_mmapper_allocate_address(): Found VM 0x%x area (vsize=0x%x)", area->addr, size); - - ppu.check_state(); - *alloc_addr = area->addr; - return CELL_OK; - } - - return CELL_ENOMEM; - } - } - - return CELL_EALIGN; -} - -error_code sys_mmapper_allocate_fixed_address(ppu_thread& ppu) -{ - ppu.state += cpu_flag::wait; - - sys_mmapper.warning("sys_mmapper_allocate_fixed_address()"); - - if (!vm::map(0xB0000000, 0x10000000, SYS_MEMORY_PAGE_SIZE_1M)) - { - return CELL_EEXIST; - } - - return CELL_OK; -} - -error_code sys_mmapper_allocate_shared_memory(ppu_thread& ppu, u64 ipc_key, u64 size, u64 flags, vm::ptr mem_id) -{ - ppu.state += cpu_flag::wait; - - sys_mmapper.warning("sys_mmapper_allocate_shared_memory(ipc_key=0x%x, size=0x%x, flags=0x%x, mem_id=*0x%x)", ipc_key, size, flags, mem_id); - - if (size == 0) - { - return CELL_EALIGN; - } - - // Check page granularity - switch (flags & SYS_MEMORY_GRANULARITY_MASK) - { - case 0: - case SYS_MEMORY_GRANULARITY_1M: - { - if (size % 0x100000) - { - return CELL_EALIGN; - } - - break; - } - case SYS_MEMORY_GRANULARITY_64K: - { - if (size % 0x10000) - { - return CELL_EALIGN; - } - - break; - } - default: - { - return CELL_EINVAL; - } - } - - // Get "default" memory container - auto& dct = g_fxo->get(); - - if (auto error = create_lv2_shm(ipc_key != SYS_MMAPPER_NO_SHM_KEY, ipc_key, size, flags & SYS_MEMORY_PAGE_SIZE_64K ? 0x10000 : 0x100000, flags, &dct)) - { - return error; - } - - ppu.check_state(); - *mem_id = idm::last_id(); - return CELL_OK; -} - -error_code sys_mmapper_allocate_shared_memory_from_container(ppu_thread& ppu, u64 ipc_key, u64 size, u32 cid, u64 flags, vm::ptr mem_id) -{ - ppu.state += cpu_flag::wait; - - sys_mmapper.warning("sys_mmapper_allocate_shared_memory_from_container(ipc_key=0x%x, size=0x%x, cid=0x%x, flags=0x%x, mem_id=*0x%x)", ipc_key, size, cid, flags, mem_id); - - if (size == 0) - { - return CELL_EALIGN; - } - - // Check page granularity. - switch (flags & SYS_MEMORY_GRANULARITY_MASK) - { - case 0: - case SYS_MEMORY_GRANULARITY_1M: - { - if (size % 0x100000) - { - return CELL_EALIGN; - } - - break; - } - case SYS_MEMORY_GRANULARITY_64K: - { - if (size % 0x10000) - { - return CELL_EALIGN; - } - - break; - } - default: - { - return CELL_EINVAL; - } - } - - const auto ct = idm::get_unlocked(cid); - - if (!ct) - { - return CELL_ESRCH; - } - - if (auto error = create_lv2_shm(ipc_key != SYS_MMAPPER_NO_SHM_KEY, ipc_key, size, flags & SYS_MEMORY_PAGE_SIZE_64K ? 0x10000 : 0x100000, flags, ct.get())) - { - return error; - } - - ppu.check_state(); - *mem_id = idm::last_id(); - return CELL_OK; -} - -error_code sys_mmapper_allocate_shared_memory_ext(ppu_thread& ppu, u64 ipc_key, u64 size, u32 flags, vm::ptr entries, s32 entry_count, vm::ptr mem_id) -{ - ppu.state += cpu_flag::wait; - - sys_mmapper.todo("sys_mmapper_allocate_shared_memory_ext(ipc_key=0x%x, size=0x%x, flags=0x%x, entries=*0x%x, entry_count=0x%x, mem_id=*0x%x)", ipc_key, size, flags, entries, entry_count, mem_id); - - if (size == 0) - { - return CELL_EALIGN; - } - - switch (flags & SYS_MEMORY_GRANULARITY_MASK) - { - case SYS_MEMORY_GRANULARITY_1M: - case 0: - { - if (size % 0x100000) - { - return CELL_EALIGN; - } - - break; - } - case SYS_MEMORY_GRANULARITY_64K: - { - if (size % 0x10000) - { - return CELL_EALIGN; - } - - break; - } - default: - { - return CELL_EINVAL; - } - } - - if (flags & ~SYS_MEMORY_PAGE_SIZE_MASK) - { - return CELL_EINVAL; - } - - if (entry_count <= 0 || entry_count > 0x10) - { - return CELL_EINVAL; - } - - if constexpr (bool to_perm_check = false; true) - { - for (s32 i = 0; i < entry_count; i++) - { - const u64 type = entries[i].type; - - // The whole structure contents are unknown - sys_mmapper.todo("sys_mmapper_allocate_shared_memory_ext(): entry type = 0x%x", type); - - switch (type) - { - case 0: - case 1: - case 3: - { - break; - } - case 5: - { - to_perm_check = true; - break; - } - default: - { - return CELL_EPERM; - } - } - } - - if (to_perm_check) - { - if (flags != SYS_MEMORY_PAGE_SIZE_64K || !g_ps3_process_info.debug_or_root()) - { - return CELL_EPERM; - } - } - } - - // Get "default" memory container - auto& dct = g_fxo->get(); - - if (auto error = create_lv2_shm(true, ipc_key, size, flags & SYS_MEMORY_PAGE_SIZE_64K ? 0x10000 : 0x100000, flags, &dct)) - { - return error; - } - - ppu.check_state(); - *mem_id = idm::last_id(); - return CELL_OK; -} - -error_code sys_mmapper_allocate_shared_memory_from_container_ext(ppu_thread& ppu, u64 ipc_key, u64 size, u64 flags, u32 cid, vm::ptr entries, s32 entry_count, vm::ptr mem_id) -{ - ppu.state += cpu_flag::wait; - - sys_mmapper.todo("sys_mmapper_allocate_shared_memory_from_container_ext(ipc_key=0x%x, size=0x%x, flags=0x%x, cid=0x%x, entries=*0x%x, entry_count=0x%x, mem_id=*0x%x)", ipc_key, size, flags, cid, entries, - entry_count, mem_id); - - switch (flags & SYS_MEMORY_PAGE_SIZE_MASK) - { - case SYS_MEMORY_PAGE_SIZE_1M: - case 0: - { - if (size % 0x100000) - { - return CELL_EALIGN; - } - - break; - } - case SYS_MEMORY_PAGE_SIZE_64K: - { - if (size % 0x10000) - { - return CELL_EALIGN; - } - - break; - } - default: - { - return CELL_EINVAL; - } - } - - if (flags & ~SYS_MEMORY_PAGE_SIZE_MASK) - { - return CELL_EINVAL; - } - - if (entry_count <= 0 || entry_count > 0x10) - { - return CELL_EINVAL; - } - - if constexpr (bool to_perm_check = false; true) - { - for (s32 i = 0; i < entry_count; i++) - { - const u64 type = entries[i].type; - - sys_mmapper.todo("sys_mmapper_allocate_shared_memory_from_container_ext(): entry type = 0x%x", type); - - switch (type) - { - case 0: - case 1: - case 3: - { - break; - } - case 5: - { - to_perm_check = true; - break; - } - default: - { - return CELL_EPERM; - } - } - } - - if (to_perm_check) - { - if (flags != SYS_MEMORY_PAGE_SIZE_64K || !g_ps3_process_info.debug_or_root()) - { - return CELL_EPERM; - } - } - } - - const auto ct = idm::get_unlocked(cid); - - if (!ct) - { - return CELL_ESRCH; - } - - if (auto error = create_lv2_shm(true, ipc_key, size, flags & SYS_MEMORY_PAGE_SIZE_64K ? 0x10000 : 0x100000, flags, ct.get())) - { - return error; - } - - ppu.check_state(); - *mem_id = idm::last_id(); - return CELL_OK; -} - -error_code sys_mmapper_change_address_access_right(ppu_thread& ppu, u32 addr, u64 flags) -{ - ppu.state += cpu_flag::wait; - - sys_mmapper.todo("sys_mmapper_change_address_access_right(addr=0x%x, flags=0x%x)", addr, flags); - - return CELL_OK; -} - -error_code sys_mmapper_free_address(ppu_thread& ppu, u32 addr) -{ - ppu.state += cpu_flag::wait; - - sys_mmapper.warning("sys_mmapper_free_address(addr=0x%x)", addr); - - if (addr < 0x20000000 || addr >= 0xC0000000) - { - return {CELL_EINVAL, addr}; - } - - // If page fault notify exists and an address in this area is faulted, we can't free the memory. - auto& pf_events = g_fxo->get(); - std::lock_guard pf_lock(pf_events.pf_mutex); - - const auto mem = vm::get(vm::any, addr); - - if (!mem || mem->addr != addr) - { - return {CELL_EINVAL, addr}; - } - - for (const auto& ev : pf_events.events) - { - if (addr <= ev.second && ev.second <= addr + mem->size - 1) - { - return CELL_EBUSY; - } - } - - // Try to unmap area - const auto [area, success] = vm::unmap(addr, true, &mem); - - if (!area) - { - return {CELL_EINVAL, addr}; - } - - if (!success) - { - return CELL_EBUSY; - } - - // If a memory block is freed, remove it from page notification table. - auto& pf_entries = g_fxo->get(); - std::lock_guard lock(pf_entries.mutex); - - auto ind_to_remove = pf_entries.entries.begin(); - for (; ind_to_remove != pf_entries.entries.end(); ++ind_to_remove) - { - if (addr == ind_to_remove->start_addr) - { - break; - } - } - if (ind_to_remove != pf_entries.entries.end()) - { - pf_entries.entries.erase(ind_to_remove); - } - - return CELL_OK; -} - -error_code sys_mmapper_free_shared_memory(ppu_thread& ppu, u32 mem_id) -{ - ppu.state += cpu_flag::wait; - - sys_mmapper.warning("sys_mmapper_free_shared_memory(mem_id=0x%x)", mem_id); - - // Conditionally remove memory ID - const auto mem = idm::withdraw(mem_id, [&](lv2_memory& mem) -> CellError - { - if (mem.counter) - { - return CELL_EBUSY; - } - - lv2_obj::on_id_destroy(mem, mem.key, +mem.pshared); - - if (!mem.exists) - { - // Return "physical memory" to the memory container - mem.ct->free(mem.size); - } - - return {}; - }); - - if (!mem) - { - return CELL_ESRCH; - } - - if (mem.ret) - { - return mem.ret; - } - - return CELL_OK; -} - -error_code sys_mmapper_map_shared_memory(ppu_thread& ppu, u32 addr, u32 mem_id, u64 flags) -{ - ppu.state += cpu_flag::wait; - - sys_mmapper.warning("sys_mmapper_map_shared_memory(addr=0x%x, mem_id=0x%x, flags=0x%x)", addr, mem_id, flags); - - const auto area = vm::get(vm::any, addr); - - if (!area || addr < 0x20000000 || addr >= 0xC0000000) - { - return CELL_EINVAL; - } - - const auto mem = idm::get(mem_id, [&](lv2_memory& mem) -> CellError - { - const u32 page_alignment = area->flags & SYS_MEMORY_PAGE_SIZE_64K ? 0x10000 : 0x100000; - - if (mem.align < page_alignment) - { - return CELL_EINVAL; - } - - if (addr % page_alignment) - { - return CELL_EALIGN; - } - - mem.counter++; - return {}; - }); - - if (!mem) - { - return CELL_ESRCH; - } - - if (mem.ret) - { - return mem.ret; - } - - if (!area->falloc(addr, mem->size, &mem->shm, mem->align == 0x10000 ? SYS_MEMORY_PAGE_SIZE_64K : SYS_MEMORY_PAGE_SIZE_1M)) - { - mem->counter--; - - if (!area->is_valid()) - { - return {CELL_EINVAL, addr}; - } - - return CELL_EBUSY; - } - - vm::lock_sudo(addr, mem->size); - return CELL_OK; -} - -error_code sys_mmapper_search_and_map(ppu_thread& ppu, u32 start_addr, u32 mem_id, u64 flags, vm::ptr alloc_addr) -{ - ppu.state += cpu_flag::wait; - - sys_mmapper.warning("sys_mmapper_search_and_map(start_addr=0x%x, mem_id=0x%x, flags=0x%x, alloc_addr=*0x%x)", start_addr, mem_id, flags, alloc_addr); - - const auto area = vm::get(vm::any, start_addr); - - if (!area || start_addr != area->addr || start_addr < 0x20000000 || start_addr >= 0xC0000000) - { - return {CELL_EINVAL, start_addr}; - } - - const auto mem = idm::get(mem_id, [&](lv2_memory& mem) -> CellError - { - const u32 page_alignment = area->flags & SYS_MEMORY_PAGE_SIZE_64K ? 0x10000 : 0x100000; - - if (mem.align < page_alignment) - { - return CELL_EALIGN; - } - - mem.counter++; - return {}; - }); - - if (!mem) - { - return CELL_ESRCH; - } - - if (mem.ret) - { - return mem.ret; - } - - const u32 addr = area->alloc(mem->size, &mem->shm, mem->align, mem->align == 0x10000 ? SYS_MEMORY_PAGE_SIZE_64K : SYS_MEMORY_PAGE_SIZE_1M); - - if (!addr) - { - mem->counter--; - - if (!area->is_valid()) - { - return {CELL_EINVAL, start_addr}; - } - - return CELL_ENOMEM; - } - - sys_mmapper.notice("sys_mmapper_search_and_map(): Found 0x%x address", addr); - - vm::lock_sudo(addr, mem->size); - ppu.check_state(); - *alloc_addr = addr; - return CELL_OK; -} - -error_code sys_mmapper_unmap_shared_memory(ppu_thread& ppu, u32 addr, vm::ptr mem_id) -{ - ppu.state += cpu_flag::wait; - - sys_mmapper.warning("sys_mmapper_unmap_shared_memory(addr=0x%x, mem_id=*0x%x)", addr, mem_id); - - const auto area = vm::get(vm::any, addr); - - if (!area || addr < 0x20000000 || addr >= 0xC0000000) - { - return {CELL_EINVAL, addr}; - } - - const auto shm = area->peek(addr); - - if (!shm.second) - { - return {CELL_EINVAL, addr}; - } - - const auto mem = idm::select([&](u32 id, lv2_memory& mem) -> u32 - { - if (mem.shm.get() == shm.second.get()) - { - return id; - } - - return 0; - }); - - if (!mem) - { - return {CELL_EINVAL, addr}; - } - - if (!area->dealloc(addr, &shm.second)) - { - return {CELL_EINVAL, addr}; - } - - // Write out the ID - ppu.check_state(); - *mem_id = mem.ret; - - // Acknowledge - mem->counter--; - - return CELL_OK; -} - -error_code sys_mmapper_enable_page_fault_notification(ppu_thread& ppu, u32 start_addr, u32 event_queue_id) -{ - ppu.state += cpu_flag::wait; - - sys_mmapper.warning("sys_mmapper_enable_page_fault_notification(start_addr=0x%x, event_queue_id=0x%x)", start_addr, event_queue_id); - - auto mem = vm::get(vm::any, start_addr); - if (!mem || start_addr != mem->addr || start_addr < 0x20000000 || start_addr >= 0xC0000000) - { - return {CELL_EINVAL, start_addr}; - } - - // TODO: Check memory region's flags to make sure the memory can be used for page faults. - - auto queue = idm::get_unlocked(event_queue_id); - - if (!queue) - { // Can't connect the queue if it doesn't exist. - return CELL_ESRCH; - } - - vm::var port_id(0); - error_code res = sys_event_port_create(ppu, port_id, SYS_EVENT_PORT_LOCAL, SYS_MEMORY_PAGE_FAULT_EVENT_KEY); - sys_event_port_connect_local(ppu, *port_id, event_queue_id); - - if (res + 0u == CELL_EAGAIN) - { - // Not enough system resources. - return CELL_EAGAIN; - } - - auto& pf_entries = g_fxo->get(); - std::unique_lock lock(pf_entries.mutex); - - // Return error code if page fault notifications are already enabled - for (const auto& entry : pf_entries.entries) - { - if (entry.start_addr == start_addr) - { - lock.unlock(); - sys_event_port_disconnect(ppu, *port_id); - sys_event_port_destroy(ppu, *port_id); - return CELL_EBUSY; - } - } - - page_fault_notification_entry entry{start_addr, event_queue_id, port_id->value()}; - pf_entries.entries.emplace_back(entry); - - return CELL_OK; -} - -error_code mmapper_thread_recover_page_fault(cpu_thread* cpu) -{ - // We can only wake a thread if it is being suspended for a page fault. - auto& pf_events = g_fxo->get(); - { - std::lock_guard pf_lock(pf_events.pf_mutex); - const auto pf_event_ind = pf_events.events.find(cpu); - - if (pf_event_ind == pf_events.events.end()) - { - // if not found... - return CELL_EINVAL; - } - - pf_events.events.erase(pf_event_ind); - - if (cpu->get_class() == thread_class::ppu) - { - lv2_obj::awake(cpu); - } - else - { - cpu->state += cpu_flag::signal; - } - } - - if (cpu->state & cpu_flag::signal) - { - cpu->state.notify_one(); - } - - return CELL_OK; -} diff --git a/rpcs3/Emu/Cell/lv2/sys_mmapper.h b/rpcs3/Emu/Cell/lv2/sys_mmapper.h deleted file mode 100644 index a24f7f3c1..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_mmapper.h +++ /dev/null @@ -1,115 +0,0 @@ -#pragma once - -#include "sys_sync.h" - -#include "Emu/Memory/vm_ptr.h" -#include "Emu/Cell/ErrorCodes.h" - -#include - -struct lv2_memory_container; - -namespace utils -{ - class shm; -} - -struct lv2_memory : lv2_obj -{ - static const u32 id_base = 0x08000000; - - const u32 size; // Memory size - const u32 align; // Alignment required - const u64 flags; - const u64 key; // IPC key - const bool pshared; // Process shared flag - lv2_memory_container* const ct; // Associated memory container - const std::shared_ptr shm; - - atomic_t counter{0}; - - lv2_memory(u32 size, u32 align, u64 flags, u64 key, bool pshared, lv2_memory_container* ct); - - lv2_memory(utils::serial& ar); - static std::function load(utils::serial& ar); - void save(utils::serial& ar); - - CellError on_id_create(); -}; - -enum : u64 -{ - SYS_MEMORY_PAGE_FAULT_EVENT_KEY = 0xfffe000000000000ULL, -}; - -enum : u64 -{ - SYS_MMAPPER_NO_SHM_KEY = 0xffff000000000000ull, // Unofficial name -}; - -enum : u64 -{ - SYS_MEMORY_PAGE_FAULT_CAUSE_NON_MAPPED = 0x2ULL, - SYS_MEMORY_PAGE_FAULT_CAUSE_READ_ONLY = 0x1ULL, - SYS_MEMORY_PAGE_FAULT_TYPE_PPU_THREAD = 0x0ULL, - SYS_MEMORY_PAGE_FAULT_TYPE_SPU_THREAD = 0x1ULL, - SYS_MEMORY_PAGE_FAULT_TYPE_RAW_SPU = 0x2ULL, -}; - -struct page_fault_notification_entry -{ - ENABLE_BITWISE_SERIALIZATION; - - u32 start_addr; // Starting address of region to monitor. - u32 event_queue_id; // Queue to be notified. - u32 port_id; // Port used to notify the queue. -}; - -// Used to hold list of queues to be notified on page fault event. -struct page_fault_notification_entries -{ - std::vector entries; - shared_mutex mutex; - - SAVESTATE_INIT_POS(44); - - page_fault_notification_entries() = default; - page_fault_notification_entries(utils::serial& ar); - void save(utils::serial& ar); -}; - -struct page_fault_event_entries -{ - // First = thread, second = addr - std::unordered_map events; - shared_mutex pf_mutex; -}; - -struct mmapper_unk_entry_struct0 -{ - be_t a; // 0x0 - be_t b; // 0x4 - be_t c; // 0x8 - be_t d; // 0xc - be_t type; // 0x10 -}; - -// Aux -class ppu_thread; - -error_code mmapper_thread_recover_page_fault(cpu_thread* cpu); - -// SysCalls -error_code sys_mmapper_allocate_address(ppu_thread&, u64 size, u64 flags, u64 alignment, vm::ptr alloc_addr); -error_code sys_mmapper_allocate_fixed_address(ppu_thread&); -error_code sys_mmapper_allocate_shared_memory(ppu_thread&, u64 ipc_key, u64 size, u64 flags, vm::ptr mem_id); -error_code sys_mmapper_allocate_shared_memory_from_container(ppu_thread&, u64 ipc_key, u64 size, u32 cid, u64 flags, vm::ptr mem_id); -error_code sys_mmapper_allocate_shared_memory_ext(ppu_thread&, u64 ipc_key, u64 size, u32 flags, vm::ptr entries, s32 entry_count, vm::ptr mem_id); -error_code sys_mmapper_allocate_shared_memory_from_container_ext(ppu_thread&, u64 ipc_key, u64 size, u64 flags, u32 cid, vm::ptr entries, s32 entry_count, vm::ptr mem_id); -error_code sys_mmapper_change_address_access_right(ppu_thread&, u32 addr, u64 flags); -error_code sys_mmapper_free_address(ppu_thread&, u32 addr); -error_code sys_mmapper_free_shared_memory(ppu_thread&, u32 mem_id); -error_code sys_mmapper_map_shared_memory(ppu_thread&, u32 addr, u32 mem_id, u64 flags); -error_code sys_mmapper_search_and_map(ppu_thread&, u32 start_addr, u32 mem_id, u64 flags, vm::ptr alloc_addr); -error_code sys_mmapper_unmap_shared_memory(ppu_thread&, u32 addr, vm::ptr mem_id); -error_code sys_mmapper_enable_page_fault_notification(ppu_thread&, u32 start_addr, u32 event_queue_id); diff --git a/rpcs3/Emu/Cell/lv2/sys_mutex.cpp b/rpcs3/Emu/Cell/lv2/sys_mutex.cpp deleted file mode 100644 index 28460f76c..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_mutex.cpp +++ /dev/null @@ -1,379 +0,0 @@ -#include "stdafx.h" - -#include "Emu/IdManager.h" - -#include "Emu/Cell/ErrorCodes.h" -#include "Emu/Cell/PPUThread.h" - -#include "util/asm.hpp" - -#include "sys_mutex.h" - -LOG_CHANNEL(sys_mutex); - -lv2_mutex::lv2_mutex(utils::serial& ar) - : protocol(ar), recursive(ar), adaptive(ar), key(ar), name(ar) -{ - ar(lock_count, control.raw().owner); - - // For backwards compatibility - control.raw().owner >>= 1; -} - -std::function lv2_mutex::load(utils::serial& ar) -{ - return load_func(make_shared(exact_t(ar))); -} - -void lv2_mutex::save(utils::serial& ar) -{ - ar(protocol, recursive, adaptive, key, name, lock_count, control.raw().owner << 1); -} - -error_code sys_mutex_create(ppu_thread& ppu, vm::ptr mutex_id, vm::ptr attr) -{ - ppu.state += cpu_flag::wait; - - sys_mutex.trace("sys_mutex_create(mutex_id=*0x%x, attr=*0x%x)", mutex_id, attr); - - if (!mutex_id || !attr) - { - return CELL_EFAULT; - } - - const auto _attr = *attr; - - const u64 ipc_key = lv2_obj::get_key(_attr); - - if (ipc_key) - { - sys_mutex.warning("sys_mutex_create(mutex_id=*0x%x, attr=*0x%x): IPC=0x%016x", mutex_id, attr, ipc_key); - } - - switch (_attr.protocol) - { - case SYS_SYNC_FIFO: break; - case SYS_SYNC_PRIORITY: break; - case SYS_SYNC_PRIORITY_INHERIT: - sys_mutex.warning("sys_mutex_create(): SYS_SYNC_PRIORITY_INHERIT"); - break; - default: - { - sys_mutex.error("sys_mutex_create(): unknown protocol (0x%x)", _attr.protocol); - return CELL_EINVAL; - } - } - - switch (_attr.recursive) - { - case SYS_SYNC_RECURSIVE: break; - case SYS_SYNC_NOT_RECURSIVE: break; - default: - { - sys_mutex.error("sys_mutex_create(): unknown recursive (0x%x)", _attr.recursive); - return CELL_EINVAL; - } - } - - if (_attr.adaptive != SYS_SYNC_NOT_ADAPTIVE) - { - sys_mutex.todo("sys_mutex_create(): unexpected adaptive (0x%x)", _attr.adaptive); - } - - if (auto error = lv2_obj::create(_attr.pshared, _attr.ipc_key, _attr.flags, [&]() - { - return make_shared( - _attr.protocol, - _attr.recursive, - _attr.adaptive, - ipc_key, - _attr.name_u64); - })) - { - return error; - } - - ppu.check_state(); - *mutex_id = idm::last_id(); - return CELL_OK; -} - -error_code sys_mutex_destroy(ppu_thread& ppu, u32 mutex_id) -{ - ppu.state += cpu_flag::wait; - - sys_mutex.trace("sys_mutex_destroy(mutex_id=0x%x)", mutex_id); - - const auto mutex = idm::withdraw(mutex_id, [](lv2_mutex& mutex) -> CellError - { - std::lock_guard lock(mutex.mutex); - - if (atomic_storage::load(mutex.control.raw().owner)) - { - return CELL_EBUSY; - } - - if (mutex.cond_count) - { - return CELL_EPERM; - } - - lv2_obj::on_id_destroy(mutex, mutex.key); - return {}; - }); - - if (!mutex) - { - return CELL_ESRCH; - } - - if (mutex->key) - { - sys_mutex.warning("sys_mutex_destroy(mutex_id=0x%x): IPC=0x%016x", mutex_id, mutex->key); - } - - if (mutex.ret) - { - return mutex.ret; - } - - return CELL_OK; -} - -error_code sys_mutex_lock(ppu_thread& ppu, u32 mutex_id, u64 timeout) -{ - ppu.state += cpu_flag::wait; - - sys_mutex.trace("sys_mutex_lock(mutex_id=0x%x, timeout=0x%llx)", mutex_id, timeout); - - const auto mutex = idm::get(mutex_id, [&, notify = lv2_obj::notify_all_t()](lv2_mutex& mutex) - { - CellError result = mutex.try_lock(ppu); - - if (result == CELL_EBUSY && !atomic_storage::load(mutex.control.raw().sq)) - { - // Try busy waiting a bit if advantageous - for (u32 i = 0, end = lv2_obj::has_ppus_in_running_state() ? 3 : 10; id_manager::g_mutex.is_lockable() && i < end; i++) - { - busy_wait(300); - result = mutex.try_lock(ppu); - - if (!result || atomic_storage::load(mutex.control.raw().sq)) - { - break; - } - } - } - - if (result == CELL_EBUSY) - { - lv2_obj::prepare_for_sleep(ppu); - - ppu.cancel_sleep = 1; - - if (mutex.try_own(ppu) || !mutex.sleep(ppu, timeout)) - { - result = {}; - } - - if (ppu.cancel_sleep != 1) - { - notify.cleanup(); - } - - ppu.cancel_sleep = 0; - } - - return result; - }); - - if (!mutex) - { - return CELL_ESRCH; - } - - if (mutex.ret) - { - if (mutex.ret != CELL_EBUSY) - { - return mutex.ret; - } - } - else - { - return CELL_OK; - } - - ppu.gpr[3] = CELL_OK; - - while (auto state = +ppu.state) - { - if (state & cpu_flag::signal && ppu.state.test_and_reset(cpu_flag::signal)) - { - break; - } - - if (is_stopped(state)) - { - std::lock_guard lock(mutex->mutex); - - for (auto cpu = atomic_storage::load(mutex->control.raw().sq); cpu; cpu = cpu->next_cpu) - { - if (cpu == &ppu) - { - ppu.state += cpu_flag::again; - return {}; - } - } - - break; - } - - for (usz i = 0; cpu_flag::signal - ppu.state && i < 40; i++) - { - busy_wait(500); - } - - if (ppu.state & cpu_flag::signal) - { - continue; - } - - if (timeout) - { - if (lv2_obj::wait_timeout(timeout, &ppu)) - { - // Wait for rescheduling - if (ppu.check_state()) - { - continue; - } - - ppu.state += cpu_flag::wait; - - if (!atomic_storage::load(mutex->control.raw().sq)) - { - // Waiters queue is empty, so the thread must have been signaled - mutex->mutex.lock_unlock(); - break; - } - - std::lock_guard lock(mutex->mutex); - - bool success = false; - - mutex->control.fetch_op([&](lv2_mutex::control_data_t& data) - { - success = false; - - ppu_thread* sq = static_cast(data.sq); - - const bool retval = &ppu == sq; - - if (!mutex->unqueue(sq, &ppu)) - { - return false; - } - - success = true; - - if (!retval) - { - return false; - } - - data.sq = sq; - return true; - }); - - if (success) - { - ppu.next_cpu = nullptr; - ppu.gpr[3] = CELL_ETIMEDOUT; - } - - break; - } - } - else - { - ppu.state.wait(state); - } - } - - return not_an_error(ppu.gpr[3]); -} - -error_code sys_mutex_trylock(ppu_thread& ppu, u32 mutex_id) -{ - ppu.state += cpu_flag::wait; - - sys_mutex.trace("sys_mutex_trylock(mutex_id=0x%x)", mutex_id); - - const auto mutex = idm::check(mutex_id, [&](lv2_mutex& mutex) - { - return mutex.try_lock(ppu); - }); - - if (!mutex) - { - return CELL_ESRCH; - } - - if (mutex.ret) - { - if (mutex.ret == CELL_EBUSY) - { - return not_an_error(CELL_EBUSY); - } - - return mutex.ret; - } - - return CELL_OK; -} - -error_code sys_mutex_unlock(ppu_thread& ppu, u32 mutex_id) -{ - ppu.state += cpu_flag::wait; - - sys_mutex.trace("sys_mutex_unlock(mutex_id=0x%x)", mutex_id); - - const auto mutex = idm::check(mutex_id, [&, notify = lv2_obj::notify_all_t()](lv2_mutex& mutex) -> CellError - { - auto result = mutex.try_unlock(ppu); - - if (result == CELL_EBUSY) - { - std::lock_guard lock(mutex.mutex); - - if (auto cpu = mutex.reown()) - { - if (cpu->state & cpu_flag::again) - { - ppu.state += cpu_flag::again; - return {}; - } - - mutex.awake(cpu); - } - - result = {}; - } - - notify.cleanup(); - return result; - }); - - if (!mutex) - { - return CELL_ESRCH; - } - - if (mutex.ret) - { - return mutex.ret; - } - - return CELL_OK; -} diff --git a/rpcs3/Emu/Cell/lv2/sys_mutex.h b/rpcs3/Emu/Cell/lv2/sys_mutex.h deleted file mode 100644 index 1dee5ec8b..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_mutex.h +++ /dev/null @@ -1,202 +0,0 @@ -#pragma once - -#include "sys_sync.h" - -#include "Emu/Memory/vm_ptr.h" - -#include "Emu/Cell/PPUThread.h" - -struct sys_mutex_attribute_t -{ - be_t protocol; // SYS_SYNC_FIFO, SYS_SYNC_PRIORITY or SYS_SYNC_PRIORITY_INHERIT - be_t recursive; // SYS_SYNC_RECURSIVE or SYS_SYNC_NOT_RECURSIVE - be_t pshared; - be_t adaptive; - be_t ipc_key; - be_t flags; - be_t pad; - - union - { - nse_t name_u64; - char name[sizeof(u64)]; - }; -}; - -class ppu_thread; - -struct lv2_mutex final : lv2_obj -{ - static const u32 id_base = 0x85000000; - - const lv2_protocol protocol; - const u32 recursive; - const u32 adaptive; - const u64 key; - const u64 name; - - u32 cond_count = 0; // Condition Variables - shared_mutex mutex; - atomic_t lock_count{0}; // Recursive Locks - - struct alignas(16) control_data_t - { - u32 owner{}; - u32 reserved{}; - ppu_thread* sq{}; - }; - - atomic_t control{}; - - lv2_mutex(u32 protocol, u32 recursive, u32 adaptive, u64 key, u64 name) noexcept - : protocol{static_cast(protocol)}, recursive(recursive), adaptive(adaptive), key(key), name(name) - { - } - - lv2_mutex(utils::serial& ar); - static std::function load(utils::serial& ar); - void save(utils::serial& ar); - - template - CellError try_lock(T& cpu) - { - auto it = control.load(); - - if (!it.owner) - { - auto store = it; - store.owner = cpu.id; - if (!control.compare_and_swap_test(it, store)) - { - return CELL_EBUSY; - } - - return {}; - } - - if (it.owner == cpu.id) - { - // Recursive locking - if (recursive == SYS_SYNC_RECURSIVE) - { - if (lock_count == 0xffffffffu) - { - return CELL_EKRESOURCE; - } - - lock_count++; - return {}; - } - - return CELL_EDEADLK; - } - - return CELL_EBUSY; - } - - template - bool try_own(T& cpu) - { - if (control.atomic_op([&](control_data_t& data) - { - if (data.owner) - { - cpu.prio.atomic_op([tag = ++g_priority_order_tag](std::common_type_t& prio) - { - prio.order = tag; - }); - - cpu.next_cpu = data.sq; - data.sq = &cpu; - return false; - } - else - { - data.owner = cpu.id; - return true; - } - })) - { - cpu.next_cpu = nullptr; - return true; - } - - return false; - } - - template - CellError try_unlock(T& cpu) - { - auto it = control.load(); - - if (it.owner != cpu.id) - { - return CELL_EPERM; - } - - if (lock_count) - { - lock_count--; - return {}; - } - - if (!it.sq) - { - auto store = it; - store.owner = 0; - - if (control.compare_and_swap_test(it, store)) - { - return {}; - } - } - - return CELL_EBUSY; - } - - template - T* reown() - { - T* res{}; - - control.fetch_op([&](control_data_t& data) - { - res = nullptr; - - if (auto sq = static_cast(data.sq)) - { - res = schedule(data.sq, protocol, false); - - if (sq == data.sq) - { - atomic_storage::release(control.raw().owner, res->id); - return false; - } - - data.owner = res->id; - return true; - } - else - { - data.owner = 0; - return true; - } - }); - - if (res && cpu_flag::again - res->state) - { - // Detach manually (fetch_op can fail, so avoid side-effects on the first node in this case) - res->next_cpu = nullptr; - } - - return res; - } -}; - -// Syscalls - -error_code sys_mutex_create(ppu_thread& ppu, vm::ptr mutex_id, vm::ptr attr); -error_code sys_mutex_destroy(ppu_thread& ppu, u32 mutex_id); -error_code sys_mutex_lock(ppu_thread& ppu, u32 mutex_id, u64 timeout); -error_code sys_mutex_trylock(ppu_thread& ppu, u32 mutex_id); -error_code sys_mutex_unlock(ppu_thread& ppu, u32 mutex_id); diff --git a/rpcs3/Emu/Cell/lv2/sys_net.cpp b/rpcs3/Emu/Cell/lv2/sys_net.cpp deleted file mode 100644 index d790e2f5f..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_net.cpp +++ /dev/null @@ -1,1895 +0,0 @@ -#include "stdafx.h" -#include "sys_net.h" - -#include "Emu/IdManager.h" -#include "Emu/Cell/PPUThread.h" -#include "util/Thread.h" - -#include "sys_sync.h" - -#ifdef _WIN32 -#include -#include -#else -#ifdef __clang__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wold-style-cast" -#endif -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef __clang__ -#pragma GCC diagnostic pop -#endif -#endif - -#include "Emu/NP/np_handler.h" -#include "Emu/NP/np_helpers.h" -#include "Emu/Cell/timers.hpp" -#include - -#include "sys_net/network_context.h" -#include "sys_net/lv2_socket.h" -#include "sys_net/lv2_socket_native.h" -#include "sys_net/lv2_socket_raw.h" -#include "sys_net/lv2_socket_p2p.h" -#include "sys_net/lv2_socket_p2ps.h" -#include "sys_net/sys_net_helpers.h" - -LOG_CHANNEL(sys_net); -LOG_CHANNEL(sys_net_dump); - -template <> -void fmt_class_string::format(std::string& out, u64 arg) -{ - format_enum(out, arg, [](auto error) - { - switch (static_cast(error)) - { -#define SYS_NET_ERROR_CASE(x) \ - case -x: return "-" #x; \ - case x: \ - return #x - SYS_NET_ERROR_CASE(SYS_NET_ENOENT); - SYS_NET_ERROR_CASE(SYS_NET_EINTR); - SYS_NET_ERROR_CASE(SYS_NET_EBADF); - SYS_NET_ERROR_CASE(SYS_NET_ENOMEM); - SYS_NET_ERROR_CASE(SYS_NET_EACCES); - SYS_NET_ERROR_CASE(SYS_NET_EFAULT); - SYS_NET_ERROR_CASE(SYS_NET_EBUSY); - SYS_NET_ERROR_CASE(SYS_NET_EINVAL); - SYS_NET_ERROR_CASE(SYS_NET_EMFILE); - SYS_NET_ERROR_CASE(SYS_NET_ENOSPC); - SYS_NET_ERROR_CASE(SYS_NET_EPIPE); - SYS_NET_ERROR_CASE(SYS_NET_EAGAIN); - static_assert(SYS_NET_EWOULDBLOCK == SYS_NET_EAGAIN); - SYS_NET_ERROR_CASE(SYS_NET_EINPROGRESS); - SYS_NET_ERROR_CASE(SYS_NET_EALREADY); - SYS_NET_ERROR_CASE(SYS_NET_EDESTADDRREQ); - SYS_NET_ERROR_CASE(SYS_NET_EMSGSIZE); - SYS_NET_ERROR_CASE(SYS_NET_EPROTOTYPE); - SYS_NET_ERROR_CASE(SYS_NET_ENOPROTOOPT); - SYS_NET_ERROR_CASE(SYS_NET_EPROTONOSUPPORT); - SYS_NET_ERROR_CASE(SYS_NET_EOPNOTSUPP); - SYS_NET_ERROR_CASE(SYS_NET_EPFNOSUPPORT); - SYS_NET_ERROR_CASE(SYS_NET_EAFNOSUPPORT); - SYS_NET_ERROR_CASE(SYS_NET_EADDRINUSE); - SYS_NET_ERROR_CASE(SYS_NET_EADDRNOTAVAIL); - SYS_NET_ERROR_CASE(SYS_NET_ENETDOWN); - SYS_NET_ERROR_CASE(SYS_NET_ENETUNREACH); - SYS_NET_ERROR_CASE(SYS_NET_ECONNABORTED); - SYS_NET_ERROR_CASE(SYS_NET_ECONNRESET); - SYS_NET_ERROR_CASE(SYS_NET_ENOBUFS); - SYS_NET_ERROR_CASE(SYS_NET_EISCONN); - SYS_NET_ERROR_CASE(SYS_NET_ENOTCONN); - SYS_NET_ERROR_CASE(SYS_NET_ESHUTDOWN); - SYS_NET_ERROR_CASE(SYS_NET_ETOOMANYREFS); - SYS_NET_ERROR_CASE(SYS_NET_ETIMEDOUT); - SYS_NET_ERROR_CASE(SYS_NET_ECONNREFUSED); - SYS_NET_ERROR_CASE(SYS_NET_EHOSTDOWN); - SYS_NET_ERROR_CASE(SYS_NET_EHOSTUNREACH); -#undef SYS_NET_ERROR_CASE - } - - return unknown; - }); -} - -template <> -void fmt_class_string::format(std::string& out, u64 arg) -{ - format_enum(out, arg, [](auto value) - { - switch (value) - { - case SYS_NET_SOCK_STREAM: return "STREAM"; - case SYS_NET_SOCK_DGRAM: return "DGRAM"; - case SYS_NET_SOCK_RAW: return "RAW"; - case SYS_NET_SOCK_DGRAM_P2P: return "DGRAM-P2P"; - case SYS_NET_SOCK_STREAM_P2P: return "STREAM-P2P"; - } - - return unknown; - }); -} - -template <> -void fmt_class_string::format(std::string& out, u64 arg) -{ - format_enum(out, arg, [](auto value) - { - switch (value) - { - case SYS_NET_AF_UNSPEC: return "UNSPEC"; - case SYS_NET_AF_LOCAL: return "LOCAL"; - case SYS_NET_AF_INET: return "INET"; - case SYS_NET_AF_INET6: return "INET6"; - } - - return unknown; - }); -} - -template <> -void fmt_class_string::format(std::string& out, u64 arg) -{ - format_enum(out, arg, [](auto value) - { - switch (value) - { - case SYS_NET_IPPROTO_IP: return "IPPROTO_IP"; - case SYS_NET_IPPROTO_ICMP: return "IPPROTO_ICMP"; - case SYS_NET_IPPROTO_IGMP: return "IPPROTO_IGMP"; - case SYS_NET_IPPROTO_TCP: return "IPPROTO_TCP"; - case SYS_NET_IPPROTO_UDP: return "IPPROTO_UDP"; - case SYS_NET_IPPROTO_ICMPV6: return "IPPROTO_ICMPV6"; - } - - return unknown; - }); -} - -template <> -void fmt_class_string::format(std::string& out, u64 arg) -{ - format_enum(out, arg, [](auto value) - { - switch (value) - { - case SYS_NET_TCP_NODELAY: return "TCP_NODELAY"; - case SYS_NET_TCP_MAXSEG: return "TCP_MAXSEG"; - case SYS_NET_TCP_MSS_TO_ADVERTISE: return "TCP_MSS_TO_ADVERTISE"; - } - - return unknown; - }); -} - -template <> -void fmt_class_string::format(std::string& out, u64 arg) -{ - format_enum(out, arg, [](auto value) - { - switch (value) - { - case SYS_NET_SO_SNDBUF: return "SO_SNDBUF"; - case SYS_NET_SO_RCVBUF: return "SO_RCVBUF"; - case SYS_NET_SO_SNDLOWAT: return "SO_SNDLOWAT"; - case SYS_NET_SO_RCVLOWAT: return "SO_RCVLOWAT"; - case SYS_NET_SO_SNDTIMEO: return "SO_SNDTIMEO"; - case SYS_NET_SO_RCVTIMEO: return "SO_RCVTIMEO"; - case SYS_NET_SO_ERROR: return "SO_ERROR"; - case SYS_NET_SO_TYPE: return "SO_TYPE"; - case SYS_NET_SO_NBIO: return "SO_NBIO"; - case SYS_NET_SO_TPPOLICY: return "SO_TPPOLICY"; - case SYS_NET_SO_REUSEADDR: return "SO_REUSEADDR"; - case SYS_NET_SO_KEEPALIVE: return "SO_KEEPALIVE"; - case SYS_NET_SO_BROADCAST: return "SO_BROADCAST"; - case SYS_NET_SO_LINGER: return "SO_LINGER"; - case SYS_NET_SO_OOBINLINE: return "SO_OOBINLINE"; - case SYS_NET_SO_REUSEPORT: return "SO_REUSEPORT"; - case SYS_NET_SO_ONESBCAST: return "SO_ONESBCAST"; - case SYS_NET_SO_USECRYPTO: return "SO_USECRYPTO"; - case SYS_NET_SO_USESIGNATURE: return "SO_USESIGNATURE"; - case SYS_NET_SOL_SOCKET: return "SOL_SOCKET"; - } - - return unknown; - }); -} - -template <> -void fmt_class_string::format(std::string& out, u64 arg) -{ - format_enum(out, arg, [](auto value) - { - switch (value) - { - case SYS_NET_IP_HDRINCL: return "IP_HDRINCL"; - case SYS_NET_IP_TOS: return "IP_TOS"; - case SYS_NET_IP_TTL: return "IP_TTL"; - case SYS_NET_IP_MULTICAST_IF: return "IP_MULTICAST_IF"; - case SYS_NET_IP_MULTICAST_TTL: return "IP_MULTICAST_TTL"; - case SYS_NET_IP_MULTICAST_LOOP: return "IP_MULTICAST_LOOP"; - case SYS_NET_IP_ADD_MEMBERSHIP: return "IP_ADD_MEMBERSHIP"; - case SYS_NET_IP_DROP_MEMBERSHIP: return "IP_DROP_MEMBERSHIP"; - case SYS_NET_IP_TTLCHK: return "IP_TTLCHK"; - case SYS_NET_IP_MAXTTL: return "IP_MAXTTL"; - case SYS_NET_IP_DONTFRAG: return "IP_DONTFRAG"; - } - - return unknown; - }); -} - -template <> -void fmt_class_string::format(std::string& out, u64 arg) -{ - const u8* data = reinterpret_cast(&get_object(arg)); - - fmt::append(out, "%u.%u.%u.%u", data[0], data[1], data[2], data[3]); -} - -lv2_socket::lv2_socket(utils::serial& ar, lv2_socket_type _type) - : family(ar), type(_type), protocol(ar), so_nbio(ar), so_error(ar), so_tcp_maxseg(ar) -#ifdef _WIN32 - , - so_reuseaddr(ar), so_reuseport(ar) -{ -#else -{ - // Try to match structure between different platforms - ar.pos += 8; -#endif - - [[maybe_unused]] const s32 version = GET_SERIALIZATION_VERSION(lv2_net); - - ar(so_rcvtimeo, so_sendtimeo); - - lv2_id = idm::last_id(); - - ar(last_bound_addr); -} - -std::function lv2_socket::load(utils::serial& ar) -{ - const lv2_socket_type type{ar}; - - shared_ptr sock_lv2; - - switch (type) - { - case SYS_NET_SOCK_STREAM: - case SYS_NET_SOCK_DGRAM: - { - auto lv2_native = make_shared(ar, type); - ensure(lv2_native->create_socket() >= 0); - sock_lv2 = std::move(lv2_native); - break; - } - case SYS_NET_SOCK_RAW: sock_lv2 = make_shared(ar, type); break; - case SYS_NET_SOCK_DGRAM_P2P: sock_lv2 = make_shared(ar, type); break; - case SYS_NET_SOCK_STREAM_P2P: sock_lv2 = make_shared(ar, type); break; - } - - if (std::memcmp(&sock_lv2->last_bound_addr, std::array{}.data(), 16)) - { - // NOTE: It is allowed fail - sock_lv2->bind(sock_lv2->last_bound_addr); - } - - return [ptr = sock_lv2](void* storage) - { - *static_cast*>(storage) = ptr; - }; - ; -} - -void lv2_socket::save(utils::serial& ar, bool save_only_this_class) -{ - USING_SERIALIZATION_VERSION(lv2_net); - - if (save_only_this_class) - { - ar(family, protocol, so_nbio, so_error, so_tcp_maxseg); -#ifdef _WIN32 - ar(so_reuseaddr, so_reuseport); -#else - ar(std::array{}); -#endif - ar(so_rcvtimeo, so_sendtimeo); - ar(last_bound_addr); - return; - } - - ar(type); - - switch (type) - { - case SYS_NET_SOCK_STREAM: - case SYS_NET_SOCK_DGRAM: - { - static_cast(this)->save(ar); - break; - } - case SYS_NET_SOCK_RAW: static_cast(this)->save(ar); break; - case SYS_NET_SOCK_DGRAM_P2P: static_cast(this)->save(ar); break; - case SYS_NET_SOCK_STREAM_P2P: static_cast(this)->save(ar); break; - } -} - -void sys_net_dump_data(std::string_view desc, const u8* data, s32 len, const void* addr) -{ - const sys_net_sockaddr_in_p2p* p2p_addr = reinterpret_cast(addr); - - if (p2p_addr) - sys_net_dump.trace("%s(%s:%d:%d): %s", desc, np::ip_to_string(std::bit_cast(p2p_addr->sin_addr)), p2p_addr->sin_port, p2p_addr->sin_vport, fmt::buf_to_hexstring(data, len)); - else - sys_net_dump.trace("%s: %s", desc, fmt::buf_to_hexstring(data, len)); -} - -error_code sys_net_bnet_accept(ppu_thread& ppu, s32 s, vm::ptr addr, vm::ptr paddrlen) -{ - ppu.state += cpu_flag::wait; - - sys_net.warning("sys_net_bnet_accept(s=%d, addr=*0x%x, paddrlen=*0x%x)", s, addr, paddrlen); - - if (addr.operator bool() != paddrlen.operator bool() || (paddrlen && *paddrlen < addr.size())) - { - return -SYS_NET_EINVAL; - } - - s32 result = 0; - sys_net_sockaddr sn_addr{}; - shared_ptr new_socket{}; - - const auto sock = idm::check(s, [&, notify = lv2_obj::notify_all_t()](lv2_socket& sock) - { - auto [success, res, res_socket, res_addr] = sock.accept(); - - if (success) - { - result = res; - sn_addr = res_addr; - new_socket = std::move(res_socket); - return true; - } - - auto lock = sock.lock(); - - sock.poll_queue(idm::get_unlocked>(ppu.id), lv2_socket::poll_t::read, [&](bs_t events) -> bool - { - if (events & lv2_socket::poll_t::read) - { - auto [success, res, res_socket, res_addr] = sock.accept(false); - if (success) - { - result = res; - sn_addr = res_addr; - new_socket = std::move(res_socket); - lv2_obj::awake(&ppu); - return success; - } - } - - sock.set_poll_event(lv2_socket::poll_t::read); - return false; - }); - - lv2_obj::prepare_for_sleep(ppu); - lv2_obj::sleep(ppu); - return false; - }); - - if (!sock) - { - return -SYS_NET_EBADF; - } - - if (!sock.ret) - { - while (auto state = ppu.state.fetch_sub(cpu_flag::signal)) - { - if (is_stopped(state)) - { - return {}; - } - - if (state & cpu_flag::signal) - { - break; - } - - ppu.state.wait(state); - } - - if (ppu.gpr[3] == static_cast(-SYS_NET_EINTR)) - { - return -SYS_NET_EINTR; - } - - if (result < 0) - { - return sys_net_error{result}; - } - } - - if (result < 0) - { - return sys_net_error{result}; - } - - s32 id_ps3 = result; - - if (!id_ps3) - { - ensure(new_socket); - id_ps3 = idm::import_existing(new_socket); - if (id_ps3 == id_manager::id_traits::invalid) - { - return -SYS_NET_EMFILE; - } - } - - static_cast(ppu.test_stopped()); - - if (addr) - { - *paddrlen = sizeof(sys_net_sockaddr_in); - *addr = sn_addr; - } - - // Socket ID - return not_an_error(id_ps3); -} - -error_code sys_net_bnet_bind(ppu_thread& ppu, s32 s, vm::cptr addr, u32 addrlen) -{ - ppu.state += cpu_flag::wait; - - sys_net.warning("sys_net_bnet_bind(s=%d, addr=*0x%x, addrlen=%u)", s, addr, addrlen); - - if (!addr || addrlen < addr.size()) - { - return -SYS_NET_EINVAL; - } - - if (!idm::check_unlocked(s)) - { - return -SYS_NET_EBADF; - } - - const sys_net_sockaddr sn_addr = *addr; - - // 0 presumably defaults to AF_INET(to check?) - if (sn_addr.sa_family != SYS_NET_AF_INET && sn_addr.sa_family != SYS_NET_AF_UNSPEC) - { - sys_net.error("sys_net_bnet_bind: unsupported sa_family (%d)", sn_addr.sa_family); - return -SYS_NET_EAFNOSUPPORT; - } - - const auto sock = idm::check(s, [&, notify = lv2_obj::notify_all_t()](lv2_socket& sock) -> s32 - { - return sock.bind(sn_addr); - }); - - if (!sock) - { - return -SYS_NET_EBADF; - } - - if (sock.ret) - { - return sys_net_error{sock.ret}; - } - - return CELL_OK; -} - -error_code sys_net_bnet_connect(ppu_thread& ppu, s32 s, vm::ptr addr, u32 addrlen) -{ - ppu.state += cpu_flag::wait; - - sys_net.warning("sys_net_bnet_connect(s=%d, addr=*0x%x, addrlen=%u)", s, addr, addrlen); - - if (!addr || addrlen < addr.size()) - { - return -SYS_NET_EINVAL; - } - - if (addr->sa_family != SYS_NET_AF_INET) - { - sys_net.error("sys_net_bnet_connect(s=%d): unsupported sa_family (%d)", s, addr->sa_family); - return -SYS_NET_EAFNOSUPPORT; - } - - if (!idm::check_unlocked(s)) - { - return -SYS_NET_EBADF; - } - - s32 result = 0; - sys_net_sockaddr sn_addr = *addr; - - const auto sock = idm::check(s, [&, notify = lv2_obj::notify_all_t()](lv2_socket& sock) - { - const auto success = sock.connect(sn_addr); - - if (success) - { - result = *success; - return true; - } - - auto lock = sock.lock(); - - sock.poll_queue(idm::get_unlocked>(ppu.id), lv2_socket::poll_t::write, [&](bs_t events) -> bool - { - if (events & lv2_socket::poll_t::write) - { - result = sock.connect_followup(); - - lv2_obj::awake(&ppu); - return true; - } - sock.set_poll_event(lv2_socket::poll_t::write); - return false; - }); - - lv2_obj::prepare_for_sleep(ppu); - lv2_obj::sleep(ppu); - return false; - }); - - if (!sock) - { - return -SYS_NET_EBADF; - } - - if (sock.ret) - { - if (result < 0) - { - return sys_net_error{result}; - } - - return not_an_error(result); - } - - if (!sock.ret) - { - while (auto state = ppu.state.fetch_sub(cpu_flag::signal)) - { - if (is_stopped(state)) - { - return {}; - } - - if (state & cpu_flag::signal) - { - break; - } - - ppu.state.wait(state); - } - - if (ppu.gpr[3] == static_cast(-SYS_NET_EINTR)) - { - return -SYS_NET_EINTR; - } - - if (result) - { - if (result < 0) - { - return sys_net_error{result}; - } - - return not_an_error(result); - } - } - - return CELL_OK; -} - -error_code sys_net_bnet_getpeername(ppu_thread& ppu, s32 s, vm::ptr addr, vm::ptr paddrlen) -{ - ppu.state += cpu_flag::wait; - - sys_net.warning("sys_net_bnet_getpeername(s=%d, addr=*0x%x, paddrlen=*0x%x)", s, addr, paddrlen); - - // Note: paddrlen is both an input and output argument - if (!addr || !paddrlen || *paddrlen < addr.size()) - { - return -SYS_NET_EINVAL; - } - - const auto sock = idm::check(s, [&, notify = lv2_obj::notify_all_t()](lv2_socket& sock) -> s32 - { - auto [res, sn_addr] = sock.getpeername(); - - if (res == CELL_OK) - { - *paddrlen = sizeof(sys_net_sockaddr); - *addr = sn_addr; - } - - return res; - }); - - if (!sock) - { - return -SYS_NET_EBADF; - } - - if (sock.ret < 0) - { - return sys_net_error{sock.ret}; - } - - return CELL_OK; -} - -error_code sys_net_bnet_getsockname(ppu_thread& ppu, s32 s, vm::ptr addr, vm::ptr paddrlen) -{ - ppu.state += cpu_flag::wait; - - sys_net.warning("sys_net_bnet_getsockname(s=%d, addr=*0x%x, paddrlen=*0x%x)", s, addr, paddrlen); - - // Note: paddrlen is both an input and output argument - if (!addr || !paddrlen || *paddrlen < addr.size()) - { - return -SYS_NET_EINVAL; - } - - const auto sock = idm::check(s, [&, notify = lv2_obj::notify_all_t()](lv2_socket& sock) -> s32 - { - auto [res, sn_addr] = sock.getsockname(); - - if (res == CELL_OK) - { - *paddrlen = sizeof(sys_net_sockaddr); - *addr = sn_addr; - } - - return res; - }); - - if (!sock) - { - return -SYS_NET_EBADF; - } - - if (sock.ret < 0) - { - return sys_net_error{sock.ret}; - } - - return CELL_OK; -} - -error_code sys_net_bnet_getsockopt(ppu_thread& ppu, s32 s, s32 level, s32 optname, vm::ptr optval, vm::ptr optlen) -{ - ppu.state += cpu_flag::wait; - - switch (level) - { - case SYS_NET_SOL_SOCKET: - sys_net.warning("sys_net_bnet_getsockopt(s=%d, level=SYS_NET_SOL_SOCKET, optname=%s, optval=*0x%x, optlen=%u)", s, static_cast(optname), optval, optlen); - break; - case SYS_NET_IPPROTO_TCP: - sys_net.warning("sys_net_bnet_getsockopt(s=%d, level=SYS_NET_IPPROTO_TCP, optname=%s, optval=*0x%x, optlen=%u)", s, static_cast(optname), optval, optlen); - break; - case SYS_NET_IPPROTO_IP: - sys_net.warning("sys_net_bnet_getsockopt(s=%d, level=SYS_NET_IPPROTO_IP, optname=%s, optval=*0x%x, optlen=%u)", s, static_cast(optname), optval, optlen); - break; - default: - sys_net.warning("sys_net_bnet_getsockopt(s=%d, level=0x%x, optname=0x%x, optval=*0x%x, optlen=%u)", s, level, optname, optval, optlen); - break; - } - - if (!optval || !optlen) - { - return -SYS_NET_EINVAL; - } - - const u32 len = *optlen; - - if (!len) - { - return -SYS_NET_EINVAL; - } - - const auto sock = idm::check(s, [&, notify = lv2_obj::notify_all_t()](lv2_socket& sock) -> s32 - { - if (len < sizeof(s32)) - { - return -SYS_NET_EINVAL; - } - - const auto& [res, out_val, out_len] = sock.getsockopt(level, optname, *optlen); - - if (res == CELL_OK) - { - std::memcpy(optval.get_ptr(), out_val.ch, out_len); - *optlen = out_len; - } - - return res; - }); - - if (!sock) - { - return -SYS_NET_EBADF; - } - - if (sock.ret < 0) - { - return sys_net_error{sock.ret}; - } - - return CELL_OK; -} - -error_code sys_net_bnet_listen(ppu_thread& ppu, s32 s, s32 backlog) -{ - ppu.state += cpu_flag::wait; - - sys_net.warning("sys_net_bnet_listen(s=%d, backlog=%d)", s, backlog); - - if (backlog <= 0) - { - return -SYS_NET_EINVAL; - } - - const auto sock = idm::check(s, [&, notify = lv2_obj::notify_all_t()](lv2_socket& sock) -> s32 - { - return sock.listen(backlog); - }); - - if (!sock) - { - return -SYS_NET_EBADF; - } - - if (sock.ret < 0) - { - return sys_net_error{sock.ret}; - } - - return CELL_OK; -} - -error_code sys_net_bnet_recvfrom(ppu_thread& ppu, s32 s, vm::ptr buf, u32 len, s32 flags, vm::ptr addr, vm::ptr paddrlen) -{ - ppu.state += cpu_flag::wait; - - sys_net.trace("sys_net_bnet_recvfrom(s=%d, buf=*0x%x, len=%u, flags=0x%x, addr=*0x%x, paddrlen=*0x%x)", s, buf, len, flags, addr, paddrlen); - - // If addr is null, paddrlen must be null as well - if (!buf || !len || addr.operator bool() != paddrlen.operator bool()) - { - return -SYS_NET_EINVAL; - } - - if (flags & ~(SYS_NET_MSG_PEEK | SYS_NET_MSG_DONTWAIT | SYS_NET_MSG_WAITALL | SYS_NET_MSG_USECRYPTO | SYS_NET_MSG_USESIGNATURE)) - { - fmt::throw_exception("sys_net_bnet_recvfrom(s=%d): unknown flags (0x%x)", flags); - } - - s32 result = 0; - sys_net_sockaddr sn_addr{}; - - const auto sock = idm::check(s, [&, notify = lv2_obj::notify_all_t()](lv2_socket& sock) - { - const auto success = sock.recvfrom(flags, len); - - if (success) - { - const auto& [res, vec, res_addr] = *success; - if (res > 0) - { - sn_addr = res_addr; - std::memcpy(buf.get_ptr(), vec.data(), res); - sys_net_dump_data("recvfrom", vec.data(), res, &res_addr); - } - - result = res; - return true; - } - - auto lock = sock.lock(); - - sock.poll_queue(idm::get_unlocked>(ppu.id), lv2_socket::poll_t::read, [&](bs_t events) -> bool - { - if (events & lv2_socket::poll_t::read) - { - const auto success = sock.recvfrom(flags, len, false); - - if (success) - { - const auto& [res, vec, res_addr] = *success; - if (res > 0) - { - sn_addr = res_addr; - std::memcpy(buf.get_ptr(), vec.data(), res); - sys_net_dump_data("recvfrom", vec.data(), res, &res_addr); - } - result = res; - lv2_obj::awake(&ppu); - return true; - } - } - - if (sock.so_rcvtimeo && get_guest_system_time() - ppu.start_time > sock.so_rcvtimeo) - { - result = -SYS_NET_EWOULDBLOCK; - lv2_obj::awake(&ppu); - return true; - } - - sock.set_poll_event(lv2_socket::poll_t::read); - return false; - }); - - lv2_obj::prepare_for_sleep(ppu); - lv2_obj::sleep(ppu); - return false; - }); - - if (!sock) - { - return -SYS_NET_EBADF; - } - - if (!sock.ret) - { - - while (auto state = ppu.state.fetch_sub(cpu_flag::signal)) - { - if (is_stopped(state)) - { - return {}; - } - - if (state & cpu_flag::signal) - { - break; - } - - ppu.state.wait(state); - } - - if (ppu.gpr[3] == static_cast(-SYS_NET_EINTR)) - { - return -SYS_NET_EINTR; - } - } - - static_cast(ppu.test_stopped()); - - if (result == -SYS_NET_EWOULDBLOCK) - { - return not_an_error(result); - } - - if (result >= 0) - { - if (addr) - { - *paddrlen = sizeof(sys_net_sockaddr_in); - *addr = sn_addr; - } - - return not_an_error(result); - } - - return sys_net_error{result}; -} - -error_code sys_net_bnet_recvmsg(ppu_thread& ppu, s32 s, vm::ptr msg, s32 flags) -{ - ppu.state += cpu_flag::wait; - - sys_net.todo("sys_net_bnet_recvmsg(s=%d, msg=*0x%x, flags=0x%x)", s, msg, flags); - return CELL_OK; -} - -error_code sys_net_bnet_sendmsg(ppu_thread& ppu, s32 s, vm::cptr msg, s32 flags) -{ - ppu.state += cpu_flag::wait; - - sys_net.warning("sys_net_bnet_sendmsg(s=%d, msg=*0x%x, flags=0x%x)", s, msg, flags); - - if (flags & ~(SYS_NET_MSG_DONTWAIT | SYS_NET_MSG_WAITALL | SYS_NET_MSG_USECRYPTO | SYS_NET_MSG_USESIGNATURE)) - { - fmt::throw_exception("sys_net_bnet_sendmsg(s=%d): unknown flags (0x%x)", flags); - } - - s32 result{}; - - const auto sock = idm::check(s, [&](lv2_socket& sock) - { - auto netmsg = msg.get_ptr(); - const auto success = sock.sendmsg(flags, *netmsg); - - if (success) - { - result = *success; - - return true; - } - - sock.poll_queue(idm::get_unlocked>(ppu.id), lv2_socket::poll_t::write, [&](bs_t events) -> bool - { - if (events & lv2_socket::poll_t::write) - { - const auto success = sock.sendmsg(flags, *netmsg, false); - - if (success) - { - result = *success; - lv2_obj::awake(&ppu); - return true; - } - } - - sock.set_poll_event(lv2_socket::poll_t::write); - return false; - }); - - lv2_obj::sleep(ppu); - return false; - }); - - if (!sock) - { - return -SYS_NET_EBADF; - } - - if (!sock.ret) - { - while (true) - { - const auto state = ppu.state.fetch_sub(cpu_flag::signal); - if (is_stopped(state) || state & cpu_flag::signal) - { - break; - } - thread_ctrl::wait_on(ppu.state, state); - } - - if (ppu.gpr[3] == static_cast(-SYS_NET_EINTR)) - { - return -SYS_NET_EINTR; - } - } - - if (result >= 0 || result == -SYS_NET_EWOULDBLOCK) - { - return not_an_error(result); - } - - return sys_net_error{result}; -} - -error_code sys_net_bnet_sendto(ppu_thread& ppu, s32 s, vm::cptr buf, u32 len, s32 flags, vm::cptr addr, u32 addrlen) -{ - ppu.state += cpu_flag::wait; - - sys_net.trace("sys_net_bnet_sendto(s=%d, buf=*0x%x, len=%u, flags=0x%x, addr=*0x%x, addrlen=%u)", s, buf, len, flags, addr, addrlen); - - if (flags & ~(SYS_NET_MSG_DONTWAIT | SYS_NET_MSG_WAITALL | SYS_NET_MSG_USECRYPTO | SYS_NET_MSG_USESIGNATURE)) - { - fmt::throw_exception("sys_net_bnet_sendto(s=%d): unknown flags (0x%x)", flags); - } - - if (addr && addrlen < 8) - { - sys_net.error("sys_net_bnet_sendto(s=%d): bad addrlen (%u)", s, addrlen); - return -SYS_NET_EINVAL; - } - - if (addr && addr->sa_family != SYS_NET_AF_INET) - { - sys_net.error("sys_net_bnet_sendto(s=%d): unsupported sa_family (%d)", s, addr->sa_family); - return -SYS_NET_EAFNOSUPPORT; - } - - sys_net_dump_data("sendto", static_cast(buf.get_ptr()), len, addr ? addr.get_ptr() : nullptr); - - const std::optional sn_addr = addr ? std::optional(*addr) : std::nullopt; - const std::vector buf_copy(vm::_ptr(buf.addr()), vm::_ptr(buf.addr()) + len); - s32 result{}; - - const auto sock = idm::check(s, [&, notify = lv2_obj::notify_all_t()](lv2_socket& sock) - { - auto success = sock.sendto(flags, buf_copy, sn_addr); - - if (success) - { - result = *success; - return true; - } - - auto lock = sock.lock(); - - // Enable write event - sock.poll_queue(idm::get_unlocked>(ppu.id), lv2_socket::poll_t::write, [&](bs_t events) -> bool - { - if (events & lv2_socket::poll_t::write) - { - auto success = sock.sendto(flags, buf_copy, sn_addr, false); - if (success) - { - result = *success; - lv2_obj::awake(&ppu); - return true; - } - } - - if (sock.so_sendtimeo && get_guest_system_time() - ppu.start_time > sock.so_sendtimeo) - { - result = -SYS_NET_EWOULDBLOCK; - lv2_obj::awake(&ppu); - return true; - } - - sock.set_poll_event(lv2_socket::poll_t::write); - return false; - }); - - lv2_obj::prepare_for_sleep(ppu); - lv2_obj::sleep(ppu); - return false; - }); - - if (!sock) - { - return -SYS_NET_EBADF; - } - - if (!sock.ret) - { - while (true) - { - const auto state = ppu.state.fetch_sub(cpu_flag::signal); - if (is_stopped(state) || state & cpu_flag::signal) - { - break; - } - ppu.state.wait(state); - } - - if (ppu.gpr[3] == static_cast(-SYS_NET_EINTR)) - { - return -SYS_NET_EINTR; - } - } - - if (result >= 0 || result == -SYS_NET_EWOULDBLOCK) - { - return not_an_error(result); - } - - return sys_net_error{result}; -} - -error_code sys_net_bnet_setsockopt(ppu_thread& ppu, s32 s, s32 level, s32 optname, vm::cptr optval, u32 optlen) -{ - ppu.state += cpu_flag::wait; - - switch (level) - { - case SYS_NET_SOL_SOCKET: - sys_net.warning("sys_net_bnet_setsockopt(s=%d, level=SYS_NET_SOL_SOCKET, optname=%s, optval=*0x%x, optlen=%u)", s, static_cast(optname), optval, optlen); - break; - case SYS_NET_IPPROTO_TCP: - sys_net.warning("sys_net_bnet_setsockopt(s=%d, level=SYS_NET_IPPROTO_TCP, optname=%s, optval=*0x%x, optlen=%u)", s, static_cast(optname), optval, optlen); - break; - case SYS_NET_IPPROTO_IP: - sys_net.warning("sys_net_bnet_setsockopt(s=%d, level=SYS_NET_IPPROTO_IP, optname=%s, optval=*0x%x, optlen=%u)", s, static_cast(optname), optval, optlen); - break; - default: - sys_net.warning("sys_net_bnet_setsockopt(s=%d, level=0x%x, optname=0x%x, optval=*0x%x, optlen=%u)", s, level, optname, optval, optlen); - break; - } - - switch (optlen) - { - case 1: - sys_net.warning("optval: 0x%02X", *static_cast(optval.get_ptr())); - break; - case 2: - sys_net.warning("optval: 0x%04X", *static_cast*>(optval.get_ptr())); - break; - case 4: - sys_net.warning("optval: 0x%08X", *static_cast*>(optval.get_ptr())); - break; - case 8: - sys_net.warning("optval: 0x%016X", *static_cast*>(optval.get_ptr())); - break; - } - - if (optlen < sizeof(s32)) - { - return -SYS_NET_EINVAL; - } - - std::vector optval_copy(vm::_ptr(optval.addr()), vm::_ptr(optval.addr() + optlen)); - - const auto sock = idm::check(s, [&, notify = lv2_obj::notify_all_t()](lv2_socket& sock) -> s32 - { - return sock.setsockopt(level, optname, optval_copy); - }); - - if (!sock) - { - return -SYS_NET_EBADF; - } - - if (sock.ret < 0) - { - return sys_net_error{sock.ret}; - } - - return not_an_error(sock.ret); -} - -error_code sys_net_bnet_shutdown(ppu_thread& ppu, s32 s, s32 how) -{ - ppu.state += cpu_flag::wait; - - sys_net.warning("sys_net_bnet_shutdown(s=%d, how=%d)", s, how); - - if (how < 0 || how > 2) - { - return -SYS_NET_EINVAL; - } - - const auto sock = idm::check(s, [&, notify = lv2_obj::notify_all_t()](lv2_socket& sock) -> s32 - { - return sock.shutdown(how); - }); - - if (!sock) - { - return -SYS_NET_EBADF; - } - - if (sock.ret < 0) - { - return sys_net_error{sock.ret}; - } - - return CELL_OK; -} - -error_code sys_net_bnet_socket(ppu_thread& ppu, lv2_socket_family family, lv2_socket_type type, lv2_ip_protocol protocol) -{ - ppu.state += cpu_flag::wait; - - sys_net.warning("sys_net_bnet_socket(family=%s, type=%s, protocol=%s)", family, type, protocol); - - if (family != SYS_NET_AF_INET) - { - sys_net.error("sys_net_bnet_socket(): unknown family (%d)", family); - } - - if (type != SYS_NET_SOCK_STREAM && type != SYS_NET_SOCK_DGRAM && type != SYS_NET_SOCK_RAW && type != SYS_NET_SOCK_DGRAM_P2P && type != SYS_NET_SOCK_STREAM_P2P) - { - sys_net.error("sys_net_bnet_socket(): unsupported type (%d)", type); - return -SYS_NET_EPROTONOSUPPORT; - } - - shared_ptr sock_lv2; - - switch (type) - { - case SYS_NET_SOCK_STREAM: - case SYS_NET_SOCK_DGRAM: - { - auto lv2_native = make_shared(family, type, protocol); - if (s32 result = lv2_native->create_socket(); result < 0) - { - return sys_net_error{result}; - } - - sock_lv2 = std::move(lv2_native); - break; - } - case SYS_NET_SOCK_RAW: sock_lv2 = make_shared(family, type, protocol); break; - case SYS_NET_SOCK_DGRAM_P2P: sock_lv2 = make_shared(family, type, protocol); break; - case SYS_NET_SOCK_STREAM_P2P: sock_lv2 = make_shared(family, type, protocol); break; - } - - const s32 s = idm::import_existing(sock_lv2); - - // Can't allocate more than 1000 sockets - if (s == id_manager::id_traits::invalid) - { - return -SYS_NET_EMFILE; - } - - sock_lv2->set_lv2_id(s); - - return not_an_error(s); -} - -error_code sys_net_bnet_close(ppu_thread& ppu, s32 s) -{ - ppu.state += cpu_flag::wait; - - sys_net.warning("sys_net_bnet_close(s=%d)", s); - - auto sock = idm::withdraw(s); - - if (!sock) - { - return -SYS_NET_EBADF; - } - - if (sock->get_queue_size()) - { - sock->abort_socket(0); - } - - sock->close(); - - { - // Ensures the socket has no lingering copy from the network thread - std::lock_guard nw_lock(g_fxo->get().mutex_thread_loop); - sock.reset(); - } - - return CELL_OK; -} - -error_code sys_net_bnet_poll(ppu_thread& ppu, vm::ptr fds, s32 nfds, s32 ms) -{ - ppu.state += cpu_flag::wait; - - sys_net.trace("sys_net_bnet_poll(fds=*0x%x, nfds=%d, ms=%d)", fds, nfds, ms); - - if (nfds <= 0) - { - return not_an_error(0); - } - - atomic_t signaled{0}; - - u64 timeout = ms < 0 ? 0 : ms * 1000ull; - - std::vector fds_buf; - - { - fds_buf.assign(fds.get_ptr(), fds.get_ptr() + nfds); - - lv2_obj::prepare_for_sleep(ppu); - - std::unique_lock nw_lock(g_fxo->get().mutex_thread_loop); - std::shared_lock lock(id_manager::g_mutex); - - std::vector<::pollfd> _fds(nfds); -#ifdef _WIN32 - std::vector connecting(nfds); -#endif - - for (s32 i = 0; i < nfds; i++) - { - _fds[i].fd = -1; - fds_buf[i].revents = 0; - - if (fds_buf[i].fd < 0) - { - continue; - } - - if (auto sock = idm::check_unlocked(fds_buf[i].fd)) - { - signaled += sock->poll(fds_buf[i], _fds[i]); -#ifdef _WIN32 - connecting[i] = sock->is_connecting(); -#endif - } - else - { - fds_buf[i].revents |= SYS_NET_POLLNVAL; - signaled++; - } - } - -#ifdef _WIN32 - windows_poll(_fds, nfds, 0, connecting); -#else - ::poll(_fds.data(), nfds, 0); -#endif - for (s32 i = 0; i < nfds; i++) - { - if (_fds[i].revents & (POLLIN | POLLHUP)) - fds_buf[i].revents |= SYS_NET_POLLIN; - if (_fds[i].revents & POLLOUT) - fds_buf[i].revents |= SYS_NET_POLLOUT; - if (_fds[i].revents & POLLERR) - fds_buf[i].revents |= SYS_NET_POLLERR; - - if (fds_buf[i].revents) - { - signaled++; - } - } - - if (ms == 0 || signaled) - { - lock.unlock(); - nw_lock.unlock(); - std::memcpy(fds.get_ptr(), fds_buf.data(), nfds * sizeof(sys_net_pollfd)); - return not_an_error(signaled); - } - - for (s32 i = 0; i < nfds; i++) - { - if (fds_buf[i].fd < 0) - { - continue; - } - - if (auto sock = idm::check_unlocked(fds_buf[i].fd)) - { - auto lock = sock->lock(); - -#ifdef _WIN32 - sock->set_connecting(connecting[i]); -#endif - - bs_t selected = +lv2_socket::poll_t::error; - - if (fds_buf[i].events & SYS_NET_POLLIN) - selected += lv2_socket::poll_t::read; - if (fds_buf[i].events & SYS_NET_POLLOUT) - selected += lv2_socket::poll_t::write; - // if (fds_buf[i].events & SYS_NET_POLLPRI) // Unimplemented - // selected += lv2_socket::poll::error; - - sock->poll_queue(idm::get_unlocked>(ppu.id), selected, [sock, selected, &fds_buf, i, &signaled, &ppu](bs_t events) - { - if (events & selected) - { - if (events & selected & lv2_socket::poll_t::read) - fds_buf[i].revents |= SYS_NET_POLLIN; - if (events & selected & lv2_socket::poll_t::write) - fds_buf[i].revents |= SYS_NET_POLLOUT; - if (events & selected & lv2_socket::poll_t::error) - fds_buf[i].revents |= SYS_NET_POLLERR; - - signaled++; - sock->queue_wake(&ppu); - return true; - } - - sock->set_poll_event(selected); - return false; - }); - } - } - - lv2_obj::sleep(ppu, timeout); - } - - bool has_timedout = false; - - while (auto state = ppu.state.fetch_sub(cpu_flag::signal)) - { - if (is_stopped(state)) - { - return {}; - } - - if (state & cpu_flag::signal) - { - break; - } - - if (timeout) - { - if (lv2_obj::wait_timeout(timeout, &ppu)) - { - // Wait for rescheduling - if (ppu.check_state()) - { - return {}; - } - - has_timedout = network_clear_queue(ppu); - clear_ppu_to_awake(ppu); - ppu.state -= cpu_flag::signal; - break; - } - } - else - { - ppu.state.wait(state); - } - } - - if (!has_timedout && !signaled) - { - return -SYS_NET_EINTR; - } - - std::memcpy(fds.get_ptr(), fds_buf.data(), nfds * sizeof(fds[0])); - - return not_an_error(signaled); -} - -error_code sys_net_bnet_select(ppu_thread& ppu, s32 nfds, vm::ptr readfds, vm::ptr writefds, vm::ptr exceptfds, vm::ptr _timeout) -{ - ppu.state += cpu_flag::wait; - - sys_net.trace("sys_net_bnet_select(nfds=%d, readfds=*0x%x, writefds=*0x%x, exceptfds=*0x%x, timeout=*0x%x(%d:%d))", nfds, readfds, writefds, exceptfds, _timeout, _timeout ? _timeout->tv_sec.value() : 0, _timeout ? _timeout->tv_usec.value() : 0); - - atomic_t signaled{0}; - - if (exceptfds) - { - struct log_t - { - atomic_t logged = false; - }; - - if (!g_fxo->get().logged.exchange(true)) - { - sys_net.error("sys_net_bnet_select(): exceptfds not implemented"); - } - } - - sys_net_fd_set rread{}, _readfds{}; - sys_net_fd_set rwrite{}, _writefds{}; - sys_net_fd_set rexcept{}, _exceptfds{}; - u64 timeout = !_timeout ? 0 : _timeout->tv_sec * 1000000ull + _timeout->tv_usec; - - if (nfds > 0 && nfds <= 1024) - { - if (readfds) - _readfds = *readfds; - if (writefds) - _writefds = *writefds; - if (exceptfds) - _exceptfds = *exceptfds; - - std::lock_guard nw_lock(g_fxo->get().mutex_thread_loop); - reader_lock lock(id_manager::g_mutex); - - std::vector<::pollfd> _fds(nfds); -#ifdef _WIN32 - std::vector connecting(nfds); -#endif - - for (s32 i = 0; i < nfds; i++) - { - _fds[i].fd = -1; - bs_t selected{}; - - if (readfds && _readfds.bit(i)) - selected += lv2_socket::poll_t::read; - if (writefds && _writefds.bit(i)) - selected += lv2_socket::poll_t::write; - // if (exceptfds && _exceptfds.bit(i)) - // selected += lv2_socket::poll::error; - - if (selected) - { - selected += lv2_socket::poll_t::error; - } - else - { - continue; - } - - if (auto sock = idm::check_unlocked((lv2_socket::id_base & -1024) + i)) - { - auto [read_set, write_set, except_set] = sock->select(selected, _fds[i]); - - if (read_set || write_set || except_set) - { - signaled++; - } - - if (read_set) - { - rread.set(i); - } - - if (write_set) - { - rwrite.set(i); - } - - if (except_set) - { - rexcept.set(i); - } - -#ifdef _WIN32 - connecting[i] = sock->is_connecting(); -#endif - } - else - { - return -SYS_NET_EBADF; - } - } - -#ifdef _WIN32 - windows_poll(_fds, nfds, 0, connecting); -#else - ::poll(_fds.data(), nfds, 0); -#endif - for (s32 i = 0; i < nfds; i++) - { - bool sig = false; - if (_fds[i].revents & (POLLIN | POLLHUP | POLLERR)) - sig = true, rread.set(i); - if (_fds[i].revents & (POLLOUT | POLLERR)) - sig = true, rwrite.set(i); - - if (sig) - { - signaled++; - } - } - - if ((_timeout && !timeout) || signaled) - { - if (readfds) - *readfds = rread; - if (writefds) - *writefds = rwrite; - if (exceptfds) - *exceptfds = rexcept; - return not_an_error(signaled); - } - - for (s32 i = 0; i < nfds; i++) - { - bs_t selected{}; - - if (readfds && _readfds.bit(i)) - selected += lv2_socket::poll_t::read; - if (writefds && _writefds.bit(i)) - selected += lv2_socket::poll_t::write; - // if (exceptfds && _exceptfds.bit(i)) - // selected += lv2_socket::poll_t::error; - - if (selected) - { - selected += lv2_socket::poll_t::error; - } - else - { - continue; - } - - if (auto sock = idm::check_unlocked((lv2_socket::id_base & -1024) + i)) - { - auto lock = sock->lock(); -#ifdef _WIN32 - sock->set_connecting(connecting[i]); -#endif - - sock->poll_queue(idm::get_unlocked>(ppu.id), selected, [sock, selected, i, &rread, &rwrite, &rexcept, &signaled, &ppu](bs_t events) - { - if (events & selected) - { - if (selected & lv2_socket::poll_t::read && events & (lv2_socket::poll_t::read + lv2_socket::poll_t::error)) - rread.set(i); - if (selected & lv2_socket::poll_t::write && events & (lv2_socket::poll_t::write + lv2_socket::poll_t::error)) - rwrite.set(i); - // if (events & (selected & lv2_socket::poll::error)) - // rexcept.set(i); - - signaled++; - sock->queue_wake(&ppu); - return true; - } - - sock->set_poll_event(selected); - return false; - }); - } - else - { - return -SYS_NET_EBADF; - } - } - - lv2_obj::sleep(ppu, timeout); - } - else - { - return -SYS_NET_EINVAL; - } - - bool has_timedout = false; - - while (auto state = ppu.state.fetch_sub(cpu_flag::signal)) - { - if (is_stopped(state)) - { - return {}; - } - - if (state & cpu_flag::signal) - { - break; - } - - if (timeout) - { - if (lv2_obj::wait_timeout(timeout, &ppu)) - { - // Wait for rescheduling - if (ppu.check_state()) - { - return {}; - } - - has_timedout = network_clear_queue(ppu); - clear_ppu_to_awake(ppu); - ppu.state -= cpu_flag::signal; - break; - } - } - else - { - ppu.state.wait(state); - } - } - - if (!has_timedout && !signaled) - { - return -SYS_NET_EINTR; - } - - if (readfds) - *readfds = rread; - if (writefds) - *writefds = rwrite; - if (exceptfds) - *exceptfds = rexcept; - - return not_an_error(signaled); -} - -error_code _sys_net_open_dump(ppu_thread& ppu, s32 len, s32 flags) -{ - ppu.state += cpu_flag::wait; - - sys_net.todo("_sys_net_open_dump(len=%d, flags=0x%x)", len, flags); - return CELL_OK; -} - -error_code _sys_net_read_dump(ppu_thread& ppu, s32 id, vm::ptr buf, s32 len, vm::ptr pflags) -{ - ppu.state += cpu_flag::wait; - - sys_net.todo("_sys_net_read_dump(id=0x%x, buf=*0x%x, len=%d, pflags=*0x%x)", id, buf, len, pflags); - return CELL_OK; -} - -error_code _sys_net_close_dump(ppu_thread& ppu, s32 id, vm::ptr pflags) -{ - ppu.state += cpu_flag::wait; - - sys_net.todo("_sys_net_close_dump(id=0x%x, pflags=*0x%x)", id, pflags); - return CELL_OK; -} - -error_code _sys_net_write_dump(ppu_thread& ppu, s32 id, vm::cptr buf, s32 len, u32 unknown) -{ - ppu.state += cpu_flag::wait; - - sys_net.todo("_sys_net_write_dump(id=0x%x, buf=*0x%x, len=%d, unk=0x%x)", id, buf, len, unknown); - return CELL_OK; -} - -error_code lv2_socket::abort_socket(s32 flags) -{ - decltype(queue) qcopy; - { - std::lock_guard lock(mutex); - - if (queue.empty()) - { - if (flags & SYS_NET_ABORT_STRICT_CHECK) - { - // Strict error checking: ENOENT if nothing happened - return -SYS_NET_ENOENT; - } - - // TODO: Abort the subsequent function called on this socket (need to investigate correct behaviour) - return CELL_OK; - } - - qcopy = std::move(queue); - queue = {}; - events.store({}); - } - - for (auto& [ppu, _] : qcopy) - { - if (!ppu) - continue; - - // Avoid possible double signaling - network_clear_queue(*ppu); - clear_ppu_to_awake(*ppu); - - sys_net.warning("lv2_socket::abort_socket(): waking up \"%s\": (func: %s, r3=0x%x, r4=0x%x, r5=0x%x, r6=0x%x)", ppu->get_name(), ppu->current_function, ppu->gpr[3], ppu->gpr[4], ppu->gpr[5], ppu->gpr[6]); - ppu->gpr[3] = static_cast(-SYS_NET_EINTR); - lv2_obj::append(ppu.get()); - } - - const u32 num_waiters = ::size32(qcopy); - if (num_waiters && (type == SYS_NET_SOCK_STREAM || type == SYS_NET_SOCK_DGRAM)) - { - auto& nc = g_fxo->get(); - const u32 prev_value = nc.num_polls.fetch_sub(num_waiters); - ensure(prev_value >= num_waiters); - } - - lv2_obj::awake_all(); - return CELL_OK; -} - -error_code sys_net_abort(ppu_thread& ppu, s32 type, u64 arg, s32 flags) -{ - ppu.state += cpu_flag::wait; - - sys_net.warning("sys_net_abort(type=%d, arg=0x%x, flags=0x%x)", type, arg, flags); - - enum abort_type : s32 - { - _socket, - resolver, - type_2, // ?? - type_3, // ?? - all, - }; - - switch (type) - { - case _socket: - { - std::lock_guard nw_lock(g_fxo->get().mutex_thread_loop); - - const auto sock = idm::get_unlocked(static_cast(arg)); - - if (!sock) - { - return -SYS_NET_EBADF; - } - - return sock->abort_socket(flags); - } - case all: - { - std::vector sockets; - - idm::select([&](u32 id, lv2_socket&) - { - sockets.emplace_back(id); - }); - - s32 failed = 0; - - for (u32 id : sockets) - { - const auto sock = idm::withdraw(id); - - if (!sock) - { - failed++; - continue; - } - - if (sock->get_queue_size()) - sys_net.error("ABORT 4"); - - sock->close(); - - sys_net.success("lv2_socket::handle_abort(): Closed socket %d", id); - } - - // Ensures the socket has no lingering copy from the network thread - g_fxo->get().mutex_thread_loop.lock_unlock(); - - return not_an_error(::narrow(sockets.size()) - failed); - } - case resolver: - case type_2: - case type_3: - { - break; - } - default: return -SYS_NET_EINVAL; - } - - return CELL_OK; -} - -struct net_infoctl_cmd_9_t -{ - be_t zero; - vm::bptr server_name; - // More (TODO) -}; - -error_code sys_net_infoctl(ppu_thread& ppu, s32 cmd, vm::ptr arg) -{ - ppu.state += cpu_flag::wait; - - sys_net.todo("sys_net_infoctl(cmd=%d, arg=*0x%x)", cmd, arg); - - // TODO - switch (cmd) - { - case 9: - { - constexpr auto nameserver = "nameserver \0"sv; - - char buffer[nameserver.size() + 80]{}; - std::memcpy(buffer, nameserver.data(), nameserver.size()); - - auto& nph = g_fxo->get>(); - const auto dns_str = np::ip_to_string(nph.get_dns_ip()); - std::memcpy(buffer + nameserver.size() - 1, dns_str.data(), dns_str.size()); - - std::string_view name{buffer}; - vm::static_ptr_cast(arg)->zero = 0; - std::memcpy(vm::static_ptr_cast(arg)->server_name.get_ptr(), name.data(), name.size()); - break; - } - default: break; - } - - return CELL_OK; -} - -error_code sys_net_control(ppu_thread& ppu, u32 arg1, s32 arg2, vm::ptr arg3, s32 arg4) -{ - ppu.state += cpu_flag::wait; - - sys_net.todo("sys_net_control(0x%x, %d, *0x%x, %d)", arg1, arg2, arg3, arg4); - return CELL_OK; -} - -error_code sys_net_bnet_ioctl(ppu_thread& ppu, s32 arg1, u32 arg2, u32 arg3) -{ - ppu.state += cpu_flag::wait; - - sys_net.todo("sys_net_bnet_ioctl(%d, 0x%x, 0x%x)", arg1, arg2, arg3); - return CELL_OK; -} - -error_code sys_net_bnet_sysctl(ppu_thread& ppu, u32 arg1, u32 arg2, u32 arg3, vm::ptr arg4, u32 arg5, u32 arg6) -{ - ppu.state += cpu_flag::wait; - - sys_net.todo("sys_net_bnet_sysctl(0x%x, 0x%x, 0x%x, *0x%x, 0x%x, 0x%x)", arg1, arg2, arg3, arg4, arg5, arg6); - return CELL_OK; -} - -error_code sys_net_eurus_post_command(ppu_thread& ppu, s32 arg1, u32 arg2, u32 arg3) -{ - ppu.state += cpu_flag::wait; - - sys_net.todo("sys_net_eurus_post_command(%d, 0x%x, 0x%x)", arg1, arg2, arg3); - return CELL_OK; -} diff --git a/rpcs3/Emu/Cell/lv2/sys_net.h b/rpcs3/Emu/Cell/lv2/sys_net.h deleted file mode 100644 index f0d5dd99a..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_net.h +++ /dev/null @@ -1,368 +0,0 @@ -#pragma once - -#include "util/bit_set.h" -#include "util/mutex.h" - -#include "Emu/Memory/vm_ptr.h" -#include "Emu/Cell/ErrorCodes.h" - -#include -#include -#include -#include - -// Error codes -enum sys_net_error : s32 -{ - SYS_NET_ENOENT = 2, - SYS_NET_EINTR = 4, - SYS_NET_EBADF = 9, - SYS_NET_ENOMEM = 12, - SYS_NET_EACCES = 13, - SYS_NET_EFAULT = 14, - SYS_NET_EBUSY = 16, - SYS_NET_EINVAL = 22, - SYS_NET_EMFILE = 24, - SYS_NET_ENOSPC = 28, - SYS_NET_EPIPE = 32, - SYS_NET_EAGAIN = 35, - SYS_NET_EWOULDBLOCK = SYS_NET_EAGAIN, - SYS_NET_EINPROGRESS = 36, - SYS_NET_EALREADY = 37, - SYS_NET_EDESTADDRREQ = 39, - SYS_NET_EMSGSIZE = 40, - SYS_NET_EPROTOTYPE = 41, - SYS_NET_ENOPROTOOPT = 42, - SYS_NET_EPROTONOSUPPORT = 43, - SYS_NET_EOPNOTSUPP = 45, - SYS_NET_EPFNOSUPPORT = 46, - SYS_NET_EAFNOSUPPORT = 47, - SYS_NET_EADDRINUSE = 48, - SYS_NET_EADDRNOTAVAIL = 49, - SYS_NET_ENETDOWN = 50, - SYS_NET_ENETUNREACH = 51, - SYS_NET_ECONNABORTED = 53, - SYS_NET_ECONNRESET = 54, - SYS_NET_ENOBUFS = 55, - SYS_NET_EISCONN = 56, - SYS_NET_ENOTCONN = 57, - SYS_NET_ESHUTDOWN = 58, - SYS_NET_ETOOMANYREFS = 59, - SYS_NET_ETIMEDOUT = 60, - SYS_NET_ECONNREFUSED = 61, - SYS_NET_EHOSTDOWN = 64, - SYS_NET_EHOSTUNREACH = 65, -}; - -static constexpr sys_net_error operator-(sys_net_error v) -{ - return sys_net_error{-+v}; -} - -// Socket types (prefixed with SYS_NET_) -enum lv2_socket_type : s32 -{ - SYS_NET_SOCK_STREAM = 1, - SYS_NET_SOCK_DGRAM = 2, - SYS_NET_SOCK_RAW = 3, - SYS_NET_SOCK_DGRAM_P2P = 6, - SYS_NET_SOCK_STREAM_P2P = 10, -}; - -// Socket options (prefixed with SYS_NET_) -enum lv2_socket_option : s32 -{ - SYS_NET_SO_SNDBUF = 0x1001, - SYS_NET_SO_RCVBUF = 0x1002, - SYS_NET_SO_SNDLOWAT = 0x1003, - SYS_NET_SO_RCVLOWAT = 0x1004, - SYS_NET_SO_SNDTIMEO = 0x1005, - SYS_NET_SO_RCVTIMEO = 0x1006, - SYS_NET_SO_ERROR = 0x1007, - SYS_NET_SO_TYPE = 0x1008, - SYS_NET_SO_NBIO = 0x1100, // Non-blocking IO - SYS_NET_SO_TPPOLICY = 0x1101, - - SYS_NET_SO_REUSEADDR = 0x0004, - SYS_NET_SO_KEEPALIVE = 0x0008, - SYS_NET_SO_BROADCAST = 0x0020, - SYS_NET_SO_LINGER = 0x0080, - SYS_NET_SO_OOBINLINE = 0x0100, - SYS_NET_SO_REUSEPORT = 0x0200, - SYS_NET_SO_ONESBCAST = 0x0800, - SYS_NET_SO_USECRYPTO = 0x1000, - SYS_NET_SO_USESIGNATURE = 0x2000, - - SYS_NET_SOL_SOCKET = 0xffff, -}; - -// IP options (prefixed with SYS_NET_) -enum lv2_ip_option : s32 -{ - SYS_NET_IP_HDRINCL = 2, - SYS_NET_IP_TOS = 3, - SYS_NET_IP_TTL = 4, - SYS_NET_IP_MULTICAST_IF = 9, - SYS_NET_IP_MULTICAST_TTL = 10, - SYS_NET_IP_MULTICAST_LOOP = 11, - SYS_NET_IP_ADD_MEMBERSHIP = 12, - SYS_NET_IP_DROP_MEMBERSHIP = 13, - SYS_NET_IP_TTLCHK = 23, - SYS_NET_IP_MAXTTL = 24, - SYS_NET_IP_DONTFRAG = 26 -}; - -// Family (prefixed with SYS_NET_) -enum lv2_socket_family : s32 -{ - SYS_NET_AF_UNSPEC = 0, - SYS_NET_AF_LOCAL = 1, - SYS_NET_AF_UNIX = SYS_NET_AF_LOCAL, - SYS_NET_AF_INET = 2, - SYS_NET_AF_INET6 = 24, -}; - -// Flags (prefixed with SYS_NET_) -enum -{ - SYS_NET_MSG_OOB = 0x1, - SYS_NET_MSG_PEEK = 0x2, - SYS_NET_MSG_DONTROUTE = 0x4, - SYS_NET_MSG_EOR = 0x8, - SYS_NET_MSG_TRUNC = 0x10, - SYS_NET_MSG_CTRUNC = 0x20, - SYS_NET_MSG_WAITALL = 0x40, - SYS_NET_MSG_DONTWAIT = 0x80, - SYS_NET_MSG_BCAST = 0x100, - SYS_NET_MSG_MCAST = 0x200, - SYS_NET_MSG_USECRYPTO = 0x400, - SYS_NET_MSG_USESIGNATURE = 0x800, -}; - -// Shutdown types (prefixed with SYS_NET_) -enum -{ - SYS_NET_SHUT_RD = 0, - SYS_NET_SHUT_WR = 1, - SYS_NET_SHUT_RDWR = 2, -}; - -// TCP options (prefixed with SYS_NET_) -enum lv2_tcp_option : s32 -{ - SYS_NET_TCP_NODELAY = 1, - SYS_NET_TCP_MAXSEG = 2, - SYS_NET_TCP_MSS_TO_ADVERTISE = 3, -}; - -// IP protocols (prefixed with SYS_NET_) -enum lv2_ip_protocol : s32 -{ - SYS_NET_IPPROTO_IP = 0, - SYS_NET_IPPROTO_ICMP = 1, - SYS_NET_IPPROTO_IGMP = 2, - SYS_NET_IPPROTO_TCP = 6, - SYS_NET_IPPROTO_UDP = 17, - SYS_NET_IPPROTO_ICMPV6 = 58, -}; - -// Poll events (prefixed with SYS_NET_) -enum -{ - SYS_NET_POLLIN = 0x0001, - SYS_NET_POLLPRI = 0x0002, - SYS_NET_POLLOUT = 0x0004, - SYS_NET_POLLERR = 0x0008, /* revent only */ - SYS_NET_POLLHUP = 0x0010, /* revent only */ - SYS_NET_POLLNVAL = 0x0020, /* revent only */ - SYS_NET_POLLRDNORM = 0x0040, - SYS_NET_POLLWRNORM = SYS_NET_POLLOUT, - SYS_NET_POLLRDBAND = 0x0080, - SYS_NET_POLLWRBAND = 0x0100, -}; - -enum lv2_socket_abort_flags : s32 -{ - SYS_NET_ABORT_STRICT_CHECK = 1, -}; - -// in_addr_t type prefixed with sys_net_ -using sys_net_in_addr_t = u32; - -// in_port_t type prefixed with sys_net_ -using sys_net_in_port_t = u16; - -// sa_family_t type prefixed with sys_net_ -using sys_net_sa_family_t = u8; - -// socklen_t type prefixed with sys_net_ -using sys_net_socklen_t = u32; - -// fd_set prefixed with sys_net_ -struct sys_net_fd_set -{ - be_t fds_bits[32]; - - u32 bit(s32 s) const - { - return (fds_bits[(s >> 5) & 31] >> (s & 31)) & 1u; - } - - void set(s32 s) - { - fds_bits[(s >> 5) & 31] |= (1u << (s & 31)); - } -}; - -// hostent prefixed with sys_net_ -struct sys_net_hostent -{ - vm::bptr h_name; - vm::bpptr h_aliases; - be_t h_addrtype; - be_t h_length; - vm::bpptr h_addr_list; -}; - -// in_addr prefixed with sys_net_ -struct sys_net_in_addr -{ - be_t _s_addr; -}; - -// iovec prefixed with sys_net_ -struct sys_net_iovec -{ - be_t zero1; - vm::bptr iov_base; - be_t zero2; - be_t iov_len; -}; - -// ip_mreq prefixed with sys_net_ -struct sys_net_ip_mreq -{ - be_t imr_multiaddr; - be_t imr_interface; -}; - -// msghdr prefixed with sys_net_ -struct sys_net_msghdr -{ - be_t zero1; - vm::bptr msg_name; - be_t msg_namelen; - be_t pad1; - be_t zero2; - vm::bptr msg_iov; - be_t msg_iovlen; - be_t pad2; - be_t zero3; - vm::bptr msg_control; - be_t msg_controllen; - be_t msg_flags; -}; - -// pollfd prefixed with sys_net_ -struct sys_net_pollfd -{ - be_t fd; - be_t events; - be_t revents; -}; - -// sockaddr prefixed with sys_net_ -struct sys_net_sockaddr -{ - ENABLE_BITWISE_SERIALIZATION; - - u8 sa_len; - u8 sa_family; - char sa_data[14]; -}; - -// sockaddr_dl prefixed with sys_net_ -struct sys_net_sockaddr_dl -{ - ENABLE_BITWISE_SERIALIZATION; - - u8 sdl_len; - u8 sdl_family; - be_t sdl_index; - u8 sdl_type; - u8 sdl_nlen; - u8 sdl_alen; - u8 sdl_slen; - char sdl_data[12]; -}; - -// sockaddr_in prefixed with sys_net_ -struct sys_net_sockaddr_in -{ - ENABLE_BITWISE_SERIALIZATION; - - u8 sin_len; - u8 sin_family; - be_t sin_port; - be_t sin_addr; - be_t sin_zero; -}; - -// sockaddr_in_p2p prefixed with sys_net_ -struct sys_net_sockaddr_in_p2p -{ - ENABLE_BITWISE_SERIALIZATION; - - u8 sin_len; - u8 sin_family; - be_t sin_port; - be_t sin_addr; - be_t sin_vport; - char sin_zero[6]; -}; - -// timeval prefixed with sys_net_ -struct sys_net_timeval -{ - be_t tv_sec; - be_t tv_usec; -}; - -// linger prefixed with sys_net_ -struct sys_net_linger -{ - be_t l_onoff; - be_t l_linger; -}; - -class ppu_thread; - -// Syscalls - -error_code sys_net_bnet_accept(ppu_thread&, s32 s, vm::ptr addr, vm::ptr paddrlen); -error_code sys_net_bnet_bind(ppu_thread&, s32 s, vm::cptr addr, u32 addrlen); -error_code sys_net_bnet_connect(ppu_thread&, s32 s, vm::ptr addr, u32 addrlen); -error_code sys_net_bnet_getpeername(ppu_thread&, s32 s, vm::ptr addr, vm::ptr paddrlen); -error_code sys_net_bnet_getsockname(ppu_thread&, s32 s, vm::ptr addr, vm::ptr paddrlen); -error_code sys_net_bnet_getsockopt(ppu_thread&, s32 s, s32 level, s32 optname, vm::ptr optval, vm::ptr optlen); -error_code sys_net_bnet_listen(ppu_thread&, s32 s, s32 backlog); -error_code sys_net_bnet_recvfrom(ppu_thread&, s32 s, vm::ptr buf, u32 len, s32 flags, vm::ptr addr, vm::ptr paddrlen); -error_code sys_net_bnet_recvmsg(ppu_thread&, s32 s, vm::ptr msg, s32 flags); -error_code sys_net_bnet_sendmsg(ppu_thread&, s32 s, vm::cptr msg, s32 flags); -error_code sys_net_bnet_sendto(ppu_thread&, s32 s, vm::cptr buf, u32 len, s32 flags, vm::cptr addr, u32 addrlen); -error_code sys_net_bnet_setsockopt(ppu_thread&, s32 s, s32 level, s32 optname, vm::cptr optval, u32 optlen); -error_code sys_net_bnet_shutdown(ppu_thread&, s32 s, s32 how); -error_code sys_net_bnet_socket(ppu_thread&, lv2_socket_family family, lv2_socket_type type, lv2_ip_protocol protocol); -error_code sys_net_bnet_close(ppu_thread&, s32 s); -error_code sys_net_bnet_poll(ppu_thread&, vm::ptr fds, s32 nfds, s32 ms); -error_code sys_net_bnet_select(ppu_thread&, s32 nfds, vm::ptr readfds, vm::ptr writefds, vm::ptr exceptfds, vm::ptr timeout); -error_code _sys_net_open_dump(ppu_thread&, s32 len, s32 flags); -error_code _sys_net_read_dump(ppu_thread&, s32 id, vm::ptr buf, s32 len, vm::ptr pflags); -error_code _sys_net_close_dump(ppu_thread&, s32 id, vm::ptr pflags); -error_code _sys_net_write_dump(ppu_thread&, s32 id, vm::cptr buf, s32 len, u32 unknown); -error_code sys_net_abort(ppu_thread&, s32 type, u64 arg, s32 flags); -error_code sys_net_infoctl(ppu_thread&, s32 cmd, vm::ptr arg); -error_code sys_net_control(ppu_thread&, u32 arg1, s32 arg2, vm::ptr arg3, s32 arg4); -error_code sys_net_bnet_ioctl(ppu_thread&, s32 arg1, u32 arg2, u32 arg3); -error_code sys_net_bnet_sysctl(ppu_thread&, u32 arg1, u32 arg2, u32 arg3, vm::ptr arg4, u32 arg5, u32 arg6); -error_code sys_net_eurus_post_command(ppu_thread&, s32 arg1, u32 arg2, u32 arg3); diff --git a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket.cpp b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket.cpp deleted file mode 100644 index cc3fd027b..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket.cpp +++ /dev/null @@ -1,191 +0,0 @@ -#include "stdafx.h" -#include "lv2_socket.h" -#include "network_context.h" - -LOG_CHANNEL(sys_net); - -lv2_socket::lv2_socket(lv2_socket_family family, lv2_socket_type type, lv2_ip_protocol protocol) -{ - this->family = family; - this->type = type; - this->protocol = protocol; -} - -std::unique_lock lv2_socket::lock() -{ - return std::unique_lock(mutex); -} - -lv2_socket_family lv2_socket::get_family() const -{ - return family; -} - -lv2_socket_type lv2_socket::get_type() const -{ - return type; -} -lv2_ip_protocol lv2_socket::get_protocol() const -{ - return protocol; -} -std::size_t lv2_socket::get_queue_size() const -{ - return queue.size(); -} -socket_type lv2_socket::get_socket() const -{ - return native_socket; -} - -#ifdef _WIN32 -bool lv2_socket::is_connecting() const -{ - return connecting; -} -void lv2_socket::set_connecting(bool connecting) -{ - this->connecting = connecting; -} -#endif - -void lv2_socket::set_lv2_id(u32 id) -{ - lv2_id = id; -} - -bs_t lv2_socket::get_events() const -{ - return events.load(); -} - -void lv2_socket::set_poll_event(bs_t event) -{ - events += event; -} - -void lv2_socket::poll_queue(shared_ptr ppu, bs_t event, std::function)> poll_cb) -{ - set_poll_event(event); - queue.emplace_back(std::move(ppu), poll_cb); - - // Makes sure network_context thread is awaken - if (type == SYS_NET_SOCK_STREAM || type == SYS_NET_SOCK_DGRAM) - { - auto& nc = g_fxo->get(); - const u32 prev_value = nc.num_polls.fetch_add(1); - if (!prev_value) - { - nc.num_polls.notify_one(); - } - } -} - -u32 lv2_socket::clear_queue(ppu_thread* ppu) -{ - std::lock_guard lock(mutex); - - u32 cleared = 0; - - for (auto it = queue.begin(); it != queue.end();) - { - if (it->first.get() == ppu) - { - it = queue.erase(it); - cleared++; - continue; - } - - it++; - } - - if (queue.empty()) - { - events.store({}); - } - - if (cleared && (type == SYS_NET_SOCK_STREAM || type == SYS_NET_SOCK_DGRAM)) - { - // Makes sure network_context thread can go back to sleep if there is no active polling - const u32 prev_value = g_fxo->get().num_polls.fetch_sub(cleared); - ensure(prev_value >= cleared); - } - - return cleared; -} - -void lv2_socket::handle_events(const pollfd& native_pfd, [[maybe_unused]] bool unset_connecting) -{ - bs_t events_happening{}; - - if (native_pfd.revents & (POLLIN | POLLHUP) && events.test_and_reset(lv2_socket::poll_t::read)) - events_happening += lv2_socket::poll_t::read; - if (native_pfd.revents & POLLOUT && events.test_and_reset(lv2_socket::poll_t::write)) - events_happening += lv2_socket::poll_t::write; - if (native_pfd.revents & POLLERR && events.test_and_reset(lv2_socket::poll_t::error)) - events_happening += lv2_socket::poll_t::error; - - if (events_happening || (!queue.empty() && (so_rcvtimeo || so_sendtimeo))) - { - std::lock_guard lock(mutex); -#ifdef _WIN32 - if (unset_connecting) - set_connecting(false); -#endif - u32 handled = 0; - - for (auto it = queue.begin(); it != queue.end();) - { - if (it->second(events_happening)) - { - it = queue.erase(it); - handled++; - continue; - } - - it++; - } - - if (handled && (type == SYS_NET_SOCK_STREAM || type == SYS_NET_SOCK_DGRAM)) - { - const u32 prev_value = g_fxo->get().num_polls.fetch_sub(handled); - ensure(prev_value >= handled); - } - - if (queue.empty()) - { - events.store({}); - } - } -} - -void lv2_socket::queue_wake(ppu_thread* ppu) -{ - switch (type) - { - case SYS_NET_SOCK_STREAM: - case SYS_NET_SOCK_DGRAM: - g_fxo->get().add_ppu_to_awake(ppu); - break; - case SYS_NET_SOCK_DGRAM_P2P: - case SYS_NET_SOCK_STREAM_P2P: - g_fxo->get().add_ppu_to_awake(ppu); - break; - default: - break; - } -} - -lv2_socket& lv2_socket::operator=(thread_state s) noexcept -{ - if (s == thread_state::destroying_context) - { - close(); - } - - return *this; -} - -lv2_socket::~lv2_socket() noexcept -{ -} diff --git a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket.h b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket.h deleted file mode 100644 index d31be953a..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket.h +++ /dev/null @@ -1,156 +0,0 @@ -#pragma once - -#include -#include - -#include "util/mutex.h" -#include "Emu/IdManager.h" -#include "Emu/Cell/lv2/sys_net.h" -#include "Emu/NP/ip_address.h" - -#ifdef _WIN32 -#include -#include -#else -#ifdef __clang__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wold-style-cast" -#endif -#include -#ifdef __clang__ -#pragma GCC diagnostic pop -#endif -#endif - -enum class thread_state : u32; - -class lv2_socket -{ -public: - // Poll events - enum class poll_t - { - read, - write, - error, - - __bitset_enum_max - }; - - union sockopt_data - { - char ch[128]; - be_t _int = 0; - sys_net_timeval timeo; - sys_net_linger linger; - }; - - struct sockopt_cache - { - sockopt_data data{}; - s32 len = 0; - }; - -public: - SAVESTATE_INIT_POS(7); // Dependency on RPCN - - lv2_socket(lv2_socket_family family, lv2_socket_type type, lv2_ip_protocol protocol); - lv2_socket(utils::serial&) {} - lv2_socket(utils::serial&, lv2_socket_type type); - static std::function load(utils::serial& ar); - void save(utils::serial&, bool save_only_this_class = false); - virtual ~lv2_socket() noexcept; - lv2_socket& operator=(thread_state s) noexcept; - - std::unique_lock lock(); - - void set_lv2_id(u32 id); - bs_t get_events() const; - void set_poll_event(bs_t event); - void poll_queue(shared_ptr ppu, bs_t event, std::function)> poll_cb); - u32 clear_queue(ppu_thread*); - void handle_events(const pollfd& native_fd, bool unset_connecting = false); - void queue_wake(ppu_thread* ppu); - - lv2_socket_family get_family() const; - lv2_socket_type get_type() const; - lv2_ip_protocol get_protocol() const; - std::size_t get_queue_size() const; - socket_type get_socket() const; -#ifdef _WIN32 - bool is_connecting() const; - void set_connecting(bool is_connecting); -#endif - -public: - virtual std::tuple, sys_net_sockaddr> accept(bool is_lock = true) = 0; - virtual s32 bind(const sys_net_sockaddr& addr) = 0; - - virtual std::optional connect(const sys_net_sockaddr& addr) = 0; - virtual s32 connect_followup() = 0; - - virtual std::pair getpeername() = 0; - virtual std::pair getsockname() = 0; - - virtual std::tuple getsockopt(s32 level, s32 optname, u32 len) = 0; - virtual s32 setsockopt(s32 level, s32 optname, const std::vector& optval) = 0; - - virtual s32 listen(s32 backlog) = 0; - - virtual std::optional, sys_net_sockaddr>> recvfrom(s32 flags, u32 len, bool is_lock = true) = 0; - virtual std::optional sendto(s32 flags, const std::vector& buf, std::optional opt_sn_addr, bool is_lock = true) = 0; - virtual std::optional sendmsg(s32 flags, const sys_net_msghdr& msg, bool is_lock = true) = 0; - - virtual void close() = 0; - virtual s32 shutdown(s32 how) = 0; - - virtual s32 poll(sys_net_pollfd& sn_pfd, pollfd& native_pfd) = 0; - virtual std::tuple select(bs_t selected, pollfd& native_pfd) = 0; - - error_code abort_socket(s32 flags); - -public: - // IDM data - static const u32 id_base = 24; - static const u32 id_step = 1; - static const u32 id_count = 1000; - -protected: - lv2_socket(utils::serial&, bool); - - shared_mutex mutex; - s32 lv2_id = 0; - - socket_type native_socket = 0; - - lv2_socket_family family{}; - lv2_socket_type type{}; - lv2_ip_protocol protocol{}; - - // Events selected for polling - atomic_bs_t events{}; - - // Event processing workload (pair of thread id and the processing function) - std::vector, std::function)>>> queue; - - // Socket options value keepers - // Non-blocking IO option - s32 so_nbio = 0; - // Error, only used for connection result for non blocking stream sockets - s32 so_error = 0; - // Unsupported option - s32 so_tcp_maxseg = 1500; -#ifdef _WIN32 - s32 so_reuseaddr = 0; - s32 so_reuseport = 0; - - // Tracks connect for WSAPoll workaround - bool connecting = false; -#endif - - sys_net_sockaddr last_bound_addr{}; - -public: - u64 so_rcvtimeo = 0; - u64 so_sendtimeo = 0; -}; diff --git a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_native.cpp b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_native.cpp deleted file mode 100644 index 2f23375b9..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_native.cpp +++ /dev/null @@ -1,1254 +0,0 @@ -#include "stdafx.h" - -#include "Emu/Cell/lv2/sys_net.h" -#include "Emu/NP/np_dnshook.h" -#include "Emu/NP/np_handler.h" -#include "lv2_socket_native.h" -#include "sys_net_helpers.h" - -#ifdef _WIN32 -constexpr SOCKET invalid_socket = INVALID_SOCKET; -#else -constexpr int invalid_socket = -1; -#endif - -LOG_CHANNEL(sys_net); - -lv2_socket_native::lv2_socket_native(lv2_socket_family family, lv2_socket_type type, lv2_ip_protocol protocol) - : lv2_socket(family, type, protocol) -{ -} - -lv2_socket_native::lv2_socket_native(utils::serial& ar, lv2_socket_type type) - : lv2_socket(make_exact(ar), type) -{ - [[maybe_unused]] const s32 version = GET_SERIALIZATION_VERSION(lv2_net); - -#ifdef _WIN32 - ar(so_reuseaddr, so_reuseport); -#else - std::array dummy{}; - ar(dummy); - - if (dummy != std::array{}) - { - sys_net.error("[Native] Savestate tried to load Win32 specific data, compatibility may be affected"); - } -#endif - - if (version >= 2) - { - // Flag to signal failure of TCP connection on socket start - ar(feign_tcp_conn_failure); - } -} - -void lv2_socket_native::save(utils::serial& ar) -{ - USING_SERIALIZATION_VERSION(lv2_net); - - lv2_socket::save(ar, true); -#ifdef _WIN32 - ar(so_reuseaddr, so_reuseport); -#else - ar(std::array{}); -#endif - - ar(is_socket_connected()); -} - -lv2_socket_native::~lv2_socket_native() noexcept -{ - lv2_socket_native::close(); -} - -s32 lv2_socket_native::create_socket() -{ - ensure(family == SYS_NET_AF_INET); - ensure(type == SYS_NET_SOCK_STREAM || type == SYS_NET_SOCK_DGRAM); - ensure(protocol == SYS_NET_IPPROTO_IP || protocol == SYS_NET_IPPROTO_TCP || protocol == SYS_NET_IPPROTO_UDP); - - const int native_domain = AF_INET; - - const int native_type = type == SYS_NET_SOCK_STREAM ? SOCK_STREAM : SOCK_DGRAM; - - int native_proto = protocol == SYS_NET_IPPROTO_TCP ? IPPROTO_TCP : - protocol == SYS_NET_IPPROTO_UDP ? IPPROTO_UDP : - IPPROTO_IP; - - auto socket_res = ::socket(native_domain, native_type, native_proto); - - if (socket_res == invalid_socket) - { - return -get_last_error(false); - } - - set_socket(socket_res, family, type, protocol); - return CELL_OK; -} - -void lv2_socket_native::set_socket(socket_type native_socket, lv2_socket_family family, lv2_socket_type type, lv2_ip_protocol protocol) -{ - this->native_socket = native_socket; - this->family = family; - this->type = type; - this->protocol = protocol; - - set_default_buffers(); - set_non_blocking(); -} - -std::tuple, sys_net_sockaddr> lv2_socket_native::accept(bool is_lock) -{ - std::unique_lock lock(mutex, std::defer_lock); - - if (is_lock) - { - lock.lock(); - } - - ::sockaddr_storage native_addr; - ::socklen_t native_addrlen = sizeof(native_addr); - - if (feign_tcp_conn_failure) - { - sys_net.error("Calling socket::accept() from a previously connected socket!"); - } - - socket_type client_socket = ::accept(native_socket, reinterpret_cast(&native_addr), &native_addrlen); - - if (client_socket != invalid_socket) - { - auto newsock = make_single(family, type, protocol); - newsock->set_socket(client_socket, family, type, protocol); - - // Sockets inherit non blocking behaviour from their parent - newsock->so_nbio = so_nbio; - - sys_net_sockaddr ps3_addr = native_addr_to_sys_net_addr(native_addr); - - return {true, 0, std::move(newsock), ps3_addr}; - } - - if (auto result = get_last_error(!so_nbio); result) - { - return {true, -result, {}, {}}; - } - - return {false, {}, {}, {}}; -} - -s32 lv2_socket_native::bind(const sys_net_sockaddr& addr) -{ - std::lock_guard lock(mutex); - - const auto* psa_in = reinterpret_cast(&addr); - - auto& nph = g_fxo->get>(); - u32 saddr = nph.get_bind_ip(); - if (saddr == 0) - { - // If zero use the supplied address - saddr = std::bit_cast(psa_in->sin_addr); - } - - if (feign_tcp_conn_failure) - { - sys_net.error("Calling socket::bind() from a previously connected socket!"); - } - - ::sockaddr_in native_addr{}; - native_addr.sin_family = AF_INET; - native_addr.sin_port = std::bit_cast(psa_in->sin_port); - native_addr.sin_addr.s_addr = saddr; - ::socklen_t native_addr_len = sizeof(native_addr); - - // Note that this is a hack(TODO) - // ATM we don't support binding 3658 udp because we use it for the p2ps main socket - // Only Fat Princess is known to do this to my knowledge - if (psa_in->sin_port == 3658 && type == SYS_NET_SOCK_DGRAM) - { - native_addr.sin_port = std::bit_cast>(3659); - } - - sys_net.warning("[Native] Trying to bind %s:%d", native_addr.sin_addr, std::bit_cast, u16>(native_addr.sin_port)); - - if (::bind(native_socket, reinterpret_cast(&native_addr), native_addr_len) == 0) - { - // Only UPNP port forward binds to 0.0.0.0 - if (saddr == 0) - { - if (native_addr.sin_port == 0) - { - sockaddr_in client_addr; - socklen_t client_addr_size = sizeof(client_addr); - ensure(::getsockname(native_socket, reinterpret_cast(&client_addr), &client_addr_size) == 0); - bound_port = std::bit_cast>(client_addr.sin_port); - } - else - { - bound_port = std::bit_cast>(native_addr.sin_port); - } - - nph.upnp_add_port_mapping(bound_port, type == SYS_NET_SOCK_STREAM ? "TCP" : "UDP"); - } - - last_bound_addr = addr; - return CELL_OK; - } - - auto error = get_last_error(false); - -#ifdef __linux__ - if (error == SYS_NET_EACCES && std::bit_cast, u16>(native_addr.sin_port) < 1024) - { - sys_net.error("The game tried to bind a port < 1024 which is privileged on Linux\n" - "Consider setting rpcs3 privileges for it with: setcap 'cap_net_bind_service=+ep' /path/to/rpcs3"); - } -#endif - - return -error; -} - -std::optional lv2_socket_native::connect(const sys_net_sockaddr& addr) -{ - std::lock_guard lock(mutex); - - const auto* psa_in = reinterpret_cast(&addr); - - ::sockaddr_in native_addr = sys_net_addr_to_native_addr(addr); - ::socklen_t native_addr_len = sizeof(native_addr); - - sys_net.notice("[Native] Attempting to connect on %s:%d", native_addr.sin_addr, std::bit_cast, u16>(native_addr.sin_port)); - - auto& nph = g_fxo->get>(); - if (!nph.get_net_status() && is_ip_public_address(native_addr)) - { - return -SYS_NET_EADDRNOTAVAIL; - } - - if (psa_in->sin_port == 53) - { - // Add socket to the dns hook list - sys_net.notice("[Native] sys_net_bnet_connect: using DNS..."); - auto& dnshook = g_fxo->get(); - dnshook.add_dns_spy(lv2_id); - } - -#ifdef _WIN32 - bool was_connecting = connecting; -#endif - - if (feign_tcp_conn_failure) - { - // As if still connected - return -SYS_NET_EALREADY; - } - - if (::connect(native_socket, reinterpret_cast(&native_addr), native_addr_len) == 0) - { - return CELL_OK; - } - - sys_net_error result = get_last_error(!so_nbio); - -#ifdef _WIN32 - // See https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-connect - if (was_connecting && (result == SYS_NET_EINVAL || result == SYS_NET_EWOULDBLOCK)) - return -SYS_NET_EALREADY; -#endif - - if (result) - { - if (result == SYS_NET_EWOULDBLOCK || result == SYS_NET_EINPROGRESS) - { - result = SYS_NET_EINPROGRESS; -#ifdef _WIN32 - connecting = true; -#endif - this->poll_queue(null_ptr, lv2_socket::poll_t::write, [this](bs_t events) -> bool - { - if (events & lv2_socket::poll_t::write) - { - int native_error; - ::socklen_t size = sizeof(native_error); - if (::getsockopt(native_socket, SOL_SOCKET, SO_ERROR, reinterpret_cast(&native_error), &size) != 0 || size != sizeof(int)) - { - so_error = 1; - } - else - { - // TODO: check error formats (both native and translated) - so_error = native_error ? convert_error(false, native_error) : 0; - } - - return true; - } - - events += lv2_socket::poll_t::write; - return false; - }); - } - - return -result; - } - -#ifdef _WIN32 - connecting = true; -#endif - - return std::nullopt; -} - -s32 lv2_socket_native::connect_followup() -{ - int native_error; - ::socklen_t size = sizeof(native_error); - if (::getsockopt(native_socket, SOL_SOCKET, SO_ERROR, reinterpret_cast(&native_error), &size) != 0 || size != sizeof(int)) - { - return -1; - } - - // TODO: check error formats (both native and translated) - return native_error ? -convert_error(false, native_error) : 0; -} - -std::pair lv2_socket_native::getpeername() -{ - std::lock_guard lock(mutex); - - ::sockaddr_storage native_addr; - ::socklen_t native_addrlen = sizeof(native_addr); - - if (::getpeername(native_socket, reinterpret_cast(&native_addr), &native_addrlen) == 0) - { - ensure(native_addr.ss_family == AF_INET); - - sys_net_sockaddr sn_addr = native_addr_to_sys_net_addr(native_addr); - - return {CELL_OK, sn_addr}; - } - - return {-get_last_error(false), {}}; -} - -std::pair lv2_socket_native::getsockname() -{ - std::lock_guard lock(mutex); - - ::sockaddr_storage native_addr; - ::socklen_t native_addrlen = sizeof(native_addr); - - if (::getsockname(native_socket, reinterpret_cast(&native_addr), &native_addrlen) == 0) - { - ensure(native_addr.ss_family == AF_INET); - - sys_net_sockaddr sn_addr = native_addr_to_sys_net_addr(native_addr); - - return {CELL_OK, sn_addr}; - } -#ifdef _WIN32 - else - { - // windows doesn't support getsockname for sockets that are not bound - if (get_native_error() == WSAEINVAL) - { - return {CELL_OK, {}}; - } - } -#endif - - return {-get_last_error(false), {}}; -} - -std::tuple lv2_socket_native::getsockopt(s32 level, s32 optname, u32 len) -{ - std::lock_guard lock(mutex); - - sockopt_data out_val; - u32 out_len = sizeof(out_val); - - int native_level = -1; - int native_opt = -1; - - union - { - char ch[128]; - int _int = 0; - ::timeval timeo; - ::linger linger; - } native_val; - ::socklen_t native_len = sizeof(native_val); - - if (level == SYS_NET_SOL_SOCKET) - { - native_level = SOL_SOCKET; - - switch (optname) - { - case SYS_NET_SO_NBIO: - { - // Special - out_val._int = so_nbio; - out_len = sizeof(s32); - return {CELL_OK, out_val, out_len}; - } - case SYS_NET_SO_ERROR: - { - // Special - out_val._int = std::exchange(so_error, 0); - out_len = sizeof(s32); - return {CELL_OK, out_val, out_len}; - } - case SYS_NET_SO_KEEPALIVE: - { - native_opt = SO_KEEPALIVE; - break; - } - case SYS_NET_SO_SNDBUF: - { - native_opt = SO_SNDBUF; - break; - } - case SYS_NET_SO_RCVBUF: - { - native_opt = SO_RCVBUF; - break; - } - case SYS_NET_SO_SNDLOWAT: - { - native_opt = SO_SNDLOWAT; - break; - } - case SYS_NET_SO_RCVLOWAT: - { - native_opt = SO_RCVLOWAT; - break; - } - case SYS_NET_SO_BROADCAST: - { - native_opt = SO_BROADCAST; - break; - } -#ifdef _WIN32 - case SYS_NET_SO_REUSEADDR: - { - out_val._int = so_reuseaddr; - out_len = sizeof(s32); - return {CELL_OK, out_val, out_len}; - } - case SYS_NET_SO_REUSEPORT: - { - out_val._int = so_reuseport; - out_len = sizeof(s32); - return {CELL_OK, out_val, out_len}; - } -#else - case SYS_NET_SO_REUSEADDR: - { - native_opt = SO_REUSEADDR; - break; - } - case SYS_NET_SO_REUSEPORT: - { - native_opt = SO_REUSEPORT; - break; - } -#endif - case SYS_NET_SO_SNDTIMEO: - case SYS_NET_SO_RCVTIMEO: - { - if (len < sizeof(sys_net_timeval)) - return {-SYS_NET_EINVAL, {}, {}}; - - native_opt = optname == SYS_NET_SO_SNDTIMEO ? SO_SNDTIMEO : SO_RCVTIMEO; - break; - } - case SYS_NET_SO_LINGER: - { - if (len < sizeof(sys_net_linger)) - return {-SYS_NET_EINVAL, {}, {}}; - - native_opt = SO_LINGER; - break; - } - default: - { - sys_net.error("sys_net_bnet_getsockopt(s=%d, SOL_SOCKET): unknown option (0x%x)", lv2_id, optname); - return {-SYS_NET_EINVAL, {}, {}}; - } - } - } - else if (level == SYS_NET_IPPROTO_TCP) - { - native_level = IPPROTO_TCP; - - switch (optname) - { - case SYS_NET_TCP_MAXSEG: - { - // Special (no effect) - out_val._int = so_tcp_maxseg; - out_len = sizeof(s32); - return {CELL_OK, out_val, out_len}; - } - case SYS_NET_TCP_NODELAY: - { - native_opt = TCP_NODELAY; - break; - } - default: - { - sys_net.error("sys_net_bnet_getsockopt(s=%d, IPPROTO_TCP): unknown option (0x%x)", lv2_id, optname); - return {-SYS_NET_EINVAL, {}, {}}; - } - } - } - else if (level == SYS_NET_IPPROTO_IP) - { - native_level = IPPROTO_IP; - switch (optname) - { - case SYS_NET_IP_HDRINCL: - { - native_opt = IP_HDRINCL; - break; - } - case SYS_NET_IP_TOS: - { - native_opt = IP_TOS; - break; - } - case SYS_NET_IP_TTL: - { - native_opt = IP_TTL; - break; - } - case SYS_NET_IP_MULTICAST_IF: - { - native_opt = IP_MULTICAST_IF; - break; - } - case SYS_NET_IP_MULTICAST_TTL: - { - native_opt = IP_MULTICAST_TTL; - break; - } - case SYS_NET_IP_MULTICAST_LOOP: - { - native_opt = IP_MULTICAST_LOOP; - break; - } - case SYS_NET_IP_ADD_MEMBERSHIP: - { - native_opt = IP_ADD_MEMBERSHIP; - break; - } - case SYS_NET_IP_DROP_MEMBERSHIP: - { - native_opt = IP_DROP_MEMBERSHIP; - break; - } - case SYS_NET_IP_TTLCHK: - { - sys_net.error("sys_net_bnet_getsockopt(IPPROTO_IP, SYS_NET_IP_TTLCHK): stubbed option"); - return {CELL_OK, out_val, out_len}; - } - case SYS_NET_IP_MAXTTL: - { - sys_net.error("sys_net_bnet_getsockopt(IPPROTO_IP, SYS_NET_IP_MAXTTL): stubbed option"); - return {CELL_OK, out_val, out_len}; - } - case SYS_NET_IP_DONTFRAG: - { -#ifdef _WIN32 - native_opt = IP_DONTFRAGMENT; -#else - native_opt = IP_DF; -#endif - break; - } - default: - { - sys_net.error("sys_net_bnet_getsockopt(s=%d, IPPROTO_IP): unknown option (0x%x)", lv2_id, optname); - return {-SYS_NET_EINVAL, {}, {}}; - } - } - } - else - { - sys_net.error("sys_net_bnet_getsockopt(s=%d): unknown level (0x%x)", lv2_id, level); - return {-SYS_NET_EINVAL, {}, {}}; - } - - if (::getsockopt(native_socket, native_level, native_opt, native_val.ch, &native_len) != 0) - { - return {-get_last_error(false), {}, {}}; - } - - if (level == SYS_NET_SOL_SOCKET) - { - switch (optname) - { - case SYS_NET_SO_SNDTIMEO: - case SYS_NET_SO_RCVTIMEO: - { - // TODO - out_val.timeo = {::narrow(native_val.timeo.tv_sec), ::narrow(native_val.timeo.tv_usec)}; - out_len = sizeof(sys_net_timeval); - return {CELL_OK, out_val, out_len}; - } - case SYS_NET_SO_LINGER: - { - // TODO - out_val.linger = {::narrow(native_val.linger.l_onoff), ::narrow(native_val.linger.l_linger)}; - out_len = sizeof(sys_net_linger); - return {CELL_OK, out_val, out_len}; - } - default: break; - } - } - - // Fallback to int - out_val._int = native_val._int; - out_len = sizeof(s32); - return {CELL_OK, out_val, out_len}; -} - -s32 lv2_socket_native::setsockopt(s32 level, s32 optname, const std::vector& optval) -{ - std::lock_guard lock(mutex); - - int native_int = 0; - int native_level = -1; - int native_opt = -1; - const void* native_val = &native_int; - ::socklen_t native_len = sizeof(int); - ::linger native_linger; - ::ip_mreq native_mreq; - -#ifdef _WIN32 - u32 native_timeo; -#else - ::timeval native_timeo; -#endif - - native_int = *reinterpret_cast*>(optval.data()); - - if (level == SYS_NET_SOL_SOCKET) - { - native_level = SOL_SOCKET; - - switch (optname) - { - case SYS_NET_SO_NBIO: - { - // Special - so_nbio = native_int; - return {}; - } - case SYS_NET_SO_KEEPALIVE: - { - native_opt = SO_KEEPALIVE; - break; - } - case SYS_NET_SO_SNDBUF: - { - native_opt = SO_SNDBUF; - break; - } - case SYS_NET_SO_RCVBUF: - { - native_opt = SO_RCVBUF; - break; - } - case SYS_NET_SO_SNDLOWAT: - { - native_opt = SO_SNDLOWAT; - break; - } - case SYS_NET_SO_RCVLOWAT: - { - native_opt = SO_RCVLOWAT; - break; - } - case SYS_NET_SO_BROADCAST: - { - native_opt = SO_BROADCAST; - break; - } -#ifdef _WIN32 - case SYS_NET_SO_REUSEADDR: - { - native_opt = SO_REUSEADDR; - so_reuseaddr = native_int; - native_int = so_reuseaddr || so_reuseport ? 1 : 0; - break; - } - case SYS_NET_SO_REUSEPORT: - { - native_opt = SO_REUSEADDR; - so_reuseport = native_int; - native_int = so_reuseaddr || so_reuseport ? 1 : 0; - break; - } -#else - case SYS_NET_SO_REUSEADDR: - { - native_opt = SO_REUSEADDR; - break; - } - case SYS_NET_SO_REUSEPORT: - { - native_opt = SO_REUSEPORT; - break; - } -#endif - case SYS_NET_SO_SNDTIMEO: - case SYS_NET_SO_RCVTIMEO: - { - if (optval.size() < sizeof(sys_net_timeval)) - return -SYS_NET_EINVAL; - - native_opt = optname == SYS_NET_SO_SNDTIMEO ? SO_SNDTIMEO : SO_RCVTIMEO; - native_val = &native_timeo; - native_len = sizeof(native_timeo); - - const int tv_sec = ::narrow(reinterpret_cast(optval.data())->tv_sec); - const int tv_usec = ::narrow(reinterpret_cast(optval.data())->tv_usec); -#ifdef _WIN32 - native_timeo = tv_sec * 1000; - native_timeo += tv_usec / 1000; -#else - native_timeo.tv_sec = tv_sec; - native_timeo.tv_usec = tv_usec; -#endif - // TODO: Overflow detection? - (optname == SYS_NET_SO_SNDTIMEO ? so_sendtimeo : so_rcvtimeo) = tv_usec + tv_sec * 1000000; - break; - } - case SYS_NET_SO_LINGER: - { - if (optval.size() < sizeof(sys_net_linger)) - return -SYS_NET_EINVAL; - - // TODO - native_opt = SO_LINGER; - native_val = &native_linger; - native_len = sizeof(native_linger); - native_linger.l_onoff = reinterpret_cast(optval.data())->l_onoff; - native_linger.l_linger = reinterpret_cast(optval.data())->l_linger; - break; - } - case SYS_NET_SO_USECRYPTO: - { - // TODO - sys_net.error("sys_net_bnet_setsockopt(s=%d, SOL_SOCKET): Stubbed option (0x%x) (SYS_NET_SO_USECRYPTO)", lv2_id, optname); - return {}; - } - case SYS_NET_SO_USESIGNATURE: - { - // TODO - sys_net.error("sys_net_bnet_setsockopt(s=%d, SOL_SOCKET): Stubbed option (0x%x) (SYS_NET_SO_USESIGNATURE)", lv2_id, optname); - return {}; - } - default: - { - sys_net.error("sys_net_bnet_setsockopt(s=%d, SOL_SOCKET): unknown option (0x%x)", lv2_id, optname); - return -SYS_NET_EINVAL; - } - } - } - else if (level == SYS_NET_IPPROTO_TCP) - { - native_level = IPPROTO_TCP; - - switch (optname) - { - case SYS_NET_TCP_MAXSEG: - { - // Special (no effect) - so_tcp_maxseg = native_int; - return {}; - } - case SYS_NET_TCP_NODELAY: - { - native_opt = TCP_NODELAY; - break; - } - default: - { - sys_net.error("sys_net_bnet_setsockopt(s=%d, IPPROTO_TCP): unknown option (0x%x)", lv2_id, optname); - return -SYS_NET_EINVAL; - } - } - } - else if (level == SYS_NET_IPPROTO_IP) - { - native_level = IPPROTO_IP; - - switch (optname) - { - case SYS_NET_IP_HDRINCL: - { - native_opt = IP_HDRINCL; - break; - } - case SYS_NET_IP_TOS: - { - native_opt = IP_TOS; - break; - } - case SYS_NET_IP_TTL: - { - native_opt = IP_TTL; - break; - } - case SYS_NET_IP_MULTICAST_IF: - { - native_opt = IP_MULTICAST_IF; - break; - } - case SYS_NET_IP_MULTICAST_TTL: - { - native_opt = IP_MULTICAST_TTL; - break; - } - case SYS_NET_IP_MULTICAST_LOOP: - { - native_opt = IP_MULTICAST_LOOP; - break; - } - case SYS_NET_IP_ADD_MEMBERSHIP: - case SYS_NET_IP_DROP_MEMBERSHIP: - { - if (optval.size() < sizeof(sys_net_ip_mreq)) - return -SYS_NET_EINVAL; - - native_opt = optname == SYS_NET_IP_ADD_MEMBERSHIP ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP; - native_val = &native_mreq; - native_len = sizeof(::ip_mreq); - native_mreq.imr_interface.s_addr = std::bit_cast(reinterpret_cast(optval.data())->imr_interface); - native_mreq.imr_multiaddr.s_addr = std::bit_cast(reinterpret_cast(optval.data())->imr_multiaddr); - break; - } - case SYS_NET_IP_TTLCHK: - { - sys_net.error("sys_net_bnet_setsockopt(s=%d, IPPROTO_IP): Stubbed option (0x%x) (SYS_NET_IP_TTLCHK)", lv2_id, optname); - break; - } - case SYS_NET_IP_MAXTTL: - { - sys_net.error("sys_net_bnet_setsockopt(s=%d, IPPROTO_IP): Stubbed option (0x%x) (SYS_NET_IP_MAXTTL)", lv2_id, optname); - break; - } - case SYS_NET_IP_DONTFRAG: - { -#ifdef _WIN32 - native_opt = IP_DONTFRAGMENT; -#else - native_opt = IP_DF; -#endif - break; - } - default: - { - sys_net.error("sys_net_bnet_setsockopt(s=%d, IPPROTO_IP): unknown option (0x%x)", lv2_id, optname); - return -SYS_NET_EINVAL; - } - } - } - else - { - sys_net.error("sys_net_bnet_setsockopt(s=%d): unknown level (0x%x)", lv2_id, level); - return -SYS_NET_EINVAL; - } - - if (::setsockopt(native_socket, native_level, native_opt, static_cast(native_val), native_len) == 0) - { - return {}; - } - - return -get_last_error(false); -} - -s32 lv2_socket_native::listen(s32 backlog) -{ - std::lock_guard lock(mutex); - - if (::listen(native_socket, backlog) == 0) - { - return CELL_OK; - } - - return -get_last_error(false); -} - -std::optional, sys_net_sockaddr>> lv2_socket_native::recvfrom(s32 flags, u32 len, bool is_lock) -{ - std::unique_lock lock(mutex, std::defer_lock); - - if (is_lock) - { - lock.lock(); - } - - if (feign_tcp_conn_failure) - { - // As if just lost the connection - feign_tcp_conn_failure = false; - return {{-SYS_NET_ECONNRESET, {}, {}}}; - } - - int native_flags = 0; - ::sockaddr_storage native_addr{}; - ::socklen_t native_addrlen = sizeof(native_addr); - std::vector res_buf(len); - - auto& dnshook = g_fxo->get(); - if (dnshook.is_dns(lv2_id) && dnshook.is_dns_queue(lv2_id)) - { - auto& nph = g_fxo->get>(); - const auto packet = dnshook.get_dns_packet(lv2_id); - ensure(packet.size() < len); - memcpy(res_buf.data(), packet.data(), packet.size()); - native_addr.ss_family = AF_INET; - (reinterpret_cast<::sockaddr_in*>(&native_addr))->sin_port = std::bit_cast>(53); // htons(53) - (reinterpret_cast<::sockaddr_in*>(&native_addr))->sin_addr.s_addr = nph.get_dns_ip(); - const auto sn_addr = native_addr_to_sys_net_addr(native_addr); - - return {{::narrow(packet.size()), res_buf, sn_addr}}; - } - - if (flags & SYS_NET_MSG_PEEK) - { - native_flags |= MSG_PEEK; - } - - if (flags & SYS_NET_MSG_WAITALL) - { - native_flags |= MSG_WAITALL; - } - - auto native_result = ::recvfrom(native_socket, reinterpret_cast(res_buf.data()), len, native_flags, reinterpret_cast(&native_addr), &native_addrlen); - - if (native_result >= 0) - { - const auto sn_addr = native_addr_to_sys_net_addr(native_addr); - return {{::narrow(native_result), res_buf, sn_addr}}; - } -#ifdef _WIN32 - else - { - // Windows returns an error when trying to peek at a message and buffer not long enough to contain the whole message, should be ignored - if ((native_flags & MSG_PEEK) && get_native_error() == WSAEMSGSIZE) - { - const auto sn_addr = native_addr_to_sys_net_addr(native_addr); - return {{len, res_buf, sn_addr}}; - } - // Windows will return WSASHUTDOWN when the connection is shutdown, POSIX just returns EOF (0) in this situation. - if (get_native_error() == WSAESHUTDOWN) - { - const auto sn_addr = native_addr_to_sys_net_addr(native_addr); - return {{0, {}, sn_addr}}; - } - } - const auto result = get_last_error(!so_nbio && (flags & SYS_NET_MSG_DONTWAIT) == 0, connecting); -#else - const auto result = get_last_error(!so_nbio && (flags & SYS_NET_MSG_DONTWAIT) == 0); -#endif - - if (result) - { - return {{-result, {}, {}}}; - } - - return std::nullopt; -} - -std::optional lv2_socket_native::sendto(s32 flags, const std::vector& buf, std::optional opt_sn_addr, bool is_lock) -{ - std::unique_lock lock(mutex, std::defer_lock); - - if (is_lock) - { - lock.lock(); - } - - int native_flags = 0; - int native_result = -1; - std::optional native_addr = std::nullopt; - - if (opt_sn_addr) - { - native_addr = sys_net_addr_to_native_addr(*opt_sn_addr); - sys_net.trace("[Native] Attempting to send to %s:%d", (*native_addr).sin_addr, std::bit_cast, u16>((*native_addr).sin_port)); - - auto& nph = g_fxo->get>(); - if (!nph.get_net_status() && is_ip_public_address(*native_addr)) - { - return -SYS_NET_EADDRNOTAVAIL; - } - } - else if (feign_tcp_conn_failure) - { - // As if just lost the connection - feign_tcp_conn_failure = false; - return -SYS_NET_ECONNRESET; - } - - sys_net_error result{}; - - if (flags & SYS_NET_MSG_WAITALL) - { - native_flags |= MSG_WAITALL; - } - - auto& dnshook = g_fxo->get(); - if (opt_sn_addr && type == SYS_NET_SOCK_DGRAM && reinterpret_cast(&*opt_sn_addr)->sin_port == 53) - { - dnshook.add_dns_spy(lv2_id); - } - - if (dnshook.is_dns(lv2_id)) - { - const s32 ret_analyzer = dnshook.analyze_dns_packet(lv2_id, reinterpret_cast(buf.data()), ::size32(buf)); - - // Check if the packet is intercepted - if (ret_analyzer >= 0) - { - return {ret_analyzer}; - } - } - - native_result = ::sendto(native_socket, reinterpret_cast(buf.data()), ::narrow(buf.size()), native_flags, native_addr ? reinterpret_cast(&native_addr.value()) : nullptr, native_addr ? sizeof(sockaddr_in) : 0); - - if (native_result >= 0) - { - return {native_result}; - } - -#ifdef _WIN32 - result = get_last_error(!so_nbio && (flags & SYS_NET_MSG_DONTWAIT) == 0, connecting); -#else - result = get_last_error(!so_nbio && (flags & SYS_NET_MSG_DONTWAIT) == 0); -#endif - - if (result) - { - return {-result}; - } - - // Note that this can only happen if the send buffer is full - return std::nullopt; -} - -std::optional lv2_socket_native::sendmsg(s32 flags, const sys_net_msghdr& msg, bool is_lock) -{ - std::unique_lock lock(mutex, std::defer_lock); - - if (is_lock) - { - lock.lock(); - } - - int native_flags = 0; - int native_result = -1; - - sys_net_error result{}; - - if (flags & SYS_NET_MSG_WAITALL) - { - native_flags |= MSG_WAITALL; - } - - if (feign_tcp_conn_failure) - { - // As if just lost the connection - feign_tcp_conn_failure = false; - return {-SYS_NET_ECONNRESET}; - } - - for (int i = 0; i < msg.msg_iovlen; i++) - { - auto iov_base = msg.msg_iov[i].iov_base; - const u32 len = msg.msg_iov[i].iov_len; - const std::vector buf_copy(vm::_ptr(iov_base.addr()), vm::_ptr(iov_base.addr()) + len); - - native_result = ::send(native_socket, reinterpret_cast(buf_copy.data()), ::narrow(buf_copy.size()), native_flags); - - if (native_result >= 0) - { - return {native_result}; - } - } - - result = get_last_error(!so_nbio && (flags & SYS_NET_MSG_DONTWAIT) == 0); - - if (result) - { - return {-result}; - } - - return std::nullopt; -} - -void lv2_socket_native::close() -{ - std::lock_guard lock(mutex); - - np::close_socket(native_socket); - native_socket = {}; - - if (auto dnshook = g_fxo->try_get()) - { - dnshook->remove_dns_spy(lv2_id); - } - - if (bound_port && g_fxo->is_init>()) - { - auto& nph = g_fxo->get>(); - nph.upnp_remove_port_mapping(bound_port, type == SYS_NET_SOCK_STREAM ? "TCP" : "UDP"); - bound_port = 0; - } -} - -s32 lv2_socket_native::shutdown(s32 how) -{ - std::lock_guard lock(mutex); - - if (feign_tcp_conn_failure) - { - // As if still connected - return CELL_OK; - } - -#ifdef _WIN32 - const int native_how = - how == SYS_NET_SHUT_RD ? SD_RECEIVE : - how == SYS_NET_SHUT_WR ? SD_SEND : - SD_BOTH; -#else - const int native_how = - how == SYS_NET_SHUT_RD ? SHUT_RD : - how == SYS_NET_SHUT_WR ? SHUT_WR : - SHUT_RDWR; -#endif - - if (::shutdown(native_socket, native_how) == 0) - { - return CELL_OK; - } - - return -get_last_error(false); -} - -s32 lv2_socket_native::poll(sys_net_pollfd& sn_pfd, pollfd& native_pfd) -{ - // Check for fake packet for dns interceptions - auto& dnshook = g_fxo->get(); - if (sn_pfd.events & SYS_NET_POLLIN && dnshook.is_dns(sn_pfd.fd) && dnshook.is_dns_queue(sn_pfd.fd)) - { - sn_pfd.revents |= SYS_NET_POLLIN; - return 1; - } - if (sn_pfd.events & ~(SYS_NET_POLLIN | SYS_NET_POLLOUT | SYS_NET_POLLERR)) - { - sys_net.warning("sys_net_bnet_poll(fd=%d): events=0x%x", sn_pfd.fd, sn_pfd.events); - } - - native_pfd.fd = native_socket; - - if (sn_pfd.events & SYS_NET_POLLIN) - { - native_pfd.events |= POLLIN; - } - if (sn_pfd.events & SYS_NET_POLLOUT) - { - native_pfd.events |= POLLOUT; - } - - return 0; -} - -std::tuple lv2_socket_native::select(bs_t selected, pollfd& native_pfd) -{ - native_pfd.fd = native_socket; - if (selected & lv2_socket::poll_t::read) - { - native_pfd.events |= POLLIN; - } - if (selected & lv2_socket::poll_t::write) - { - native_pfd.events |= POLLOUT; - } - - return {}; -} - -void lv2_socket_native::set_default_buffers() -{ - // Those are the default PS3 values - u32 default_RCVBUF = (type == SYS_NET_SOCK_STREAM) ? 65535 : 9216; - if (::setsockopt(native_socket, SOL_SOCKET, SO_RCVBUF, reinterpret_cast(&default_RCVBUF), sizeof(default_RCVBUF)) != 0) - { - sys_net.error("Error setting default SO_RCVBUF on sys_net_bnet_socket socket"); - } - u32 default_SNDBUF = 131072; - if (::setsockopt(native_socket, SOL_SOCKET, SO_SNDBUF, reinterpret_cast(&default_SNDBUF), sizeof(default_SNDBUF)) != 0) - { - sys_net.error("Error setting default SO_SNDBUF on sys_net_bnet_socket socket"); - } -} - -void lv2_socket_native::set_non_blocking() -{ - // Set non-blocking - // This is done to avoid having threads stuck on blocking socket functions - // Blocking functions just put the thread to sleep and delegate the waking up to network_thread which polls the sockets - np::set_socket_non_blocking(native_socket); -} - -bool lv2_socket_native::is_socket_connected() -{ - if (type != SYS_NET_SOCK_STREAM) - { - return false; - } - - std::lock_guard lock(mutex); - - int listening = 0; - socklen_t len = sizeof(listening); - - if (::getsockopt(native_socket, SOL_SOCKET, SO_ACCEPTCONN, reinterpret_cast(&listening), &len) == -1) - { - return false; - } - - if (listening) - { - // Would be handled in other ways - return false; - } - - fd_set readfds, writefds; - struct timeval timeout{0, 0}; // Zero timeout - - FD_ZERO(&readfds); - FD_ZERO(&writefds); - FD_SET(native_socket, &readfds); - FD_SET(native_socket, &writefds); - - // Use select to check for readability and writability - const int result = ::select(1, &readfds, &writefds, NULL, &timeout); - - if (result < 0) - { - // Error occurred - return false; - } - - // Socket is connected if it's readable or writable - return FD_ISSET(native_socket, &readfds) || FD_ISSET(native_socket, &writefds); -} diff --git a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_native.h b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_native.h deleted file mode 100644 index cf07dfcb7..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_native.h +++ /dev/null @@ -1,75 +0,0 @@ - -#pragma once - -#ifdef _WIN32 -#include -#include -#else -#ifdef __clang__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wold-style-cast" -#endif -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef __clang__ -#pragma GCC diagnostic pop -#endif -#endif - -#include "lv2_socket.h" - -class lv2_socket_native final : public lv2_socket -{ -public: - static constexpr u32 id_type = 1; - - lv2_socket_native(lv2_socket_family family, lv2_socket_type type, lv2_ip_protocol protocol); - lv2_socket_native(utils::serial& ar, lv2_socket_type type); - ~lv2_socket_native() noexcept override; - void save(utils::serial& ar); - s32 create_socket(); - - std::tuple, sys_net_sockaddr> accept(bool is_lock = true) override; - s32 bind(const sys_net_sockaddr& addr) override; - - std::optional connect(const sys_net_sockaddr& addr) override; - s32 connect_followup() override; - - std::pair getpeername() override; - std::pair getsockname() override; - std::tuple getsockopt(s32 level, s32 optname, u32 len) override; - s32 setsockopt(s32 level, s32 optname, const std::vector& optval) override; - std::optional, sys_net_sockaddr>> recvfrom(s32 flags, u32 len, bool is_lock = true) override; - std::optional sendto(s32 flags, const std::vector& buf, std::optional opt_sn_addr, bool is_lock = true) override; - std::optional sendmsg(s32 flags, const sys_net_msghdr& msg, bool is_lock = true) override; - - s32 poll(sys_net_pollfd& sn_pfd, pollfd& native_pfd) override; - std::tuple select(bs_t selected, pollfd& native_pfd) override; - - bool is_socket_connected(); - - s32 listen(s32 backlog) override; - void close() override; - s32 shutdown(s32 how) override; - -private: - void set_socket(socket_type native_socket, lv2_socket_family family, lv2_socket_type type, lv2_ip_protocol protocol); - void set_default_buffers(); - void set_non_blocking(); - -private: - // Value keepers -#ifdef _WIN32 - s32 so_reuseaddr = 0; - s32 so_reuseport = 0; -#endif - u16 bound_port = 0; - bool feign_tcp_conn_failure = false; // Savestate load related -}; diff --git a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2p.cpp b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2p.cpp deleted file mode 100644 index 157f5c4ec..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2p.cpp +++ /dev/null @@ -1,408 +0,0 @@ -#include "stdafx.h" -#include "lv2_socket_p2p.h" -#include "Emu/NP/np_helpers.h" -#include "network_context.h" -#include "sys_net_helpers.h" - -LOG_CHANNEL(sys_net); - -lv2_socket_p2p::lv2_socket_p2p(lv2_socket_family family, lv2_socket_type type, lv2_ip_protocol protocol) - : lv2_socket(family, type, protocol) -{ - sockopt_cache cache_type; - cache_type.data._int = SYS_NET_SOCK_DGRAM_P2P; - cache_type.len = 4; - - sockopts[(static_cast(SYS_NET_SOL_SOCKET) << 32ull) | SYS_NET_SO_TYPE] = cache_type; -} - -lv2_socket_p2p::lv2_socket_p2p(utils::serial& ar, lv2_socket_type type) - : lv2_socket(make_exact(ar), type) -{ - ar(port, vport, bound_addr); - - auto data_dequeue = ar.pop>>>(); - - for (; !data_dequeue.empty(); data_dequeue.pop_front()) - { - data.push(std::move(data_dequeue.front())); - } -} - -void lv2_socket_p2p::save(utils::serial& ar) -{ - lv2_socket::save(ar, true); - ar(port, vport, bound_addr); - - std::deque>> data_dequeue; - - for (auto save_data = ::as_rvalue(data); !save_data.empty(); save_data.pop()) - { - data_dequeue.push_back(std::move(save_data.front())); - } - - ar(data_dequeue); -} - -void lv2_socket_p2p::handle_new_data(sys_net_sockaddr_in_p2p p2p_addr, std::vector p2p_data) -{ - std::lock_guard lock(mutex); - - sys_net.trace("Received a P2P packet for vport %d and saved it", p2p_addr.sin_vport); - data.push(std::make_pair(std::move(p2p_addr), std::move(p2p_data))); - - // Check if poll is happening - if (events.test_and_reset(lv2_socket::poll_t::read)) - { - bs_t read_event = lv2_socket::poll_t::read; - for (auto it = queue.begin(); it != queue.end();) - { - if (it->second(read_event)) - { - it = queue.erase(it); - continue; - } - it++; - } - - if (queue.empty()) - { - events.store({}); - } - } -} - -std::tuple, sys_net_sockaddr> lv2_socket_p2p::accept([[maybe_unused]] bool is_lock) -{ - sys_net.fatal("[P2P] accept() called on a P2P socket"); - return {}; -} - -std::optional lv2_socket_p2p::connect([[maybe_unused]] const sys_net_sockaddr& addr) -{ - sys_net.fatal("[P2P] connect() called on a P2P socket"); - return {}; -} - -s32 lv2_socket_p2p::connect_followup() -{ - sys_net.fatal("[P2P] connect_followup() called on a P2P socket"); - return {}; -} - -std::pair lv2_socket_p2p::getpeername() -{ - sys_net.fatal("[P2P] getpeername() called on a P2P socket"); - return {}; -} - -s32 lv2_socket_p2p::listen([[maybe_unused]] s32 backlog) -{ - sys_net.fatal("[P2P] listen() called on a P2P socket"); - return {}; -} - -s32 lv2_socket_p2p::bind(const sys_net_sockaddr& addr) -{ - const auto* psa_in_p2p = reinterpret_cast(&addr); - u16 p2p_port = psa_in_p2p->sin_port; - u16 p2p_vport = psa_in_p2p->sin_vport; - - sys_net.notice("[P2P] Trying to bind %s:%d:%d", np::ip_to_string(std::bit_cast(psa_in_p2p->sin_addr)), p2p_port, p2p_vport); - - if (p2p_port != SCE_NP_PORT) - { - if (p2p_port == 0) - { - return -SYS_NET_EINVAL; - } - sys_net.warning("[P2P] Attempting to bind a socket to a port != %d", +SCE_NP_PORT); - } - - socket_type real_socket{}; - - auto& nc = g_fxo->get(); - { - std::lock_guard list_lock(nc.list_p2p_ports_mutex); - - nc.create_p2p_port(p2p_port); - auto& pport = ::at32(nc.list_p2p_ports, p2p_port); - real_socket = pport.p2p_socket; - - { - std::lock_guard lock(pport.bound_p2p_vports_mutex); - - if (p2p_vport == 0) - { - // Find a free vport starting at 30000 - p2p_vport = 30000; - while (pport.bound_p2p_vports.contains(p2p_vport)) - { - p2p_vport++; - } - } - - if (pport.bound_p2p_vports.contains(p2p_vport)) - { - // Check that all other sockets are SO_REUSEADDR or SO_REUSEPORT - auto& bound_sockets = ::at32(pport.bound_p2p_vports, p2p_vport); - if (!sys_net_helpers::all_reusable(bound_sockets)) - { - return -SYS_NET_EADDRINUSE; - } - - bound_sockets.insert(lv2_id); - } - else - { - std::set bound_ports{lv2_id}; - pport.bound_p2p_vports.insert(std::make_pair(p2p_vport, std::move(bound_ports))); - } - } - } - - { - std::lock_guard lock(mutex); - port = p2p_port; - vport = p2p_vport; - native_socket = real_socket; - bound_addr = psa_in_p2p->sin_addr; - } - - return CELL_OK; -} - -std::pair lv2_socket_p2p::getsockname() -{ - std::lock_guard lock(mutex); - - // Unbound socket - if (!native_socket) - { - return {CELL_OK, {}}; - } - - sys_net_sockaddr sn_addr{}; - sys_net_sockaddr_in_p2p* paddr = reinterpret_cast(&sn_addr); - - paddr->sin_len = sizeof(sys_net_sockaddr_in); - paddr->sin_family = SYS_NET_AF_INET; - paddr->sin_port = port; - paddr->sin_vport = vport; - paddr->sin_addr = bound_addr; - - return {CELL_OK, sn_addr}; -} - -std::tuple lv2_socket_p2p::getsockopt(s32 level, s32 optname, u32 len) -{ - std::lock_guard lock(mutex); - - const u64 key = (static_cast(level) << 32) | static_cast(optname); - - if (!sockopts.contains(key)) - { - sys_net.error("Unhandled getsockopt(level=%d, optname=%d, len=%d)", level, optname, len); - return {}; - } - - const auto& cache = ::at32(sockopts, key); - return {CELL_OK, cache.data, cache.len}; -} - -s32 lv2_socket_p2p::setsockopt(s32 level, s32 optname, const std::vector& optval) -{ - std::lock_guard lock(mutex); - - int native_int = *reinterpret_cast*>(optval.data()); - - if (level == SYS_NET_SOL_SOCKET && optname == SYS_NET_SO_NBIO) - { - so_nbio = native_int; - } - - const u64 key = (static_cast(level) << 32) | static_cast(optname); - sockopt_cache cache{}; - memcpy(&cache.data._int, optval.data(), optval.size()); - cache.len = ::size32(optval); - - sockopts[key] = std::move(cache); - - return CELL_OK; -} - -std::optional, sys_net_sockaddr>> lv2_socket_p2p::recvfrom(s32 flags, u32 len, bool is_lock) -{ - std::unique_lock lock(mutex, std::defer_lock); - - if (is_lock) - { - lock.lock(); - } - - if (data.empty()) - { - if (so_nbio || (flags & SYS_NET_MSG_DONTWAIT)) - return {{-SYS_NET_EWOULDBLOCK, {}, {}}}; - - return std::nullopt; - } - - sys_net.trace("[P2P] p2p_data for vport %d contains %d elements", vport, data.size()); - - std::vector res_buf(len); - - const auto& p2p_data = data.front(); - s32 native_result = std::min(len, static_cast(p2p_data.second.size())); - memcpy(res_buf.data(), p2p_data.second.data(), native_result); - - sys_net_sockaddr sn_addr; - memcpy(&sn_addr, &p2p_data.first, sizeof(sn_addr)); - - data.pop(); - - return {{native_result, res_buf, sn_addr}}; -} - -std::optional lv2_socket_p2p::sendto(s32 flags, const std::vector& buf, std::optional opt_sn_addr, bool is_lock) -{ - std::unique_lock lock(mutex, std::defer_lock); - - if (is_lock) - { - lock.lock(); - } - - ensure(opt_sn_addr); - ensure(socket); // ensures it has been bound - ensure(buf.size() <= static_cast(65535 - VPORT_P2P_HEADER_SIZE)); // catch games using full payload for future fragmentation implementation if necessary - const u16 p2p_port = reinterpret_cast(&*opt_sn_addr)->sin_port; - const u16 p2p_vport = reinterpret_cast(&*opt_sn_addr)->sin_vport; - - auto native_addr = sys_net_addr_to_native_addr(*opt_sn_addr); - - char ip_str[16]; - inet_ntop(AF_INET, &native_addr.sin_addr, ip_str, sizeof(ip_str)); - sys_net.trace("[P2P] Sending a packet to %s:%d:%d", ip_str, p2p_port, p2p_vport); - - std::vector p2p_data(buf.size() + VPORT_P2P_HEADER_SIZE); - const le_t p2p_vport_le = p2p_vport; - const le_t src_vport_le = vport; - const le_t p2p_flags_le = P2P_FLAG_P2P; - memcpy(p2p_data.data(), &p2p_vport_le, sizeof(u16)); - memcpy(p2p_data.data() + sizeof(u16), &src_vport_le, sizeof(u16)); - memcpy(p2p_data.data() + sizeof(u16) + sizeof(u16), &p2p_flags_le, sizeof(u16)); - memcpy(p2p_data.data() + VPORT_P2P_HEADER_SIZE, buf.data(), buf.size()); - - int native_flags = 0; - if (flags & SYS_NET_MSG_WAITALL) - { - native_flags |= MSG_WAITALL; - } - - auto native_result = np::sendto_possibly_ipv6(native_socket, reinterpret_cast(p2p_data.data()), ::size32(p2p_data), &native_addr, native_flags); - - if (native_result >= 0) - { - return {std::max(native_result - VPORT_P2P_HEADER_SIZE, 0l)}; - } - - s32 result = get_last_error(!so_nbio && (flags & SYS_NET_MSG_DONTWAIT) == 0); - - if (result) - { - return {-result}; - } - - // Note that this can only happen if the send buffer is full - return std::nullopt; -} - -std::optional lv2_socket_p2p::sendmsg([[maybe_unused]] s32 flags, [[maybe_unused]] const sys_net_msghdr& msg, [[maybe_unused]] bool is_lock) -{ - sys_net.todo("lv2_socket_p2p::sendmsg"); - return {}; -} - -void lv2_socket_p2p::close() -{ - if (!port || !vport) - { - return; - } - - if (g_fxo->is_init()) - { - auto& nc = g_fxo->get(); - std::lock_guard lock(nc.list_p2p_ports_mutex); - - if (!nc.list_p2p_ports.contains(port)) - return; - - auto& p2p_port = ::at32(nc.list_p2p_ports, port); - { - std::lock_guard lock(p2p_port.bound_p2p_vports_mutex); - if (!p2p_port.bound_p2p_vports.contains(vport)) - { - return; - } - - auto& bound_sockets = ::at32(p2p_port.bound_p2p_vports, vport); - bound_sockets.erase(lv2_id); - - if (bound_sockets.empty()) - { - p2p_port.bound_p2p_vports.erase(vport); - } - } - } -} - -s32 lv2_socket_p2p::shutdown([[maybe_unused]] s32 how) -{ - sys_net.todo("[P2P] shutdown"); - return CELL_OK; -} - -s32 lv2_socket_p2p::poll(sys_net_pollfd& sn_pfd, [[maybe_unused]] pollfd& native_pfd) -{ - std::lock_guard lock(mutex); - ensure(vport); - - // Check if it's a bound P2P socket - if ((sn_pfd.events & SYS_NET_POLLIN) && !data.empty()) - { - sys_net.trace("[P2P] p2p_data for vport %d contains %d elements", vport, data.size()); - sn_pfd.revents |= SYS_NET_POLLIN; - } - - // Data can always be written on a dgram socket - if (sn_pfd.events & SYS_NET_POLLOUT) - { - sn_pfd.revents |= SYS_NET_POLLOUT; - } - - return sn_pfd.revents ? 1 : 0; -} - -std::tuple lv2_socket_p2p::select(bs_t selected, [[maybe_unused]] pollfd& native_pfd) -{ - std::lock_guard lock(mutex); - - bool read_set = false; - bool write_set = false; - - // Check if it's a bound P2P socket - if ((selected & lv2_socket::poll_t::read) && vport && !data.empty()) - { - sys_net.trace("[P2P] p2p_data for vport %d contains %d elements", vport, data.size()); - read_set = true; - } - - if (selected & lv2_socket::poll_t::write) - { - write_set = true; - } - - return {read_set, write_set, false}; -} diff --git a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2p.h b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2p.h deleted file mode 100644 index b8fadb3d5..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2p.h +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once - -#include "lv2_socket.h" - -class lv2_socket_p2p : public lv2_socket -{ -public: - lv2_socket_p2p(lv2_socket_family family, lv2_socket_type type, lv2_ip_protocol protocol); - lv2_socket_p2p(utils::serial& ar, lv2_socket_type type); - void save(utils::serial& ar); - - std::tuple, sys_net_sockaddr> accept(bool is_lock = true) override; - s32 bind(const sys_net_sockaddr& addr) override; - - std::optional connect(const sys_net_sockaddr& addr) override; - s32 connect_followup() override; - - std::pair getpeername() override; - std::pair getsockname() override; - - std::tuple getsockopt(s32 level, s32 optname, u32 len) override; - s32 setsockopt(s32 level, s32 optname, const std::vector& optval) override; - - s32 listen(s32 backlog) override; - - std::optional, sys_net_sockaddr>> recvfrom(s32 flags, u32 len, bool is_lock = true) override; - std::optional sendto(s32 flags, const std::vector& buf, std::optional opt_sn_addr, bool is_lock = true) override; - std::optional sendmsg(s32 flags, const sys_net_msghdr& msg, bool is_lock = true) override; - - void close() override; - s32 shutdown(s32 how) override; - - s32 poll(sys_net_pollfd& sn_pfd, pollfd& native_pfd) override; - std::tuple select(bs_t selected, pollfd& native_pfd) override; - - void handle_new_data(sys_net_sockaddr_in_p2p p2p_addr, std::vector p2p_data); - -protected: - // Port(actual bound port) and Virtual Port(indicated by u16 at the start of the packet) - u16 port = 3658, vport = 0; - u32 bound_addr = 0; - // Queue containing received packets from network_thread for SYS_NET_SOCK_DGRAM_P2P sockets - std::queue>> data{}; - // List of sock options - std::map sockopts; -}; diff --git a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2ps.cpp b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2ps.cpp deleted file mode 100644 index f199ab079..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2ps.cpp +++ /dev/null @@ -1,1048 +0,0 @@ -#include "stdafx.h" - -#include "util/Thread.h" -#include "util/atomic.hpp" -#include "lv2_socket_p2ps.h" -#include "Emu/NP/np_helpers.h" -#include "nt_p2p_port.h" -#include "network_context.h" -#include "sys_net_helpers.h" - -LOG_CHANNEL(sys_net); - -// Object in charge of retransmiting packets for STREAM_P2P sockets -class tcp_timeout_monitor -{ -public: - void add_message(s32 sock_id, const sockaddr_in* dst, std::vector data, u64 seq) - { - { - std::lock_guard lock(data_mutex); - - const auto now = steady_clock::now(); - - message msg; - msg.dst_addr = *dst; - msg.sock_id = sock_id; - msg.data = std::move(data); - msg.seq = seq; - msg.initial_sendtime = now; - - rtt_info rtt = rtts[sock_id]; - - const auto expected_time = now + rtt.rtt_time; - - msgs.insert(std::make_pair(expected_time, std::move(msg))); - } - wakey.release(1); - wakey.notify_one(); // TODO: Should be improved to only wake if new timeout < old timeout - } - - void confirm_data_received(s32 sock_id, u64 ack) - { - std::lock_guard lock(data_mutex); - rtts[sock_id].num_retries = 0; - - const auto now = steady_clock::now(); - - for (auto it = msgs.begin(); it != msgs.end();) - { - auto& msg = it->second; - if (msg.sock_id == sock_id && msg.seq < ack) - { - // Decreases RTT if msg is early - if (now < it->first) - { - const auto actual_rtt = std::chrono::duration_cast(now - it->second.initial_sendtime); - const auto cur_rtt = rtts[sock_id].rtt_time; - if (cur_rtt > actual_rtt) - { - rtts[sock_id].rtt_time = (actual_rtt + cur_rtt) / 2; - } - } - it = msgs.erase(it); - continue; - } - it++; - } - } - - void clear_all_messages(s32 sock_id) - { - std::lock_guard lock(data_mutex); - - for (auto it = msgs.begin(); it != msgs.end();) - { - auto& msg = it->second; - - if (msg.sock_id == sock_id) - { - it = msgs.erase(it); - continue; - } - it++; - } - } - - void operator()() - { - atomic_wait_timeout timeout = atomic_wait_timeout::inf; - - while (thread_ctrl::state() != thread_state::aborting) - { - if (!wakey) - { - wakey.wait(0, timeout); - } - - wakey = 0; - - if (thread_ctrl::state() == thread_state::aborting) - return; - - std::lock_guard lock(data_mutex); - - const auto now = steady_clock::now(); - // Check for messages that haven't been acked - std::set rtt_increased; - for (auto it = msgs.begin(); it != msgs.end();) - { - if (it->first > now) - break; - - // reply is late, increases rtt - auto& msg = it->second; - const auto addr = msg.dst_addr.sin_addr.s_addr; - rtt_info rtt = rtts[msg.sock_id]; - // Only increases rtt once per loop(in case a big number of packets are sent at once) - if (!rtt_increased.count(msg.sock_id)) - { - rtt.num_retries += 1; - // Increases current rtt by 10% - rtt.rtt_time += (rtt.rtt_time / 10); - rtts[addr] = rtt; - - rtt_increased.emplace(msg.sock_id); - } - - if (rtt.num_retries >= 10) - { - // Too many retries, need to notify the socket that the connection is dead - idm::check(msg.sock_id, [&](lv2_socket& sock) - { - sys_net.error("[P2PS] Too many retries, closing the stream"); - ensure(sock.get_type() == SYS_NET_SOCK_STREAM_P2P); - auto& sock_p2ps = reinterpret_cast(sock); - sock_p2ps.close_stream(); - }); - it = msgs.erase(it); - continue; - } - - // resend the message - const auto res = idm::check(msg.sock_id, [&](lv2_socket& sock) -> bool - { - ensure(sock.get_type() == SYS_NET_SOCK_STREAM_P2P); - auto& sock_p2ps = reinterpret_cast(sock); - - while (np::sendto_possibly_ipv6(sock_p2ps.get_socket(), reinterpret_cast(msg.data.data()), ::size32(msg.data), &msg.dst_addr, 0) == -1) - { - const sys_net_error err = get_last_error(false); - // concurrency on the socket(from a sendto for example) can result in EAGAIN error in which case we try again - if (err == SYS_NET_EAGAIN) - { - continue; - } - - sys_net.error("[P2PS] Resending the packet failed(%s), closing the stream", err); - sock_p2ps.close_stream(); - return false; - } - return true; - }); - - if (!res || !res.ret) - { - it = msgs.erase(it); - continue; - } - - // Update key timeout - msgs.insert(std::make_pair(now + rtt.rtt_time, std::move(msg))); - it = msgs.erase(it); - } - - if (!msgs.empty()) - { - const auto current_timepoint = steady_clock::now(); - const auto expected_timepoint = msgs.begin()->first; - if (current_timepoint > expected_timepoint) - { - wakey = 1; - } - else - { - timeout = static_cast(std::chrono::duration_cast(expected_timepoint - current_timepoint).count()); - } - } - else - { - timeout = atomic_wait_timeout::inf; - } - } - } - - tcp_timeout_monitor& operator=(thread_state) - { - wakey.release(1); - wakey.notify_one(); - return *this; - } - -public: - static constexpr auto thread_name = "Tcp Over Udp Timeout Manager Thread"sv; - -private: - atomic_t wakey = 0; - shared_mutex data_mutex; - // List of outgoing messages - struct message - { - s32 sock_id = 0; - ::sockaddr_in dst_addr{}; - std::vector data; - u64 seq = 0; - steady_clock::time_point initial_sendtime{}; - }; - std::map msgs; // (wakeup time, msg) - // List of rtts - struct rtt_info - { - unsigned long num_retries = 0; - std::chrono::milliseconds rtt_time = 50ms; - }; - std::unordered_map rtts; // (sock_id, rtt) -}; - -u16 u2s_tcp_checksum(const le_t* buffer, usz size) -{ - u32 cksum = 0; - while (size > 1) - { - cksum += *buffer++; - size -= sizeof(u16); - } - if (size) - cksum += *reinterpret_cast(buffer); - - cksum = (cksum >> 16) + (cksum & 0xffff); - cksum += (cksum >> 16); - return static_cast(~cksum); -} - -std::vector generate_u2s_packet(const p2ps_encapsulated_tcp& header, const u8* data, const u32 datasize) -{ - const u32 packet_size = (VPORT_P2P_HEADER_SIZE + sizeof(p2ps_encapsulated_tcp) + datasize); - ensure(packet_size < 65535); // packet size shouldn't be bigger than possible UDP payload - std::vector packet(packet_size); - u8* packet_data = packet.data(); - le_t dst_port_le = +header.dst_port; - le_t src_port_le = +header.src_port; - le_t p2p_flags_le = P2P_FLAG_P2PS; - - memcpy(packet_data, &dst_port_le, sizeof(u16)); - memcpy(packet_data + sizeof(u16), &src_port_le, sizeof(u16)); - memcpy(packet_data + sizeof(u16) + sizeof(u16), &p2p_flags_le, sizeof(u16)); - memcpy(packet_data + VPORT_P2P_HEADER_SIZE, &header, sizeof(p2ps_encapsulated_tcp)); - if (datasize) - memcpy(packet_data + VPORT_P2P_HEADER_SIZE + sizeof(p2ps_encapsulated_tcp), data, datasize); - - auto* hdr_ptr = reinterpret_cast(packet_data + VPORT_P2P_HEADER_SIZE); - hdr_ptr->checksum = 0; - hdr_ptr->checksum = u2s_tcp_checksum(utils::bless>(hdr_ptr), sizeof(p2ps_encapsulated_tcp) + datasize); - - return packet; -} - -lv2_socket_p2ps::lv2_socket_p2ps(lv2_socket_family family, lv2_socket_type type, lv2_ip_protocol protocol) - : lv2_socket_p2p(family, type, protocol) -{ - sockopt_cache cache_type; - cache_type.data._int = SYS_NET_SOCK_STREAM_P2P; - cache_type.len = 4; - - sockopts[(static_cast(SYS_NET_SOL_SOCKET) << 32ull) | SYS_NET_SO_TYPE] = cache_type; -} - -lv2_socket_p2ps::lv2_socket_p2ps(socket_type native_socket, u16 port, u16 vport, u32 op_addr, u16 op_port, u16 op_vport, u64 cur_seq, u64 data_beg_seq, s32 so_nbio) - : lv2_socket_p2p(SYS_NET_AF_INET, SYS_NET_SOCK_STREAM_P2P, SYS_NET_IPPROTO_IP) -{ - this->native_socket = native_socket; - this->port = port; - this->vport = vport; - this->op_addr = op_addr; - this->op_port = op_port; - this->op_vport = op_vport; - this->cur_seq = cur_seq; - this->data_beg_seq = data_beg_seq; - this->so_nbio = so_nbio; - status = p2ps_stream_status::stream_connected; -} - -lv2_socket_p2ps::lv2_socket_p2ps(utils::serial& ar, lv2_socket_type type) - : lv2_socket_p2p(ar, type) -{ - ar(status, max_backlog, backlog, op_port, op_vport, op_addr, data_beg_seq, received_data, cur_seq); -} - -void lv2_socket_p2ps::save(utils::serial& ar) -{ - static_cast(this)->save(ar); - ar(status, max_backlog, backlog, op_port, op_vport, op_addr, data_beg_seq, received_data, cur_seq); -} - -bool lv2_socket_p2ps::handle_connected(p2ps_encapsulated_tcp* tcp_header, u8* data, ::sockaddr_storage* op_addr, nt_p2p_port* p2p_port) -{ - std::lock_guard lock(mutex); - - if (status != p2ps_stream_status::stream_connected && status != p2ps_stream_status::stream_handshaking) - { - sys_net.error("[P2PS] lv2_socket_p2ps::handle_connected() called on a non connected/handshaking socket(%d)!", static_cast(status)); - return false; - } - - if (tcp_header->flags & static_cast(p2ps_tcp_flags::ACK)) - { - auto& tcpm = g_fxo->get>(); - tcpm.confirm_data_received(lv2_id, tcp_header->ack); - } - - auto send_ack = [&]() - { - auto final_ack = data_beg_seq; - while (received_data.contains(final_ack)) - { - final_ack += ::at32(received_data, final_ack).size(); - } - data_available = final_ack - data_beg_seq; - - p2ps_encapsulated_tcp send_hdr; - send_hdr.src_port = tcp_header->dst_port; - send_hdr.dst_port = tcp_header->src_port; - send_hdr.flags = p2ps_tcp_flags::ACK; - send_hdr.ack = final_ack; - auto packet = generate_u2s_packet(send_hdr, nullptr, 0); - sys_net.trace("[P2PS] Sent ack %d", final_ack); - send_u2s_packet(std::move(packet), reinterpret_cast<::sockaddr_in*>(op_addr), 0, false); - - // check if polling is happening - if (data_available && events.test_and_reset(lv2_socket::poll_t::read)) - { - bs_t read_event = lv2_socket::poll_t::read; - for (auto it = queue.begin(); it != queue.end();) - { - if (it->second(read_event)) - { - it = queue.erase(it); - continue; - } - it++; - } - - if (queue.empty()) - { - events.store({}); - } - } - }; - - if (status == p2ps_stream_status::stream_handshaking) - { - // Only expect SYN|ACK - if (tcp_header->flags == (p2ps_tcp_flags::SYN | p2ps_tcp_flags::ACK)) - { - sys_net.trace("[P2PS] Received SYN|ACK, status is now connected"); - data_beg_seq = tcp_header->seq + 1; - status = p2ps_stream_status::stream_connected; - send_ack(); - } - else - { - sys_net.error("[P2PS] Unexpected U2S TCP flag received with handshaking state: 0x%02X", tcp_header->flags); - } - - return true; - } - else if (status == p2ps_stream_status::stream_connected) - { - switch (tcp_header->flags) - { - case 0: - case p2ps_tcp_flags::PSH: - case p2ps_tcp_flags::ACK: - case p2ps_tcp_flags::SYN: - case p2ps_tcp_flags::SYN | p2ps_tcp_flags::ACK: - { - if (tcp_header->seq < data_beg_seq) - { - // Data has already been processed - sys_net.trace("[P2PS] Data has already been processed"); - if (tcp_header->flags != p2ps_tcp_flags::ACK) - send_ack(); - return true; - } - - if (!received_data.count(tcp_header->seq)) - { - // New data - received_data.emplace(tcp_header->seq, std::vector(data, data + tcp_header->length)); - } - else - { - sys_net.trace("[P2PS] Data was not new!"); - } - - send_ack(); - return true; - } - case p2ps_tcp_flags::RST: - case p2ps_tcp_flags::FIN: - { - sys_net.error("[P2PS] Received RST/FIN packet(%d), closing the stream", tcp_header->flags); - close_stream_nl(p2p_port); - return false; - } - default: - { - sys_net.error("[P2PS] Unexpected U2S TCP flag received with connected state: 0x%02X", tcp_header->flags); - return true; - } - } - } - - return true; -} - -bool lv2_socket_p2ps::handle_listening(p2ps_encapsulated_tcp* tcp_header, [[maybe_unused]] u8* data, ::sockaddr_storage* op_addr) -{ - std::lock_guard lock(mutex); - - if (status != p2ps_stream_status::stream_listening) - { - sys_net.error("[P2PS] lv2_socket_p2ps::handle_listening() called on a non listening socket(%d)!", static_cast(status)); - return false; - } - - // Only valid packet - if (tcp_header->flags == static_cast(p2ps_tcp_flags::SYN)) - { - if (backlog.size() >= max_backlog) - { - // Send a RST packet on backlog full - sys_net.trace("[P2PS] Backlog was full, sent a RST packet"); - p2ps_encapsulated_tcp send_hdr; - send_hdr.src_port = tcp_header->dst_port; - send_hdr.dst_port = tcp_header->src_port; - send_hdr.flags = p2ps_tcp_flags::RST; - auto packet = generate_u2s_packet(send_hdr, nullptr, 0); - send_u2s_packet(std::move(packet), reinterpret_cast<::sockaddr_in*>(op_addr), 0, false); - return true; - } - - // Yes, new connection and a backlog is available, create a new lv2_socket for it and send SYN|ACK - // Prepare reply packet - sys_net.notice("[P2PS] Received connection on listening STREAM-P2P socket!"); - p2ps_encapsulated_tcp send_hdr; - send_hdr.src_port = tcp_header->dst_port; - send_hdr.dst_port = tcp_header->src_port; - send_hdr.flags = p2ps_tcp_flags::SYN | p2ps_tcp_flags::ACK; - send_hdr.ack = tcp_header->seq + 1; - // Generates random starting SEQ - send_hdr.seq = rand(); - - // Create new socket - const u32 new_op_addr = reinterpret_cast(op_addr)->sin_addr.s_addr; - const u16 new_op_port = std::bit_cast>((reinterpret_cast(op_addr)->sin_port)); - const u16 new_op_vport = tcp_header->src_port; - const u64 new_cur_seq = send_hdr.seq + 1; - const u64 new_data_beg_seq = send_hdr.ack; - auto sock_lv2 = make_shared(native_socket, port, vport, new_op_addr, new_op_port, new_op_vport, new_cur_seq, new_data_beg_seq, so_nbio); - const s32 new_sock_id = idm::import_existing(sock_lv2); - sock_lv2->set_lv2_id(new_sock_id); - const u64 key_connected = (reinterpret_cast(op_addr)->sin_addr.s_addr) | (static_cast(tcp_header->src_port) << 48) | (static_cast(tcp_header->dst_port) << 32); - - { - auto& nc = g_fxo->get(); - auto& pport = ::at32(nc.list_p2p_ports, port); - pport.bound_p2p_streams.emplace(key_connected, new_sock_id); - } - - auto packet = generate_u2s_packet(send_hdr, nullptr, 0); - { - std::lock_guard lock(sock_lv2->mutex); - sock_lv2->send_u2s_packet(std::move(packet), reinterpret_cast<::sockaddr_in*>(op_addr), send_hdr.seq, true); - } - - backlog.push_back(new_sock_id); - if (events.test_and_reset(lv2_socket::poll_t::read)) - { - bs_t read_event = lv2_socket::poll_t::read; - for (auto it = queue.begin(); it != queue.end();) - { - if (it->second(read_event)) - { - it = queue.erase(it); - continue; - } - it++; - } - - if (queue.empty()) - { - events.store({}); - } - } - } - else - { - sys_net.error("[P2PS] Unexpected U2S TCP flag received on listening socket: 0x%02X", tcp_header->flags); - } - - // Ignore other packets? - - return true; -} - -void lv2_socket_p2ps::send_u2s_packet(std::vector data, const ::sockaddr_in* dst, u64 seq, bool require_ack) -{ - char ip_str[16]; - inet_ntop(AF_INET, &dst->sin_addr, ip_str, sizeof(ip_str)); - sys_net.trace("[P2PS] Sending U2S packet on socket %d(id:%d): data(%d, seq %d, require_ack %d) to %s:%d", native_socket, lv2_id, data.size(), seq, require_ack, ip_str, std::bit_cast>(dst->sin_port)); - - while (np::sendto_possibly_ipv6(native_socket, reinterpret_cast(data.data()), ::size32(data), dst, 0) == -1) - { - const sys_net_error err = get_last_error(false); - // concurrency on the socket can result in EAGAIN error in which case we try again - if (err == SYS_NET_EAGAIN) - { - continue; - } - - sys_net.error("[P2PS] Attempting to send a u2s packet failed(%s)!", err); - return; - } - - // Adds to tcp timeout monitor to resend the message until an ack is received - if (require_ack) - { - auto& tcpm = g_fxo->get>(); - tcpm.add_message(lv2_id, dst, std::move(data), seq); - } -} - -void lv2_socket_p2ps::close_stream_nl(nt_p2p_port* p2p_port) -{ - status = p2ps_stream_status::stream_closed; - - for (auto it = p2p_port->bound_p2p_streams.begin(); it != p2p_port->bound_p2p_streams.end();) - { - if (it->second == lv2_id) - { - it = p2p_port->bound_p2p_streams.erase(it); - continue; - } - it++; - } - - auto& tcpm = g_fxo->get>(); - tcpm.clear_all_messages(lv2_id); -} - -void lv2_socket_p2ps::close_stream() -{ - auto& nc = g_fxo->get(); - - std::lock_guard lock(nc.list_p2p_ports_mutex); - auto& p2p_port = ::at32(nc.list_p2p_ports, port); - - std::scoped_lock more_lock(p2p_port.bound_p2p_vports_mutex, mutex); - close_stream_nl(&p2p_port); -} - -p2ps_stream_status lv2_socket_p2ps::get_status() const -{ - return status; -} - -void lv2_socket_p2ps::set_status(p2ps_stream_status new_status) -{ - status = new_status; -} - -std::pair lv2_socket_p2ps::getpeername() -{ - std::lock_guard lock(mutex); - - if (!op_addr || !op_port || !op_vport) - { - return {-SYS_NET_ENOTCONN, {}}; - } - - sys_net_sockaddr res{}; - sys_net_sockaddr_in_p2p* p2p_addr = reinterpret_cast(&res); - - p2p_addr->sin_len = sizeof(sys_net_sockaddr_in_p2p); - p2p_addr->sin_family = SYS_NET_AF_INET; - p2p_addr->sin_addr = std::bit_cast, u32>(op_addr); - p2p_addr->sin_port = op_vport; - p2p_addr->sin_vport = op_port; - - return {CELL_OK, res}; -} - -std::tuple, sys_net_sockaddr> lv2_socket_p2ps::accept(bool is_lock) -{ - std::unique_lock lock(mutex, std::defer_lock); - - if (is_lock) - { - lock.lock(); - } - - if (backlog.size() == 0) - { - if (so_nbio) - { - return {true, -SYS_NET_EWOULDBLOCK, {}, {}}; - } - - return {false, {}, {}, {}}; - } - - auto p2ps_client = backlog.front(); - backlog.pop_front(); - - sys_net_sockaddr ps3_addr{}; - auto* paddr = reinterpret_cast(&ps3_addr); - - lv2_socket_p2ps* sock_client = reinterpret_cast(idm::check_unlocked(p2ps_client)); - { - std::lock_guard lock(sock_client->mutex); - paddr->sin_family = SYS_NET_AF_INET; - paddr->sin_addr = std::bit_cast, u32>(sock_client->op_addr); - paddr->sin_port = sock_client->op_vport; - paddr->sin_vport = sock_client->op_port; - paddr->sin_len = sizeof(sys_net_sockaddr_in_p2p); - } - - return {true, p2ps_client, {}, ps3_addr}; -} - -s32 lv2_socket_p2ps::bind(const sys_net_sockaddr& addr) -{ - const auto* psa_in_p2p = reinterpret_cast(&addr); - - // For SYS_NET_SOCK_STREAM_P2P sockets, the port is the "fake" tcp port and the vport is the udp port it's bound to - u16 p2p_port = psa_in_p2p->sin_vport; - u16 p2p_vport = psa_in_p2p->sin_port; - - sys_net.notice("[P2PS] Trying to bind %s:%d:%d", np::ip_to_string(std::bit_cast(psa_in_p2p->sin_addr)), p2p_port, p2p_vport); - - if (p2p_port == 0) - { - p2p_port = SCE_NP_PORT; - } - - if (p2p_port != SCE_NP_PORT) - { - sys_net.warning("[P2PS] Attempting to bind a socket to a port != %d", +SCE_NP_PORT); - } - - socket_type real_socket{}; - - auto& nc = g_fxo->get(); - { - std::lock_guard list_lock(nc.list_p2p_ports_mutex); - - nc.create_p2p_port(p2p_port); - auto& pport = ::at32(nc.list_p2p_ports, p2p_port); - real_socket = pport.p2p_socket; - - { - // Ensures the socket & the bound list are updated at the same time to avoid races - std::lock_guard vport_lock(pport.bound_p2p_vports_mutex); - std::lock_guard sock_lock(mutex); - - if (p2p_vport == 0) - { - sys_net.warning("[P2PS] vport was unassigned in bind!"); - p2p_vport = pport.get_port(); - - while (pport.bound_p2ps_vports.contains(p2p_vport)) - { - p2p_vport = pport.get_port(); - } - - std::set bound_ports{lv2_id}; - pport.bound_p2ps_vports.insert(std::make_pair(p2p_vport, std::move(bound_ports))); - } - else - { - if (pport.bound_p2ps_vports.contains(p2p_vport)) - { - auto& bound_sockets = ::at32(pport.bound_p2ps_vports, p2p_vport); - if (!sys_net_helpers::all_reusable(bound_sockets)) - { - return -SYS_NET_EADDRINUSE; - } - - bound_sockets.insert(lv2_id); - } - else - { - std::set bound_ports{lv2_id}; - pport.bound_p2ps_vports.insert(std::make_pair(p2p_vport, std::move(bound_ports))); - } - } - - port = p2p_port; - vport = p2p_vport; - native_socket = real_socket; - bound_addr = psa_in_p2p->sin_addr; - } - } - - return CELL_OK; -} - -std::pair lv2_socket_p2ps::getsockname() -{ - std::lock_guard lock(mutex); - - // Unbound socket - if (!native_socket) - { - return {CELL_OK, {}}; - } - - sys_net_sockaddr sn_addr{}; - sys_net_sockaddr_in_p2p* paddr = reinterpret_cast(&sn_addr); - - paddr->sin_len = sizeof(sys_net_sockaddr_in); - paddr->sin_family = SYS_NET_AF_INET; - paddr->sin_port = vport; - paddr->sin_vport = port; - paddr->sin_addr = bound_addr; - - return {CELL_OK, sn_addr}; -} - -std::optional lv2_socket_p2ps::connect(const sys_net_sockaddr& addr) -{ - std::lock_guard lock(mutex); - - if (status != p2ps_stream_status::stream_closed) - { - sys_net.error("[P2PS] Called connect on a socket that is not closed!"); - return -SYS_NET_EALREADY; - } - - p2ps_encapsulated_tcp send_hdr; - const auto psa_in_p2p = reinterpret_cast(&addr); - auto name = sys_net_addr_to_native_addr(addr); - - // This is purposefully inverted, not a bug - const u16 dst_vport = psa_in_p2p->sin_port; - const u16 dst_port = psa_in_p2p->sin_vport; - - socket_type real_socket{}; - - auto& nc = g_fxo->get(); - { - std::lock_guard list_lock(nc.list_p2p_ports_mutex); - - nc.create_p2p_port(port); - auto& pport = ::at32(nc.list_p2p_ports, port); - real_socket = pport.p2p_socket; - - { - std::lock_guard lock(pport.bound_p2p_vports_mutex); - if (vport == 0) - { - // Unassigned vport, assigns one - sys_net.warning("[P2PS] vport was unassigned before connect!"); - vport = pport.get_port(); - - while (pport.bound_p2p_vports.count(vport) || pport.bound_p2p_streams.count(static_cast(vport) << 32)) - { - vport = pport.get_port(); - } - } - const u64 key = name.sin_addr.s_addr | (static_cast(vport) << 32) | (static_cast(dst_vport) << 48); - pport.bound_p2p_streams.emplace(key, lv2_id); - } - } - - native_socket = real_socket; - - send_hdr.src_port = vport; - send_hdr.dst_port = dst_vport; - send_hdr.flags = p2ps_tcp_flags::SYN; - send_hdr.seq = rand(); - - op_addr = name.sin_addr.s_addr; - op_port = dst_port; - op_vport = dst_vport; - cur_seq = send_hdr.seq + 1; - data_beg_seq = 0; - data_available = 0u; - received_data.clear(); - status = p2ps_stream_status::stream_handshaking; - - std::vector packet = generate_u2s_packet(send_hdr, nullptr, 0); - name.sin_port = std::bit_cast>(dst_port); // not a bug - send_u2s_packet(std::move(packet), reinterpret_cast<::sockaddr_in*>(&name), send_hdr.seq, true); - - return CELL_OK; -} - -s32 lv2_socket_p2ps::listen(s32 backlog) -{ - std::lock_guard lock(mutex); - - status = p2ps_stream_status::stream_listening; - max_backlog = backlog; - - return CELL_OK; -} - -std::optional, sys_net_sockaddr>> lv2_socket_p2ps::recvfrom([[maybe_unused]] s32 flags, u32 len, bool is_lock) -{ - std::unique_lock lock(mutex, std::defer_lock); - - if (is_lock) - { - lock.lock(); - } - - if (!data_available) - { - if (status == p2ps_stream_status::stream_closed) - { - sys_net.error("[P2PS] Called recvfrom on closed socket!"); - return {{0, {}, {}}}; - } - - if (so_nbio || (flags & SYS_NET_MSG_DONTWAIT)) - { - return {{-SYS_NET_EWOULDBLOCK, {}, {}}}; - } - - return std::nullopt; - } - - const u32 to_give = static_cast(std::min(data_available, len)); - sys_net_sockaddr addr{}; - std::vector dest_buf(to_give); - - sys_net.trace("[P2PS] STREAM-P2P socket had %u available, given %u", data_available, to_give); - - u32 left_to_give = to_give; - while (left_to_give) - { - auto& cur_data = received_data.begin()->second; - auto to_give_for_this_packet = std::min(static_cast(cur_data.size()), left_to_give); - memcpy(dest_buf.data() + (to_give - left_to_give), cur_data.data(), to_give_for_this_packet); - if (cur_data.size() != to_give_for_this_packet) - { - auto amount_left = cur_data.size() - to_give_for_this_packet; - std::vector new_vec(amount_left); - memcpy(new_vec.data(), cur_data.data() + to_give_for_this_packet, amount_left); - auto new_key = (received_data.begin()->first) + to_give_for_this_packet; - received_data.emplace(new_key, std::move(new_vec)); - } - - received_data.erase(received_data.begin()); - - left_to_give -= to_give_for_this_packet; - } - - data_available -= to_give; - data_beg_seq += to_give; - - sys_net_sockaddr_in_p2p* addr_p2p = reinterpret_cast(&addr); - addr_p2p->sin_family = AF_INET; - addr_p2p->sin_addr = std::bit_cast, u32>(op_addr); - addr_p2p->sin_port = op_vport; - addr_p2p->sin_vport = op_port; - addr_p2p->sin_len = sizeof(sys_net_sockaddr_in_p2p); - - return {{to_give, dest_buf, addr}}; -} - -std::optional lv2_socket_p2ps::sendto([[maybe_unused]] s32 flags, const std::vector& buf, std::optional opt_sn_addr, bool is_lock) -{ - std::unique_lock lock(mutex, std::defer_lock); - - if (is_lock) - { - lock.lock(); - } - - if (status == p2ps_stream_status::stream_closed) - { - sys_net.error("[P2PS] Called sendto on a closed socket!"); - return -SYS_NET_ECONNRESET; - } - - constexpr u32 max_data_len = (65535 - (VPORT_P2P_HEADER_SIZE + sizeof(p2ps_encapsulated_tcp))); - - ::sockaddr_in name{}; - if (opt_sn_addr) - { - name = sys_net_addr_to_native_addr(*opt_sn_addr); - } - - // Prepare address - name.sin_family = AF_INET; - name.sin_port = std::bit_cast>(op_port); - name.sin_addr.s_addr = op_addr; - // Prepares encapsulated tcp - p2ps_encapsulated_tcp tcp_header; - tcp_header.src_port = vport; - tcp_header.dst_port = op_vport; - // chop it up - std::vector> stream_packets; - u32 cur_total_len = ::size32(buf); - while (cur_total_len > 0) - { - u32 cur_data_len = std::min(cur_total_len, max_data_len); - - tcp_header.length = cur_data_len; - tcp_header.seq = cur_seq; - - auto packet = generate_u2s_packet(tcp_header, &buf[buf.size() - cur_total_len], cur_data_len); - send_u2s_packet(std::move(packet), &name, tcp_header.seq, true); - - cur_total_len -= cur_data_len; - cur_seq += cur_data_len; - } - - return {::size32(buf)}; -} - -std::optional lv2_socket_p2ps::sendmsg([[maybe_unused]] s32 flags, [[maybe_unused]] const sys_net_msghdr& msg, [[maybe_unused]] bool is_lock) -{ - sys_net.todo("lv2_socket_p2ps::sendmsg"); - return {}; -} - -void lv2_socket_p2ps::close() -{ - if (!port || !vport) - { - return; - } - - if (g_fxo->is_init()) - { - auto& nc = g_fxo->get(); - std::lock_guard lock(nc.list_p2p_ports_mutex); - auto& p2p_port = ::at32(nc.list_p2p_ports, port); - { - std::lock_guard lock(p2p_port.bound_p2p_vports_mutex); - for (auto it = p2p_port.bound_p2p_streams.begin(); it != p2p_port.bound_p2p_streams.end();) - { - if (it->second == lv2_id) - { - it = p2p_port.bound_p2p_streams.erase(it); - continue; - } - it++; - } - - if (p2p_port.bound_p2ps_vports.contains(vport)) - { - auto& bound_ports = ::at32(p2p_port.bound_p2ps_vports, vport); - bound_ports.erase(lv2_id); - - if (bound_ports.empty()) - { - p2p_port.bound_p2ps_vports.erase(vport); - } - } - } - } - - if (const auto tcpm = g_fxo->try_get>()) - { - tcpm->clear_all_messages(lv2_id); - } -} - -s32 lv2_socket_p2ps::shutdown([[maybe_unused]] s32 how) -{ - sys_net.todo("[P2PS] shutdown"); - return CELL_OK; -} - -s32 lv2_socket_p2ps::poll(sys_net_pollfd& sn_pfd, [[maybe_unused]] pollfd& native_pfd) -{ - std::lock_guard lock(mutex); - sys_net.trace("[P2PS] poll checking for 0x%X", sn_pfd.events); - if (status == p2ps_stream_status::stream_connected) - { - if ((sn_pfd.events & SYS_NET_POLLIN) && data_available) - { - sys_net.trace("[P2PS] p2ps has %u bytes available", data_available); - sn_pfd.revents |= SYS_NET_POLLIN; - } - - // Data can only be written if the socket is connected - if (sn_pfd.events & SYS_NET_POLLOUT && status == p2ps_stream_status::stream_connected) - { - sn_pfd.revents |= SYS_NET_POLLOUT; - } - - if (sn_pfd.revents) - { - return 1; - } - } - - return 0; -} - -std::tuple lv2_socket_p2ps::select(bs_t selected, [[maybe_unused]] pollfd& native_pfd) -{ - std::lock_guard lock(mutex); - - bool read_set = false; - bool write_set = false; - - if (status == p2ps_stream_status::stream_connected) - { - if ((selected & lv2_socket::poll_t::read) && data_available) - { - sys_net.trace("[P2PS] socket has %d bytes available", data_available); - read_set = true; - } - - if (selected & lv2_socket::poll_t::write) - { - sys_net.trace("[P2PS] socket is writeable"); - write_set = true; - } - } - else if (status == p2ps_stream_status::stream_listening) - { - const auto bsize = backlog.size(); - if ((selected & lv2_socket::poll_t::read) && bsize) - { - sys_net.trace("[P2PS] socket has %d clients available", bsize); - read_set = true; - } - } - - return {read_set, write_set, false}; -} diff --git a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2ps.h b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2ps.h deleted file mode 100644 index f06630b39..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2ps.h +++ /dev/null @@ -1,114 +0,0 @@ -#pragma once - -#ifdef _WIN32 -#include -#include -#else -#ifdef __clang__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wold-style-cast" -#endif -#include -#ifdef __clang__ -#pragma GCC diagnostic pop -#endif -#endif - -#include "lv2_socket_p2p.h" - -struct nt_p2p_port; - -constexpr be_t P2PS_U2S_SIG = (static_cast('U') << 24 | static_cast('2') << 16 | static_cast('S') << 8 | static_cast('0')); - -struct p2ps_encapsulated_tcp -{ - be_t signature = P2PS_U2S_SIG; // Signature to verify it's P2P Stream data - be_t length = 0; // Length of data - be_t seq = 0; // This should be u32 but changed to u64 for simplicity - be_t ack = 0; - be_t src_port = 0; // fake source tcp port - be_t dst_port = 0; // fake dest tcp port(should be == vport) - be_t checksum = 0; - u8 flags = 0; -}; - -enum p2ps_stream_status -{ - stream_closed, // Default when port is not listening nor connected - stream_listening, // Stream is listening, accepting SYN packets - stream_handshaking, // Currently handshaking - stream_connected, // This is an established connection(after tcp handshake) -}; - -enum p2ps_tcp_flags : u8 -{ - FIN = (1 << 0), - SYN = (1 << 1), - RST = (1 << 2), - PSH = (1 << 3), - ACK = (1 << 4), - URG = (1 << 5), - ECE = (1 << 6), - CWR = (1 << 7), -}; - -u16 u2s_tcp_checksum(const le_t* buffer, usz size); -std::vector generate_u2s_packet(const p2ps_encapsulated_tcp& header, const u8* data, const u32 datasize); - -class lv2_socket_p2ps final : public lv2_socket_p2p -{ -public: - static constexpr u32 id_type = 2; - - lv2_socket_p2ps(lv2_socket_family family, lv2_socket_type type, lv2_ip_protocol protocol); - lv2_socket_p2ps(socket_type socket, u16 port, u16 vport, u32 op_addr, u16 op_port, u16 op_vport, u64 cur_seq, u64 data_beg_seq, s32 so_nbio); - lv2_socket_p2ps(utils::serial& ar, lv2_socket_type type); - void save(utils::serial& ar); - - p2ps_stream_status get_status() const; - void set_status(p2ps_stream_status new_status); - bool handle_connected(p2ps_encapsulated_tcp* tcp_header, u8* data, ::sockaddr_storage* op_addr, nt_p2p_port* p2p_port); - bool handle_listening(p2ps_encapsulated_tcp* tcp_header, u8* data, ::sockaddr_storage* op_addr); - void send_u2s_packet(std::vector data, const ::sockaddr_in* dst, u64 seq, bool require_ack); - void close_stream(); - - std::tuple, sys_net_sockaddr> accept(bool is_lock = true) override; - s32 bind(const sys_net_sockaddr& addr) override; - - std::optional connect(const sys_net_sockaddr& addr) override; - - std::pair getpeername() override; - std::pair getsockname() override; - - s32 listen(s32 backlog) override; - - std::optional, sys_net_sockaddr>> recvfrom(s32 flags, u32 len, bool is_lock = true) override; - std::optional sendto(s32 flags, const std::vector& buf, std::optional opt_sn_addr, bool is_lock = true) override; - std::optional sendmsg(s32 flags, const sys_net_msghdr& msg, bool is_lock = true) override; - - void close() override; - s32 shutdown(s32 how) override; - - s32 poll(sys_net_pollfd& sn_pfd, pollfd& native_pfd) override; - std::tuple select(bs_t selected, pollfd& native_pfd) override; - -private: - void close_stream_nl(nt_p2p_port* p2p_port); - -private: - static constexpr usz MAX_RECEIVED_BUFFER = (1024 * 1024 * 10); - - p2ps_stream_status status = p2ps_stream_status::stream_closed; - - usz max_backlog = 0; // set on listen - std::deque backlog; - - u16 op_port = 0, op_vport = 0; - u32 op_addr = 0; - - u64 data_beg_seq = 0; // Seq of first byte of received_data - u64 data_available = 0; // Amount of continuous data available(calculated on ACK send) - std::map> received_data; // holds seq/data of data received - - u64 cur_seq = 0; // SEQ of next packet to be sent -}; diff --git a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_raw.cpp b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_raw.cpp deleted file mode 100644 index 3dd109ca0..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_raw.cpp +++ /dev/null @@ -1,147 +0,0 @@ -#include "stdafx.h" -#include "lv2_socket_raw.h" -#include "Emu/NP/vport0.h" - -LOG_CHANNEL(sys_net); - -template -struct socket_raw_logging -{ - socket_raw_logging() = default; - - socket_raw_logging(const socket_raw_logging&) = delete; - socket_raw_logging& operator=(const socket_raw_logging&) = delete; - - atomic_t logged = false; -}; - -#define LOG_ONCE(raw_var, message) \ - if (!g_fxo->get>().logged.exchange(true)) \ - { \ - sys_net.todo(message); \ - } - -lv2_socket_raw::lv2_socket_raw(lv2_socket_family family, lv2_socket_type type, lv2_ip_protocol protocol) - : lv2_socket(family, type, protocol) -{ -} - -lv2_socket_raw::lv2_socket_raw(utils::serial& ar, lv2_socket_type type) - : lv2_socket(make_exact(ar), type) -{ -} - -void lv2_socket_raw::save(utils::serial& ar) -{ - lv2_socket::save(ar, true); -} - -std::tuple, sys_net_sockaddr> lv2_socket_raw::accept([[maybe_unused]] bool is_lock) -{ - sys_net.fatal("[RAW] accept() called on a RAW socket"); - return {}; -} - -std::optional lv2_socket_raw::connect([[maybe_unused]] const sys_net_sockaddr& addr) -{ - sys_net.fatal("[RAW] connect() called on a RAW socket"); - return CELL_OK; -} - -s32 lv2_socket_raw::connect_followup() -{ - sys_net.fatal("[RAW] connect_followup() called on a RAW socket"); - return CELL_OK; -} - -std::pair lv2_socket_raw::getpeername() -{ - LOG_ONCE(raw_getpeername, "[RAW] getpeername() called on a RAW socket"); - return {}; -} - -s32 lv2_socket_raw::listen([[maybe_unused]] s32 backlog) -{ - LOG_ONCE(raw_listen, "[RAW] listen() called on a RAW socket"); - return {}; -} - -s32 lv2_socket_raw::bind([[maybe_unused]] const sys_net_sockaddr& addr) -{ - LOG_ONCE(raw_bind, "lv2_socket_raw::bind"); - return {}; -} - -std::pair lv2_socket_raw::getsockname() -{ - LOG_ONCE(raw_getsockname, "lv2_socket_raw::getsockname"); - return {}; -} - -std::tuple lv2_socket_raw::getsockopt([[maybe_unused]] s32 level, [[maybe_unused]] s32 optname, [[maybe_unused]] u32 len) -{ - LOG_ONCE(raw_getsockopt, "lv2_socket_raw::getsockopt"); - return {}; -} - -s32 lv2_socket_raw::setsockopt(s32 level, s32 optname, const std::vector& optval) -{ - LOG_ONCE(raw_setsockopt, "lv2_socket_raw::setsockopt"); - - // TODO - int native_int = *reinterpret_cast*>(optval.data()); - - if (level == SYS_NET_SOL_SOCKET && optname == SYS_NET_SO_NBIO) - { - so_nbio = native_int; - } - - return {}; -} - -std::optional, sys_net_sockaddr>> lv2_socket_raw::recvfrom(s32 flags, [[maybe_unused]] u32 len, [[maybe_unused]] bool is_lock) -{ - LOG_ONCE(raw_recvfrom, "lv2_socket_raw::recvfrom"); - - if (so_nbio || (flags & SYS_NET_MSG_DONTWAIT)) - { - return {{-SYS_NET_EWOULDBLOCK, {}, {}}}; - } - - return {}; -} - -std::optional lv2_socket_raw::sendto([[maybe_unused]] s32 flags, [[maybe_unused]] const std::vector& buf, [[maybe_unused]] std::optional opt_sn_addr, [[maybe_unused]] bool is_lock) -{ - LOG_ONCE(raw_sendto, "lv2_socket_raw::sendto"); - return ::size32(buf); -} - -std::optional lv2_socket_raw::sendmsg([[maybe_unused]] s32 flags, [[maybe_unused]] const sys_net_msghdr& msg, [[maybe_unused]] bool is_lock) -{ - LOG_ONCE(raw_sendmsg, "lv2_socket_raw::sendmsg"); - return {}; -} - -void lv2_socket_raw::close() -{ - LOG_ONCE(raw_close, "lv2_socket_raw::close"); -} - -s32 lv2_socket_raw::shutdown([[maybe_unused]] s32 how) -{ - LOG_ONCE(raw_shutdown, "lv2_socket_raw::shutdown"); - return {}; -} - -s32 lv2_socket_raw::poll([[maybe_unused]] sys_net_pollfd& sn_pfd, [[maybe_unused]] pollfd& native_pfd) -{ - LOG_ONCE(raw_poll, "lv2_socket_raw::poll"); - return {}; -} - -std::tuple lv2_socket_raw::select([[maybe_unused]] bs_t selected, [[maybe_unused]] pollfd& native_pfd) -{ - LOG_ONCE(raw_select, "lv2_socket_raw::select"); - return {}; -} diff --git a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_raw.h b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_raw.h deleted file mode 100644 index 01b725588..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_raw.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include "lv2_socket.h" - -class lv2_socket_raw final : public lv2_socket -{ -public: - static constexpr u32 id_type = 1; - - lv2_socket_raw(lv2_socket_family family, lv2_socket_type type, lv2_ip_protocol protocol); - lv2_socket_raw(utils::serial& ar, lv2_socket_type type); - void save(utils::serial& ar); - - std::tuple, sys_net_sockaddr> accept(bool is_lock = true) override; - s32 bind(const sys_net_sockaddr& addr) override; - - std::optional connect(const sys_net_sockaddr& addr) override; - s32 connect_followup() override; - - std::pair getpeername() override; - std::pair getsockname() override; - - std::tuple getsockopt(s32 level, s32 optname, u32 len) override; - s32 setsockopt(s32 level, s32 optname, const std::vector& optval) override; - - s32 listen(s32 backlog) override; - - std::optional, sys_net_sockaddr>> recvfrom(s32 flags, u32 len, bool is_lock = true) override; - std::optional sendto(s32 flags, const std::vector& buf, std::optional opt_sn_addr, bool is_lock = true) override; - std::optional sendmsg(s32 flags, const sys_net_msghdr& msg, bool is_lock = true) override; - - void close() override; - s32 shutdown(s32 how) override; - - s32 poll(sys_net_pollfd& sn_pfd, pollfd& native_pfd) override; - std::tuple select(bs_t selected, pollfd& native_pfd) override; -}; diff --git a/rpcs3/Emu/Cell/lv2/sys_net/network_context.cpp b/rpcs3/Emu/Cell/lv2/sys_net/network_context.cpp deleted file mode 100644 index 6f754c9a9..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_net/network_context.cpp +++ /dev/null @@ -1,329 +0,0 @@ -#include "stdafx.h" -#include "Emu/NP/ip_address.h" -#include "Emu/Cell/lv2/sys_sync.h" -#include "rpcsx/fw/ps3/sceNp.h" // for SCE_NP_PORT - -#include "network_context.h" -#include "sys_net_helpers.h" - -LOG_CHANNEL(sys_net); - -// Used by RPCN to send signaling packets to RPCN server(for UDP hole punching) -bool send_packet_from_p2p_port_ipv4(const std::vector& data, const sockaddr_in& addr) -{ - auto& nc = g_fxo->get(); - { - std::lock_guard list_lock(nc.list_p2p_ports_mutex); - if (nc.list_p2p_ports.contains(SCE_NP_PORT)) - { - auto& def_port = ::at32(nc.list_p2p_ports, SCE_NP_PORT); - - if (def_port.is_ipv6) - { - const auto addr6 = np::sockaddr_to_sockaddr6(addr); - - if (::sendto(def_port.p2p_socket, reinterpret_cast(data.data()), ::size32(data), 0, reinterpret_cast(&addr6), sizeof(sockaddr_in6)) == -1) - { - sys_net.error("Failed to send IPv4 signaling packet on IPv6 socket: %s", get_last_error(false, false)); - return false; - } - } - else if (::sendto(def_port.p2p_socket, reinterpret_cast(data.data()), ::size32(data), 0, reinterpret_cast(&addr), sizeof(sockaddr_in)) == -1) - { - sys_net.error("Failed to send signaling packet on IPv4 socket: %s", get_last_error(false, false)); - return false; - } - } - else - { - sys_net.error("send_packet_from_p2p_port_ipv4: port %d not present", +SCE_NP_PORT); - return false; - } - } - - return true; -} - -bool send_packet_from_p2p_port_ipv6(const std::vector& data, const sockaddr_in6& addr) -{ - auto& nc = g_fxo->get(); - { - std::lock_guard list_lock(nc.list_p2p_ports_mutex); - if (nc.list_p2p_ports.contains(SCE_NP_PORT)) - { - auto& def_port = ::at32(nc.list_p2p_ports, SCE_NP_PORT); - ensure(def_port.is_ipv6); - - if (::sendto(def_port.p2p_socket, reinterpret_cast(data.data()), ::size32(data), 0, reinterpret_cast(&addr), sizeof(sockaddr_in6)) == -1) - { - sys_net.error("Failed to send signaling packet on IPv6 socket: %s", get_last_error(false, false)); - return false; - } - } - else - { - sys_net.error("send_packet_from_p2p_port_ipv6: port %d not present", +SCE_NP_PORT); - return false; - } - } - - return true; -} - -std::vector> get_rpcn_msgs() -{ - std::vector> msgs; - auto& nc = g_fxo->get(); - { - std::lock_guard list_lock(nc.list_p2p_ports_mutex); - if (nc.list_p2p_ports.contains(SCE_NP_PORT)) - { - auto& def_port = ::at32(nc.list_p2p_ports, SCE_NP_PORT); - { - std::lock_guard lock(def_port.s_rpcn_mutex); - msgs = std::move(def_port.rpcn_msgs); - def_port.rpcn_msgs.clear(); - } - } - else - { - sys_net.error("get_rpcn_msgs: port %d not present", +SCE_NP_PORT); - } - } - - return msgs; -} - -std::vector get_sign_msgs() -{ - std::vector msgs; - auto& nc = g_fxo->get(); - { - std::lock_guard list_lock(nc.list_p2p_ports_mutex); - if (nc.list_p2p_ports.contains(SCE_NP_PORT)) - { - auto& def_port = ::at32(nc.list_p2p_ports, SCE_NP_PORT); - { - std::lock_guard lock(def_port.s_sign_mutex); - msgs = std::move(def_port.sign_msgs); - def_port.sign_msgs.clear(); - } - } - else - { - sys_net.error("get_sign_msgs: port %d not present", +SCE_NP_PORT); - } - } - - return msgs; -} - -namespace np -{ - void init_np_handler_dependencies(); -} - -void base_network_thread::add_ppu_to_awake(ppu_thread* ppu) -{ - std::lock_guard lock(mutex_ppu_to_awake); - ppu_to_awake.emplace_back(ppu); -} - -void base_network_thread::del_ppu_to_awake(ppu_thread* ppu) -{ - std::lock_guard lock(mutex_ppu_to_awake); - - for (auto it = ppu_to_awake.begin(); it != ppu_to_awake.end();) - { - if (*it == ppu) - { - it = ppu_to_awake.erase(it); - continue; - } - - it++; - } -} - -void base_network_thread::wake_threads() -{ - std::lock_guard lock(mutex_ppu_to_awake); - - ppu_to_awake.erase(std::unique(ppu_to_awake.begin(), ppu_to_awake.end()), ppu_to_awake.end()); - for (ppu_thread* ppu : ppu_to_awake) - { - network_clear_queue(*ppu); - lv2_obj::append(ppu); - } - - if (!ppu_to_awake.empty()) - { - ppu_to_awake.clear(); - lv2_obj::awake_all(); - } -} - -p2p_thread::p2p_thread() -{ - np::init_np_handler_dependencies(); -} - -void p2p_thread::bind_sce_np_port() -{ - std::lock_guard list_lock(list_p2p_ports_mutex); - create_p2p_port(SCE_NP_PORT); -} - -void network_thread::operator()() -{ - std::vector> socklist; - socklist.reserve(lv2_socket::id_count); - - { - std::lock_guard lock(mutex_ppu_to_awake); - ppu_to_awake.clear(); - } - - std::vector<::pollfd> fds(lv2_socket::id_count); -#ifdef _WIN32 - std::vector connecting(lv2_socket::id_count); - std::vector was_connecting(lv2_socket::id_count); -#endif - - while (thread_ctrl::state() != thread_state::aborting) - { - if (!num_polls) - { - thread_ctrl::wait_on(num_polls, 0); - continue; - } - - ensure(socklist.size() <= lv2_socket::id_count); - - // Wait with 1ms timeout -#ifdef _WIN32 - windows_poll(fds, ::size32(socklist), 1, connecting); -#else - ::poll(fds.data(), socklist.size(), 1); -#endif - - std::lock_guard lock(mutex_thread_loop); - - for (usz i = 0; i < socklist.size(); i++) - { -#ifdef _WIN32 - socklist[i]->handle_events(fds[i], was_connecting[i] && !connecting[i]); -#else - socklist[i]->handle_events(fds[i]); -#endif - } - - wake_threads(); - socklist.clear(); - - // Obtain all native active sockets - idm::select([&](u32 id, lv2_socket& s) - { - if (s.get_type() == SYS_NET_SOCK_DGRAM || s.get_type() == SYS_NET_SOCK_STREAM) - { - socklist.emplace_back(idm::get_unlocked(id)); - } - }); - - for (usz i = 0; i < socklist.size(); i++) - { - auto events = socklist[i]->get_events(); - - fds[i].fd = events ? socklist[i]->get_socket() : -1; - fds[i].events = - (events & lv2_socket::poll_t::read ? POLLIN : 0) | - (events & lv2_socket::poll_t::write ? POLLOUT : 0) | - 0; - fds[i].revents = 0; -#ifdef _WIN32 - const auto cur_connecting = socklist[i]->is_connecting(); - was_connecting[i] = cur_connecting; - connecting[i] = cur_connecting; -#endif - } - } -} - -// Must be used under list_p2p_ports_mutex lock! -void p2p_thread::create_p2p_port(u16 p2p_port) -{ - if (!list_p2p_ports.contains(p2p_port)) - { - list_p2p_ports.emplace(std::piecewise_construct, std::forward_as_tuple(p2p_port), std::forward_as_tuple(p2p_port)); - const u32 prev_value = num_p2p_ports.fetch_add(1); - if (!prev_value) - { - num_p2p_ports.notify_one(); - } - } -} - -void p2p_thread::operator()() -{ - std::vector<::pollfd> p2p_fd(lv2_socket::id_count); - - while (thread_ctrl::state() != thread_state::aborting) - { - if (!num_p2p_ports) - { - thread_ctrl::wait_on(num_p2p_ports, 0); - continue; - } - - // Check P2P sockets for incoming packets - auto num_p2p_sockets = 0; - std::memset(p2p_fd.data(), 0, p2p_fd.size() * sizeof(::pollfd)); - { - auto set_fd = [&](socket_type socket) - { - p2p_fd[num_p2p_sockets].events = POLLIN; - p2p_fd[num_p2p_sockets].revents = 0; - p2p_fd[num_p2p_sockets].fd = socket; - num_p2p_sockets++; - }; - - std::lock_guard lock(list_p2p_ports_mutex); - for (const auto& [_, p2p_port] : list_p2p_ports) - { - set_fd(p2p_port.p2p_socket); - } - } - -#ifdef _WIN32 - const auto ret_p2p = WSAPoll(p2p_fd.data(), num_p2p_sockets, 1); -#else - const auto ret_p2p = ::poll(p2p_fd.data(), num_p2p_sockets, 1); -#endif - if (ret_p2p > 0) - { - std::lock_guard lock(list_p2p_ports_mutex); - auto fd_index = 0; - - auto process_fd = [&](nt_p2p_port& p2p_port) - { - if ((p2p_fd[fd_index].revents & POLLIN) == POLLIN || (p2p_fd[fd_index].revents & POLLRDNORM) == POLLRDNORM) - { - while (p2p_port.recv_data()) - ; - } - fd_index++; - }; - - for (auto& [_, p2p_port] : list_p2p_ports) - { - process_fd(p2p_port); - } - - wake_threads(); - } - else if (ret_p2p < 0) - { - sys_net.error("[P2P] Error poll on master P2P socket: %d", get_last_error(false)); - } - } -} diff --git a/rpcs3/Emu/Cell/lv2/sys_net/network_context.h b/rpcs3/Emu/Cell/lv2/sys_net/network_context.h deleted file mode 100644 index bfff69fa2..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_net/network_context.h +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once - -#include -#include -#include "util/mutex.h" -#include "Emu/Cell/PPUThread.h" - -#include "nt_p2p_port.h" - -struct base_network_thread -{ - void add_ppu_to_awake(ppu_thread* ppu); - void del_ppu_to_awake(ppu_thread* ppu); - - shared_mutex mutex_ppu_to_awake; - std::vector ppu_to_awake; - - void wake_threads(); -}; - -struct network_thread : base_network_thread -{ - shared_mutex mutex_thread_loop; - atomic_t num_polls = 0; - - static constexpr auto thread_name = "Network Thread"; - - void operator()(); -}; - -struct p2p_thread : base_network_thread -{ - shared_mutex list_p2p_ports_mutex; - std::map list_p2p_ports; - atomic_t num_p2p_ports = 0; - - static constexpr auto thread_name = "Network P2P Thread"; - - p2p_thread(); - - void create_p2p_port(u16 p2p_port); - - void bind_sce_np_port(); - void operator()(); -}; - -using network_context = named_thread; -using p2p_context = named_thread; diff --git a/rpcs3/Emu/Cell/lv2/sys_net/nt_p2p_port.cpp b/rpcs3/Emu/Cell/lv2/sys_net/nt_p2p_port.cpp deleted file mode 100644 index 3ded718af..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_net/nt_p2p_port.cpp +++ /dev/null @@ -1,361 +0,0 @@ -#include "stdafx.h" -#include "Emu/NP/ip_address.h" -#include "nt_p2p_port.h" -#include "lv2_socket_p2ps.h" -#include "sys_net_helpers.h" -#include "Emu/NP/signaling_handler.h" -#include "sys_net_helpers.h" -#include "Emu/NP/vport0.h" -#include "Emu/NP/np_handler.h" - -LOG_CHANNEL(sys_net); - -namespace sys_net_helpers -{ - bool all_reusable(const std::set& sock_ids) - { - for (const s32 sock_id : sock_ids) - { - const auto [_, reusable] = idm::check(sock_id, [&](lv2_socket& sock) -> bool - { - auto [res_reuseaddr, optval_reuseaddr, optlen_reuseaddr] = sock.getsockopt(SYS_NET_SOL_SOCKET, SYS_NET_SO_REUSEADDR, sizeof(s32)); - auto [res_reuseport, optval_reuseport, optlen_reuseport] = sock.getsockopt(SYS_NET_SOL_SOCKET, SYS_NET_SO_REUSEPORT, sizeof(s32)); - - const bool reuse_addr = optlen_reuseaddr == 4 && !!optval_reuseaddr._int; - const bool reuse_port = optlen_reuseport == 4 && !!optval_reuseport._int; - - return (reuse_addr || reuse_port); - }); - - if (!reusable) - { - return false; - } - } - - return true; - } -} // namespace sys_net_helpers - -nt_p2p_port::nt_p2p_port(u16 port) - : port(port) -{ - is_ipv6 = np::is_ipv6_supported(); - - // Creates and bind P2P Socket - p2p_socket = is_ipv6 ? ::socket(AF_INET6, SOCK_DGRAM, 0) : ::socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); -#ifdef _WIN32 - if (p2p_socket == INVALID_SOCKET) -#else - if (p2p_socket == -1) -#endif - fmt::throw_exception("Failed to create DGRAM socket for P2P socket: %s!", get_last_error(true)); - - np::set_socket_non_blocking(p2p_socket); - - u32 optval = 131072; // value obtained from DECR for a SOCK_DGRAM_P2P socket(should maybe be bigger for actual socket?) - if (setsockopt(p2p_socket, SOL_SOCKET, SO_RCVBUF, reinterpret_cast(&optval), sizeof(optval)) != 0) - fmt::throw_exception("Error setsockopt SO_RCVBUF on P2P socket: %s", get_last_error(true)); - - int ret_bind = 0; - const u16 be_port = std::bit_cast>(port); - - if (is_ipv6) - { - // Some OS(Windows, maybe more) will only support IPv6 adressing by default and we need IPv4 over IPv6 - optval = 0; - if (setsockopt(p2p_socket, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast(&optval), sizeof(optval)) != 0) - fmt::throw_exception("Error setsockopt IPV6_V6ONLY on P2P socket: %s", get_last_error(true)); - - ::sockaddr_in6 p2p_ipv6_addr{.sin6_family = AF_INET6, .sin6_port = be_port}; - ret_bind = ::bind(p2p_socket, reinterpret_cast(&p2p_ipv6_addr), sizeof(p2p_ipv6_addr)); - } - else - { - ::sockaddr_in p2p_ipv4_addr{.sin_family = AF_INET, .sin_port = be_port}; - ret_bind = ::bind(p2p_socket, reinterpret_cast(&p2p_ipv4_addr), sizeof(p2p_ipv4_addr)); - } - - if (ret_bind == -1) - fmt::throw_exception("Failed to bind DGRAM socket to %d for P2P: %s!", port, get_last_error(true)); - - auto& nph = g_fxo->get>(); - nph.upnp_add_port_mapping(port, "UDP"); - - sys_net.notice("P2P port %d was bound!", port); -} - -nt_p2p_port::~nt_p2p_port() -{ - np::close_socket(p2p_socket); -} - -void nt_p2p_port::dump_packet(p2ps_encapsulated_tcp* tcph) -{ - sys_net.trace("PACKET DUMP:\nsrc_port: %d\ndst_port: %d\nflags: %d\nseq: %d\nack: %d\nlen: %d", tcph->src_port, tcph->dst_port, tcph->flags, tcph->seq, tcph->ack, tcph->length); -} - -// Must be used under bound_p2p_vports_mutex lock -u16 nt_p2p_port::get_port() -{ - if (binding_port == 0) - { - binding_port = 30000; - } - - return binding_port++; -} - -bool nt_p2p_port::handle_connected(s32 sock_id, p2ps_encapsulated_tcp* tcp_header, u8* data, ::sockaddr_storage* op_addr) -{ - const auto sock = idm::check(sock_id, [&](lv2_socket& sock) -> bool - { - ensure(sock.get_type() == SYS_NET_SOCK_STREAM_P2P); - auto& sock_p2ps = reinterpret_cast(sock); - - return sock_p2ps.handle_connected(tcp_header, data, op_addr, this); - }); - - if (!sock) - { - sys_net.error("[P2PS] Couldn't find the socket!"); - return false; - } - - if (!sock.ret) - { - sys_net.error("[P2PS] handle_connected() failed!"); - return false; - } - - return true; -} - -bool nt_p2p_port::handle_listening(s32 sock_id, p2ps_encapsulated_tcp* tcp_header, u8* data, ::sockaddr_storage* op_addr) -{ - auto sock = idm::get_unlocked(sock_id); - if (!sock) - return false; - - auto& sock_p2ps = reinterpret_cast(*sock); - return sock_p2ps.handle_listening(tcp_header, data, op_addr); -} - -bool nt_p2p_port::recv_data() -{ - ::sockaddr_storage native_addr{}; - ::socklen_t native_addrlen = sizeof(native_addr); - const auto recv_res = ::recvfrom(p2p_socket, reinterpret_cast(p2p_recv_data.data()), ::size32(p2p_recv_data), 0, reinterpret_cast(&native_addr), &native_addrlen); - - if (recv_res == -1) - { - auto lerr = get_last_error(false); - if (lerr != SYS_NET_EINPROGRESS && lerr != SYS_NET_EWOULDBLOCK) - sys_net.error("Error recvfrom on %s P2P socket: %d", is_ipv6 ? "IPv6" : "IPv4", lerr); - - return false; - } - - if (recv_res < static_cast(sizeof(u16))) - { - sys_net.error("Received badly formed packet on P2P port(no vport)!"); - return true; - } - - u16 dst_vport = reinterpret_cast&>(p2p_recv_data[0]); - - if (is_ipv6) - { - const auto* addr_ipv6 = reinterpret_cast(&native_addr); - const auto addr_ipv4 = np::sockaddr6_to_sockaddr(*addr_ipv6); - native_addr = {}; - std::memcpy(&native_addr, &addr_ipv4, sizeof(addr_ipv4)); - } - - if (dst_vport == 0) - { - if (recv_res < VPORT_0_HEADER_SIZE) - { - sys_net.error("Bad vport 0 packet(no subset)!"); - return true; - } - - const u8 subset = p2p_recv_data[2]; - const auto data_size = recv_res - VPORT_0_HEADER_SIZE; - std::vector vport_0_data(p2p_recv_data.data() + VPORT_0_HEADER_SIZE, p2p_recv_data.data() + VPORT_0_HEADER_SIZE + data_size); - - switch (subset) - { - case SUBSET_RPCN: - { - std::lock_guard lock(s_rpcn_mutex); - rpcn_msgs.push_back(std::move(vport_0_data)); - return true; - } - case SUBSET_SIGNALING: - { - signaling_message msg; - msg.src_addr = reinterpret_cast(&native_addr)->sin_addr.s_addr; - msg.src_port = std::bit_cast>(reinterpret_cast(&native_addr)->sin_port); - msg.data = std::move(vport_0_data); - - { - std::lock_guard lock(s_sign_mutex); - sign_msgs.push_back(std::move(msg)); - } - - auto& sigh = g_fxo->get>(); - sigh.wake_up(); - return true; - } - default: - { - sys_net.error("Invalid vport 0 subset!"); - return true; - } - } - } - - if (recv_res < VPORT_P2P_HEADER_SIZE) - { - return true; - } - - const u16 src_vport = *reinterpret_cast*>(p2p_recv_data.data() + sizeof(u16)); - const u16 vport_flags = *reinterpret_cast*>(p2p_recv_data.data() + sizeof(u16) + sizeof(u16)); - std::vector p2p_data(recv_res - VPORT_P2P_HEADER_SIZE); - memcpy(p2p_data.data(), p2p_recv_data.data() + VPORT_P2P_HEADER_SIZE, p2p_data.size()); - - if (vport_flags & P2P_FLAG_P2P) - { - std::lock_guard lock(bound_p2p_vports_mutex); - if (bound_p2p_vports.contains(dst_vport)) - { - sys_net_sockaddr_in_p2p p2p_addr{}; - - p2p_addr.sin_len = sizeof(sys_net_sockaddr_in); - p2p_addr.sin_family = SYS_NET_AF_INET; - p2p_addr.sin_addr = std::bit_cast, u32>(reinterpret_cast(&native_addr)->sin_addr.s_addr); - p2p_addr.sin_vport = src_vport; - p2p_addr.sin_port = std::bit_cast, u16>(reinterpret_cast(&native_addr)->sin_port); - - auto& bound_sockets = ::at32(bound_p2p_vports, dst_vport); - - for (const auto sock_id : bound_sockets) - { - const auto sock = idm::check(sock_id, [&](lv2_socket& sock) - { - ensure(sock.get_type() == SYS_NET_SOCK_DGRAM_P2P); - auto& sock_p2p = reinterpret_cast(sock); - - sock_p2p.handle_new_data(p2p_addr, p2p_data); - }); - - if (!sock) - { - sys_net.error("Socket %d found in bound_p2p_vports didn't exist!", sock_id); - bound_sockets.erase(sock_id); - if (bound_sockets.empty()) - { - bound_p2p_vports.erase(dst_vport); - } - } - } - - return true; - } - } - else if (vport_flags & P2P_FLAG_P2PS) - { - if (p2p_data.size() < sizeof(p2ps_encapsulated_tcp)) - { - sys_net.notice("Received P2P packet targeted at unbound vport(likely) or invalid(vport=%d)", dst_vport); - return true; - } - - auto* tcp_header = reinterpret_cast(p2p_data.data()); - - // Validate signature & length - if (tcp_header->signature != P2PS_U2S_SIG) - { - sys_net.notice("Received P2P packet targeted at unbound vport(vport=%d)", dst_vport); - return true; - } - - if (tcp_header->length != (p2p_data.size() - sizeof(p2ps_encapsulated_tcp))) - { - sys_net.error("Received STREAM-P2P packet tcp length didn't match packet length"); - return true; - } - - // Sanity check - if (tcp_header->dst_port != dst_vport) - { - sys_net.error("Received STREAM-P2P packet with dst_port != vport"); - return true; - } - - // Validate checksum - u16 given_checksum = tcp_header->checksum; - tcp_header->checksum = 0; - if (given_checksum != u2s_tcp_checksum(reinterpret_cast*>(p2p_data.data()), p2p_data.size())) - { - sys_net.error("Checksum is invalid, dropping packet!"); - return true; - } - - // The packet is valid - dump_packet(tcp_header); - - // Check if it's bound - const u64 key_connected = (reinterpret_cast(&native_addr)->sin_addr.s_addr) | (static_cast(tcp_header->src_port) << 48) | (static_cast(tcp_header->dst_port) << 32); - - { - std::lock_guard lock(bound_p2p_vports_mutex); - if (bound_p2p_streams.contains(key_connected)) - { - const auto sock_id = ::at32(bound_p2p_streams, key_connected); - sys_net.trace("Received packet for connected STREAM-P2P socket(s=%d)", sock_id); - handle_connected(sock_id, tcp_header, p2p_data.data() + sizeof(p2ps_encapsulated_tcp), &native_addr); - return true; - } - - if (bound_p2ps_vports.contains(tcp_header->dst_port)) - { - const auto& bound_sockets = ::at32(bound_p2ps_vports, tcp_header->dst_port); - - for (const auto sock_id : bound_sockets) - { - sys_net.trace("Received packet for listening STREAM-P2P socket(s=%d)", sock_id); - handle_listening(sock_id, tcp_header, p2p_data.data() + sizeof(p2ps_encapsulated_tcp), &native_addr); - } - return true; - } - - if (tcp_header->flags == p2ps_tcp_flags::RST) - { - sys_net.trace("[P2PS] Received RST on unbound P2PS"); - return true; - } - - // The P2PS packet was sent to an unbound vport, send a RST packet - p2ps_encapsulated_tcp send_hdr; - send_hdr.src_port = tcp_header->dst_port; - send_hdr.dst_port = tcp_header->src_port; - send_hdr.flags = p2ps_tcp_flags::RST; - auto packet = generate_u2s_packet(send_hdr, nullptr, 0); - - if (np::sendto_possibly_ipv6(p2p_socket, reinterpret_cast(packet.data()), ::size32(packet), reinterpret_cast(&native_addr), 0) == -1) - { - sys_net.error("[P2PS] Error sending RST to sender to unbound P2PS: %s", get_last_error(false)); - return true; - } - - sys_net.trace("[P2PS] Sent RST to sender to unbound P2PS"); - return true; - } - } - - sys_net.notice("Received a P2P packet with no bound target(dst_vport = %d)", dst_vport); - return true; -} diff --git a/rpcs3/Emu/Cell/lv2/sys_net/nt_p2p_port.h b/rpcs3/Emu/Cell/lv2/sys_net/nt_p2p_port.h deleted file mode 100644 index c367812ed..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_net/nt_p2p_port.h +++ /dev/null @@ -1,83 +0,0 @@ -#pragma once - -#include - -#include "lv2_socket_p2ps.h" - -#ifdef _WIN32 -#include -#include -#else -#ifdef __clang__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wold-style-cast" -#endif -#include -#include -#include -#ifdef __clang__ -#pragma GCC diagnostic pop -#endif -#endif - -// dst_vport src_vport flags -constexpr s32 VPORT_P2P_HEADER_SIZE = sizeof(u16) + sizeof(u16) + sizeof(u16); - -enum VPORT_P2P_FLAGS -{ - P2P_FLAG_P2P = 1, - P2P_FLAG_P2PS = 1 << 1, -}; - -struct signaling_message -{ - u32 src_addr = 0; - u16 src_port = 0; - - std::vector data; -}; - -namespace sys_net_helpers -{ - bool all_reusable(const std::set& sock_ids); -} - -struct nt_p2p_port -{ - // Real socket where P2P packets are received/sent - socket_type p2p_socket = 0; - u16 port = 0; - - bool is_ipv6 = false; - - shared_mutex bound_p2p_vports_mutex; - // For DGRAM_P2P sockets (vport, sock_ids) - std::map> bound_p2p_vports{}; - // For STREAM_P2P sockets (vport, sock_ids) - std::map> bound_p2ps_vports{}; - // List of active(either from a connect or an accept) P2PS sockets (key, sock_id) - // key is ( (src_vport) << 48 | (dst_vport) << 32 | addr ) with src_vport and addr being 0 for listening sockets - std::map bound_p2p_streams{}; - // Current free port index - u16 binding_port = 30000; - - // Queued messages from RPCN - shared_mutex s_rpcn_mutex; - std::vector> rpcn_msgs{}; - // Queued signaling messages - shared_mutex s_sign_mutex; - std::vector sign_msgs{}; - - std::array p2p_recv_data{}; - - nt_p2p_port(u16 port); - ~nt_p2p_port(); - - static void dump_packet(p2ps_encapsulated_tcp* tcph); - - u16 get_port(); - - bool handle_connected(s32 sock_id, p2ps_encapsulated_tcp* tcp_header, u8* data, ::sockaddr_storage* op_addr); - bool handle_listening(s32 sock_id, p2ps_encapsulated_tcp* tcp_header, u8* data, ::sockaddr_storage* op_addr); - bool recv_data(); -}; diff --git a/rpcs3/Emu/Cell/lv2/sys_net/sys_net_helpers.cpp b/rpcs3/Emu/Cell/lv2/sys_net/sys_net_helpers.cpp deleted file mode 100644 index 4650f6bce..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_net/sys_net_helpers.cpp +++ /dev/null @@ -1,254 +0,0 @@ -#include "stdafx.h" -#include "Emu/IdManager.h" -#include "Emu/Cell/PPUThread.h" -#include "lv2_socket.h" -#include "sys_net_helpers.h" -#include "network_context.h" - -LOG_CHANNEL(sys_net); - -int get_native_error() -{ - int native_error; -#ifdef _WIN32 - native_error = WSAGetLastError(); -#else - native_error = errno; -#endif - - return native_error; -} - -sys_net_error convert_error(bool is_blocking, int native_error, [[maybe_unused]] bool is_connecting) -{ - // Convert the error code for socket functions to a one for sys_net - sys_net_error result{}; - const char* name{}; - -#ifdef _WIN32 -#define ERROR_CASE(error) \ - case WSA##error: \ - result = SYS_NET_##error; \ - name = #error; \ - break; -#else -#define ERROR_CASE(error) \ - case error: \ - result = SYS_NET_##error; \ - name = #error; \ - break; -#endif - switch (native_error) - { -#ifndef _WIN32 - ERROR_CASE(ENOENT); - ERROR_CASE(ENOMEM); - ERROR_CASE(EBUSY); - ERROR_CASE(ENOSPC); - ERROR_CASE(EPIPE); -#endif - - // TODO: We don't currently support EFAULT or EINTR - // ERROR_CASE(EFAULT); - // ERROR_CASE(EINTR); - - ERROR_CASE(EBADF); - ERROR_CASE(EACCES); - ERROR_CASE(EINVAL); - ERROR_CASE(EMFILE); - ERROR_CASE(EWOULDBLOCK); - ERROR_CASE(EINPROGRESS); - ERROR_CASE(EALREADY); - ERROR_CASE(EDESTADDRREQ); - ERROR_CASE(EMSGSIZE); - ERROR_CASE(EPROTOTYPE); - ERROR_CASE(ENOPROTOOPT); - ERROR_CASE(EPROTONOSUPPORT); - ERROR_CASE(EOPNOTSUPP); - ERROR_CASE(EPFNOSUPPORT); - ERROR_CASE(EAFNOSUPPORT); - ERROR_CASE(EADDRINUSE); - ERROR_CASE(EADDRNOTAVAIL); - ERROR_CASE(ENETDOWN); - ERROR_CASE(ENETUNREACH); - ERROR_CASE(ECONNABORTED); - ERROR_CASE(ECONNRESET); - ERROR_CASE(ENOBUFS); - ERROR_CASE(EISCONN); - ERROR_CASE(ENOTCONN); - ERROR_CASE(ESHUTDOWN); - ERROR_CASE(ETOOMANYREFS); - ERROR_CASE(ETIMEDOUT); - ERROR_CASE(ECONNREFUSED); - ERROR_CASE(EHOSTDOWN); - ERROR_CASE(EHOSTUNREACH); -#ifdef _WIN32 - // Windows likes to be special with unique errors - case WSAENETRESET: - result = SYS_NET_ECONNRESET; - name = "WSAENETRESET"; - break; -#endif - default: - fmt::throw_exception("sys_net get_last_error(is_blocking=%d, native_error=%d): Unknown/illegal socket error", is_blocking, native_error); - } - -#ifdef _WIN32 - if (is_connecting) - { - // Windows will return SYS_NET_ENOTCONN when recvfrom/sendto is called on a socket that is connecting but not yet connected - if (result == SYS_NET_ENOTCONN) - return SYS_NET_EAGAIN; - } -#endif - - if (name && result != SYS_NET_EWOULDBLOCK && result != SYS_NET_EINPROGRESS) - { - sys_net.error("Socket error %s", name); - } - - if (is_blocking && result == SYS_NET_EWOULDBLOCK) - { - return {}; - } - - if (is_blocking && result == SYS_NET_EINPROGRESS) - { - return {}; - } - - return result; -#undef ERROR_CASE -} - -sys_net_error get_last_error(bool is_blocking, bool is_connecting) -{ - return convert_error(is_blocking, get_native_error(), is_connecting); -} - -sys_net_sockaddr native_addr_to_sys_net_addr(const ::sockaddr_storage& native_addr) -{ - ensure(native_addr.ss_family == AF_INET || native_addr.ss_family == AF_UNSPEC); - - sys_net_sockaddr sn_addr; - - sys_net_sockaddr_in* paddr = reinterpret_cast(&sn_addr); - *paddr = {}; - - paddr->sin_len = sizeof(sys_net_sockaddr_in); - paddr->sin_family = SYS_NET_AF_INET; - paddr->sin_port = std::bit_cast, u16>(reinterpret_cast(&native_addr)->sin_port); - paddr->sin_addr = std::bit_cast, u32>(reinterpret_cast(&native_addr)->sin_addr.s_addr); - - return sn_addr; -} - -::sockaddr_in sys_net_addr_to_native_addr(const sys_net_sockaddr& sn_addr) -{ - ensure(sn_addr.sa_family == SYS_NET_AF_INET); - - const sys_net_sockaddr_in* psa_in = reinterpret_cast(&sn_addr); - - ::sockaddr_in native_addr{}; - native_addr.sin_family = AF_INET; - native_addr.sin_port = std::bit_cast(psa_in->sin_port); - native_addr.sin_addr.s_addr = std::bit_cast(psa_in->sin_addr); - -#ifdef _WIN32 - // Windows doesn't support sending packets to 0.0.0.0 but it works on unixes, send to 127.0.0.1 instead - if (native_addr.sin_addr.s_addr == 0x00000000) - { - sys_net.warning("[Native] Redirected 0.0.0.0 to 127.0.0.1"); - native_addr.sin_addr.s_addr = std::bit_cast>(0x7F000001); - } -#endif - - return native_addr; -} - -bool is_ip_public_address(const ::sockaddr_in& addr) -{ - const u8* ip = reinterpret_cast(&addr.sin_addr.s_addr); - - if ((ip[0] == 10) || - (ip[0] == 127) || - (ip[0] == 172 && (ip[1] >= 16 && ip[1] <= 31)) || - (ip[0] == 192 && ip[1] == 168)) - { - return false; - } - - return true; -} - -u32 network_clear_queue(ppu_thread& ppu) -{ - u32 cleared = 0; - - idm::select([&](u32, lv2_socket& sock) - { - cleared += sock.clear_queue(&ppu); - }); - - return cleared; -} - -void clear_ppu_to_awake(ppu_thread& ppu) -{ - g_fxo->get().del_ppu_to_awake(&ppu); - g_fxo->get().del_ppu_to_awake(&ppu); -} - -#ifdef _WIN32 -// Workaround function for WSAPoll not reporting failed connections -// Note that this was fixed in Windows 10 version 2004 (after more than 10 years lol) -void windows_poll(std::vector& fds, unsigned long nfds, int timeout, std::vector& connecting) -{ - ensure(fds.size() >= nfds); - ensure(connecting.size() >= nfds); - - // Don't call WSAPoll with zero nfds (errors 10022 or 10038) - if (std::none_of(fds.begin(), fds.begin() + nfds, [](pollfd& pfd) - { - return pfd.fd != INVALID_SOCKET; - })) - { - if (timeout > 0) - { - Sleep(timeout); - } - - return; - } - - int r = ::WSAPoll(fds.data(), nfds, timeout); - - if (r == SOCKET_ERROR) - { - sys_net.error("WSAPoll failed: %s", fmt::win_error{static_cast(WSAGetLastError()), nullptr}); - return; - } - - for (unsigned long i = 0; i < nfds; i++) - { - if (connecting[i]) - { - if (!fds[i].revents) - { - int error = 0; - socklen_t intlen = sizeof(error); - if (getsockopt(fds[i].fd, SOL_SOCKET, SO_ERROR, reinterpret_cast(&error), &intlen) == -1 || error != 0) - { - // Connection silently failed - connecting[i] = false; - fds[i].revents = POLLERR | POLLHUP | (fds[i].events & (POLLIN | POLLOUT)); - } - } - else - { - connecting[i] = false; - } - } - } -} -#endif diff --git a/rpcs3/Emu/Cell/lv2/sys_net/sys_net_helpers.h b/rpcs3/Emu/Cell/lv2/sys_net/sys_net_helpers.h deleted file mode 100644 index 1cd9e136c..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_net/sys_net_helpers.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#ifdef _WIN32 -#include -#include -#else -#ifdef __clang__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wold-style-cast" -#endif -#include -#include -#ifdef __clang__ -#pragma GCC diagnostic pop -#endif -#endif - -#include "Emu/Cell/lv2/sys_net.h" - -int get_native_error(); -sys_net_error convert_error(bool is_blocking, int native_error, bool is_connecting = false); -sys_net_error get_last_error(bool is_blocking, bool is_connecting = false); -sys_net_sockaddr native_addr_to_sys_net_addr(const ::sockaddr_storage& native_addr); -::sockaddr_in sys_net_addr_to_native_addr(const sys_net_sockaddr& sn_addr); -bool is_ip_public_address(const ::sockaddr_in& addr); -u32 network_clear_queue(ppu_thread& ppu); -void clear_ppu_to_awake(ppu_thread& ppu); - -#ifdef _WIN32 -void windows_poll(std::vector& fds, unsigned long nfds, int timeout, std::vector& connecting); -#endif diff --git a/rpcs3/Emu/Cell/lv2/sys_overlay.cpp b/rpcs3/Emu/Cell/lv2/sys_overlay.cpp deleted file mode 100644 index 894892f9b..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_overlay.cpp +++ /dev/null @@ -1,204 +0,0 @@ -#include "stdafx.h" - -#include "Emu/Cell/ErrorCodes.h" -#include "Emu/Memory/vm_ptr.h" -#include "Emu/VFS.h" -#include "Emu/IdManager.h" -#include "Emu/system_config.h" -#include "Crypto/unself.h" -#include "Crypto/unedat.h" -#include "Loader/ELF.h" - -#include "sys_process.h" -#include "sys_overlay.h" -#include "sys_fs.h" - -extern std::pair, CellError> ppu_load_overlay(const ppu_exec_object&, bool virtual_load, const std::string& path, s64 file_offset, utils::serial* ar = nullptr); - -extern bool ppu_initialize(const ppu_module&, bool check_only = false, u64 file_size = 0); -extern void ppu_finalize(const ppu_module& info, bool force_mem_release = false); - -LOG_CHANNEL(sys_overlay); - -static error_code overlay_load_module(vm::ptr ovlmid, const std::string& vpath, u64 /*flags*/, vm::ptr entry, fs::file src = {}, s64 file_offset = 0) -{ - if (!src) - { - auto [fs_error, ppath, path, lv2_file, type] = lv2_file::open(vpath, 0, 0); - - if (fs_error) - { - return {fs_error, vpath}; - } - - src = std::move(lv2_file); - } - - u128 klic = g_fxo->get().last_key(); - - src = decrypt_self(std::move(src), reinterpret_cast(&klic)); - - if (!src) - { - return {CELL_ENOEXEC, +"Failed to decrypt file"}; - } - - ppu_exec_object obj = std::move(src); - src.close(); - - if (obj != elf_error::ok) - { - return {CELL_ENOEXEC, obj.operator elf_error()}; - } - - const auto [ovlm, error] = ppu_load_overlay(obj, false, vfs::get(vpath), file_offset); - - obj.clear(); - - if (error) - { - if (error == CELL_CANCEL + 0u) - { - // Emulation stopped - return {}; - } - - return error; - } - - ppu_initialize(*ovlm); - - sys_overlay.success("Loaded overlay: \"%s\" (id=0x%x)", vpath, idm::last_id()); - - *ovlmid = idm::last_id(); - *entry = ovlm->entry; - - return CELL_OK; -} - -fs::file make_file_view(fs::file&& file, u64 offset, u64 size); - -std::function lv2_overlay::load(utils::serial& ar) -{ - const std::string vpath = ar.pop(); - const std::string path = vfs::get(vpath); - const s64 offset = ar.pop(); - - sys_overlay.success("lv2_overlay::load(): vpath='%s', path='%s', offset=0x%x", vpath, path, offset); - - shared_ptr ovlm; - - fs::file file{path.substr(0, path.size() - (offset ? fmt::format("_x%x", offset).size() : 0))}; - - if (file) - { - u128 klic = g_fxo->get().last_key(); - file = make_file_view(std::move(file), offset, umax); - ovlm = ppu_load_overlay(ppu_exec_object{decrypt_self(std::move(file), reinterpret_cast(&klic))}, false, path, 0, &ar).first; - - if (!ovlm) - { - fmt::throw_exception("lv2_overlay::load(): ppu_load_overlay() failed. (vpath='%s', offset=0x%x)", vpath, offset); - } - } - else if (!g_cfg.savestate.state_inspection_mode.get()) - { - fmt::throw_exception("lv2_overlay::load(): Failed to find file. (vpath='%s', offset=0x%x)", vpath, offset); - } - else - { - sys_overlay.error("lv2_overlay::load(): Failed to find file. (vpath='%s', offset=0x%x)", vpath, offset); - } - - return [ovlm](void* storage) - { - *static_cast*>(storage) = ovlm; - }; -} - -void lv2_overlay::save(utils::serial& ar) -{ - USING_SERIALIZATION_VERSION(lv2_prx_overlay); - - const std::string vpath = vfs::retrieve(path); - (vpath.empty() ? sys_overlay.error : sys_overlay.success)("lv2_overlay::save(): vpath='%s', offset=0x%x", vpath, offset); - - ar(vpath, offset); -} - -error_code sys_overlay_load_module(vm::ptr ovlmid, vm::cptr path, u64 flags, vm::ptr entry) -{ - sys_overlay.warning("sys_overlay_load_module(ovlmid=*0x%x, path=%s, flags=0x%x, entry=*0x%x)", ovlmid, path, flags, entry); - - if (!g_ps3_process_info.ppc_seg) - { - // Process not permitted - return CELL_ENOSYS; - } - - if (!path) - { - return CELL_EFAULT; - } - - return overlay_load_module(ovlmid, path.get_ptr(), flags, entry); -} - -error_code sys_overlay_load_module_by_fd(vm::ptr ovlmid, u32 fd, u64 offset, u64 flags, vm::ptr entry) -{ - sys_overlay.warning("sys_overlay_load_module_by_fd(ovlmid=*0x%x, fd=%d, offset=0x%llx, flags=0x%x, entry=*0x%x)", ovlmid, fd, offset, flags, entry); - - if (!g_ps3_process_info.ppc_seg) - { - // Process not permitted - return CELL_ENOSYS; - } - - if (static_cast(offset) < 0) - { - return CELL_EINVAL; - } - - const auto file = idm::get_unlocked(fd); - - if (!file) - { - return CELL_EBADF; - } - - std::lock_guard lock(file->mp->mutex); - - if (!file->file) - { - return CELL_EBADF; - } - - return overlay_load_module(ovlmid, offset ? fmt::format("%s_x%x", file->name.data(), offset) : file->name.data(), flags, entry, lv2_file::make_view(file, offset), offset); -} - -error_code sys_overlay_unload_module(u32 ovlmid) -{ - sys_overlay.warning("sys_overlay_unload_module(ovlmid=0x%x)", ovlmid); - - if (!g_ps3_process_info.ppc_seg) - { - // Process not permitted - return CELL_ENOSYS; - } - - const auto _main = idm::withdraw(ovlmid); - - if (!_main) - { - return CELL_ESRCH; - } - - for (auto& seg : _main->segs) - { - vm::dealloc(seg.addr); - } - - ppu_finalize(*_main); - - return CELL_OK; -} diff --git a/rpcs3/Emu/Cell/lv2/sys_ppu_thread.cpp b/rpcs3/Emu/Cell/lv2/sys_ppu_thread.cpp deleted file mode 100644 index d10c64039..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_ppu_thread.cpp +++ /dev/null @@ -1,678 +0,0 @@ -#include "stdafx.h" -#include "sys_ppu_thread.h" - -#include "Emu/System.h" -#include "Emu/IdManager.h" - -#include "Emu/Cell/ErrorCodes.h" -#include "Emu/Cell/PPUThread.h" -#include "Emu/Cell/PPUCallback.h" -#include "Emu/Cell/PPUOpcodes.h" -#include "Emu/Memory/vm_locking.h" -#include "sys_event.h" -#include "sys_process.h" -#include "sys_mmapper.h" -#include "sys_memory.h" - -#include "util/asm.hpp" - -#include - -LOG_CHANNEL(sys_ppu_thread); - -// Simple structure to cleanup previous thread, because can't remove its own thread -struct ppu_thread_cleaner -{ - shared_ptr> old; - - shared_ptr> clean(shared_ptr> ptr) - { - return std::exchange(old, std::move(ptr)); - } - - ppu_thread_cleaner() = default; - - ppu_thread_cleaner(const ppu_thread_cleaner&) = delete; - - ppu_thread_cleaner& operator=(const ppu_thread_cleaner&) = delete; - - ppu_thread_cleaner& operator=(thread_state state) noexcept - { - reader_lock lock(id_manager::g_mutex); - - if (old) - { - // It is detached from IDM now so join must be done explicitly now - *static_cast*>(old.get()) = state; - } - - return *this; - } -}; - -void ppu_thread_exit(ppu_thread& ppu, ppu_opcode_t, be_t*, struct ppu_intrp_func*) -{ - ppu.state += cpu_flag::exit + cpu_flag::wait; - - // Deallocate Stack Area - ensure(vm::dealloc(ppu.stack_addr, vm::stack) == ppu.stack_size); - - if (auto dct = g_fxo->try_get()) - { - dct->free(ppu.stack_size); - } - - if (ppu.call_history.index) - { - ppu_log.notice("Calling history: %s", ppu.call_history); - ppu.call_history.index = 0; - } - - if (ppu.syscall_history.index) - { - ppu_log.notice("HLE/LV2 history: %s", ppu.syscall_history); - ppu.syscall_history.index = 0; - } -} - -constexpr u32 c_max_ppu_name_size = 28; - -void _sys_ppu_thread_exit(ppu_thread& ppu, u64 errorcode) -{ - ppu.state += cpu_flag::wait; - u64 writer_mask = 0; - - sys_ppu_thread.trace("_sys_ppu_thread_exit(errorcode=0x%llx)", errorcode); - - ppu_join_status old_status; - - // Avoid cases where cleaning causes the destructor to be called inside IDM lock scope (for performance) - shared_ptr> old_ppu; - { - lv2_obj::notify_all_t notify; - lv2_obj::prepare_for_sleep(ppu); - - std::lock_guard lock(id_manager::g_mutex); - - // Get joiner ID - old_status = ppu.joiner.fetch_op([](ppu_join_status& status) - { - if (status == ppu_join_status::joinable) - { - // Joinable, not joined - status = ppu_join_status::zombie; - return; - } - - // Set deleted thread status - status = ppu_join_status::exited; - }); - - if (old_status >= ppu_join_status::max) - { - lv2_obj::append(idm::check_unlocked>(static_cast(old_status))); - } - - if (old_status != ppu_join_status::joinable) - { - // Remove self ID from IDM, move owning ptr - old_ppu = g_fxo->get().clean(idm::withdraw>(ppu.id, 0, std::false_type{})); - } - - // Get writers mask (wait for all current writers to quit) - writer_mask = vm::g_range_lock_bits[1]; - - // Unqueue - lv2_obj::sleep(ppu); - notify.cleanup(); - - // Remove suspend state (TODO) - ppu.state -= cpu_flag::suspend; - } - - while (ppu.joiner == ppu_join_status::zombie) - { - if (ppu.is_stopped() && ppu.joiner.compare_and_swap_test(ppu_join_status::zombie, ppu_join_status::joinable)) - { - // Abort - ppu.state += cpu_flag::again; - return; - } - - // Wait for termination - thread_ctrl::wait_on(ppu.joiner, ppu_join_status::zombie); - } - - ppu_thread_exit(ppu, {}, nullptr, nullptr); - - if (old_ppu) - { - // It is detached from IDM now so join must be done explicitly now - *old_ppu = thread_state::finished; - } - - // Need to wait until the current writers finish - if (ppu.state & cpu_flag::memory) - { - for (; writer_mask; writer_mask &= vm::g_range_lock_bits[1]) - { - busy_wait(200); - } - } -} - -s32 sys_ppu_thread_yield(ppu_thread& ppu) -{ - ppu.state += cpu_flag::wait; - - sys_ppu_thread.trace("sys_ppu_thread_yield()"); - - const s32 success = lv2_obj::yield(ppu) ? CELL_OK : CELL_CANCEL; - - if (success == CELL_CANCEL) - { - // Do other work in the meantime - lv2_obj::notify_all(); - } - - // Return 0 on successful context switch, 1 otherwise - return success; -} - -error_code sys_ppu_thread_join(ppu_thread& ppu, u32 thread_id, vm::ptr vptr) -{ - lv2_obj::prepare_for_sleep(ppu); - - sys_ppu_thread.trace("sys_ppu_thread_join(thread_id=0x%x, vptr=*0x%x)", thread_id, vptr); - - if (thread_id == ppu.id) - { - return CELL_EDEADLK; - } - - auto thread = idm::get>(thread_id, [&, notify = lv2_obj::notify_all_t()](ppu_thread& thread) -> CellError - { - CellError result = thread.joiner.atomic_op([&](ppu_join_status& value) -> CellError - { - switch (value) - { - case ppu_join_status::joinable: - value = ppu_join_status{ppu.id}; - return {}; - case ppu_join_status::zombie: - value = ppu_join_status::exited; - return CELL_EAGAIN; - case ppu_join_status::exited: - return CELL_ESRCH; - case ppu_join_status::detached: - default: - return CELL_EINVAL; - } - }); - - if (!result) - { - lv2_obj::prepare_for_sleep(ppu); - lv2_obj::sleep(ppu); - } - - notify.cleanup(); - return result; - }); - - if (!thread) - { - return CELL_ESRCH; - } - - if (thread.ret && thread.ret != CELL_EAGAIN) - { - return thread.ret; - } - - if (thread.ret == CELL_EAGAIN) - { - // Notify thread if waiting for a joiner - thread->joiner.notify_one(); - } - - // Wait for cleanup - (*thread.ptr)(); - - if (thread->joiner != ppu_join_status::exited) - { - // Thread aborted, log it later - ppu.state += cpu_flag::again; - return {}; - } - - static_cast(ppu.test_stopped()); - - // Get the exit status from the register - const u64 vret = thread->gpr[3]; - - if (thread.ret == CELL_EAGAIN) - { - // Cleanup - ensure(idm::remove_verify>(thread_id, std::move(thread.ptr))); - } - - if (!vptr) - { - return not_an_error(CELL_EFAULT); - } - - *vptr = vret; - return CELL_OK; -} - -error_code sys_ppu_thread_detach(ppu_thread& ppu, u32 thread_id) -{ - ppu.state += cpu_flag::wait; - - sys_ppu_thread.trace("sys_ppu_thread_detach(thread_id=0x%x)", thread_id); - - CellError result = CELL_ESRCH; - - auto [ptr, _] = idm::withdraw>(thread_id, [&](ppu_thread& thread) - { - result = thread.joiner.atomic_op([](ppu_join_status& value) -> CellError - { - switch (value) - { - case ppu_join_status::joinable: - value = ppu_join_status::detached; - return {}; - case ppu_join_status::detached: - return CELL_EINVAL; - case ppu_join_status::zombie: - value = ppu_join_status::exited; - return CELL_EAGAIN; - case ppu_join_status::exited: - return CELL_ESRCH; - default: - return CELL_EBUSY; - } - }); - - // Remove ID on EAGAIN - return result != CELL_EAGAIN; - }); - - if (result) - { - if (result == CELL_EAGAIN) - { - // Join and notify thread (it is detached from IDM now so it must be done explicitly now) - *ptr = thread_state::finished; - } - - return result; - } - - return CELL_OK; -} - -error_code sys_ppu_thread_get_join_state(ppu_thread& ppu, vm::ptr isjoinable) -{ - sys_ppu_thread.trace("sys_ppu_thread_get_join_state(isjoinable=*0x%x)", isjoinable); - - if (!isjoinable) - { - return CELL_EFAULT; - } - - *isjoinable = ppu.joiner != ppu_join_status::detached; - return CELL_OK; -} - -error_code sys_ppu_thread_set_priority(ppu_thread& ppu, u32 thread_id, s32 prio) -{ - ppu.state += cpu_flag::wait; - - sys_ppu_thread.trace("sys_ppu_thread_set_priority(thread_id=0x%x, prio=%d)", thread_id, prio); - - if (prio < (g_ps3_process_info.debug_or_root() ? -512 : 0) || prio > 3071) - { - return CELL_EINVAL; - } - - if (thread_id == ppu.id) - { - // Fast path for self - if (ppu.prio.load().prio != prio) - { - lv2_obj::set_priority(ppu, prio); - } - - return CELL_OK; - } - - const auto thread = idm::check>(thread_id, [&, notify = lv2_obj::notify_all_t()](ppu_thread& thread) - { - lv2_obj::set_priority(thread, prio); - }); - - if (!thread) - { - return CELL_ESRCH; - } - - return CELL_OK; -} - -error_code sys_ppu_thread_get_priority(ppu_thread& ppu, u32 thread_id, vm::ptr priop) -{ - ppu.state += cpu_flag::wait; - - sys_ppu_thread.trace("sys_ppu_thread_get_priority(thread_id=0x%x, priop=*0x%x)", thread_id, priop); - u32 prio{}; - - if (thread_id == ppu.id) - { - // Fast path for self - for (; !ppu.is_stopped(); std::this_thread::yield()) - { - if (reader_lock lock(lv2_obj::g_mutex); cpu_flag::suspend - ppu.state) - { - prio = ppu.prio.load().prio; - break; - } - - ppu.check_state(); - ppu.state += cpu_flag::wait; - } - - ppu.check_state(); - *priop = prio; - return CELL_OK; - } - - for (; !ppu.is_stopped(); std::this_thread::yield()) - { - bool check_state = false; - const auto thread = idm::check>(thread_id, [&](ppu_thread& thread) - { - if (reader_lock lock(lv2_obj::g_mutex); cpu_flag::suspend - ppu.state) - { - prio = thread.prio.load().prio; - } - else - { - check_state = true; - } - }); - - if (check_state) - { - ppu.check_state(); - ppu.state += cpu_flag::wait; - continue; - } - - if (!thread) - { - return CELL_ESRCH; - } - - ppu.check_state(); - *priop = prio; - break; - } - - return CELL_OK; -} - -error_code sys_ppu_thread_get_stack_information(ppu_thread& ppu, vm::ptr sp) -{ - sys_ppu_thread.trace("sys_ppu_thread_get_stack_information(sp=*0x%x)", sp); - - sp->pst_addr = ppu.stack_addr; - sp->pst_size = ppu.stack_size; - - return CELL_OK; -} - -error_code sys_ppu_thread_stop(ppu_thread& ppu, u32 thread_id) -{ - ppu.state += cpu_flag::wait; - - sys_ppu_thread.todo("sys_ppu_thread_stop(thread_id=0x%x)", thread_id); - - if (!g_ps3_process_info.has_root_perm()) - { - return CELL_ENOSYS; - } - - const auto thread = idm::check>(thread_id, [](named_thread&) {}); - - if (!thread) - { - return CELL_ESRCH; - } - - return CELL_OK; -} - -error_code sys_ppu_thread_restart(ppu_thread& ppu) -{ - ppu.state += cpu_flag::wait; - - sys_ppu_thread.todo("sys_ppu_thread_restart()"); - - if (!g_ps3_process_info.has_root_perm()) - { - return CELL_ENOSYS; - } - - return CELL_OK; -} - -error_code _sys_ppu_thread_create(ppu_thread& ppu, vm::ptr thread_id, vm::ptr param, u64 arg, u64 unk, s32 prio, u32 _stacksz, u64 flags, vm::cptr threadname) -{ - ppu.state += cpu_flag::wait; - - sys_ppu_thread.warning("_sys_ppu_thread_create(thread_id=*0x%x, param=*0x%x, arg=0x%llx, unk=0x%llx, prio=%d, stacksize=0x%x, flags=0x%llx, threadname=*0x%x)", - thread_id, param, arg, unk, prio, _stacksz, flags, threadname); - - // thread_id is checked for null in stub -> CELL_ENOMEM - // unk is set to 0 in sys_ppu_thread_create stub - - if (!param || !param->entry) - { - return CELL_EFAULT; - } - - if (prio < (g_ps3_process_info.debug_or_root() ? -512 : 0) || prio > 3071) - { - return CELL_EINVAL; - } - - if ((flags & 3) == 3) // Check two flags: joinable + interrupt not allowed - { - return CELL_EPERM; - } - - const ppu_func_opd_t entry = param->entry.opd(); - const u32 tls = param->tls; - - // Compute actual stack size and allocate - const u32 stack_size = utils::align(std::max(_stacksz, 4096), 4096); - - auto& dct = g_fxo->get(); - - // Try to obtain "physical memory" from the default container - if (!dct.take(stack_size)) - { - return {CELL_ENOMEM, dct.size - dct.used}; - } - - const vm::addr_t stack_base{vm::alloc(stack_size, vm::stack, 4096)}; - - if (!stack_base) - { - dct.free(stack_size); - return CELL_ENOMEM; - } - - std::string ppu_name; - - if (threadname) - { - constexpr u32 max_size = c_max_ppu_name_size - 1; // max size excluding null terminator - - if (!vm::read_string(threadname.addr(), max_size, ppu_name, true)) - { - dct.free(stack_size); - return CELL_EFAULT; - } - } - - const u32 tid = idm::import >([&]() - { - ppu_thread_params p; - p.stack_addr = stack_base; - p.stack_size = stack_size; - p.tls_addr = tls; - p.entry = entry; - p.arg0 = arg; - p.arg1 = unk; - - return stx::make_shared>(p, ppu_name, prio, 1 - static_cast(flags & 3)); - }); - - if (!tid) - { - vm::dealloc(stack_base); - dct.free(stack_size); - return CELL_EAGAIN; - } - - sys_ppu_thread.warning("_sys_ppu_thread_create(): Thread \"%s\" created (id=0x%x, func=*0x%x, rtoc=0x%x, user-tls=0x%x)", ppu_name, tid, entry.addr, entry.rtoc, tls); - - ppu.check_state(); - *thread_id = tid; - return CELL_OK; -} - -error_code sys_ppu_thread_start(ppu_thread& ppu, u32 thread_id) -{ - ppu.state += cpu_flag::wait; - - sys_ppu_thread.trace("sys_ppu_thread_start(thread_id=0x%x)", thread_id); - - const auto thread = idm::get>(thread_id, [&, notify = lv2_obj::notify_all_t()](ppu_thread& thread) -> CellError - { - if (!thread.state.test_and_reset(cpu_flag::stop)) - { - // Already started - return CELL_EBUSY; - } - - ensure(lv2_obj::awake(&thread)); - - thread.cmd_list({ - {ppu_cmd::entry_call, 0}, - }); - - return {}; - }); - - if (!thread) - { - return CELL_ESRCH; - } - - if (thread.ret) - { - return thread.ret; - } - else - { - thread->cmd_notify.store(1); - thread->cmd_notify.notify_one(); - } - - return CELL_OK; -} - -error_code sys_ppu_thread_rename(ppu_thread& ppu, u32 thread_id, vm::cptr name) -{ - ppu.state += cpu_flag::wait; - - sys_ppu_thread.warning("sys_ppu_thread_rename(thread_id=0x%x, name=*0x%x)", thread_id, name); - - const auto thread = idm::get_unlocked>(thread_id); - - if (!thread) - { - return CELL_ESRCH; - } - - if (!name) - { - return CELL_EFAULT; - } - - constexpr u32 max_size = c_max_ppu_name_size - 1; // max size excluding null terminator - - // Make valid name - std::string out_str; - if (!vm::read_string(name.addr(), max_size, out_str, true)) - { - return CELL_EFAULT; - } - - auto _name = make_single(std::move(out_str)); - - // thread_ctrl name is not changed (TODO) - sys_ppu_thread.warning("sys_ppu_thread_rename(): Thread renamed to \"%s\"", *_name); - thread->ppu_tname.store(std::move(_name)); - thread_ctrl::set_name(*thread, thread->thread_name); // TODO: Currently sets debugger thread name only for local thread - - return CELL_OK; -} - -error_code sys_ppu_thread_recover_page_fault(ppu_thread& ppu, u32 thread_id) -{ - ppu.state += cpu_flag::wait; - - sys_ppu_thread.warning("sys_ppu_thread_recover_page_fault(thread_id=0x%x)", thread_id); - - const auto thread = idm::get_unlocked>(thread_id); - - if (!thread) - { - return CELL_ESRCH; - } - - return mmapper_thread_recover_page_fault(thread.get()); -} - -error_code sys_ppu_thread_get_page_fault_context(ppu_thread& ppu, u32 thread_id, vm::ptr ctxt) -{ - ppu.state += cpu_flag::wait; - - sys_ppu_thread.todo("sys_ppu_thread_get_page_fault_context(thread_id=0x%x, ctxt=*0x%x)", thread_id, ctxt); - - const auto thread = idm::get_unlocked>(thread_id); - - if (!thread) - { - return CELL_ESRCH; - } - - // We can only get a context if the thread is being suspended for a page fault. - auto& pf_events = g_fxo->get(); - reader_lock lock(pf_events.pf_mutex); - - const auto evt = pf_events.events.find(thread.get()); - if (evt == pf_events.events.end()) - { - return CELL_EINVAL; - } - - // TODO: Fill ctxt with proper information. - - return CELL_OK; -} diff --git a/rpcs3/Emu/Cell/lv2/sys_ppu_thread.h b/rpcs3/Emu/Cell/lv2/sys_ppu_thread.h deleted file mode 100644 index 81598288e..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_ppu_thread.h +++ /dev/null @@ -1,60 +0,0 @@ -#pragma once - -#include "Emu/Memory/vm_ptr.h" -#include "Emu/Cell/ErrorCodes.h" - -class ppu_thread; - -enum : s32 -{ - SYS_PPU_THREAD_ONCE_INIT = 0, - SYS_PPU_THREAD_DONE_INIT = 1, -}; - -// PPU Thread Flags -enum : u64 -{ - SYS_PPU_THREAD_CREATE_JOINABLE = 0x1, - SYS_PPU_THREAD_CREATE_INTERRUPT = 0x2, -}; - -struct sys_ppu_thread_stack_t -{ - be_t pst_addr; - be_t pst_size; -}; - -struct ppu_thread_param_t -{ - vm::bptr entry; - be_t tls; // vm::bptr -}; - -struct sys_ppu_thread_icontext_t -{ - be_t gpr[32]; - be_t cr; - be_t rsv1; - be_t xer; - be_t lr; - be_t ctr; - be_t pc; -}; - -// Syscalls - -void _sys_ppu_thread_exit(ppu_thread& ppu, u64 errorcode); -s32 sys_ppu_thread_yield(ppu_thread& ppu); // Return value is ignored by the library -error_code sys_ppu_thread_join(ppu_thread& ppu, u32 thread_id, vm::ptr vptr); -error_code sys_ppu_thread_detach(ppu_thread& ppu, u32 thread_id); -error_code sys_ppu_thread_get_join_state(ppu_thread& ppu, vm::ptr isjoinable); // Error code is ignored by the library -error_code sys_ppu_thread_set_priority(ppu_thread& ppu, u32 thread_id, s32 prio); -error_code sys_ppu_thread_get_priority(ppu_thread& ppu, u32 thread_id, vm::ptr priop); -error_code sys_ppu_thread_get_stack_information(ppu_thread& ppu, vm::ptr sp); -error_code sys_ppu_thread_stop(ppu_thread& ppu, u32 thread_id); -error_code sys_ppu_thread_restart(ppu_thread& ppu); -error_code _sys_ppu_thread_create(ppu_thread& ppu, vm::ptr thread_id, vm::ptr param, u64 arg, u64 arg4, s32 prio, u32 stacksize, u64 flags, vm::cptr threadname); -error_code sys_ppu_thread_start(ppu_thread& ppu, u32 thread_id); -error_code sys_ppu_thread_rename(ppu_thread& ppu, u32 thread_id, vm::cptr name); -error_code sys_ppu_thread_recover_page_fault(ppu_thread& ppu, u32 thread_id); -error_code sys_ppu_thread_get_page_fault_context(ppu_thread& ppu, u32 thread_id, vm::ptr ctxt); diff --git a/rpcs3/Emu/Cell/lv2/sys_process.cpp b/rpcs3/Emu/Cell/lv2/sys_process.cpp deleted file mode 100644 index 3a8297ddb..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_process.cpp +++ /dev/null @@ -1,539 +0,0 @@ -#include "stdafx.h" -#include "sys_process.h" -#include "Emu/Memory/vm_ptr.h" -#include "Emu/System.h" -#include "Emu/VFS.h" -#include "Emu/IdManager.h" - -#include "Crypto/unedat.h" -#include "Emu/Cell/ErrorCodes.h" -#include "Emu/Cell/PPUThread.h" -#include "sys_lwmutex.h" -#include "sys_lwcond.h" -#include "sys_mutex.h" -#include "sys_cond.h" -#include "sys_event.h" -#include "sys_event_flag.h" -#include "sys_interrupt.h" -#include "sys_memory.h" -#include "sys_mmapper.h" -#include "sys_prx.h" -#include "sys_overlay.h" -#include "sys_rwlock.h" -#include "sys_semaphore.h" -#include "sys_timer.h" -#include "sys_fs.h" -#include "sys_vm.h" -#include "sys_spu.h" - -// Check all flags known to be related to extended permissions (TODO) -// It's possible anything which has root flags implicitly has debug perm as well -// But I haven't confirmed it. -bool ps3_process_info_t::debug_or_root() const -{ - return (ctrl_flags1 & (0xe << 28)) != 0; -} - -bool ps3_process_info_t::has_root_perm() const -{ - return (ctrl_flags1 & (0xc << 28)) != 0; -} - -bool ps3_process_info_t::has_debug_perm() const -{ - return (ctrl_flags1 & (0xa << 28)) != 0; -} - -// If a SELF file is of CellOS return its filename, otheriwse return an empty string -std::string_view ps3_process_info_t::get_cellos_appname() const -{ - if (!has_root_perm() || !Emu.GetTitleID().empty()) - { - return {}; - } - - return std::string_view(Emu.GetBoot()).substr(Emu.GetBoot().find_last_of('/') + 1); -} - -LOG_CHANNEL(sys_process); - -ps3_process_info_t g_ps3_process_info; - -s32 process_getpid() -{ - // TODO: get current process id - return 1; -} - -s32 sys_process_getpid() -{ - sys_process.trace("sys_process_getpid() -> 1"); - return process_getpid(); -} - -s32 sys_process_getppid() -{ - sys_process.todo("sys_process_getppid() -> 0"); - return 0; -} - -template -u32 idm_get_count() -{ - return idm::select([&](u32, Get&) {}); -} - -error_code sys_process_get_number_of_object(u32 object, vm::ptr nump) -{ - sys_process.error("sys_process_get_number_of_object(object=0x%x, nump=*0x%x)", object, nump); - - switch (object) - { - case SYS_MEM_OBJECT: *nump = idm_get_count(); break; - case SYS_MUTEX_OBJECT: *nump = idm_get_count(); break; - case SYS_COND_OBJECT: *nump = idm_get_count(); break; - case SYS_RWLOCK_OBJECT: *nump = idm_get_count(); break; - case SYS_INTR_TAG_OBJECT: *nump = idm_get_count(); break; - case SYS_INTR_SERVICE_HANDLE_OBJECT: *nump = idm_get_count(); break; - case SYS_EVENT_QUEUE_OBJECT: *nump = idm_get_count(); break; - case SYS_EVENT_PORT_OBJECT: *nump = idm_get_count(); break; - case SYS_TRACE_OBJECT: - sys_process.error("sys_process_get_number_of_object: object = SYS_TRACE_OBJECT"); - *nump = 0; - break; - case SYS_SPUIMAGE_OBJECT: *nump = idm_get_count(); break; - case SYS_PRX_OBJECT: *nump = idm_get_count(); break; - case SYS_SPUPORT_OBJECT: - sys_process.error("sys_process_get_number_of_object: object = SYS_SPUPORT_OBJECT"); - *nump = 0; - break; - case SYS_OVERLAY_OBJECT: *nump = idm_get_count(); break; - case SYS_LWMUTEX_OBJECT: *nump = idm_get_count(); break; - case SYS_TIMER_OBJECT: *nump = idm_get_count(); break; - case SYS_SEMAPHORE_OBJECT: *nump = idm_get_count(); break; - case SYS_FS_FD_OBJECT: *nump = idm_get_count(); break; - case SYS_LWCOND_OBJECT: *nump = idm_get_count(); break; - case SYS_EVENT_FLAG_OBJECT: *nump = idm_get_count(); break; - - default: - { - return CELL_EINVAL; - } - } - - return CELL_OK; -} - -#include - -template -void idm_get_set(std::set& out) -{ - idm::select([&](u32 id, Get&) - { - out.emplace(id); - }); -} - -static error_code process_get_id(u32 object, vm::ptr buffer, u32 size, vm::ptr set_size) -{ - std::set objects; - - switch (object) - { - case SYS_MEM_OBJECT: idm_get_set(objects); break; - case SYS_MUTEX_OBJECT: idm_get_set(objects); break; - case SYS_COND_OBJECT: idm_get_set(objects); break; - case SYS_RWLOCK_OBJECT: idm_get_set(objects); break; - case SYS_INTR_TAG_OBJECT: idm_get_set(objects); break; - case SYS_INTR_SERVICE_HANDLE_OBJECT: idm_get_set(objects); break; - case SYS_EVENT_QUEUE_OBJECT: idm_get_set(objects); break; - case SYS_EVENT_PORT_OBJECT: idm_get_set(objects); break; - case SYS_TRACE_OBJECT: fmt::throw_exception("SYS_TRACE_OBJECT"); - case SYS_SPUIMAGE_OBJECT: idm_get_set(objects); break; - case SYS_PRX_OBJECT: idm_get_set(objects); break; - case SYS_OVERLAY_OBJECT: idm_get_set(objects); break; - case SYS_LWMUTEX_OBJECT: idm_get_set(objects); break; - case SYS_TIMER_OBJECT: idm_get_set(objects); break; - case SYS_SEMAPHORE_OBJECT: idm_get_set(objects); break; - case SYS_FS_FD_OBJECT: idm_get_set(objects); break; - case SYS_LWCOND_OBJECT: idm_get_set(objects); break; - case SYS_EVENT_FLAG_OBJECT: idm_get_set(objects); break; - case SYS_SPUPORT_OBJECT: fmt::throw_exception("SYS_SPUPORT_OBJECT"); - default: - { - return CELL_EINVAL; - } - } - - u32 i = 0; - - // NOTE: Treats negative and 0 values as 1 due to signed checks and "do-while" behavior of fw - for (auto id = objects.begin(); i < std::max(size, 1) + 0u && id != objects.end(); id++, i++) - { - buffer[i] = *id; - } - - *set_size = i; - - return CELL_OK; -} - -error_code sys_process_get_id(u32 object, vm::ptr buffer, u32 size, vm::ptr set_size) -{ - sys_process.error("sys_process_get_id(object=0x%x, buffer=*0x%x, size=%d, set_size=*0x%x)", object, buffer, size, set_size); - - if (object == SYS_SPUPORT_OBJECT) - { - // Unallowed for this syscall - return CELL_EINVAL; - } - - return process_get_id(object, buffer, size, set_size); -} - -error_code sys_process_get_id2(u32 object, vm::ptr buffer, u32 size, vm::ptr set_size) -{ - sys_process.error("sys_process_get_id2(object=0x%x, buffer=*0x%x, size=%d, set_size=*0x%x)", object, buffer, size, set_size); - - if (!g_ps3_process_info.has_root_perm()) - { - // This syscall is more capable than sys_process_get_id but also needs a root perm check - return CELL_ENOSYS; - } - - return process_get_id(object, buffer, size, set_size); -} - -CellError process_is_spu_lock_line_reservation_address(u32 addr, u64 flags) -{ - if (!flags || flags & ~(SYS_MEMORY_ACCESS_RIGHT_SPU_THR | SYS_MEMORY_ACCESS_RIGHT_RAW_SPU)) - { - return CELL_EINVAL; - } - - // TODO: respect sys_mmapper region's access rights - switch (addr >> 28) - { - case 0x0: // Main memory - case 0x1: // Main memory - case 0x2: // User 64k (sys_memory) - case 0xc: // RSX Local memory - case 0xe: // RawSPU MMIO - break; - - case 0xf: // Private SPU MMIO - { - if (flags & SYS_MEMORY_ACCESS_RIGHT_RAW_SPU) - { - // Cannot be accessed by RawSPU - return CELL_EPERM; - } - - break; - } - - case 0xd: // PPU Stack area - return CELL_EPERM; - default: - { - if (auto vm0 = idm::get_unlocked(sys_vm_t::find_id(addr))) - { - // sys_vm area was not covering the address specified but made a reservation on the entire 256mb region - if (vm0->addr + vm0->size - 1 < addr) - { - return CELL_EINVAL; - } - - // sys_vm memory is not allowed - return CELL_EPERM; - } - - if (!vm::get(vm::any, addr & -0x1000'0000)) - { - return CELL_EINVAL; - } - - break; - } - } - - return {}; -} - -error_code sys_process_is_spu_lock_line_reservation_address(u32 addr, u64 flags) -{ - sys_process.warning("sys_process_is_spu_lock_line_reservation_address(addr=0x%x, flags=0x%llx)", addr, flags); - - if (auto err = process_is_spu_lock_line_reservation_address(addr, flags)) - { - return err; - } - - return CELL_OK; -} - -error_code _sys_process_get_paramsfo(vm::ptr buffer) -{ - sys_process.warning("_sys_process_get_paramsfo(buffer=0x%x)", buffer); - - if (Emu.GetTitleID().empty()) - { - return CELL_ENOENT; - } - - memset(buffer.get_ptr(), 0, 0x40); - memcpy(buffer.get_ptr() + 1, Emu.GetTitleID().c_str(), std::min(Emu.GetTitleID().length(), 9)); - - return CELL_OK; -} - -s32 process_get_sdk_version(u32 /*pid*/, s32& ver) -{ - // get correct SDK version for selected pid - ver = g_ps3_process_info.sdk_ver; - - return CELL_OK; -} - -error_code sys_process_get_sdk_version(u32 pid, vm::ptr version) -{ - sys_process.warning("sys_process_get_sdk_version(pid=0x%x, version=*0x%x)", pid, version); - - s32 sdk_ver; - const s32 ret = process_get_sdk_version(pid, sdk_ver); - if (ret != CELL_OK) - { - return CellError{ret + 0u}; // error code - } - else - { - *version = sdk_ver; - return CELL_OK; - } -} - -error_code sys_process_kill(u32 pid) -{ - sys_process.todo("sys_process_kill(pid=0x%x)", pid); - return CELL_OK; -} - -error_code sys_process_wait_for_child(u32 pid, vm::ptr status, u64 unk) -{ - sys_process.todo("sys_process_wait_for_child(pid=0x%x, status=*0x%x, unk=0x%llx", pid, status, unk); - - return CELL_OK; -} - -error_code sys_process_wait_for_child2(u64 unk1, u64 unk2, u64 unk3, u64 unk4, u64 unk5, u64 unk6) -{ - sys_process.todo("sys_process_wait_for_child2(unk1=0x%llx, unk2=0x%llx, unk3=0x%llx, unk4=0x%llx, unk5=0x%llx, unk6=0x%llx)", - unk1, unk2, unk3, unk4, unk5, unk6); - return CELL_OK; -} - -error_code sys_process_get_status(u64 unk) -{ - sys_process.todo("sys_process_get_status(unk=0x%llx)", unk); - // vm::write32(CPU.gpr[4], GetPPUThreadStatus(CPU)); - return CELL_OK; -} - -error_code sys_process_detach_child(u64 unk) -{ - sys_process.todo("sys_process_detach_child(unk=0x%llx)", unk); - return CELL_OK; -} - -extern void signal_system_cache_can_stay(); - -void _sys_process_exit(ppu_thread& ppu, s32 status, u32 arg2, u32 arg3) -{ - ppu.state += cpu_flag::wait; - - sys_process.warning("_sys_process_exit(status=%d, arg2=0x%x, arg3=0x%x)", status, arg2, arg3); - - Emu.CallFromMainThread([]() - { - sys_process.success("Process finished"); - signal_system_cache_can_stay(); - Emu.Kill(); - }); - - // Wait for GUI thread - while (auto state = +ppu.state) - { - if (is_stopped(state)) - { - break; - } - - ppu.state.wait(state); - } -} - -void _sys_process_exit2(ppu_thread& ppu, s32 status, vm::ptr arg, u32 arg_size, u32 arg4) -{ - ppu.state += cpu_flag::wait; - - sys_process.warning("_sys_process_exit2(status=%d, arg=*0x%x, arg_size=0x%x, arg4=0x%x)", status, arg, arg_size, arg4); - - auto pstr = +arg->args; - - std::vector argv; - std::vector envp; - - while (auto ptr = *pstr++) - { - argv.emplace_back(ptr.get_ptr()); - sys_process.notice(" *** arg: %s", ptr); - } - - while (auto ptr = *pstr++) - { - envp.emplace_back(ptr.get_ptr()); - sys_process.notice(" *** env: %s", ptr); - } - - std::vector data; - - if (arg_size > 0x1030) - { - data.resize(0x1000); - std::memcpy(data.data(), vm::base(arg.addr() + arg_size - 0x1000), 0x1000); - } - - if (argv.empty()) - { - return _sys_process_exit(ppu, status, 0, 0); - } - - // TODO: set prio, flags - - lv2_exitspawn(ppu, argv, envp, data); -} - -void lv2_exitspawn(ppu_thread& ppu, std::vector& argv, std::vector& envp, std::vector& data) -{ - ppu.state += cpu_flag::wait; - - // sys_sm_shutdown - const bool is_real_reboot = (ppu.gpr[11] == 379); - - Emu.CallFromMainThread([is_real_reboot, argv = std::move(argv), envp = std::move(envp), data = std::move(data)]() mutable - { - sys_process.success("Process finished -> %s", argv[0]); - - std::string disc; - - if (Emu.GetCat() == "DG" || Emu.GetCat() == "GD") - disc = vfs::get("/dev_bdvd/"); - if (disc.empty() && !Emu.GetTitleID().empty()) - disc = vfs::get(Emu.GetDir()); - - std::string path = vfs::get(argv[0]); - std::string hdd1 = vfs::get("/dev_hdd1/"); - - const u128 klic = g_fxo->get().last_key(); - - using namespace id_manager; - - shared_ptr idm_capture = make_shared(); - - if (!is_real_reboot) - { - reader_lock rlock{id_manager::g_mutex}; - g_fxo->get>().save(*idm_capture); - stx::serial_breathe_and_tag(*idm_capture, "id_map", false); - } - - idm_capture->set_reading_state(); - - auto func = [is_real_reboot, old_size = g_fxo->get().size, idm_capture](u32 sdk_suggested_mem) mutable - { - if (is_real_reboot) - { - // Do not save containers on actual reboot - ensure(g_fxo->init>()); - } - else - { - // Save LV2 memory containers - ensure(g_fxo->init>(*idm_capture)); - } - - // Empty the containers, accumulate their total size - u32 total_size = 0; - idm::select([&](u32, lv2_memory_container& ctr) - { - ctr.used = 0; - total_size += ctr.size; - }); - - // The default memory container capacity can only decrease after exitspawn - // 1. If newer SDK version suggests higher memory capacity - it is ignored - // 2. If newer SDK version suggests lower memory capacity - it is lowered - // And if 2. happens while user memory containers exist, the left space can be spent on user memory containers - ensure(g_fxo->init(std::min(old_size - total_size, sdk_suggested_mem) + total_size)); - }; - - Emu.after_kill_callback = [func = std::move(func), argv = std::move(argv), envp = std::move(envp), data = std::move(data), - disc = std::move(disc), path = std::move(path), hdd1 = std::move(hdd1), old_config = Emu.GetUsedConfig(), klic]() mutable - { - Emu.argv = std::move(argv); - Emu.envp = std::move(envp); - Emu.data = std::move(data); - Emu.disc = std::move(disc); - Emu.hdd1 = std::move(hdd1); - Emu.init_mem_containers = std::move(func); - - if (klic) - { - Emu.klic.emplace_back(klic); - } - - Emu.SetForceBoot(true); - - auto res = Emu.BootGame(path, "", true, cfg_mode::continuous, old_config); - - if (res != game_boot_result::no_errors) - { - sys_process.fatal("Failed to boot from exitspawn! (path=\"%s\", error=%s)", path, res); - } - }; - - signal_system_cache_can_stay(); - - // Make sure we keep the game window opened - Emu.SetContinuousMode(true); - Emu.Kill(false); - }); - - // Wait for GUI thread - while (auto state = +ppu.state) - { - if (is_stopped(state)) - { - break; - } - - ppu.state.wait(state); - } -} - -void sys_process_exit3(ppu_thread& ppu, s32 status) -{ - ppu.state += cpu_flag::wait; - - sys_process.warning("_sys_process_exit3(status=%d)", status); - - return _sys_process_exit(ppu, status, 0, 0); -} - -error_code sys_process_spawns_a_self2(vm::ptr pid, u32 primary_prio, u64 flags, vm::ptr stack, u32 stack_size, u32 mem_id, vm::ptr param_sfo, vm::ptr dbg_data) -{ - sys_process.todo("sys_process_spawns_a_self2(pid=*0x%x, primary_prio=0x%x, flags=0x%llx, stack=*0x%x, stack_size=0x%x, mem_id=0x%x, param_sfo=*0x%x, dbg_data=*0x%x", pid, primary_prio, flags, stack, stack_size, mem_id, param_sfo, dbg_data); - - return CELL_OK; -} diff --git a/rpcs3/Emu/Cell/lv2/sys_process.h b/rpcs3/Emu/Cell/lv2/sys_process.h deleted file mode 100644 index 8e18ed7ba..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_process.h +++ /dev/null @@ -1,126 +0,0 @@ -#pragma once - -#include "Crypto/unself.h" -#include "Emu/Memory/vm_ptr.h" -#include "Emu/Cell/ErrorCodes.h" - -// Process Local Object Type -enum : u32 -{ - SYS_MEM_OBJECT = 0x08, - SYS_MUTEX_OBJECT = 0x85, - SYS_COND_OBJECT = 0x86, - SYS_RWLOCK_OBJECT = 0x88, - SYS_INTR_TAG_OBJECT = 0x0A, - SYS_INTR_SERVICE_HANDLE_OBJECT = 0x0B, - SYS_EVENT_QUEUE_OBJECT = 0x8D, - SYS_EVENT_PORT_OBJECT = 0x0E, - SYS_TRACE_OBJECT = 0x21, - SYS_SPUIMAGE_OBJECT = 0x22, - SYS_PRX_OBJECT = 0x23, - SYS_SPUPORT_OBJECT = 0x24, - SYS_OVERLAY_OBJECT = 0x25, - SYS_LWMUTEX_OBJECT = 0x95, - SYS_TIMER_OBJECT = 0x11, - SYS_SEMAPHORE_OBJECT = 0x96, - SYS_FS_FD_OBJECT = 0x73, - SYS_LWCOND_OBJECT = 0x97, - SYS_EVENT_FLAG_OBJECT = 0x98, - SYS_RSXAUDIO_OBJECT = 0x60, -}; - -enum : u64 -{ - SYS_PROCESS_PRIMARY_STACK_SIZE_32K = 0x0000000000000010, - SYS_PROCESS_PRIMARY_STACK_SIZE_64K = 0x0000000000000020, - SYS_PROCESS_PRIMARY_STACK_SIZE_96K = 0x0000000000000030, - SYS_PROCESS_PRIMARY_STACK_SIZE_128K = 0x0000000000000040, - SYS_PROCESS_PRIMARY_STACK_SIZE_256K = 0x0000000000000050, - SYS_PROCESS_PRIMARY_STACK_SIZE_512K = 0x0000000000000060, - SYS_PROCESS_PRIMARY_STACK_SIZE_1M = 0x0000000000000070, -}; - -constexpr auto SYS_PROCESS_PARAM_SECTION_NAME = ".sys_proc_param"; - -enum -{ - SYS_PROCESS_PARAM_INVALID_PRIO = -32768, -}; - -enum : u32 -{ - SYS_PROCESS_PARAM_INVALID_STACK_SIZE = 0xffffffff, - - SYS_PROCESS_PARAM_STACK_SIZE_MIN = 0x1000, // 4KB - SYS_PROCESS_PARAM_STACK_SIZE_MAX = 0x100000, // 1MB - - SYS_PROCESS_PARAM_VERSION_INVALID = 0xffffffff, - SYS_PROCESS_PARAM_VERSION_1 = 0x00000001, // for SDK 08X - SYS_PROCESS_PARAM_VERSION_084_0 = 0x00008400, - SYS_PROCESS_PARAM_VERSION_090_0 = 0x00009000, - SYS_PROCESS_PARAM_VERSION_330_0 = 0x00330000, - - SYS_PROCESS_PARAM_MAGIC = 0x13bcc5f6, - - SYS_PROCESS_PARAM_MALLOC_PAGE_SIZE_NONE = 0x00000000, - SYS_PROCESS_PARAM_MALLOC_PAGE_SIZE_64K = 0x00010000, - SYS_PROCESS_PARAM_MALLOC_PAGE_SIZE_1M = 0x00100000, - - SYS_PROCESS_PARAM_PPC_SEG_DEFAULT = 0x00000000, - SYS_PROCESS_PARAM_PPC_SEG_OVLM = 0x00000001, - SYS_PROCESS_PARAM_PPC_SEG_FIXEDADDR_PRX = 0x00000002, - - SYS_PROCESS_PARAM_SDK_VERSION_UNKNOWN = 0xffffffff, -}; - -struct sys_exit2_param -{ - be_t x0; // 0x85 - be_t this_size; // 0x30 - be_t next_size; - be_t prio; - be_t flags; - vm::bpptr args; -}; - -struct ps3_process_info_t -{ - u32 sdk_ver; - u32 ppc_seg; - SelfAdditionalInfo self_info; - u32 ctrl_flags1 = 0; - - bool has_root_perm() const; - bool has_debug_perm() const; - bool debug_or_root() const; - std::string_view get_cellos_appname() const; -}; - -extern ps3_process_info_t g_ps3_process_info; - -// Auxiliary functions -s32 process_getpid(); -s32 process_get_sdk_version(u32 pid, s32& ver); -void lv2_exitspawn(ppu_thread& ppu, std::vector& argv, std::vector& envp, std::vector& data); - -enum CellError : u32; -CellError process_is_spu_lock_line_reservation_address(u32 addr, u64 flags); - -// SysCalls -s32 sys_process_getpid(); -s32 sys_process_getppid(); -error_code sys_process_get_number_of_object(u32 object, vm::ptr nump); -error_code sys_process_get_id(u32 object, vm::ptr buffer, u32 size, vm::ptr set_size); -error_code sys_process_get_id2(u32 object, vm::ptr buffer, u32 size, vm::ptr set_size); -error_code _sys_process_get_paramsfo(vm::ptr buffer); -error_code sys_process_get_sdk_version(u32 pid, vm::ptr version); -error_code sys_process_get_status(u64 unk); -error_code sys_process_is_spu_lock_line_reservation_address(u32 addr, u64 flags); -error_code sys_process_kill(u32 pid); -error_code sys_process_wait_for_child(u32 pid, vm::ptr status, u64 unk); -error_code sys_process_wait_for_child2(u64 unk1, u64 unk2, u64 unk3, u64 unk4, u64 unk5, u64 unk6); -error_code sys_process_detach_child(u64 unk); -void _sys_process_exit(ppu_thread& ppu, s32 status, u32 arg2, u32 arg3); -void _sys_process_exit2(ppu_thread& ppu, s32 status, vm::ptr arg, u32 arg_size, u32 arg4); -void sys_process_exit3(ppu_thread& ppu, s32 status); -error_code sys_process_spawns_a_self2(vm::ptr pid, u32 primary_prio, u64 flags, vm::ptr stack, u32 stack_size, u32 mem_id, vm::ptr param_sfo, vm::ptr dbg_data); diff --git a/rpcs3/Emu/Cell/lv2/sys_prx.cpp b/rpcs3/Emu/Cell/lv2/sys_prx.cpp deleted file mode 100644 index 15e7ea982..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_prx.cpp +++ /dev/null @@ -1,1202 +0,0 @@ -#include "stdafx.h" -#include "sys_prx.h" - -#include "Emu/System.h" -#include "Emu/system_config.h" -#include "Emu/VFS.h" -#include "Emu/IdManager.h" -#include "Crypto/unself.h" -#include "Loader/ELF.h" - -#include "Emu/Cell/PPUThread.h" -#include "Emu/Cell/ErrorCodes.h" -#include "Crypto/unedat.h" -#include "util/StrUtil.h" -#include "sys_fs.h" -#include "sys_process.h" -#include "sys_memory.h" -#include - -extern void dump_executable(std::span data, const ppu_module* _module, std::string_view title_id); - -extern shared_ptr ppu_load_prx(const ppu_prx_object&, bool virtual_load, const std::string&, s64, utils::serial* = nullptr); -extern void ppu_unload_prx(const lv2_prx& prx); -extern bool ppu_initialize(const ppu_module&, bool check_only = false, u64 file_size = 0); -extern void ppu_finalize(const ppu_module& info, bool force_mem_release = false); -extern void ppu_manual_load_imports_exports(u32 imports_start, u32 imports_size, u32 exports_start, u32 exports_size, std::basic_string& loaded_flags); - -LOG_CHANNEL(sys_prx); - -// -extern const std::map g_prx_list{ - {"/dev_flash/sys/internal/libfs_utility_init.sprx", 1}, - {"libaacenc.sprx", 0}, - {"libaacenc_spurs.sprx", 0}, - {"libac3dec.sprx", 0}, - {"libac3dec2.sprx", 0}, - {"libadec.sprx", 1}, - {"libadec2.sprx", 0}, - {"libadec_internal.sprx", 0}, - {"libad_async.sprx", 0}, - {"libad_billboard_util.sprx", 0}, - {"libad_core.sprx", 0}, - {"libapostsrc_mini.sprx", 0}, - {"libasfparser2_astd.sprx", 0}, - {"libat3dec.sprx", 0}, - {"libat3multidec.sprx", 0}, - {"libatrac3multi.sprx", 0}, - {"libatrac3plus.sprx", 0}, - {"libatxdec.sprx", 1}, - {"libatxdec2.sprx", 0}, - {"libaudio.sprx", 1}, - {"libavcdec.sprx", 0}, - {"libavcenc.sprx", 0}, - {"libavcenc_small.sprx", 0}, - {"libavchatjpgdec.sprx", 0}, - {"libbeisobmf.sprx", 0}, - {"libbemp2sys.sprx", 0}, - {"libcamera.sprx", 1}, - {"libcelp8dec.sprx", 0}, - {"libcelp8enc.sprx", 0}, - {"libcelpdec.sprx", 0}, - {"libcelpenc.sprx", 0}, - {"libddpdec.sprx", 0}, - {"libdivxdec.sprx", 0}, - {"libdmux.sprx", 0}, - {"libdmuxpamf.sprx", 0}, - {"libdtslbrdec.sprx", 0}, - {"libfiber.sprx", 0}, - {"libfont.sprx", 0}, - {"libfontFT.sprx", 0}, - {"libfreetype.sprx", 0}, - {"libfreetypeTT.sprx", 0}, - {"libfs.sprx", 0}, - {"libfs_155.sprx", 0}, - {"libgcm_sys.sprx", 0}, - {"libgem.sprx", 1}, - {"libgifdec.sprx", 0}, - {"libhttp.sprx", 0}, - {"libio.sprx", 1}, - {"libjpgdec.sprx", 0}, - {"libjpgenc.sprx", 0}, - {"libkey2char.sprx", 0}, - {"libl10n.sprx", 0}, - {"liblv2.sprx", 0}, - {"liblv2coredump.sprx", 0}, - {"liblv2dbg_for_cex.sprx", 0}, - {"libm2bcdec.sprx", 0}, - {"libm4aacdec.sprx", 0}, - {"libm4aacdec2ch.sprx", 0}, - {"libm4hdenc.sprx", 0}, - {"libm4venc.sprx", 0}, - {"libmedi.sprx", 1}, - {"libmic.sprx", 1}, - {"libmp3dec.sprx", 0}, - {"libmp4.sprx", 0}, - {"libmpl1dec.sprx", 0}, - {"libmvcdec.sprx", 0}, - {"libnet.sprx", 0}, - {"libnetctl.sprx", 1}, - {"libpamf.sprx", 1}, - {"libpngdec.sprx", 0}, - {"libpngenc.sprx", 0}, - {"libresc.sprx", 0}, - {"librtc.sprx", 1}, - {"librudp.sprx", 0}, - {"libsail.sprx", 0}, - {"libsail_avi.sprx", 0}, - {"libsail_rec.sprx", 0}, - {"libsjvtd.sprx", 0}, - {"libsmvd2.sprx", 0}, - {"libsmvd4.sprx", 0}, - {"libspurs_jq.sprx", 0}, - {"libsre.sprx", 0}, - {"libssl.sprx", 0}, - {"libsvc1d.sprx", 0}, - {"libsync2.sprx", 0}, - {"libsysmodule.sprx", 0}, - {"libsysutil.sprx", 1}, - {"libsysutil_ap.sprx", 1}, - {"libsysutil_authdialog.sprx", 1}, - {"libsysutil_avc2.sprx", 1}, - {"libsysutil_avconf_ext.sprx", 1}, - {"libsysutil_avc_ext.sprx", 1}, - {"libsysutil_bgdl.sprx", 1}, - {"libsysutil_cross_controller.sprx", 1}, - {"libsysutil_dec_psnvideo.sprx", 1}, - {"libsysutil_dtcp_ip.sprx", 1}, - {"libsysutil_game.sprx", 1}, - {"libsysutil_game_exec.sprx", 1}, - {"libsysutil_imejp.sprx", 1}, - {"libsysutil_misc.sprx", 1}, - {"libsysutil_music.sprx", 1}, - {"libsysutil_music_decode.sprx", 1}, - {"libsysutil_music_export.sprx", 1}, - {"libsysutil_np.sprx", 1}, - {"libsysutil_np2.sprx", 1}, - {"libsysutil_np_clans.sprx", 1}, - {"libsysutil_np_commerce2.sprx", 1}, - {"libsysutil_np_eula.sprx", 1}, - {"libsysutil_np_installer.sprx", 1}, - {"libsysutil_np_sns.sprx", 1}, - {"libsysutil_np_trophy.sprx", 1}, - {"libsysutil_np_tus.sprx", 1}, - {"libsysutil_np_util.sprx", 1}, - {"libsysutil_oskdialog_ext.sprx", 1}, - {"libsysutil_pesm.sprx", 1}, - {"libsysutil_photo_decode.sprx", 1}, - {"libsysutil_photo_export.sprx", 1}, - {"libsysutil_photo_export2.sprx", 1}, - {"libsysutil_photo_import.sprx", 1}, - {"libsysutil_photo_network_sharing.sprx", 1}, - {"libsysutil_print.sprx", 1}, - {"libsysutil_rec.sprx", 1}, - {"libsysutil_remoteplay.sprx", 1}, - {"libsysutil_rtcalarm.sprx", 1}, - {"libsysutil_savedata.sprx", 1}, - {"libsysutil_savedata_psp.sprx", 1}, - {"libsysutil_screenshot.sprx", 1}, - {"libsysutil_search.sprx", 1}, - {"libsysutil_storagedata.sprx", 1}, - {"libsysutil_subdisplay.sprx", 1}, - {"libsysutil_syschat.sprx", 1}, - {"libsysutil_sysconf_ext.sprx", 1}, - {"libsysutil_userinfo.sprx", 1}, - {"libsysutil_video_export.sprx", 1}, - {"libsysutil_video_player.sprx", 1}, - {"libsysutil_video_upload.sprx", 1}, - {"libusbd.sprx", 0}, - {"libusbpspcm.sprx", 0}, - {"libvdec.sprx", 1}, - {"libvoice.sprx", 1}, - {"libvpost.sprx", 0}, - {"libvpost2.sprx", 0}, - {"libwmadec.sprx", 0}, -}; - -bool ppu_register_library_lock(std::string_view libname, bool lock_lib); - -static error_code prx_load_module(const std::string& vpath, u64 flags, vm::ptr /*pOpt*/, fs::file src = {}, s64 file_offset = 0) -{ - if (flags != 0) - { - if (flags & SYS_PRX_LOAD_MODULE_FLAGS_INVALIDMASK) - { - return CELL_EINVAL; - } - - if (flags & SYS_PRX_LOAD_MODULE_FLAGS_FIXEDADDR && !g_ps3_process_info.ppc_seg) - { - return CELL_ENOSYS; - } - - fmt::throw_exception("sys_prx: Unimplemented fixed address allocations"); - } - - std::string vpath0; - std::string path = vfs::get(vpath, nullptr, &vpath0); - std::string name = vpath0.substr(vpath0.find_last_of('/') + 1); - - bool ignore = false; - - constexpr std::string_view firmware_sprx_dir = "/dev_flash/sys/external/"; - const bool is_firmware_sprx = vpath0.starts_with(firmware_sprx_dir) && g_prx_list.count(std::string_view(vpath0).substr(firmware_sprx_dir.size())); - - if (is_firmware_sprx) - { - if (g_cfg.core.libraries_control.get_set().count(name + ":lle")) - { - // Force LLE - ignore = false; - } - else if (g_cfg.core.libraries_control.get_set().count(name + ":hle")) - { - // Force HLE - ignore = true; - } - else - { - // Use list - ignore = ::at32(g_prx_list, name) != 0; - } - } - else if (vpath0.starts_with("/")) - { - // Special case : HLE for files outside of "/dev_flash/sys/external/" - // Have to specify full path for them - ignore = g_prx_list.count(vpath0) && ::at32(g_prx_list, vpath0); - } - - auto hle_load = [&]() - { - const auto prx = idm::make_ptr(); - - prx->name = std::move(name); - prx->path = std::move(path); - - sys_prx.warning("Ignored module: \"%s\" (id=0x%x)", vpath, idm::last_id()); - - return not_an_error(idm::last_id()); - }; - - if (ignore) - { - return hle_load(); - } - - if (!src) - { - auto [fs_error, ppath, path0, lv2_file, type] = lv2_file::open(vpath, 0, 0); - - if (fs_error) - { - if (fs_error + 0u == CELL_ENOENT && is_firmware_sprx) - { - sys_prx.error("firmware SPRX not found: \"%s\" (forcing HLE implementation)", vpath, idm::last_id()); - return hle_load(); - } - - return {fs_error, vpath}; - } - - src = std::move(lv2_file); - } - - u128 klic = g_fxo->get().last_key(); - - src = decrypt_self(std::move(src), reinterpret_cast(&klic)); - - if (!src) - { - return {CELL_PRX_ERROR_UNSUPPORTED_PRX_TYPE, +"Failed to decrypt file"}; - } - - const auto src_data = g_cfg.core.ppu_debug ? src.to_vector() : std::vector{}; - - ppu_prx_object obj = std::move(src); - src.close(); - - if (obj != elf_error::ok) - { - return {CELL_PRX_ERROR_UNSUPPORTED_PRX_TYPE, obj.get_error()}; - } - - const auto prx = ppu_load_prx(obj, false, path, file_offset); - - if (g_cfg.core.ppu_debug) - { - dump_executable({src_data.data(), src_data.size()}, prx.get(), Emu.GetTitleID()); - } - - obj.clear(); - - if (!prx) - { - return CELL_PRX_ERROR_ILLEGAL_LIBRARY; - } - - ppu_initialize(*prx); - - sys_prx.success("Loaded module: \"%s\" (id=0x%x)", vpath, idm::last_id()); - - return not_an_error(idm::last_id()); -} - -fs::file make_file_view(fs::file&& file, u64 offset, u64 size); - -std::function lv2_prx::load(utils::serial& ar) -{ - [[maybe_unused]] const s32 version = GET_SERIALIZATION_VERSION(lv2_prx_overlay); - - const std::string path = vfs::get(ar.pop()); - const s64 offset = ar; - const u32 state = ar; - - usz seg_count = 0; - ar.deserialize_vle(seg_count); - - shared_ptr prx; - - auto hle_load = [&]() - { - prx = make_shared(); - prx->path = path; - prx->name = path.substr(path.find_last_of(fs::delim) + 1); - }; - - if (seg_count) - { - std::basic_string loaded_flags, external_flags; - - ar(loaded_flags, external_flags); - - fs::file file{path.substr(0, path.size() - (offset ? fmt::format("_x%x", offset).size() : 0))}; - - if (file) - { - u128 klic = g_fxo->get().last_key(); - file = make_file_view(std::move(file), offset, umax); - prx = ppu_load_prx(ppu_prx_object{decrypt_self(std::move(file), reinterpret_cast(&klic))}, false, path, 0, &ar); - prx->m_loaded_flags = std::move(loaded_flags); - prx->m_external_loaded_flags = std::move(external_flags); - - if (state <= PRX_STATE_STARTED) - { - prx->restore_exports(); - } - - ensure(prx); - } - else - { - ensure(g_cfg.savestate.state_inspection_mode.get()); - - hle_load(); - - // Partially recover information - for (usz i = 0; i < seg_count; i++) - { - auto& seg = prx->segs.emplace_back(); - seg.addr = ar; - seg.size = 1; // TODO - } - } - } - else - { - hle_load(); - } - - prx->state = state; - - return [prx](void* storage) - { - *static_cast*>(storage) = prx; - }; -} - -void lv2_prx::save(utils::serial& ar) -{ - USING_SERIALIZATION_VERSION(lv2_prx_overlay); - - ar(vfs::retrieve(path), offset, state); - - // Save segments count - ar.serialize_vle(segs.size()); - - if (!segs.empty()) - { - ar(m_loaded_flags); - ar(m_external_loaded_flags); - } - - for (const ppu_segment& seg : segs) - { - if (seg.type == 0x1u && seg.size) - ar(seg.addr); - } -} - -error_code sys_prx_get_ppu_guid(ppu_thread& ppu) -{ - ppu.state += cpu_flag::wait; - - sys_prx.todo("sys_prx_get_ppu_guid()"); - return CELL_OK; -} - -error_code _sys_prx_load_module_by_fd(ppu_thread& ppu, s32 fd, u64 offset, u64 flags, vm::ptr pOpt) -{ - ppu.state += cpu_flag::wait; - - sys_prx.warning("_sys_prx_load_module_by_fd(fd=%d, offset=0x%x, flags=0x%x, pOpt=*0x%x)", fd, offset, flags, pOpt); - - const auto file = idm::get_unlocked(fd); - - if (!file) - { - return CELL_EBADF; - } - - std::lock_guard lock(file->mp->mutex); - - if (!file->file) - { - return CELL_EBADF; - } - - return prx_load_module(offset ? fmt::format("%s_x%x", file->name.data(), offset) : file->name.data(), flags, pOpt, lv2_file::make_view(file, offset), offset); -} - -error_code _sys_prx_load_module_on_memcontainer_by_fd(ppu_thread& ppu, s32 fd, u64 offset, u32 mem_ct, u64 flags, vm::ptr pOpt) -{ - ppu.state += cpu_flag::wait; - - sys_prx.warning("_sys_prx_load_module_on_memcontainer_by_fd(fd=%d, offset=0x%x, mem_ct=0x%x, flags=0x%x, pOpt=*0x%x)", fd, offset, mem_ct, flags, pOpt); - - return _sys_prx_load_module_by_fd(ppu, fd, offset, flags, pOpt); -} - -static error_code prx_load_module_list(ppu_thread& ppu, s32 count, vm::cpptr path_list, u32 /*mem_ct*/, u64 flags, vm::ptr pOpt, vm::ptr id_list) -{ - if (flags != 0) - { - if (flags & SYS_PRX_LOAD_MODULE_FLAGS_INVALIDMASK) - { - return CELL_EINVAL; - } - - if (flags & SYS_PRX_LOAD_MODULE_FLAGS_FIXEDADDR && !g_ps3_process_info.ppc_seg) - { - return CELL_ENOSYS; - } - - fmt::throw_exception("sys_prx: Unimplemented fixed address allocations"); - } - - for (s32 i = 0; i < count; ++i) - { - const auto result = prx_load_module(path_list[i].get_ptr(), flags, pOpt); - - if (result < 0) - { - while (--i >= 0) - { - // Unload already loaded modules - _sys_prx_unload_module(ppu, id_list[i], 0, vm::null); - } - - // Fill with -1 - std::memset(id_list.get_ptr(), -1, count * sizeof(id_list[0])); - return result; - } - - id_list[i] = result; - } - - return CELL_OK; -} - -error_code _sys_prx_load_module_list(ppu_thread& ppu, s32 count, vm::cpptr path_list, u64 flags, vm::ptr pOpt, vm::ptr id_list) -{ - ppu.state += cpu_flag::wait; - - sys_prx.warning("_sys_prx_load_module_list(count=%d, path_list=**0x%x, flags=0x%x, pOpt=*0x%x, id_list=*0x%x)", count, path_list, flags, pOpt, id_list); - - return prx_load_module_list(ppu, count, path_list, SYS_MEMORY_CONTAINER_ID_INVALID, flags, pOpt, id_list); -} -error_code _sys_prx_load_module_list_on_memcontainer(ppu_thread& ppu, s32 count, vm::cpptr path_list, u32 mem_ct, u64 flags, vm::ptr pOpt, vm::ptr id_list) -{ - ppu.state += cpu_flag::wait; - - sys_prx.warning("_sys_prx_load_module_list_on_memcontainer(count=%d, path_list=**0x%x, mem_ct=0x%x, flags=0x%x, pOpt=*0x%x, id_list=*0x%x)", count, path_list, mem_ct, flags, pOpt, id_list); - - return prx_load_module_list(ppu, count, path_list, mem_ct, flags, pOpt, id_list); -} - -error_code _sys_prx_load_module_on_memcontainer(ppu_thread& ppu, vm::cptr path, u32 mem_ct, u64 flags, vm::ptr pOpt) -{ - ppu.state += cpu_flag::wait; - - sys_prx.warning("_sys_prx_load_module_on_memcontainer(path=%s, mem_ct=0x%x, flags=0x%x, pOpt=*0x%x)", path, mem_ct, flags, pOpt); - - return prx_load_module(path.get_ptr(), flags, pOpt); -} - -error_code _sys_prx_load_module(ppu_thread& ppu, vm::cptr path, u64 flags, vm::ptr pOpt) -{ - ppu.state += cpu_flag::wait; - - sys_prx.warning("_sys_prx_load_module(path=%s, flags=0x%x, pOpt=*0x%x)", path, flags, pOpt); - - return prx_load_module(path.get_ptr(), flags, pOpt); -} - -error_code _sys_prx_start_module(ppu_thread& ppu, u32 id, u64 flags, vm::ptr pOpt) -{ - ppu.state += cpu_flag::wait; - - sys_prx.warning("_sys_prx_start_module(id=0x%x, flags=0x%x, pOpt=*0x%x)", id, flags, pOpt); - - if (id == 0 || !pOpt) - { - return CELL_EINVAL; - } - - const auto prx = idm::get_unlocked(id); - - if (!prx) - { - return CELL_ESRCH; - } - - switch (pOpt->cmd & 0xf) - { - case 1: - { - std::lock_guard lock(prx->mutex); - - if (!prx->state.compare_and_swap_test(PRX_STATE_INITIALIZED, PRX_STATE_STARTING)) - { - if (prx->state == PRX_STATE_DESTROYED) - { - return CELL_ESRCH; - } - - return CELL_PRX_ERROR_ERROR; - } - - prx->load_exports(); - break; - } - case 2: - { - switch (const u64 res = pOpt->res) - { - case SYS_PRX_RESIDENT: - { - // No error code on invalid state, so throw on unexpected state - ensure(prx->state.compare_and_swap_test(PRX_STATE_STARTING, PRX_STATE_STARTED)); - return CELL_OK; - } - default: - { - if (res & 0xffff'ffffu) - { - // Unload the module (SYS_PRX_NO_RESIDENT expected) - sys_prx.warning("_sys_prx_start_module(): Start entry function returned SYS_PRX_NO_RESIDENT (res=0x%llx)", res); - - // Thread-safe if called from liblv2.sprx, due to internal lwmutex lock before it - prx->state = PRX_STATE_STOPPED; - prx->unload_exports(); - _sys_prx_unload_module(ppu, id, 0, vm::null); - - // Return the exact value returned by the start function (as an error) - return static_cast(res); - } - - // Return type of start entry function is s32 - // And according to RE this path results in weird behavior - sys_prx.error("_sys_prx_start_module(): Start entry function returned weird value (res=0x%llx)", res); - return CELL_OK; - } - } - } - default: - return CELL_PRX_ERROR_ERROR; - } - - ppu.check_state(); - pOpt->entry.set(prx->start ? prx->start.addr() : ~0ull); - - // This check is probably for older fw - if (pOpt->size != 0x20u) - { - pOpt->entry2.set(prx->prologue ? prx->prologue.addr() : ~0ull); - } - - return CELL_OK; -} - -error_code _sys_prx_stop_module(ppu_thread& ppu, u32 id, u64 flags, vm::ptr pOpt) -{ - ppu.state += cpu_flag::wait; - - sys_prx.warning("_sys_prx_stop_module(id=0x%x, flags=0x%x, pOpt=*0x%x)", id, flags, pOpt); - - const auto prx = idm::get_unlocked(id); - - if (!prx) - { - return CELL_ESRCH; - } - - if (!pOpt) - { - return CELL_EINVAL; - } - - auto set_entry2 = [&](u64 addr) - { - if (pOpt->size != 0x20u) - { - pOpt->entry2.set(addr); - } - }; - - switch (pOpt->cmd & 0xf) - { - case 1: - { - switch (const auto old = prx->state.compare_and_swap(PRX_STATE_STARTED, PRX_STATE_STOPPING)) - { - case PRX_STATE_INITIALIZED: return CELL_PRX_ERROR_NOT_STARTED; - case PRX_STATE_STOPPED: return CELL_PRX_ERROR_ALREADY_STOPPED; - case PRX_STATE_STOPPING: return CELL_PRX_ERROR_ALREADY_STOPPING; // Internal error - case PRX_STATE_STARTING: return CELL_PRX_ERROR_ERROR; // Internal error - case PRX_STATE_DESTROYED: return CELL_ESRCH; - case PRX_STATE_STARTED: break; - default: - fmt::throw_exception("Invalid prx state (%d)", old); - } - - ppu.check_state(); - pOpt->entry.set(prx->stop ? prx->stop.addr() : ~0ull); - set_entry2(prx->epilogue ? prx->epilogue.addr() : ~0ull); - return CELL_OK; - } - case 2: - { - switch (pOpt->res) - { - case 0: - { - // No error code on invalid state, so throw on unexpected state - std::lock_guard lock(prx->mutex); - ensure(prx->exports_end <= prx->exports_start || (prx->state == PRX_STATE_STOPPING)); - - prx->unload_exports(); - - ensure(prx->state.compare_and_swap_test(PRX_STATE_STOPPING, PRX_STATE_STOPPED)); - return CELL_OK; - } - case 1: - return CELL_PRX_ERROR_CAN_NOT_STOP; // Internal error - default: - // Nothing happens (probably unexpected value) - return CELL_OK; - } - } - - // These commands are not used by liblv2.sprx - case 4: // Get start entry and stop functions - case 8: // Disable stop function execution - { - switch (const auto old = +prx->state) - { - case PRX_STATE_INITIALIZED: return CELL_PRX_ERROR_NOT_STARTED; - case PRX_STATE_STOPPED: return CELL_PRX_ERROR_ALREADY_STOPPED; - case PRX_STATE_STOPPING: return CELL_PRX_ERROR_ALREADY_STOPPING; // Internal error - case PRX_STATE_STARTING: return CELL_PRX_ERROR_ERROR; // Internal error - case PRX_STATE_DESTROYED: return CELL_ESRCH; - case PRX_STATE_STARTED: break; - default: - fmt::throw_exception("Invalid prx state (%d)", old); - } - - if (pOpt->cmd == 4u) - { - ppu.check_state(); - pOpt->entry.set(prx->stop ? prx->stop.addr() : ~0ull); - set_entry2(prx->epilogue ? prx->epilogue.addr() : ~0ull); - } - else - { - // Disables stop function execution (but the real value can be read through _sys_prx_get_module_info) - sys_prx.todo("_sys_prx_stop_module(): cmd is 8 (stop function = *0x%x)", prx->stop); - // prx->stop = vm::null; - } - - return CELL_OK; - } - default: - return CELL_PRX_ERROR_ERROR; - } -} - -error_code _sys_prx_unload_module(ppu_thread& ppu, u32 id, u64 flags, vm::ptr pOpt) -{ - ppu.state += cpu_flag::wait; - - // Get the PRX, free the used memory and delete the object and its ID - const auto prx = idm::withdraw(id, [](lv2_prx& prx) -> CellPrxError - { - switch (prx.state.fetch_op([](u32& value) - { - if (value == PRX_STATE_INITIALIZED || value == PRX_STATE_STOPPED) - { - value = PRX_STATE_DESTROYED; - return true; - } - - return false; - }) - .first) - { - case PRX_STATE_INITIALIZED: - case PRX_STATE_STOPPED: - return {}; - default: break; - } - - return CELL_PRX_ERROR_NOT_REMOVABLE; - }); - - if (!prx) - { - return {CELL_PRX_ERROR_UNKNOWN_MODULE, id}; - } - - if (prx.ret) - { - return {prx.ret, "%s (id=%s)", prx->name, id}; - } - - sys_prx.success("_sys_prx_unload_module(id=0x%x, flags=0x%x, pOpt=*0x%x): name='%s'", id, flags, pOpt, prx->name); - - prx->mutex.lock_unlock(); - - ppu_unload_prx(*prx); - - ppu_finalize(*prx); - - // s32 result = prx->exit ? prx->exit() : CELL_OK; - - return CELL_OK; -} - -void lv2_prx::load_exports() -{ - if (exports_end <= exports_start) - { - // Nothing to load - return; - } - - if (!m_loaded_flags.empty()) - { - // Already loaded - return; - } - - ppu_manual_load_imports_exports(0, 0, exports_start, exports_end - exports_start, m_loaded_flags); -} - -void lv2_prx::restore_exports() -{ - constexpr usz sizeof_export_data = 0x1C; - - std::basic_string loaded_flags_empty; - - for (u32 start = exports_start, i = 0; start < exports_end; i++, start += vm::read8(start) ? vm::read8(start) : sizeof_export_data) - { - if (::at32(m_external_loaded_flags, i) || (!m_loaded_flags.empty() && ::at32(m_loaded_flags, i))) - { - loaded_flags_empty.clear(); - ppu_manual_load_imports_exports(0, 0, start, sizeof_export_data, loaded_flags_empty); - } - } -} - -void lv2_prx::unload_exports() -{ - if (m_loaded_flags.empty()) - { - // Not loaded - return; - } - - std::basic_string merged = m_loaded_flags; - - for (usz i = 0; i < merged.size(); i++) - { - merged[i] |= ::at32(m_external_loaded_flags, i); - } - - ppu_manual_load_imports_exports(0, 0, exports_start, exports_end - exports_start, merged); -} - -error_code _sys_prx_register_module(ppu_thread& ppu, vm::cptr name, vm::ptr opt) -{ - ppu.state += cpu_flag::wait; - - sys_prx.todo("_sys_prx_register_module(name=%s, opt=*0x%x)", name, opt); - - if (!opt) - { - return CELL_EINVAL; - } - - sys_prx_register_module_0x30_type_1_t info{}; - - switch (const u64 size_struct = vm::read64(opt.addr())) - { - case 0x1c: - case 0x20: - { - const auto _info = vm::static_ptr_cast(opt); - - sys_prx.todo("_sys_prx_register_module(): opt size is 0x%x", size_struct); - - // Rebuild info with corresponding members of old structures - // Weird that type is set to 0 because 0 means NO-OP in this syscall - info.size = 0x30; - info.lib_stub_size = _info->stubs_size; - info.lib_stub_ea = _info->stubs_ea; - info.error_handler = _info->error_handler; - info.type = 0; - break; - } - case 0x30: - { - std::memcpy(&info, opt.get_ptr(), sizeof(info)); - break; - } - default: return CELL_EINVAL; - } - - sys_prx.warning("opt: size=0x%x, type=0x%x, unk3=0x%x, unk4=0x%x, lib_entries_ea=%s, lib_entries_size=0x%x" - ", lib_stub_ea=%s, lib_stub_size=0x%x, error_handler=%s", - info.size, info.type, info.unk3, info.unk4, info.lib_entries_ea, info.lib_entries_size, info.lib_stub_ea, info.lib_stub_size, info.error_handler); - - if (info.type & 0x1) - { - if (Emu.IsVsh()) - { - ppu_manual_load_imports_exports(info.lib_stub_ea.addr(), info.lib_stub_size, info.lib_entries_ea.addr(), info.lib_entries_size, *std::make_unique>()); - } - else - { - // Only VSH is allowed to load it manually - return not_an_error(CELL_PRX_ERROR_ELF_IS_REGISTERED); - } - } - - return CELL_OK; -} - -error_code _sys_prx_query_module(ppu_thread& ppu) -{ - ppu.state += cpu_flag::wait; - - sys_prx.todo("_sys_prx_query_module()"); - return CELL_OK; -} - -error_code _sys_prx_register_library(ppu_thread& ppu, vm::ptr library) -{ - ppu.state += cpu_flag::wait; - - sys_prx.notice("_sys_prx_register_library(library=*0x%x)", library); - - if (!vm::check_addr(library.addr())) - { - return CELL_EFAULT; - } - - constexpr u32 sizeof_lib = 0x1c; - - std::array mem_copy{}; - std::memcpy(mem_copy.data(), library.get_ptr(), sizeof_lib); - - std::basic_string flags; - ppu_manual_load_imports_exports(0, 0, library.addr(), sizeof_lib, flags); - - if (flags.front()) - { - const bool success = idm::select([&](u32 /*id*/, lv2_prx& prx) - { - if (prx.state == PRX_STATE_INITIALIZED) - { - for (u32 lib_addr = prx.exports_start, index = 0; lib_addr < prx.exports_end; index++, lib_addr += vm::read8(lib_addr) ? vm::read8(lib_addr) : sizeof_lib) - { - if (std::memcpy(vm::base(lib_addr), mem_copy.data(), sizeof_lib) == 0) - { - atomic_storage::release(prx.m_external_loaded_flags[index], true); - return true; - } - } - } - - return false; - }).ret; - - if (!success) - { - sys_prx.error("_sys_prx_register_library(): Failed to associate library to PRX!"); - } - } - - return CELL_OK; -} - -error_code _sys_prx_unregister_library(ppu_thread& ppu, vm::ptr library) -{ - ppu.state += cpu_flag::wait; - - sys_prx.todo("_sys_prx_unregister_library(library=*0x%x)", library); - return CELL_OK; -} - -error_code _sys_prx_link_library(ppu_thread& ppu) -{ - ppu.state += cpu_flag::wait; - - sys_prx.todo("_sys_prx_link_library()"); - return CELL_OK; -} - -error_code _sys_prx_unlink_library(ppu_thread& ppu) -{ - ppu.state += cpu_flag::wait; - - sys_prx.todo("_sys_prx_unlink_library()"); - return CELL_OK; -} - -error_code _sys_prx_query_library(ppu_thread& ppu) -{ - ppu.state += cpu_flag::wait; - - sys_prx.todo("_sys_prx_query_library()"); - return CELL_OK; -} - -error_code _sys_prx_get_module_list(ppu_thread& ppu, u64 flags, vm::ptr pInfo) -{ - ppu.state += cpu_flag::wait; - - if (flags & 0x1) - { - sys_prx.todo("_sys_prx_get_module_list(flags=%d, pInfo=*0x%x)", flags, pInfo); - } - else - { - sys_prx.warning("_sys_prx_get_module_list(flags=%d, pInfo=*0x%x)", flags, pInfo); - } - - // TODO: Some action occurs if LSB of flags is set here - - if (!(flags & 0x2)) - { - // Do nothing - return CELL_OK; - } - - if (pInfo->size == pInfo.size()) - { - const u32 max_count = pInfo->max; - const auto idlist = +pInfo->idlist; - u32 count = 0; - - if (max_count) - { - const std::string liblv2_path = vfs::get("/dev_flash/sys/external/liblv2.sprx"); - - idm::select([&](u32 id, lv2_prx& prx) - { - if (count >= max_count) - { - return true; - } - - if (prx.path == liblv2_path) - { - // Hide liblv2.sprx for now - return false; - } - - idlist[count++] = id; - return false; - }); - } - - pInfo->count = count; - } - else - { - // TODO: A different structure should be served here with sizeof == 0x18 - sys_prx.todo("_sys_prx_get_module_list(): Unknown structure specified (size=0x%llx)", pInfo->size); - } - - return CELL_OK; -} - -error_code _sys_prx_get_module_info(ppu_thread& ppu, u32 id, u64 flags, vm::ptr pOpt) -{ - ppu.state += cpu_flag::wait; - - sys_prx.warning("_sys_prx_get_module_info(id=0x%x, flags=%d, pOpt=*0x%x)", id, flags, pOpt); - - const auto prx = idm::get_unlocked(id); - - if (!pOpt) - { - return CELL_EFAULT; - } - - if (pOpt->size != pOpt.size()) - { - return CELL_EINVAL; - } - - if (!pOpt->info) - { - return CELL_EFAULT; - } - - if (pOpt->info->size != pOpt->info.size() && pOpt->info_v2->size != pOpt->info_v2.size()) - { - return CELL_EINVAL; - } - - if (!prx) - { - return CELL_PRX_ERROR_UNKNOWN_MODULE; - } - - strcpy_trunc(pOpt->info->name, prx->module_info_name); - pOpt->info->version[0] = prx->module_info_version[0]; - pOpt->info->version[1] = prx->module_info_version[1]; - pOpt->info->modattribute = prx->module_info_attributes; - pOpt->info->start_entry = prx->start.addr(); - pOpt->info->stop_entry = prx->stop.addr(); - pOpt->info->all_segments_num = ::size32(prx->segs); - if (pOpt->info->filename) - { - std::span dst(pOpt->info->filename.get_ptr(), pOpt->info->filename_size); - strcpy_trunc(dst, vfs::retrieve(prx->path)); - } - - if (pOpt->info->segments) - { - u32 i = 0; - for (; i < prx->segs.size() && i < pOpt->info->segments_num; i++) - { - if (!prx->segs[i].addr) - continue; // TODO: Check this - pOpt->info->segments[i].index = i; - pOpt->info->segments[i].base = prx->segs[i].addr; - pOpt->info->segments[i].filesz = prx->segs[i].filesz; - pOpt->info->segments[i].memsz = prx->segs[i].size; - pOpt->info->segments[i].type = prx->segs[i].type; - } - pOpt->info->segments_num = i; - } - - if (pOpt->info_v2->size == pOpt->info_v2.size()) - { - pOpt->info_v2->exports_addr = prx->exports_start; - pOpt->info_v2->exports_size = prx->exports_end - prx->exports_start; - pOpt->info_v2->imports_addr = prx->imports_start; - pOpt->info_v2->imports_size = prx->imports_end - prx->imports_start; - } - - return CELL_OK; -} - -error_code _sys_prx_get_module_id_by_name(ppu_thread& ppu, vm::cptr name, u64 flags, vm::ptr pOpt) -{ - ppu.state += cpu_flag::wait; - - sys_prx.warning("_sys_prx_get_module_id_by_name(name=%s, flags=%d, pOpt=*0x%x)", name, flags, pOpt); - - std::string module_name; - if (!vm::read_string(name.addr(), 28, module_name)) - { - return CELL_EINVAL; - } - - const auto [prx, id] = idm::select([&](u32 id, lv2_prx& prx) -> u32 - { - if (strncmp(module_name.c_str(), prx.module_info_name, sizeof(prx.module_info_name)) == 0) - { - return id; - } - - return 0; - }); - - if (!id) - { - return CELL_PRX_ERROR_UNKNOWN_MODULE; - } - - return not_an_error(id); -} - -error_code _sys_prx_get_module_id_by_address(ppu_thread& ppu, u32 addr) -{ - ppu.state += cpu_flag::wait; - - sys_prx.warning("_sys_prx_get_module_id_by_address(addr=0x%x)", addr); - - if (!vm::check_addr(addr)) - { - // Fast check for an invalid argument - return {CELL_PRX_ERROR_UNKNOWN_MODULE, addr}; - } - - const auto [prx, id] = idm::select([&](u32 id, lv2_prx& prx) -> u32 - { - for (const ppu_segment& seg : prx.segs) - { - if (seg.size && addr >= seg.addr && addr < seg.addr + seg.size) - { - return id; - } - } - - return 0; - }); - - if (!id) - { - return {CELL_PRX_ERROR_UNKNOWN_MODULE, addr}; - } - - return not_an_error(id); -} - -error_code _sys_prx_start(ppu_thread& ppu) -{ - ppu.state += cpu_flag::wait; - - sys_prx.todo("sys_prx_start()"); - return CELL_OK; -} - -error_code _sys_prx_stop(ppu_thread& ppu) -{ - ppu.state += cpu_flag::wait; - - sys_prx.todo("sys_prx_stop()"); - return CELL_OK; -} - -template <> -void fmt_class_string::format(std::string& out, u64 arg) -{ - format_enum(out, arg, [](CellPrxError value) - { - switch (value) - { - STR_CASE(CELL_PRX_ERROR_ERROR); - STR_CASE(CELL_PRX_ERROR_ILLEGAL_PERM); - STR_CASE(CELL_PRX_ERROR_UNKNOWN_MODULE); - STR_CASE(CELL_PRX_ERROR_ALREADY_STARTED); - STR_CASE(CELL_PRX_ERROR_NOT_STARTED); - STR_CASE(CELL_PRX_ERROR_ALREADY_STOPPED); - STR_CASE(CELL_PRX_ERROR_CAN_NOT_STOP); - STR_CASE(CELL_PRX_ERROR_NOT_REMOVABLE); - STR_CASE(CELL_PRX_ERROR_LIBRARY_NOT_YET_LINKED); - STR_CASE(CELL_PRX_ERROR_LIBRARY_FOUND); - STR_CASE(CELL_PRX_ERROR_LIBRARY_NOTFOUND); - STR_CASE(CELL_PRX_ERROR_ILLEGAL_LIBRARY); - STR_CASE(CELL_PRX_ERROR_LIBRARY_INUSE); - STR_CASE(CELL_PRX_ERROR_ALREADY_STOPPING); - STR_CASE(CELL_PRX_ERROR_UNSUPPORTED_PRX_TYPE); - STR_CASE(CELL_PRX_ERROR_INVAL); - STR_CASE(CELL_PRX_ERROR_ILLEGAL_PROCESS); - STR_CASE(CELL_PRX_ERROR_NO_LIBLV2); - STR_CASE(CELL_PRX_ERROR_UNSUPPORTED_ELF_TYPE); - STR_CASE(CELL_PRX_ERROR_UNSUPPORTED_ELF_CLASS); - STR_CASE(CELL_PRX_ERROR_UNDEFINED_SYMBOL); - STR_CASE(CELL_PRX_ERROR_UNSUPPORTED_RELOCATION_TYPE); - STR_CASE(CELL_PRX_ERROR_ELF_IS_REGISTERED); - STR_CASE(CELL_PRX_ERROR_NO_EXIT_ENTRY); - } - - return unknown; - }); -} diff --git a/rpcs3/Emu/Cell/lv2/sys_prx.h b/rpcs3/Emu/Cell/lv2/sys_prx.h deleted file mode 100644 index 5481e76a4..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_prx.h +++ /dev/null @@ -1,271 +0,0 @@ -#pragma once - -#include "sys_sync.h" - -#include "Emu/Cell/PPUAnalyser.h" -#include "Emu/Cell/ErrorCodes.h" -#include "Emu/Memory/vm_ptr.h" - -// Return codes -enum CellPrxError : u32 -{ - CELL_PRX_ERROR_ERROR = 0x80011001, // Error state - CELL_PRX_ERROR_ILLEGAL_PERM = 0x800110d1, // No permission to execute API - CELL_PRX_ERROR_UNKNOWN_MODULE = 0x8001112e, // Specified PRX could not be found - CELL_PRX_ERROR_ALREADY_STARTED = 0x80011133, // Specified PRX is already started - CELL_PRX_ERROR_NOT_STARTED = 0x80011134, // Specified PRX is not started - CELL_PRX_ERROR_ALREADY_STOPPED = 0x80011135, // Specified PRX is already stopped - CELL_PRX_ERROR_CAN_NOT_STOP = 0x80011136, // Specified PRX must not be stopped - CELL_PRX_ERROR_NOT_REMOVABLE = 0x80011138, // Specified PRX must not be deleted - CELL_PRX_ERROR_LIBRARY_NOT_YET_LINKED = 0x8001113a, // Called unlinked function - CELL_PRX_ERROR_LIBRARY_FOUND = 0x8001113b, // Specified library is already registered - CELL_PRX_ERROR_LIBRARY_NOTFOUND = 0x8001113c, // Specified library is not registered - CELL_PRX_ERROR_ILLEGAL_LIBRARY = 0x8001113d, // Library structure is invalid - CELL_PRX_ERROR_LIBRARY_INUSE = 0x8001113e, // Library cannot be deleted because it is linked - CELL_PRX_ERROR_ALREADY_STOPPING = 0x8001113f, // Specified PRX is in the process of stopping - CELL_PRX_ERROR_UNSUPPORTED_PRX_TYPE = 0x80011148, // Specified PRX format is invalid and cannot be loaded - CELL_PRX_ERROR_INVAL = 0x80011324, // Argument value is invalid - CELL_PRX_ERROR_ILLEGAL_PROCESS = 0x80011801, // Specified process does not exist - CELL_PRX_ERROR_NO_LIBLV2 = 0x80011881, // liblv2.sprx does not exist - CELL_PRX_ERROR_UNSUPPORTED_ELF_TYPE = 0x80011901, // ELF type of specified file is not supported - CELL_PRX_ERROR_UNSUPPORTED_ELF_CLASS = 0x80011902, // ELF class of specified file is not supported - CELL_PRX_ERROR_UNDEFINED_SYMBOL = 0x80011904, // References undefined symbols - CELL_PRX_ERROR_UNSUPPORTED_RELOCATION_TYPE = 0x80011905, // Uses unsupported relocation type - CELL_PRX_ERROR_ELF_IS_REGISTERED = 0x80011910, // Fixed ELF is already registered - CELL_PRX_ERROR_NO_EXIT_ENTRY = 0x80011911, -}; - -enum -{ - SYS_PRX_MODULE_FILENAME_SIZE = 512 -}; - -struct sys_prx_get_module_id_by_name_option_t -{ - be_t size; - vm::ptr base; -}; - -struct sys_prx_load_module_option_t -{ - be_t size; - vm::bptr base_addr; -}; - -struct sys_prx_segment_info_t -{ - be_t base; - be_t filesz; - be_t memsz; - be_t index; - be_t type; -}; - -struct sys_prx_module_info_t -{ - be_t size; // 0 - char name[30]; // 8 - char version[2]; // 0x26 - be_t modattribute; // 0x28 - be_t start_entry; // 0x2c - be_t stop_entry; // 0x30 - be_t all_segments_num; // 0x34 - vm::bptr filename; // 0x38 - be_t filename_size; // 0x3c - vm::bptr segments; // 0x40 - be_t segments_num; // 0x44 -}; - -struct sys_prx_module_info_v2_t : sys_prx_module_info_t -{ - be_t exports_addr; // 0x48 - be_t exports_size; // 0x4C - be_t imports_addr; // 0x50 - be_t imports_size; // 0x54 -}; - -struct sys_prx_module_info_option_t -{ - be_t size; // 0x10 - union - { - vm::bptr info; - vm::bptr info_v2; - }; -}; - -struct sys_prx_start_module_option_t -{ - be_t size; -}; - -struct sys_prx_stop_module_option_t -{ - be_t size; -}; - -struct sys_prx_start_stop_module_option_t -{ - be_t size; - be_t cmd; - vm::bptr argv), u64> entry; - be_t res; - vm::bptr), u64>, u32 argc, vm::ptr argv), u64> entry2; -}; - -struct sys_prx_unload_module_option_t -{ - be_t size; -}; - -struct sys_prx_get_module_list_t -{ - be_t size; - be_t max; - be_t count; - vm::bptr idlist; -}; - -struct sys_prx_get_module_list_option_t -{ - be_t size; // 0x20 - be_t pad; - be_t max; - be_t count; - vm::bptr idlist; - be_t unk; // 0 -}; - -struct sys_prx_register_module_0x20_t -{ - be_t size; // 0x0 - be_t toc; // 0x8 - be_t toc_size; // 0xC - vm::bptr stubs_ea; // 0x10 - be_t stubs_size; // 0x14 - vm::bptr error_handler; // 0x18 - char pad[4]; // 0x1C -}; - -struct sys_prx_register_module_0x30_type_1_t -{ - be_t size; // 0x0 - be_t type; // 0x8 - be_t unk3; // 0x10 - be_t unk4; // 0x14 - vm::bptr lib_entries_ea; // 0x18 - be_t lib_entries_size; // 0x1C - vm::bptr lib_stub_ea; // 0x20 - be_t lib_stub_size; // 0x24 - vm::bptr error_handler; // 0x28 - char pad[4]; // 0x2C -}; - -enum : u32 -{ - SYS_PRX_RESIDENT = 0, - SYS_PRX_NO_RESIDENT = 1, - - SYS_PRX_START_OK = 0, - - SYS_PRX_STOP_SUCCESS = 0, - SYS_PRX_STOP_OK = 0, - SYS_PRX_STOP_FAILED = 1 -}; - -// Unofficial names for PRX state -enum : u32 -{ - PRX_STATE_INITIALIZED, - PRX_STATE_STARTING, // In-between state between initialized and started (internal) - PRX_STATE_STARTED, - PRX_STATE_STOPPING, // In-between state between started and stopped (internal) - PRX_STATE_STOPPED, // Last state, the module cannot be restarted - PRX_STATE_DESTROYED, // Last state, the module cannot be restarted -}; - -struct lv2_prx final : ppu_module -{ - static const u32 id_base = 0x23000000; - - atomic_t state = PRX_STATE_INITIALIZED; - shared_mutex mutex; - - std::unordered_map specials; - - vm::ptr argv)> start = vm::null; - vm::ptr argv)> stop = vm::null; - vm::ptr argv)> prologue = vm::null; - vm::ptr argv)> epilogue = vm::null; - vm::ptr exit = vm::null; - - char module_info_name[28]{}; - u8 module_info_version[2]{}; - be_t module_info_attributes{}; - - u32 imports_start = umax; - u32 imports_end = 0; - - u32 exports_start = umax; - u32 exports_end = 0; - - std::basic_string m_loaded_flags; - std::basic_string m_external_loaded_flags; - - void load_exports(); // (Re)load exports - void restore_exports(); // For savestates - void unload_exports(); - - lv2_prx() noexcept = default; - lv2_prx(utils::serial&) {} - static std::function load(utils::serial&); - void save(utils::serial& ar); -}; - -enum : u64 -{ - SYS_PRX_LOAD_MODULE_FLAGS_FIXEDADDR = 0x1ull, - SYS_PRX_LOAD_MODULE_FLAGS_INVALIDMASK = ~SYS_PRX_LOAD_MODULE_FLAGS_FIXEDADDR, -}; - -// PPC -enum -{ - SYS_PRX_R_PPC_ADDR32 = 1, - SYS_PRX_R_PPC_ADDR16_LO = 4, - SYS_PRX_R_PPC_ADDR16_HI = 5, - SYS_PRX_R_PPC_ADDR16_HA = 6, - - SYS_PRX_R_PPC64_ADDR32 = SYS_PRX_R_PPC_ADDR32, - SYS_PRX_R_PPC64_ADDR16_LO = SYS_PRX_R_PPC_ADDR16_LO, - SYS_PRX_R_PPC64_ADDR16_HI = SYS_PRX_R_PPC_ADDR16_HI, - SYS_PRX_R_PPC64_ADDR16_HA = SYS_PRX_R_PPC_ADDR16_HA, - SYS_PRX_R_PPC64_ADDR64 = 38, - SYS_PRX_VARLINK_TERMINATE32 = 0x00000000 -}; - -// SysCalls - -error_code sys_prx_get_ppu_guid(ppu_thread& ppu); -error_code _sys_prx_load_module_by_fd(ppu_thread& ppu, s32 fd, u64 offset, u64 flags, vm::ptr pOpt); -error_code _sys_prx_load_module_on_memcontainer_by_fd(ppu_thread& ppu, s32 fd, u64 offset, u32 mem_ct, u64 flags, vm::ptr pOpt); -error_code _sys_prx_load_module_list(ppu_thread& ppu, s32 count, vm::cpptr path_list, u64 flags, vm::ptr pOpt, vm::ptr id_list); -error_code _sys_prx_load_module_list_on_memcontainer(ppu_thread& ppu, s32 count, vm::cpptr path_list, u32 mem_ct, u64 flags, vm::ptr pOpt, vm::ptr id_list); -error_code _sys_prx_load_module_on_memcontainer(ppu_thread& ppu, vm::cptr path, u32 mem_ct, u64 flags, vm::ptr pOpt); -error_code _sys_prx_load_module(ppu_thread& ppu, vm::cptr path, u64 flags, vm::ptr pOpt); -error_code _sys_prx_start_module(ppu_thread& ppu, u32 id, u64 flags, vm::ptr pOpt); -error_code _sys_prx_stop_module(ppu_thread& ppu, u32 id, u64 flags, vm::ptr pOpt); -error_code _sys_prx_unload_module(ppu_thread& ppu, u32 id, u64 flags, vm::ptr pOpt); -error_code _sys_prx_register_module(ppu_thread& ppu, vm::cptr name, vm::ptr opt); -error_code _sys_prx_query_module(ppu_thread& ppu); -error_code _sys_prx_register_library(ppu_thread& ppu, vm::ptr library); -error_code _sys_prx_unregister_library(ppu_thread& ppu, vm::ptr library); -error_code _sys_prx_link_library(ppu_thread& ppu); -error_code _sys_prx_unlink_library(ppu_thread& ppu); -error_code _sys_prx_query_library(ppu_thread& ppu); -error_code _sys_prx_get_module_list(ppu_thread& ppu, u64 flags, vm::ptr pInfo); -error_code _sys_prx_get_module_info(ppu_thread& ppu, u32 id, u64 flags, vm::ptr pOpt); -error_code _sys_prx_get_module_id_by_name(ppu_thread& ppu, vm::cptr name, u64 flags, vm::ptr pOpt); -error_code _sys_prx_get_module_id_by_address(ppu_thread& ppu, u32 addr); -error_code _sys_prx_start(ppu_thread& ppu); -error_code _sys_prx_stop(ppu_thread& ppu); diff --git a/rpcs3/Emu/Cell/lv2/sys_rsx.cpp b/rpcs3/Emu/Cell/lv2/sys_rsx.cpp deleted file mode 100644 index e219feef5..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_rsx.cpp +++ /dev/null @@ -1,990 +0,0 @@ -#include "stdafx.h" -#include "sys_rsx.h" - -#include "Emu/System.h" -#include "Emu/Cell/PPUModule.h" -#include "Emu/Cell/ErrorCodes.h" -#include "Emu/Cell/timers.hpp" -#include "Emu/Memory/vm_locking.h" -#include "Emu/RSX/Core/RSXEngLock.hpp" -#include "Emu/RSX/Core/RSXReservationLock.hpp" -#include "Emu/RSX/RSXThread.h" -#include "util/asm.hpp" -#include "sys_event.h" -#include "sys_vm.h" - -LOG_CHANNEL(sys_rsx); - -// Unknown error code returned by sys_rsx_context_attribute -enum sys_rsx_error : s32 -{ - SYS_RSX_CONTEXT_ATTRIBUTE_ERROR = -17 -}; - -template <> -void fmt_class_string::format(std::string& out, u64 arg) -{ - format_enum(out, arg, [](auto error) - { - switch (error) - { - STR_CASE(SYS_RSX_CONTEXT_ATTRIBUTE_ERROR); - } - - return unknown; - }); -} - -static u64 rsx_timeStamp() -{ - return get_timebased_time(); -} - -static void set_rsx_dmactl(rsx::thread* render, u64 get_put) -{ - { - rsx::eng_lock rlock(render); - render->fifo_ctrl->abort(); - - // Unconditional set - while (!render->new_get_put.compare_and_swap_test(u64{umax}, get_put)) - { - // Wait for the first store to complete (or be aborted) - if (auto cpu = cpu_thread::get_current()) - { - if (cpu->state & cpu_flag::exit) - { - // Retry - cpu->state += cpu_flag::again; - return; - } - } - - utils::pause(); - } - - // Schedule FIFO interrupt to deal with this immediately - render->m_eng_interrupt_mask |= rsx::dma_control_interrupt; - } - - if (auto cpu = cpu_thread::get_current()) - { - // Wait for the first store to complete (or be aborted) - while (render->new_get_put != usz{umax}) - { - if (cpu->state & cpu_flag::exit) - { - if (render->new_get_put.compare_and_swap_test(get_put, umax)) - { - // Retry - cpu->state += cpu_flag::again; - return; - } - } - - thread_ctrl::wait_for(1000); - } - } -} - -bool rsx::thread::send_event(u64 data1, u64 event_flags, u64 data3) -{ - // Filter event bits, send them only if they are masked by gcm - // Except the upper 32-bits, they are reserved for unmapped io events and execute unconditionally - event_flags &= vm::_ref(driver_info).handlers | 0xffff'ffffull << 32; - - if (!event_flags) - { - // Nothing to do - return true; - } - - auto error = sys_event_port_send(rsx_event_port, data1, event_flags, data3); - - while (error + 0u == CELL_EBUSY) - { - auto cpu = get_current_cpu_thread(); - - if (cpu && cpu->get_class() == thread_class::ppu) - { - // Deschedule - lv2_obj::sleep(*cpu, 100); - } - - // Wait a bit before resending event - thread_ctrl::wait_for(100); - - if (cpu && cpu->get_class() == thread_class::rsx) - cpu->cpu_wait({}); - - if (Emu.IsStopped() || (cpu && cpu->check_state())) - { - error = 0; - break; - } - - error = sys_event_port_send(rsx_event_port, data1, event_flags, data3); - } - - if (error + 0u == CELL_EAGAIN) - { - // Thread has aborted when sending event (VBLANK duplicates are allowed) - ensure((unsent_gcm_events.fetch_or(event_flags) & event_flags & ~(SYS_RSX_EVENT_VBLANK | SYS_RSX_EVENT_SECOND_VBLANK_BASE | SYS_RSX_EVENT_SECOND_VBLANK_BASE * 2)) == 0); - return false; - } - - if (error && error + 0u != CELL_ENOTCONN) - { - fmt::throw_exception("rsx::thread::send_event() Failed to send event! (error=%x)", +error); - } - - return true; -} - -error_code sys_rsx_device_open(cpu_thread& cpu) -{ - cpu.state += cpu_flag::wait; - - sys_rsx.todo("sys_rsx_device_open()"); - - return CELL_OK; -} - -error_code sys_rsx_device_close(cpu_thread& cpu) -{ - cpu.state += cpu_flag::wait; - - sys_rsx.todo("sys_rsx_device_close()"); - - return CELL_OK; -} - -/* - * lv2 SysCall 668 (0x29C): sys_rsx_memory_allocate - * @param mem_handle (OUT): Context / ID, which is used by sys_rsx_memory_free to free allocated memory. - * @param mem_addr (OUT): Returns the local memory base address, usually 0xC0000000. - * @param size (IN): Local memory size. E.g. 0x0F900000 (249 MB). (changes with sdk version) - * @param flags (IN): E.g. Immediate value passed in cellGcmSys is 8. - * @param a5 (IN): E.g. Immediate value passed in cellGcmSys is 0x00300000 (3 MB?). - * @param a6 (IN): E.g. Immediate value passed in cellGcmSys is 16. - * @param a7 (IN): E.g. Immediate value passed in cellGcmSys is 8. - */ -error_code sys_rsx_memory_allocate(cpu_thread& cpu, vm::ptr mem_handle, vm::ptr mem_addr, u32 size, u64 flags, u64 a5, u64 a6, u64 a7) -{ - cpu.state += cpu_flag::wait; - - sys_rsx.warning("sys_rsx_memory_allocate(mem_handle=*0x%x, mem_addr=*0x%x, size=0x%x, flags=0x%llx, a5=0x%llx, a6=0x%llx, a7=0x%llx)", mem_handle, mem_addr, size, flags, a5, a6, a7); - - if (vm::falloc(rsx::constants::local_mem_base, size, vm::video)) - { - rsx::get_current_renderer()->local_mem_size = size; - - if (u32 addr = rsx::get_current_renderer()->driver_info) - { - vm::_ref(addr).memory_size = size; - } - - *mem_addr = rsx::constants::local_mem_base; - *mem_handle = 0x5a5a5a5b; - return CELL_OK; - } - - return CELL_ENOMEM; -} - -/* - * lv2 SysCall 669 (0x29D): sys_rsx_memory_free - * @param mem_handle (OUT): Context / ID, for allocated local memory generated by sys_rsx_memory_allocate - */ -error_code sys_rsx_memory_free(cpu_thread& cpu, u32 mem_handle) -{ - cpu.state += cpu_flag::wait; - - sys_rsx.warning("sys_rsx_memory_free(mem_handle=0x%x)", mem_handle); - - if (!vm::check_addr(rsx::constants::local_mem_base)) - { - return CELL_ENOMEM; - } - - if (rsx::get_current_renderer()->dma_address) - { - fmt::throw_exception("Attempting to dealloc rsx memory when the context is still being used"); - } - - if (!vm::dealloc(rsx::constants::local_mem_base)) - { - return CELL_ENOMEM; - } - - return CELL_OK; -} - -/* - * lv2 SysCall 670 (0x29E): sys_rsx_context_allocate - * @param context_id (OUT): RSX context, E.g. 0x55555555 (in vsh.self) - * @param lpar_dma_control (OUT): Control register area. E.g. 0x60100000 (in vsh.self) - * @param lpar_driver_info (OUT): RSX data like frequencies, sizes, version... E.g. 0x60200000 (in vsh.self) - * @param lpar_reports (OUT): Report data area. E.g. 0x60300000 (in vsh.self) - * @param mem_ctx (IN): mem_ctx given by sys_rsx_memory_allocate - * @param system_mode (IN): - */ -error_code sys_rsx_context_allocate(cpu_thread& cpu, vm::ptr context_id, vm::ptr lpar_dma_control, vm::ptr lpar_driver_info, vm::ptr lpar_reports, u64 mem_ctx, u64 system_mode) -{ - cpu.state += cpu_flag::wait; - - sys_rsx.warning("sys_rsx_context_allocate(context_id=*0x%x, lpar_dma_control=*0x%x, lpar_driver_info=*0x%x, lpar_reports=*0x%x, mem_ctx=0x%llx, system_mode=0x%llx)", - context_id, lpar_dma_control, lpar_driver_info, lpar_reports, mem_ctx, system_mode); - - if (!vm::check_addr(rsx::constants::local_mem_base)) - { - return CELL_EINVAL; - } - - const auto render = rsx::get_current_renderer(); - - std::lock_guard lock(render->sys_rsx_mtx); - - if (render->dma_address) - { - // We currently do not support multiple contexts - fmt::throw_exception("sys_rsx_context_allocate was called twice"); - } - - const auto area = vm::reserve_map(vm::rsx_context, 0, 0x10000000, 0x403); - const u32 dma_address = area ? area->alloc(0x300000) : 0; - - if (!dma_address) - { - return CELL_ENOMEM; - } - - sys_rsx.warning("sys_rsx_context_allocate(): Mapped address 0x%x", dma_address); - - *lpar_dma_control = dma_address; - *lpar_driver_info = dma_address + 0x100000; - *lpar_reports = dma_address + 0x200000; - - auto& reports = vm::_ref(vm::cast(*lpar_reports)); - std::memset(&reports, 0, sizeof(RsxReports)); - - for (usz i = 0; i < std::size(reports.notify); ++i) - reports.notify[i].timestamp = -1; - - for (usz i = 0; i < std::size(reports.semaphore); i += 4) - { - reports.semaphore[i + 0].val.raw() = 0x1337C0D3; - reports.semaphore[i + 1].val.raw() = 0x1337BABE; - reports.semaphore[i + 2].val.raw() = 0x1337BEEF; - reports.semaphore[i + 3].val.raw() = 0x1337F001; - } - - for (usz i = 0; i < std::size(reports.report); ++i) - { - reports.report[i].val = 0; - reports.report[i].timestamp = -1; - reports.report[i].pad = -1; - } - - auto& driverInfo = vm::_ref(vm::cast(*lpar_driver_info)); - - std::memset(&driverInfo, 0, sizeof(RsxDriverInfo)); - - driverInfo.version_driver = 0x211; - driverInfo.version_gpu = 0x5c; - driverInfo.memory_size = render->local_mem_size; - driverInfo.nvcore_frequency = 500000000; // 0x1DCD6500 - driverInfo.memory_frequency = 650000000; // 0x26BE3680 - driverInfo.reportsNotifyOffset = 0x1000; - driverInfo.reportsOffset = 0; - driverInfo.reportsReportOffset = 0x1400; - driverInfo.systemModeFlags = static_cast(system_mode); - driverInfo.hardware_channel = 1; // * i think* this 1 for games, 0 for vsh - - render->driver_info = vm::cast(*lpar_driver_info); - - auto& dmaControl = vm::_ref(vm::cast(*lpar_dma_control)); - dmaControl.get = 0; - dmaControl.put = 0; - dmaControl.ref = 0; // Set later to -1 by cellGcmSys - - if ((false /*system_mode & something*/ || g_cfg.video.decr_memory_layout) && g_cfg.core.debug_console_mode) - rsx::get_current_renderer()->main_mem_size = 0x20000000; // 512MB - else - rsx::get_current_renderer()->main_mem_size = 0x10000000; // 256MB - - vm::var> attr; - attr->protocol = SYS_SYNC_PRIORITY; - attr->type = SYS_PPU_QUEUE; - attr->name_u64 = 0; - - sys_event_port_create(cpu, vm::get_addr(&driverInfo.handler_queue), SYS_EVENT_PORT_LOCAL, 0); - render->rsx_event_port = driverInfo.handler_queue; - sys_event_queue_create(cpu, vm::get_addr(&driverInfo.handler_queue), attr, 0, 0x20); - sys_event_port_connect_local(cpu, render->rsx_event_port, driverInfo.handler_queue); - - render->display_buffers_count = 0; - render->current_display_buffer = 0; - render->label_addr = vm::cast(*lpar_reports); - render->init(dma_address); - - *context_id = 0x55555555; - - return CELL_OK; -} - -/* - * lv2 SysCall 671 (0x29F): sys_rsx_context_free - * @param context_id (IN): RSX context generated by sys_rsx_context_allocate to free the context. - */ -error_code sys_rsx_context_free(ppu_thread& ppu, u32 context_id) -{ - ppu.state += cpu_flag::wait; - - sys_rsx.todo("sys_rsx_context_free(context_id=0x%x)", context_id); - - const auto render = rsx::get_current_renderer(); - - rsx::eng_lock fifo_lock(render); - std::scoped_lock lock(render->sys_rsx_mtx); - - const u32 dma_address = render->dma_address; - render->dma_address = 0; - - if (context_id != 0x55555555 || !dma_address || render->state & cpu_flag::ret) - { - return CELL_EINVAL; - } - - g_fxo->get() = thread_state::finished; - - const u32 queue_id = vm::_ptr(render->driver_info)->handler_queue; - - render->state += cpu_flag::ret; - - while (render->state & cpu_flag::ret) - { - thread_ctrl::wait_for(1000); - } - - sys_event_port_disconnect(ppu, render->rsx_event_port); - sys_event_port_destroy(ppu, render->rsx_event_port); - sys_event_queue_destroy(ppu, queue_id, SYS_EVENT_QUEUE_DESTROY_FORCE); - - render->label_addr = 0; - render->driver_info = 0; - render->main_mem_size = 0; - render->rsx_event_port = 0; - render->display_buffers_count = 0; - render->current_display_buffer = 0; - render->ctrl = nullptr; - render->rsx_thread_running = false; - render->serialized = false; - - ensure(vm::get(vm::rsx_context)->dealloc(dma_address)); - - return CELL_OK; -} - -/* - * lv2 SysCall 672 (0x2A0): sys_rsx_context_iomap - * @param context_id (IN): RSX context, E.g. 0x55555555 (in vsh.self) - * @param io (IN): IO offset mapping area. E.g. 0x00600000 - * @param ea (IN): Start address of mapping area. E.g. 0x20400000 - * @param size (IN): Size of mapping area in bytes. E.g. 0x00200000 - * @param flags (IN): - */ -error_code sys_rsx_context_iomap(cpu_thread& cpu, u32 context_id, u32 io, u32 ea, u32 size, u64 flags) -{ - cpu.state += cpu_flag::wait; - - sys_rsx.warning("sys_rsx_context_iomap(context_id=0x%x, io=0x%x, ea=0x%x, size=0x%x, flags=0x%llx)", context_id, io, ea, size, flags); - - const auto render = rsx::get_current_renderer(); - - if (!size || io & 0xFFFFF || ea + u64{size} > rsx::constants::local_mem_base || ea & 0xFFFFF || size & 0xFFFFF || - context_id != 0x55555555 || render->main_mem_size < io + u64{size}) - { - return CELL_EINVAL; - } - - if (!render->is_fifo_idle()) - { - sys_rsx.warning("sys_rsx_context_iomap(): RSX is not idle while mapping io"); - } - - // Wait until we have no active RSX locks and reserve iomap for use. Must do so before acquiring vm lock to avoid deadlocks - rsx::reservation_lock rsx_lock(ea, size); - - vm::writer_lock rlock; - - for (u32 addr = ea, end = ea + size; addr < end; addr += 0x100000) - { - if (!vm::check_addr(addr, vm::page_readable | (addr < 0x20000000 ? 0 : vm::page_1m_size))) - { - return CELL_EINVAL; - } - - if ((addr == ea || !(addr % 0x1000'0000)) && idm::check_unlocked(sys_vm_t::find_id(addr))) - { - // Virtual memory is disallowed - return CELL_EINVAL; - } - } - - io >>= 20, ea >>= 20, size >>= 20; - - rsx::eng_lock fifo_lock(render); - std::scoped_lock lock(render->sys_rsx_mtx); - - for (u32 i = 0; i < size; i++) - { - auto& table = render->iomap_table; - - // TODO: Investigate relaxed memory ordering - const u32 prev_ea = table.ea[io + i]; - table.ea[io + i].release((ea + i) << 20); - if (prev_ea + 1) - table.io[prev_ea >> 20].release(-1); // Clear previous mapping if exists - table.io[ea + i].release((io + i) << 20); - } - - return CELL_OK; -} - -/* - * lv2 SysCall 673 (0x2A1): sys_rsx_context_iounmap - * @param context_id (IN): RSX context, E.g. 0x55555555 (in vsh.self) - * @param io (IN): IO address. E.g. 0x00600000 (Start page 6) - * @param size (IN): Size to unmap in byte. E.g. 0x00200000 - */ -error_code sys_rsx_context_iounmap(cpu_thread& cpu, u32 context_id, u32 io, u32 size) -{ - cpu.state += cpu_flag::wait; - - sys_rsx.warning("sys_rsx_context_iounmap(context_id=0x%x, io=0x%x, size=0x%x)", context_id, io, size); - - const auto render = rsx::get_current_renderer(); - - if (!size || size & 0xFFFFF || io & 0xFFFFF || context_id != 0x55555555 || - render->main_mem_size < io + u64{size}) - { - return CELL_EINVAL; - } - - if (!render->is_fifo_idle()) - { - sys_rsx.warning("sys_rsx_context_iounmap(): RSX is not idle while unmapping io"); - } - - vm::writer_lock rlock; - - std::scoped_lock lock(render->sys_rsx_mtx); - - for (const u32 end = (io >>= 20) + (size >>= 20); io < end;) - { - auto& table = render->iomap_table; - - const u32 ea_entry = table.ea[io]; - table.ea[io++].release(-1); - if (ea_entry + 1) - table.io[ea_entry >> 20].release(-1); - } - - return CELL_OK; -} - -/* - * lv2 SysCall 674 (0x2A2): sys_rsx_context_attribute - * @param context_id (IN): RSX context, e.g. 0x55555555 - * @param package_id (IN): - * @param a3 (IN): - * @param a4 (IN): - * @param a5 (IN): - * @param a6 (IN): - */ -error_code sys_rsx_context_attribute(u32 context_id, u32 package_id, u64 a3, u64 a4, u64 a5, u64 a6) -{ - if (auto cpu = get_current_cpu_thread()) - { - cpu->state += cpu_flag::wait; - } - - // Flip/queue/reset flip/flip event/user command/vblank as trace to help with log spam - const bool trace_log = (package_id == 0x102 || package_id == 0x103 || package_id == 0x10a || package_id == 0xFEC || package_id == 0xFED || package_id == 0xFEF); - (trace_log ? sys_rsx.trace : sys_rsx.warning)("sys_rsx_context_attribute(context_id=0x%x, package_id=0x%x, a3=0x%llx, a4=0x%llx, a5=0x%llx, a6=0x%llx)", context_id, package_id, a3, a4, a5, a6); - - // todo: these event ports probly 'shouldnt' be here as i think its supposed to be interrupts that are sent from rsx somewhere in lv1 - - const auto render = rsx::get_current_renderer(); - - if (!render->dma_address) - { - return {CELL_EINVAL, "dma_address is 0"}; - } - - if (context_id != 0x55555555) - { - return {CELL_EINVAL, "context_id is 0x%x", context_id}; - } - - auto& driverInfo = vm::_ref(render->driver_info); - switch (package_id) - { - case 0x001: // FIFO - { - const u64 get = static_cast(a3); - const u64 put = static_cast(a4); - const u64 get_put = put << 32 | get; - - std::lock_guard lock(render->sys_rsx_mtx); - set_rsx_dmactl(render, get_put); - break; - } - case 0x100: // Display mode set - break; - case 0x101: // Display sync set, cellGcmSetFlipMode - // a4 == 2 is vsync, a4 == 1 is hsync - render->requested_vsync.store(a4 == 2); - break; - - case 0x102: // Display flip - { - u32 flip_idx = ~0u; - - // high bit signifys grabbing a queued buffer - // otherwise it contains a display buffer offset - if ((a4 & 0x80000000) != 0) - { - // NOTE: There currently seem to only be 2 active heads on PS3 - ensure(a3 < 2); - - // last half byte gives buffer, 0xf seems to trigger just last queued - u8 idx_check = a4 & 0xf; - if (idx_check > 7) - flip_idx = driverInfo.head[a3].lastQueuedBufferId; - else - flip_idx = idx_check; - - // fyi -- u32 hardware_channel = (a4 >> 8) & 0xFF; - - // sanity check, the head should have a 'queued' buffer on it, and it should have been previously 'queued' - const u32 sanity_check = 0x40000000 & (1 << flip_idx); - if ((driverInfo.head[a3].flipFlags & sanity_check) != sanity_check) - rsx_log.error("Display Flip Queued: Flipping non previously queued buffer 0x%llx", a4); - } - else - { - for (u32 i = 0; i < render->display_buffers_count; ++i) - { - if (render->display_buffers[i].offset == a4) - { - flip_idx = i; - break; - } - } - if (flip_idx == ~0u) - { - rsx_log.error("Display Flip: Couldn't find display buffer offset, flipping 0. Offset: 0x%x", a4); - flip_idx = 0; - } - } - - if (!render->request_emu_flip(flip_idx)) - { - if (auto cpu = get_current_cpu_thread()) - { - cpu->state += cpu_flag::exit; - cpu->state += cpu_flag::again; - } - - return {}; - } - break; - } - case 0x103: // Display Queue - { - // NOTE: There currently seem to only be 2 active heads on PS3 - ensure(a3 < 2); - - driverInfo.head[a3].lastQueuedBufferId = static_cast(a4); - driverInfo.head[a3].flipFlags |= 0x40000000 | (1 << a4); - - render->on_frame_end(static_cast(a4)); - if (!render->send_event(0, SYS_RSX_EVENT_QUEUE_BASE << a3, 0)) - { - break; - } - - if (g_cfg.video.frame_limit == frame_limit_type::infinite) - { - render->post_vblank_event(get_system_time()); - } - break; - } - case 0x104: // Display buffer - { - const u8 id = a3 & 0xFF; - if (id > 7) - { - return SYS_RSX_CONTEXT_ATTRIBUTE_ERROR; - } - - std::lock_guard lock(render->sys_rsx_mtx); - - // Note: no error checking is being done - - const u32 width = (a4 >> 32) & 0xFFFFFFFF; - const u32 height = a4 & 0xFFFFFFFF; - const u32 pitch = (a5 >> 32) & 0xFFFFFFFF; - const u32 offset = a5 & 0xFFFFFFFF; - - render->display_buffers[id].width = width; - render->display_buffers[id].height = height; - render->display_buffers[id].pitch = pitch; - render->display_buffers[id].offset = offset; - - render->display_buffers_count = std::max(id + 1, render->display_buffers_count); - break; - } - case 0x105: // destroy buffer? - break; - - case 0x106: // ? (Used by cellGcmInitPerfMon) - break; - - case 0x108: // cellGcmSetVBlankFrequency, cellGcmSetSecondVFrequency - // a4 == 3, CELL_GCM_DISPLAY_FREQUENCY_59_94HZ - // a4 == 2, CELL_GCM_DISPLAY_FREQUENCY_SCANOUT - // a4 == 4, CELL_GCM_DISPLAY_FREQUENCY_DISABLE - - if (a5 == 1u) - { - // This function resets vsync state to enabled - render->requested_vsync = true; - - // TODO: Set vblank frequency - } - else if (ensure(a5 == 2u)) - { - // TODO: Implement its frequency as well - render->enable_second_vhandler.store(a4 != 4); - } - - break; - - case 0x10a: // ? Involved in managing flip status through cellGcmResetFlipStatus - { - if (a3 > 7) - { - return SYS_RSX_CONTEXT_ATTRIBUTE_ERROR; - } - - // NOTE: There currently seem to only be 2 active heads on PS3 - ensure(a3 < 2); - - driverInfo.head[a3].flipFlags.atomic_op([&](be_t& flipStatus) - { - flipStatus = (flipStatus & static_cast(a4)) | static_cast(a5); - }); - break; - } - case 0x10D: // Called by cellGcmInitCursor - break; - - case 0x300: // Tiles - { - // a4 high bits = ret.tile = (location + 1) | (bank << 4) | ((offset / 0x10000) << 16) | (location << 31); - // a4 low bits = ret.limit = ((offset + size - 1) / 0x10000) << 16 | (location << 31); - // a5 high bits = ret.pitch = (pitch / 0x100) << 8; - // a5 low bits = ret.format = base | ((base + ((size - 1) / 0x10000)) << 13) | (comp << 26) | (1 << 30); - - ensure(a3 < std::size(render->tiles)); - - if (!render->is_fifo_idle()) - { - sys_rsx.warning("sys_rsx_context_attribute(): RSX is not idle while setting tile"); - } - - auto& tile = render->tiles[a3]; - - const u32 location = ((a4 >> 32) & 0x3) - 1; - const u32 offset = ((((a4 >> 32) & 0x7FFFFFFF) >> 16) * 0x10000); - const u32 size = ((((a4 & 0x7FFFFFFF) >> 16) + 1) * 0x10000) - offset; - const u32 pitch = (((a5 >> 32) & 0xFFFFFFFF) >> 8) * 0x100; - const u32 comp = ((a5 & 0xFFFFFFFF) >> 26) & 0xF; - const u32 base = (a5 & 0xFFFFFFFF) & 0x7FF; - // const u32 bank = (((a4 >> 32) & 0xFFFFFFFF) >> 4) & 0xF; - const bool bound = ((a4 >> 32) & 0x3) != 0; - - const auto range = utils::address_range::start_length(offset, size); - - if (bound) - { - if (!size || !pitch) - { - return {CELL_EINVAL, "size or pitch are 0 (size=%d, pitch=%d)", size, pitch}; - } - - u32 limit = -1; - - switch (location) - { - case CELL_GCM_LOCATION_MAIN: limit = render->main_mem_size; break; - case CELL_GCM_LOCATION_LOCAL: limit = render->local_mem_size; break; - default: fmt::throw_exception("sys_rsx_context_attribute(): Unexpected location value (location=0x%x)", location); - } - - if (!range.valid() || range.end >= limit) - { - return {CELL_EINVAL, "range invalid (valid=%d, end=%d, limit=%d)", range.valid(), range.end, limit}; - } - - // Hardcoded value in gcm - ensure(a5 & (1 << 30)); - } - - std::lock_guard lock(render->sys_rsx_mtx); - - // When tile is going to be unbound, we can use it as a hint that the address will no longer be used as a surface and can be removed/invalidated - // Todo: There may be more checks such as format/size/width can could be done - if (tile.bound && !bound) - render->notify_tile_unbound(static_cast(a3)); - - if (location == CELL_GCM_LOCATION_MAIN && bound) - { - vm::writer_lock rlock; - - for (u32 io = (offset >> 20), end = (range.end >> 20); io <= end; io++) - { - if (render->iomap_table.ea[io] == umax) - { - return {CELL_EINVAL, "iomap_table ea is umax"}; - } - } - } - - tile.location = location; - tile.offset = offset; - tile.size = size; - tile.pitch = pitch; - tile.comp = comp; - tile.base = base; - tile.bank = base; - tile.bound = bound; - break; - } - case 0x301: // Depth-buffer (Z-cull) - { - // a4 high = region = (1 << 0) | (zFormat << 4) | (aaFormat << 8); - // a4 low = size = ((width >> 6) << 22) | ((height >> 6) << 6); - // a5 high = start = cullStart&(~0xFFF); - // a5 low = offset = offset; - // a6 high = status0 = (zcullDir << 1) | (zcullFormat << 2) | ((sFunc & 0xF) << 12) | (sRef << 16) | (sMask << 24); - // a6 low = status1 = (0x2000 << 0) | (0x20 << 16); - - if (a3 >= std::size(render->zculls)) - { - return SYS_RSX_CONTEXT_ATTRIBUTE_ERROR; - } - - if (!render->is_fifo_idle()) - { - sys_rsx.warning("sys_rsx_context_attribute(): RSX is not idle while setting zcull"); - } - - const u32 width = ((a4 & 0xFFFFFFFF) >> 22) << 6; - const u32 height = ((a4 & 0x0000FFFF) >> 6) << 6; - const u32 cullStart = (a5 >> 32) & ~0xFFF; - const u32 offset = (a5 & 0x0FFFFFFF); - const bool bound = (a6 & 0xFFFFFFFF) != 0; - - if (bound) - { - const auto cull_range = utils::address_range::start_length(cullStart, width * height); - - // cullStart is an offset inside ZCULL RAM which is 3MB long, check bounds - // width and height are not allowed to be zero (checked by range.valid()) - if (!cull_range.valid() || cull_range.end >= 3u << 20 || offset >= render->local_mem_size) - { - return {CELL_EINVAL, "cull_range invalid (valid=%d, end=%d, offset=%d, local_mem_size=%d)", cull_range.valid(), cull_range.end, offset, render->local_mem_size}; - } - - if (a5 & 0xF0000000) - { - sys_rsx.warning("sys_rsx_context_attribute(): ZCULL offset greater than 256MB (offset=0x%x)", offset); - } - - // Hardcoded values in gcm - ensure(a4 & (1ull << 32)); - ensure((a6 & 0xFFFFFFFF) == 0u + ((0x2000 << 0) | (0x20 << 16))); - } - - std::lock_guard lock(render->sys_rsx_mtx); - - auto& zcull = render->zculls[a3]; - - zcull.zFormat = ((a4 >> 32) >> 4) & 0xF; - zcull.aaFormat = ((a4 >> 32) >> 8) & 0xF; - zcull.width = width; - zcull.height = height; - zcull.cullStart = cullStart; - zcull.offset = offset; - zcull.zcullDir = ((a6 >> 32) >> 1) & 0x1; - zcull.zcullFormat = ((a6 >> 32) >> 2) & 0x3FF; - zcull.sFunc = ((a6 >> 32) >> 12) & 0xF; - zcull.sRef = ((a6 >> 32) >> 16) & 0xFF; - zcull.sMask = ((a6 >> 32) >> 24) & 0xFF; - zcull.bound = bound; - break; - } - - case 0x302: // something with zcull - break; - - case 0x600: // Framebuffer setup - break; - - case 0x601: // Framebuffer blit - break; - - case 0x602: // Framebuffer blit sync - break; - - case 0x603: // Framebuffer close - break; - - case 0xFEC: // hack: flip event notification - { - // we only ever use head 1 for now - driverInfo.head[1].flipFlags |= 0x80000000; - driverInfo.head[1].lastFlipTime = rsx_timeStamp(); // should rsxthread set this? - driverInfo.head[1].flipBufferId = static_cast(a3); - - // seems gcmSysWaitLabel uses this offset, so lets set it to 0 every flip - // NOTE: Realhw resets 16 bytes of this semaphore for some reason - vm::_ref>(render->label_addr + 0x10).store(u128{}); - - render->send_event(0, SYS_RSX_EVENT_FLIP_BASE << 1, 0); - break; - } - case 0xFED: // hack: vblank command - { - if (cpu_thread::get_current()) - { - // VBLANK/RSX thread only - return {CELL_EINVAL, "wrong thread"}; - } - - // NOTE: There currently seem to only be 2 active heads on PS3 - ensure(a3 < 2); - - // todo: this is wrong and should be 'second' vblank handler and freq, but since currently everything is reported as being 59.94, this should be fine - driverInfo.head[a3].lastSecondVTime.atomic_op([&](be_t& time) - { - a4 = std::max(a4, time + 1); - time = a4; - }); - - // Time point is supplied in argument 4 (todo: convert it to MFTB rate and use it) - const u64 current_time = rsx_timeStamp(); - - // Note: not atomic - driverInfo.head[a3].lastVTimeLow = static_cast(current_time); - driverInfo.head[a3].lastVTimeHigh = static_cast(current_time >> 32); - - driverInfo.head[a3].vBlankCount++; - - u64 event_flags = SYS_RSX_EVENT_VBLANK; - - if (render->enable_second_vhandler) - event_flags |= SYS_RSX_EVENT_SECOND_VBLANK_BASE << a3; // second vhandler - - render->send_event(0, event_flags, 0); - break; - } - - case 0xFEF: // hack: user command - { - // 'custom' invalid package id for now - // as i think we need custom lv1 interrupts to handle this accurately - // this also should probly be set by rsxthread - driverInfo.userCmdParam = static_cast(a4); - render->send_event(0, SYS_RSX_EVENT_USER_CMD, 0); - break; - } - - default: - return {CELL_EINVAL, "unsupported package id %d", package_id}; - } - - return CELL_OK; -} - -/* - * lv2 SysCall 675 (0x2A3): sys_rsx_device_map - * @param a1 (OUT): rsx device map address : 0x40000000, 0x50000000.. 0xB0000000 - * @param a2 (OUT): Unused - * @param dev_id (IN): An immediate value and always 8. (cellGcmInitPerfMon uses 11, 10, 9, 7, 12 successively). - */ -error_code sys_rsx_device_map(cpu_thread& cpu, vm::ptr dev_addr, vm::ptr a2, u32 dev_id) -{ - cpu.state += cpu_flag::wait; - - sys_rsx.warning("sys_rsx_device_map(dev_addr=*0x%x, a2=*0x%x, dev_id=0x%x)", dev_addr, a2, dev_id); - - if (dev_id != 8) - { - // TODO: lv1 related - fmt::throw_exception("sys_rsx_device_map: Invalid dev_id %d", dev_id); - } - - const auto render = rsx::get_current_renderer(); - - std::scoped_lock lock(render->sys_rsx_mtx); - - if (!render->device_addr) - { - const auto area = vm::reserve_map(vm::rsx_context, 0, 0x10000000, 0x403); - const u32 addr = area ? area->alloc(0x100000) : 0; - - if (!addr) - { - return CELL_ENOMEM; - } - - sys_rsx.warning("sys_rsx_device_map(): Mapped address 0x%x", addr); - - *dev_addr = addr; - render->device_addr = addr; - return CELL_OK; - } - - *dev_addr = render->device_addr; - return CELL_OK; -} - -/* - * lv2 SysCall 676 (0x2A4): sys_rsx_device_unmap - * @param dev_id (IN): An immediate value and always 8. - */ -error_code sys_rsx_device_unmap(cpu_thread& cpu, u32 dev_id) -{ - cpu.state += cpu_flag::wait; - - sys_rsx.todo("sys_rsx_device_unmap(dev_id=0x%x)", dev_id); - - return CELL_OK; -} - -/* - * lv2 SysCall 677 (0x2A5): sys_rsx_attribute - */ -error_code sys_rsx_attribute(cpu_thread& cpu, u32 packageId, u32 a2, u32 a3, u32 a4, u32 a5) -{ - cpu.state += cpu_flag::wait; - - sys_rsx.warning("sys_rsx_attribute(packageId=0x%x, a2=0x%x, a3=0x%x, a4=0x%x, a5=0x%x)", packageId, a2, a3, a4, a5); - - return CELL_OK; -} diff --git a/rpcs3/Emu/Cell/lv2/sys_rsx.h b/rpcs3/Emu/Cell/lv2/sys_rsx.h deleted file mode 100644 index 1e626c763..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_rsx.h +++ /dev/null @@ -1,142 +0,0 @@ -#pragma once - -#include "Emu/Memory/vm_ptr.h" -#include "Emu/Cell/ErrorCodes.h" - -class cpu_thread; - -struct RsxDriverInfo -{ - be_t version_driver; // 0x0 - be_t version_gpu; // 0x4 - be_t memory_size; // 0x8 - be_t hardware_channel; // 0xC - be_t nvcore_frequency; // 0x10 - be_t memory_frequency; // 0x14 - be_t unk1[4]; // 0x18 - 0x24 - be_t unk2; // 0x28 -- pgraph stuff - be_t reportsNotifyOffset; // 0x2C offset to notify memory - be_t reportsOffset; // 0x30 offset to reports memory - be_t reportsReportOffset; // 0x34 offset to reports in reports memory - be_t unk3[6]; // 0x38-0x54 - be_t systemModeFlags; // 0x54 - u8 unk4[0x1064]; // 0x10B8 - - struct Head - { - be_t lastFlipTime; // 0x0 last flip time - atomic_be_t flipFlags; // 0x8 flags to handle flip/queue - be_t offset; // 0xC - be_t flipBufferId; // 0x10 - be_t lastQueuedBufferId; // 0x14 todo: this is definately not this variable but its 'unused' so im using it for queueId to pass to flip handler - be_t unk3; // 0x18 - be_t lastVTimeLow; // 0x1C last time for first vhandler freq (low 32-bits) - atomic_be_t lastSecondVTime; // 0x20 last time for second vhandler freq - be_t unk4; // 0x28 - atomic_be_t vBlankCount; // 0x30 - be_t unk; // 0x38 possible u32, 'flip field', top/bottom for interlaced - be_t lastVTimeHigh; // 0x3C last time for first vhandler freq (high 32-bits) - } head[8]; // size = 0x40, 0x200 - - be_t unk7; // 0x12B8 - be_t unk8; // 0x12BC - atomic_be_t handlers; // 0x12C0 -- flags showing which handlers are set - be_t unk9; // 0x12C4 - be_t unk10; // 0x12C8 - be_t userCmdParam; // 0x12CC - be_t handler_queue; // 0x12D0 - be_t unk11; // 0x12D4 - be_t unk12; // 0x12D8 - be_t unk13; // 0x12DC - be_t unk14; // 0x12E0 - be_t unk15; // 0x12E4 - be_t unk16; // 0x12E8 - be_t unk17; // 0x12F0 - be_t lastError; // 0x12F4 error param for cellGcmSetGraphicsHandler - // todo: theres more to this -}; - -static_assert(sizeof(RsxDriverInfo) == 0x12F8, "rsxSizeTest"); -static_assert(sizeof(RsxDriverInfo::Head) == 0x40, "rsxHeadSizeTest"); - -enum : u64 -{ - // Unused - SYS_RSX_IO_MAP_IS_STRICT = 1ull << 60 -}; - -// Unofficial event names -enum : u64 -{ - // SYS_RSX_EVENT_GRAPHICS_ERROR = 1 << 0, - SYS_RSX_EVENT_VBLANK = 1 << 1, - SYS_RSX_EVENT_FLIP_BASE = 1 << 3, - SYS_RSX_EVENT_QUEUE_BASE = 1 << 5, - SYS_RSX_EVENT_USER_CMD = 1 << 7, - SYS_RSX_EVENT_SECOND_VBLANK_BASE = 1 << 10, - SYS_RSX_EVENT_UNMAPPED_BASE = 1ull << 32, -}; - -struct RsxDmaControl -{ - u8 resv[0x40]; - atomic_be_t put; - atomic_be_t get; - atomic_be_t ref; - be_t unk[2]; - be_t unk1; -}; - -struct RsxSemaphore -{ - atomic_be_t val; -}; - -struct alignas(16) RsxNotify -{ - be_t timestamp; - be_t zero; -}; - -struct alignas(16) RsxReport -{ - be_t timestamp; - be_t val; - be_t pad; -}; - -struct RsxReports -{ - RsxSemaphore semaphore[1024]; - RsxNotify notify[64]; - RsxReport report[2048]; -}; - -struct RsxDisplayInfo -{ - be_t offset{0}; - be_t pitch{0}; - be_t width{0}; - be_t height{0}; - - ENABLE_BITWISE_SERIALIZATION; - - bool valid() const - { - return height != 0u && width != 0u; - } -}; - -// SysCalls -error_code sys_rsx_device_open(cpu_thread& cpu); -error_code sys_rsx_device_close(cpu_thread& cpu); -error_code sys_rsx_memory_allocate(cpu_thread& cpu, vm::ptr mem_handle, vm::ptr mem_addr, u32 size, u64 flags, u64 a5, u64 a6, u64 a7); -error_code sys_rsx_memory_free(cpu_thread& cpu, u32 mem_handle); -error_code sys_rsx_context_allocate(cpu_thread& cpu, vm::ptr context_id, vm::ptr lpar_dma_control, vm::ptr lpar_driver_info, vm::ptr lpar_reports, u64 mem_ctx, u64 system_mode); -error_code sys_rsx_context_free(ppu_thread& ppu, u32 context_id); -error_code sys_rsx_context_iomap(cpu_thread& cpu, u32 context_id, u32 io, u32 ea, u32 size, u64 flags); -error_code sys_rsx_context_iounmap(cpu_thread& cpu, u32 context_id, u32 io, u32 size); -error_code sys_rsx_context_attribute(u32 context_id, u32 package_id, u64 a3, u64 a4, u64 a5, u64 a6); -error_code sys_rsx_device_map(cpu_thread& cpu, vm::ptr dev_addr, vm::ptr a2, u32 dev_id); -error_code sys_rsx_device_unmap(cpu_thread& cpu, u32 dev_id); -error_code sys_rsx_attribute(cpu_thread& cpu, u32 packageId, u32 a2, u32 a3, u32 a4, u32 a5); diff --git a/rpcs3/Emu/Cell/lv2/sys_rsxaudio.cpp b/rpcs3/Emu/Cell/lv2/sys_rsxaudio.cpp deleted file mode 100644 index cb462684b..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_rsxaudio.cpp +++ /dev/null @@ -1,2369 +0,0 @@ -#include "stdafx.h" -#include "Emu/Memory/vm.h" -#include "Emu/IdManager.h" -#include "Emu/System.h" -#include "Emu/system_config.h" -#include "Emu//Audio/audio_utils.h" -#include "util/video_provider.h" - -#include "sys_rsxaudio.h" - -#include -#include -#include - -#ifdef __linux__ -#include -#include -#include -#include -#elif defined(BSD) || defined(__APPLE__) -#include -#endif - -LOG_CHANNEL(sys_rsxaudio); - -extern atomic_t g_recording_mode; - -namespace rsxaudio_ringbuf_reader -{ - static constexpr void clean_buf(rsxaudio_shmem::ringbuf_t& ring_buf) - { - ring_buf.unk2 = 100; - ring_buf.read_idx = 0; - ring_buf.write_idx = 0; - ring_buf.queue_notify_idx = 0; - ring_buf.next_blk_idx = 0; - - for (auto& ring_entry : ring_buf.entries) - { - ring_entry.valid = 0; - ring_entry.audio_blk_idx = 0; - ring_entry.timestamp = 0; - } - } - - static void set_timestamp(rsxaudio_shmem::ringbuf_t& ring_buf, u64 timestamp) - { - const s32 entry_idx_raw = (ring_buf.read_idx + ring_buf.rw_max_idx - (ring_buf.rw_max_idx > 2) - 1) % ring_buf.rw_max_idx; - const s32 entry_idx = std::clamp(entry_idx_raw, 0, SYS_RSXAUDIO_RINGBUF_SZ); - - ring_buf.entries[entry_idx].timestamp = convert_to_timebased_time(timestamp); - } - - static std::tuple update_status(rsxaudio_shmem::ringbuf_t& ring_buf) - { - const s32 read_idx = std::clamp(ring_buf.read_idx, 0, SYS_RSXAUDIO_RINGBUF_SZ); - - if ((ring_buf.entries[read_idx].valid & 1) == 0U) - { - return {}; - } - - const s32 entry_idx_raw = (ring_buf.read_idx + ring_buf.rw_max_idx - (ring_buf.rw_max_idx > 2)) % ring_buf.rw_max_idx; - const s32 entry_idx = std::clamp(entry_idx_raw, 0, SYS_RSXAUDIO_RINGBUF_SZ); - - ring_buf.entries[read_idx].valid = 0; - ring_buf.queue_notify_idx = (ring_buf.queue_notify_idx + 1) % ring_buf.queue_notify_step; - ring_buf.read_idx = (ring_buf.read_idx + 1) % ring_buf.rw_max_idx; - - return std::make_tuple(((ring_buf.rw_max_idx > 2) ^ ring_buf.queue_notify_idx) == 0, ring_buf.entries[entry_idx].audio_blk_idx, ring_buf.entries[entry_idx].timestamp); - } - - static std::pair get_addr(const rsxaudio_shmem::ringbuf_t& ring_buf) - { - const s32 read_idx = std::clamp(ring_buf.read_idx, 0, SYS_RSXAUDIO_RINGBUF_SZ); - - if (ring_buf.entries[read_idx].valid & 1) - { - return std::make_pair(true, ring_buf.entries[read_idx].dma_addr); - } - - return std::make_pair(false, ring_buf.dma_silence_addr); - } - - [[maybe_unused]] - static std::optional get_spdif_channel_data(RsxaudioPort dst, rsxaudio_shmem& shmem) - { - if (dst == RsxaudioPort::SPDIF_0) - { - if (shmem.ctrl.spdif_ch0_channel_data_tx_cycles) - { - shmem.ctrl.spdif_ch0_channel_data_tx_cycles--; - return static_cast(shmem.ctrl.spdif_ch0_channel_data_hi) << 32 | shmem.ctrl.spdif_ch0_channel_data_lo; - } - } - else - { - if (shmem.ctrl.spdif_ch1_channel_data_tx_cycles) - { - shmem.ctrl.spdif_ch1_channel_data_tx_cycles--; - return static_cast(shmem.ctrl.spdif_ch1_channel_data_hi) << 32 | shmem.ctrl.spdif_ch1_channel_data_lo; - } - } - - return std::nullopt; - } -} // namespace rsxaudio_ringbuf_reader - -lv2_rsxaudio::lv2_rsxaudio(utils::serial& ar) noexcept - : lv2_obj{1}, init(ar) -{ - if (init) - { - ar(shmem); - - for (auto& port : event_queue) - { - port = lv2_event_queue::load_ptr(ar, port, "rsxaudio"); - } - } -} - -void lv2_rsxaudio::save(utils::serial& ar) -{ - USING_SERIALIZATION_VERSION(LLE); - - ar(init); - - if (init) - { - ar(shmem); - - for (const auto& port : event_queue) - { - lv2_event_queue::save_ptr(ar, port.get()); - } - } -} - -error_code sys_rsxaudio_initialize(vm::ptr handle) -{ - sys_rsxaudio.trace("sys_rsxaudio_initialize(handle=*0x%x)", handle); - - auto& rsxaudio_thread = g_fxo->get(); - - if (rsxaudio_thread.rsxaudio_ctx_allocated.test_and_set()) - { - return CELL_EINVAL; - } - - if (!vm::check_addr(handle.addr(), vm::page_writable, sizeof(u32))) - { - rsxaudio_thread.rsxaudio_ctx_allocated = false; - return CELL_EFAULT; - } - - const u32 id = idm::make(); - - if (!id) - { - rsxaudio_thread.rsxaudio_ctx_allocated = false; - return CELL_ENOMEM; - } - - const auto rsxaudio_obj = idm::get_unlocked(id); - std::lock_guard lock(rsxaudio_obj->mutex); - - rsxaudio_obj->shmem = vm::addr_t{vm::alloc(sizeof(rsxaudio_shmem), vm::main)}; - - if (!rsxaudio_obj->shmem) - { - idm::remove(id); - rsxaudio_thread.rsxaudio_ctx_allocated = false; - return CELL_ENOMEM; - } - - rsxaudio_obj->page_lock(); - - rsxaudio_shmem* sh_page = rsxaudio_obj->get_rw_shared_page(); - sh_page->ctrl = {}; - - for (auto& uf : sh_page->ctrl.channel_uf) - { - uf.uf_event_cnt = 0; - uf.unk1 = 0; - } - - sh_page->ctrl.unk4 = 0x8000; - sh_page->ctrl.intr_thread_prio = 0xDEADBEEF; - sh_page->ctrl.unk5 = 0; - - rsxaudio_obj->init = true; - *handle = id; - - return CELL_OK; -} - -error_code sys_rsxaudio_finalize(u32 handle) -{ - sys_rsxaudio.trace("sys_rsxaudio_finalize(handle=0x%x)", handle); - - const auto rsxaudio_obj = idm::get_unlocked(handle); - - if (!rsxaudio_obj) - { - return CELL_ESRCH; - } - - std::lock_guard lock(rsxaudio_obj->mutex); - - if (!rsxaudio_obj->init) - { - return CELL_ESRCH; - } - - auto& rsxaudio_thread = g_fxo->get(); - - { - std::lock_guard ra_obj_lock{rsxaudio_thread.rsxaudio_obj_upd_m}; - rsxaudio_thread.rsxaudio_obj_ptr = null_ptr; - } - - rsxaudio_obj->init = false; - vm::dealloc(rsxaudio_obj->shmem, vm::main); - - idm::remove(handle); - rsxaudio_thread.rsxaudio_ctx_allocated = false; - - return CELL_OK; -} - -error_code sys_rsxaudio_import_shared_memory(u32 handle, vm::ptr addr) -{ - sys_rsxaudio.trace("sys_rsxaudio_import_shared_memory(handle=0x%x, addr=*0x%x)", handle, addr); - - const auto rsxaudio_obj = idm::get_unlocked(handle); - - if (!rsxaudio_obj) - { - return CELL_ESRCH; - } - - std::lock_guard lock(rsxaudio_obj->mutex); - - if (!rsxaudio_obj->init) - { - return CELL_ESRCH; - } - - if (!vm::check_addr(addr.addr(), vm::page_writable, sizeof(u64))) - { - return CELL_EFAULT; - } - - *addr = rsxaudio_obj->shmem; - rsxaudio_obj->page_unlock(); - - return CELL_OK; -} - -error_code sys_rsxaudio_unimport_shared_memory(u32 handle, vm::ptr addr /* unused */) -{ - sys_rsxaudio.trace("sys_rsxaudio_unimport_shared_memory(handle=0x%x, addr=*0x%x)", handle, addr); - - const auto rsxaudio_obj = idm::get_unlocked(handle); - - if (!rsxaudio_obj) - { - return CELL_ESRCH; - } - - std::lock_guard lock(rsxaudio_obj->mutex); - - if (!rsxaudio_obj->init) - { - return CELL_ESRCH; - } - - rsxaudio_obj->page_lock(); - - return CELL_OK; -} - -error_code sys_rsxaudio_create_connection(u32 handle) -{ - sys_rsxaudio.trace("sys_rsxaudio_create_connection(handle=0x%x)", handle); - - const auto rsxaudio_obj = idm::get_unlocked(handle); - - if (!rsxaudio_obj) - { - return CELL_ESRCH; - } - - std::lock_guard lock(rsxaudio_obj->mutex); - - if (!rsxaudio_obj->init) - { - return CELL_ESRCH; - } - - rsxaudio_shmem* sh_page = rsxaudio_obj->get_rw_shared_page(); - - const error_code port_create_status = [&]() -> error_code - { - if (auto queue1 = idm::get_unlocked(sh_page->ctrl.event_queue_1_id)) - { - rsxaudio_obj->event_queue[0] = queue1; - - if (auto queue2 = idm::get_unlocked(sh_page->ctrl.event_queue_2_id)) - { - rsxaudio_obj->event_queue[1] = queue2; - - if (auto queue3 = idm::get_unlocked(sh_page->ctrl.event_queue_3_id)) - { - rsxaudio_obj->event_queue[2] = queue3; - - return CELL_OK; - } - } - } - - return CELL_ESRCH; - }(); - - if (port_create_status != CELL_OK) - { - return port_create_status; - } - - for (auto& rb : sh_page->ctrl.ringbuf) - { - rb.dma_silence_addr = rsxaudio_obj->dma_io_base + offsetof(rsxaudio_shmem, dma_silence_region); - rb.unk2 = 100; - } - - for (u32 entry_idx = 0; entry_idx < SYS_RSXAUDIO_RINGBUF_SZ; entry_idx++) - { - sh_page->ctrl.ringbuf[static_cast(RsxaudioPort::SERIAL)].entries[entry_idx].dma_addr = rsxaudio_obj->dma_io_base + u32{offsetof(rsxaudio_shmem, dma_serial_region)} + SYS_RSXAUDIO_RINGBUF_BLK_SZ_SERIAL * entry_idx; - sh_page->ctrl.ringbuf[static_cast(RsxaudioPort::SPDIF_0)].entries[entry_idx].dma_addr = rsxaudio_obj->dma_io_base + u32{offsetof(rsxaudio_shmem, dma_spdif_0_region)} + SYS_RSXAUDIO_RINGBUF_BLK_SZ_SPDIF * entry_idx; - sh_page->ctrl.ringbuf[static_cast(RsxaudioPort::SPDIF_1)].entries[entry_idx].dma_addr = rsxaudio_obj->dma_io_base + u32{offsetof(rsxaudio_shmem, dma_spdif_1_region)} + SYS_RSXAUDIO_RINGBUF_BLK_SZ_SPDIF * entry_idx; - } - - return CELL_OK; -} - -error_code sys_rsxaudio_close_connection(u32 handle) -{ - sys_rsxaudio.trace("sys_rsxaudio_close_connection(handle=0x%x)", handle); - - const auto rsxaudio_obj = idm::get_unlocked(handle); - - if (!rsxaudio_obj) - { - return CELL_ESRCH; - } - - std::lock_guard lock(rsxaudio_obj->mutex); - - if (!rsxaudio_obj->init) - { - return CELL_ESRCH; - } - - { - auto& rsxaudio_thread = g_fxo->get(); - std::lock_guard ra_obj_lock{rsxaudio_thread.rsxaudio_obj_upd_m}; - rsxaudio_thread.rsxaudio_obj_ptr = null_ptr; - } - - for (u32 q_idx = 0; q_idx < SYS_RSXAUDIO_PORT_CNT; q_idx++) - { - rsxaudio_obj->event_queue[q_idx].reset(); - } - - return CELL_OK; -} - -error_code sys_rsxaudio_prepare_process(u32 handle) -{ - sys_rsxaudio.trace("sys_rsxaudio_prepare_process(handle=0x%x)", handle); - - const auto rsxaudio_obj = idm::get_unlocked(handle); - - if (!rsxaudio_obj) - { - return CELL_ESRCH; - } - - std::lock_guard lock(rsxaudio_obj->mutex); - - if (!rsxaudio_obj->init) - { - return CELL_ESRCH; - } - - auto& rsxaudio_thread = g_fxo->get(); - std::lock_guard ra_obj_lock{rsxaudio_thread.rsxaudio_obj_upd_m}; - - if (rsxaudio_thread.rsxaudio_obj_ptr) - { - return -1; - } - - rsxaudio_thread.rsxaudio_obj_ptr = rsxaudio_obj; - - return CELL_OK; -} - -error_code sys_rsxaudio_start_process(u32 handle) -{ - sys_rsxaudio.trace("sys_rsxaudio_start_process(handle=0x%x)", handle); - - const auto rsxaudio_obj = idm::get_unlocked(handle); - - if (!rsxaudio_obj) - { - return CELL_ESRCH; - } - - std::lock_guard lock(rsxaudio_obj->mutex); - - if (!rsxaudio_obj->init) - { - return CELL_ESRCH; - } - - rsxaudio_shmem* sh_page = rsxaudio_obj->get_rw_shared_page(); - - for (auto& rb : sh_page->ctrl.ringbuf) - { - if (rb.active) - rsxaudio_ringbuf_reader::clean_buf(rb); - } - - for (auto& uf : sh_page->ctrl.channel_uf) - { - uf.uf_event_cnt = 0; - uf.unk1 = 0; - } - - auto& rsxaudio_thread = g_fxo->get(); - rsxaudio_thread.update_hw_param([&](auto& param) - { - if (sh_page->ctrl.ringbuf[static_cast(RsxaudioPort::SERIAL)].active) - param.serial.dma_en = true; - if (sh_page->ctrl.ringbuf[static_cast(RsxaudioPort::SPDIF_0)].active) - param.spdif[0].dma_en = true; - if (sh_page->ctrl.ringbuf[static_cast(RsxaudioPort::SPDIF_1)].active) - param.spdif[1].dma_en = true; - }); - - for (u32 q_idx = 0; q_idx < SYS_RSXAUDIO_PORT_CNT; q_idx++) - { - if (const auto& queue = rsxaudio_obj->event_queue[q_idx]; queue && sh_page->ctrl.ringbuf[q_idx].active) - { - queue->send(rsxaudio_obj->event_port_name[q_idx], q_idx, 0, 0); - } - } - - return CELL_OK; -} - -error_code sys_rsxaudio_stop_process(u32 handle) -{ - sys_rsxaudio.trace("sys_rsxaudio_stop_process(handle=0x%x)", handle); - - const auto rsxaudio_obj = idm::get_unlocked(handle); - - if (!rsxaudio_obj) - { - return CELL_ESRCH; - } - - std::lock_guard lock(rsxaudio_obj->mutex); - - if (!rsxaudio_obj->init) - { - return CELL_ESRCH; - } - - auto& rsxaudio_thread = g_fxo->get(); - - rsxaudio_thread.update_hw_param([&](auto& param) - { - param.serial.dma_en = false; - param.serial.muted = true; - param.serial.en = false; - - for (auto& spdif : param.spdif) - { - spdif.dma_en = false; - if (!spdif.use_serial_buf) - { - spdif.en = false; - } - } - - param.spdif[1].muted = true; - }); - - rsxaudio_shmem* sh_page = rsxaudio_obj->get_rw_shared_page(); - - for (auto& rb : sh_page->ctrl.ringbuf) - { - if (rb.active) - rsxaudio_ringbuf_reader::clean_buf(rb); - } - - return CELL_OK; -} - -error_code sys_rsxaudio_get_dma_param(u32 handle, u32 flag, vm::ptr out) -{ - sys_rsxaudio.trace("sys_rsxaudio_get_dma_param(handle=0x%x, flag=0x%x, out=0x%x)", handle, flag, out); - - const auto rsxaudio_obj = idm::get_unlocked(handle); - - if (!rsxaudio_obj) - { - return CELL_ESRCH; - } - - std::lock_guard lock(rsxaudio_obj->mutex); - - if (!rsxaudio_obj->init) - { - return CELL_ESRCH; - } - - if (!vm::check_addr(out.addr(), vm::page_writable, sizeof(u64))) - { - return CELL_EFAULT; - } - - if (flag == rsxaudio_dma_flag::IO_ID) - { - *out = rsxaudio_obj->dma_io_id; - } - else if (flag == rsxaudio_dma_flag::IO_BASE) - { - *out = rsxaudio_obj->dma_io_base; - } - - return CELL_OK; -} - -rsxaudio_data_container::rsxaudio_data_container(const rsxaudio_hw_param_t& hw_param, const buf_t& buf, bool serial_rdy, bool spdif_0_rdy, bool spdif_1_rdy) : hwp(hw_param), out_buf(buf) -{ - if (serial_rdy) - { - avport_data_avail[static_cast(RsxaudioAvportIdx::AVMULTI)] = true; - - if (hwp.spdif[0].use_serial_buf) - { - avport_data_avail[static_cast(RsxaudioAvportIdx::SPDIF_0)] = true; - } - - if (hwp.spdif[1].use_serial_buf) - { - avport_data_avail[static_cast(RsxaudioAvportIdx::SPDIF_1)] = true; - } - } - - if (spdif_0_rdy && !hwp.spdif[0].use_serial_buf) - { - avport_data_avail[static_cast(RsxaudioAvportIdx::SPDIF_0)] = true; - } - - if (spdif_1_rdy && !hwp.spdif[1].use_serial_buf) - { - avport_data_avail[static_cast(RsxaudioAvportIdx::SPDIF_1)] = true; - } - - if (hwp.hdmi[0].init) - { - if (hwp.hdmi[0].use_spdif_1) - { - avport_data_avail[static_cast(RsxaudioAvportIdx::HDMI_0)] = avport_data_avail[static_cast(RsxaudioAvportIdx::SPDIF_1)]; - } - else - { - avport_data_avail[static_cast(RsxaudioAvportIdx::HDMI_0)] = serial_rdy; - } - - hdmi_stream_cnt[0] = static_cast(hwp.hdmi[0].ch_cfg.total_ch_cnt) / SYS_RSXAUDIO_CH_PER_STREAM; - } - - if (hwp.hdmi[1].init) - { - if (hwp.hdmi[1].use_spdif_1) - { - avport_data_avail[static_cast(RsxaudioAvportIdx::HDMI_1)] = avport_data_avail[static_cast(RsxaudioAvportIdx::SPDIF_1)]; - } - else - { - avport_data_avail[static_cast(RsxaudioAvportIdx::HDMI_1)] = serial_rdy; - } - - hdmi_stream_cnt[1] = static_cast(hwp.hdmi[1].ch_cfg.total_ch_cnt) / SYS_RSXAUDIO_CH_PER_STREAM; - } -} - -u32 rsxaudio_data_container::get_data_size(RsxaudioAvportIdx avport) -{ - if (!avport_data_avail[static_cast(avport)]) - { - return 0; - } - - switch (avport) - { - case RsxaudioAvportIdx::HDMI_0: - { - const RsxaudioSampleSize depth = hwp.hdmi[0].use_spdif_1 ? hwp.spdif[1].depth : hwp.serial.depth; - - return (depth == RsxaudioSampleSize::_16BIT ? SYS_RSXAUDIO_STREAM_SIZE * 2 : SYS_RSXAUDIO_STREAM_SIZE) * hdmi_stream_cnt[0]; - } - case RsxaudioAvportIdx::HDMI_1: - { - const RsxaudioSampleSize depth = hwp.hdmi[1].use_spdif_1 ? hwp.spdif[1].depth : hwp.serial.depth; - - return (depth == RsxaudioSampleSize::_16BIT ? SYS_RSXAUDIO_STREAM_SIZE * 2 : SYS_RSXAUDIO_STREAM_SIZE) * hdmi_stream_cnt[1]; - } - case RsxaudioAvportIdx::AVMULTI: - { - return hwp.serial.depth == RsxaudioSampleSize::_16BIT ? SYS_RSXAUDIO_STREAM_SIZE * 2 : SYS_RSXAUDIO_STREAM_SIZE; - } - case RsxaudioAvportIdx::SPDIF_0: - { - const RsxaudioSampleSize depth = hwp.spdif[0].use_serial_buf ? hwp.serial.depth : hwp.spdif[0].depth; - - return depth == RsxaudioSampleSize::_16BIT ? SYS_RSXAUDIO_STREAM_SIZE * 2 : SYS_RSXAUDIO_STREAM_SIZE; - } - case RsxaudioAvportIdx::SPDIF_1: - { - const RsxaudioSampleSize depth = hwp.spdif[1].use_serial_buf ? hwp.serial.depth : hwp.spdif[1].depth; - - return depth == RsxaudioSampleSize::_16BIT ? SYS_RSXAUDIO_STREAM_SIZE * 2 : SYS_RSXAUDIO_STREAM_SIZE; - } - default: - { - return 0; - } - } -} - -void rsxaudio_data_container::get_data(RsxaudioAvportIdx avport, data_blk_t& data_out) -{ - if (!avport_data_avail[static_cast(avport)]) - { - return; - } - - data_was_written = true; - - auto spdif_filter_map = [&](u8 hdmi_idx) - { - std::array result; - - for (u64 i = 0; i < SYS_RSXAUDIO_SERIAL_MAX_CH; i++) - { - const u8 old_val = hwp.hdmi[hdmi_idx].ch_cfg.map[i]; - result[i] = old_val >= SYS_RSXAUDIO_SPDIF_MAX_CH ? rsxaudio_hw_param_t::hdmi_param_t::MAP_SILENT_CH : old_val; - } - - return result; - }; - - switch (avport) - { - case RsxaudioAvportIdx::HDMI_0: - case RsxaudioAvportIdx::HDMI_1: - { - const u8 hdmi_idx = avport == RsxaudioAvportIdx::HDMI_1; - - switch (hdmi_stream_cnt[hdmi_idx]) - { - default: - case 0: - { - return; - } - case 1: - { - if (hwp.hdmi[hdmi_idx].use_spdif_1) - { - if (hwp.spdif[1].use_serial_buf) - { - mix<2>(spdif_filter_map(hdmi_idx), hwp.serial.depth, out_buf.serial, data_out); - } - else - { - mix<2>(hwp.hdmi[hdmi_idx].ch_cfg.map, hwp.spdif[1].depth, out_buf.spdif[1], data_out); - } - } - else - { - mix<2>(hwp.hdmi[hdmi_idx].ch_cfg.map, hwp.serial.depth, out_buf.serial, data_out); - } - break; - } - case 3: - { - if (hwp.hdmi[hdmi_idx].use_spdif_1) - { - if (hwp.spdif[1].use_serial_buf) - { - mix<6>(spdif_filter_map(hdmi_idx), hwp.serial.depth, out_buf.serial, data_out); - } - else - { - mix<6>(hwp.hdmi[hdmi_idx].ch_cfg.map, hwp.spdif[1].depth, out_buf.spdif[1], data_out); - } - } - else - { - mix<6>(hwp.hdmi[hdmi_idx].ch_cfg.map, hwp.serial.depth, out_buf.serial, data_out); - } - break; - } - case 4: - { - if (hwp.hdmi[hdmi_idx].use_spdif_1) - { - if (hwp.spdif[1].use_serial_buf) - { - mix<8>(spdif_filter_map(hdmi_idx), hwp.serial.depth, out_buf.serial, data_out); - } - else - { - mix<8>(hwp.hdmi[hdmi_idx].ch_cfg.map, hwp.spdif[1].depth, out_buf.spdif[1], data_out); - } - } - else - { - mix<8>(hwp.hdmi[hdmi_idx].ch_cfg.map, hwp.serial.depth, out_buf.serial, data_out); - } - break; - } - } - - break; - } - case RsxaudioAvportIdx::AVMULTI: - { - mix<2>({2, 3}, hwp.serial.depth, out_buf.serial, data_out); - break; - } - case RsxaudioAvportIdx::SPDIF_0: - case RsxaudioAvportIdx::SPDIF_1: - { - const u8 spdif_idx = avport == RsxaudioAvportIdx::SPDIF_1; - - if (hwp.spdif[spdif_idx].use_serial_buf) - { - mix<2>({0, 1}, hwp.serial.depth, out_buf.serial, data_out); - } - else - { - mix<2>({0, 1}, hwp.spdif[spdif_idx].depth, out_buf.spdif[spdif_idx], data_out); - } - break; - } - default: - { - return; - } - } -} - -bool rsxaudio_data_container::data_was_used() -{ - return data_was_written; -} - -rsxaudio_data_thread::rsxaudio_data_thread() {} - -void rsxaudio_data_thread::operator()() -{ - thread_ctrl::scoped_priority high_prio(+1); - - while (thread_ctrl::state() != thread_state::aborting) - { - static const std::function tmr_callback = [this]() - { - extract_audio_data(); - }; - - switch (timer.wait(tmr_callback)) - { - case rsxaudio_periodic_tmr::wait_result::SUCCESS: - case rsxaudio_periodic_tmr::wait_result::TIMEOUT: - case rsxaudio_periodic_tmr::wait_result::TIMER_CANCELED: - { - continue; - } - case rsxaudio_periodic_tmr::wait_result::INVALID_PARAM: - case rsxaudio_periodic_tmr::wait_result::TIMER_ERROR: - default: - { - fmt::throw_exception("rsxaudio_periodic_tmr::wait() failed"); - } - } - } -} - -rsxaudio_data_thread& rsxaudio_data_thread::operator=(thread_state /* state */) -{ - timer.cancel_wait(); - return *this; -} - -void rsxaudio_data_thread::advance_all_timers() -{ - const u64 crnt_time = get_system_time(); - - timer.vtimer_skip_periods(static_cast(RsxaudioPort::SERIAL), crnt_time); - timer.vtimer_skip_periods(static_cast(RsxaudioPort::SPDIF_0), crnt_time); - timer.vtimer_skip_periods(static_cast(RsxaudioPort::SPDIF_1), crnt_time); -} - -void rsxaudio_data_thread::extract_audio_data() -{ - // Accessing timer state is safe here, since we're in timer::wait() - - const auto rsxaudio_obj = [&]() - { - std::lock_guard ra_obj_lock{rsxaudio_obj_upd_m}; - return rsxaudio_obj_ptr; - }(); - - if (Emu.IsPausedOrReady() || !rsxaudio_obj) - { - advance_all_timers(); - return; - } - - std::lock_guard rsxaudio_lock(rsxaudio_obj->mutex); - - if (!rsxaudio_obj->init) - { - advance_all_timers(); - return; - } - - rsxaudio_shmem* sh_page = rsxaudio_obj->get_rw_shared_page(); - const auto hw_cfg = hw_param_ts.get_current(); - const u64 crnt_time = get_system_time(); - - auto process_rb = [&](RsxaudioPort dst, bool dma_en) - { - // SPDIF channel data and underflow events are always disabled by lv1 - - const u32 dst_raw = static_cast(dst); - rsxaudio_ringbuf_reader::set_timestamp(sh_page->ctrl.ringbuf[dst_raw], timer.vtimer_get_sched_time(dst_raw)); - - const auto [data_present, rb_addr] = get_ringbuf_addr(dst, *rsxaudio_obj); - bool reset_periods = !enqueue_data(dst, rb_addr == nullptr, rb_addr, *hw_cfg); - - if (dma_en) - { - if (const auto [notify, blk_idx, timestamp] = rsxaudio_ringbuf_reader::update_status(sh_page->ctrl.ringbuf[dst_raw]); notify) - { - // Too late to recover - reset_periods = true; - - if (const auto& queue = rsxaudio_obj->event_queue[dst_raw]) - { - queue->send(rsxaudio_obj->event_port_name[dst_raw], dst_raw, blk_idx, timestamp); - } - } - } - - if (reset_periods) - { - timer.vtimer_skip_periods(dst_raw, crnt_time); - } - else - { - timer.vtimer_incr(dst_raw, crnt_time); - } - }; - - if (timer.is_vtimer_behind(static_cast(RsxaudioPort::SERIAL), crnt_time)) - { - process_rb(RsxaudioPort::SERIAL, hw_cfg->serial.dma_en); - } - - if (timer.is_vtimer_behind(static_cast(RsxaudioPort::SPDIF_0), crnt_time)) - { - process_rb(RsxaudioPort::SPDIF_0, hw_cfg->spdif[0].dma_en); - } - - if (timer.is_vtimer_behind(static_cast(RsxaudioPort::SPDIF_1), crnt_time)) - { - process_rb(RsxaudioPort::SPDIF_1, hw_cfg->spdif[1].dma_en); - } -} - -std::pair rsxaudio_data_thread::get_ringbuf_addr(RsxaudioPort dst, const lv2_rsxaudio& rsxaudio_obj) -{ - ensure(dst <= RsxaudioPort::SPDIF_1); - - rsxaudio_shmem* sh_page = rsxaudio_obj.get_rw_shared_page(); - const auto [data_present, addr] = rsxaudio_ringbuf_reader::get_addr(sh_page->ctrl.ringbuf[static_cast(dst)]); - const u32 buf_size = dst == RsxaudioPort::SERIAL ? SYS_RSXAUDIO_RINGBUF_BLK_SZ_SERIAL : SYS_RSXAUDIO_RINGBUF_BLK_SZ_SPDIF; - - if (addr >= rsxaudio_obj.dma_io_base && addr < rsxaudio_obj.dma_io_base + sizeof(rsxaudio_shmem) - buf_size) - { - return std::make_pair(data_present, reinterpret_cast(rsxaudio_obj.get_rw_shared_page()) + addr - rsxaudio_obj.dma_io_base); - } - - // Buffer address is invalid - return std::make_pair(false, nullptr); -} - -void rsxaudio_data_thread::reset_hw() -{ - update_hw_param([&](rsxaudio_hw_param_t& current) - { - const bool serial_dma_en = current.serial.dma_en; - current.serial = {}; - current.serial.dma_en = serial_dma_en; - - for (auto& spdif : current.spdif) - { - const bool spdif_dma_en = spdif.dma_en; - spdif = {}; - spdif.dma_en = spdif_dma_en; - } - - current.serial_freq_base = SYS_RSXAUDIO_FREQ_BASE_384K; - current.spdif_freq_base = SYS_RSXAUDIO_FREQ_BASE_352K; - current.avport_src.fill(RsxaudioPort::INVALID); - }); -} - -void rsxaudio_data_thread::update_hw_param(std::function update_callback) -{ - ensure(update_callback); - - hw_param_ts.add_op([&]() - { - auto new_hw_param = std::make_shared(*hw_param_ts.get_current()); - - update_callback(*new_hw_param); - - const bool serial_active = calc_port_active_state(RsxaudioPort::SERIAL, *new_hw_param); - const bool spdif_active[SYS_RSXAUDIO_SPDIF_CNT] = - { - calc_port_active_state(RsxaudioPort::SPDIF_0, *new_hw_param), - calc_port_active_state(RsxaudioPort::SPDIF_1, *new_hw_param)}; - - std::array port_cfg{}; - port_cfg[static_cast(RsxaudioAvportIdx::AVMULTI)] = {static_cast(new_hw_param->serial_freq_base / new_hw_param->serial.freq_div), AudioChannelCnt::STEREO}; - - auto gen_spdif_port_cfg = [&](u8 spdif_idx) - { - if (new_hw_param->spdif[spdif_idx].use_serial_buf) - { - return port_cfg[static_cast(RsxaudioAvportIdx::AVMULTI)]; - } - - return rsxaudio_backend_thread::port_config{static_cast(new_hw_param->spdif_freq_base / new_hw_param->spdif[spdif_idx].freq_div), AudioChannelCnt::STEREO}; - }; - - port_cfg[static_cast(RsxaudioAvportIdx::SPDIF_0)] = gen_spdif_port_cfg(0); - port_cfg[static_cast(RsxaudioAvportIdx::SPDIF_1)] = gen_spdif_port_cfg(1); - - auto gen_hdmi_port_cfg = [&](u8 hdmi_idx) - { - if (new_hw_param->hdmi[hdmi_idx].use_spdif_1) - { - return rsxaudio_backend_thread::port_config{port_cfg[static_cast(RsxaudioAvportIdx::SPDIF_1)].freq, new_hw_param->hdmi[hdmi_idx].ch_cfg.total_ch_cnt}; - } - - return rsxaudio_backend_thread::port_config{port_cfg[static_cast(RsxaudioAvportIdx::AVMULTI)].freq, new_hw_param->hdmi[hdmi_idx].ch_cfg.total_ch_cnt}; - }; - - port_cfg[static_cast(RsxaudioAvportIdx::HDMI_0)] = gen_hdmi_port_cfg(0); - port_cfg[static_cast(RsxaudioAvportIdx::HDMI_1)] = gen_hdmi_port_cfg(1); - // TODO: ideally, old data must be flushed from backend buffers if channel became inactive or its src changed - g_fxo->get().set_new_stream_param(port_cfg, calc_avport_mute_state(*new_hw_param)); - - timer.vtimer_access_sec([&]() - { - const u64 crnt_time = get_system_time(); - - if (serial_active) - { - // 2 channels per stream, streams go in parallel - const u32 new_timer_rate = static_cast(port_cfg[static_cast(RsxaudioAvportIdx::AVMULTI)].freq) * - static_cast(new_hw_param->serial.depth) * - SYS_RSXAUDIO_CH_PER_STREAM; - - timer.enable_vtimer(static_cast(RsxaudioPort::SERIAL), new_timer_rate, crnt_time); - } - else - { - timer.disable_vtimer(static_cast(RsxaudioPort::SERIAL)); - } - - for (u8 spdif_idx = 0; spdif_idx < SYS_RSXAUDIO_SPDIF_CNT; spdif_idx++) - { - const u32 vtimer_id = static_cast(RsxaudioPort::SPDIF_0) + spdif_idx; - - if (spdif_active[spdif_idx] && !new_hw_param->spdif[spdif_idx].use_serial_buf) - { - // 2 channels per stream, single stream - const u32 new_timer_rate = static_cast(port_cfg[static_cast(RsxaudioAvportIdx::SPDIF_0) + spdif_idx].freq) * - static_cast(new_hw_param->spdif[spdif_idx].depth) * - SYS_RSXAUDIO_CH_PER_STREAM; - - timer.enable_vtimer(vtimer_id, new_timer_rate, crnt_time); - } - else - { - timer.disable_vtimer(vtimer_id); - } - } - }); - - return new_hw_param; - }); -} - -void rsxaudio_data_thread::update_mute_state(RsxaudioPort port, bool muted) -{ - hw_param_ts.add_op([&]() - { - auto new_hw_param = std::make_shared(*hw_param_ts.get_current()); - - switch (port) - { - case RsxaudioPort::SERIAL: - { - new_hw_param->serial.muted = muted; - break; - } - case RsxaudioPort::SPDIF_0: - { - new_hw_param->spdif[0].muted = muted; - break; - } - case RsxaudioPort::SPDIF_1: - { - new_hw_param->spdif[1].muted = muted; - break; - } - default: - { - fmt::throw_exception("Invalid RSXAudio port: %u", static_cast(port)); - } - } - - g_fxo->get().set_mute_state(calc_avport_mute_state(*new_hw_param)); - - return new_hw_param; - }); -} - -void rsxaudio_data_thread::update_av_mute_state(RsxaudioAvportIdx avport, bool muted, bool force_mute, bool set) -{ - hw_param_ts.add_op([&]() - { - auto new_hw_param = std::make_shared(*hw_param_ts.get_current()); - - switch (avport) - { - case RsxaudioAvportIdx::HDMI_0: - case RsxaudioAvportIdx::HDMI_1: - { - const u32 hdmi_idx = avport == RsxaudioAvportIdx::HDMI_1; - - if (muted) - { - new_hw_param->hdmi[hdmi_idx].muted = set; - } - - if (force_mute) - { - new_hw_param->hdmi[hdmi_idx].force_mute = set; - } - break; - } - case RsxaudioAvportIdx::AVMULTI: - { - if (muted) - { - new_hw_param->avmulti_av_muted = set; - } - break; - } - default: - { - fmt::throw_exception("Invalid RSXAudio avport: %u", static_cast(avport)); - } - } - - g_fxo->get().set_mute_state(calc_avport_mute_state(*new_hw_param)); - - return new_hw_param; - }); -} - -rsxaudio_backend_thread::avport_bit rsxaudio_data_thread::calc_avport_mute_state(const rsxaudio_hw_param_t& hwp) -{ - const bool serial_active = calc_port_active_state(RsxaudioPort::SERIAL, hwp); - - const bool spdif_active[SYS_RSXAUDIO_SPDIF_CNT] = - { - calc_port_active_state(RsxaudioPort::SPDIF_0, hwp), - calc_port_active_state(RsxaudioPort::SPDIF_1, hwp)}; - - const bool avmulti = !serial_active || hwp.serial.muted || hwp.avmulti_av_muted; - - auto spdif_muted = [&](u8 spdif_idx) - { - const u8 spdif_port = spdif_idx == 1; - - if (hwp.spdif[spdif_port].use_serial_buf) - { - // TODO: HW test if both serial and spdif mutes are used in serial mode for spdif - return !serial_active || hwp.spdif[spdif_port].freq_div != hwp.serial.freq_div || hwp.serial.muted || hwp.spdif[spdif_port].muted; - } - - return !spdif_active[spdif_port] || hwp.spdif[spdif_port].muted; - }; - - auto hdmi_muted = [&](u8 hdmi_idx) - { - const u8 hdmi_port = hdmi_idx == 1; - - if (hwp.hdmi[hdmi_idx].use_spdif_1) - { - return spdif_muted(1) || hwp.hdmi[hdmi_port].muted || hwp.hdmi[hdmi_port].force_mute || !hwp.hdmi[hdmi_port].init; - } - - return !serial_active || hwp.serial.muted || hwp.hdmi[hdmi_port].muted || hwp.hdmi[hdmi_port].force_mute || !hwp.hdmi[hdmi_port].init; - }; - - return {hdmi_muted(0), hdmi_muted(1), avmulti, spdif_muted(0), spdif_muted(1)}; -} - -bool rsxaudio_data_thread::calc_port_active_state(RsxaudioPort port, const rsxaudio_hw_param_t& hwp) -{ - auto gen_serial_active = [&]() - { - return hwp.serial.dma_en && hwp.serial.buf_empty_en && hwp.serial.en; - }; - - auto gen_spdif_active = [&](u8 spdif_idx) - { - if (hwp.spdif[spdif_idx].use_serial_buf) - { - return gen_serial_active() && (hwp.spdif[spdif_idx].freq_div == hwp.serial.freq_div); - } - - return hwp.spdif[spdif_idx].dma_en && hwp.spdif[spdif_idx].buf_empty_en && hwp.spdif[spdif_idx].en; - }; - - switch (port) - { - case RsxaudioPort::SERIAL: - { - return gen_serial_active(); - } - case RsxaudioPort::SPDIF_0: - { - return gen_spdif_active(0); - } - case RsxaudioPort::SPDIF_1: - { - return gen_spdif_active(1); - } - default: - { - return false; - } - } -} - -f32 rsxaudio_data_thread::pcm_to_float(s32 sample) -{ - return sample * (1.0f / 2147483648.0f); -} - -f32 rsxaudio_data_thread::pcm_to_float(s16 sample) -{ - return sample * (1.0f / 32768.0f); -} - -void rsxaudio_data_thread::pcm_serial_process_channel(RsxaudioSampleSize word_bits, ra_stream_blk_t& buf_out_l, ra_stream_blk_t& buf_out_r, const void* buf_in, u8 src_stream) -{ - const u8 input_word_sz = static_cast(word_bits); - u64 ch_dst = 0; - - for (u64 blk_idx = 0; blk_idx < SYS_RSXAUDIO_STREAM_DATA_BLK_CNT; blk_idx++) - { - for (u64 offset = 0; offset < SYS_RSXAUDIO_DATA_BLK_SIZE / 2; offset += input_word_sz, ch_dst++) - { - const u64 left_ch_src = (blk_idx * SYS_RSXAUDIO_STREAM_SIZE + src_stream * SYS_RSXAUDIO_DATA_BLK_SIZE + offset) / input_word_sz; - const u64 right_ch_src = left_ch_src + (SYS_RSXAUDIO_DATA_BLK_SIZE / 2) / input_word_sz; - - if (word_bits == RsxaudioSampleSize::_16BIT) - { - buf_out_l[ch_dst] = pcm_to_float(static_cast*>(buf_in)[left_ch_src]); - buf_out_r[ch_dst] = pcm_to_float(static_cast*>(buf_in)[right_ch_src]); - } - else - { - // Looks like rsx treats 20bit/24bit samples as 32bit ones - buf_out_l[ch_dst] = pcm_to_float(static_cast*>(buf_in)[left_ch_src]); - buf_out_r[ch_dst] = pcm_to_float(static_cast*>(buf_in)[right_ch_src]); - } - } - } -} - -void rsxaudio_data_thread::pcm_spdif_process_channel(RsxaudioSampleSize word_bits, ra_stream_blk_t& buf_out_l, ra_stream_blk_t& buf_out_r, const void* buf_in) -{ - const u8 input_word_sz = static_cast(word_bits); - - for (u64 offset = 0; offset < SYS_RSXAUDIO_RINGBUF_BLK_SZ_SPDIF / (input_word_sz * SYS_RSXAUDIO_SPDIF_MAX_CH); offset++) - { - const u64 left_ch_src = offset * SYS_RSXAUDIO_SPDIF_MAX_CH; - const u64 right_ch_src = left_ch_src + 1; - - if (word_bits == RsxaudioSampleSize::_16BIT) - { - buf_out_l[offset] = pcm_to_float(static_cast*>(buf_in)[left_ch_src]); - buf_out_r[offset] = pcm_to_float(static_cast*>(buf_in)[right_ch_src]); - } - else - { - // Looks like rsx treats 20bit/24bit samples as 32bit ones - buf_out_l[offset] = pcm_to_float(static_cast*>(buf_in)[left_ch_src]); - buf_out_r[offset] = pcm_to_float(static_cast*>(buf_in)[right_ch_src]); - } - } -} - -bool rsxaudio_data_thread::enqueue_data(RsxaudioPort dst, bool silence, const void* src_addr, const rsxaudio_hw_param_t& hwp) -{ - auto& backend_thread = g_fxo->get(); - - if (dst == RsxaudioPort::SERIAL) - { - if (!silence) - { - for (u8 stream_idx = 0; stream_idx < SYS_RSXAUDIO_SERIAL_STREAM_CNT; stream_idx++) - { - pcm_serial_process_channel(hwp.serial.depth, output_buf.serial[stream_idx * 2], output_buf.serial[stream_idx * 2 + 1], src_addr, stream_idx); - } - } - else - { - output_buf.serial.fill({}); - } - - rsxaudio_data_container cont{hwp, output_buf, true, false, false}; - backend_thread.add_data(cont); - return cont.data_was_used(); - } - else if (dst == RsxaudioPort::SPDIF_0) - { - if (!silence) - { - pcm_spdif_process_channel(hwp.spdif[0].depth, output_buf.spdif[0][0], output_buf.spdif[0][1], src_addr); - } - else - { - output_buf.spdif[0].fill({}); - } - - rsxaudio_data_container cont{hwp, output_buf, false, true, false}; - backend_thread.add_data(cont); - return cont.data_was_used(); - } - else if (dst == RsxaudioPort::SPDIF_1) - { - if (!silence) - { - pcm_spdif_process_channel(hwp.spdif[1].depth, output_buf.spdif[1][0], output_buf.spdif[1][1], src_addr); - } - else - { - output_buf.spdif[1].fill({}); - } - - rsxaudio_data_container cont{hwp, output_buf, false, false, true}; - backend_thread.add_data(cont); - return cont.data_was_used(); - } - - return false; -} - -namespace audio -{ - extern void configure_rsxaudio() - { - if (g_cfg.audio.provider == audio_provider::rsxaudio && g_fxo->is_init()) - { - g_fxo->get().update_emu_cfg(); - } - } -} // namespace audio - -rsxaudio_backend_thread::rsxaudio_backend_thread() -{ - new_emu_cfg = get_emu_cfg(); - - const f32 new_vol = audio::get_volume(); - - callback_cfg.atomic_op([&](callback_config& val) - { - val.target_volume = static_cast(new_vol * callback_config::VOL_NOMINAL); - val.initial_volume = val.current_volume; - }); -} - -rsxaudio_backend_thread::~rsxaudio_backend_thread() -{ - if (backend) - { - backend->Close(); - backend->SetWriteCallback(nullptr); - backend->SetStateCallback(nullptr); - backend = nullptr; - } -} - -void rsxaudio_backend_thread::update_emu_cfg() -{ - std::unique_lock lock(state_update_m); - const emu_audio_cfg _new_emu_cfg = get_emu_cfg(); - const f32 new_vol = audio::get_volume(); - - callback_cfg.atomic_op([&](callback_config& val) - { - val.target_volume = static_cast(new_vol * callback_config::VOL_NOMINAL); - val.initial_volume = val.current_volume; - }); - - if (new_emu_cfg != _new_emu_cfg) - { - new_emu_cfg = _new_emu_cfg; - emu_cfg_changed = true; - lock.unlock(); - state_update_c.notify_all(); - } -} - -u32 rsxaudio_backend_thread::get_sample_rate() const -{ - return callback_cfg.load().freq; -} - -u8 rsxaudio_backend_thread::get_channel_count() const -{ - return callback_cfg.load().input_ch_cnt; -} - -rsxaudio_backend_thread::emu_audio_cfg rsxaudio_backend_thread::get_emu_cfg() -{ - // Get max supported channel count - AudioChannelCnt out_ch_cnt = AudioBackend::get_max_channel_count(0); // CELL_AUDIO_OUT_PRIMARY - - emu_audio_cfg cfg = - { - .audio_device = g_cfg.audio.audio_device, - .desired_buffer_duration = g_cfg.audio.desired_buffer_duration, - .time_stretching_threshold = g_cfg.audio.time_stretching_threshold / 100.0, - .buffering_enabled = static_cast(g_cfg.audio.enable_buffering), - .convert_to_s16 = static_cast(g_cfg.audio.convert_to_s16), - .enable_time_stretching = static_cast(g_cfg.audio.enable_time_stretching), - .dump_to_file = static_cast(g_cfg.audio.dump_to_file), - .channels = out_ch_cnt, - .channel_layout = g_cfg.audio.channel_layout, - .renderer = g_cfg.audio.renderer, - .provider = g_cfg.audio.provider, - .avport = convert_avport(g_cfg.audio.rsxaudio_port)}; - - cfg.buffering_enabled = cfg.buffering_enabled && cfg.renderer != audio_renderer::null; - cfg.enable_time_stretching = cfg.buffering_enabled && cfg.enable_time_stretching && cfg.time_stretching_threshold > 0.0; - - return cfg; -} - -void rsxaudio_backend_thread::operator()() -{ - if (g_cfg.audio.provider != audio_provider::rsxaudio) - { - return; - } - - static rsxaudio_state ra_state{}; - static emu_audio_cfg emu_cfg{}; - static bool backend_failed = false; - - for (;;) - { - bool should_update_backend = false; - bool reset_backend = false; - bool checkDefaultDevice = false; - bool should_service_stream = false; - - { - std::unique_lock lock(state_update_m); - for (;;) - { - // Unsafe to access backend under lock (state_changed_callback uses state_update_m -> possible deadlock) - - if (thread_ctrl::state() == thread_state::aborting) - { - lock.unlock(); - backend_stop(); - return; - } - - if (backend_device_changed) - { - should_update_backend = true; - checkDefaultDevice = true; - backend_device_changed = false; - } - - // Emulated state changed - if (ra_state_changed) - { - const callback_config cb_cfg = callback_cfg.observe(); - ra_state_changed = false; - ra_state = new_ra_state; - - if (cb_cfg.cfg_changed) - { - should_update_backend = true; - checkDefaultDevice = false; - callback_cfg.atomic_op([&](callback_config& val) - { - val.cfg_changed = false; // Acknowledge cfg update - }); - } - } - - // Update emu config - if (emu_cfg_changed) - { - reset_backend |= emu_cfg.renderer != new_emu_cfg.renderer; - emu_cfg_changed = false; - emu_cfg = new_emu_cfg; - should_update_backend = true; - checkDefaultDevice = false; - } - - // Handle backend error notification - if (backend_error_occured) - { - reset_backend = true; - should_update_backend = true; - checkDefaultDevice = false; - backend_error_occured = false; - } - - if (should_update_backend) - { - backend_current_cfg.cfg = ra_state.port[static_cast(emu_cfg.avport)]; - backend_current_cfg.avport = emu_cfg.avport; - break; - } - - if (backend_failed) - { - state_update_c.wait(state_update_m, ERROR_SERVICE_PERIOD); - break; - } - - if (use_aux_ringbuf) - { - const u64 next_period_time = get_time_until_service(); - should_service_stream = next_period_time <= SERVICE_THRESHOLD; - - if (should_service_stream) - { - break; - } - - state_update_c.wait(state_update_m, next_period_time); - } - else - { - // Nothing to do - wait for events - state_update_c.wait(state_update_m, umax); - } - } - } - - if (should_update_backend && (!checkDefaultDevice || backend->DefaultDeviceChanged())) - { - backend_init(ra_state, emu_cfg, reset_backend); - - if (emu_cfg.enable_time_stretching) - { - resampler.set_params(backend_current_cfg.cfg.ch_cnt, backend_current_cfg.cfg.freq); - resampler.set_tempo(RESAMPLER_MAX_FREQ_VAL); - } - - if (emu_cfg.dump_to_file) - { - dumper.Open(backend_current_cfg.cfg.ch_cnt, backend_current_cfg.cfg.freq, AudioSampleSize::FLOAT); - } - else - { - dumper.Close(); - } - } - - if (!backend->Operational()) - { - if (!backend_failed) - { - sys_rsxaudio.warning("Backend stopped unexpectedly (likely device change). Attempting to recover..."); - } - - backend_init(ra_state, emu_cfg); - backend_failed = true; - continue; - } - - if (backend_failed) - { - sys_rsxaudio.warning("Backend recovered"); - backend_failed = false; - } - - if (!Emu.IsPausedOrReady() || !use_aux_ringbuf) // Don't pause if thread is in direct mode - { - if (!backend_playing()) - { - backend_start(); - reset_service_time(); - continue; - } - - if (should_service_stream) - { - void* crnt_buf = thread_tmp_buf.data(); - - const u64 bytes_req = ringbuf.get_free_size(); - const u64 bytes_read = aux_ringbuf.pop(crnt_buf, bytes_req, true); - u64 crnt_buf_size = bytes_read; - - if (emu_cfg.enable_time_stretching) - { - const u64 input_ch_cnt = static_cast(ra_state.port[static_cast(emu_cfg.avport)].ch_cnt); - const u64 bytes_per_sample = static_cast(AudioSampleSize::FLOAT) * input_ch_cnt; - const u64 samples_req = bytes_req / bytes_per_sample; - const u64 samples_avail = crnt_buf_size / bytes_per_sample; - const f64 resampler_ratio = resampler.get_resample_ratio(); - f64 fullness_ratio = static_cast(samples_avail + resampler.samples_available()) / samples_req; - - if (fullness_ratio < emu_cfg.time_stretching_threshold) - { - fullness_ratio /= emu_cfg.time_stretching_threshold; - const f64 new_resampler_ratio = (resampler_ratio + fullness_ratio) / 2.0; - if (std::abs(new_resampler_ratio - resampler_ratio) >= TIME_STRETCHING_STEP) - { - resampler.set_tempo(new_resampler_ratio); - } - } - else if (resampler_ratio != RESAMPLER_MAX_FREQ_VAL) - { - resampler.set_tempo(RESAMPLER_MAX_FREQ_VAL); - } - - resampler.put_samples(static_cast(crnt_buf), static_cast(samples_avail)); - const auto [resampled_data, sample_cnt] = resampler.get_samples(static_cast(samples_req)); - crnt_buf = resampled_data; - crnt_buf_size = sample_cnt * bytes_per_sample; - } - - // Dump audio if enabled - if (emu_cfg.dump_to_file) - { - dumper.WriteData(crnt_buf, static_cast(crnt_buf_size)); - } - - ringbuf.push(crnt_buf, crnt_buf_size); - - update_service_time(); - } - } - else - { - if (backend_playing()) - { - backend_stop(); - } - - if (should_service_stream) - { - update_service_time(); - } - } - } -} - -rsxaudio_backend_thread& rsxaudio_backend_thread::operator=(thread_state /* state */) -{ - { - std::lock_guard lock(state_update_m); - } - state_update_c.notify_all(); - return *this; -} - -void rsxaudio_backend_thread::set_new_stream_param(const std::array& cfg, avport_bit muted_avports) -{ - std::unique_lock lock(state_update_m); - - const auto new_mute_state = gen_mute_state(muted_avports); - const bool should_update = backend_current_cfg.cfg != cfg[static_cast(backend_current_cfg.avport)]; - - callback_cfg.atomic_op([&](callback_config& val) - { - val.mute_state = new_mute_state; - - if (should_update) - { - val.ready = false; // Prevent audio playback until backend is reconfigured - val.cfg_changed = true; - } - }); - - if (new_ra_state.port != cfg) - { - new_ra_state.port = cfg; - ra_state_changed = true; - lock.unlock(); - state_update_c.notify_all(); - } -} - -void rsxaudio_backend_thread::set_mute_state(avport_bit muted_avports) -{ - const auto new_mute_state = gen_mute_state(muted_avports); - - callback_cfg.atomic_op([&](callback_config& val) - { - val.mute_state = new_mute_state; - }); -} - -u8 rsxaudio_backend_thread::gen_mute_state(avport_bit avports) -{ - std::bitset mute_state{0}; - - if (avports.hdmi_0) - mute_state[static_cast(RsxaudioAvportIdx::HDMI_0)] = true; - if (avports.hdmi_1) - mute_state[static_cast(RsxaudioAvportIdx::HDMI_1)] = true; - if (avports.avmulti) - mute_state[static_cast(RsxaudioAvportIdx::AVMULTI)] = true; - if (avports.spdif_0) - mute_state[static_cast(RsxaudioAvportIdx::SPDIF_0)] = true; - if (avports.spdif_1) - mute_state[static_cast(RsxaudioAvportIdx::SPDIF_1)] = true; - - return static_cast(mute_state.to_ulong()); -} - -void rsxaudio_backend_thread::add_data(rsxaudio_data_container& cont) -{ - std::unique_lock lock(ringbuf_mutex, std::try_to_lock); - if (!lock.owns_lock()) - { - return; - } - - const callback_config cb_cfg = callback_cfg.observe(); - if (!cb_cfg.ready || !cb_cfg.callback_active) - { - return; - } - - static rsxaudio_data_container::data_blk_t in_data_blk{}; - - if (u32 len = cont.get_data_size(cb_cfg.avport_idx)) - { - if (use_aux_ringbuf) - { - if (aux_ringbuf.get_free_size() >= len) - { - cont.get_data(cb_cfg.avport_idx, in_data_blk); - aux_ringbuf.push(in_data_blk.data(), len); - } - } - else - { - if (ringbuf.get_free_size() >= len) - { - cont.get_data(cb_cfg.avport_idx, in_data_blk); - ringbuf.push(in_data_blk.data(), len); - } - } - } -} - -RsxaudioAvportIdx rsxaudio_backend_thread::convert_avport(audio_avport avport) -{ - switch (avport) - { - case audio_avport::hdmi_0: return RsxaudioAvportIdx::HDMI_0; - case audio_avport::hdmi_1: return RsxaudioAvportIdx::HDMI_1; - case audio_avport::avmulti: return RsxaudioAvportIdx::AVMULTI; - case audio_avport::spdif_0: return RsxaudioAvportIdx::SPDIF_0; - case audio_avport::spdif_1: return RsxaudioAvportIdx::SPDIF_1; - default: - { - fmt::throw_exception("Invalid RSXAudio avport: %u", static_cast(avport)); - } - } -} - -void rsxaudio_backend_thread::backend_init(const rsxaudio_state& ra_state, const emu_audio_cfg& emu_cfg, bool reset_backend) -{ - if (reset_backend || !backend) - { - backend = nullptr; - backend = Emu.GetCallbacks().get_audio(); - backend->SetWriteCallback(std::bind(&rsxaudio_backend_thread::write_data_callback, this, std::placeholders::_1, std::placeholders::_2)); - backend->SetStateCallback(std::bind(&rsxaudio_backend_thread::state_changed_callback, this, std::placeholders::_1)); - } - - const port_config& port_cfg = ra_state.port[static_cast(emu_cfg.avport)]; - const AudioSampleSize sample_size = emu_cfg.convert_to_s16 ? AudioSampleSize::S16 : AudioSampleSize::FLOAT; - const AudioChannelCnt ch_cnt = static_cast(std::min(static_cast(port_cfg.ch_cnt), static_cast(emu_cfg.channels))); - - f64 cb_frame_len = 0.0; - audio_channel_layout backend_channel_layout = audio_channel_layout::stereo; - - if (backend->Open(emu_cfg.audio_device, port_cfg.freq, sample_size, ch_cnt, emu_cfg.channel_layout)) - { - cb_frame_len = backend->GetCallbackFrameLen(); - backend_channel_layout = backend->get_channel_layout(); - sys_rsxaudio.notice("Opened audio backend (sampling_rate=%d, sample_size=%d, channels=%d, layout=%s)", backend->get_sampling_rate(), backend->get_sample_size(), backend->get_channels(), backend->get_channel_layout()); - } - else - { - sys_rsxaudio.error("Failed to open audio backend. Make sure that no other application is running that might block audio access (e.g. Netflix)."); - } - - static constexpr f64 _10ms = 512.0 / 48000.0; - const f64 buffering_len = emu_cfg.buffering_enabled ? (emu_cfg.desired_buffer_duration / 1000.0) : 0.0; - const u64 bytes_per_sec = static_cast(AudioSampleSize::FLOAT) * static_cast(port_cfg.ch_cnt) * static_cast(port_cfg.freq); - - { - std::lock_guard lock(ringbuf_mutex); - use_aux_ringbuf = emu_cfg.enable_time_stretching || emu_cfg.dump_to_file; - - if (use_aux_ringbuf) - { - const f64 frame_len = std::max(buffering_len * 0.5, SERVICE_PERIOD_SEC) + cb_frame_len + _10ms; - const u64 frame_len_bytes = static_cast(std::round(frame_len * bytes_per_sec)); - aux_ringbuf.set_buf_size(frame_len_bytes); - ringbuf.set_buf_size(frame_len_bytes); - thread_tmp_buf.resize(frame_len_bytes); - } - else - { - const f64 frame_len = std::max(buffering_len, cb_frame_len) + _10ms; - ringbuf.set_buf_size(static_cast(std::round(frame_len * bytes_per_sec))); - thread_tmp_buf.resize(0); - } - - callback_tmp_buf.resize(static_cast((cb_frame_len + _10ms) * static_cast(AudioSampleSize::FLOAT) * static_cast(port_cfg.ch_cnt) * static_cast(port_cfg.freq))); - } - - callback_cfg.atomic_op([&](callback_config& val) - { - val.callback_active = false; // Backend may take some time to activate. This prevents overflows on input side. - - if (!val.cfg_changed) - { - val.freq = static_cast(port_cfg.freq); - val.input_ch_cnt = static_cast(port_cfg.ch_cnt); - val.output_channel_layout = static_cast(backend_channel_layout); - val.convert_to_s16 = emu_cfg.convert_to_s16; - val.avport_idx = emu_cfg.avport; - val.ready = true; - } - }); -} - -void rsxaudio_backend_thread::backend_start() -{ - ensure(backend != nullptr); - - if (use_aux_ringbuf) - { - resampler.set_tempo(RESAMPLER_MAX_FREQ_VAL); - resampler.flush(); - aux_ringbuf.reader_flush(); - } - - ringbuf.reader_flush(); - backend->Play(); -} - -void rsxaudio_backend_thread::backend_stop() -{ - if (backend == nullptr) - { - return; - } - - backend->Pause(); - callback_cfg.atomic_op([&](callback_config& val) - { - val.callback_active = false; - }); -} - -bool rsxaudio_backend_thread::backend_playing() -{ - if (backend == nullptr) - { - return false; - } - - return backend->IsPlaying(); -} - -u32 rsxaudio_backend_thread::write_data_callback(u32 bytes, void* buf) -{ - const callback_config cb_cfg = callback_cfg.atomic_op([&](callback_config& val) - { - val.callback_active = true; - return val; - }); - - const std::bitset mute_state{cb_cfg.mute_state}; - - if (cb_cfg.ready && !mute_state[static_cast(cb_cfg.avport_idx)] && Emu.IsRunning()) - { - const audio_channel_layout output_channel_layout = static_cast(cb_cfg.output_channel_layout); - const u32 output_ch_cnt = AudioBackend::default_layout_channel_count(output_channel_layout); - const u32 bytes_ch_adjusted = bytes / output_ch_cnt * cb_cfg.input_ch_cnt; - const u32 bytes_from_rb = cb_cfg.convert_to_s16 ? bytes_ch_adjusted / static_cast(AudioSampleSize::S16) * static_cast(AudioSampleSize::FLOAT) : bytes_ch_adjusted; - - ensure(callback_tmp_buf.size() * static_cast(AudioSampleSize::FLOAT) >= bytes_from_rb); - - const u32 byte_cnt = static_cast(ringbuf.pop(callback_tmp_buf.data(), bytes_from_rb, true)); - const u32 sample_cnt = byte_cnt / static_cast(AudioSampleSize::FLOAT); - const u32 sample_cnt_out = sample_cnt / cb_cfg.input_ch_cnt * output_ch_cnt; - - // Buffer is in weird state - drop acquired data - if (sample_cnt == 0 || sample_cnt % cb_cfg.input_ch_cnt != 0) - { - memset(buf, 0, bytes); - return bytes; - } - - // Record audio if enabled - if (g_recording_mode != recording_mode::stopped) - { - utils::video_provider& provider = g_fxo->get(); - provider.present_samples(reinterpret_cast(callback_tmp_buf.data()), sample_cnt / cb_cfg.input_ch_cnt, cb_cfg.input_ch_cnt); - } - - // Downmix if necessary - AudioBackend::downmix(sample_cnt, cb_cfg.input_ch_cnt, output_channel_layout, callback_tmp_buf.data(), callback_tmp_buf.data()); - - if (cb_cfg.target_volume != cb_cfg.current_volume) - { - const AudioBackend::VolumeParam param = - { - .initial_volume = cb_cfg.initial_volume * callback_config::VOL_NOMINAL_INV, - .current_volume = cb_cfg.current_volume * callback_config::VOL_NOMINAL_INV, - .target_volume = cb_cfg.target_volume * callback_config::VOL_NOMINAL_INV, - .freq = cb_cfg.freq, - .ch_cnt = cb_cfg.input_ch_cnt}; - - const u16 new_vol = static_cast(std::round(AudioBackend::apply_volume(param, sample_cnt_out, callback_tmp_buf.data(), callback_tmp_buf.data()) * callback_config::VOL_NOMINAL)); - callback_cfg.atomic_op([&](callback_config& val) - { - if (val.target_volume != cb_cfg.target_volume) - { - val.initial_volume = new_vol; - } - - // We don't care about proper volume adjustment if underflow has occured - val.current_volume = bytes_from_rb != byte_cnt ? val.target_volume : new_vol; - }); - } - else if (cb_cfg.current_volume != callback_config::VOL_NOMINAL) - { - AudioBackend::apply_volume_static(cb_cfg.current_volume * callback_config::VOL_NOMINAL_INV, sample_cnt_out, callback_tmp_buf.data(), callback_tmp_buf.data()); - } - - if (cb_cfg.convert_to_s16) - { - AudioBackend::convert_to_s16(sample_cnt_out, callback_tmp_buf.data(), buf); - return sample_cnt_out * static_cast(AudioSampleSize::S16); - } - - AudioBackend::normalize(sample_cnt_out, callback_tmp_buf.data(), static_cast(buf)); - return sample_cnt_out * static_cast(AudioSampleSize::FLOAT); - } - - ringbuf.reader_flush(); - memset(buf, 0, bytes); - return bytes; -} - -void rsxaudio_backend_thread::state_changed_callback(AudioStateEvent event) -{ - { - std::lock_guard lock(state_update_m); - switch (event) - { - case AudioStateEvent::UNSPECIFIED_ERROR: - { - backend_error_occured = true; - break; - } - case AudioStateEvent::DEFAULT_DEVICE_MAYBE_CHANGED: - { - backend_device_changed = true; - break; - } - default: - { - fmt::throw_exception("Unknown audio state event"); - } - } - } - state_update_c.notify_all(); -} - -u64 rsxaudio_backend_thread::get_time_until_service() -{ - const u64 next_service_time = start_time + time_period_idx * SERVICE_PERIOD; - const u64 current_time = get_system_time(); - - return next_service_time >= current_time ? next_service_time - current_time : 0; -} - -void rsxaudio_backend_thread::update_service_time() -{ - if (get_time_until_service() <= SERVICE_THRESHOLD) - time_period_idx++; -} - -void rsxaudio_backend_thread::reset_service_time() -{ - start_time = get_system_time(); - time_period_idx = 1; -} - -void rsxaudio_periodic_tmr::sched_timer() -{ - u64 interval = get_rel_next_time(); - - if (interval == 0) - { - zero_period = true; - } - else if (interval == UINT64_MAX) - { - interval = 0; - zero_period = false; - } - else - { - zero_period = false; - } - -#if defined(_WIN32) - if (interval) - { - LARGE_INTEGER due_time{}; - due_time.QuadPart = -static_cast(interval * 10); - ensure(SetWaitableTimerEx(timer_handle, &due_time, 0, nullptr, nullptr, nullptr, 0)); - } - else - { - ensure(CancelWaitableTimer(timer_handle)); - } -#elif defined(__linux__) - const time_t secs = interval / 1'000'000; - const long nsecs = (interval - secs * 1'000'000) * 1000; - const itimerspec tspec = {{}, {secs, nsecs}}; - ensure(timerfd_settime(timer_handle, 0, &tspec, nullptr) == 0); -#elif defined(BSD) || defined(__APPLE__) - handle[TIMER_ID].data = interval * 1000; - if (interval) - { - handle[TIMER_ID].flags = (handle[TIMER_ID].flags & ~EV_DISABLE) | EV_ENABLE; - } - else - { - handle[TIMER_ID].flags = (handle[TIMER_ID].flags & ~EV_ENABLE) | EV_DISABLE; - } - ensure(kevent(kq, &handle[TIMER_ID], 1, nullptr, 0, nullptr) >= 0); -#else -#error "Implement" -#endif -} - -void rsxaudio_periodic_tmr::cancel_timer_unlocked() -{ - zero_period = false; - -#if defined(_WIN32) - ensure(CancelWaitableTimer(timer_handle)); - if (in_wait) - { - ensure(SetEvent(cancel_event)); - } -#elif defined(__linux__) - const itimerspec tspec{}; - ensure(timerfd_settime(timer_handle, 0, &tspec, nullptr) == 0); - if (in_wait) - { - const u64 flag = 1; - const auto wr_res = write(cancel_event, &flag, sizeof(flag)); - ensure(wr_res == sizeof(flag) || wr_res == -EAGAIN); - } -#elif defined(BSD) || defined(__APPLE__) - handle[TIMER_ID].flags = (handle[TIMER_ID].flags & ~EV_ENABLE) | EV_DISABLE; - handle[TIMER_ID].data = 0; - if (in_wait) - { - ensure(kevent(kq, handle, 2, nullptr, 0, nullptr) >= 0); - } - else - { - ensure(kevent(kq, &handle[TIMER_ID], 1, nullptr, 0, nullptr) >= 0); - } -#else -#error "Implement" -#endif -} - -void rsxaudio_periodic_tmr::reset_cancel_flag() -{ -#if defined(_WIN32) - ensure(ResetEvent(cancel_event)); -#elif defined(__linux__) - u64 tmp_buf{}; - [[maybe_unused]] const auto nread = read(cancel_event, &tmp_buf, sizeof(tmp_buf)); -#elif defined(BSD) || defined(__APPLE__) - // Cancel event is reset automatically -#else -#error "Implement" -#endif -} - -rsxaudio_periodic_tmr::rsxaudio_periodic_tmr() -{ -#if defined(_WIN32) - ensure(cancel_event = CreateEvent(nullptr, false, false, nullptr)); - ensure(timer_handle = CreateWaitableTimer(nullptr, false, nullptr)); -#elif defined(__linux__) - timer_handle = timerfd_create(CLOCK_MONOTONIC, 0); - ensure((epoll_fd = epoll_create(2)) >= 0); - epoll_event evnt{EPOLLIN, {}}; - evnt.data.fd = timer_handle; - ensure(timer_handle >= 0 && epoll_ctl(epoll_fd, EPOLL_CTL_ADD, timer_handle, &evnt) == 0); - cancel_event = eventfd(0, EFD_NONBLOCK); - evnt.data.fd = cancel_event; - ensure(cancel_event >= 0 && epoll_ctl(epoll_fd, EPOLL_CTL_ADD, cancel_event, &evnt) == 0); -#elif defined(BSD) || defined(__APPLE__) - -#if defined(__APPLE__) - static constexpr unsigned int TMR_CFG = NOTE_NSECONDS | NOTE_CRITICAL; -#else - static constexpr unsigned int TMR_CFG = NOTE_NSECONDS; -#endif - - ensure((kq = kqueue()) >= 0); - EV_SET(&handle[TIMER_ID], TIMER_ID, EVFILT_TIMER, EV_ADD | EV_ENABLE | EV_ONESHOT, TMR_CFG, 0, nullptr); - EV_SET(&handle[CANCEL_ID], CANCEL_ID, EVFILT_USER, EV_ADD | EV_ENABLE | EV_CLEAR, NOTE_FFNOP, 0, nullptr); - ensure(kevent(kq, &handle[CANCEL_ID], 1, nullptr, 0, nullptr) >= 0); - handle[CANCEL_ID].fflags |= NOTE_TRIGGER; -#else -#error "Implement" -#endif -} - -rsxaudio_periodic_tmr::~rsxaudio_periodic_tmr() -{ -#if defined(_WIN32) - CloseHandle(timer_handle); - CloseHandle(cancel_event); -#elif defined(__linux__) - close(epoll_fd); - close(timer_handle); - close(cancel_event); -#elif defined(BSD) || defined(__APPLE__) - close(kq); -#else -#error "Implement" -#endif -} - -rsxaudio_periodic_tmr::wait_result rsxaudio_periodic_tmr::wait(const std::function& callback) -{ - std::unique_lock lock(mutex); - - if (in_wait || !callback) - { - return wait_result::INVALID_PARAM; - } - - in_wait = true; - - bool tmr_error = false; - bool timeout = false; - bool wait_canceled = false; - - if (!zero_period) - { - lock.unlock(); - constexpr u8 obj_wait_cnt = 2; - -#if defined(_WIN32) - const HANDLE wait_arr[obj_wait_cnt] = {timer_handle, cancel_event}; - const auto wait_status = WaitForMultipleObjects(obj_wait_cnt, wait_arr, false, INFINITE); - - if (wait_status == WAIT_FAILED || (wait_status >= WAIT_ABANDONED_0 && wait_status < WAIT_ABANDONED_0 + obj_wait_cnt)) - { - tmr_error = true; - } - else if (wait_status == WAIT_TIMEOUT) - { - timeout = true; - } - else if (wait_status == WAIT_OBJECT_0 + 1) - { - wait_canceled = true; - } -#elif defined(__linux__) - epoll_event event[obj_wait_cnt]{}; - int wait_status = 0; - do - { - wait_status = epoll_wait(epoll_fd, event, obj_wait_cnt, -1); - } while (wait_status == -1 && errno == EINTR); - - if (wait_status < 0 || wait_status > obj_wait_cnt) - { - tmr_error = true; - } - else if (wait_status == 0) - { - timeout = true; - } - else - { - for (int i = 0; i < wait_status; i++) - { - if (event[i].data.fd == cancel_event) - { - wait_canceled = true; - break; - } - } - } -#elif defined(BSD) || defined(__APPLE__) - struct kevent event[obj_wait_cnt]{}; - int wait_status = 0; - do - { - wait_status = kevent(kq, nullptr, 0, event, obj_wait_cnt, nullptr); - } while (wait_status == -1 && errno == EINTR); - - if (wait_status < 0 || wait_status > obj_wait_cnt) - { - tmr_error = true; - } - else if (wait_status == 0) - { - timeout = true; - } - else - { - for (int i = 0; i < wait_status; i++) - { - if (event[i].ident == CANCEL_ID) - { - wait_canceled = true; - break; - } - } - } -#else -#error "Implement" -#endif - lock.lock(); - } - else - { - zero_period = false; - } - - in_wait = false; - - if (wait_canceled) - { - reset_cancel_flag(); - } - - if (tmr_error) - { - return wait_result::TIMER_ERROR; - } - - if (timeout) - { - return wait_result::TIMEOUT; - } - - if (wait_canceled) - { - sched_timer(); - return wait_result::TIMER_CANCELED; - } - - callback(); - sched_timer(); - return wait_result::SUCCESS; -} - -u64 rsxaudio_periodic_tmr::get_rel_next_time() -{ - const u64 crnt_time = get_system_time(); - u64 next_time = UINT64_MAX; - - for (vtimer& vtimer : vtmr_pool) - { - if (!vtimer.active) - continue; - - u64 next_blk_time = static_cast(vtimer.blk_cnt * vtimer.blk_time); - - if (crnt_time >= next_blk_time) - { - const u64 crnt_blk = get_crnt_blk(crnt_time, vtimer.blk_time); - - if (crnt_blk > vtimer.blk_cnt + MAX_BURST_PERIODS) - { - vtimer.blk_cnt = std::max(vtimer.blk_cnt, crnt_blk - MAX_BURST_PERIODS); - next_blk_time = static_cast(vtimer.blk_cnt * vtimer.blk_time); - } - } - - if (crnt_time >= next_blk_time) - { - next_time = 0; - } - else - { - next_time = std::min(next_time, next_blk_time - crnt_time); - } - } - - return next_time; -} - -void rsxaudio_periodic_tmr::cancel_wait() -{ - std::lock_guard lock(mutex); - cancel_timer_unlocked(); -} - -void rsxaudio_periodic_tmr::enable_vtimer(u32 vtimer_id, u32 rate, u64 crnt_time) -{ - ensure(vtimer_id < VTIMER_MAX && rate); - - vtimer& vtimer = vtmr_pool[vtimer_id]; - const f64 new_blk_time = get_blk_time(rate); - - // Avoid timer reset when possible - if (!vtimer.active || new_blk_time != vtimer.blk_time) - { - vtimer.blk_cnt = get_crnt_blk(crnt_time, new_blk_time); - } - - vtimer.blk_time = new_blk_time; - vtimer.active = true; -} - -void rsxaudio_periodic_tmr::disable_vtimer(u32 vtimer_id) -{ - ensure(vtimer_id < VTIMER_MAX); - - vtimer& vtimer = vtmr_pool[vtimer_id]; - vtimer.active = false; -} - -bool rsxaudio_periodic_tmr::is_vtimer_behind(u32 vtimer_id, u64 crnt_time) const -{ - ensure(vtimer_id < VTIMER_MAX); - - const vtimer& vtimer = vtmr_pool[vtimer_id]; - - return is_vtimer_behind(vtimer, crnt_time); -} - -void rsxaudio_periodic_tmr::vtimer_skip_periods(u32 vtimer_id, u64 crnt_time) -{ - ensure(vtimer_id < VTIMER_MAX); - - vtimer& vtimer = vtmr_pool[vtimer_id]; - - if (is_vtimer_behind(vtimer, crnt_time)) - { - vtimer.blk_cnt = get_crnt_blk(crnt_time, vtimer.blk_time); - } -} - -void rsxaudio_periodic_tmr::vtimer_incr(u32 vtimer_id, u64 crnt_time) -{ - ensure(vtimer_id < VTIMER_MAX); - - vtimer& vtimer = vtmr_pool[vtimer_id]; - - if (is_vtimer_behind(vtimer, crnt_time)) - { - vtimer.blk_cnt++; - } -} - -bool rsxaudio_periodic_tmr::is_vtimer_active(u32 vtimer_id) const -{ - ensure(vtimer_id < VTIMER_MAX); - - const vtimer& vtimer = vtmr_pool[vtimer_id]; - - return vtimer.active; -} - -u64 rsxaudio_periodic_tmr::vtimer_get_sched_time(u32 vtimer_id) const -{ - ensure(vtimer_id < VTIMER_MAX); - - const vtimer& vtimer = vtmr_pool[vtimer_id]; - - return static_cast(vtimer.blk_cnt * vtimer.blk_time); -} - -bool rsxaudio_periodic_tmr::is_vtimer_behind(const vtimer& vtimer, u64 crnt_time) const -{ - return vtimer.active && vtimer.blk_cnt < get_crnt_blk(crnt_time, vtimer.blk_time); -} - -u64 rsxaudio_periodic_tmr::get_crnt_blk(u64 crnt_time, f64 blk_time) const -{ - return static_cast(std::floor(static_cast(crnt_time) / blk_time)) + 1; -} - -f64 rsxaudio_periodic_tmr::get_blk_time(u32 data_rate) const -{ - return static_cast(SYS_RSXAUDIO_STREAM_SIZE * 1'000'000) / data_rate; -} diff --git a/rpcs3/Emu/Cell/lv2/sys_rsxaudio.h b/rpcs3/Emu/Cell/lv2/sys_rsxaudio.h deleted file mode 100644 index 766817409..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_rsxaudio.h +++ /dev/null @@ -1,635 +0,0 @@ -#pragma once - -#include "sys_sync.h" -#include "sys_event.h" -#include "util/simple_ringbuf.h" -#include "util/transactional_storage.h" -#include "util/cond.h" -#include "Emu/system_config_types.h" -#include "Emu/Memory/vm_ptr.h" -#include "Emu/Cell/ErrorCodes.h" -#include "Emu/Audio/AudioDumper.h" -#include "Emu/Audio/AudioBackend.h" -#include "Emu/Audio/audio_resampler.h" - -#if defined(unix) || defined(__unix) || defined(__unix__) -// For BSD detection -#include -#endif - -#ifdef _WIN32 -#include -#elif defined(BSD) || defined(__APPLE__) -#include -#endif - -enum : u32 -{ - SYS_RSXAUDIO_SERIAL_STREAM_CNT = 4, - SYS_RSXAUDIO_STREAM_DATA_BLK_CNT = 4, - SYS_RSXAUDIO_DATA_BLK_SIZE = 256, - SYS_RSXAUDIO_STREAM_SIZE = SYS_RSXAUDIO_DATA_BLK_SIZE * SYS_RSXAUDIO_STREAM_DATA_BLK_CNT, - SYS_RSXAUDIO_CH_PER_STREAM = 2, - SYS_RSXAUDIO_SERIAL_MAX_CH = 8, - SYS_RSXAUDIO_SPDIF_MAX_CH = 2, - SYS_RSXAUDIO_STREAM_SAMPLE_CNT = SYS_RSXAUDIO_STREAM_SIZE / SYS_RSXAUDIO_CH_PER_STREAM / sizeof(f32), - - SYS_RSXAUDIO_RINGBUF_BLK_SZ_SERIAL = SYS_RSXAUDIO_STREAM_SIZE * SYS_RSXAUDIO_SERIAL_STREAM_CNT, - SYS_RSXAUDIO_RINGBUF_BLK_SZ_SPDIF = SYS_RSXAUDIO_STREAM_SIZE, - - SYS_RSXAUDIO_RINGBUF_SZ = 16, - - SYS_RSXAUDIO_AVPORT_CNT = 5, - - SYS_RSXAUDIO_FREQ_BASE_384K = 384000, - SYS_RSXAUDIO_FREQ_BASE_352K = 352800, - - SYS_RSXAUDIO_PORT_CNT = 3, - - SYS_RSXAUDIO_SPDIF_CNT = 2, -}; - -enum class RsxaudioAvportIdx : u8 -{ - HDMI_0 = 0, - HDMI_1 = 1, - AVMULTI = 2, - SPDIF_0 = 3, - SPDIF_1 = 4, -}; - -enum class RsxaudioPort : u8 -{ - SERIAL = 0, - SPDIF_0 = 1, - SPDIF_1 = 2, - INVALID = 0xFF, -}; - -enum class RsxaudioSampleSize : u8 -{ - _16BIT = 2, - _32BIT = 4, -}; - -struct rsxaudio_shmem -{ - struct ringbuf_t - { - struct entry_t - { - be_t valid{}; - be_t unk1{}; - be_t audio_blk_idx{}; - be_t timestamp{}; - be_t buf_addr{}; - be_t dma_addr{}; - }; - - be_t active{}; - be_t unk2{}; - be_t read_idx{}; - be_t write_idx{}; - be_t rw_max_idx{}; - be_t queue_notify_idx{}; - be_t queue_notify_step{}; - be_t unk6{}; - be_t dma_silence_addr{}; - be_t unk7{}; - be_t next_blk_idx{}; - - entry_t entries[16]{}; - }; - - struct uf_event_t - { - be_t unk1{}; - be_t uf_event_cnt{}; - u8 unk2[244]{}; - }; - - struct ctrl_t - { - ringbuf_t ringbuf[SYS_RSXAUDIO_PORT_CNT]{}; - - be_t unk1{}; - be_t event_queue_1_id{}; - u8 unk2[16]{}; - be_t event_queue_2_id{}; - be_t spdif_ch0_channel_data_lo{}; - be_t spdif_ch0_channel_data_hi{}; - be_t spdif_ch0_channel_data_tx_cycles{}; - be_t unk3{}; - be_t event_queue_3_id{}; - be_t spdif_ch1_channel_data_lo{}; - be_t spdif_ch1_channel_data_hi{}; - be_t spdif_ch1_channel_data_tx_cycles{}; - be_t unk4{}; - be_t intr_thread_prio{}; - be_t unk5{}; - u8 unk6[248]{}; - uf_event_t channel_uf[SYS_RSXAUDIO_PORT_CNT]{}; - u8 pad[0x3530]{}; - }; - - u8 dma_serial_region[0x10000]{}; - u8 dma_spdif_0_region[0x4000]{}; - u8 dma_spdif_1_region[0x4000]{}; - u8 dma_silence_region[0x4000]{}; - ctrl_t ctrl{}; -}; - -static_assert(sizeof(rsxaudio_shmem::ringbuf_t) == 0x230U, "rsxAudioRingBufSizeTest"); -static_assert(sizeof(rsxaudio_shmem::uf_event_t) == 0x100U, "rsxAudioUfEventTest"); -static_assert(sizeof(rsxaudio_shmem::ctrl_t) == 0x4000U, "rsxAudioCtrlSizeTest"); -static_assert(sizeof(rsxaudio_shmem) == 0x20000U, "rsxAudioShmemSizeTest"); - -enum rsxaudio_dma_flag : u32 -{ - IO_BASE = 0, - IO_ID = 1 -}; - -struct lv2_rsxaudio final : lv2_obj -{ - static constexpr u32 id_base = 0x60000000; - static constexpr u64 dma_io_id = 1; - static constexpr u32 dma_io_base = 0x30000000; - - shared_mutex mutex{}; - bool init = false; - - vm::addr_t shmem{}; - - std::array, SYS_RSXAUDIO_PORT_CNT> event_queue{}; - - // lv2 uses port memory addresses for their names - static constexpr std::array event_port_name{0x8000000000400100, 0x8000000000400200, 0x8000000000400300}; - - lv2_rsxaudio() noexcept = default; - lv2_rsxaudio(utils::serial& ar) noexcept; - void save(utils::serial& ar); - - void page_lock() - { - ensure(shmem && vm::page_protect(shmem, sizeof(rsxaudio_shmem), 0, 0, vm::page_readable | vm::page_writable | vm::page_executable)); - } - - void page_unlock() - { - ensure(shmem && vm::page_protect(shmem, sizeof(rsxaudio_shmem), 0, vm::page_readable | vm::page_writable)); - } - - rsxaudio_shmem* get_rw_shared_page() const - { - return reinterpret_cast(vm::g_sudo_addr + u32{shmem}); - } -}; - -class rsxaudio_periodic_tmr -{ -public: - enum class wait_result - { - SUCCESS, - INVALID_PARAM, - TIMEOUT, - TIMER_ERROR, - TIMER_CANCELED, - }; - - rsxaudio_periodic_tmr(); - ~rsxaudio_periodic_tmr(); - - rsxaudio_periodic_tmr(const rsxaudio_periodic_tmr&) = delete; - rsxaudio_periodic_tmr& operator=(const rsxaudio_periodic_tmr&) = delete; - - // Wait until timer fires and calls callback. - wait_result wait(const std::function& callback); - - // Cancel wait() call - void cancel_wait(); - - // VTimer funtions - - void vtimer_access_sec(std::invocable<> auto func) - { - std::lock_guard lock(mutex); - std::invoke(func); - - // Adjust timer expiration - cancel_timer_unlocked(); - sched_timer(); - } - - void enable_vtimer(u32 vtimer_id, u32 rate, u64 crnt_time); - - void disable_vtimer(u32 vtimer_id); - - bool is_vtimer_behind(u32 vtimer_id, u64 crnt_time) const; - - void vtimer_skip_periods(u32 vtimer_id, u64 crnt_time); - - void vtimer_incr(u32 vtimer_id, u64 crnt_time); - - bool is_vtimer_active(u32 vtimer_id) const; - - u64 vtimer_get_sched_time(u32 vtimer_id) const; - -private: - static constexpr u64 MAX_BURST_PERIODS = SYS_RSXAUDIO_RINGBUF_SZ; - static constexpr u32 VTIMER_MAX = 4; - - struct vtimer - { - u64 blk_cnt = 0; - f64 blk_time = 0.0; - bool active = false; - }; - - std::array vtmr_pool{}; - - shared_mutex mutex{}; - bool in_wait = false; - bool zero_period = false; - -#if defined(_WIN32) - HANDLE cancel_event{}; - HANDLE timer_handle{}; -#elif defined(__linux__) - int cancel_event{}; - int timer_handle{}; - int epoll_fd{}; -#elif defined(BSD) || defined(__APPLE__) - static constexpr u64 TIMER_ID = 0; - static constexpr u64 CANCEL_ID = 1; - - int kq{}; - struct kevent handle[2]{}; -#else -#error "Implement" -#endif - - void sched_timer(); - void cancel_timer_unlocked(); - void reset_cancel_flag(); - - bool is_vtimer_behind(const vtimer& vtimer, u64 crnt_time) const; - - u64 get_crnt_blk(u64 crnt_time, f64 blk_time) const; - f64 get_blk_time(u32 data_rate) const; - - u64 get_rel_next_time(); -}; - -struct rsxaudio_hw_param_t -{ - struct serial_param_t - { - bool dma_en = false; - bool buf_empty_en = false; - bool muted = true; - bool en = false; - u8 freq_div = 8; - RsxaudioSampleSize depth = RsxaudioSampleSize::_16BIT; - }; - - struct spdif_param_t - { - bool dma_en = false; - bool buf_empty_en = false; - bool muted = true; - bool en = false; - bool use_serial_buf = true; - u8 freq_div = 8; - RsxaudioSampleSize depth = RsxaudioSampleSize::_16BIT; - std::array cs_data = {0x00, 0x90, 0x00, 0x40, 0x80, 0x00}; // HW supports only 6 bytes (uart pkt has 8) - }; - - struct hdmi_param_t - { - struct hdmi_ch_cfg_t - { - std::array map{}; - AudioChannelCnt total_ch_cnt = AudioChannelCnt::STEREO; - }; - - static constexpr u8 MAP_SILENT_CH = umax; - - bool init = false; - hdmi_ch_cfg_t ch_cfg{}; - std::array info_frame{}; // TODO: check chstat and info_frame for info on audio layout, add default values - std::array chstat{}; - - bool muted = true; - bool force_mute = true; - bool use_spdif_1 = false; // TODO: unused for now - }; - - u32 serial_freq_base = SYS_RSXAUDIO_FREQ_BASE_384K; - u32 spdif_freq_base = SYS_RSXAUDIO_FREQ_BASE_352K; - - bool avmulti_av_muted = true; - - serial_param_t serial{}; - spdif_param_t spdif[2]{}; - hdmi_param_t hdmi[2]{}; - - std::array avport_src = - { - RsxaudioPort::INVALID, - RsxaudioPort::INVALID, - RsxaudioPort::INVALID, - RsxaudioPort::INVALID, - RsxaudioPort::INVALID}; -}; - -// 16-bit PCM converted into float, so buffer must be twice as big -using ra_stream_blk_t = std::array; - -class rsxaudio_data_container -{ -public: - struct buf_t - { - std::array serial{}; - std::array spdif[SYS_RSXAUDIO_SPDIF_CNT]{}; - }; - - using data_blk_t = std::array; - - rsxaudio_data_container(const rsxaudio_hw_param_t& hw_param, const buf_t& buf, bool serial_rdy, bool spdif_0_rdy, bool spdif_1_rdy); - u32 get_data_size(RsxaudioAvportIdx avport); - void get_data(RsxaudioAvportIdx avport, data_blk_t& data_out); - bool data_was_used(); - -private: - const rsxaudio_hw_param_t& hwp; - const buf_t& out_buf; - - std::array avport_data_avail{}; - u8 hdmi_stream_cnt[2]{}; - bool data_was_written = false; - - rsxaudio_data_container(const rsxaudio_data_container&) = delete; - rsxaudio_data_container& operator=(const rsxaudio_data_container&) = delete; - - rsxaudio_data_container(rsxaudio_data_container&&) = delete; - rsxaudio_data_container& operator=(rsxaudio_data_container&&) = delete; - - // Mix individual channels into final PCM stream. Channels in channel map that are > input_ch_cnt treated as silent. - template - requires(output_ch_cnt > 0 && output_ch_cnt <= 8 && input_ch_cnt > 0) - constexpr void mix(const std::array& ch_map, RsxaudioSampleSize sample_size, const std::array& input_channels, data_blk_t& data_out) - { - const ra_stream_blk_t silent_channel{}; - - // Build final map - std::array real_input_ch = {}; - for (u64 ch_idx = 0; ch_idx < output_ch_cnt; ch_idx++) - { - if (ch_map[ch_idx] >= input_ch_cnt) - { - real_input_ch[ch_idx] = &silent_channel; - } - else - { - real_input_ch[ch_idx] = &input_channels[ch_map[ch_idx]]; - } - } - - const u32 samples_in_buf = sample_size == RsxaudioSampleSize::_16BIT ? SYS_RSXAUDIO_STREAM_SAMPLE_CNT * 2 : SYS_RSXAUDIO_STREAM_SAMPLE_CNT; - - for (u32 sample_idx = 0; sample_idx < samples_in_buf * output_ch_cnt; sample_idx += output_ch_cnt) - { - const u32 src_sample_idx = sample_idx / output_ch_cnt; - - if constexpr (output_ch_cnt >= 1) - data_out[sample_idx + 0] = (*real_input_ch[0])[src_sample_idx]; - if constexpr (output_ch_cnt >= 2) - data_out[sample_idx + 1] = (*real_input_ch[1])[src_sample_idx]; - if constexpr (output_ch_cnt >= 3) - data_out[sample_idx + 2] = (*real_input_ch[2])[src_sample_idx]; - if constexpr (output_ch_cnt >= 4) - data_out[sample_idx + 3] = (*real_input_ch[3])[src_sample_idx]; - if constexpr (output_ch_cnt >= 5) - data_out[sample_idx + 4] = (*real_input_ch[4])[src_sample_idx]; - if constexpr (output_ch_cnt >= 6) - data_out[sample_idx + 5] = (*real_input_ch[5])[src_sample_idx]; - if constexpr (output_ch_cnt >= 7) - data_out[sample_idx + 6] = (*real_input_ch[6])[src_sample_idx]; - if constexpr (output_ch_cnt >= 8) - data_out[sample_idx + 7] = (*real_input_ch[7])[src_sample_idx]; - } - } -}; - -namespace audio -{ - void configure_rsxaudio(); -} - -class rsxaudio_backend_thread -{ -public: - struct port_config - { - AudioFreq freq = AudioFreq::FREQ_48K; - AudioChannelCnt ch_cnt = AudioChannelCnt::STEREO; - - auto operator<=>(const port_config&) const = default; - }; - - struct avport_bit - { - bool hdmi_0 : 1; - bool hdmi_1 : 1; - bool avmulti : 1; - bool spdif_0 : 1; - bool spdif_1 : 1; - }; - - rsxaudio_backend_thread(); - ~rsxaudio_backend_thread(); - - void operator()(); - rsxaudio_backend_thread& operator=(thread_state state); - - void set_new_stream_param(const std::array& cfg, avport_bit muted_avports); - void set_mute_state(avport_bit muted_avports); - void add_data(rsxaudio_data_container& cont); - - void update_emu_cfg(); - - u32 get_sample_rate() const; - u8 get_channel_count() const; - - static constexpr auto thread_name = "RsxAudio Backend Thread"sv; - - SAVESTATE_INIT_POS(8.91); // Depends on audio_out_configuration - -private: - struct emu_audio_cfg - { - std::string audio_device{}; - s64 desired_buffer_duration = 0; - f64 time_stretching_threshold = 0; - bool buffering_enabled = false; - bool convert_to_s16 = false; - bool enable_time_stretching = false; - bool dump_to_file = false; - AudioChannelCnt channels = AudioChannelCnt::STEREO; - audio_channel_layout channel_layout = audio_channel_layout::automatic; - audio_renderer renderer = audio_renderer::null; - audio_provider provider = audio_provider::none; - RsxaudioAvportIdx avport = RsxaudioAvportIdx::HDMI_0; - - auto operator<=>(const emu_audio_cfg&) const = default; - }; - - struct rsxaudio_state - { - std::array port{}; - }; - - struct alignas(16) callback_config - { - static constexpr u16 VOL_NOMINAL = 10000; - static constexpr f32 VOL_NOMINAL_INV = 1.0f / VOL_NOMINAL; - - u32 freq : 20 = 48000; - - u16 target_volume = 10000; - u16 initial_volume = 10000; - u16 current_volume = 10000; - - RsxaudioAvportIdx avport_idx = RsxaudioAvportIdx::HDMI_0; - u8 mute_state : SYS_RSXAUDIO_AVPORT_CNT = 0b11111; - - u8 input_ch_cnt : 4 = 2; - u8 output_channel_layout : 4 = static_cast(audio_channel_layout::stereo); - - bool ready : 1 = false; - bool convert_to_s16 : 1 = false; - bool cfg_changed : 1 = false; - bool callback_active : 1 = false; - }; - - static_assert(sizeof(callback_config) <= 16); - - struct backend_config - { - port_config cfg{}; - RsxaudioAvportIdx avport = RsxaudioAvportIdx::HDMI_0; - }; - - static constexpr u64 ERROR_SERVICE_PERIOD = 500'000; - static constexpr u64 SERVICE_PERIOD = 10'000; - static constexpr f64 SERVICE_PERIOD_SEC = SERVICE_PERIOD / 1'000'000.0; - static constexpr u64 SERVICE_THRESHOLD = 1'500; - - static constexpr f64 TIME_STRETCHING_STEP = 0.1f; - - u64 start_time = get_system_time(); - u64 time_period_idx = 1; - - emu_audio_cfg new_emu_cfg{}; - bool emu_cfg_changed = true; - - rsxaudio_state new_ra_state{}; - bool ra_state_changed = true; - - shared_mutex state_update_m{}; - cond_variable state_update_c{}; - - simple_ringbuf ringbuf{}; - simple_ringbuf aux_ringbuf{}; - std::vector thread_tmp_buf{}; - std::vector callback_tmp_buf{}; - bool use_aux_ringbuf = false; - shared_mutex ringbuf_mutex{}; - - std::shared_ptr backend{}; - backend_config backend_current_cfg{{}, new_emu_cfg.avport}; - atomic_t callback_cfg{}; - bool backend_error_occured = false; - bool backend_device_changed = false; - - AudioDumper dumper{}; - audio_resampler resampler{}; - - // Backend - void backend_init(const rsxaudio_state& ra_state, const emu_audio_cfg& emu_cfg, bool reset_backend = true); - void backend_start(); - void backend_stop(); - bool backend_playing(); - u32 write_data_callback(u32 bytes, void* buf); - void state_changed_callback(AudioStateEvent event); - - // Time management - u64 get_time_until_service(); - void update_service_time(); - void reset_service_time(); - - // Helpers - static emu_audio_cfg get_emu_cfg(); - static u8 gen_mute_state(avport_bit avports); - static RsxaudioAvportIdx convert_avport(audio_avport avport); -}; - -class rsxaudio_data_thread -{ -public: - // Prevent creation of multiple rsxaudio contexts - atomic_t rsxaudio_ctx_allocated = false; - - shared_mutex rsxaudio_obj_upd_m{}; - shared_ptr rsxaudio_obj_ptr{}; - - void operator()(); - rsxaudio_data_thread& operator=(thread_state state); - - rsxaudio_data_thread(); - - void update_hw_param(std::function update_callback); - void update_mute_state(RsxaudioPort port, bool muted); - void update_av_mute_state(RsxaudioAvportIdx avport, bool muted, bool force_mute, bool set = true); - void reset_hw(); - - static constexpr auto thread_name = "RsxAudioData Thread"sv; - -private: - rsxaudio_data_container::buf_t output_buf{}; - - transactional_storage hw_param_ts{std::make_shared(), std::make_shared()}; - rsxaudio_periodic_tmr timer{}; - - void advance_all_timers(); - void extract_audio_data(); - static std::pair get_ringbuf_addr(RsxaudioPort dst, const lv2_rsxaudio& rsxaudio_obj); - - static f32 pcm_to_float(s32 sample); - static f32 pcm_to_float(s16 sample); - static void pcm_serial_process_channel(RsxaudioSampleSize word_bits, ra_stream_blk_t& buf_out_l, ra_stream_blk_t& buf_out_r, const void* buf_in, u8 src_stream); - static void pcm_spdif_process_channel(RsxaudioSampleSize word_bits, ra_stream_blk_t& buf_out_l, ra_stream_blk_t& buf_out_r, const void* buf_in); - bool enqueue_data(RsxaudioPort dst, bool silence, const void* src_addr, const rsxaudio_hw_param_t& hwp); - - static rsxaudio_backend_thread::avport_bit calc_avport_mute_state(const rsxaudio_hw_param_t& hwp); - static bool calc_port_active_state(RsxaudioPort port, const rsxaudio_hw_param_t& hwp); -}; - -using rsx_audio_backend = named_thread; -using rsx_audio_data = named_thread; - -// SysCalls - -error_code sys_rsxaudio_initialize(vm::ptr handle); -error_code sys_rsxaudio_finalize(u32 handle); -error_code sys_rsxaudio_import_shared_memory(u32 handle, vm::ptr addr); -error_code sys_rsxaudio_unimport_shared_memory(u32 handle, vm::ptr addr); -error_code sys_rsxaudio_create_connection(u32 handle); -error_code sys_rsxaudio_close_connection(u32 handle); -error_code sys_rsxaudio_prepare_process(u32 handle); -error_code sys_rsxaudio_start_process(u32 handle); -error_code sys_rsxaudio_stop_process(u32 handle); -error_code sys_rsxaudio_get_dma_param(u32 handle, u32 flag, vm::ptr out); diff --git a/rpcs3/Emu/Cell/lv2/sys_rwlock.cpp b/rpcs3/Emu/Cell/lv2/sys_rwlock.cpp deleted file mode 100644 index da47702cb..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_rwlock.cpp +++ /dev/null @@ -1,589 +0,0 @@ -#include "stdafx.h" -#include "sys_rwlock.h" - -#include "Emu/IdManager.h" - -#include "Emu/Cell/ErrorCodes.h" -#include "Emu/Cell/PPUThread.h" - -#include "util/asm.hpp" - -LOG_CHANNEL(sys_rwlock); - -lv2_rwlock::lv2_rwlock(utils::serial& ar) - : protocol(ar), key(ar), name(ar) -{ - ar(owner); -} - -std::function lv2_rwlock::load(utils::serial& ar) -{ - return load_func(make_shared(exact_t(ar))); -} - -void lv2_rwlock::save(utils::serial& ar) -{ - USING_SERIALIZATION_VERSION(lv2_sync); - ar(protocol, key, name, owner); -} - -error_code sys_rwlock_create(ppu_thread& ppu, vm::ptr rw_lock_id, vm::ptr attr) -{ - ppu.state += cpu_flag::wait; - - sys_rwlock.warning("sys_rwlock_create(rw_lock_id=*0x%x, attr=*0x%x)", rw_lock_id, attr); - - if (!rw_lock_id || !attr) - { - return CELL_EFAULT; - } - - const auto _attr = *attr; - - const u32 protocol = _attr.protocol; - - if (protocol != SYS_SYNC_FIFO && protocol != SYS_SYNC_PRIORITY) - { - sys_rwlock.error("sys_rwlock_create(): unknown protocol (0x%x)", protocol); - return CELL_EINVAL; - } - - const u64 ipc_key = lv2_obj::get_key(_attr); - - if (auto error = lv2_obj::create(_attr.pshared, ipc_key, _attr.flags, [&] - { - return make_shared(protocol, ipc_key, _attr.name_u64); - })) - { - return error; - } - - ppu.check_state(); - *rw_lock_id = idm::last_id(); - return CELL_OK; -} - -error_code sys_rwlock_destroy(ppu_thread& ppu, u32 rw_lock_id) -{ - ppu.state += cpu_flag::wait; - - sys_rwlock.warning("sys_rwlock_destroy(rw_lock_id=0x%x)", rw_lock_id); - - const auto rwlock = idm::withdraw(rw_lock_id, [](lv2_rwlock& rw) -> CellError - { - if (rw.owner) - { - return CELL_EBUSY; - } - - lv2_obj::on_id_destroy(rw, rw.key); - return {}; - }); - - if (!rwlock) - { - return CELL_ESRCH; - } - - if (rwlock.ret) - { - return rwlock.ret; - } - - return CELL_OK; -} - -error_code sys_rwlock_rlock(ppu_thread& ppu, u32 rw_lock_id, u64 timeout) -{ - ppu.state += cpu_flag::wait; - - sys_rwlock.trace("sys_rwlock_rlock(rw_lock_id=0x%x, timeout=0x%llx)", rw_lock_id, timeout); - - const auto rwlock = idm::get(rw_lock_id, [&, notify = lv2_obj::notify_all_t()](lv2_rwlock& rwlock) - { - const s64 val = rwlock.owner; - - if (val <= 0 && !(val & 1)) - { - if (rwlock.owner.compare_and_swap_test(val, val - 2)) - { - return true; - } - } - - lv2_obj::prepare_for_sleep(ppu); - - std::lock_guard lock(rwlock.mutex); - - const s64 _old = rwlock.owner.fetch_op([&](s64& val) - { - if (val <= 0 && !(val & 1)) - { - val -= 2; - } - else - { - val |= 1; - } - }); - - if (_old > 0 || _old & 1) - { - rwlock.sleep(ppu, timeout); - lv2_obj::emplace(rwlock.rq, &ppu); - return false; - } - - return true; - }); - - if (!rwlock) - { - return CELL_ESRCH; - } - - if (rwlock.ret) - { - return CELL_OK; - } - - ppu.gpr[3] = CELL_OK; - - while (auto state = +ppu.state) - { - if (state & cpu_flag::signal && ppu.state.test_and_reset(cpu_flag::signal)) - { - break; - } - - if (is_stopped(state)) - { - std::lock_guard lock(rwlock->mutex); - - for (auto cpu = +rwlock->rq; cpu; cpu = cpu->next_cpu) - { - if (cpu == &ppu) - { - ppu.state += cpu_flag::again; - return {}; - } - } - - break; - } - - for (usz i = 0; cpu_flag::signal - ppu.state && i < 50; i++) - { - busy_wait(500); - } - - if (ppu.state & cpu_flag::signal) - { - continue; - } - - if (timeout) - { - if (lv2_obj::wait_timeout(timeout, &ppu)) - { - // Wait for rescheduling - if (ppu.check_state()) - { - continue; - } - - ppu.state += cpu_flag::wait; - - if (!atomic_storage::load(rwlock->rq)) - { - // Waiters queue is empty, so the thread must have been signaled - rwlock->mutex.lock_unlock(); - break; - } - - std::lock_guard lock(rwlock->mutex); - - if (!rwlock->unqueue(rwlock->rq, &ppu)) - { - break; - } - - ppu.gpr[3] = CELL_ETIMEDOUT; - break; - } - } - else - { - ppu.state.wait(state); - } - } - - return not_an_error(ppu.gpr[3]); -} - -error_code sys_rwlock_tryrlock(ppu_thread& ppu, u32 rw_lock_id) -{ - ppu.state += cpu_flag::wait; - - sys_rwlock.trace("sys_rwlock_tryrlock(rw_lock_id=0x%x)", rw_lock_id); - - const auto rwlock = idm::check(rw_lock_id, [](lv2_rwlock& rwlock) - { - auto [_, ok] = rwlock.owner.fetch_op([](s64& val) - { - if (val <= 0 && !(val & 1)) - { - val -= 2; - return true; - } - - return false; - }); - - return ok; - }); - - if (!rwlock) - { - return CELL_ESRCH; - } - - if (!rwlock.ret) - { - return not_an_error(CELL_EBUSY); - } - - return CELL_OK; -} - -error_code sys_rwlock_runlock(ppu_thread& ppu, u32 rw_lock_id) -{ - ppu.state += cpu_flag::wait; - - sys_rwlock.trace("sys_rwlock_runlock(rw_lock_id=0x%x)", rw_lock_id); - - const auto rwlock = idm::get(rw_lock_id, [](lv2_rwlock& rwlock) - { - const s64 val = rwlock.owner; - - if (val < 0 && !(val & 1)) - { - if (rwlock.owner.compare_and_swap_test(val, val + 2)) - { - return true; - } - } - - return false; - }); - - if (!rwlock) - { - return CELL_ESRCH; - } - - lv2_obj::notify_all_t notify; - - if (rwlock.ret) - { - return CELL_OK; - } - else - { - std::lock_guard lock(rwlock->mutex); - - // Remove one reader - const s64 _old = rwlock->owner.fetch_op([](s64& val) - { - if (val < -1) - { - val += 2; - } - }); - - if (_old >= 0) - { - return CELL_EPERM; - } - - if (_old == -1) - { - if (const auto cpu = rwlock->schedule(rwlock->wq, rwlock->protocol)) - { - if (static_cast(cpu)->state & cpu_flag::again) - { - ppu.state += cpu_flag::again; - return {}; - } - - rwlock->owner = cpu->id << 1 | !!rwlock->wq | !!rwlock->rq; - - rwlock->awake(cpu); - } - else - { - rwlock->owner = 0; - - ensure(!rwlock->rq); - } - } - } - - return CELL_OK; -} - -error_code sys_rwlock_wlock(ppu_thread& ppu, u32 rw_lock_id, u64 timeout) -{ - ppu.state += cpu_flag::wait; - - sys_rwlock.trace("sys_rwlock_wlock(rw_lock_id=0x%x, timeout=0x%llx)", rw_lock_id, timeout); - - const auto rwlock = idm::get(rw_lock_id, [&, notify = lv2_obj::notify_all_t()](lv2_rwlock& rwlock) -> s64 - { - const s64 val = rwlock.owner; - - if (val == 0) - { - if (rwlock.owner.compare_and_swap_test(0, ppu.id << 1)) - { - return 0; - } - } - else if (val >> 1 == ppu.id) - { - return val; - } - - lv2_obj::prepare_for_sleep(ppu); - - std::lock_guard lock(rwlock.mutex); - - const s64 _old = rwlock.owner.fetch_op([&](s64& val) - { - if (val == 0) - { - val = ppu.id << 1; - } - else - { - val |= 1; - } - }); - - if (_old != 0) - { - rwlock.sleep(ppu, timeout); - lv2_obj::emplace(rwlock.wq, &ppu); - } - - return _old; - }); - - if (!rwlock) - { - return CELL_ESRCH; - } - - if (rwlock.ret == 0) - { - return CELL_OK; - } - - if (rwlock.ret >> 1 == ppu.id) - { - return CELL_EDEADLK; - } - - ppu.gpr[3] = CELL_OK; - - while (auto state = +ppu.state) - { - if (state & cpu_flag::signal && ppu.state.test_and_reset(cpu_flag::signal)) - { - break; - } - - if (is_stopped(state)) - { - std::lock_guard lock(rwlock->mutex); - - for (auto cpu = +rwlock->wq; cpu; cpu = cpu->next_cpu) - { - if (cpu == &ppu) - { - ppu.state += cpu_flag::again; - return {}; - } - } - - break; - } - - for (usz i = 0; cpu_flag::signal - ppu.state && i < 50; i++) - { - busy_wait(500); - } - - if (ppu.state & cpu_flag::signal) - { - continue; - } - - if (timeout) - { - if (lv2_obj::wait_timeout(timeout, &ppu)) - { - // Wait for rescheduling - if (ppu.check_state()) - { - continue; - } - - std::lock_guard lock(rwlock->mutex); - - if (!rwlock->unqueue(rwlock->wq, &ppu)) - { - break; - } - - // If the last waiter quit the writer sleep queue, wake blocked readers - if (rwlock->rq && !rwlock->wq && rwlock->owner < 0) - { - s64 size = 0; - - // Protocol doesn't matter here since they are all enqueued anyways - while (auto cpu = rwlock->schedule(rwlock->rq, SYS_SYNC_FIFO)) - { - size++; - rwlock->append(cpu); - } - - rwlock->owner.atomic_op([&](s64& owner) - { - owner -= 2 * size; // Add readers to value - owner &= -2; // Clear wait bit - }); - - lv2_obj::awake_all(); - } - else if (!rwlock->rq && !rwlock->wq) - { - rwlock->owner &= -2; - } - - ppu.gpr[3] = CELL_ETIMEDOUT; - break; - } - } - else - { - ppu.state.wait(state); - } - } - - return not_an_error(ppu.gpr[3]); -} - -error_code sys_rwlock_trywlock(ppu_thread& ppu, u32 rw_lock_id) -{ - ppu.state += cpu_flag::wait; - - sys_rwlock.trace("sys_rwlock_trywlock(rw_lock_id=0x%x)", rw_lock_id); - - const auto rwlock = idm::check(rw_lock_id, [&](lv2_rwlock& rwlock) - { - const s64 val = rwlock.owner; - - // Return previous value - return val ? val : rwlock.owner.compare_and_swap(0, ppu.id << 1); - }); - - if (!rwlock) - { - return CELL_ESRCH; - } - - if (rwlock.ret != 0) - { - if (rwlock.ret >> 1 == ppu.id) - { - return CELL_EDEADLK; - } - - return not_an_error(CELL_EBUSY); - } - - return CELL_OK; -} - -error_code sys_rwlock_wunlock(ppu_thread& ppu, u32 rw_lock_id) -{ - ppu.state += cpu_flag::wait; - - sys_rwlock.trace("sys_rwlock_wunlock(rw_lock_id=0x%x)", rw_lock_id); - - const auto rwlock = idm::get(rw_lock_id, [&](lv2_rwlock& rwlock) - { - const s64 val = rwlock.owner; - - // Return previous value - return val != ppu.id << 1 ? val : rwlock.owner.compare_and_swap(val, 0); - }); - - if (!rwlock) - { - return CELL_ESRCH; - } - - if (rwlock.ret >> 1 != ppu.id) - { - return CELL_EPERM; - } - - if (lv2_obj::notify_all_t notify; rwlock.ret & 1) - { - std::lock_guard lock(rwlock->mutex); - - if (auto cpu = rwlock->schedule(rwlock->wq, rwlock->protocol)) - { - if (static_cast(cpu)->state & cpu_flag::again) - { - ppu.state += cpu_flag::again; - return {}; - } - - rwlock->owner = cpu->id << 1 | !!rwlock->wq | !!rwlock->rq; - - rwlock->awake(cpu); - } - else if (rwlock->rq) - { - for (auto cpu = +rwlock->rq; cpu; cpu = cpu->next_cpu) - { - if (cpu->state & cpu_flag::again) - { - ppu.state += cpu_flag::again; - return {}; - } - } - - s64 size = 0; - - // Protocol doesn't matter here since they are all enqueued anyways - while (auto cpu = rwlock->schedule(rwlock->rq, SYS_SYNC_FIFO)) - { - size++; - rwlock->append(cpu); - } - - rwlock->owner.release(-2 * static_cast(size)); - lv2_obj::awake_all(); - } - else - { - rwlock->owner = 0; - } - } - - return CELL_OK; -} diff --git a/rpcs3/Emu/Cell/lv2/sys_rwlock.h b/rpcs3/Emu/Cell/lv2/sys_rwlock.h deleted file mode 100644 index c64a9bff5..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_rwlock.h +++ /dev/null @@ -1,59 +0,0 @@ -#pragma once - -#include "sys_sync.h" - -#include "Emu/Memory/vm_ptr.h" - -struct sys_rwlock_attribute_t -{ - be_t protocol; - be_t pshared; - be_t ipc_key; - be_t flags; - be_t pad; - - union - { - nse_t name_u64; - char name[sizeof(u64)]; - }; -}; - -struct lv2_rwlock final : lv2_obj -{ - static const u32 id_base = 0x88000000; - - const lv2_protocol protocol; - const u64 key; - const u64 name; - - shared_mutex mutex; - atomic_t owner{0}; - ppu_thread* rq{}; - ppu_thread* wq{}; - - lv2_rwlock(u32 protocol, u64 key, u64 name) noexcept - : protocol{static_cast(protocol)}, key(key), name(name) - { - } - - lv2_rwlock(utils::serial& ar); - static std::function load(utils::serial& ar); - void save(utils::serial& ar); -}; - -// Aux -class ppu_thread; - -// Syscalls - -error_code sys_rwlock_create(ppu_thread& ppu, vm::ptr rw_lock_id, vm::ptr attr); -error_code sys_rwlock_destroy(ppu_thread& ppu, u32 rw_lock_id); -error_code sys_rwlock_rlock(ppu_thread& ppu, u32 rw_lock_id, u64 timeout); -error_code sys_rwlock_tryrlock(ppu_thread& ppu, u32 rw_lock_id); -error_code sys_rwlock_runlock(ppu_thread& ppu, u32 rw_lock_id); -error_code sys_rwlock_wlock(ppu_thread& ppu, u32 rw_lock_id, u64 timeout); -error_code sys_rwlock_trywlock(ppu_thread& ppu, u32 rw_lock_id); -error_code sys_rwlock_wunlock(ppu_thread& ppu, u32 rw_lock_id); - -constexpr auto _sys_rwlock_trywlock = sys_rwlock_trywlock; diff --git a/rpcs3/Emu/Cell/lv2/sys_semaphore.cpp b/rpcs3/Emu/Cell/lv2/sys_semaphore.cpp deleted file mode 100644 index dc685c02d..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_semaphore.cpp +++ /dev/null @@ -1,362 +0,0 @@ -#include "stdafx.h" -#include "sys_semaphore.h" - -#include "Emu/IdManager.h" - -#include "Emu/Cell/ErrorCodes.h" -#include "Emu/Cell/PPUThread.h" - -#include "util/asm.hpp" - -LOG_CHANNEL(sys_semaphore); - -lv2_sema::lv2_sema(utils::serial& ar) - : protocol(ar), key(ar), name(ar), max(ar) -{ - ar(val); -} - -std::function lv2_sema::load(utils::serial& ar) -{ - return load_func(make_shared(exact_t(ar))); -} - -void lv2_sema::save(utils::serial& ar) -{ - USING_SERIALIZATION_VERSION(lv2_sync); - ar(protocol, key, name, max, std::max(+val, 0)); -} - -error_code sys_semaphore_create(ppu_thread& ppu, vm::ptr sem_id, vm::ptr attr, s32 initial_val, s32 max_val) -{ - ppu.state += cpu_flag::wait; - - sys_semaphore.trace("sys_semaphore_create(sem_id=*0x%x, attr=*0x%x, initial_val=%d, max_val=%d)", sem_id, attr, initial_val, max_val); - - if (!sem_id || !attr) - { - return CELL_EFAULT; - } - - if (max_val <= 0 || initial_val > max_val || initial_val < 0) - { - sys_semaphore.error("sys_semaphore_create(): invalid parameters (initial_val=%d, max_val=%d)", initial_val, max_val); - return CELL_EINVAL; - } - - const auto _attr = *attr; - - const u32 protocol = _attr.protocol; - - if (protocol != SYS_SYNC_FIFO && protocol != SYS_SYNC_PRIORITY) - { - sys_semaphore.error("sys_semaphore_create(): unknown protocol (0x%x)", protocol); - return CELL_EINVAL; - } - - const u64 ipc_key = lv2_obj::get_key(_attr); - - if (ipc_key) - { - sys_semaphore.warning("sys_semaphore_create(sem_id=*0x%x, attr=*0x%x, initial_val=%d, max_val=%d): IPC=0x%016x", sem_id, attr, initial_val, max_val, ipc_key); - } - - if (auto error = lv2_obj::create(_attr.pshared, ipc_key, _attr.flags, [&] - { - return make_shared(protocol, ipc_key, _attr.name_u64, max_val, initial_val); - })) - { - return error; - } - - static_cast(ppu.test_stopped()); - - *sem_id = idm::last_id(); - return CELL_OK; -} - -error_code sys_semaphore_destroy(ppu_thread& ppu, u32 sem_id) -{ - ppu.state += cpu_flag::wait; - - sys_semaphore.trace("sys_semaphore_destroy(sem_id=0x%x)", sem_id); - - const auto sem = idm::withdraw(sem_id, [](lv2_sema& sema) -> CellError - { - if (sema.val < 0) - { - return CELL_EBUSY; - } - - lv2_obj::on_id_destroy(sema, sema.key); - return {}; - }); - - if (!sem) - { - return CELL_ESRCH; - } - - if (sem->key) - { - sys_semaphore.warning("sys_semaphore_destroy(sem_id=0x%x): IPC=0x%016x", sem_id, sem->key); - } - - if (sem.ret) - { - return sem.ret; - } - - return CELL_OK; -} - -error_code sys_semaphore_wait(ppu_thread& ppu, u32 sem_id, u64 timeout) -{ - ppu.state += cpu_flag::wait; - - sys_semaphore.trace("sys_semaphore_wait(sem_id=0x%x, timeout=0x%llx)", sem_id, timeout); - - const auto sem = idm::get(sem_id, [&, notify = lv2_obj::notify_all_t()](lv2_sema& sema) - { - const s32 val = sema.val; - - if (val > 0) - { - if (sema.val.compare_and_swap_test(val, val - 1)) - { - return true; - } - } - - lv2_obj::prepare_for_sleep(ppu); - - std::lock_guard lock(sema.mutex); - - if (sema.val-- <= 0) - { - sema.sleep(ppu, timeout); - lv2_obj::emplace(sema.sq, &ppu); - return false; - } - - return true; - }); - - if (!sem) - { - return CELL_ESRCH; - } - - if (sem.ret) - { - return CELL_OK; - } - - ppu.gpr[3] = CELL_OK; - - while (auto state = +ppu.state) - { - if (state & cpu_flag::signal && ppu.state.test_and_reset(cpu_flag::signal)) - { - break; - } - - if (is_stopped(state)) - { - std::lock_guard lock(sem->mutex); - - for (auto cpu = +sem->sq; cpu; cpu = cpu->next_cpu) - { - if (cpu == &ppu) - { - ppu.state += cpu_flag::again; - return {}; - } - } - - break; - } - - for (usz i = 0; cpu_flag::signal - ppu.state && i < 50; i++) - { - busy_wait(500); - } - - if (ppu.state & cpu_flag::signal) - { - continue; - } - - if (timeout) - { - if (lv2_obj::wait_timeout(timeout, &ppu)) - { - // Wait for rescheduling - if (ppu.check_state()) - { - continue; - } - - ppu.state += cpu_flag::wait; - - std::lock_guard lock(sem->mutex); - - if (!sem->unqueue(sem->sq, &ppu)) - { - break; - } - - ensure(0 > sem->val.fetch_op([](s32& val) - { - if (val < 0) - { - val++; - } - })); - - ppu.gpr[3] = CELL_ETIMEDOUT; - break; - } - } - else - { - ppu.state.wait(state); - } - } - - return not_an_error(ppu.gpr[3]); -} - -error_code sys_semaphore_trywait(ppu_thread& ppu, u32 sem_id) -{ - ppu.state += cpu_flag::wait; - - sys_semaphore.trace("sys_semaphore_trywait(sem_id=0x%x)", sem_id); - - const auto sem = idm::check(sem_id, [&](lv2_sema& sema) - { - return sema.val.try_dec(0); - }); - - if (!sem) - { - return CELL_ESRCH; - } - - if (!sem.ret) - { - return not_an_error(CELL_EBUSY); - } - - return CELL_OK; -} - -error_code sys_semaphore_post(ppu_thread& ppu, u32 sem_id, s32 count) -{ - ppu.state += cpu_flag::wait; - - sys_semaphore.trace("sys_semaphore_post(sem_id=0x%x, count=%d)", sem_id, count); - - const auto sem = idm::get(sem_id, [&](lv2_sema& sema) - { - const s32 val = sema.val; - - if (val >= 0 && count > 0 && count <= sema.max - val) - { - if (sema.val.compare_and_swap_test(val, val + count)) - { - return true; - } - } - - return false; - }); - - if (!sem) - { - return CELL_ESRCH; - } - - if (count <= 0) - { - return CELL_EINVAL; - } - - lv2_obj::notify_all_t notify; - - if (sem.ret) - { - return CELL_OK; - } - else - { - std::lock_guard lock(sem->mutex); - - for (auto cpu = +sem->sq; cpu; cpu = cpu->next_cpu) - { - if (static_cast(cpu)->state & cpu_flag::again) - { - ppu.state += cpu_flag::again; - return {}; - } - } - - const auto [val, ok] = sem->val.fetch_op([&](s32& val) - { - if (count + 0u <= sem->max + 0u - val) - { - val += count; - return true; - } - - return false; - }); - - if (!ok) - { - return not_an_error(CELL_EBUSY); - } - - // Wake threads - const s32 to_awake = std::min(-std::min(val, 0), count); - - for (s32 i = 0; i < to_awake; i++) - { - sem->append((ensure(sem->schedule(sem->sq, sem->protocol)))); - } - - if (to_awake > 0) - { - lv2_obj::awake_all(); - } - } - - return CELL_OK; -} - -error_code sys_semaphore_get_value(ppu_thread& ppu, u32 sem_id, vm::ptr count) -{ - ppu.state += cpu_flag::wait; - - sys_semaphore.trace("sys_semaphore_get_value(sem_id=0x%x, count=*0x%x)", sem_id, count); - - const auto sema = idm::check(sem_id, [](lv2_sema& sema) - { - return std::max(0, sema.val); - }); - - if (!sema) - { - return CELL_ESRCH; - } - - if (!count) - { - return CELL_EFAULT; - } - - static_cast(ppu.test_stopped()); - - *count = sema.ret; - return CELL_OK; -} diff --git a/rpcs3/Emu/Cell/lv2/sys_semaphore.h b/rpcs3/Emu/Cell/lv2/sys_semaphore.h deleted file mode 100644 index d24cdcea4..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_semaphore.h +++ /dev/null @@ -1,55 +0,0 @@ -#pragma once - -#include "sys_sync.h" - -#include "Emu/Memory/vm_ptr.h" - -struct sys_semaphore_attribute_t -{ - be_t protocol; - be_t pshared; - be_t ipc_key; - be_t flags; - be_t pad; - - union - { - nse_t name_u64; - char name[sizeof(u64)]; - }; -}; - -struct lv2_sema final : lv2_obj -{ - static const u32 id_base = 0x96000000; - - const lv2_protocol protocol; - const u64 key; - const u64 name; - const s32 max; - - shared_mutex mutex; - atomic_t val; - ppu_thread* sq{}; - - lv2_sema(u32 protocol, u64 key, u64 name, s32 max, s32 value) noexcept - : protocol{static_cast(protocol)}, key(key), name(name), max(max), val(value) - { - } - - lv2_sema(utils::serial& ar); - static std::function load(utils::serial& ar); - void save(utils::serial& ar); -}; - -// Aux -class ppu_thread; - -// Syscalls - -error_code sys_semaphore_create(ppu_thread& ppu, vm::ptr sem_id, vm::ptr attr, s32 initial_val, s32 max_val); -error_code sys_semaphore_destroy(ppu_thread& ppu, u32 sem_id); -error_code sys_semaphore_wait(ppu_thread& ppu, u32 sem_id, u64 timeout); -error_code sys_semaphore_trywait(ppu_thread& ppu, u32 sem_id); -error_code sys_semaphore_post(ppu_thread& ppu, u32 sem_id, s32 count); -error_code sys_semaphore_get_value(ppu_thread& ppu, u32 sem_id, vm::ptr count); diff --git a/rpcs3/Emu/Cell/lv2/sys_sm.cpp b/rpcs3/Emu/Cell/lv2/sys_sm.cpp deleted file mode 100644 index d663b9404..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_sm.cpp +++ /dev/null @@ -1,125 +0,0 @@ -#include "stdafx.h" -#include "Emu/System.h" - -#include "Emu/Cell/ErrorCodes.h" -#include "Emu/Cell/PPUThread.h" -#include "Emu/Cell/lv2/sys_process.h" - -#include "sys_sm.h" - -LOG_CHANNEL(sys_sm); - -error_code sys_sm_get_params(vm::ptr a, vm::ptr b, vm::ptr c, vm::ptr d) -{ - sys_sm.todo("sys_sm_get_params(a=*0x%x, b=*0x%x, c=*0x%x, d=*0x%x)", a, b, c, d); - - if (a) - *a = 0; - else - return CELL_EFAULT; - if (b) - *b = 0; - else - return CELL_EFAULT; - if (c) - *c = 0x200; - else - return CELL_EFAULT; - if (d) - *d = 7; - else - return CELL_EFAULT; - - return CELL_OK; -} - -error_code sys_sm_get_ext_event2(vm::ptr a1, vm::ptr a2, vm::ptr a3, u64 a4) -{ - sys_sm.todo("sys_sm_get_ext_event2(a1=*0x%x, a2=*0x%x, a3=*0x%x, a4=*0x%x, a4=0x%xll", a1, a2, a3, a4); - - if (a4 != 0 && a4 != 1) - { - return CELL_EINVAL; - } - - // a1 == 7 - 'console too hot, restart' - // a2 looks to be used if a1 is either 5 or 3? - // a3 looks to be ignored in vsh - - if (a1) - *a1 = 0; - else - return CELL_EFAULT; - if (a2) - *a2 = 0; - else - return CELL_EFAULT; - if (a3) - *a3 = 0; - else - return CELL_EFAULT; - - // eagain for no event - return not_an_error(CELL_EAGAIN); -} - -error_code sys_sm_shutdown(ppu_thread& ppu, u16 op, vm::ptr param, u64 size) -{ - ppu.state += cpu_flag::wait; - - sys_sm.success("sys_sm_shutdown(op=0x%x, param=*0x%x, size=0x%x)", op, param, size); - - if (!g_ps3_process_info.has_root_perm()) - { - return CELL_ENOSYS; - } - - switch (op) - { - case 0x100: - case 0x1100: - { - sys_sm.success("Received shutdown request from application"); - _sys_process_exit(ppu, 0, 0, 0); - break; - } - case 0x200: - case 0x1200: - { - sys_sm.success("Received reboot request from application"); - lv2_exitspawn(ppu, Emu.argv, Emu.envp, Emu.data); - break; - } - case 0x8201: - case 0x8202: - case 0x8204: - { - sys_sm.warning("Unsupported LPAR operation: 0x%x", op); - return CELL_ENOTSUP; - } - default: return CELL_EINVAL; - } - - return CELL_OK; -} - -error_code sys_sm_set_shop_mode(s32 mode) -{ - sys_sm.todo("sys_sm_set_shop_mode(mode=0x%x)", mode); - - return CELL_OK; -} - -error_code sys_sm_control_led(u8 led, u8 action) -{ - sys_sm.todo("sys_sm_control_led(led=0x%x, action=0x%x)", led, action); - - return CELL_OK; -} - -error_code sys_sm_ring_buzzer(u64 packet, u64 a1, u64 a2) -{ - sys_sm.todo("sys_sm_ring_buzzer(packet=0x%x, a1=0x%x, a2=0x%x)", packet, a1, a2); - - return CELL_OK; -} diff --git a/rpcs3/Emu/Cell/lv2/sys_spu.cpp b/rpcs3/Emu/Cell/lv2/sys_spu.cpp deleted file mode 100644 index addb39876..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_spu.cpp +++ /dev/null @@ -1,2782 +0,0 @@ -#include "stdafx.h" -#include "sys_spu.h" - -#include "Emu/System.h" -#include "Emu/IdManager.h" -#include "Crypto/unself.h" -#include "Crypto/unedat.h" -#include "Crypto/sha1.h" -#include "Loader/ELF.h" -#include "util/bin_patch.h" - -#include "Emu/Cell/ErrorCodes.h" -#include "Emu/Cell/PPUThread.h" -#include "Emu/Cell/timers.hpp" -#include "Emu/Memory/vm_reservation.h" -#include "sys_interrupt.h" -#include "sys_process.h" -#include "sys_memory.h" -#include "sys_mmapper.h" -#include "sys_event.h" -#include "sys_fs.h" - -#include "util/asm.hpp" - -LOG_CHANNEL(sys_spu); - -template <> -void fmt_class_string::format(std::string& out, u64 arg) -{ - format_enum(out, arg, [](spu_group_status value) - { - switch (value) - { - case SPU_THREAD_GROUP_STATUS_NOT_INITIALIZED: return "uninitialized"; - case SPU_THREAD_GROUP_STATUS_INITIALIZED: return "initialized"; - case SPU_THREAD_GROUP_STATUS_READY: return "ready"; - case SPU_THREAD_GROUP_STATUS_WAITING: return "waiting"; - case SPU_THREAD_GROUP_STATUS_SUSPENDED: return "suspended"; - case SPU_THREAD_GROUP_STATUS_WAITING_AND_SUSPENDED: return "waiting and suspended"; - case SPU_THREAD_GROUP_STATUS_RUNNING: return "running"; - case SPU_THREAD_GROUP_STATUS_STOPPED: return "stopped"; - case SPU_THREAD_GROUP_STATUS_DESTROYED: return "destroyed"; - case SPU_THREAD_GROUP_STATUS_UNKNOWN: break; - } - - return unknown; - }); -} - -template <> -void fmt_class_string::format(std::string& out, u64 arg) -{ - format_enum(out, arg, [](spu_stop_syscall value) - { - switch (value) - { - case SYS_SPU_THREAD_STOP_YIELD: return "sys_spu_thread_yield"; - case SYS_SPU_THREAD_STOP_GROUP_EXIT: return "sys_spu_thread_group_exit"; - case SYS_SPU_THREAD_STOP_THREAD_EXIT: return "sys_spu_thread_thread_exit"; - case SYS_SPU_THREAD_STOP_RECEIVE_EVENT: return "sys_spu_thread_receive_event"; - case SYS_SPU_THREAD_STOP_TRY_RECEIVE_EVENT: return "sys_spu_thread_tryreceive_event"; - case SYS_SPU_THREAD_STOP_SWITCH_SYSTEM_MODULE: return "sys_spu_thread_switch_system_module"; - } - - return unknown; - }); -} - -void sys_spu_image::load(const fs::file& stream) -{ - const spu_exec_object obj{stream, 0, elf_opt::no_sections + elf_opt::no_data}; - - if (obj != elf_error::ok) - { - fmt::throw_exception("Failed to load SPU image: %s", obj.get_error()); - } - - for (const auto& shdr : obj.shdrs) - { - spu_log.notice("** Section: sh_type=0x%x, addr=0x%llx, size=0x%llx, flags=0x%x", std::bit_cast(shdr.sh_type), shdr.sh_addr, shdr.sh_size, shdr._sh_flags); - } - - for (const auto& prog : obj.progs) - { - spu_log.notice("** Segment: p_type=0x%x, p_vaddr=0x%llx, p_filesz=0x%llx, p_memsz=0x%llx, flags=0x%x", prog.p_type, prog.p_vaddr, prog.p_filesz, prog.p_memsz, prog.p_flags); - - if (prog.p_type != u32{SYS_SPU_SEGMENT_TYPE_COPY} && prog.p_type != u32{SYS_SPU_SEGMENT_TYPE_INFO}) - { - spu_log.error("Unknown program type (0x%x)", prog.p_type); - } - } - - this->type = SYS_SPU_IMAGE_TYPE_KERNEL; - const s32 nsegs = sys_spu_image::get_nsegs(obj.progs); - - const u32 mem_size = nsegs * sizeof(sys_spu_segment) + ::size32(stream); - const vm::ptr segs = vm::cast(vm::alloc(mem_size, vm::main)); - - // const u32 entry = obj.header.e_entry; - - const u32 src = (segs + nsegs).addr(); - - stream.seek(0); - stream.read(vm::base(src), stream.size()); - - if (nsegs <= 0 || nsegs > 0x20 || sys_spu_image::fill(segs, nsegs, obj.progs, src) != nsegs) - { - fmt::throw_exception("Failed to load SPU segments (%d)", nsegs); - } - - // Write ID and save entry - this->entry_point = idm::make(+obj.header.e_entry, segs, nsegs); - - // Unused and set to 0 - this->nsegs = 0; - this->segs = vm::null; - - vm::page_protect(segs.addr(), utils::align(mem_size, 4096), 0, 0, vm::page_writable); -} - -void sys_spu_image::free() const -{ - if (type == SYS_SPU_IMAGE_TYPE_KERNEL) - { - // TODO: Remove, should be handled by syscalls - ensure(vm::dealloc(segs.addr(), vm::main)); - } -} - -void sys_spu_image::deploy(u8* loc, std::span segs, bool is_verbose) -{ - // Segment info dump - std::string dump; - - // Executable hash - sha1_context sha; - sha1_starts(&sha); - u8 sha1_hash[20]; - - for (const auto& seg : segs) - { - fmt::append(dump, "\n\t[%u] t=0x%x, ls=0x%x, size=0x%x, addr=0x%x", &seg - segs.data(), seg.type, seg.ls, seg.size, seg.addr); - - sha1_update(&sha, reinterpret_cast(&seg.type), sizeof(seg.type)); - - // Hash big-endian values - if (seg.type == SYS_SPU_SEGMENT_TYPE_COPY) - { - std::memcpy(loc + seg.ls, vm::base(seg.addr), seg.size); - sha1_update(&sha, reinterpret_cast(&seg.size), sizeof(seg.size)); - sha1_update(&sha, reinterpret_cast(&seg.ls), sizeof(seg.ls)); - sha1_update(&sha, vm::_ptr(seg.addr), seg.size); - } - else if (seg.type == SYS_SPU_SEGMENT_TYPE_FILL) - { - if ((seg.ls | seg.size) % 4) - { - spu_log.error("Unaligned SPU FILL type segment (ls=0x%x, size=0x%x)", seg.ls, seg.size); - } - - std::fill_n(reinterpret_cast*>(loc + seg.ls), seg.size / 4, seg.addr); - sha1_update(&sha, reinterpret_cast(&seg.size), sizeof(seg.size)); - sha1_update(&sha, reinterpret_cast(&seg.ls), sizeof(seg.ls)); - sha1_update(&sha, reinterpret_cast(&seg.addr), sizeof(seg.addr)); - } - else if (seg.type == SYS_SPU_SEGMENT_TYPE_INFO) - { - const be_t size = seg.size + 0x14; // Workaround - sha1_update(&sha, reinterpret_cast(&size), sizeof(size)); - } - } - - sha1_finish(&sha, sha1_hash); - - // Format patch name - std::string hash("SPU-0000000000000000000000000000000000000000"); - for (u32 i = 0; i < sizeof(sha1_hash); i++) - { - constexpr auto pal = "0123456789abcdef"; - hash[4 + i * 2] = pal[sha1_hash[i] >> 4]; - hash[5 + i * 2] = pal[sha1_hash[i] & 15]; - } - - auto mem_translate = [loc](u32 addr, u32 size) - { - return utils::add_saturate(addr, size) <= SPU_LS_SIZE ? loc + addr : nullptr; - }; - - // Apply the patch - std::vector applied; - g_fxo->get().apply(applied, hash, mem_translate); - - if (!Emu.GetTitleID().empty()) - { - // Alternative patch - g_fxo->get().apply(applied, Emu.GetTitleID() + '-' + hash, mem_translate); - } - - (is_verbose ? spu_log.notice : sys_spu.trace)("Loaded SPU image: %s (<- %u)%s", hash, applied.size(), dump); -} - -lv2_spu_group::lv2_spu_group(utils::serial& ar) noexcept - : name(ar.pop()), id(idm::last_id()), max_num(ar), mem_size(ar), type(ar) // SPU Thread Group Type - , - ct(lv2_memory_container::search(ar)), has_scheduler_context(ar.pop()), max_run(ar), init(ar), prio([&ar]() - { - std::common_type_t prio{}; - - ar(prio.all); - - return prio; - }()), - run_state(ar.pop()), exit_status(ar) -{ - for (auto& thread : threads) - { - if (ar.pop()) - { - ar(id_manager::g_id); - thread = stx::make_shared>(stx::launch_retainer{}, ar, this); - ensure(idm::import_existing>(thread, idm::last_id())); - running += !thread->stop_flag_removal_protection; - } - } - - ar(threads_map); - ar(imgs); - ar(args); - - for (auto ep : {&ep_run, &ep_exception, &ep_sysmodule}) - { - *ep = idm::get_unlocked(ar.pop()); - } - - waiter_spu_index = -1; - - switch (run_state) - { - // Commented stuff are handled by different means currently - // case SPU_THREAD_GROUP_STATUS_NOT_INITIALIZED: - // case SPU_THREAD_GROUP_STATUS_INITIALIZED: - // case SPU_THREAD_GROUP_STATUS_READY: - case SPU_THREAD_GROUP_STATUS_WAITING: - { - run_state = SPU_THREAD_GROUP_STATUS_RUNNING; - ar(waiter_spu_index); - [[fallthrough]]; - } - case SPU_THREAD_GROUP_STATUS_WAITING_AND_SUSPENDED: - { - if (run_state == SPU_THREAD_GROUP_STATUS_WAITING_AND_SUSPENDED) - { - run_state = SPU_THREAD_GROUP_STATUS_SUSPENDED; - } - - [[fallthrough]]; - } - case SPU_THREAD_GROUP_STATUS_SUSPENDED: - { - // Suspend all SPU threads except a thread that waits on sys_spu_thread_receive_event - for (const auto& thread : threads) - { - if (thread) - { - if (thread->index == waiter_spu_index) - { - lv2_obj::set_future_sleep(thread.get()); - continue; - } - - thread->state += cpu_flag::suspend; - } - } - - break; - } - // case SPU_THREAD_GROUP_STATUS_RUNNING: - // case SPU_THREAD_GROUP_STATUS_STOPPED: - // case SPU_THREAD_GROUP_STATUS_UNKNOWN: - default: - { - break; - } - } -} - -void lv2_spu_group::save(utils::serial& ar) -{ - USING_SERIALIZATION_VERSION(spu); - - ar(name, max_num, mem_size, type, ct->id, has_scheduler_context, max_run, init, prio.load().all, run_state, exit_status); - - for (const auto& thread : threads) - { - ar(u8{thread.operator bool()}); - - if (thread) - { - ar(thread->id); - thread->save(ar); - } - } - - ar(threads_map); - ar(imgs); - ar(args); - - for (auto ep : {&ep_run, &ep_exception, &ep_sysmodule}) - { - ar(lv2_obj::check(*ep) ? (*ep)->id : 0); - } - - if (run_state == SPU_THREAD_GROUP_STATUS_WAITING) - { - ar(waiter_spu_index); - } -} - -lv2_spu_image::lv2_spu_image(utils::serial& ar) - : e_entry(ar), segs(ar.pop()), nsegs(ar) -{ -} - -void lv2_spu_image::save(utils::serial& ar) -{ - ar(e_entry, segs, nsegs); -} - -// Get spu thread ptr, returns group ptr as well for refcounting -std::pair*, shared_ptr> lv2_spu_group::get_thread(u32 id) -{ - if (id >= 0x06000000) - { - // thread index is out of range (5 max) - return {}; - } - - // Bits 0-23 contain group id (without id base) - decltype(get_thread(0)) res{nullptr, idm::get_unlocked((id & 0xFFFFFF) | (lv2_spu_group::id_base & ~0xFFFFFF))}; - - // Bits 24-31 contain thread index within the group - const u32 index = id >> 24; - - if (auto group = res.second.get(); group && group->init > index) - { - res.first = group->threads[index].get(); - } - - return res; -} - -struct limits_data -{ - u32 physical = 0; - u32 raw_spu = 0; - u32 controllable = 0; - u32 spu_limit = umax; - u32 raw_limit = umax; -}; - -struct spu_limits_t -{ - u32 max_raw = 0; - u32 max_spu = 6; - shared_mutex mutex; - - spu_limits_t() = default; - - spu_limits_t(utils::serial& ar) noexcept - { - ar(max_raw, max_spu); - } - - void save(utils::serial& ar) - { - ar(max_raw, max_spu); - } - - SAVESTATE_INIT_POS(47); - - bool check(const limits_data& init) const - { - u32 physical_spus_count = init.physical; - u32 raw_spu_count = init.raw_spu; - u32 controllable_spu_count = init.controllable; - const u32 spu_limit = init.spu_limit != umax ? init.spu_limit : max_spu; - const u32 raw_limit = init.raw_limit != umax ? init.raw_limit : max_raw; - - idm::select([&](u32, lv2_spu_group& group) - { - if (group.has_scheduler_context) - { - controllable_spu_count = std::max(controllable_spu_count, group.max_num); - } - else - { - physical_spus_count += group.max_num; - } - }); - - raw_spu_count += spu_thread::g_raw_spu_ctr; - - if (spu_limit + raw_limit > 6 || raw_spu_count > raw_limit || physical_spus_count >= spu_limit || physical_spus_count + controllable_spu_count > spu_limit) - { - return false; - } - - return true; - } -}; - -error_code sys_spu_initialize(ppu_thread& ppu, u32 max_usable_spu, u32 max_raw_spu) -{ - ppu.state += cpu_flag::wait; - - sys_spu.warning("sys_spu_initialize(max_usable_spu=%d, max_raw_spu=%d)", max_usable_spu, max_raw_spu); - - auto& limits = g_fxo->get(); - - if (max_raw_spu > 5) - { - return CELL_EINVAL; - } - - // NOTE: This value can be changed by VSH in theory - max_usable_spu = 6; - - std::lock_guard lock(limits.mutex); - - if (!limits.check(limits_data{.spu_limit = max_usable_spu - max_raw_spu, .raw_limit = max_raw_spu})) - { - return CELL_EBUSY; - } - - limits.max_raw = max_raw_spu; - limits.max_spu = max_usable_spu - max_raw_spu; - return CELL_OK; -} - -error_code _sys_spu_image_get_information(ppu_thread& ppu, vm::ptr img, vm::ptr entry_point, vm::ptr nsegs) -{ - ppu.state += cpu_flag::wait; - - sys_spu.warning("_sys_spu_image_get_information(img=*0x%x, entry_point=*0x%x, nsegs=*0x%x)", img, entry_point, nsegs); - - if (img->type != SYS_SPU_IMAGE_TYPE_KERNEL) - { - return CELL_EINVAL; - } - - const auto image = idm::get_unlocked(img->entry_point); - - if (!image) - { - return CELL_ESRCH; - } - - ppu.check_state(); - *entry_point = image->e_entry; - *nsegs = image->nsegs; - return CELL_OK; -} - -error_code sys_spu_image_open(ppu_thread& ppu, vm::ptr img, vm::cptr path) -{ - ppu.state += cpu_flag::wait; - - sys_spu.warning("sys_spu_image_open(img=*0x%x, path=%s)", img, path); - - auto [fs_error, ppath, path0, file, type] = lv2_file::open(path.get_ptr(), 0, 0); - - if (fs_error) - { - return {fs_error, path}; - } - - u128 klic = g_fxo->get().last_key(); - - const fs::file elf_file = decrypt_self(std::move(file), reinterpret_cast(&klic)); - - if (!elf_file) - { - sys_spu.error("sys_spu_image_open(): file %s is illegal for SPU image!", path); - return {CELL_ENOEXEC, path}; - } - - img->load(elf_file); - return CELL_OK; -} - -error_code _sys_spu_image_import(ppu_thread& ppu, vm::ptr img, u32 src, u32 size, u32 arg4) -{ - ppu.state += cpu_flag::wait; - - sys_spu.warning("_sys_spu_image_import(img=*0x%x, src=*0x%x, size=0x%x, arg4=0x%x)", img, src, size, arg4); - - img->load(fs::file{vm::base(src), size}); - return CELL_OK; -} - -error_code _sys_spu_image_close(ppu_thread& ppu, vm::ptr img) -{ - ppu.state += cpu_flag::wait; - - sys_spu.warning("_sys_spu_image_close(img=*0x%x)", img); - - if (img->type != SYS_SPU_IMAGE_TYPE_KERNEL) - { - return CELL_EINVAL; - } - - const auto handle = idm::withdraw(img->entry_point); - - if (!handle) - { - return CELL_ESRCH; - } - - ensure(vm::dealloc(handle->segs.addr(), vm::main)); - return CELL_OK; -} - -error_code _sys_spu_image_get_segments(ppu_thread& ppu, vm::ptr img, vm::ptr segments, s32 nseg) -{ - ppu.state += cpu_flag::wait; - - sys_spu.error("_sys_spu_image_get_segments(img=*0x%x, segments=*0x%x, nseg=%d)", img, segments, nseg); - - if (nseg <= 0 || nseg > 0x20 || img->type != SYS_SPU_IMAGE_TYPE_KERNEL) - { - return CELL_EINVAL; - } - - const auto handle = idm::get_unlocked(img->entry_point); - - if (!handle) - { - return CELL_ESRCH; - } - - // TODO: apply SPU patches - ppu.check_state(); - std::memcpy(segments.get_ptr(), handle->segs.get_ptr(), sizeof(sys_spu_segment) * std::min(nseg, handle->nsegs)); - return CELL_OK; -} - -error_code sys_spu_thread_initialize(ppu_thread& ppu, vm::ptr thread, u32 group_id, u32 spu_num, vm::ptr img, vm::ptr attr, vm::ptr arg) -{ - ppu.state += cpu_flag::wait; - - sys_spu.warning("sys_spu_thread_initialize(thread=*0x%x, group=0x%x, spu_num=%d, img=*0x%x, attr=*0x%x, arg=*0x%x)", thread, group_id, spu_num, img, attr, arg); - - if (spu_num >= std::size(decltype(lv2_spu_group::threads_map){})) - { - return CELL_EINVAL; - } - - if (!attr) - { - return CELL_EFAULT; - } - - const sys_spu_thread_attribute attr_data = *attr; - - if (attr_data.name_len > 0x80) - { - return CELL_EINVAL; - } - - if (!arg) - { - return CELL_EFAULT; - } - - const sys_spu_thread_argument args = *arg; - const u32 option = attr_data.option; - - if (option & ~(SYS_SPU_THREAD_OPTION_DEC_SYNC_TB_ENABLE | SYS_SPU_THREAD_OPTION_ASYNC_INTR_ENABLE)) - { - return CELL_EINVAL; - } - - if (!img) - { - return CELL_EFAULT; - } - - sys_spu_image image = *img; - - switch (image.type) - { - case SYS_SPU_IMAGE_TYPE_KERNEL: - { - const auto handle = idm::get_unlocked(image.entry_point); - - if (!handle) - { - return CELL_ESRCH; - } - - // Image information is stored in IDM - image.entry_point = handle->e_entry; - image.nsegs = handle->nsegs; - image.segs = handle->segs; - image.type = SYS_SPU_IMAGE_TYPE_KERNEL; - break; - } - case SYS_SPU_IMAGE_TYPE_USER: - { - if (image.entry_point > 0x3fffc || image.nsegs <= 0 || image.nsegs > 0x20) - { - return CELL_EINVAL; - } - - break; - } - default: return CELL_EINVAL; - } - - std::vector spu_segs(image.segs.get_ptr(), image.segs.get_ptr() + image.nsegs); - - bool found_info_segment = false; - bool found_copy_segment = false; - - for (const auto& seg : spu_segs) - { - if (image.type == SYS_SPU_IMAGE_TYPE_KERNEL) - { - // Assume valid, values are coming from LV2 - found_copy_segment = true; - break; - } - - switch (seg.type) - { - case SYS_SPU_SEGMENT_TYPE_COPY: - { - if (seg.addr % 4) - { - // 4-bytes unaligned address is not valid - return CELL_EINVAL; - } - - found_copy_segment = true; - break; - } - case SYS_SPU_SEGMENT_TYPE_FILL: - { - break; - } - case SYS_SPU_SEGMENT_TYPE_INFO: - { - // There can only be one INFO segment at max - if (seg.size > 256u || found_info_segment) - { - return CELL_EINVAL; - } - - found_info_segment = true; - continue; - } - default: return CELL_EINVAL; - } - - if (!seg.size || (seg.ls | seg.size) % 0x10 || seg.ls >= SPU_LS_SIZE || seg.size > SPU_LS_SIZE) - { - return CELL_EINVAL; - } - - for (auto it = spu_segs.data(); it != &seg; it++) - { - if (it->type != SYS_SPU_SEGMENT_TYPE_INFO) - { - if (seg.ls + seg.size > it->ls && it->ls + it->size > seg.ls) - { - // Overlapping segments are not allowed - return CELL_EINVAL; - } - } - } - } - - // There must be at least one COPY segment - if (!found_copy_segment) - { - return CELL_EINVAL; - } - - // Read thread name - const std::string thread_name(attr_data.name.get_ptr(), std::max(attr_data.name_len, 1) - 1); - - const auto group = idm::get_unlocked(group_id); - - if (!group) - { - return CELL_ESRCH; - } - - std::unique_lock lock(group->mutex); - - if (auto state = +group->run_state; state != SPU_THREAD_GROUP_STATUS_NOT_INITIALIZED) - { - if (state == SPU_THREAD_GROUP_STATUS_DESTROYED) - { - return CELL_ESRCH; - } - - return CELL_EBUSY; - } - - if (group->threads_map[spu_num] != -1) - { - return CELL_EBUSY; - } - - if (option & SYS_SPU_THREAD_OPTION_ASYNC_INTR_ENABLE) - { - sys_spu.warning("Unimplemented SPU Thread options (0x%x)", option); - } - - const u32 inited = group->init; - - const u32 tid = (inited << 24) | (group_id & 0xffffff); - - ensure(idm::import >([&]() - { - const auto spu = stx::make_shared>(group.get(), spu_num, thread_name, tid, false, option); - group->threads[inited] = spu; - group->threads_map[spu_num] = static_cast(inited); - return spu; - })); - - // alloc_hidden indicates falloc to allocate page with no access rights in base memory - auto& spu = group->threads[inited]; - ensure(vm::get(vm::spu)->falloc(spu->vm_offset(), SPU_LS_SIZE, &spu->shm, static_cast(vm::page_size_64k) | static_cast(vm::alloc_hidden))); - spu->map_ls(*spu->shm, spu->ls); - - group->args[inited] = {args.arg1, args.arg2, args.arg3, args.arg4}; - group->imgs[inited].first = image.entry_point; - group->imgs[inited].second = std::move(spu_segs); - - if (++group->init == group->max_num) - { - const auto old = group->run_state.compare_and_swap(SPU_THREAD_GROUP_STATUS_NOT_INITIALIZED, SPU_THREAD_GROUP_STATUS_INITIALIZED); - - if (old == SPU_THREAD_GROUP_STATUS_DESTROYED) - { - return CELL_ESRCH; - } - } - - lock.unlock(); - sys_spu.warning("sys_spu_thread_initialize(): Thread \"%s\" created (id=0x%x)", thread_name, tid); - - ppu.check_state(); - *thread = tid; - return CELL_OK; -} - -error_code sys_spu_thread_set_argument(ppu_thread& ppu, u32 id, vm::ptr arg) -{ - ppu.state += cpu_flag::wait; - - sys_spu.warning("sys_spu_thread_set_argument(id=0x%x, arg=*0x%x)", id, arg); - - const auto [thread, group] = lv2_spu_group::get_thread(id); - - if (!thread) [[unlikely]] - { - return CELL_ESRCH; - } - - std::lock_guard lock(group->mutex); - - group->args[id >> 24] = {arg->arg1, arg->arg2, arg->arg3, arg->arg4}; - - return CELL_OK; -} - -error_code sys_spu_thread_get_exit_status(ppu_thread& ppu, u32 id, vm::ptr status) -{ - ppu.state += cpu_flag::wait; - - sys_spu.warning("sys_spu_thread_get_exit_status(id=0x%x, status=*0x%x)", id, status); - - const auto [thread, group] = lv2_spu_group::get_thread(id); - - if (!thread) [[unlikely]] - { - return CELL_ESRCH; - } - - u32 data; - - if (thread->exit_status.try_read(data)) - { - ppu.check_state(); - *status = static_cast(data); - return CELL_OK; - } - - return CELL_ESTAT; -} - -error_code sys_spu_thread_group_create(ppu_thread& ppu, vm::ptr id, u32 num, s32 prio, vm::ptr attr) -{ - ppu.state += cpu_flag::wait; - - sys_spu.warning("sys_spu_thread_group_create(id=*0x%x, num=%d, prio=%d, attr=*0x%x)", id, num, prio, attr); - - const s32 min_prio = g_ps3_process_info.has_root_perm() ? 0 : 16; - - const sys_spu_thread_group_attribute attr_data = *attr; - - if (attr_data.nsize > 0x80 || !num) - { - return CELL_EINVAL; - } - - const s32 type = attr_data.type; - - bool use_scheduler = true; - bool use_memct = !!(type & SYS_SPU_THREAD_GROUP_TYPE_MEMORY_FROM_CONTAINER); - bool needs_root = false; - u32 max_threads = 6; - u32 min_threads = 1; - u32 mem_size = 0; - lv2_memory_container* ct{}; - - if (type) - { - sys_spu.warning("sys_spu_thread_group_create(): SPU Thread Group type (0x%x)", type); - } - - switch (type) - { - case 0x0: - case 0x4: - case 0x18: - { - break; - } - - case 0x20: - case 0x22: - case 0x24: - case 0x26: - { - if (type == 0x22 || type == 0x26) - { - needs_root = true; - } - - min_threads = 2; // That's what appears from reversing - break; - } - - case 0x2: - case 0x6: - case 0xA: - - case 0x102: - case 0x106: - case 0x10A: - - case 0x202: - case 0x206: - case 0x20A: - - case 0x902: - case 0x906: - - case 0xA02: - case 0xA06: - - case 0xC02: - case 0xC06: - { - if (type & 0x700) - { - max_threads = 1; - } - - needs_root = true; - break; - } - default: return CELL_EINVAL; - } - - const bool is_system_coop = type & SYS_SPU_THREAD_GROUP_TYPE_COOPERATE_WITH_SYSTEM; - - if (is_system_coop) - { - // Constant size, unknown what it means - mem_size = SPU_LS_SIZE; - } - else if (type & SYS_SPU_THREAD_GROUP_TYPE_NON_CONTEXT) - { - // No memory consumed - mem_size = 0; - use_scheduler = false; - } - else - { - // 256kb for each spu thread, probably for saving and restoring SPU LS (used by scheduler?) - mem_size = SPU_LS_SIZE * num; - } - - if (num < min_threads || num > max_threads || - (needs_root && min_prio == 0x10) || (use_scheduler && !is_system_coop && (prio > 255 || prio < min_prio))) - { - return CELL_EINVAL; - } - - if (use_memct && mem_size) - { - const auto sct = idm::get_unlocked(attr_data.ct); - - if (!sct) - { - return CELL_ESRCH; - } - - if (sct->take(mem_size) != mem_size) - { - return CELL_ENOMEM; - } - - ct = sct.get(); - } - else - { - ct = &g_fxo->get(); - - if (ct->take(mem_size) != mem_size) - { - return CELL_ENOMEM; - } - } - - auto& limits = g_fxo->get(); - - std::unique_lock lock(limits.mutex); - - if (!limits.check(use_scheduler ? limits_data{.controllable = num} : limits_data{.physical = num})) - { - ct->free(mem_size); - return CELL_EBUSY; - } - - const auto group = idm::make_ptr(std::string(attr_data.name.get_ptr(), std::max(attr_data.nsize, 1) - 1), num, prio, type, ct, use_scheduler, mem_size); - - if (!group) - { - ct->free(mem_size); - return CELL_EAGAIN; - } - - lock.unlock(); - sys_spu.warning("sys_spu_thread_group_create(): Thread group \"%s\" created (id=0x%x)", group->name, idm::last_id()); - - ppu.check_state(); - *id = idm::last_id(); - return CELL_OK; -} - -error_code sys_spu_thread_group_destroy(ppu_thread& ppu, u32 id) -{ - ppu.state += cpu_flag::wait; - - sys_spu.warning("sys_spu_thread_group_destroy(id=0x%x)", id); - - auto& limits = g_fxo->get(); - - std::lock_guard lock(limits.mutex); - - const auto group = idm::withdraw(id, [](lv2_spu_group& group) -> CellError - { - if (!group.run_state.fetch_op([](spu_group_status& state) - { - if (state <= SPU_THREAD_GROUP_STATUS_INITIALIZED) - { - state = SPU_THREAD_GROUP_STATUS_DESTROYED; - return true; - } - - return false; - }) - .second) - { - return CELL_EBUSY; - } - - group.ct->free(group.mem_size); - return {}; - }); - - if (!group) - { - return CELL_ESRCH; - } - - if (group.ret) - { - return group.ret; - } - - group->mutex.lock_unlock(); - - for (const auto& t : group->threads) - { - if (auto thread = t.get()) - { - // Deallocate LS - thread->cleanup(); - - // Remove ID from IDM (destruction will occur in group destructor) - idm::remove>(thread->id); - } - } - - return CELL_OK; -} - -error_code sys_spu_thread_group_start(ppu_thread& ppu, u32 id) -{ - ppu.state += cpu_flag::wait; - - sys_spu.trace("sys_spu_thread_group_start(id=0x%x)", id); - - const auto group = idm::get_unlocked(id); - - if (!group) - { - return CELL_ESRCH; - } - - struct notify_on_exit - { - usz index = umax; - std::array threads; // Raw pointer suffices, as long as group is referenced its SPUs exist - - ~notify_on_exit() noexcept - { - for (; index != umax; index--) - { - threads[index]->state.notify_one(); - } - } - } notify_threads; - - std::lock_guard lock(group->mutex); - - // SPU_THREAD_GROUP_STATUS_READY state is not used - switch (group->run_state.compare_and_swap(SPU_THREAD_GROUP_STATUS_INITIALIZED, SPU_THREAD_GROUP_STATUS_RUNNING)) - { - case SPU_THREAD_GROUP_STATUS_INITIALIZED: break; - case SPU_THREAD_GROUP_STATUS_DESTROYED: return CELL_ESRCH; - default: return CELL_ESTAT; - } - - const u32 max_threads = group->max_num; - - group->join_state = 0; - group->exit_status = 0; - group->running = max_threads; - group->set_terminate = false; - - for (auto& thread : group->threads) - { - if (thread) - { - auto& args = group->args[thread->lv2_id >> 24]; - auto& img = group->imgs[thread->lv2_id >> 24]; - - sys_spu_image::deploy(thread->ls, std::span(img.second.data(), img.second.size()), group->stop_count < 5); - - thread->cpu_init(); - thread->gpr[3] = v128::from64(0, args[0]); - thread->gpr[4] = v128::from64(0, args[1]); - thread->gpr[5] = v128::from64(0, args[2]); - thread->gpr[6] = v128::from64(0, args[3]); - - thread->status_npc = {SPU_STATUS_RUNNING, img.first}; - } - } - - // Because SPU_THREAD_GROUP_STATUS_READY is not possible, run event is delivered immediately - // TODO: check data2 and data3 - group->send_run_event(id, 0, 0); - - u32 ran_threads = max_threads; - - for (auto& thread : group->threads) - { - if (!ran_threads) - { - break; - } - - if (thread && ran_threads--) - { - thread->state -= cpu_flag::stop; - notify_threads.threads[++notify_threads.index] = thread.get(); - } - } - - return CELL_OK; -} - -error_code sys_spu_thread_group_suspend(ppu_thread& ppu, u32 id) -{ - ppu.state += cpu_flag::wait; - - sys_spu.trace("sys_spu_thread_group_suspend(id=0x%x)", id); - - const auto group = idm::get_unlocked(id); - - if (!group) - { - return CELL_ESRCH; - } - - if (!group->has_scheduler_context || group->type & 0xf00) - { - return CELL_EINVAL; - } - - std::lock_guard lock(group->mutex); - - CellError error; - - group->run_state.fetch_op([&error](spu_group_status& state) - { - if (state == SPU_THREAD_GROUP_STATUS_DESTROYED) - { - error = CELL_ESRCH; - return false; - } - - if (state <= SPU_THREAD_GROUP_STATUS_INITIALIZED || state == SPU_THREAD_GROUP_STATUS_STOPPED) - { - error = CELL_ESTAT; - return false; - } - - // SPU_THREAD_GROUP_STATUS_READY state is not used - - if (state == SPU_THREAD_GROUP_STATUS_RUNNING) - { - state = SPU_THREAD_GROUP_STATUS_SUSPENDED; - } - else if (state == SPU_THREAD_GROUP_STATUS_WAITING) - { - state = SPU_THREAD_GROUP_STATUS_WAITING_AND_SUSPENDED; - } - else if (state == SPU_THREAD_GROUP_STATUS_SUSPENDED || state == SPU_THREAD_GROUP_STATUS_WAITING_AND_SUSPENDED) - { - error = {}; - return false; - } - else - { - - error = CELL_ESTAT; - return false; - } - - error = CellError{CELL_CANCEL + 0u}; - return true; - }); - - if (error != CELL_CANCEL + 0u) - { - if (!error) - { - return CELL_OK; - } - - return error; - } - - for (auto& thread : group->threads) - { - if (thread) - { - thread->state += cpu_flag::suspend; - } - } - - return CELL_OK; -} - -error_code sys_spu_thread_group_resume(ppu_thread& ppu, u32 id) -{ - ppu.state += cpu_flag::wait; - - sys_spu.trace("sys_spu_thread_group_resume(id=0x%x)", id); - - const auto group = idm::get_unlocked(id); - - if (!group) - { - return CELL_ESRCH; - } - - if (!group->has_scheduler_context || group->type & 0xf00) - { - return CELL_EINVAL; - } - - struct notify_on_exit - { - usz index = umax; - std::array threads; // Raw pointer suffices, as long as group is referenced its SPUs exist - - ~notify_on_exit() noexcept - { - for (; index != umax; index--) - { - threads[index]->state.notify_one(); - } - } - } notify_threads; - - std::lock_guard lock(group->mutex); - - CellError error; - - group->run_state.fetch_op([&error](spu_group_status& state) - { - if (state == SPU_THREAD_GROUP_STATUS_DESTROYED) - { - error = CELL_ESRCH; - return false; - } - - // SPU_THREAD_GROUP_STATUS_READY state is not used - - if (state == SPU_THREAD_GROUP_STATUS_SUSPENDED) - { - state = SPU_THREAD_GROUP_STATUS_RUNNING; - } - else if (state == SPU_THREAD_GROUP_STATUS_WAITING_AND_SUSPENDED) - { - state = SPU_THREAD_GROUP_STATUS_WAITING; - error = CellError{}; - return true; - } - else - { - error = CELL_ESTAT; - return false; - } - - error = CellError{CELL_CANCEL + 0u}; - return true; - }); - - if (error != CELL_CANCEL + 0u) - { - if (error) - { - return error; - } - - return CELL_OK; - } - - for (auto& thread : group->threads) - { - if (thread) - { - thread->state -= cpu_flag::suspend; - notify_threads.threads[++notify_threads.index] = thread.get(); - } - } - - return CELL_OK; -} - -error_code sys_spu_thread_group_yield(ppu_thread& ppu, u32 id) -{ - ppu.state += cpu_flag::wait; - - sys_spu.trace("sys_spu_thread_group_yield(id=0x%x)", id); - - const auto group = idm::get_unlocked(id); - - if (!group) - { - return CELL_ESRCH; - } - - // No effect on these group types - if (!group->has_scheduler_context || group->type & 0xf00) - { - return CELL_OK; - } - - if (auto state = +group->run_state; state != SPU_THREAD_GROUP_STATUS_RUNNING) - { - if (state == SPU_THREAD_GROUP_STATUS_DESTROYED) - { - return CELL_ESRCH; - } - - return CELL_ESTAT; - } - - // SPU_THREAD_GROUP_STATUS_READY state is not used, so this function does nothing - - return CELL_OK; -} - -error_code sys_spu_thread_group_terminate(ppu_thread& ppu, u32 id, s32 value) -{ - ppu.state += cpu_flag::wait; - - sys_spu.trace("sys_spu_thread_group_terminate(id=0x%x, value=0x%x)", id, value); - - const auto group = idm::get_unlocked(id); - - if (!group) - { - return CELL_ESRCH; - } - - std::unique_lock lock(group->mutex); - - // There should be a small period of sleep when the PPU waits for a signal of termination - auto short_sleep = [](ppu_thread& ppu) - { - lv2_obj::sleep(ppu); - busy_wait(3000); - ppu.check_state(); - ppu.state += cpu_flag::wait; - }; - - if (auto state = +group->run_state; - state <= SPU_THREAD_GROUP_STATUS_INITIALIZED || - state == SPU_THREAD_GROUP_STATUS_WAITING || - state == SPU_THREAD_GROUP_STATUS_WAITING_AND_SUSPENDED || - state == SPU_THREAD_GROUP_STATUS_DESTROYED) - { - if (state == SPU_THREAD_GROUP_STATUS_DESTROYED) - { - return CELL_ESRCH; - } - - return CELL_ESTAT; - } - - if (group->set_terminate) - { - // Wait for termination, only then return error code - const u32 last_stop = group->stop_count; - group->wait_term_count++; - lock.unlock(); - short_sleep(ppu); - - while (group->stop_count == last_stop) - { - group->stop_count.wait(last_stop); - } - - group->wait_term_count--; - return CELL_ESTAT; - } - - group->set_terminate = true; - - for (auto& thread : group->threads) - { - if (thread) - { - thread->state.fetch_op([](bs_t& flags) - { - if (flags & cpu_flag::stop) - { - // In case the thread raised the ret flag itself at some point do not raise it again - return false; - } - - flags += cpu_flag::stop + cpu_flag::ret; - return true; - }); - } - } - - u32 prev_resv = 0; - - for (auto& thread : group->threads) - { - while (thread && group->running && thread->state & cpu_flag::wait) - { - thread_ctrl::notify(*thread); - - if (u32 resv = atomic_storage::load(thread->raddr)) - { - if (prev_resv && prev_resv != resv) - { - // Batch reservation notifications if possible - vm::reservation_notifier_notify(prev_resv); - } - - prev_resv = resv; - } - } - } - - if (prev_resv) - { - vm::reservation_notifier_notify(prev_resv); - } - - group->exit_status = value; - group->join_state = SYS_SPU_THREAD_GROUP_JOIN_TERMINATED; - - // Wait until the threads are actually stopped - const u32 last_stop = group->stop_count; - group->wait_term_count++; - lock.unlock(); - short_sleep(ppu); - - while (group->stop_count == last_stop) - { - group->stop_count.wait(last_stop); - } - - group->wait_term_count--; - return CELL_OK; -} - -error_code sys_spu_thread_group_join(ppu_thread& ppu, u32 id, vm::ptr cause, vm::ptr status) -{ - ppu.state += cpu_flag::wait; - - sys_spu.trace("sys_spu_thread_group_join(id=0x%x, cause=*0x%x, status=*0x%x)", id, cause, status); - - const auto group = idm::get_unlocked(id); - - if (!group) - { - return CELL_ESRCH; - } - - do - { - lv2_obj::prepare_for_sleep(ppu); - - std::unique_lock lock(group->mutex); - - const auto state = +group->run_state; - - if (state == SPU_THREAD_GROUP_STATUS_DESTROYED) - { - return CELL_ESRCH; - } - - if (state < SPU_THREAD_GROUP_STATUS_INITIALIZED) - { - return CELL_ESTAT; - } - - if (group->waiter) - { - // another PPU thread is joining this thread group - return CELL_EBUSY; - } - - if (group->join_state && state == SPU_THREAD_GROUP_STATUS_INITIALIZED) - { - // Already signaled - ppu.gpr[4] = group->join_state; - ppu.gpr[5] = group->exit_status; - group->join_state.release(0); - break; - } - else - { - // Subscribe to receive status in r4-r5 - group->waiter = &ppu; - } - - { - lv2_obj::notify_all_t notify; - lv2_obj::sleep(ppu); - lock.unlock(); - } - - while (auto state = +ppu.state) - { - if (state & cpu_flag::signal && ppu.state.test_and_reset(cpu_flag::signal)) - { - break; - } - - if (is_stopped(state)) - { - std::lock_guard lock(group->mutex); - - if (group->waiter != &ppu) - { - break; - } - - ppu.state += cpu_flag::again; - break; - } - - ppu.state.wait(state); - } - } while (false); - - ppu.check_state(); - - if (!cause) - { - if (status) - { - // Report unwritten data - return CELL_EFAULT; - } - - return not_an_error(CELL_EFAULT); - } - - *cause = static_cast(ppu.gpr[4]); - - if (!status) - { - return not_an_error(CELL_EFAULT); - } - - *status = static_cast(ppu.gpr[5]); - return CELL_OK; -} - -error_code sys_spu_thread_group_set_priority(ppu_thread& ppu, u32 id, s32 priority) -{ - ppu.state += cpu_flag::wait; - - sys_spu.trace("sys_spu_thread_group_set_priority(id=0x%x, priority=%d)", id, priority); - - const auto group = idm::get_unlocked(id); - - if (!group) - { - return CELL_ESRCH; - } - - if (!group->has_scheduler_context || priority < (g_ps3_process_info.has_root_perm() ? 0 : 16) || priority > 255) - { - return CELL_EINVAL; - } - - group->prio.atomic_op([&](std::common_type_t& prio) - { - prio.prio = priority; - }); - - return CELL_OK; -} - -error_code sys_spu_thread_group_get_priority(ppu_thread& ppu, u32 id, vm::ptr priority) -{ - ppu.state += cpu_flag::wait; - - sys_spu.trace("sys_spu_thread_group_get_priority(id=0x%x, priority=*0x%x)", id, priority); - - const auto group = idm::get_unlocked(id); - - if (!group) - { - return CELL_ESRCH; - } - - ppu.check_state(); - - if (!group->has_scheduler_context) - { - *priority = 0; - } - else - { - *priority = group->prio.load().prio; - } - - return CELL_OK; -} - -error_code sys_spu_thread_group_set_cooperative_victims(ppu_thread& ppu, u32 id, u32 threads_mask) -{ - ppu.state += cpu_flag::wait; - - sys_spu.warning("sys_spu_thread_group_set_cooperative_victims(id=0x%x, threads_mask=0x%x)", id, threads_mask); - - const auto group = idm::get_unlocked(id); - - if (!group) - { - return CELL_ESRCH; - } - - if (!(group->type & SYS_SPU_THREAD_GROUP_TYPE_COOPERATE_WITH_SYSTEM)) - { - return CELL_EINVAL; - } - - if (threads_mask >= 1u << group->max_num) - { - return CELL_EINVAL; - } - - // TODO - - return CELL_OK; -} - -error_code sys_spu_thread_group_syscall_253(ppu_thread& ppu, u32 id, vm::ptr info) -{ - ppu.state += cpu_flag::wait; - - sys_spu.warning("sys_spu_thread_group_syscall_253(id=0x%x, info=*0x%x)", id, info); - - const auto group = idm::get_unlocked(id); - - if (!group) - { - return CELL_ESRCH; - } - - if (!(group->type & SYS_SPU_THREAD_GROUP_TYPE_COOPERATE_WITH_SYSTEM)) - { - return CELL_EINVAL; - } - - // TODO - - ppu.check_state(); - info->deadlineMissCounter = 0; - info->deadlineMeetCounter = 0; - info->timestamp = get_timebased_time(); - return CELL_OK; -} - -error_code sys_spu_thread_write_ls(ppu_thread& ppu, u32 id, u32 lsa, u64 value, u32 type) -{ - ppu.state += cpu_flag::wait; - - sys_spu.trace("sys_spu_thread_write_ls(id=0x%x, lsa=0x%05x, value=0x%llx, type=%d)", id, lsa, value, type); - - if (lsa >= SPU_LS_SIZE || type > 8 || !type || (type | lsa) & (type - 1)) // check range and alignment - { - return CELL_EINVAL; - } - - const auto [thread, group] = lv2_spu_group::get_thread(id); - - if (!thread) [[unlikely]] - { - return CELL_ESRCH; - } - - std::lock_guard lock(group->mutex); - - if (auto state = +group->run_state; - state < SPU_THREAD_GROUP_STATUS_WAITING || state > SPU_THREAD_GROUP_STATUS_RUNNING) - { - if (state == SPU_THREAD_GROUP_STATUS_DESTROYED) - { - return CELL_ESRCH; - } - - return CELL_ESTAT; - } - - switch (type) - { - case 1: thread->_ref(lsa) = static_cast(value); break; - case 2: thread->_ref(lsa) = static_cast(value); break; - case 4: thread->_ref(lsa) = static_cast(value); break; - case 8: thread->_ref(lsa) = value; break; - default: fmt::throw_exception("Unreachable"); - } - - return CELL_OK; -} - -error_code sys_spu_thread_read_ls(ppu_thread& ppu, u32 id, u32 lsa, vm::ptr value, u32 type) -{ - ppu.state += cpu_flag::wait; - - sys_spu.trace("sys_spu_thread_read_ls(id=0x%x, lsa=0x%05x, value=*0x%x, type=%d)", id, lsa, value, type); - - if (lsa >= SPU_LS_SIZE || type > 8 || !type || (type | lsa) & (type - 1)) // check range and alignment - { - return CELL_EINVAL; - } - - const auto [thread, group] = lv2_spu_group::get_thread(id); - - if (!thread) [[unlikely]] - { - return CELL_ESRCH; - } - - std::unique_lock lock(group->mutex); - - if (auto state = +group->run_state; - state < SPU_THREAD_GROUP_STATUS_WAITING || state > SPU_THREAD_GROUP_STATUS_RUNNING) - { - if (state == SPU_THREAD_GROUP_STATUS_DESTROYED) - { - return CELL_ESRCH; - } - - return CELL_ESTAT; - } - - u64 _value{}; - - switch (type) - { - case 1: _value = thread->_ref(lsa); break; - case 2: _value = thread->_ref(lsa); break; - case 4: _value = thread->_ref(lsa); break; - case 8: _value = thread->_ref(lsa); break; - default: fmt::throw_exception("Unreachable"); - } - - lock.unlock(); - ppu.check_state(); - - *value = _value; - return CELL_OK; -} - -error_code sys_spu_thread_write_spu_mb(ppu_thread& ppu, u32 id, u32 value) -{ - ppu.state += cpu_flag::wait; - - sys_spu.warning("sys_spu_thread_write_spu_mb(id=0x%x, value=0x%x)", id, value); - - const auto [thread, group] = lv2_spu_group::get_thread(id); - - if (!thread) [[unlikely]] - { - return CELL_ESRCH; - } - - spu_channel_op_state state{}; - { - std::lock_guard lock(group->mutex); - - state = thread->ch_in_mbox.push(value, true); - } - - if (!state.op_done) - { - ppu.state += cpu_flag::again; - return {}; - } - - if (state.notify) - { - thread->ch_in_mbox.notify(); - } - - return CELL_OK; -} - -error_code sys_spu_thread_set_spu_cfg(ppu_thread& ppu, u32 id, u64 value) -{ - ppu.state += cpu_flag::wait; - - sys_spu.warning("sys_spu_thread_set_spu_cfg(id=0x%x, value=0x%x)", id, value); - - if (value > 3) - { - return CELL_EINVAL; - } - - const auto [thread, group] = lv2_spu_group::get_thread(id); - - if (!thread) [[unlikely]] - { - return CELL_ESRCH; - } - - thread->snr_config = value; - - return CELL_OK; -} - -error_code sys_spu_thread_get_spu_cfg(ppu_thread& ppu, u32 id, vm::ptr value) -{ - ppu.state += cpu_flag::wait; - - sys_spu.warning("sys_spu_thread_get_spu_cfg(id=0x%x, value=*0x%x)", id, value); - - const auto [thread, group] = lv2_spu_group::get_thread(id); - - if (!thread) [[unlikely]] - { - return CELL_ESRCH; - } - - ppu.check_state(); - *value = thread->snr_config; - - return CELL_OK; -} - -error_code sys_spu_thread_write_snr(ppu_thread& ppu, u32 id, u32 number, u32 value) -{ - ppu.state += cpu_flag::wait; - - sys_spu.trace("sys_spu_thread_write_snr(id=0x%x, number=%d, value=0x%x)", id, number, value); - - if (number > 1) - { - return CELL_EINVAL; - } - - const auto [thread, group] = lv2_spu_group::get_thread(id); - - if (!thread) [[unlikely]] - { - return CELL_ESRCH; - } - - thread->push_snr(number, value); - - return CELL_OK; -} - -error_code sys_spu_thread_group_connect_event(ppu_thread& ppu, u32 id, u32 eq, u32 et) -{ - ppu.state += cpu_flag::wait; - - sys_spu.warning("sys_spu_thread_group_connect_event(id=0x%x, eq=0x%x, et=%d)", id, eq, et); - - const auto group = idm::get_unlocked(id); - - if (!group) - { - return CELL_ESRCH; - } - - const auto ep = - et == SYS_SPU_THREAD_GROUP_EVENT_SYSTEM_MODULE ? &group->ep_sysmodule : - et == SYS_SPU_THREAD_GROUP_EVENT_EXCEPTION ? &group->ep_exception : - et == SYS_SPU_THREAD_GROUP_EVENT_RUN ? &group->ep_run : - nullptr; - - if (!ep) - { - sys_spu.error("sys_spu_thread_group_connect_event(): unknown event type (%d)", et); - return CELL_EINVAL; - } - - if (et == SYS_SPU_THREAD_GROUP_EVENT_SYSTEM_MODULE && !(group->type & SYS_SPU_THREAD_GROUP_TYPE_COOPERATE_WITH_SYSTEM)) - { - return CELL_EINVAL; - } - - auto queue = idm::get_unlocked(eq); - - std::lock_guard lock(group->mutex); - - if (lv2_obj::check(*ep)) - { - return CELL_EBUSY; - } - - // ESRCH of event queue after EBUSY - if (!queue) - { - return CELL_ESRCH; - } - - *ep = std::move(queue); - return CELL_OK; -} - -error_code sys_spu_thread_group_disconnect_event(ppu_thread& ppu, u32 id, u32 et) -{ - ppu.state += cpu_flag::wait; - - sys_spu.warning("sys_spu_thread_group_disconnect_event(id=0x%x, et=%d)", id, et); - - const auto group = idm::get_unlocked(id); - - if (!group) - { - return CELL_ESRCH; - } - - const auto ep = - et == SYS_SPU_THREAD_GROUP_EVENT_SYSTEM_MODULE ? &group->ep_sysmodule : - et == SYS_SPU_THREAD_GROUP_EVENT_EXCEPTION ? &group->ep_exception : - et == SYS_SPU_THREAD_GROUP_EVENT_RUN ? &group->ep_run : - nullptr; - - if (!ep) - { - sys_spu.error("sys_spu_thread_group_disconnect_event(): unknown event type (%d)", et); - return CELL_OK; - } - - // No error checking is performed - - std::lock_guard lock(group->mutex); - - ep->reset(); - - return CELL_OK; -} - -error_code sys_spu_thread_connect_event(ppu_thread& ppu, u32 id, u32 eq, u32 et, u32 spup) -{ - ppu.state += cpu_flag::wait; - - sys_spu.warning("sys_spu_thread_connect_event(id=0x%x, eq=0x%x, et=%d, spup=%d)", id, eq, et, spup); - - const auto [thread, group] = lv2_spu_group::get_thread(id); - auto queue = idm::get_unlocked(eq); - - if (!queue || !thread) [[unlikely]] - { - return CELL_ESRCH; - } - - if (et != SYS_SPU_THREAD_EVENT_USER || spup > 63) - { - sys_spu.error("sys_spu_thread_connect_event(): invalid arguments (et=%d, spup=%d, queue->type=%d)", et, spup, queue->type); - return CELL_EINVAL; - } - - std::lock_guard lock(group->mutex); - - auto& port = thread->spup[spup]; - - if (lv2_obj::check(port)) - { - return CELL_EISCONN; - } - - port = std::move(queue); - - return CELL_OK; -} - -error_code sys_spu_thread_disconnect_event(ppu_thread& ppu, u32 id, u32 et, u32 spup) -{ - ppu.state += cpu_flag::wait; - - sys_spu.warning("sys_spu_thread_disconnect_event(id=0x%x, et=%d, spup=%d)", id, et, spup); - - const auto [thread, group] = lv2_spu_group::get_thread(id); - - if (!thread) [[unlikely]] - { - return CELL_ESRCH; - } - - if (et != SYS_SPU_THREAD_EVENT_USER || spup > 63) - { - sys_spu.error("sys_spu_thread_disconnect_event(): invalid arguments (et=%d, spup=%d)", et, spup); - return CELL_EINVAL; - } - - std::lock_guard lock(group->mutex); - - auto& port = thread->spup[spup]; - - if (!lv2_obj::check(port)) - { - return CELL_ENOTCONN; - } - - port.reset(); - - return CELL_OK; -} - -error_code sys_spu_thread_bind_queue(ppu_thread& ppu, u32 id, u32 spuq, u32 spuq_num) -{ - ppu.state += cpu_flag::wait; - - sys_spu.warning("sys_spu_thread_bind_queue(id=0x%x, spuq=0x%x, spuq_num=0x%x)", id, spuq, spuq_num); - - const auto [thread, group] = lv2_spu_group::get_thread(id); - auto queue = idm::get_unlocked(spuq); - ; - - if (!queue || !thread) [[unlikely]] - { - return CELL_ESRCH; - } - - if (queue->type != SYS_SPU_QUEUE) - { - return CELL_EINVAL; - } - - std::lock_guard lock(group->mutex); - - decltype(std::data(thread->spuq)) q{}; - - for (auto& v : thread->spuq) - { - // Check if the entry is assigned at all - if (!v.second) - { - if (!q) - { - q = &v; - } - - continue; - } - - if (v.first == spuq_num || v.second == queue) - { - return CELL_EBUSY; - } - } - - if (!q) - { - return CELL_EAGAIN; - } - - q->first = spuq_num; - q->second = std::move(queue); - return CELL_OK; -} - -error_code sys_spu_thread_unbind_queue(ppu_thread& ppu, u32 id, u32 spuq_num) -{ - ppu.state += cpu_flag::wait; - - sys_spu.warning("sys_spu_thread_unbind_queue(id=0x%x, spuq_num=0x%x)", id, spuq_num); - - const auto [thread, group] = lv2_spu_group::get_thread(id); - - if (!thread) [[unlikely]] - { - return CELL_ESRCH; - } - - std::lock_guard lock(group->mutex); - - for (auto& v : thread->spuq) - { - if (v.first != spuq_num) - { - continue; - } - - if (!v.second) - { - continue; - } - - v.second.reset(); - return CELL_OK; - } - - return CELL_ESRCH; -} - -error_code sys_spu_thread_group_connect_event_all_threads(ppu_thread& ppu, u32 id, u32 eq, u64 req, vm::ptr spup) -{ - ppu.state += cpu_flag::wait; - - sys_spu.warning("sys_spu_thread_group_connect_event_all_threads(id=0x%x, eq=0x%x, req=0x%llx, spup=*0x%x)", id, eq, req, spup); - - if (!req) - { - return CELL_EINVAL; - } - - const auto group = idm::get_unlocked(id); - const auto queue = idm::get_unlocked(eq); - - if (!group || !queue) - { - return CELL_ESRCH; - } - - std::unique_lock lock(group->mutex); - - if (auto state = +group->run_state; - state < SPU_THREAD_GROUP_STATUS_INITIALIZED || state == SPU_THREAD_GROUP_STATUS_DESTROYED) - { - if (state == SPU_THREAD_GROUP_STATUS_DESTROYED) - { - return CELL_ESRCH; - } - - return CELL_ESTAT; - } - - u8 port = 0; // SPU Port number - - for (; port < 64; port++) - { - if (!(req & (1ull << port))) - { - continue; - } - - bool found = true; - - for (auto& t : group->threads) - { - if (t) - { - if (lv2_obj::check(t->spup[port])) - { - found = false; - break; - } - } - } - - if (found) - { - break; - } - } - - if (port == 64) - { - return CELL_EISCONN; - } - - for (auto& t : group->threads) - { - if (t) - { - t->spup[port] = queue; - } - } - - lock.unlock(); - ppu.check_state(); - - *spup = port; - - return CELL_OK; -} - -error_code sys_spu_thread_group_disconnect_event_all_threads(ppu_thread& ppu, u32 id, u32 spup) -{ - ppu.state += cpu_flag::wait; - - sys_spu.warning("sys_spu_thread_group_disconnect_event_all_threads(id=0x%x, spup=%d)", id, spup); - - if (spup > 63) - { - return CELL_EINVAL; - } - - const auto group = idm::get_unlocked(id); - - if (!group) - { - return CELL_ESRCH; - } - - std::lock_guard lock(group->mutex); - - for (auto& t : group->threads) - { - if (t) - { - t->spup[spup].reset(); - } - } - - return CELL_OK; -} - -error_code sys_spu_thread_group_log(ppu_thread& ppu, s32 command, vm::ptr stat) -{ - ppu.state += cpu_flag::wait; - - sys_spu.warning("sys_spu_thread_group_log(command=0x%x, stat=*0x%x)", command, stat); - - struct spu_group_log_state_t - { - atomic_t state = SYS_SPU_THREAD_GROUP_LOG_ON; - }; - - auto& state = g_fxo->get(); - - switch (command) - { - case SYS_SPU_THREAD_GROUP_LOG_GET_STATUS: - { - if (!stat) - { - return CELL_EFAULT; - } - - ppu.check_state(); - *stat = state.state; - break; - } - case SYS_SPU_THREAD_GROUP_LOG_ON: - case SYS_SPU_THREAD_GROUP_LOG_OFF: - { - state.state.release(command); - break; - } - default: return CELL_EINVAL; - } - - return CELL_OK; -} - -error_code sys_spu_thread_recover_page_fault(ppu_thread& ppu, u32 id) -{ - ppu.state += cpu_flag::wait; - - sys_spu.warning("sys_spu_thread_recover_page_fault(id=0x%x)", id); - - const auto [thread, group] = lv2_spu_group::get_thread(id); - - if (!thread) [[unlikely]] - { - return CELL_ESRCH; - } - - return mmapper_thread_recover_page_fault(thread); -} - -error_code sys_raw_spu_recover_page_fault(ppu_thread& ppu, u32 id) -{ - ppu.state += cpu_flag::wait; - - sys_spu.warning("sys_raw_spu_recover_page_fault(id=0x%x)", id); - - const auto thread = idm::get_unlocked>(spu_thread::find_raw_spu(id)); - - if (!thread) [[unlikely]] - { - return CELL_ESRCH; - } - - return mmapper_thread_recover_page_fault(thread.get()); -} - -error_code sys_raw_spu_create(ppu_thread& ppu, vm::ptr id, vm::ptr attr) -{ - ppu.state += cpu_flag::wait; - - sys_spu.warning("sys_raw_spu_create(id=*0x%x, attr=*0x%x)", id, attr); - - auto& limits = g_fxo->get(); - - std::lock_guard lock(limits.mutex); - - if (!limits.check(limits_data{.raw_spu = 1})) - { - return CELL_EAGAIN; - } - - if (!spu_thread::g_raw_spu_ctr.try_inc(5)) - { - return CELL_EAGAIN; - } - - u32 index = 0; - - // Find free RawSPU ID - while (!spu_thread::g_raw_spu_id[index].try_inc(1)) - { - if (++index == 5) - index = 0; - } - - const auto spu = idm::make_ptr>(nullptr, index, "", index); - ensure(vm::get(vm::spu)->falloc(spu->vm_offset(), SPU_LS_SIZE, &spu->shm, vm::page_size_64k)); - spu->map_ls(*spu->shm, spu->ls); - - spu_thread::g_raw_spu_id[index] = idm::last_id(); - - ppu.check_state(); - *id = index; - - return CELL_OK; -} - -error_code sys_isolated_spu_create(ppu_thread& ppu, vm::ptr id, vm::ptr image, u64 arg1, u64 arg2, u64 arg3, u64 arg4) -{ - ppu.state += cpu_flag::wait; - - sys_spu.todo("sys_isolated_spu_create(id=*0x%x, image=*0x%x, arg1=0x%llx, arg2=0x%llx, arg3=0x%llx, arg4=0x%llx)", id, image, arg1, arg2, arg3, arg4); - - // TODO: More accurate SPU image memory size calculation - u32 max = image.addr() & -4096; - - while (max != 0u - 4096 && vm::check_addr(max)) - { - max += 4096; - } - - const auto obj = decrypt_self(fs::file{image.get_ptr(), max - image.addr()}); - - if (!obj) - { - return CELL_EAUTHFAIL; - } - - auto& limits = g_fxo->get(); - - std::lock_guard lock(limits.mutex); - - if (!limits.check(limits_data{.raw_spu = 1})) - { - return CELL_EAGAIN; - } - - if (!spu_thread::g_raw_spu_ctr.try_inc(5)) - { - return CELL_EAGAIN; - } - - u32 index = 0; - - // Find free RawSPU ID - while (!spu_thread::g_raw_spu_id[index].try_inc(1)) - { - if (++index == 5) - index = 0; - } - - const u32 ls_addr = RAW_SPU_BASE_ADDR + RAW_SPU_OFFSET * index; - - const auto thread = idm::make_ptr>(nullptr, index, "", index, true); - - thread->gpr[3] = v128::from64(0, arg1); - thread->gpr[4] = v128::from64(0, arg2); - thread->gpr[5] = v128::from64(0, arg3); - thread->gpr[6] = v128::from64(0, arg4); - - spu_thread::g_raw_spu_id[index] = (ensure(thread->id)); - - sys_spu_image img; - img.load(obj); - - auto image_info = idm::get_unlocked(img.entry_point); - img.deploy(thread->ls, std::span(image_info->segs.get_ptr(), image_info->nsegs)); - - thread->write_reg(ls_addr + RAW_SPU_PROB_OFFSET + SPU_NPC_offs, image_info->e_entry); - ensure(idm::remove_verify(img.entry_point, std::move(image_info))); - - *id = index; - return CELL_OK; -} - -template -error_code raw_spu_destroy(ppu_thread& ppu, u32 id) -{ - const u32 idm_id = spu_thread::find_raw_spu(id); - - auto thread = idm::get>(idm_id, [](named_thread& thread) - { - if (thread.get_type() != (isolated ? spu_type::isolated : spu_type::raw)) - { - return false; - } - - // Stop thread - thread = thread_state::aborting; - return true; - }); - - if (!thread || !thread.ret) [[unlikely]] - { - return CELL_ESRCH; - } - - // TODO: CELL_EBUSY is not returned - - // Kernel objects which must be removed - std::vector, u32>> to_remove; - - // Clear interrupt handlers - for (auto& intr : thread->int_ctrl) - { - if (auto& tag = intr.tag; lv2_obj::check(tag)) - { - if (auto& handler = tag->handler; lv2_obj::check(handler)) - { - // SLEEP - lv2_obj::sleep(ppu); - handler->join(); - to_remove.emplace_back(handler, handler->id); - } - - to_remove.emplace_back(tag, tag->id); - } - } - - // Remove IDs - for (auto&& pair : to_remove) - { - if (pair.second >> 24 == 0xa) - idm::remove_verify(pair.second, std::move(pair.first)); - if (pair.second >> 24 == 0xb) - idm::remove_verify(pair.second, std::move(pair.first)); - } - - (*thread)(); - - auto& limits = g_fxo->get(); - - std::lock_guard lock(limits.mutex); - - if (auto ret = idm::withdraw>(idm_id, [&](spu_thread& spu) -> CellError - { - if (std::addressof(spu) != std::addressof(*thread)) - { - return CELL_ESRCH; - } - - spu.cleanup(); - return {}; - }); - !ret || ret.ret) - { - // Other thread destroyed beforehead - return CELL_ESRCH; - } - - return CELL_OK; -} - -error_code sys_raw_spu_destroy(ppu_thread& ppu, u32 id) -{ - ppu.state += cpu_flag::wait; - - sys_spu.warning("sys_raw_spu_destroy(id=%d)", id); - - return raw_spu_destroy(ppu, id); -} - -error_code sys_isolated_spu_destroy(ppu_thread& ppu, u32 id) -{ - ppu.state += cpu_flag::wait; - - sys_spu.todo("sys_isolated_spu_destroy(id=%d)", id); - - return raw_spu_destroy(ppu, id); -} - -template -error_code raw_spu_create_interrupt_tag(u32 id, u32 class_id, u32 /*hwthread*/, vm::ptr intrtag) -{ - if (class_id != 0 && class_id != 2) - { - return CELL_EINVAL; - } - - CellError error = {}; - - const auto tag = idm::import ([&]() - { - shared_ptr result; - - auto thread = idm::check_unlocked>(spu_thread::find_raw_spu(id)); - - if (!thread || *thread == thread_state::aborting || thread->get_type() != (isolated ? spu_type::isolated : spu_type::raw)) - { - error = CELL_ESRCH; - return result; - } - - auto& int_ctrl = thread->int_ctrl[class_id]; - - if (lv2_obj::check(int_ctrl.tag)) - { - error = CELL_EAGAIN; - return result; - } - - result = make_single(); - int_ctrl.tag = result; - return result; - }); - - if (tag) - { - cpu_thread::get_current()->check_state(); - *intrtag = tag; - return CELL_OK; - } - - return error; -} - -error_code sys_raw_spu_create_interrupt_tag(ppu_thread& ppu, u32 id, u32 class_id, u32 hwthread, vm::ptr intrtag) -{ - ppu.state += cpu_flag::wait; - - sys_spu.warning("sys_raw_spu_create_interrupt_tag(id=%d, class_id=%d, hwthread=0x%x, intrtag=*0x%x)", id, class_id, hwthread, intrtag); - - return raw_spu_create_interrupt_tag(id, class_id, hwthread, intrtag); -} - -error_code sys_isolated_spu_create_interrupt_tag(ppu_thread& ppu, u32 id, u32 class_id, u32 hwthread, vm::ptr intrtag) -{ - ppu.state += cpu_flag::wait; - - sys_spu.todo("sys_isolated_spu_create_interrupt_tag(id=%d, class_id=%d, hwthread=0x%x, intrtag=*0x%x)", id, class_id, hwthread, intrtag); - - return raw_spu_create_interrupt_tag(id, class_id, hwthread, intrtag); -} - -template -error_code raw_spu_set_int_mask(u32 id, u32 class_id, u64 mask) -{ - if (class_id != 0 && class_id != 2) - { - return CELL_EINVAL; - } - - const auto thread = idm::get_unlocked>(spu_thread::find_raw_spu(id)); - - if (!thread || thread->get_type() != (isolated ? spu_type::isolated : spu_type::raw)) [[unlikely]] - { - return CELL_ESRCH; - } - - thread->int_ctrl[class_id].mask.exchange(mask); - - return CELL_OK; -} - -error_code sys_raw_spu_set_int_mask(ppu_thread& ppu, u32 id, u32 class_id, u64 mask) -{ - ppu.state += cpu_flag::wait; - - sys_spu.trace("sys_raw_spu_set_int_mask(id=%d, class_id=%d, mask=0x%llx)", id, class_id, mask); - - return raw_spu_set_int_mask(id, class_id, mask); -} - -error_code sys_isolated_spu_set_int_mask(ppu_thread& ppu, u32 id, u32 class_id, u64 mask) -{ - ppu.state += cpu_flag::wait; - - sys_spu.todo("sys_isolated_spu_set_int_mask(id=%d, class_id=%d, mask=0x%llx)", id, class_id, mask); - - return raw_spu_set_int_mask(id, class_id, mask); -} - -template -error_code raw_spu_set_int_stat(u32 id, u32 class_id, u64 stat) -{ - if (class_id != 0 && class_id != 2) - { - return CELL_EINVAL; - } - - const auto thread = idm::get_unlocked>(spu_thread::find_raw_spu(id)); - - if (!thread || thread->get_type() != (isolated ? spu_type::isolated : spu_type::raw)) [[unlikely]] - { - return CELL_ESRCH; - } - - thread->int_ctrl[class_id].clear(stat); - - return CELL_OK; -} - -error_code sys_raw_spu_set_int_stat(ppu_thread& ppu, u32 id, u32 class_id, u64 stat) -{ - ppu.state += cpu_flag::wait; - - sys_spu.trace("sys_raw_spu_set_int_stat(id=%d, class_id=%d, stat=0x%llx)", id, class_id, stat); - - return raw_spu_set_int_stat(id, class_id, stat); -} - -error_code sys_isolated_spu_set_int_stat(ppu_thread& ppu, u32 id, u32 class_id, u64 stat) -{ - ppu.state += cpu_flag::wait; - - sys_spu.todo("sys_isolated_spu_set_int_stat(id=%d, class_id=%d, stat=0x%llx)", id, class_id, stat); - - return raw_spu_set_int_stat(id, class_id, stat); -} - -template -error_code raw_spu_get_int_control(u32 id, u32 class_id, vm::ptr value, atomic_t spu_int_ctrl_t::* control) -{ - if (class_id != 0 && class_id != 2) - { - return CELL_EINVAL; - } - - const auto thread = idm::get_unlocked>(spu_thread::find_raw_spu(id)); - - if (!thread || thread->get_type() != (isolated ? spu_type::isolated : spu_type::raw)) [[unlikely]] - { - return CELL_ESRCH; - } - - cpu_thread::get_current()->check_state(); - *value = thread->int_ctrl[class_id].*control; - - return CELL_OK; -} - -error_code sys_raw_spu_get_int_mask(ppu_thread& ppu, u32 id, u32 class_id, vm::ptr mask) -{ - ppu.state += cpu_flag::wait; - - sys_spu.trace("sys_raw_spu_get_int_mask(id=%d, class_id=%d, mask=*0x%x)", id, class_id, mask); - - return raw_spu_get_int_control(id, class_id, mask, &spu_int_ctrl_t::mask); -} - -error_code sys_isolated_spu_get_int_mask(ppu_thread& ppu, u32 id, u32 class_id, vm::ptr mask) -{ - ppu.state += cpu_flag::wait; - - sys_spu.trace("sys_isolated_spu_get_int_mask(id=%d, class_id=%d, mask=*0x%x)", id, class_id, mask); - - return raw_spu_get_int_control(id, class_id, mask, &spu_int_ctrl_t::mask); -} - -error_code sys_raw_spu_get_int_stat(ppu_thread& ppu, u32 id, u32 class_id, vm::ptr stat) -{ - ppu.state += cpu_flag::wait; - - sys_spu.trace("sys_raw_spu_get_int_stat(id=%d, class_id=%d, stat=*0x%x)", id, class_id, stat); - - return raw_spu_get_int_control(id, class_id, stat, &spu_int_ctrl_t::stat); -} - -error_code sys_isolated_spu_get_int_stat(ppu_thread& ppu, u32 id, u32 class_id, vm::ptr stat) -{ - ppu.state += cpu_flag::wait; - - sys_spu.todo("sys_isolated_spu_get_int_stat(id=%d, class_id=%d, stat=*0x%x)", id, class_id, stat); - - return raw_spu_get_int_control(id, class_id, stat, &spu_int_ctrl_t::stat); -} - -template -error_code raw_spu_read_puint_mb(u32 id, vm::ptr value) -{ - const auto thread = idm::get_unlocked>(spu_thread::find_raw_spu(id)); - - if (!thread || thread->get_type() != (isolated ? spu_type::isolated : spu_type::raw)) [[unlikely]] - { - return CELL_ESRCH; - } - - cpu_thread::get_current()->check_state(); - *value = thread->ch_out_intr_mbox.pop(); - - return CELL_OK; -} - -error_code sys_raw_spu_read_puint_mb(ppu_thread& ppu, u32 id, vm::ptr value) -{ - ppu.state += cpu_flag::wait; - - sys_spu.trace("sys_raw_spu_read_puint_mb(id=%d, value=*0x%x)", id, value); - - return raw_spu_read_puint_mb(id, value); -} - -error_code sys_isolated_spu_read_puint_mb(ppu_thread& ppu, u32 id, vm::ptr value) -{ - ppu.state += cpu_flag::wait; - - sys_spu.todo("sys_isolated_spu_read_puint_mb(id=%d, value=*0x%x)", id, value); - - return raw_spu_read_puint_mb(id, value); -} - -template -error_code raw_spu_set_spu_cfg(u32 id, u32 value) -{ - if (value > 3) - { - fmt::throw_exception("Unexpected value (0x%x)", value); - } - - const auto thread = idm::get_unlocked>(spu_thread::find_raw_spu(id)); - - if (!thread || thread->get_type() != (isolated ? spu_type::isolated : spu_type::raw)) [[unlikely]] - { - return CELL_ESRCH; - } - - thread->snr_config = value; - - return CELL_OK; -} - -error_code sys_raw_spu_set_spu_cfg(ppu_thread& ppu, u32 id, u32 value) -{ - ppu.state += cpu_flag::wait; - - sys_spu.trace("sys_raw_spu_set_spu_cfg(id=%d, value=0x%x)", id, value); - - return raw_spu_set_spu_cfg(id, value); -} - -error_code sys_isolated_spu_set_spu_cfg(ppu_thread& ppu, u32 id, u32 value) -{ - ppu.state += cpu_flag::wait; - - sys_spu.todo("sys_isolated_spu_set_spu_cfg(id=%d, value=0x%x)", id, value); - - return raw_spu_set_spu_cfg(id, value); -} - -template -error_code raw_spu_get_spu_cfg(u32 id, vm::ptr value) -{ - const auto thread = idm::get_unlocked>(spu_thread::find_raw_spu(id)); - - if (!thread || thread->get_type() != (isolated ? spu_type::isolated : spu_type::raw)) [[unlikely]] - { - return CELL_ESRCH; - } - - cpu_thread::get_current()->check_state(); - *value = static_cast(thread->snr_config); - - return CELL_OK; -} - -error_code sys_raw_spu_get_spu_cfg(ppu_thread& ppu, u32 id, vm::ptr value) -{ - ppu.state += cpu_flag::wait; - - sys_spu.trace("sys_raw_spu_get_spu_afg(id=%d, value=*0x%x)", id, value); - - return raw_spu_get_spu_cfg(id, value); -} - -error_code sys_isolated_spu_get_spu_cfg(ppu_thread& ppu, u32 id, vm::ptr value) -{ - ppu.state += cpu_flag::wait; - - sys_spu.todo("sys_isolated_spu_get_spu_afg(id=%d, value=*0x%x)", id, value); - - return raw_spu_get_spu_cfg(id, value); -} - -error_code sys_isolated_spu_start(ppu_thread& ppu, u32 id) -{ - ppu.state += cpu_flag::wait; - - sys_spu.todo("sys_isolated_spu_start(id=%d)", id); - - const auto thread = idm::get_unlocked>(spu_thread::find_raw_spu(id)); - - if (!thread) [[unlikely]] - { - return CELL_ESRCH; - } - - // TODO: Can return ESTAT if called twice - thread->write_reg(RAW_SPU_BASE_ADDR + thread->lv2_id * RAW_SPU_OFFSET + RAW_SPU_PROB_OFFSET + SPU_RunCntl_offs, SPU_RUNCNTL_RUN_REQUEST); - return CELL_OK; -} diff --git a/rpcs3/Emu/Cell/lv2/sys_spu.h b/rpcs3/Emu/Cell/lv2/sys_spu.h deleted file mode 100644 index c72b99de2..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_spu.h +++ /dev/null @@ -1,400 +0,0 @@ -#pragma once - -#include "sys_sync.h" -#include "sys_event.h" -#include "Emu/Cell/SPUThread.h" -#include "Emu/Cell/ErrorCodes.h" - -#include "Emu/Memory/vm_ptr.h" -#include "util/File.h" - -#include - -struct lv2_memory_container; - -enum : s32 -{ - SYS_SPU_THREAD_GROUP_TYPE_NORMAL = 0x00, - // SYS_SPU_THREAD_GROUP_TYPE_SEQUENTIAL = 0x01, doesn't exist - SYS_SPU_THREAD_GROUP_TYPE_SYSTEM = 0x02, - SYS_SPU_THREAD_GROUP_TYPE_MEMORY_FROM_CONTAINER = 0x04, - SYS_SPU_THREAD_GROUP_TYPE_NON_CONTEXT = 0x08, - SYS_SPU_THREAD_GROUP_TYPE_EXCLUSIVE_NON_CONTEXT = 0x18, - SYS_SPU_THREAD_GROUP_TYPE_COOPERATE_WITH_SYSTEM = 0x20, -}; - -enum -{ - SYS_SPU_THREAD_GROUP_JOIN_GROUP_EXIT = 0x0001, - SYS_SPU_THREAD_GROUP_JOIN_ALL_THREADS_EXIT = 0x0002, - SYS_SPU_THREAD_GROUP_JOIN_TERMINATED = 0x0004 -}; - -enum -{ - SYS_SPU_THREAD_GROUP_EVENT_RUN = 1, - SYS_SPU_THREAD_GROUP_EVENT_EXCEPTION = 2, - SYS_SPU_THREAD_GROUP_EVENT_SYSTEM_MODULE = 4, -}; - -enum : u64 -{ - SYS_SPU_THREAD_GROUP_EVENT_RUN_KEY = 0xFFFFFFFF53505500ull, - SYS_SPU_THREAD_GROUP_EVENT_EXCEPTION_KEY = 0xFFFFFFFF53505503ull, - SYS_SPU_THREAD_GROUP_EVENT_SYSTEM_MODULE_KEY = 0xFFFFFFFF53505504ull, -}; - -enum -{ - SYS_SPU_THREAD_GROUP_LOG_ON = 0x0, - SYS_SPU_THREAD_GROUP_LOG_OFF = 0x1, - SYS_SPU_THREAD_GROUP_LOG_GET_STATUS = 0x2, -}; - -enum spu_group_status : u32 -{ - SPU_THREAD_GROUP_STATUS_NOT_INITIALIZED, - SPU_THREAD_GROUP_STATUS_INITIALIZED, - SPU_THREAD_GROUP_STATUS_READY, - SPU_THREAD_GROUP_STATUS_WAITING, - SPU_THREAD_GROUP_STATUS_SUSPENDED, - SPU_THREAD_GROUP_STATUS_WAITING_AND_SUSPENDED, - SPU_THREAD_GROUP_STATUS_RUNNING, - SPU_THREAD_GROUP_STATUS_STOPPED, - SPU_THREAD_GROUP_STATUS_DESTROYED, // Internal state - SPU_THREAD_GROUP_STATUS_UNKNOWN, -}; - -enum : s32 -{ - SYS_SPU_SEGMENT_TYPE_COPY = 1, - SYS_SPU_SEGMENT_TYPE_FILL = 2, - SYS_SPU_SEGMENT_TYPE_INFO = 4, -}; - -enum spu_stop_syscall : u32 -{ - SYS_SPU_THREAD_STOP_YIELD = 0x0100, - SYS_SPU_THREAD_STOP_GROUP_EXIT = 0x0101, - SYS_SPU_THREAD_STOP_THREAD_EXIT = 0x0102, - SYS_SPU_THREAD_STOP_RECEIVE_EVENT = 0x0110, - SYS_SPU_THREAD_STOP_TRY_RECEIVE_EVENT = 0x0111, - SYS_SPU_THREAD_STOP_SWITCH_SYSTEM_MODULE = 0x0120, -}; - -struct sys_spu_thread_group_attribute -{ - be_t nsize; // name length including NULL terminator - vm::bcptr name; - be_t type; - be_t ct; // memory container id -}; - -enum : u32 -{ - SYS_SPU_THREAD_OPTION_NONE = 0, - SYS_SPU_THREAD_OPTION_ASYNC_INTR_ENABLE = 1, - SYS_SPU_THREAD_OPTION_DEC_SYNC_TB_ENABLE = 2, -}; - -struct sys_spu_thread_attribute -{ - vm::bcptr name; - be_t name_len; - be_t option; -}; - -struct sys_spu_thread_argument -{ - be_t arg1; - be_t arg2; - be_t arg3; - be_t arg4; -}; - -struct sys_spu_segment -{ - ENABLE_BITWISE_SERIALIZATION; - - be_t type; // copy, fill, info - be_t ls; // local storage address - be_t size; - - union - { - be_t addr; // address or fill value - u64 pad; - }; -}; - -CHECK_SIZE(sys_spu_segment, 0x18); - -enum : u32 -{ - SYS_SPU_IMAGE_TYPE_USER = 0, - SYS_SPU_IMAGE_TYPE_KERNEL = 1, -}; - -struct sys_spu_image -{ - be_t type; // user, kernel - be_t entry_point; // Note: in kernel mode it's used to store id - vm::bptr segs; - be_t nsegs; - - template - static s32 get_nsegs(const Phdrs& phdrs) - { - s32 num_segs = 0; - - for (const auto& phdr : phdrs) - { - if (phdr.p_type != 1u && phdr.p_type != 4u) - { - return -1; - } - - if (phdr.p_type == 1u && phdr.p_filesz != phdr.p_memsz && phdr.p_filesz) - { - num_segs += 2; - } - else if (phdr.p_type == 1u || CountInfo) - { - num_segs += 1; - } - } - - return num_segs; - } - - template - static s32 fill(vm::ptr segs, s32 nsegs, const Phdrs& phdrs, u32 src) - { - s32 num_segs = 0; - - for (const auto& phdr : phdrs) - { - if (phdr.p_type == 1u) - { - if (phdr.p_filesz) - { - if (num_segs >= nsegs) - { - return -2; - } - - auto* seg = &segs[num_segs++]; - seg->type = SYS_SPU_SEGMENT_TYPE_COPY; - seg->ls = static_cast(phdr.p_vaddr); - seg->size = static_cast(phdr.p_filesz); - seg->addr = static_cast(phdr.p_offset + src); - } - - if (phdr.p_memsz > phdr.p_filesz) - { - if (num_segs >= nsegs) - { - return -2; - } - - auto* seg = &segs[num_segs++]; - seg->type = SYS_SPU_SEGMENT_TYPE_FILL; - seg->ls = static_cast(phdr.p_vaddr + phdr.p_filesz); - seg->size = static_cast(phdr.p_memsz - phdr.p_filesz); - seg->addr = 0; - } - } - else if (WriteInfo && phdr.p_type == 4u) - { - if (num_segs >= nsegs) - { - return -2; - } - - auto* seg = &segs[num_segs++]; - seg->type = SYS_SPU_SEGMENT_TYPE_INFO; - seg->size = 0x20; - seg->addr = static_cast(phdr.p_offset + 0x14 + src); - } - else if (phdr.p_type != 4u) - { - return -1; - } - } - - return num_segs; - } - - void load(const fs::file& stream); - void free() const; - static void deploy(u8* loc, std::span segs, bool is_verbose = true); -}; - -enum : u32 -{ - SYS_SPU_IMAGE_PROTECT = 0, - SYS_SPU_IMAGE_DIRECT = 1, -}; - -struct lv2_spu_image : lv2_obj -{ - static const u32 id_base = 0x22000000; - - const u32 e_entry; - const vm::ptr segs; - const s32 nsegs; - - lv2_spu_image(u32 entry, vm::ptr segs, s32 nsegs) - : e_entry(entry), segs(segs), nsegs(nsegs) - { - } - - lv2_spu_image(utils::serial& ar); - void save(utils::serial& ar); -}; - -struct sys_spu_thread_group_syscall_253_info -{ - be_t deadlineMeetCounter; // From cellSpursGetInfo - be_t deadlineMissCounter; // Same - be_t timestamp; - be_t _x10[6]; -}; - -struct lv2_spu_group -{ - static const u32 id_base = 0x04000100; - static const u32 id_step = 0x100; - static const u32 id_count = 255; - static constexpr std::pair id_invl_range = {0, 8}; - - static_assert(spu_thread::id_count == id_count * 6 + 5); - - const std::string name; - const u32 id; - const u32 max_num; - const u32 mem_size; - const s32 type; // SPU Thread Group Type - lv2_memory_container* const ct; // Memory Container - const bool has_scheduler_context; - u32 max_run; - - shared_mutex mutex; - - atomic_t init; // Initialization Counter - atomic_t prio{}; // SPU Thread Group Priority - atomic_t run_state; // SPU Thread Group State - atomic_t exit_status; // SPU Thread Group Exit Status - atomic_t join_state; // flags used to detect exit cause and signal - atomic_t running = 0; // Number of running threads - atomic_t spurs_running = 0; - atomic_t stop_count = 0; - atomic_t wait_term_count = 0; - u32 waiter_spu_index = -1; // Index of SPU executing a waiting syscall - class ppu_thread* waiter = nullptr; - bool set_terminate = false; - - std::array>, 8> threads; // SPU Threads - std::array threads_map; // SPU Threads map based number - std::array>, 8> imgs; // Entry points, SPU image segments - std::array, 8> args; // SPU Thread Arguments - - shared_ptr ep_run; // port for SYS_SPU_THREAD_GROUP_EVENT_RUN events - shared_ptr ep_exception; // TODO: SYS_SPU_THREAD_GROUP_EVENT_EXCEPTION - shared_ptr ep_sysmodule; // TODO: SYS_SPU_THREAD_GROUP_EVENT_SYSTEM_MODULE - - lv2_spu_group(std::string name, u32 num, s32 _prio, s32 type, lv2_memory_container* ct, bool uses_scheduler, u32 mem_size) noexcept - : name(std::move(name)), id(idm::last_id()), max_num(num), mem_size(mem_size), type(type), ct(ct), has_scheduler_context(uses_scheduler), max_run(num), init(0), run_state(SPU_THREAD_GROUP_STATUS_NOT_INITIALIZED), exit_status(0), join_state(0), args({}) - { - threads_map.fill(-1); - prio.raw().prio = _prio; - } - - SAVESTATE_INIT_POS(8); // Dependency on SPUs - - lv2_spu_group(utils::serial& ar) noexcept; - void save(utils::serial& ar); - - CellError send_run_event(u64 data1, u64 data2, u64 data3) const - { - return ep_run ? ep_run->send(SYS_SPU_THREAD_GROUP_EVENT_RUN_KEY, data1, data2, data3) : CELL_ENOTCONN; - } - - CellError send_exception_event(u64 data1, u64 data2, u64 data3) const - { - return ep_exception ? ep_exception->send(SYS_SPU_THREAD_GROUP_EVENT_EXCEPTION_KEY, data1, data2, data3) : CELL_ENOTCONN; - } - - CellError send_sysmodule_event(u64 data1, u64 data2, u64 data3) const - { - return ep_sysmodule ? ep_sysmodule->send(SYS_SPU_THREAD_GROUP_EVENT_SYSTEM_MODULE_KEY, data1, data2, data3) : CELL_ENOTCONN; - } - - static std::pair*, shared_ptr> get_thread(u32 id); -}; - -class ppu_thread; - -// Syscalls - -error_code sys_spu_initialize(ppu_thread&, u32 max_usable_spu, u32 max_raw_spu); -error_code _sys_spu_image_get_information(ppu_thread&, vm::ptr img, vm::ptr entry_point, vm::ptr nsegs); -error_code sys_spu_image_open(ppu_thread&, vm::ptr img, vm::cptr path); -error_code _sys_spu_image_import(ppu_thread&, vm::ptr img, u32 src, u32 size, u32 arg4); -error_code _sys_spu_image_close(ppu_thread&, vm::ptr img); -error_code _sys_spu_image_get_segments(ppu_thread&, vm::ptr img, vm::ptr segments, s32 nseg); -error_code sys_spu_thread_initialize(ppu_thread&, vm::ptr thread, u32 group, u32 spu_num, vm::ptr, vm::ptr, vm::ptr); -error_code sys_spu_thread_set_argument(ppu_thread&, u32 id, vm::ptr arg); -error_code sys_spu_thread_group_create(ppu_thread&, vm::ptr id, u32 num, s32 prio, vm::ptr attr); -error_code sys_spu_thread_group_destroy(ppu_thread&, u32 id); -error_code sys_spu_thread_group_start(ppu_thread&, u32 id); -error_code sys_spu_thread_group_suspend(ppu_thread&, u32 id); -error_code sys_spu_thread_group_resume(ppu_thread&, u32 id); -error_code sys_spu_thread_group_yield(ppu_thread&, u32 id); -error_code sys_spu_thread_group_terminate(ppu_thread&, u32 id, s32 value); -error_code sys_spu_thread_group_join(ppu_thread&, u32 id, vm::ptr cause, vm::ptr status); -error_code sys_spu_thread_group_set_priority(ppu_thread&, u32 id, s32 priority); -error_code sys_spu_thread_group_get_priority(ppu_thread&, u32 id, vm::ptr priority); -error_code sys_spu_thread_group_connect_event(ppu_thread&, u32 id, u32 eq, u32 et); -error_code sys_spu_thread_group_disconnect_event(ppu_thread&, u32 id, u32 et); -error_code sys_spu_thread_group_connect_event_all_threads(ppu_thread&, u32 id, u32 eq_id, u64 req, vm::ptr spup); -error_code sys_spu_thread_group_disconnect_event_all_threads(ppu_thread&, u32 id, u32 spup); -error_code sys_spu_thread_group_set_cooperative_victims(ppu_thread&, u32 id, u32 threads_mask); -error_code sys_spu_thread_group_syscall_253(ppu_thread& ppu, u32 id, vm::ptr info); -error_code sys_spu_thread_group_log(ppu_thread&, s32 command, vm::ptr stat); -error_code sys_spu_thread_write_ls(ppu_thread&, u32 id, u32 lsa, u64 value, u32 type); -error_code sys_spu_thread_read_ls(ppu_thread&, u32 id, u32 lsa, vm::ptr value, u32 type); -error_code sys_spu_thread_write_spu_mb(ppu_thread&, u32 id, u32 value); -error_code sys_spu_thread_set_spu_cfg(ppu_thread&, u32 id, u64 value); -error_code sys_spu_thread_get_spu_cfg(ppu_thread&, u32 id, vm::ptr value); -error_code sys_spu_thread_write_snr(ppu_thread&, u32 id, u32 number, u32 value); -error_code sys_spu_thread_connect_event(ppu_thread&, u32 id, u32 eq, u32 et, u32 spup); -error_code sys_spu_thread_disconnect_event(ppu_thread&, u32 id, u32 et, u32 spup); -error_code sys_spu_thread_bind_queue(ppu_thread&, u32 id, u32 spuq, u32 spuq_num); -error_code sys_spu_thread_unbind_queue(ppu_thread&, u32 id, u32 spuq_num); -error_code sys_spu_thread_get_exit_status(ppu_thread&, u32 id, vm::ptr status); -error_code sys_spu_thread_recover_page_fault(ppu_thread&, u32 id); - -error_code sys_raw_spu_create(ppu_thread&, vm::ptr id, vm::ptr attr); -error_code sys_raw_spu_destroy(ppu_thread& ppu, u32 id); -error_code sys_raw_spu_create_interrupt_tag(ppu_thread&, u32 id, u32 class_id, u32 hwthread, vm::ptr intrtag); -error_code sys_raw_spu_set_int_mask(ppu_thread&, u32 id, u32 class_id, u64 mask); -error_code sys_raw_spu_get_int_mask(ppu_thread&, u32 id, u32 class_id, vm::ptr mask); -error_code sys_raw_spu_set_int_stat(ppu_thread&, u32 id, u32 class_id, u64 stat); -error_code sys_raw_spu_get_int_stat(ppu_thread&, u32 id, u32 class_id, vm::ptr stat); -error_code sys_raw_spu_read_puint_mb(ppu_thread&, u32 id, vm::ptr value); -error_code sys_raw_spu_set_spu_cfg(ppu_thread&, u32 id, u32 value); -error_code sys_raw_spu_get_spu_cfg(ppu_thread&, u32 id, vm::ptr value); -error_code sys_raw_spu_recover_page_fault(ppu_thread&, u32 id); - -error_code sys_isolated_spu_create(ppu_thread&, vm::ptr id, vm::ptr image, u64 arg1, u64 arg2, u64 arg3, u64 arg4); -error_code sys_isolated_spu_start(ppu_thread&, u32 id); -error_code sys_isolated_spu_destroy(ppu_thread& ppu, u32 id); -error_code sys_isolated_spu_create_interrupt_tag(ppu_thread&, u32 id, u32 class_id, u32 hwthread, vm::ptr intrtag); -error_code sys_isolated_spu_set_int_mask(ppu_thread&, u32 id, u32 class_id, u64 mask); -error_code sys_isolated_spu_get_int_mask(ppu_thread&, u32 id, u32 class_id, vm::ptr mask); -error_code sys_isolated_spu_set_int_stat(ppu_thread&, u32 id, u32 class_id, u64 stat); -error_code sys_isolated_spu_get_int_stat(ppu_thread&, u32 id, u32 class_id, vm::ptr stat); -error_code sys_isolated_spu_read_puint_mb(ppu_thread&, u32 id, vm::ptr value); -error_code sys_isolated_spu_set_spu_cfg(ppu_thread&, u32 id, u32 value); -error_code sys_isolated_spu_get_spu_cfg(ppu_thread&, u32 id, vm::ptr value); diff --git a/rpcs3/Emu/Cell/lv2/sys_ss.cpp b/rpcs3/Emu/Cell/lv2/sys_ss.cpp deleted file mode 100644 index 4ed94e146..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_ss.cpp +++ /dev/null @@ -1,566 +0,0 @@ -#include "stdafx.h" -#include "sys_ss.h" - -#include "sys_process.h" -#include "Emu/IdManager.h" -#include "Emu/Cell/timers.hpp" -#include "Emu/system_config.h" -#include "util/sysinfo.hpp" - -#include -#include -#include - -#ifdef _WIN32 -#include -#include -#endif - -struct lv2_update_manager -{ - lv2_update_manager() - { - std::string version_str = utils::get_firmware_version(); - - // For example, 4.90 should be converted to 0x4900000000000 - std::erase(version_str, '.'); - if (std::from_chars(version_str.data(), version_str.data() + version_str.size(), system_sw_version, 16).ec != std::errc{}) - system_sw_version <<= 40; - else - system_sw_version = 0; - } - - lv2_update_manager(const lv2_update_manager&) = delete; - lv2_update_manager& operator=(const lv2_update_manager&) = delete; - ~lv2_update_manager() = default; - - u64 system_sw_version; - - std::unordered_map eeprom_map // offset, value - { - // system language - // *i think* this gives english - {0x48C18, 0x00}, - {0x48C19, 0x00}, - {0x48C1A, 0x00}, - {0x48C1B, 0x01}, - // system language end - - // vsh target (seems it can be 0xFFFFFFFE, 0xFFFFFFFF, 0x00000001 default: 0x00000000 / vsh sets it to 0x00000000 on boot if it isn't 0x00000000) - {0x48C1C, 0x00}, - {0x48C1D, 0x00}, - {0x48C1E, 0x00}, - {0x48C1F, 0x00} - // vsh target end - }; - mutable std::shared_mutex eeprom_mutex; - - std::unordered_set malloc_set; - mutable std::shared_mutex malloc_mutex; - - // return address - u32 allocate(u32 size) - { - std::unique_lock unique_lock(malloc_mutex); - - if (const auto addr = vm::alloc(size, vm::main); addr) - { - malloc_set.emplace(addr); - return addr; - } - - return 0; - } - - // return size - u32 deallocate(u32 addr) - { - std::unique_lock unique_lock(malloc_mutex); - - if (malloc_set.count(addr)) - { - return vm::dealloc(addr, vm::main); - } - - return 0; - } -}; - -template <> -void fmt_class_string::format(std::string& out, u64 arg) -{ - format_enum(out, arg, [](auto error) - { - switch (error) - { - STR_CASE(SYS_SS_RNG_ERROR_INVALID_PKG); - STR_CASE(SYS_SS_RNG_ERROR_ENOMEM); - STR_CASE(SYS_SS_RNG_ERROR_EAGAIN); - STR_CASE(SYS_SS_RNG_ERROR_EFAULT); - STR_CASE(SYS_SS_RTC_ERROR_UNK); - } - - return unknown; - }); -} - -LOG_CHANNEL(sys_ss); - -error_code sys_ss_random_number_generator(u64 pkg_id, vm::ptr buf, u64 size) -{ - sys_ss.warning("sys_ss_random_number_generator(pkg_id=%u, buf=*0x%x, size=0x%x)", pkg_id, buf, size); - - if (pkg_id != 2) - { - if (pkg_id == 1) - { - if (!g_ps3_process_info.has_root_perm()) - { - return CELL_ENOSYS; - } - - sys_ss.todo("sys_ss_random_number_generator(): pkg_id=1"); - std::memset(buf.get_ptr(), 0, 0x18); - return CELL_OK; - } - - return SYS_SS_RNG_ERROR_INVALID_PKG; - } - - // TODO - if (size > 0x10000000) - { - return SYS_SS_RNG_ERROR_ENOMEM; - } - - std::unique_ptr temp(new u8[size]); - -#ifdef _WIN32 - if (auto ret = BCryptGenRandom(nullptr, temp.get(), static_cast(size), BCRYPT_USE_SYSTEM_PREFERRED_RNG)) - { - fmt::throw_exception("sys_ss_random_number_generator(): BCryptGenRandom failed (0x%08x)", ret); - } -#else - fs::file rnd{"/dev/urandom"}; - - if (!rnd || rnd.read(temp.get(), size) != size) - { - fmt::throw_exception("sys_ss_random_number_generator(): Failed to generate pseudo-random numbers"); - } -#endif - - std::memcpy(buf.get_ptr(), temp.get(), size); - return CELL_OK; -} - -error_code sys_ss_access_control_engine(u64 pkg_id, u64 a2, u64 a3) -{ - sys_ss.success("sys_ss_access_control_engine(pkg_id=0x%llx, a2=0x%llx, a3=0x%llx)", pkg_id, a2, a3); - - const u64 authid = g_ps3_process_info.self_info.valid ? - g_ps3_process_info.self_info.prog_id_hdr.program_authority_id : - 0; - - switch (pkg_id) - { - case 0x1: - { - if (!g_ps3_process_info.debug_or_root()) - { - return not_an_error(CELL_ENOSYS); - } - - if (!a2) - { - return CELL_ESRCH; - } - - ensure(a2 == static_cast(process_getpid())); - vm::write64(vm::cast(a3), authid); - break; - } - case 0x2: - { - vm::write64(vm::cast(a2), authid); - break; - } - case 0x3: - { - if (!g_ps3_process_info.debug_or_root()) - { - return CELL_ENOSYS; - } - - break; - } - default: - return 0x8001051du; - } - - return CELL_OK; -} - -error_code sys_ss_get_console_id(vm::ptr buf) -{ - sys_ss.notice("sys_ss_get_console_id(buf=*0x%x)", buf); - - return sys_ss_appliance_info_manager(0x19003, buf); -} - -error_code sys_ss_get_open_psid(vm::ptr psid) -{ - sys_ss.notice("sys_ss_get_open_psid(psid=*0x%x)", psid); - - psid->high = g_cfg.sys.console_psid_high; - psid->low = g_cfg.sys.console_psid_low; - - return CELL_OK; -} - -error_code sys_ss_appliance_info_manager(u32 code, vm::ptr buffer) -{ - sys_ss.notice("sys_ss_appliance_info_manager(code=0x%x, buffer=*0x%x)", code, buffer); - - if (!g_ps3_process_info.has_root_perm()) - return CELL_ENOSYS; - - if (!buffer) - return CELL_EFAULT; - - switch (code) - { - case 0x19002: - { - // AIM_get_device_type - constexpr u8 product_code[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x89}; - std::memcpy(buffer.get_ptr(), product_code, 16); - if (g_cfg.core.debug_console_mode) - buffer[15] = 0x81; // DECR - break; - } - case 0x19003: - { - // AIM_get_device_id - constexpr u8 idps[] = {0x00, 0x00, 0x00, 0x01, 0x00, 0x89, 0x00, 0x0B, 0x14, 0x00, 0xEF, 0xDD, 0xCA, 0x25, 0x52, 0x66}; - std::memcpy(buffer.get_ptr(), idps, 16); - if (g_cfg.core.debug_console_mode) - { - buffer[5] = 0x81; // DECR - buffer[7] = 0x09; // DECR-1400 - } - break; - } - case 0x19004: - { - // AIM_get_ps_code - constexpr u8 pscode[] = {0x00, 0x01, 0x00, 0x85, 0x00, 0x07, 0x00, 0x04}; - std::memcpy(buffer.get_ptr(), pscode, 8); - break; - } - case 0x19005: - { - // AIM_get_open_ps_id - be_t psid[2] = {+g_cfg.sys.console_psid_high, +g_cfg.sys.console_psid_low}; - std::memcpy(buffer.get_ptr(), psid, 16); - break; - } - case 0x19006: - { - // qa values (dex only) ?? - [[fallthrough]]; - } - default: sys_ss.todo("sys_ss_appliance_info_manager(code=0x%x, buffer=*0x%x)", code, buffer); - } - - return CELL_OK; -} - -error_code sys_ss_get_cache_of_product_mode(vm::ptr ptr) -{ - sys_ss.todo("sys_ss_get_cache_of_product_mode(ptr=*0x%x)", ptr); - - if (!ptr) - { - return CELL_EINVAL; - } - // 0xff Happens when hypervisor call returns an error - // 0 - disabled - // 1 - enabled - - // except something segfaults when using 0, so error it is! - *ptr = 0xFF; - - return CELL_OK; -} - -error_code sys_ss_secure_rtc(u64 cmd, u64 a2, u64 a3, u64 a4) -{ - sys_ss.todo("sys_ss_secure_rtc(cmd=0x%llx, a2=0x%x, a3=0x%llx, a4=0x%llx)", cmd, a2, a3, a4); - if (cmd == 0x3001) - { - if (a3 != 0x20) - return 0x80010500; // bad packet id - - return CELL_OK; - } - else if (cmd == 0x3002) - { - // Get time - if (a2 > 1) - return 0x80010500; // bad packet id - - // a3 is actual output, not 100% sure, but best guess is its tb val - vm::write64(::narrow(a3), get_timebased_time()); - // a4 is a pointer to status, non 0 on error - vm::write64(::narrow(a4), 0); - return CELL_OK; - } - else if (cmd == 0x3003) - { - return CELL_OK; - } - - return 0x80010500; // bad packet id -} - -error_code sys_ss_get_cache_of_flash_ext_flag(vm::ptr flag) -{ - sys_ss.todo("sys_ss_get_cache_of_flash_ext_flag(flag=*0x%x)", flag); - - if (!flag) - { - return CELL_EFAULT; - } - - *flag = 0xFE; // nand vs nor from lsb - - return CELL_OK; -} - -error_code sys_ss_get_boot_device(vm::ptr dev) -{ - sys_ss.todo("sys_ss_get_boot_device(dev=*0x%x)", dev); - - if (!dev) - { - return CELL_EINVAL; - } - - *dev = 0x190; - - return CELL_OK; -} - -error_code sys_ss_update_manager(u64 pkg_id, u64 a1, u64 a2, u64 a3, u64 a4, u64 a5, u64 a6) -{ - sys_ss.notice("sys_ss_update_manager(pkg=0x%x, a1=0x%x, a2=0x%x, a3=0x%x, a4=0x%x, a5=0x%x, a6=0x%x)", pkg_id, a1, a2, a3, a4, a5, a6); - - if (!g_ps3_process_info.has_root_perm()) - return CELL_ENOSYS; - - auto& update_manager = g_fxo->get(); - - switch (pkg_id) - { - case 0x6001: - { - // update package async - break; - } - case 0x6002: - { - // inspect package async - break; - } - case 0x6003: - { - // get installed package info - [[maybe_unused]] const auto type = ::narrow(a1); - const auto info_ptr = ::narrow(a2); - - if (!info_ptr) - return CELL_EFAULT; - - vm::write64(info_ptr, update_manager.system_sw_version); - - break; - } - case 0x6004: - { - // get fix instruction - break; - } - case 0x6005: - { - // extract package async - break; - } - case 0x6006: - { - // get extract package - break; - } - case 0x6007: - { - // get flash initialized - break; - } - case 0x6008: - { - // set flash initialized - break; - } - case 0x6009: - { - // get seed token - break; - } - case 0x600A: - { - // set seed token - break; - } - case 0x600B: - { - // read eeprom - const auto offset = ::narrow(a1); - const auto value_ptr = ::narrow(a2); - - if (!value_ptr) - return CELL_EFAULT; - - std::shared_lock shared_lock(update_manager.eeprom_mutex); - - if (const auto iterator = update_manager.eeprom_map.find(offset); iterator != update_manager.eeprom_map.end()) - vm::write8(value_ptr, iterator->second); - else - vm::write8(value_ptr, 0xFF); // 0xFF if not set - - break; - } - case 0x600C: - { - // write eeprom - const auto offset = ::narrow(a1); - const auto value = ::narrow(a2); - - std::unique_lock unique_lock(update_manager.eeprom_mutex); - - if (value != 0xFF) - update_manager.eeprom_map[offset] = value; - else - update_manager.eeprom_map.erase(offset); // 0xFF: unset - - break; - } - case 0x600D: - { - // get async status - break; - } - case 0x600E: - { - // allocate buffer - const auto size = ::narrow(a1); - const auto addr_ptr = ::narrow(a2); - - if (!addr_ptr) - return CELL_EFAULT; - - const auto addr = update_manager.allocate(size); - - if (!addr) - return CELL_ENOMEM; - - vm::write32(addr_ptr, addr); - - break; - } - case 0x600F: - { - // release buffer - const auto addr = ::narrow(a1); - - if (!update_manager.deallocate(addr)) - return CELL_ENOMEM; - - break; - } - case 0x6010: - { - // check integrity - break; - } - case 0x6011: - { - // get applicable version - const auto addr_ptr = ::narrow(a2); - - if (!addr_ptr) - return CELL_EFAULT; - - vm::write64(addr_ptr, 0x30040ULL << 32); // 3.40 - - break; - } - case 0x6012: - { - // allocate buffer from memory container - [[maybe_unused]] const auto mem_ct = ::narrow(a1); - const auto size = ::narrow(a2); - const auto addr_ptr = ::narrow(a3); - - if (!addr_ptr) - return CELL_EFAULT; - - const auto addr = update_manager.allocate(size); - - if (!addr) - return CELL_ENOMEM; - - vm::write32(addr_ptr, addr); - - break; - } - case 0x6013: - { - // unknown - break; - } - default: - { - sys_ss.error("sys_ss_update_manager(): invalid packet id 0x%x ", pkg_id); - return CELL_EINVAL; - } - } - - return CELL_OK; -} - -error_code sys_ss_virtual_trm_manager(u64 pkg_id, u64 a1, u64 a2, u64 a3, u64 a4) -{ - sys_ss.todo("sys_ss_virtual_trm_manager(pkg=0x%llx, a1=0x%llx, a2=0x%llx, a3=0x%llx, a4=0x%llx)", pkg_id, a1, a2, a3, a4); - - return CELL_OK; -} - -error_code sys_ss_individual_info_manager(u64 pkg_id, u64 a2, vm::ptr out_size, u64 a4, u64 a5, u64 a6) -{ - sys_ss.todo("sys_ss_individual_info_manager(pkg=0x%llx, a2=0x%llx, out_size=*0x%llx, a4=0x%llx, a5=0x%llx, a6=0x%llx)", pkg_id, a2, out_size, a4, a5, a6); - - switch (pkg_id) - { - // Read EID - case 0x17002: - { - // TODO - vm::_ref(a5) = a4; // Write back size of buffer - break; - } - // Get EID size - case 0x17001: *out_size = 0x100; break; - default: break; - } - - return CELL_OK; -} diff --git a/rpcs3/Emu/Cell/lv2/sys_storage.cpp b/rpcs3/Emu/Cell/lv2/sys_storage.cpp deleted file mode 100644 index 061849656..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_storage.cpp +++ /dev/null @@ -1,475 +0,0 @@ -#include "stdafx.h" -#include "Emu/IdManager.h" - -#include "Emu/Cell/ErrorCodes.h" -#include "sys_event.h" -#include "sys_fs.h" -#include "util/shared_ptr.hpp" - -#include "sys_storage.h" - -LOG_CHANNEL(sys_storage); - -namespace -{ - struct storage_manager - { - // This is probably wrong and should be assigned per fd or something - atomic_ptr asyncequeue; - }; -} // namespace - -error_code sys_storage_open(u64 device, u64 mode, vm::ptr fd, u64 flags) -{ - sys_storage.todo("sys_storage_open(device=0x%x, mode=0x%x, fd=*0x%x, flags=0x%x)", device, mode, fd, flags); - - if (device == 0) - { - return CELL_ENOENT; - } - - if (!fd) - { - return CELL_EFAULT; - } - - [[maybe_unused]] u64 storage_id = device & 0xFFFFF00FFFFFFFF; - fs::file file; - - if (const u32 id = idm::make(device, std::move(file), mode, flags)) - { - *fd = id; - return CELL_OK; - } - - return CELL_EAGAIN; -} - -error_code sys_storage_close(u32 fd) -{ - sys_storage.todo("sys_storage_close(fd=0x%x)", fd); - - idm::remove(fd); - - return CELL_OK; -} - -error_code sys_storage_read(u32 fd, u32 mode, u32 start_sector, u32 num_sectors, vm::ptr bounce_buf, vm::ptr sectors_read, u64 flags) -{ - sys_storage.todo("sys_storage_read(fd=0x%x, mode=0x%x, start_sector=0x%x, num_sectors=0x%x, bounce_buf=*0x%x, sectors_read=*0x%x, flags=0x%x)", fd, mode, start_sector, num_sectors, bounce_buf, sectors_read, flags); - - if (!bounce_buf || !sectors_read) - { - return CELL_EFAULT; - } - - std::memset(bounce_buf.get_ptr(), 0, num_sectors * 0x200ull); - const auto handle = idm::get_unlocked(fd); - - if (!handle) - { - return CELL_ESRCH; - } - - if (handle->file) - { - handle->file.seek(start_sector * 0x200ull); - const u64 size = num_sectors * 0x200ull; - const u64 result = lv2_file::op_read(handle->file, bounce_buf, size); - num_sectors = ::narrow(result / 0x200ull); - } - - *sectors_read = num_sectors; - - return CELL_OK; -} - -error_code sys_storage_write(u32 fd, u32 mode, u32 start_sector, u32 num_sectors, vm::ptr data, vm::ptr sectors_wrote, u64 flags) -{ - sys_storage.todo("sys_storage_write(fd=0x%x, mode=0x%x, start_sector=0x%x, num_sectors=0x%x, data=*=0x%x, sectors_wrote=*0x%x, flags=0x%llx)", fd, mode, start_sector, num_sectors, data, sectors_wrote, flags); - - if (!sectors_wrote) - { - return CELL_EFAULT; - } - - const auto handle = idm::get_unlocked(fd); - - if (!handle) - { - return CELL_ESRCH; - } - - *sectors_wrote = num_sectors; - - return CELL_OK; -} - -error_code sys_storage_send_device_command(u32 dev_handle, u64 cmd, vm::ptr in, u64 inlen, vm::ptr out, u64 outlen) -{ - sys_storage.todo("sys_storage_send_device_command(dev_handle=0x%x, cmd=0x%llx, in=*0x%, inlen=0x%x, out=*0x%x, outlen=0x%x)", dev_handle, cmd, in, inlen, out, outlen); - - return CELL_OK; -} - -error_code sys_storage_async_configure(u32 fd, u32 io_buf, u32 equeue_id, u32 unk) -{ - sys_storage.todo("sys_storage_async_configure(fd=0x%x, io_buf=0x%x, equeue_id=0x%x, unk=*0x%x)", fd, io_buf, equeue_id, unk); - - auto& manager = g_fxo->get(); - - if (auto queue = idm::get_unlocked(equeue_id)) - { - manager.asyncequeue.store(queue); - } - else - { - return CELL_ESRCH; - } - - return CELL_OK; -} - -error_code sys_storage_async_send_device_command(u32 dev_handle, u64 cmd, vm::ptr in, u64 inlen, vm::ptr out, u64 outlen, u64 unk) -{ - sys_storage.todo("sys_storage_async_send_device_command(dev_handle=0x%x, cmd=0x%llx, in=*0x%x, inlen=0x%x, out=*0x%x, outlen=0x%x, unk=0x%x)", dev_handle, cmd, in, inlen, out, outlen, unk); - - auto& manager = g_fxo->get(); - - if (auto q = manager.asyncequeue.load()) - { - q->send(0, unk, unk, unk); - } - - return CELL_OK; -} - -error_code sys_storage_async_read() -{ - sys_storage.todo("sys_storage_async_read()"); - - return CELL_OK; -} - -error_code sys_storage_async_write() -{ - sys_storage.todo("sys_storage_async_write()"); - - return CELL_OK; -} - -error_code sys_storage_async_cancel() -{ - sys_storage.todo("sys_storage_async_cancel()"); - - return CELL_OK; -} - -error_code sys_storage_get_device_info(u64 device, vm::ptr buffer) -{ - sys_storage.todo("sys_storage_get_device_info(device=0x%x, buffer=*0x%x)", device, buffer); - - if (!buffer) - { - return CELL_EFAULT; - } - - memset(buffer.get_ptr(), 0, sizeof(StorageDeviceInfo)); - - u64 storage = device & 0xFFFFF00FFFFFFFF; - u32 dev_num = (device >> 32) & 0xFF; - - if (storage == ATA_HDD) // dev_hdd? - { - if (dev_num > 2) - { - return not_an_error(-5); - } - - std::string u = "unnamed"; - memcpy(buffer->name, u.c_str(), u.size()); - buffer->sector_size = 0x200; - buffer->one = 1; - buffer->flags[1] = 1; - buffer->flags[2] = 1; - buffer->flags[7] = 1; - - // set partition size based on dev_num - // stole these sizes from kernel dump, unknown if they are 100% correct - // vsh reports only 2 partitions even though there is 3 sizes - switch (dev_num) - { - case 0: - buffer->sector_count = 0x2542EAB0; // possibly total size - break; - case 1: - buffer->sector_count = 0x24FAEA98; // which makes this hdd0 - break; - case 2: - buffer->sector_count = 0x3FFFF8; // and this one hdd1 - break; - } - } - else if (storage == BDVD_DRIVE) // dev_bdvd? - { - if (dev_num > 0) - { - return not_an_error(-5); - } - - std::string u = "unnamed"; - memcpy(buffer->name, u.c_str(), u.size()); - buffer->sector_count = 0x4D955; - buffer->sector_size = 0x800; - buffer->one = 1; - buffer->flags[1] = 0; - buffer->flags[2] = 1; - buffer->flags[7] = 1; - } - else if (storage == USB_MASS_STORAGE_1(0)) - { - if (dev_num > 0) - { - return not_an_error(-5); - } - - std::string u = "unnamed"; - memcpy(buffer->name, u.c_str(), u.size()); - /*buffer->sector_count = 0x4D955;*/ - buffer->sector_size = 0x200; - buffer->one = 1; - buffer->flags[1] = 0; - buffer->flags[2] = 1; - buffer->flags[7] = 1; - } - else if (storage == NAND_FLASH) - { - if (dev_num > 6) - { - return not_an_error(-5); - } - - std::string u = "unnamed"; - memcpy(buffer->name, u.c_str(), u.size()); - buffer->sector_size = 0x200; - buffer->one = 1; - buffer->flags[1] = 1; - buffer->flags[2] = 1; - buffer->flags[7] = 1; - - // see ata_hdd for explanation - switch (dev_num) - { - case 0: - buffer->sector_count = 0x80000; - break; - case 1: - buffer->sector_count = 0x75F8; - break; - case 2: - buffer->sector_count = 0x63E00; - break; - case 3: - buffer->sector_count = 0x8000; - break; - case 4: - buffer->sector_count = 0x400; - break; - case 5: - buffer->sector_count = 0x2000; - break; - case 6: - buffer->sector_count = 0x200; - break; - } - } - else if (storage == NOR_FLASH) - { - if (dev_num > 3) - { - return not_an_error(-5); - } - - std::string u = "unnamed"; - memcpy(buffer->name, u.c_str(), u.size()); - buffer->sector_size = 0x200; - buffer->one = 1; - buffer->flags[1] = 0; - buffer->flags[2] = 1; - buffer->flags[7] = 1; - - // see ata_hdd for explanation - switch (dev_num) - { - case 0: - buffer->sector_count = 0x8000; - break; - case 1: - buffer->sector_count = 0x77F8; - break; - case 2: - buffer->sector_count = 0x100; // offset, 0x20000 - break; - case 3: - buffer->sector_count = 0x400; - break; - } - } - else if (storage == NAND_UNK) - { - if (dev_num > 1) - { - return not_an_error(-5); - } - - std::string u = "unnamed"; - memcpy(buffer->name, u.c_str(), u.size()); - buffer->sector_size = 0x800; - buffer->one = 1; - buffer->flags[1] = 0; - buffer->flags[2] = 1; - buffer->flags[7] = 1; - - // see ata_hdd for explanation - switch (dev_num) - { - case 0: - buffer->sector_count = 0x7FFFFFFF; - break; - } - } - else - { - sys_storage.error("sys_storage_get_device_info(device=0x%x, buffer=*0x%x)", device, buffer); - } - - return CELL_OK; -} - -error_code sys_storage_get_device_config(vm::ptr storages, vm::ptr devices) -{ - sys_storage.todo("sys_storage_get_device_config(storages=*0x%x, devices=*0x%x)", storages, devices); - - if (storages) - *storages = 6; - else - return CELL_EFAULT; - if (devices) - *devices = 17; - else - return CELL_EFAULT; - - return CELL_OK; -} - -error_code sys_storage_report_devices(u32 storages, u32 start, u32 devices, vm::ptr device_ids) -{ - sys_storage.todo("sys_storage_report_devices(storages=0x%x, start=0x%x, devices=0x%x, device_ids=0x%x)", storages, start, devices, device_ids); - - if (!device_ids) - { - return CELL_EFAULT; - } - - static constexpr std::array all_devs = [] - { - std::array all_devs{}; - all_devs[0] = 0x10300000000000A; - - for (int i = 0; i < 7; ++i) - { - all_devs[i + 1] = 0x100000000000001 | (static_cast(i) << 32); - } - - for (int i = 0; i < 3; ++i) - { - all_devs[i + 8] = 0x101000000000007 | (static_cast(i) << 32); - } - - all_devs[11] = 0x101000000000006; - - for (int i = 0; i < 4; ++i) - { - all_devs[i + 12] = 0x100000000000004 | (static_cast(i) << 32); - } - - all_devs[16] = 0x100000000000003; - return all_devs; - }(); - - if (!devices || start >= all_devs.size() || devices > all_devs.size() - start) - { - return CELL_EINVAL; - } - - std::copy_n(all_devs.begin() + start, devices, device_ids.get_ptr()); - - return CELL_OK; -} - -error_code sys_storage_configure_medium_event(u32 fd, u32 equeue_id, u32 c) -{ - sys_storage.todo("sys_storage_configure_medium_event(fd=0x%x, equeue_id=0x%x, c=0x%x)", fd, equeue_id, c); - - return CELL_OK; -} - -error_code sys_storage_set_medium_polling_interval() -{ - sys_storage.todo("sys_storage_set_medium_polling_interval()"); - - return CELL_OK; -} - -error_code sys_storage_create_region() -{ - sys_storage.todo("sys_storage_create_region()"); - - return CELL_OK; -} - -error_code sys_storage_delete_region() -{ - sys_storage.todo("sys_storage_delete_region()"); - - return CELL_OK; -} - -error_code sys_storage_execute_device_command(u32 fd, u64 cmd, vm::ptr cmdbuf, u64 cmdbuf_size, vm::ptr databuf, u64 databuf_size, vm::ptr driver_status) -{ - sys_storage.todo("sys_storage_execute_device_command(fd=0x%x, cmd=0x%llx, cmdbuf=*0x%x, cmdbuf_size=0x%llx, databuf=*0x%x, databuf_size=0x%llx, driver_status=*0x%x)", fd, cmd, cmdbuf, cmdbuf_size, databuf, databuf_size, driver_status); - - // cmd == 2 is get device info, - // databuf, first byte 0 == status ok? - // byte 1, if < 0 , not ata device - return CELL_OK; -} - -error_code sys_storage_check_region_acl() -{ - sys_storage.todo("sys_storage_check_region_acl()"); - - return CELL_OK; -} - -error_code sys_storage_set_region_acl() -{ - sys_storage.todo("sys_storage_set_region_acl()"); - - return CELL_OK; -} - -error_code sys_storage_get_region_offset() -{ - sys_storage.todo("sys_storage_get_region_offset()"); - - return CELL_OK; -} - -error_code sys_storage_set_emulated_speed() -{ - sys_storage.todo("sys_storage_set_emulated_speed()"); - - // todo: only debug kernel has this - return CELL_ENOSYS; -} diff --git a/rpcs3/Emu/Cell/lv2/sys_storage.h b/rpcs3/Emu/Cell/lv2/sys_storage.h deleted file mode 100644 index 2312268e7..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_storage.h +++ /dev/null @@ -1,79 +0,0 @@ -#pragma once - -#include "Emu/Memory/vm_ptr.h" -#include "Emu/Cell/ErrorCodes.h" - -enum Devices : u64 -{ - ATA_HDD = 0x101000000000007, - BDVD_DRIVE = 0x101000000000006, - PATA0_HDD_DRIVE = 0x101000000000008, - PATA0_BDVD_DRIVE = BDVD_DRIVE, - PATA1_HDD_DRIVE = ATA_HDD, - BUILTIN_FLASH = 0x100000000000001, - NAND_FLASH = BUILTIN_FLASH, - NAND_UNK = 0x100000000000003, - NOR_FLASH = 0x100000000000004, - MEMORY_STICK = 0x103000000000010, - SD_CARD = 0x103000100000010, - COMPACT_FLASH = 0x103000200000010, - USB_MASS_STORAGE_1_BASE = 0x10300000000000A, - USB_MASS_STORAGE_2_BASE = 0x10300000000001F, -}; - -struct lv2_storage -{ - static const u32 id_base = 0x45000000; - static const u32 id_step = 1; - static const u32 id_count = 2048; - SAVESTATE_INIT_POS(45); - - const u64 device_id; - const fs::file file; - const u64 mode; - const u64 flags; - - lv2_storage(u64 device_id, fs::file&& file, u64 mode, u64 flags) - : device_id(device_id), file(std::move(file)), mode(mode), flags(flags) - { - } -}; - -struct StorageDeviceInfo -{ - u8 name[0x20]; // 0x0 - be_t zero; // 0x20 - be_t zero2; // 0x24 - be_t sector_count; // 0x28 - be_t sector_size; // 0x30 - be_t one; // 0x34 - u8 flags[8]; // 0x38 -}; - -#define USB_MASS_STORAGE_1(n) (USB_MASS_STORAGE_1_BASE + n) /* For 0-5 */ -#define USB_MASS_STORAGE_2(n) (USB_MASS_STORAGE_2_BASE + (n - 6)) /* For 6-127 */ - -// SysCalls - -error_code sys_storage_open(u64 device, u64 mode, vm::ptr fd, u64 flags); -error_code sys_storage_close(u32 fd); -error_code sys_storage_read(u32 fd, u32 mode, u32 start_sector, u32 num_sectors, vm::ptr bounce_buf, vm::ptr sectors_read, u64 flags); -error_code sys_storage_write(u32 fd, u32 mode, u32 start_sector, u32 num_sectors, vm::ptr data, vm::ptr sectors_wrote, u64 flags); -error_code sys_storage_send_device_command(u32 dev_handle, u64 cmd, vm::ptr in, u64 inlen, vm::ptr out, u64 outlen); -error_code sys_storage_async_configure(u32 fd, u32 io_buf, u32 equeue_id, u32 unk); -error_code sys_storage_async_read(); -error_code sys_storage_async_write(); -error_code sys_storage_async_cancel(); -error_code sys_storage_get_device_info(u64 device, vm::ptr buffer); -error_code sys_storage_get_device_config(vm::ptr storages, vm::ptr devices); -error_code sys_storage_report_devices(u32 storages, u32 start, u32 devices, vm::ptr device_ids); -error_code sys_storage_configure_medium_event(u32 fd, u32 equeue_id, u32 c); -error_code sys_storage_set_medium_polling_interval(); -error_code sys_storage_create_region(); -error_code sys_storage_delete_region(); -error_code sys_storage_execute_device_command(u32 fd, u64 cmd, vm::ptr cmdbuf, u64 cmdbuf_size, vm::ptr databuf, u64 databuf_size, vm::ptr driver_status); -error_code sys_storage_check_region_acl(); -error_code sys_storage_set_region_acl(); -error_code sys_storage_async_send_device_command(u32 dev_handle, u64 cmd, vm::ptr in, u64 inlen, vm::ptr out, u64 outlen, u64 unk); -error_code sys_storage_get_region_offset(); -error_code sys_storage_set_emulated_speed(); diff --git a/rpcs3/Emu/Cell/lv2/sys_sync.h b/rpcs3/Emu/Cell/lv2/sys_sync.h deleted file mode 100644 index e1495ba3e..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_sync.h +++ /dev/null @@ -1,522 +0,0 @@ -#pragma once - -#include "util/mutex.h" - -#include "Emu/CPU/CPUThread.h" -#include "Emu/Cell/ErrorCodes.h" -#include "Emu/IdManager.h" -#include "Emu/IPC.h" - -#include "util/shared_ptr.hpp" - -// attr_protocol (waiting scheduling policy) -enum lv2_protocol : u8 -{ - SYS_SYNC_FIFO = 0x1, // First In, First Out Order - SYS_SYNC_PRIORITY = 0x2, // Priority Order - SYS_SYNC_PRIORITY_INHERIT = 0x3, // Basic Priority Inheritance Protocol - SYS_SYNC_RETRY = 0x4, // Not selected while unlocking -}; - -enum : u32 -{ - SYS_SYNC_ATTR_PROTOCOL_MASK = 0xf, -}; - -// attr_recursive (recursive locks policy) -enum -{ - SYS_SYNC_RECURSIVE = 0x10, - SYS_SYNC_NOT_RECURSIVE = 0x20, - SYS_SYNC_ATTR_RECURSIVE_MASK = 0xf0, -}; - -// attr_pshared (sharing among processes policy) -enum -{ - SYS_SYNC_PROCESS_SHARED = 0x100, - SYS_SYNC_NOT_PROCESS_SHARED = 0x200, - SYS_SYNC_ATTR_PSHARED_MASK = 0xf00, -}; - -// attr_flags (creation policy) -enum -{ - SYS_SYNC_NEWLY_CREATED = 0x1, // Create new object, fails if specified IPC key exists - SYS_SYNC_NOT_CREATE = 0x2, // Reference existing object, fails if IPC key not found - SYS_SYNC_NOT_CARE = 0x3, // Reference existing object, create new one if IPC key not found - SYS_SYNC_ATTR_FLAGS_MASK = 0xf, -}; - -// attr_adaptive -enum -{ - SYS_SYNC_ADAPTIVE = 0x1000, - SYS_SYNC_NOT_ADAPTIVE = 0x2000, - SYS_SYNC_ATTR_ADAPTIVE_MASK = 0xf000, -}; - -enum ppu_thread_status : u32; - -struct ppu_non_sleeping_count_t -{ - bool has_running; // no actual count for optimization sake - u32 onproc_count; -}; - -// Base class for some kernel objects (shared set of 8192 objects). -struct lv2_obj -{ - static const u32 id_step = 0x100; - static const u32 id_count = 8192; - static constexpr std::pair id_invl_range = {0, 8}; - -private: - enum thread_cmd : s32 - { - yield_cmd = smin, - enqueue_cmd, - }; - - // Function executed under IDM mutex, error will make the object creation fail and the error will be returned - CellError on_id_create() - { - exists++; - return {}; - } - -public: - SAVESTATE_INIT_POS(4); // Dependency on PPUs - - lv2_obj() noexcept = default; - lv2_obj(u32 i) noexcept : exists{i} {} - lv2_obj(lv2_obj&& rhs) noexcept : exists{+rhs.exists} {} - lv2_obj(utils::serial&) noexcept {} - lv2_obj& operator=(lv2_obj&& rhs) noexcept - { - exists = +rhs.exists; - return *this; - } - void save(utils::serial&) {} - - // Existence validation (workaround for shared-ptr ref-counting) - atomic_t exists = 0; - - template - static bool check(Ptr&& ptr) - { - return ptr && ptr->exists; - } - - // wrapper for name64 string formatting - struct name_64 - { - u64 data; - }; - - static std::string name64(u64 name_u64); - - // Find and remove the object from the linked list - template - static T* unqueue(T*& first, T* object, T* T::* mem_ptr = &T::next_cpu) - { - auto it = +first; - - if (it == object) - { - atomic_storage::release(first, it->*mem_ptr); - - if constexpr (ModifyNode) - { - atomic_storage::release(it->*mem_ptr, nullptr); - } - - return it; - } - - for (; it;) - { - const auto next = it->*mem_ptr + 0; - - if (next == object) - { - atomic_storage::release(it->*mem_ptr, next->*mem_ptr); - - if constexpr (ModifyNode) - { - atomic_storage::release(next->*mem_ptr, nullptr); - } - - return next; - } - - it = next; - } - - return {}; - } - - // Remove an object from the linked set according to the protocol - template - static E* schedule(T& first, u32 protocol, bool modify_node = true) - { - auto it = static_cast(first); - - if (!it) - { - return it; - } - - auto parent_found = &first; - - if (protocol == SYS_SYNC_FIFO) - { - while (true) - { - const auto next = +it->next_cpu; - - if (next) - { - parent_found = &it->next_cpu; - it = next; - continue; - } - - if (cpu_flag::again - it->state) - { - atomic_storage::release(*parent_found, nullptr); - } - - return it; - } - } - - auto prio = it->prio.load(); - auto found = it; - - while (true) - { - auto& node = it->next_cpu; - const auto next = static_cast(node); - - if (!next) - { - break; - } - - const auto _prio = static_cast(next)->prio.load(); - - // This condition tests for equality as well so the earliest element to be pushed is popped - if (_prio.prio < prio.prio || (_prio.prio == prio.prio && _prio.order < prio.order)) - { - found = next; - parent_found = &node; - prio = _prio; - } - - it = next; - } - - if (cpu_flag::again - found->state) - { - atomic_storage::release(*parent_found, found->next_cpu); - - if (modify_node) - { - atomic_storage::release(found->next_cpu, nullptr); - } - } - - return found; - } - - template - static void emplace(T& first, T object) - { - atomic_storage::release(object->next_cpu, first); - atomic_storage::release(first, object); - - object->prio.atomic_op([order = ++g_priority_order_tag](std::common_type_t()->prio.load())>& prio) - { - if constexpr (requires { +std::declval().preserve_bit; }) - { - if (prio.preserve_bit) - { - // Restoring state on load - prio.preserve_bit = 0; - return; - } - } - - prio.order = order; - }); - } - -private: - // Remove the current thread from the scheduling queue, register timeout - static bool sleep_unlocked(cpu_thread&, u64 timeout, u64 current_time); - - // Schedule the thread - static bool awake_unlocked(cpu_thread*, s32 prio = enqueue_cmd); - -public: - static constexpr u64 max_timeout = u64{umax} / 1000; - - static bool sleep(cpu_thread& cpu, const u64 timeout = 0); - - static bool awake(cpu_thread* thread, s32 prio = enqueue_cmd); - - // Returns true on successful context switch, false otherwise - static bool yield(cpu_thread& thread); - - static void set_priority(cpu_thread& thread, s32 prio) - { - ensure(prio + 512u < 3712); - awake(&thread, prio); - } - - static inline void awake_all() - { - awake({}); - g_to_awake.clear(); - } - - static void make_scheduler_ready(); - - static std::pair ppu_state(ppu_thread* ppu, bool lock_idm = true, bool lock_lv2 = true); - - static inline void append(cpu_thread* const thread) - { - g_to_awake.emplace_back(thread); - } - - // Serialization related - static void set_future_sleep(cpu_thread* cpu); - static bool is_scheduler_ready(); - - // Must be called under IDM lock - static ppu_non_sleeping_count_t count_non_sleeping_threads(); - - static inline bool has_ppus_in_running_state() noexcept - { - return count_non_sleeping_threads().has_running != 0; - } - - static void set_yield_frequency(u64 freq, u64 max_allowed_tsx); - - static void cleanup(); - - template - static inline u64 get_key(const T& attr) - { - return (attr.pshared == SYS_SYNC_PROCESS_SHARED ? +attr.ipc_key : 0); - } - - template - static error_code create(u32 pshared, u64 ipc_key, s32 flags, F&& make, bool key_not_zero = true) - { - switch (pshared) - { - case SYS_SYNC_PROCESS_SHARED: - { - if (key_not_zero && ipc_key == 0) - { - return CELL_EINVAL; - } - - switch (flags) - { - case SYS_SYNC_NEWLY_CREATED: - case SYS_SYNC_NOT_CARE: - case SYS_SYNC_NOT_CREATE: - { - break; - } - default: return CELL_EINVAL; - } - - break; - } - case SYS_SYNC_NOT_PROCESS_SHARED: - { - break; - } - default: return CELL_EINVAL; - } - - // EAGAIN for IDM IDs shortage - CellError error = CELL_EAGAIN; - - if (!idm::import ([&]() -> shared_ptr - { - shared_ptr result = make(); - - auto finalize_construct = [&]() -> shared_ptr - { - if ((error = result->on_id_create())) - { - result.reset(); - } - - return std::move(result); - }; - - if (pshared != SYS_SYNC_PROCESS_SHARED) - { - // Creation of unique (non-shared) object handle - return finalize_construct(); - } - - auto& ipc_container = g_fxo->get>(); - - if (flags == SYS_SYNC_NOT_CREATE) - { - result = ipc_container.get(ipc_key); - - if (!result) - { - error = CELL_ESRCH; - return result; - } - - // Run on_id_create() on existing object - return finalize_construct(); - } - - bool added = false; - std::tie(added, result) = ipc_container.add(ipc_key, finalize_construct, flags != SYS_SYNC_NEWLY_CREATED); - - if (!added) - { - if (flags == SYS_SYNC_NEWLY_CREATED) - { - // Object already exists but flags does not allow it - error = CELL_EEXIST; - - // We specified we do not want to peek pointer's value, result must be empty - AUDIT(!result); - return result; - } - - // Run on_id_create() on existing object - return finalize_construct(); - } - - return result; - })) - { - return error; - } - - return CELL_OK; - } - - template - static void on_id_destroy(T& obj, u64 ipc_key, u64 pshared = umax) - { - if (pshared == umax) - { - // Default is to check key - pshared = ipc_key != 0; - } - - if (obj.exists-- == 1u && pshared) - { - g_fxo->get>().remove(ipc_key); - } - } - - template - static shared_ptr load(u64 ipc_key, shared_ptr make, u64 pshared = umax) - { - if (pshared == umax ? ipc_key != 0 : pshared != 0) - { - g_fxo->need>(); - - g_fxo->get>().add(ipc_key, [&]() - { - return make; - }); - } - - // Ensure no error - ensure(!make->on_id_create()); - return make; - } - - template - static std::function load_func(shared_ptr make, u64 pshared = umax) - { - const u64 key = make->key; - return [ptr = load(key, make, pshared)](void* storage) - { - *static_cast*>(storage) = ptr; - }; - } - - static bool wait_timeout(u64 usec, ppu_thread* cpu = {}, bool scale = true, bool is_usleep = false); - - static void notify_all() noexcept; - - // Can be called before the actual sleep call in order to move it out of mutex scope - static void prepare_for_sleep(cpu_thread& cpu); - - struct notify_all_t - { - notify_all_t() noexcept - { - g_postpone_notify_barrier = true; - } - - notify_all_t(const notify_all_t&) = delete; - - static void cleanup() - { - for (auto& cpu : g_to_notify) - { - if (!cpu) - { - return; - } - - // While IDM mutex is still locked (this function assumes so) check if the notification is still needed - // Pending flag is meant for forced notification (if the CPU really has pending work it can restore the flag in theory) - // Disabled to allow reservation notifications from here - if (false && cpu != &g_to_notify && static_cast(cpu)->none_of(cpu_flag::signal + cpu_flag::pending)) - { - // Omit it (this is a void pointer, it can hold anything) - cpu = &g_to_notify; - } - } - } - - ~notify_all_t() noexcept - { - lv2_obj::notify_all(); - } - }; - - // Scheduler mutex - static shared_mutex g_mutex; - - // Proirity tags - static atomic_t g_priority_order_tag; - -private: - // Pending list of threads to run - static thread_local std::vector g_to_awake; - - // Scheduler queue for active PPU threads - static class ppu_thread* g_ppu; - - // Waiting for the response from - static u32 g_pending; - - // Pending list of threads to notify (cpu_thread::state ptr) - static thread_local std::add_pointer_t g_to_notify[4]; - - // If a notify_all_t object exists locally, postpone notifications to the destructor of it (not recursive, notifies on the first destructor for safety) - static thread_local bool g_postpone_notify_barrier; - - static void schedule_all(u64 current_time = 0); -}; diff --git a/rpcs3/Emu/Cell/lv2/sys_time.cpp b/rpcs3/Emu/Cell/lv2/sys_time.cpp deleted file mode 100644 index 0e4adff54..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_time.cpp +++ /dev/null @@ -1,453 +0,0 @@ -#include "stdafx.h" -#include "sys_time.h" - -#include "sys_process.h" -#include "Emu/system_config.h" -#include "Emu/Cell/ErrorCodes.h" -#include "Emu/Cell/timers.hpp" -#include "util/tsc.hpp" - -#include "util/sysinfo.hpp" - -u64 g_timebase_offs{}; -static u64 systemtime_offset; - -#ifndef __linux__ -#include "util/asm.hpp" -#endif - -#ifdef _WIN32 - -#include - -struct time_aux_info_t -{ - u64 perf_freq; - u64 start_time; - u64 start_ftime; // time in 100ns units since Epoch -}; - -// Initialize time-related values -const auto s_time_aux_info = []() -> time_aux_info_t -{ - LARGE_INTEGER freq; - if (!QueryPerformanceFrequency(&freq)) - { - MessageBox(nullptr, L"Your hardware doesn't support a high-resolution performance counter", L"Error", MB_OK | MB_ICONERROR); - return {}; - } - - LARGE_INTEGER start; - QueryPerformanceCounter(&start); // get time in 1/perf_freq units from RDTSC - - FILETIME ftime; - GetSystemTimeAsFileTime(&ftime); // get time in 100ns units since January 1, 1601 (UTC) - - time_aux_info_t result; - result.perf_freq = freq.QuadPart; - result.start_time = start.QuadPart; - result.start_ftime = (ftime.dwLowDateTime | static_cast(ftime.dwHighDateTime) << 32) - 116444736000000000; - - return result; -}(); - -#elif __APPLE__ - -// XXX only supports a single timer -#if !defined(HAVE_CLOCK_GETTIME) -#define TIMER_ABSTIME -1 -// The opengroup spec isn't clear on the mapping from REALTIME to CALENDAR being appropriate or not. -// http://pubs.opengroup.org/onlinepubs/009695299/basedefs/time.h.html -#define CLOCK_REALTIME 1 // #define CALENDAR_CLOCK 1 from mach/clock_types.h -#define CLOCK_MONOTONIC 0 // #define SYSTEM_CLOCK 0 - -// the mach kernel uses struct mach_timespec, so struct timespec is loaded from for compatability -// struct timespec { time_t tv_sec; long tv_nsec; }; - -#include -#include -#include -#include -#include -#undef CPU_STATE_MAX - -#define MT_NANO (+1.0E-9) -#define MT_GIGA UINT64_C(1000000000) - -// TODO create a list of timers, -static double mt_timebase = 0.0; -static u64 mt_timestart = 0; - -static int clock_gettime(int clk_id, struct timespec* tp) -{ - kern_return_t retval = KERN_SUCCESS; - if (clk_id == TIMER_ABSTIME) - { - if (!mt_timestart) - { - // only one timer, initilized on the first call to the TIMER - mach_timebase_info_data_t tb = {0}; - mach_timebase_info(&tb); - mt_timebase = tb.numer; - mt_timebase /= tb.denom; - mt_timestart = mach_absolute_time(); - } - - double diff = (mach_absolute_time() - mt_timestart) * mt_timebase; - tp->tv_sec = diff * MT_NANO; - tp->tv_nsec = diff - (tp->tv_sec * MT_GIGA); - } - else // other clk_ids are mapped to the coresponding mach clock_service - { - clock_serv_t cclock; - mach_timespec_t mts; - - host_get_clock_service(mach_host_self(), clk_id, &cclock); - retval = clock_get_time(cclock, &mts); - mach_port_deallocate(mach_task_self(), cclock); - - tp->tv_sec = mts.tv_sec; - tp->tv_nsec = mts.tv_nsec; - } - - return retval; -} -#endif - -#endif - -#ifndef _WIN32 - -#include - -static struct timespec start_time = []() -{ - struct timespec ts; - - if (::clock_gettime(CLOCK_REALTIME, &ts) != 0) - { - // Fatal error - std::terminate(); - } - - tzset(); - - return ts; -}(); - -#endif - -LOG_CHANNEL(sys_time); - -static constexpr u64 g_timebase_freq = /*79800000*/ 80000000ull; // 80 Mhz - -// Convert time is microseconds to timebased time -u64 convert_to_timebased_time(u64 time) -{ - const u64 result = time * (g_timebase_freq / 1000000ull) * g_cfg.core.clocks_scale / 100u; - ensure(result >= g_timebase_offs); - return result - g_timebase_offs; -} - -u64 get_timebased_time() -{ - if (u64 freq = utils::get_tsc_freq()) - { - const u64 tsc = utils::get_tsc(); - -#if _MSC_VER - const u64 result = static_cast(u128_from_mul(tsc, g_timebase_freq) / freq) * g_cfg.core.clocks_scale / 100u; -#else - const u64 result = (tsc / freq * g_timebase_freq + tsc % freq * g_timebase_freq / freq) * g_cfg.core.clocks_scale / 100u; -#endif - return result - g_timebase_offs; - } - - while (true) - { -#ifdef _WIN32 - LARGE_INTEGER count; - ensure(QueryPerformanceCounter(&count)); - - const u64 time = count.QuadPart; - const u64 freq = s_time_aux_info.perf_freq; - -#if _MSC_VER - const u64 result = static_cast(u128_from_mul(time * g_cfg.core.clocks_scale, g_timebase_freq) / freq / 100u); -#else - const u64 result = (time / freq * g_timebase_freq + time % freq * g_timebase_freq / freq) * g_cfg.core.clocks_scale / 100u; -#endif -#else - struct timespec ts; - ensure(::clock_gettime(CLOCK_MONOTONIC, &ts) == 0); - - const u64 result = (static_cast(ts.tv_sec) * g_timebase_freq + static_cast(ts.tv_nsec) * g_timebase_freq / 1000000000ull) * g_cfg.core.clocks_scale / 100u; -#endif - if (result) - return result - g_timebase_offs; - } -} - -// Add an offset to get_timebased_time to avoid leaking PC's uptime into the game -// As if PS3 starts at value 0 (base time) when the game boots -// If none-zero arg is specified it will become the base time (for savestates) -void initialize_timebased_time(u64 timebased_init, bool reset) -{ - g_timebase_offs = 0; - - if (reset) - { - // We simply want to zero-out these values - systemtime_offset = 0; - return; - } - - const u64 current = get_timebased_time(); - timebased_init = current - timebased_init; - - g_timebase_offs = timebased_init; - systemtime_offset = timebased_init / (g_timebase_freq / 1000000); -} - -// Returns some relative time in microseconds, don't change this fact -u64 get_system_time() -{ - if (u64 freq = utils::get_tsc_freq()) - { - const u64 tsc = utils::get_tsc(); - -#if _MSC_VER - const u64 result = static_cast(u128_from_mul(tsc, 1000000ull) / freq); -#else - const u64 result = (tsc / freq * 1000000ull + tsc % freq * 1000000ull / freq); -#endif - return result; - } - - while (true) - { -#ifdef _WIN32 - LARGE_INTEGER count; - ensure(QueryPerformanceCounter(&count)); - - const u64 time = count.QuadPart; - const u64 freq = s_time_aux_info.perf_freq; - -#if _MSC_VER - const u64 result = static_cast(u128_from_mul(time, 1000000ull) / freq); -#else - const u64 result = time / freq * 1000000ull + (time % freq) * 1000000ull / freq; -#endif -#else - struct timespec ts; - ensure(::clock_gettime(CLOCK_MONOTONIC, &ts) == 0); - - const u64 result = static_cast(ts.tv_sec) * 1000000ull + static_cast(ts.tv_nsec) / 1000u; -#endif - - if (result) - return result; - } -} - -// As get_system_time but obeys Clocks scaling setting -u64 get_guest_system_time(u64 time) -{ - const u64 result = (time != umax ? time : get_system_time()) * g_cfg.core.clocks_scale / 100; - return result - systemtime_offset; -} - -// Functions -error_code sys_time_set_timezone(s32 timezone, s32 summertime) -{ - sys_time.trace("sys_time_set_timezone(timezone=0x%x, summertime=0x%x)", timezone, summertime); - - if (!g_ps3_process_info.has_root_perm()) - { - return CELL_ENOSYS; - } - - return CELL_OK; -} - -error_code sys_time_get_timezone(vm::ptr timezone, vm::ptr summertime) -{ - sys_time.trace("sys_time_get_timezone(timezone=*0x%x, summertime=*0x%x)", timezone, summertime); - -#ifdef _WIN32 - TIME_ZONE_INFORMATION tz{}; - switch (GetTimeZoneInformation(&tz)) - { - case TIME_ZONE_ID_UNKNOWN: - { - *timezone = -tz.Bias; - *summertime = 0; - break; - } - case TIME_ZONE_ID_STANDARD: - { - *timezone = -tz.Bias; - *summertime = -tz.StandardBias; - - if (tz.StandardBias) - { - sys_time.error("Unexpected timezone bias (base=%d, std=%d, daylight=%d)", tz.Bias, tz.StandardBias, tz.DaylightBias); - } - break; - } - case TIME_ZONE_ID_DAYLIGHT: - { - *timezone = -tz.Bias; - *summertime = -tz.DaylightBias; - break; - } - default: - { - ensure(0); - } - } -#elif __linux__ - *timezone = ::narrow(-::timezone / 60); - *summertime = !::daylight ? 0 : []() -> s32 - { - struct tm test{}; - ensure(&test == localtime_r(&start_time.tv_sec, &test)); - - // Check bounds [0,1] - if (test.tm_isdst & -2) - { - sys_time.error("No information for timezone DST bias (timezone=%.2fh, tm_gmtoff=%d)", -::timezone / 3600.0, test.tm_gmtoff); - return 0; - } - else - { - return test.tm_isdst ? ::narrow((test.tm_gmtoff + ::timezone) / 60) : 0; - } - }(); -#else - // gettimeofday doesn't return timezone on linux anymore, but this should work on other OSes? - struct timezone tz{}; - ensure(gettimeofday(nullptr, &tz) == 0); - *timezone = ::narrow(-tz.tz_minuteswest); - *summertime = !tz.tz_dsttime ? 0 : [&]() -> s32 - { - struct tm test{}; - ensure(&test == localtime_r(&start_time.tv_sec, &test)); - - return test.tm_isdst ? ::narrow(test.tm_gmtoff / 60 + tz.tz_minuteswest) : 0; - }(); -#endif - - return CELL_OK; -} - -error_code sys_time_get_current_time(vm::ptr sec, vm::ptr nsec) -{ - sys_time.trace("sys_time_get_current_time(sec=*0x%x, nsec=*0x%x)", sec, nsec); - - if (!sec) - { - return CELL_EFAULT; - } - -#ifdef _WIN32 - LARGE_INTEGER count; - ensure(QueryPerformanceCounter(&count)); - - const u64 diff_base = count.QuadPart - s_time_aux_info.start_time; - - // Get time difference in nanoseconds (using 128 bit accumulator) - const u64 diff_sl = diff_base * 1000000000ull; - const u64 diff_sh = utils::umulh64(diff_base, 1000000000ull); - const u64 diff = utils::udiv128(diff_sh, diff_sl, s_time_aux_info.perf_freq); - - // get time since Epoch in nanoseconds - const u64 time = s_time_aux_info.start_ftime * 100u + (diff * g_cfg.core.clocks_scale / 100u); - - // scale to seconds, and add the console time offset (which might be negative) - *sec = (time / 1000000000ull) + g_cfg.sys.console_time_offset; - - if (!nsec) - { - return CELL_EFAULT; - } - - *nsec = time % 1000000000ull; -#else - struct timespec ts; - ensure(::clock_gettime(CLOCK_REALTIME, &ts) == 0); - - if (g_cfg.core.clocks_scale == 100) - { - // get the seconds from the system clock, and add the console time offset (which might be negative) - *sec = ts.tv_sec + g_cfg.sys.console_time_offset; - - if (!nsec) - { - return CELL_EFAULT; - } - - *nsec = ts.tv_nsec; - return CELL_OK; - } - - u64 tv_sec = ts.tv_sec, stv_sec = start_time.tv_sec; - u64 tv_nsec = ts.tv_nsec, stv_nsec = start_time.tv_nsec; - - // Substruct time since Epoch and since start time - tv_sec -= stv_sec; - - if (tv_nsec < stv_nsec) - { - // Correct value if borrow encountered - tv_sec -= 1; - tv_nsec = 1'000'000'000ull - (stv_nsec - tv_nsec); - } - else - { - tv_nsec -= stv_nsec; - } - - // Scale nanocseconds - tv_nsec = stv_nsec + (tv_nsec * g_cfg.core.clocks_scale / 100); - - // Scale seconds and add from nanoseconds / 1'000'000'000, and add the console time offset (which might be negative) - *sec = stv_sec + (tv_sec * g_cfg.core.clocks_scale / 100u) + (tv_nsec / 1000000000ull) + g_cfg.sys.console_time_offset; - - if (!nsec) - { - return CELL_EFAULT; - } - - // Set nanoseconds - *nsec = tv_nsec % 1000000000ull; -#endif - - return CELL_OK; -} - -error_code sys_time_set_current_time(s64 sec, s64 nsec) -{ - sys_time.trace("sys_time_set_current_time(sec=0x%x, nsec=0x%x)", sec, nsec); - - if (!g_ps3_process_info.has_root_perm()) - { - return CELL_ENOSYS; - } - - return CELL_OK; -} - -u64 sys_time_get_timebase_frequency() -{ - sys_time.trace("sys_time_get_timebase_frequency()"); - - return g_timebase_freq; -} - -error_code sys_time_get_rtc(vm::ptr rtc) -{ - sys_time.todo("sys_time_get_rtc(rtc=*0x%x)", rtc); - - return CELL_OK; -} diff --git a/rpcs3/Emu/Cell/lv2/sys_timer.cpp b/rpcs3/Emu/Cell/lv2/sys_timer.cpp deleted file mode 100644 index 6665b58b9..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_timer.cpp +++ /dev/null @@ -1,478 +0,0 @@ -#include "stdafx.h" -#include "sys_timer.h" - -#include "Emu/IdManager.h" -#include "Emu/Cell/ErrorCodes.h" -#include "Emu/Cell/PPUThread.h" -#include "Emu/Cell/timers.hpp" - -#include "util/asm.hpp" -#include "Emu/System.h" -#include "Emu/system_config.h" -#include "sys_event.h" -#include "sys_process.h" - -#include -#include - -LOG_CHANNEL(sys_timer); - -struct lv2_timer_thread -{ - shared_mutex mutex; - std::deque> timers; - - lv2_timer_thread(); - void operator()(); - - // SAVESTATE_INIT_POS(46); // FREE SAVESTATE_INIT_POS number - - static constexpr auto thread_name = "Timer Thread"sv; -}; - -lv2_timer::lv2_timer(utils::serial& ar) - : lv2_obj(1), state(ar), port(lv2_event_queue::load_ptr(ar, port, "timer")), source(ar), data1(ar), data2(ar), expire(ar), period(ar) -{ -} - -void lv2_timer::save(utils::serial& ar) -{ - USING_SERIALIZATION_VERSION(lv2_sync); - ar(state), lv2_event_queue::save_ptr(ar, port.get()), ar(source, data1, data2, expire, period); -} - -u64 lv2_timer::check(u64 _now) noexcept -{ - while (true) - { - const u32 _state = +state; - - if (_state == SYS_TIMER_STATE_RUN) - { - u64 next = expire; - - // If aborting, perform the last accurate check for event - if (_now >= next) - { - lv2_obj::notify_all_t notify; - - std::lock_guard lock(mutex); - return check_unlocked(_now); - } - - return (next - _now); - } - - break; - } - - return umax; -} - -u64 lv2_timer::check_unlocked(u64 _now) noexcept -{ - const u64 next = expire; - - if (_now < next || state != SYS_TIMER_STATE_RUN) - { - return umax; - } - - if (port) - { - port->send(source, data1, data2, next); - } - - if (period) - { - // Set next expiration time and check again - const u64 expire0 = utils::add_saturate(next, period); - expire.release(expire0); - return utils::sub_saturate(expire0, _now); - } - - // Stop after oneshot - state.release(SYS_TIMER_STATE_STOP); - return umax; -} - -lv2_timer_thread::lv2_timer_thread() -{ - Emu.PostponeInitCode([this]() - { - idm::select([&](u32 id, lv2_timer&) - { - timers.emplace_back(idm::get_unlocked(id)); - }); - }); -} - -void lv2_timer_thread::operator()() -{ - u64 sleep_time = 0; - - while (true) - { - if (sleep_time != umax) - { - // Scale time - sleep_time = std::min(sleep_time, u64{umax} / 100) * 100 / g_cfg.core.clocks_scale; - } - - thread_ctrl::wait_for(sleep_time); - - if (thread_ctrl::state() == thread_state::aborting) - { - break; - } - - sleep_time = umax; - - if (Emu.IsPausedOrReady()) - { - sleep_time = 10000; - continue; - } - - const u64 _now = get_guest_system_time(); - - reader_lock lock(mutex); - - for (const auto& timer : timers) - { - while (lv2_obj::check(timer)) - { - if (thread_ctrl::state() == thread_state::aborting) - { - break; - } - - if (const u64 advised_sleep_time = timer->check(_now)) - { - if (sleep_time > advised_sleep_time) - { - sleep_time = advised_sleep_time; - } - - break; - } - } - } - } -} - -error_code sys_timer_create(ppu_thread& ppu, vm::ptr timer_id) -{ - ppu.state += cpu_flag::wait; - - sys_timer.warning("sys_timer_create(timer_id=*0x%x)", timer_id); - - if (auto ptr = idm::make_ptr()) - { - auto& thread = g_fxo->get>(); - { - std::lock_guard lock(thread.mutex); - - // Theoretically could have been destroyed by sys_timer_destroy by now - if (auto it = std::find(thread.timers.begin(), thread.timers.end(), ptr); it == thread.timers.end()) - { - thread.timers.emplace_back(std::move(ptr)); - } - } - - ppu.check_state(); - *timer_id = idm::last_id(); - return CELL_OK; - } - - return CELL_EAGAIN; -} - -error_code sys_timer_destroy(ppu_thread& ppu, u32 timer_id) -{ - ppu.state += cpu_flag::wait; - - sys_timer.warning("sys_timer_destroy(timer_id=0x%x)", timer_id); - - auto timer = idm::withdraw(timer_id, [&](lv2_timer& timer) -> CellError - { - if (reader_lock lock(timer.mutex); lv2_obj::check(timer.port)) - { - return CELL_EISCONN; - } - - timer.exists--; - return {}; - }); - - if (!timer) - { - return CELL_ESRCH; - } - - if (timer.ret) - { - return timer.ret; - } - - auto& thread = g_fxo->get>(); - std::lock_guard lock(thread.mutex); - - if (auto it = std::find(thread.timers.begin(), thread.timers.end(), timer.ptr); it != thread.timers.end()) - { - thread.timers.erase(it); - } - - return CELL_OK; -} - -error_code sys_timer_get_information(ppu_thread& ppu, u32 timer_id, vm::ptr info) -{ - ppu.state += cpu_flag::wait; - - sys_timer.trace("sys_timer_get_information(timer_id=0x%x, info=*0x%x)", timer_id, info); - - sys_timer_information_t _info{}; - const u64 now = get_guest_system_time(); - - const auto timer = idm::check(timer_id, [&](lv2_timer& timer) - { - std::lock_guard lock(timer.mutex); - - timer.check_unlocked(now); - timer.get_information(_info); - }); - - if (!timer) - { - return CELL_ESRCH; - } - - ppu.check_state(); - std::memcpy(info.get_ptr(), &_info, info.size()); - return CELL_OK; -} - -error_code _sys_timer_start(ppu_thread& ppu, u32 timer_id, u64 base_time, u64 period) -{ - ppu.state += cpu_flag::wait; - - (period ? sys_timer.warning : sys_timer.trace)("_sys_timer_start(timer_id=0x%x, base_time=0x%llx, period=0x%llx)", timer_id, base_time, period); - - const u64 start_time = get_guest_system_time(); - - if (period && period < 100) - { - // Invalid periodic timer - return CELL_EINVAL; - } - - const auto timer = idm::check(timer_id, [&](lv2_timer& timer) -> CellError - { - std::lock_guard lock(timer.mutex); - - // LV2 Disassembly: Simple nullptr check (assignment test, do not use lv2_obj::check here) - if (!timer.port) - { - return CELL_ENOTCONN; - } - - timer.check_unlocked(start_time); - if (timer.state != SYS_TIMER_STATE_STOP) - { - return CELL_EBUSY; - } - - if (!period && start_time >= base_time) - { - // Invalid oneshot - return CELL_ETIMEDOUT; - } - - const u64 expire = period == 0 ? base_time : // oneshot - base_time == 0 ? utils::add_saturate(start_time, period) : - // periodic timer with no base (using start time as base) - start_time < utils::add_saturate(base_time, period) ? utils::add_saturate(base_time, period) : - // periodic with base time over start time - [&]() -> u64 // periodic timer base before start time (align to be at least a period over start time) - { - // Optimized from a loop in LV2: - // do - // { - // base_time += period; - // } - // while (base_time < start_time); - - const u64 start_time_with_base_time_reminder = utils::add_saturate(start_time - start_time % period, base_time % period); - - return utils::add_saturate(start_time_with_base_time_reminder, start_time_with_base_time_reminder < start_time ? period : 0); - }(); - - timer.expire = expire; - timer.period = period; - timer.state = SYS_TIMER_STATE_RUN; - return {}; - }); - - if (!timer) - { - return CELL_ESRCH; - } - - if (timer.ret) - { - if (timer.ret == CELL_ETIMEDOUT) - { - return not_an_error(timer.ret); - } - - return timer.ret; - } - - g_fxo->get>()([] {}); - - return CELL_OK; -} - -error_code sys_timer_stop(ppu_thread& ppu, u32 timer_id) -{ - ppu.state += cpu_flag::wait; - - sys_timer.trace("sys_timer_stop()"); - - const auto timer = idm::check(timer_id, [now = get_guest_system_time(), notify = lv2_obj::notify_all_t()](lv2_timer& timer) - { - std::lock_guard lock(timer.mutex); - timer.check_unlocked(now); - timer.state = SYS_TIMER_STATE_STOP; - }); - - if (!timer) - { - return CELL_ESRCH; - } - - return CELL_OK; -} - -error_code sys_timer_connect_event_queue(ppu_thread& ppu, u32 timer_id, u32 queue_id, u64 name, u64 data1, u64 data2) -{ - ppu.state += cpu_flag::wait; - - sys_timer.warning("sys_timer_connect_event_queue(timer_id=0x%x, queue_id=0x%x, name=0x%llx, data1=0x%llx, data2=0x%llx)", timer_id, queue_id, name, data1, data2); - - const auto timer = idm::check(timer_id, [&](lv2_timer& timer) -> CellError - { - auto found = idm::get_unlocked(queue_id); - - if (!found) - { - return CELL_ESRCH; - } - - std::lock_guard lock(timer.mutex); - - if (lv2_obj::check(timer.port)) - { - return CELL_EISCONN; - } - - // Connect event queue - timer.port = found; - timer.source = name ? name : (u64{process_getpid() + 0u} << 32) | u64{timer_id}; - timer.data1 = data1; - timer.data2 = data2; - return {}; - }); - - if (!timer) - { - return CELL_ESRCH; - } - - if (timer.ret) - { - return timer.ret; - } - - return CELL_OK; -} - -error_code sys_timer_disconnect_event_queue(ppu_thread& ppu, u32 timer_id) -{ - ppu.state += cpu_flag::wait; - - sys_timer.warning("sys_timer_disconnect_event_queue(timer_id=0x%x)", timer_id); - - const auto timer = idm::check(timer_id, [now = get_guest_system_time(), notify = lv2_obj::notify_all_t()](lv2_timer& timer) -> CellError - { - std::lock_guard lock(timer.mutex); - - timer.check_unlocked(now); - timer.state = SYS_TIMER_STATE_STOP; - - if (!lv2_obj::check(timer.port)) - { - return CELL_ENOTCONN; - } - - timer.port.reset(); - return {}; - }); - - if (!timer) - { - return CELL_ESRCH; - } - - if (timer.ret) - { - return timer.ret; - } - - return CELL_OK; -} - -error_code sys_timer_sleep(ppu_thread& ppu, u32 sleep_time) -{ - ppu.state += cpu_flag::wait; - - sys_timer.trace("sys_timer_sleep(sleep_time=%d)", sleep_time); - - return sys_timer_usleep(ppu, sleep_time * u64{1000000}); -} - -error_code sys_timer_usleep(ppu_thread& ppu, u64 sleep_time) -{ - ppu.state += cpu_flag::wait; - - sys_timer.trace("sys_timer_usleep(sleep_time=0x%llx)", sleep_time); - - if (sleep_time) - { - const s64 add_time = g_cfg.core.usleep_addend; - - // Over/underflow checks - if (add_time >= 0) - { - sleep_time = utils::add_saturate(sleep_time, add_time); - } - else - { - sleep_time = std::max(1, utils::sub_saturate(sleep_time, -add_time)); - } - - lv2_obj::sleep(ppu, g_cfg.core.sleep_timers_accuracy < sleep_timers_accuracy_level::_usleep ? sleep_time : 0); - - if (!lv2_obj::wait_timeout(sleep_time, &ppu, true, true)) - { - ppu.state += cpu_flag::again; - } - } - else - { - std::this_thread::yield(); - } - - return CELL_OK; -} diff --git a/rpcs3/Emu/Cell/lv2/sys_trace.cpp b/rpcs3/Emu/Cell/lv2/sys_trace.cpp deleted file mode 100644 index 26f73a9fa..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_trace.cpp +++ /dev/null @@ -1,68 +0,0 @@ -#include "stdafx.h" -#include "sys_trace.h" - -#include "Emu/Cell/ErrorCodes.h" - -LOG_CHANNEL(sys_trace); - -// TODO: DEX/DECR mode support? - -s32 sys_trace_create() -{ - sys_trace.todo("sys_trace_create()"); - return CELL_ENOSYS; -} - -s32 sys_trace_start() -{ - sys_trace.todo("sys_trace_start()"); - return CELL_ENOSYS; -} - -s32 sys_trace_stop() -{ - sys_trace.todo("sys_trace_stop()"); - return CELL_ENOSYS; -} - -s32 sys_trace_update_top_index() -{ - sys_trace.todo("sys_trace_update_top_index()"); - return CELL_ENOSYS; -} - -s32 sys_trace_destroy() -{ - sys_trace.todo("sys_trace_destroy()"); - return CELL_ENOSYS; -} - -s32 sys_trace_drain() -{ - sys_trace.todo("sys_trace_drain()"); - return CELL_ENOSYS; -} - -s32 sys_trace_attach_process() -{ - sys_trace.todo("sys_trace_attach_process()"); - return CELL_ENOSYS; -} - -s32 sys_trace_allocate_buffer() -{ - sys_trace.todo("sys_trace_allocate_buffer()"); - return CELL_ENOSYS; -} - -s32 sys_trace_free_buffer() -{ - sys_trace.todo("sys_trace_free_buffer()"); - return CELL_ENOSYS; -} - -s32 sys_trace_create2() -{ - sys_trace.todo("sys_trace_create2()"); - return CELL_ENOSYS; -} diff --git a/rpcs3/Emu/Cell/lv2/sys_tty.cpp b/rpcs3/Emu/Cell/lv2/sys_tty.cpp deleted file mode 100644 index 3943acf9f..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_tty.cpp +++ /dev/null @@ -1,204 +0,0 @@ -#include "stdafx.h" -#include "Emu/system_config.h" -#include "Emu/Cell/PPUThread.h" -#include "Emu/Cell/timers.hpp" - -#include "sys_tty.h" - -#include -#include - -LOG_CHANNEL(sys_tty); - -extern fs::file g_tty; -extern atomic_t g_tty_size; -extern std::array, 16> g_tty_input; -extern std::mutex g_tty_mutex; - -error_code sys_tty_read(s32 ch, vm::ptr buf, u32 len, vm::ptr preadlen) -{ - sys_tty.trace("sys_tty_read(ch=%d, buf=*0x%x, len=%d, preadlen=*0x%x)", ch, buf, len, preadlen); - - if (!g_cfg.core.debug_console_mode) - { - return CELL_EIO; - } - - if (ch > 15 || ch < 0 || !buf) - { - return CELL_EINVAL; - } - - if (ch < SYS_TTYP_USER1) - { - sys_tty.warning("sys_tty_read called with system channel %d", ch); - } - - usz chars_to_read = 0; // number of chars that will be read from the input string - std::string tty_read; // string for storage of read chars - - if (len > 0) - { - std::lock_guard lock(g_tty_mutex); - - if (!g_tty_input[ch].empty()) - { - // reference to our first queue element - std::string& input = g_tty_input[ch].front(); - - // we have to stop reading at either a new line, the param len, or our input string size - usz new_line_pos = input.find_first_of('\n'); - - if (new_line_pos != input.npos) - { - chars_to_read = std::min(new_line_pos, static_cast(len)); - } - else - { - chars_to_read = std::min(input.size(), static_cast(len)); - } - - // read the previously calculated number of chars from the beginning of the input string - tty_read = input.substr(0, chars_to_read); - - // remove the just read text from the input string - input = input.substr(chars_to_read, input.size() - 1); - - if (input.empty()) - { - // pop the first queue element if it was completely consumed - g_tty_input[ch].pop_front(); - } - } - } - - if (!preadlen) - { - return CELL_EFAULT; - } - - *preadlen = static_cast(chars_to_read); - - if (chars_to_read > 0) - { - std::memcpy(buf.get_ptr(), tty_read.c_str(), chars_to_read); - sys_tty.success("sys_tty_read(ch=%d, len=%d) read %s with length %d", ch, len, tty_read, *preadlen); - } - - return CELL_OK; -} - -std::string dump_useful_thread_info(); - -error_code sys_tty_write([[maybe_unused]] ppu_thread& ppu, s32 ch, vm::cptr buf, u32 len, vm::ptr pwritelen) -{ - ppu.state += cpu_flag::wait; - - sys_tty.notice("sys_tty_write(ch=%d, buf=*0x%x, len=%d, pwritelen=*0x%x)", ch, buf, len, pwritelen); - - std::string msg; - - if (static_cast(len) > 0 && vm::check_addr(buf.addr(), vm::page_readable, len)) - { - msg.resize(len); - - if (!vm::try_access(buf.addr(), msg.data(), len, false)) - { - msg.clear(); - } - } - - auto find_word = [](std::string_view msg, std::string_view word) -> bool - { - // Match uppercase and lowercase starting words - const usz index = msg.find(word.substr(1)); - - if (index != umax && index >= 1u) - { - return std::tolower(static_cast(msg[index - 1])) == word[0]; - } - - return false; - }; - - std::string_view sample = std::string_view(msg).substr(0, 1024); - - const bool warning = find_word(sample, "failed"sv) || find_word(sample, "abort"sv) || find_word(sample, "crash"sv) || find_word(sample, "error"sv) || find_word(sample, "unexpected"sv) || find_word(sample, "0x8001"sv); - - sample = {}; // Remove reference to string - - if (msg.size() >= 2u && [&]() - { - static thread_local u64 last_write = 0; - - // Dump thread about every period which TTY was not being touched for about half a second - const u64 current = get_system_time(); - return current - std::exchange(last_write, current) >= (warning ? 500'000 : 3'000'000); - }()) - { - ppu_log.notice("\n%s", dump_useful_thread_info()); - } - - // Hack: write to tty even on CEX mode, but disable all error checks - if (ch < 0 || ch > 15) - { - if (g_cfg.core.debug_console_mode) - { - return CELL_EINVAL; - } - else - { - msg.clear(); - } - } - - if (g_cfg.core.debug_console_mode) - { - // Don't modify it in CEX mode - len = static_cast(len) > 0 ? len : 0; - } - - if (static_cast(len) > 0) - { - if (!msg.empty()) - { - if (msg.ends_with("\n")) - { - // Avoid logging trailing newlines, log them verbosely instead - const std::string_view msg_clear = std::string_view(msg).substr(0, msg.find_last_not_of('\n') + 1); - - if (msg.size() - 1 == msg_clear.size()) - { - (warning ? sys_tty.warning : sys_tty.notice)(u8"sys_tty_write(): “%s“ << endl", msg_clear); - } - else - { - (warning ? sys_tty.warning : sys_tty.notice)(u8"sys_tty_write(): “%s” << endl(%u)", msg_clear, msg.size() - msg_clear.size()); - } - } - else - { - (warning ? sys_tty.warning : sys_tty.notice)(u8"sys_tty_write(): “%s”", msg); - } - - if (g_tty) - { - // Lock size by making it negative - g_tty_size -= (1ll << 48); - g_tty.write(msg); - g_tty_size += (1ll << 48) + len; - } - } - else if (g_cfg.core.debug_console_mode) - { - return {CELL_EFAULT, buf.addr()}; - } - } - - if (!pwritelen.try_write(len)) - { - return {CELL_EFAULT, pwritelen}; - } - - return CELL_OK; -} diff --git a/rpcs3/Emu/Cell/lv2/sys_tty.h b/rpcs3/Emu/Cell/lv2/sys_tty.h deleted file mode 100644 index 5363df302..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_tty.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include "Emu/Memory/vm_ptr.h" -#include "Emu/Cell/ErrorCodes.h" - -// TTY channels -enum -{ - SYS_TTYP_PPU_STDIN = 0, - SYS_TTYP_PPU_STDOUT = 0, - SYS_TTYP_PPU_STDERR = 1, - SYS_TTYP_SPU_STDOUT = 2, - SYS_TTYP_USER1 = 3, - SYS_TTYP_USER2 = 4, - SYS_TTYP_USER3 = 5, - SYS_TTYP_USER4 = 6, - SYS_TTYP_USER5 = 7, - SYS_TTYP_USER6 = 8, - SYS_TTYP_USER7 = 9, - SYS_TTYP_USER8 = 10, - SYS_TTYP_USER9 = 11, - SYS_TTYP_USER10 = 12, - SYS_TTYP_USER11 = 13, - SYS_TTYP_USER12 = 14, - SYS_TTYP_USER13 = 15, -}; - -class ppu_thread; - -// SysCalls -error_code sys_tty_read(s32 ch, vm::ptr buf, u32 len, vm::ptr preadlen); -error_code sys_tty_write(ppu_thread& ppu, s32 ch, vm::cptr buf, u32 len, vm::ptr pwritelen); diff --git a/rpcs3/Emu/Cell/lv2/sys_uart.cpp b/rpcs3/Emu/Cell/lv2/sys_uart.cpp deleted file mode 100644 index f55410559..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_uart.cpp +++ /dev/null @@ -1,2490 +0,0 @@ -#include "stdafx.h" - -#include "Emu/System.h" -#include "Emu/system_config.h" -#include "Emu/Cell/PPUThread.h" -#include "Emu/Cell/ErrorCodes.h" -#include "Emu/Cell/lv2/sys_sync.h" -#include "Emu/Cell/lv2/sys_rsxaudio.h" -#include "Emu/Cell/lv2/sys_process.h" - -#include "sys_uart.h" - -LOG_CHANNEL(sys_uart); - -template <> -void fmt_class_string::format(std::string& out, u64 arg) -{ - format_enum(out, arg, [](UartAudioCtrlID value) - { - switch (value) - { - STR_CASE(UartAudioCtrlID::DAC_RESET); - STR_CASE(UartAudioCtrlID::DAC_DE_EMPHASIS); - STR_CASE(UartAudioCtrlID::AVCLK); - } - - return unknown; - }); -} - -struct av_init_cmd : public ps3av_cmd -{ - u16 get_size(vuart_av_thread& /*vuart*/, const void* /*pkt_buf*/) override - { - return sizeof(ps3av_pkt_av_init); - } - - void execute(vuart_av_thread& vuart, const void* pkt_buf) override - { - const auto pkt = static_cast(pkt_buf); - - if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) - { - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_BUFFER_OVERFLOW); - return; - } - - vuart.av_cmd_ver = pkt->hdr.version; - vuart.hdmi_events_bitmask |= pkt->event_bit; - - if (pkt->event_bit & PS3AV_EVENT_BIT_UNK) - { - // 0 or 255, probably ps2 backwards compatibility (inverted) - const ps3av_pkt_av_init_reply reply = {0}; - - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS, &reply, sizeof(ps3av_pkt_av_init_reply)); - return; - } - - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); - } -}; - -struct av_fini_cmd : public ps3av_cmd -{ - u16 get_size(vuart_av_thread& /*vuart*/, const void* /*pkt_buf*/) override - { - return sizeof(ps3av_header); - } - - void execute(vuart_av_thread& vuart, const void* pkt_buf) override - { - const auto pkt = static_cast(pkt_buf); - - if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) - { - vuart.write_resp(pkt->cid, PS3AV_STATUS_BUFFER_OVERFLOW); - return; - } - - vuart.hdmi_events_bitmask = 0; - - vuart.write_resp(pkt->cid, PS3AV_STATUS_SUCCESS); - } -}; - -struct av_get_monitor_info_cmd : public ps3av_cmd -{ - u16 get_size(vuart_av_thread& /*vuart*/, const void* /*pkt_buf*/) override - { - return sizeof(ps3av_get_monitor_info); - } - - void execute(vuart_av_thread& vuart, const void* pkt_buf) override - { - const auto pkt = static_cast(pkt_buf); - - if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) - { - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_BUFFER_OVERFLOW); - return; - } - - ps3av_get_monitor_info_reply cfg{}; - - if (pkt->avport == static_cast(UartAudioAvport::AVMULTI_0)) - { - cfg.avport = static_cast(UartAudioAvport::AVMULTI_0); - cfg.monitor_type = PS3AV_MONITOR_TYPE_AVMULTI; - cfg.res_60.res_bits = UINT32_MAX; - cfg.res_50.res_bits = UINT32_MAX; - cfg.res_vesa.res_bits = UINT32_MAX; - cfg.cs.rgb = PS3AV_CS_SUPPORTED; - cfg.cs.yuv444 = PS3AV_CS_SUPPORTED; - cfg.cs.yuv422 = PS3AV_CS_SUPPORTED; - cfg.speaker_info = 1; - cfg.num_of_audio_block = 1; - cfg.audio_info[0].sbit = 7; - cfg.audio_info[0].max_num_of_ch = 2; - cfg.audio_info[0].type = PS3AV_MON_INFO_AUDIO_TYPE_LPCM; - cfg.audio_info[0].fs = 127; - - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS, &cfg, sizeof(ps3av_get_monitor_info_reply)); - } - else if (pkt->avport <= static_cast(UartAudioAvport::HDMI_1)) - { - if (pkt->avport == static_cast(UartAudioAvport::HDMI_1) && !g_cfg.core.debug_console_mode) - { - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SYSCON_COMMUNICATE_FAIL); - return; - } - - set_hdmi_display_cfg(vuart, cfg, static_cast(pkt->avport)); - - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS, &cfg, sizeof(ps3av_get_monitor_info_reply) - 4); // Length is different for some reason - } - else - { - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_INVALID_PORT); - } - } - - static void set_hdmi_display_cfg(vuart_av_thread& vuart, ps3av_get_monitor_info_reply& cfg, u8 avport) - { - if (vuart.hdmi_behavior_mode != PS3AV_HDMI_BEHAVIOR_NORMAL && (vuart.hdmi_behavior_mode & PS3AV_HDMI_BEHAVIOR_EDID_PASS)) - { - cfg.monitor_type = vuart.hdmi_behavior_mode & PS3AV_HDMI_BEHAVIOR_DVI ? PS3AV_MONITOR_TYPE_DVI : PS3AV_MONITOR_TYPE_HDMI; - return; - } - - // Report maximum support - - static constexpr u8 mon_id[sizeof(cfg.monitor_id)] = {0x4A, 0x13, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15}; - static constexpr u8 mon_name[sizeof(cfg.monitor_name)] = {'R', 'P', 'C', 'S', '3', ' ', 'V', 'i', 'r', 't', 'M', 'o', 'n', '\0', '\0', '\0'}; - static constexpr ps3av_info_audio audio_info[PS3AV_MON_INFO_AUDIO_BLK_MAX] = - { - {PS3AV_MON_INFO_AUDIO_TYPE_LPCM, 8, 0x7F, 0x07}, - {PS3AV_MON_INFO_AUDIO_TYPE_AC3, 8, 0x7F, 0xFF}, - {PS3AV_MON_INFO_AUDIO_TYPE_AAC, 8, 0x7F, 0xFF}, - {PS3AV_MON_INFO_AUDIO_TYPE_DTS, 8, 0x7F, 0xFF}, - {PS3AV_MON_INFO_AUDIO_TYPE_DDP, 8, 0x7F, 0xFF}, - {PS3AV_MON_INFO_AUDIO_TYPE_DTS_HD, 8, 0x7F, 0xFF}, - {PS3AV_MON_INFO_AUDIO_TYPE_DOLBY_THD, 8, 0x7F, 0xFF}, - }; - - cfg.avport = avport; - memcpy(cfg.monitor_id, mon_id, sizeof(cfg.monitor_id)); - cfg.monitor_type = PS3AV_MONITOR_TYPE_HDMI; - memcpy(cfg.monitor_name, mon_name, sizeof(cfg.monitor_name)); - - const u32 native_res = [&]() - { - switch (g_cfg.video.resolution) - { - case video_resolution::_1080p: - return PS3AV_RESBIT_1920x1080P; - case video_resolution::_1080i: - return PS3AV_RESBIT_1920x1080I; - case video_resolution::_1600x1080p: - case video_resolution::_1440x1080p: - case video_resolution::_1280x1080p: - case video_resolution::_720p: - return PS3AV_RESBIT_1280x720P; - case video_resolution::_576p: - return PS3AV_RESBIT_720x576P; - default: - return PS3AV_RESBIT_720x480P; - } - }(); - - cfg.res_60.res_bits = UINT32_MAX; - cfg.res_60.native = native_res; - cfg.res_50.res_bits = UINT32_MAX; - cfg.res_50.native = native_res; - cfg.res_other.res_bits = UINT32_MAX; - cfg.res_vesa.res_bits = 1; // Always one mode at a time - - cfg.cs.rgb = PS3AV_CS_SUPPORTED | PS3AV_RGB_SELECTABLE_QAUNTIZATION_RANGE | PS3AV_12BIT_COLOR; - cfg.cs.yuv444 = PS3AV_CS_SUPPORTED | PS3AV_12BIT_COLOR; - cfg.cs.yuv422 = PS3AV_CS_SUPPORTED; - cfg.cs.colorimetry_data = PS3AV_COLORIMETRY_xvYCC_601 | PS3AV_COLORIMETRY_xvYCC_709 | PS3AV_COLORIMETRY_MD0 | PS3AV_COLORIMETRY_MD1 | PS3AV_COLORIMETRY_MD2; - - cfg.color.red_x = 1023; - cfg.color.red_y = 0; - cfg.color.green_x = 0; - cfg.color.green_y = 1023; - cfg.color.blue_x = 0; - cfg.color.blue_y = 0; - cfg.color.white_x = 341; - cfg.color.white_y = 341; - cfg.color.gamma = 100; - - cfg.supported_ai = 1; - cfg.speaker_info = 0x4F; - - // Audio formats - cfg.num_of_audio_block = 7; - memcpy(cfg.audio_info, audio_info, sizeof(cfg.audio_info)); - - // 16:9 27-inch (as a default) - cfg.hor_screen_size = 60; - cfg.ver_screen_size = 34; - - cfg.supported_content_types = 0b1111; // Graphics, cinema, photo, game - - // 3D modes, no native formats - cfg.res_60_packed_3D.res_bits = UINT32_MAX; - cfg.res_50_packed_3D.res_bits = UINT32_MAX; - cfg.res_other_3D.res_bits = UINT32_MAX; - cfg.res_60_sbs_3D.res_bits = UINT32_MAX; - cfg.res_50_sbs_3D.res_bits = UINT32_MAX; - - cfg.vendor_specific_flags = 0; // values from 0-3 (unk) - } -}; - -struct av_get_bksv_list_cmd : public ps3av_cmd -{ - u16 get_size(vuart_av_thread& /*vuart*/, const void* /*pkt_buf*/) override - { - return sizeof(ps3av_pkt_get_bksv); - } - - void execute(vuart_av_thread& vuart, const void* pkt_buf) override - { - const auto pkt = static_cast(pkt_buf); - - if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) - { - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_BUFFER_OVERFLOW); - return; - } - - if (pkt->avport > static_cast(UartAudioAvport::HDMI_1)) - { - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_INVALID_PORT); - return; - } - - if (pkt->avport == static_cast(UartAudioAvport::HDMI_1) && !g_cfg.core.debug_console_mode) - { - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SYSCON_COMMUNICATE_FAIL); - return; - } - - ps3av_pkt_get_bksv_reply reply{}; - reply.avport = pkt->avport; - u16 pkt_size = offsetof(ps3av_pkt_get_bksv_reply, ksv_arr); - - if (vuart.hdmi_behavior_mode == PS3AV_HDMI_BEHAVIOR_NORMAL || !(vuart.hdmi_behavior_mode & PS3AV_HDMI_BEHAVIOR_HDCP_OFF)) - { - reply.ksv_cnt = 1; - memcpy(reply.ksv_arr[0], PS3AV_BKSV_VALUE, sizeof(PS3AV_BKSV_VALUE)); - pkt_size = (pkt_size + 5 * reply.ksv_cnt + 3) & 0xFFFC; - } - - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS, &reply, pkt_size); - } -}; - -struct av_enable_event_cmd : public ps3av_cmd -{ - u16 get_size(vuart_av_thread& /*vuart*/, const void* /*pkt_buf*/) override - { - return sizeof(ps3av_pkt_enable_event); - } - - void execute(vuart_av_thread& vuart, const void* pkt_buf) override - { - const auto pkt = static_cast(pkt_buf); - - if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) - { - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_BUFFER_OVERFLOW); - return; - } - - vuart.hdmi_events_bitmask |= pkt->event_bit; - - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); - } -}; - -struct av_disable_event_cmd : public ps3av_cmd -{ - u16 get_size(vuart_av_thread& /*vuart*/, const void* /*pkt_buf*/) override - { - return sizeof(ps3av_pkt_enable_event); - } - - void execute(vuart_av_thread& vuart, const void* pkt_buf) override - { - const auto pkt = static_cast(pkt_buf); - - if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) - { - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_BUFFER_OVERFLOW); - return; - } - - vuart.hdmi_events_bitmask &= ~pkt->event_bit; - - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); - } -}; - -struct av_tv_mute_cmd : public ps3av_cmd -{ - u16 get_size(vuart_av_thread& /*vuart*/, const void* /*pkt_buf*/) override - { - return sizeof(ps3av_pkt_av_audio_mute); - } - - // Behavior is unknown, but it seems that this pkt could be ignored - void execute(vuart_av_thread& vuart, const void* pkt_buf) override - { - const auto pkt = static_cast(pkt_buf); - - if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) - { - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_BUFFER_OVERFLOW); - return; - } - - if (pkt->avport < (g_cfg.core.debug_console_mode ? 2 : 1)) - { - sys_uart.notice("[av_tv_mute_cmd] tv mute set to %u", pkt->mute > 0); - } - - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); - } -}; - -struct av_null_cmd : public ps3av_cmd -{ - u16 get_size(vuart_av_thread& /*vuart*/, const void* /*pkt_buf*/) override - { - return 12; - } - - void execute(vuart_av_thread& vuart, const void* pkt_buf) override - { - const auto pkt = static_cast(pkt_buf); - - if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) - { - vuart.write_resp(pkt->cid, PS3AV_STATUS_BUFFER_OVERFLOW); - return; - } - - vuart.write_resp(pkt->cid, PS3AV_STATUS_SUCCESS); - } -}; - -struct av_get_aksv_list_cmd : public ps3av_cmd -{ - u16 get_size(vuart_av_thread& /*vuart*/, const void* /*pkt_buf*/) override - { - return sizeof(ps3av_header); - } - - void execute(vuart_av_thread& vuart, const void* pkt_buf) override - { - const auto pkt = static_cast(pkt_buf); - - if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr) + sizeof(ps3av_pkt_get_aksv_reply)) - { - vuart.write_resp(pkt->cid, PS3AV_STATUS_BUFFER_OVERFLOW); - return; - } - - ps3av_pkt_get_aksv_reply reply{}; - memcpy(reply.ksv_arr[0], PS3AV_AKSV_VALUE, sizeof(PS3AV_AKSV_VALUE)); - - if (g_cfg.core.debug_console_mode) - { - memcpy(reply.ksv_arr[1], PS3AV_AKSV_VALUE, sizeof(PS3AV_AKSV_VALUE)); - reply.ksv_size = 2 * sizeof(PS3AV_AKSV_VALUE); - } - else - { - reply.ksv_size = sizeof(PS3AV_AKSV_VALUE); - } - - vuart.write_resp(pkt->cid, PS3AV_STATUS_SUCCESS, &reply, sizeof(ps3av_pkt_get_aksv_reply)); - } -}; - -struct video_disable_signal_cmd : public ps3av_cmd -{ - u16 get_size(vuart_av_thread& /*vuart*/, const void* /*pkt_buf*/) override - { - return sizeof(ps3av_pkt_video_disable_sig); - } - - // Cross color reduction filter setting in vsh. (AVMULTI) - void execute(vuart_av_thread& vuart, const void* pkt_buf) override - { - const auto pkt = static_cast(pkt_buf); - - if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) - { - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_BUFFER_OVERFLOW); - return; - } - - if (pkt->avport <= static_cast(UartAudioAvport::HDMI_1)) - { - g_fxo->get().update_av_mute_state(vuart.avport_to_idx(static_cast(pkt->avport.get())), false, true); - - if (pkt->avport == static_cast(UartAudioAvport::HDMI_1) && !g_cfg.core.debug_console_mode) - { - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SYSCON_COMMUNICATE_FAIL); - return; - } - - vuart.hdmi_res_set[pkt->avport == static_cast(UartAudioAvport::HDMI_1)] = false; - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); - } - else if (pkt->avport == static_cast(UartAudioAvport::AVMULTI_0)) - { - if (vuart.head_b_initialized) - { - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); - } - } - else - { - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); - } - } -}; - -struct av_video_ytrapcontrol_cmd : public ps3av_cmd -{ - u16 get_size(vuart_av_thread& /*vuart*/, const void* /*pkt_buf*/) override - { - return sizeof(ps3av_pkt_av_video_ytrapcontrol); - } - - // Cross color reduction filter setting in vsh. (AVMULTI) - void execute(vuart_av_thread& vuart, const void* pkt_buf) override - { - const auto pkt = static_cast(pkt_buf); - - if (vuart.get_reply_buf_free_size() < sizeof(ps3av_get_hw_info_reply) + sizeof(ps3av_pkt_reply_hdr)) - { - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_BUFFER_OVERFLOW); - return; - } - - if (pkt->unk1 && pkt->unk1 != 5U) - { - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); - return; - } - - sys_uart.notice("[av_video_ytrapcontrol_cmd] unk1=0x%04x unk2=0x%04x", pkt->unk1, pkt->unk2); - - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); - } -}; - -struct av_audio_mute_cmd : public ps3av_cmd -{ - u16 get_size(vuart_av_thread& /*vuart*/, const void* /*pkt_buf*/) override - { - return sizeof(ps3av_pkt_av_audio_mute); - } - - void execute(vuart_av_thread& vuart, const void* pkt_buf) override - { - const auto pkt = static_cast(pkt_buf); - - if (pkt->avport == static_cast(UartAudioAvport::AVMULTI_1)) - { - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_INVALID_PORT); - return; - } - - if ((pkt->avport > static_cast(UartAudioAvport::HDMI_1) && pkt->avport != static_cast(UartAudioAvport::AVMULTI_0)) || - (pkt->avport == static_cast(UartAudioAvport::HDMI_1) && !g_cfg.core.debug_console_mode)) - { - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SYSCON_COMMUNICATE_FAIL); - return; - } - - g_fxo->get().update_av_mute_state(vuart.avport_to_idx(static_cast(pkt->avport.get())), true, false, pkt->mute); - - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); - } -}; - -struct av_acp_ctrl_cmd : public ps3av_cmd -{ - u16 get_size(vuart_av_thread& /*vuart*/, const void* /*pkt_buf*/) override - { - return sizeof(ps3av_pkt_acp_ctrl); - } - - void execute(vuart_av_thread& vuart, const void* pkt_buf) override - { - const auto pkt = static_cast(pkt_buf); - - if (pkt->avport > static_cast(UartAudioAvport::HDMI_1) || - (pkt->avport == static_cast(UartAudioAvport::HDMI_1) && !g_cfg.core.debug_console_mode)) - { - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_INVALID_PORT); - return; - } - - sys_uart.notice("[av_acp_ctrl_cmd] HDMI_%u data island ctrl pkt ctrl=0x%02x", pkt->avport, pkt->packetctl); - - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); - } -}; - -struct av_set_acp_packet_cmd : public ps3av_cmd -{ - u16 get_size(vuart_av_thread& /*vuart*/, const void* /*pkt_buf*/) override - { - return sizeof(ps3av_pkt_set_acp_packet); - } - - void execute(vuart_av_thread& vuart, const void* pkt_buf) override - { - const auto pkt = static_cast(pkt_buf); - - if (pkt->avport > static_cast(UartAudioAvport::HDMI_1) || - (pkt->avport == static_cast(UartAudioAvport::HDMI_1) && !g_cfg.core.debug_console_mode) || - (pkt->pkt_type > 0x0A && pkt->pkt_type < 0x81) || - pkt->pkt_type > 0x85) - { - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_INVALID_PORT); - return; - } - - sys_uart.notice("[av_set_acp_packet_cmd] HDMI_%u data island pkt type=0x%02x", pkt->avport, pkt->pkt_type); - - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); - } -}; - -struct av_add_signal_ctl_cmd : public ps3av_cmd -{ - u16 get_size(vuart_av_thread& /*vuart*/, const void* /*pkt_buf*/) override - { - return sizeof(ps3av_pkt_add_signal_ctl); - } - - void execute(vuart_av_thread& vuart, const void* pkt_buf) override - { - const auto pkt = static_cast(pkt_buf); - - if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) - { - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_BUFFER_OVERFLOW); - return; - } - - if (pkt->avport != static_cast(UartAudioAvport::AVMULTI_0)) - { - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_INVALID_PORT); - return; - } - - sys_uart.notice("[av_add_signal_ctl_cmd] signal_ctl=0x%04x", pkt->signal_ctl); - - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); - } -}; - -struct av_set_cgms_wss_cmd : public ps3av_cmd -{ - u16 get_size(vuart_av_thread& /*vuart*/, const void* /*pkt_buf*/) override - { - return sizeof(ps3av_pkt_av_set_cgms_wss); - } - - // Something related to copy control on AVMULTI. - void execute(vuart_av_thread& vuart, const void* pkt_buf) override - { - const auto pkt = static_cast(pkt_buf); - - if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) - { - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_BUFFER_OVERFLOW); - return; - } - - if (pkt->avport != static_cast(UartAudioAvport::AVMULTI_0)) - { - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_INVALID_PORT); - return; - } - - sys_uart.notice("[av_set_cgms_wss_cmd] cgms_wss=0x%08x", pkt->cgms_wss); - - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); - } -}; - -struct av_get_hw_conf_cmd : public ps3av_cmd -{ - u16 get_size(vuart_av_thread& /*vuart*/, const void* /*pkt_buf*/) override - { - return sizeof(ps3av_header); - } - - void execute(vuart_av_thread& vuart, const void* pkt_buf) override - { - const auto pkt = static_cast(pkt_buf); - - if (vuart.get_reply_buf_free_size() < sizeof(ps3av_get_hw_info_reply) + sizeof(ps3av_pkt_reply_hdr)) - { - vuart.write_resp(pkt->cid, PS3AV_STATUS_BUFFER_OVERFLOW); - return; - } - - ps3av_get_hw_info_reply out{}; - out.num_of_hdmi = g_cfg.core.debug_console_mode ? 2 : 1; - out.num_of_avmulti = 1; - out.num_of_spdif = 1; - out.extra_bistream_support = 1; - - vuart.write_resp(pkt->cid, PS3AV_STATUS_SUCCESS, &out, sizeof(ps3av_get_hw_info_reply)); - } -}; - -struct av_set_hdmi_mode_cmd : public ps3av_cmd -{ - u16 get_size(vuart_av_thread& /*vuart*/, const void* /*pkt_buf*/) override - { - return sizeof(ps3av_pkt_set_hdmi_mode); - } - - void execute(vuart_av_thread& vuart, const void* pkt_buf) override - { - const auto pkt = static_cast(pkt_buf); - - if (pkt->mode != PS3AV_HDMI_BEHAVIOR_NORMAL) - { - if ((pkt->mode & PS3AV_HDMI_BEHAVIOR_HDCP_OFF) && !g_cfg.core.debug_console_mode) - { - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_UNSUPPORTED_HDMI_MODE); - return; - } - - if (pkt->mode & ~(PS3AV_HDMI_BEHAVIOR_HDCP_OFF | PS3AV_HDMI_BEHAVIOR_EDID_PASS | PS3AV_HDMI_BEHAVIOR_DVI)) - { - sys_uart.warning("[av_set_hdmi_mode_cmd] Unknown bits in hdmi mode: 0x%02x", pkt->mode); - } - } - - vuart.hdmi_behavior_mode = pkt->mode; - - vuart.add_hdmi_events(UartHdmiEvent::UNPLUGGED, vuart.hdmi_res_set[0] ? UartHdmiEvent::HDCP_DONE : UartHdmiEvent::PLUGGED, true, false); - vuart.add_hdmi_events(UartHdmiEvent::UNPLUGGED, vuart.hdmi_res_set[1] ? UartHdmiEvent::HDCP_DONE : UartHdmiEvent::PLUGGED, false, true); - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); - } -}; - -struct av_get_cec_status_cmd : public ps3av_cmd -{ - u16 get_size(vuart_av_thread& /*vuart*/, const void* /*pkt_buf*/) override - { - return sizeof(ps3av_header); - } - - void execute(vuart_av_thread& vuart, const void* pkt_buf) override - { - const auto pkt = static_cast(pkt_buf); - - if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_av_get_cec_config_reply) + sizeof(ps3av_pkt_reply_hdr)) - { - vuart.write_resp(pkt->cid, PS3AV_STATUS_BUFFER_OVERFLOW); - return; - } - - const ps3av_pkt_av_get_cec_config_reply reply{1}; - vuart.write_resp(pkt->cid, PS3AV_STATUS_SUCCESS, &reply, sizeof(ps3av_pkt_av_get_cec_config_reply)); - } -}; - -struct video_init_cmd : public ps3av_cmd -{ - u16 get_size(vuart_av_thread& /*vuart*/, const void* /*pkt_buf*/) override - { - return sizeof(ps3av_header); - } - - void execute(vuart_av_thread& vuart, const void* pkt_buf) override - { - const auto pkt = static_cast(pkt_buf); - - if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) - { - vuart.write_resp(pkt->cid, PS3AV_STATUS_BUFFER_OVERFLOW); - return; - } - - vuart.write_resp(pkt->cid, PS3AV_STATUS_SUCCESS); - } -}; - -struct video_set_format_cmd : public ps3av_cmd -{ - u16 get_size(vuart_av_thread& /*vuart*/, const void* /*pkt_buf*/) override - { - return sizeof(ps3av_pkt_video_format); - } - - void execute(vuart_av_thread& vuart, const void* pkt_buf) override - { - const auto pkt = static_cast(pkt_buf); - - if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) - { - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_BUFFER_OVERFLOW); - return; - } - - if (pkt->video_head > PS3AV_HEAD_B_ANALOG || pkt->video_order > 1 || pkt->video_format > 16) - { - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_INVALID_VIDEO_PARAM); - return; - } - - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); - } -}; - -struct video_set_route_cmd : public ps3av_cmd -{ - u16 get_size(vuart_av_thread& /*vuart*/, const void* /*pkt_buf*/) override - { - return 24; - } - - void execute(vuart_av_thread& vuart, const void* pkt_buf) override - { - const auto pkt = static_cast(pkt_buf); - - if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) - { - vuart.write_resp(pkt->cid, PS3AV_STATUS_BUFFER_OVERFLOW); - return; - } - - vuart.write_resp(pkt->cid, PS3AV_STATUS_NO_SEL); // Only available in PS2_GX_LPAR - } -}; - -struct video_set_pitch_cmd : public ps3av_cmd -{ - u16 get_size(vuart_av_thread& /*vuart*/, const void* /*pkt_buf*/) override - { - return sizeof(ps3av_pkt_video_set_pitch); - } - - void execute(vuart_av_thread& vuart, const void* pkt_buf) override - { - const auto pkt = static_cast(pkt_buf); - - if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) - { - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_BUFFER_OVERFLOW); - return; - } - - if (pkt->video_head > PS3AV_HEAD_B_ANALOG || (pkt->pitch & 7) != 0U || pkt->pitch > UINT16_MAX) - { - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_INVALID_VIDEO_PARAM); - return; - } - - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); - } -}; - -struct video_get_hw_cfg_cmd : public ps3av_cmd -{ - u16 get_size(vuart_av_thread& /*vuart*/, const void* /*pkt_buf*/) override - { - return sizeof(ps3av_header); - } - - void execute(vuart_av_thread& vuart, const void* pkt_buf) override - { - const auto pkt = static_cast(pkt_buf); - - if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_video_get_hw_cfg_reply) + sizeof(ps3av_pkt_reply_hdr)) - { - vuart.write_resp(pkt->cid, PS3AV_STATUS_BUFFER_OVERFLOW); - return; - } - - ps3av_pkt_video_get_hw_cfg_reply reply{}; - reply.gx_available = 0; // Set to 1 only in PS2_GX_LPAR - - vuart.write_resp(pkt->cid, PS3AV_STATUS_SUCCESS, &reply, sizeof(ps3av_pkt_video_get_hw_cfg_reply)); - } -}; - -struct audio_init_cmd : public ps3av_cmd -{ - u16 get_size(vuart_av_thread& /*vuart*/, const void* /*pkt_buf*/) override - { - return sizeof(ps3av_header); - } - - void execute(vuart_av_thread& vuart, const void* pkt_buf) override - { - const auto pkt = static_cast(pkt_buf); - - if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) - { - vuart.write_resp(pkt->cid, PS3AV_STATUS_BUFFER_OVERFLOW); - return; - } - - g_fxo->get().reset_hw(); - - vuart.write_resp(pkt->cid, PS3AV_STATUS_SUCCESS); - } -}; - -struct audio_set_mode_cmd : public ps3av_cmd -{ - u16 get_size(vuart_av_thread& /*vuart*/, const void* /*pkt_buf*/) override - { - return sizeof(ps3av_pkt_audio_mode); - } - - void execute(vuart_av_thread& vuart, const void* pkt_buf) override - { - const auto pkt = static_cast(pkt_buf); - - if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) - { - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_BUFFER_OVERFLOW); - return; - } - - if (!set_mode(*pkt)) - { - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_INVALID_AUDIO_PARAM); - } - else - { - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); - } - } - -private: - bool set_mode(const ps3av_pkt_audio_mode& pkt) - { - bool spdif_use_serial_buf = false; - RsxaudioPort avport_src, rsxaudio_port; - RsxaudioAvportIdx avport_idx; - - switch (pkt.avport) - { - case UartAudioAvport::HDMI_0: - { - avport_idx = RsxaudioAvportIdx::HDMI_0; - if (pkt.audio_source == UartAudioSource::SPDIF) - { - avport_src = rsxaudio_port = RsxaudioPort::SPDIF_1; - } - else - { - avport_src = rsxaudio_port = RsxaudioPort::SERIAL; - } - break; - } - case UartAudioAvport::HDMI_1: - { - avport_idx = RsxaudioAvportIdx::HDMI_1; - if (pkt.audio_source == UartAudioSource::SPDIF) - { - avport_src = rsxaudio_port = RsxaudioPort::SPDIF_1; - } - else - { - avport_src = rsxaudio_port = RsxaudioPort::SERIAL; - } - break; - } - case UartAudioAvport::AVMULTI_0: - { - avport_idx = RsxaudioAvportIdx::AVMULTI; - avport_src = rsxaudio_port = RsxaudioPort::SERIAL; - break; - } - case UartAudioAvport::SPDIF_0: - { - avport_idx = RsxaudioAvportIdx::SPDIF_0; - rsxaudio_port = RsxaudioPort::SPDIF_0; - if (pkt.audio_source == UartAudioSource::SERIAL) - { - spdif_use_serial_buf = true; - avport_src = RsxaudioPort::SERIAL; - } - else - { - avport_src = RsxaudioPort::SPDIF_0; - } - - break; - } - case UartAudioAvport::SPDIF_1: - { - avport_idx = RsxaudioAvportIdx::SPDIF_1; - rsxaudio_port = RsxaudioPort::SPDIF_1; - if (pkt.audio_source == UartAudioSource::SERIAL) - { - spdif_use_serial_buf = true; - avport_src = RsxaudioPort::SERIAL; - } - else - { - avport_src = RsxaudioPort::SPDIF_1; - } - - break; - } - default: - { - return false; - } - } - - if (static_cast(pkt.audio_fs.value()) > static_cast(UartAudioFreq::_192K)) - return false; - - const auto bit_cnt = [&]() - { - if ((rsxaudio_port != RsxaudioPort::SERIAL && pkt.audio_format != UartAudioFormat::PCM) || - pkt.audio_word_bits == UartAudioSampleSize::_16BIT) - { - return UartAudioSampleSize::_16BIT; - } - else - { - return UartAudioSampleSize::_24BIT; - } - }(); - - return commit_param(rsxaudio_port, avport_idx, avport_src, pkt.audio_fs, bit_cnt, spdif_use_serial_buf, pkt.audio_cs_info); - } - - bool commit_param(RsxaudioPort rsxaudio_port, RsxaudioAvportIdx avport, RsxaudioPort avport_src, UartAudioFreq freq, - UartAudioSampleSize bit_cnt, bool spdif_use_serial_buf, const u8* cs_data) - { - auto& rsxaudio_thread = g_fxo->get(); - const auto avport_idx = static_cast>(avport); - const auto rsxaudio_word_depth = bit_cnt == UartAudioSampleSize::_16BIT ? RsxaudioSampleSize::_16BIT : RsxaudioSampleSize::_32BIT; - const auto freq_param = [&]() - { - switch (freq) - { - case UartAudioFreq::_44K: return std::make_pair(8, SYS_RSXAUDIO_FREQ_BASE_352K); - default: - case UartAudioFreq::_48K: return std::make_pair(8, SYS_RSXAUDIO_FREQ_BASE_384K); - case UartAudioFreq::_88K: return std::make_pair(4, SYS_RSXAUDIO_FREQ_BASE_352K); - case UartAudioFreq::_96K: return std::make_pair(4, SYS_RSXAUDIO_FREQ_BASE_384K); - case UartAudioFreq::_176K: return std::make_pair(2, SYS_RSXAUDIO_FREQ_BASE_352K); - case UartAudioFreq::_192K: return std::make_pair(2, SYS_RSXAUDIO_FREQ_BASE_384K); - } - }(); - - switch (rsxaudio_port) - { - case RsxaudioPort::SERIAL: - { - rsxaudio_thread.update_hw_param([&](auto& obj) - { - obj.serial_freq_base = freq_param.second; - obj.serial.freq_div = freq_param.first; - obj.serial.depth = rsxaudio_word_depth; - obj.serial.buf_empty_en = true; - obj.avport_src[avport_idx] = avport_src; - }); - break; - } - case RsxaudioPort::SPDIF_0: - case RsxaudioPort::SPDIF_1: - { - const u8 spdif_idx = rsxaudio_port == RsxaudioPort::SPDIF_1; - - rsxaudio_thread.update_hw_param([&](auto& obj) - { - obj.spdif_freq_base = freq_param.second; - obj.spdif[spdif_idx].freq_div = freq_param.first; - obj.spdif[spdif_idx].depth = rsxaudio_word_depth; - obj.spdif[spdif_idx].use_serial_buf = spdif_use_serial_buf; - obj.spdif[spdif_idx].buf_empty_en = true; - obj.avport_src[avport_idx] = avport_src; - memcpy(obj.spdif[spdif_idx].cs_data.data(), cs_data, sizeof(obj.spdif[spdif_idx].cs_data)); - }); - break; - } - default: - { - return false; - } - } - - return true; - } -}; - -struct audio_mute_cmd : public ps3av_cmd -{ - u16 get_size(vuart_av_thread& /*vuart*/, const void* /*pkt_buf*/) override - { - // From RE - return 0; - } - - void execute(vuart_av_thread& vuart, const void* pkt_buf) override - { - const auto pkt = static_cast(pkt_buf); - - if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) - { - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_BUFFER_OVERFLOW); - return; - } - - switch (pkt->avport) - { - case UartAudioAvport::HDMI_0: - case UartAudioAvport::HDMI_1: - case UartAudioAvport::AVMULTI_0: - case UartAudioAvport::AVMULTI_1: - g_fxo->get().update_mute_state(RsxaudioPort::SERIAL, pkt->mute); - break; - case UartAudioAvport::SPDIF_0: - g_fxo->get().update_mute_state(RsxaudioPort::SPDIF_0, pkt->mute); - break; - case UartAudioAvport::SPDIF_1: - g_fxo->get().update_mute_state(RsxaudioPort::SPDIF_1, pkt->mute); - break; - default: - break; - } - - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); - } -}; - -struct audio_set_active_cmd : public ps3av_cmd -{ - u16 get_size(vuart_av_thread& /*vuart*/, const void* /*pkt_buf*/) override - { - return sizeof(ps3av_pkt_audio_set_active); - } - - void execute(vuart_av_thread& vuart, const void* pkt_buf) override - { - const auto pkt = static_cast(pkt_buf); - - if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) - { - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_BUFFER_OVERFLOW); - return; - } - - const bool requested_avports[SYS_RSXAUDIO_AVPORT_CNT] = - { - (pkt->audio_port & PS3AV_AUDIO_PORT_HDMI_0) != 0U, - (pkt->audio_port & PS3AV_AUDIO_PORT_HDMI_1) != 0U, - (pkt->audio_port & PS3AV_AUDIO_PORT_AVMULTI) != 0U, - (pkt->audio_port & PS3AV_AUDIO_PORT_SPDIF_0) != 0U, - (pkt->audio_port & PS3AV_AUDIO_PORT_SPDIF_1) != 0U}; - - g_fxo->get().update_hw_param([&](auto& obj) - { - for (u8 avport_idx = 0; avport_idx < SYS_RSXAUDIO_AVPORT_CNT; avport_idx++) - { - if (requested_avports[avport_idx]) - { - switch (obj.avport_src[avport_idx]) - { - case RsxaudioPort::SERIAL: - obj.serial.en = true; - break; - case RsxaudioPort::SPDIF_0: - case RsxaudioPort::SPDIF_1: - { - const u8 spdif_idx = obj.avport_src[avport_idx] == RsxaudioPort::SPDIF_1; - if (!obj.spdif[spdif_idx].use_serial_buf) - { - obj.spdif[spdif_idx].en = true; - } - break; - } - default: - break; - } - } - } - - obj.serial.muted = false; - obj.spdif[1].muted = false; - }); - - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); - } -}; - -struct audio_set_inactive_cmd : public ps3av_cmd -{ - u16 get_size(vuart_av_thread& /*vuart*/, const void* /*pkt_buf*/) override - { - return sizeof(ps3av_pkt_audio_set_active); - } - - void execute(vuart_av_thread& vuart, const void* pkt_buf) override - { - const auto pkt = static_cast(pkt_buf); - - if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) - { - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_BUFFER_OVERFLOW); - return; - } - - g_fxo->get().update_hw_param([&](auto& obj) - { - if ((pkt->audio_port & 0x8000'0000) == 0U) - { - obj.avport_src.fill(RsxaudioPort::INVALID); - } - - obj.serial.en = false; - obj.serial.muted = true; - obj.spdif[1].muted = true; - for (u8 spdif_idx = 0; spdif_idx < SYS_RSXAUDIO_SPDIF_CNT; spdif_idx++) - { - if (!obj.spdif[spdif_idx].use_serial_buf) - { - obj.spdif[spdif_idx].en = false; - } - } - }); - - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); - } -}; - -struct audio_spdif_bit_cmd : public ps3av_cmd -{ - u16 get_size(vuart_av_thread& /*vuart*/, const void* /*pkt_buf*/) override - { - return sizeof(ps3av_pkt_audio_spdif_bit); - } - - void execute(vuart_av_thread& vuart, const void* pkt_buf) override - { - const auto pkt = static_cast(pkt_buf); - - if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) - { - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_BUFFER_OVERFLOW); - return; - } - - const bool requested_avports[SYS_RSXAUDIO_AVPORT_CNT] = - { - (pkt->audio_port & PS3AV_AUDIO_PORT_HDMI_0) != 0U, - (pkt->audio_port & PS3AV_AUDIO_PORT_HDMI_1) != 0U, - (pkt->audio_port & PS3AV_AUDIO_PORT_AVMULTI) != 0U, - (pkt->audio_port & PS3AV_AUDIO_PORT_SPDIF_0) != 0U, - (pkt->audio_port & PS3AV_AUDIO_PORT_SPDIF_1) != 0U}; - - g_fxo->get().update_hw_param([&](auto& obj) - { - for (u8 avport_idx = 0; avport_idx < SYS_RSXAUDIO_AVPORT_CNT; avport_idx++) - { - if (requested_avports[avport_idx] && obj.avport_src[avport_idx] == RsxaudioPort::SPDIF_0) - { - auto& b_data = pkt->spdif_bit_data; - - sys_uart.notice("[audio_spdif_bit_cmd] Data 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x", - b_data[0], b_data[1], b_data[2], b_data[3], b_data[4], b_data[5], b_data[6], b_data[7], - b_data[8], b_data[9], b_data[10], b_data[11]); - break; - } - } - }); - - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); - } -}; - -struct audio_ctrl_cmd : public ps3av_cmd -{ - u16 get_size(vuart_av_thread& /*vuart*/, const void* /*pkt_buf*/) override - { - return sizeof(ps3av_pkt_audio_ctrl); - } - - void execute(vuart_av_thread& vuart, const void* pkt_buf) override - { - const auto pkt = static_cast(pkt_buf); - - if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) - { - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_BUFFER_OVERFLOW); - return; - } - - switch (pkt->audio_ctrl_id) - { - case UartAudioCtrlID::DAC_RESET: - case UartAudioCtrlID::DAC_DE_EMPHASIS: - case UartAudioCtrlID::AVCLK: - sys_uart.notice("[audio_ctrl_cmd] Option 0x%x", pkt->audio_ctrl_id); - break; - default: - break; - } - - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); - } -}; - -struct inc_avset_cmd : public ps3av_cmd -{ - u16 get_size(vuart_av_thread& /*vuart*/, const void* pkt_buf) override - { - const auto pkt = static_cast(pkt_buf); - - if (pkt->num_of_video_pkt > 2 || pkt->num_of_av_video_pkt > 4 || pkt->num_of_av_audio_pkt > 4) - { - return -1; - } - - const auto data_start = static_cast(pkt_buf) + sizeof(ps3av_pkt_inc_avset); - - u64 video_pkt_sec_size = 0; - u64 av_video_pkt_sec_size = 0; - u64 av_audio_pkt_sec_size = 0; - - for (u16 pkt_idx = 0; pkt_idx < pkt->num_of_video_pkt; pkt_idx++) - { - video_pkt_sec_size += reinterpret_cast(&data_start[video_pkt_sec_size])->length + 4ULL; - } - - for (u16 pkt_idx = 0; pkt_idx < pkt->num_of_av_video_pkt; pkt_idx++) - { - av_video_pkt_sec_size += reinterpret_cast(&data_start[video_pkt_sec_size + av_video_pkt_sec_size])->length + 4ULL; - } - - for (u16 pkt_idx = 0; pkt_idx < pkt->num_of_av_audio_pkt; pkt_idx++) - { - av_audio_pkt_sec_size += reinterpret_cast(&data_start[video_pkt_sec_size + av_video_pkt_sec_size + av_audio_pkt_sec_size])->length + 4ULL; - } - - return static_cast(sizeof(ps3av_pkt_inc_avset) + video_pkt_sec_size + av_video_pkt_sec_size + av_audio_pkt_sec_size); - } - - void execute(vuart_av_thread& vuart, const void* pkt_buf) override - { - const auto pkt = static_cast(pkt_buf); - auto pkt_data_addr = static_cast(pkt_buf) + sizeof(ps3av_pkt_inc_avset); - - if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) - { - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_BUFFER_OVERFLOW); - return; - } - - bool syscon_check_passed = true; - - // Video - u32 video_cmd_status = PS3AV_STATUS_SUCCESS; - for (u32 video_pkt_idx = 0; video_pkt_idx < pkt->num_of_video_pkt; ++video_pkt_idx) - { - const auto video_pkt = reinterpret_cast(pkt_data_addr); - const u32 subcmd_status = video_pkt_parse(*video_pkt); - - if (video_pkt->video_head == PS3AV_HEAD_B_ANALOG) - { - vuart.head_b_initialized = true; - } - - if (subcmd_status != PS3AV_STATUS_SUCCESS) - { - video_cmd_status = subcmd_status; - } - - pkt_data_addr += video_pkt->hdr.length + 4ULL; - } - - if (pkt->num_of_av_video_pkt == 0U && pkt->num_of_av_audio_pkt == 0U) - { - vuart.write_resp(pkt->hdr.cid, video_cmd_status); - return; - } - - bool hdcp_done[2]{}; - - // AV Video - for (u32 video_av_pkt_idx = 0; video_av_pkt_idx < pkt->num_of_av_video_pkt; video_av_pkt_idx++) - { - const auto av_video_pkt = reinterpret_cast(pkt_data_addr); - const av_video_resp subcmd_resp = av_video_pkt_parse(*av_video_pkt, syscon_check_passed); - - if (subcmd_resp.status != PS3AV_STATUS_SUCCESS) - { - vuart.write_resp(pkt->hdr.cid, subcmd_resp.status); - return; - } - - if (syscon_check_passed) - { - hdcp_done[0] |= subcmd_resp.hdcp_done_event[0]; - hdcp_done[1] |= subcmd_resp.hdcp_done_event[1]; - } - - pkt_data_addr += av_video_pkt->hdr.length + 4ULL; - } - - vuart.hdmi_res_set[0] = hdcp_done[0]; - vuart.hdmi_res_set[1] = hdcp_done[1]; - vuart.add_hdmi_events(UartHdmiEvent::HDCP_DONE, vuart.hdmi_res_set[0], vuart.hdmi_res_set[1]); - - if (vuart.hdmi_res_set[0]) - { - g_fxo->get().update_av_mute_state(RsxaudioAvportIdx::HDMI_0, false, true); - } - if (vuart.hdmi_res_set[1]) - { - g_fxo->get().update_av_mute_state(RsxaudioAvportIdx::HDMI_1, false, true); - } - - bool valid_av_audio_pkt = false; - - // AV Audio - for (u32 audio_av_pkt_idx = 0; audio_av_pkt_idx < pkt->num_of_av_audio_pkt; audio_av_pkt_idx++) - { - const auto av_audio_pkt = reinterpret_cast(pkt_data_addr); - - if (av_audio_pkt->avport <= static_cast(UartAudioAvport::HDMI_1)) - { - valid_av_audio_pkt = true; - - if (!syscon_check_passed || (av_audio_pkt->avport == static_cast(UartAudioAvport::HDMI_1) && !g_cfg.core.debug_console_mode)) - { - syscon_check_passed = false; - break; - } - - const u8 hdmi_idx = av_audio_pkt->avport == static_cast(UartAudioAvport::HDMI_1); - - g_fxo->get().update_hw_param([&](auto& obj) - { - auto& hdmi = obj.hdmi[hdmi_idx]; - hdmi.init = true; - - const std::array fifomap = - { - static_cast((av_audio_pkt->fifomap >> 0) & 3U), - static_cast((av_audio_pkt->fifomap >> 2) & 3U), - static_cast((av_audio_pkt->fifomap >> 4) & 3U), - static_cast((av_audio_pkt->fifomap >> 6) & 3U)}; - - const std::array en_streams = - { - static_cast(av_audio_pkt->enable & 0x10), - static_cast(av_audio_pkt->enable & 0x20), - static_cast(av_audio_pkt->enable & 0x40), - static_cast(av_audio_pkt->enable & 0x80)}; - - // Might be wrong - const std::array swap_lr = - { - static_cast(av_audio_pkt->swaplr & 0x10), - static_cast(av_audio_pkt->swaplr & 0x20), - static_cast(av_audio_pkt->swaplr & 0x40), - static_cast(av_audio_pkt->swaplr & 0x80)}; - - memcpy(hdmi.info_frame.data(), av_audio_pkt->info, sizeof(av_audio_pkt->info)); - memcpy(hdmi.chstat.data(), av_audio_pkt->chstat, sizeof(av_audio_pkt->chstat)); - - hdmi.ch_cfg = hdmi_param_conv(fifomap, en_streams, swap_lr); - }); - } - - pkt_data_addr += av_audio_pkt->hdr.length + 4ULL; - } - - if (pkt->num_of_av_video_pkt || valid_av_audio_pkt) - { - if (!syscon_check_passed) - { - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SYSCON_COMMUNICATE_FAIL); - return; - } - - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); - } - else - { - vuart.write_resp(pkt->hdr.cid, PS3AV_STATUS_SUCCESS); - } - } - -private: - struct video_sce_param - { - u32 width_div; - u32 width; - u32 height; - }; - - struct av_video_resp - { - u32 status = PS3AV_STATUS_SUCCESS; - bool hdcp_done_event[2]{}; - }; - - u32 video_pkt_parse(const ps3av_pkt_video_mode& video_head_cfg) - { - static constexpr video_sce_param sce_param_arr[28] = - { - {0, 0, 0}, - {4, 2880, 480}, - {4, 2880, 480}, - {4, 2880, 576}, - {4, 2880, 576}, - {2, 1440, 480}, - {2, 1440, 576}, - {1, 1920, 1080}, - {1, 1920, 1080}, - {1, 1920, 1080}, - {1, 1280, 720}, - {1, 1280, 720}, - {1, 1920, 1080}, - {1, 1920, 1080}, - {1, 1920, 1080}, - {1, 1920, 1080}, - {1, 1280, 768}, - {1, 1280, 1024}, - {1, 1920, 1200}, - {1, 1360, 768}, - {1, 1280, 1470}, - {1, 1280, 1470}, - {1, 1920, 1080}, - {1, 1920, 2205}, - {1, 1920, 2205}, - {1, 1280, 721}, - {1, 720, 481}, - {1, 720, 577}}; - - const auto sce_idx = [&]() -> u8 - { - switch (video_head_cfg.video_vid) - { - case 16: return 1; - case 1: return 2; - case 3: return 4; - case 17: return 4; - case 5: return 5; - case 6: return 6; - case 18: return 7; - case 7: return 8; - case 33: return 8; - case 8: return 9; - case 34: return 9; - case 9: return 10; - case 31: return 10; - case 10: return 11; - case 32: return 11; - case 11: return 12; - case 35: return 12; - case 37: return 12; - case 12: return 13; - case 36: return 13; - case 38: return 13; - case 19: return 14; - case 20: return 15; - case 13: return 16; - case 14: return 17; - case 15: return 18; - case 21: return 19; - case 22: return 20; - case 27: return 20; - case 23: return 21; - case 28: return 21; - case 24: return 22; - case 25: return 23; - case 29: return 23; - case 26: return 24; - case 30: return 24; - case 39: return 25; - case 40: return 26; - case 41: return 27; - default: return umax; - } - }(); - - const video_sce_param& sce_param = sce_param_arr[sce_idx]; - if (sce_idx == umax || - video_head_cfg.video_head > PS3AV_HEAD_B_ANALOG || - video_head_cfg.video_order > 1 || - video_head_cfg.video_format > 16 || - video_head_cfg.video_out_format > 16 || - ((1ULL << video_head_cfg.video_out_format) & 0x1CE07) == 0U || - video_head_cfg.unk2 > 3 || - video_head_cfg.pitch & 7 || - video_head_cfg.pitch > UINT16_MAX || - (video_head_cfg.width != 1280U && ((video_head_cfg.width & 7) != 0U || video_head_cfg.width > UINT16_MAX)) || - (sce_param.width != 720 && video_head_cfg.width > sce_param.width / sce_param.width_div) || - !((video_head_cfg.height == 1470U && (sce_param.height == 721 || sce_param.height == 481 || sce_param.height == 577)) || (video_head_cfg.height <= sce_param.height && video_head_cfg.height <= UINT16_MAX))) - { - return PS3AV_STATUS_INVALID_VIDEO_PARAM; - } - - sys_uart.notice("[inc_avset_cmd] new resolution on HEAD_%c width=%u height=%u", video_head_cfg.video_head == PS3AV_HEAD_A_HDMI ? 'A' : 'B', video_head_cfg.width, video_head_cfg.height); - - return PS3AV_STATUS_SUCCESS; - } - - av_video_resp av_video_pkt_parse(const ps3av_pkt_av_video_cs& pkt, bool& syscon_pkt_valid) - { - if (pkt.avport <= static_cast(UartAudioAvport::HDMI_1)) - { - if (pkt.av_vid > 23) - { - return {PS3AV_STATUS_INVALID_AV_PARAM}; - } - - if (pkt.avport == static_cast(UartAudioAvport::HDMI_1) && !g_cfg.core.debug_console_mode) - { - syscon_pkt_valid = false; - } - else if (syscon_pkt_valid) - { - // HDMI setup, code 0x80 - - av_video_resp resp{}; - resp.hdcp_done_event[pkt.avport] = true; - return resp; - } - } - else - { - if ((pkt.avport != static_cast(UartAudioAvport::AVMULTI_0) && pkt.avport != static_cast(UartAudioAvport::AVMULTI_1)) || - pkt.av_vid > 23 || - (pkt.av_vid > 12 && pkt.av_vid != 18U)) - { - return {PS3AV_STATUS_INVALID_AV_PARAM}; - } - - if (pkt.avport == static_cast(UartAudioAvport::AVMULTI_1)) - { - syscon_pkt_valid = false; - } - else if (syscon_pkt_valid) - { - // AVMULTI setup - } - } - - return {}; - } - - static rsxaudio_hw_param_t::hdmi_param_t::hdmi_ch_cfg_t hdmi_param_conv(const std::array& map, - const std::array& en, - const std::array& swap) - { - std::array result{}; - u8 ch_cnt = 0; - - for (usz stream_idx = 0; stream_idx < SYS_RSXAUDIO_SERIAL_STREAM_CNT; stream_idx++) - { - const u8 stream_pos = map[stream_idx]; - if (en[stream_pos]) - { - result[stream_idx * 2 + 0] = stream_pos * 2 + swap[stream_pos]; - result[stream_idx * 2 + 1] = stream_pos * 2 + !swap[stream_pos]; - ch_cnt = static_cast((stream_idx + 1) * 2); - } - else - { - result[stream_idx * 2 + 0] = rsxaudio_hw_param_t::hdmi_param_t::MAP_SILENT_CH; - result[stream_idx * 2 + 1] = rsxaudio_hw_param_t::hdmi_param_t::MAP_SILENT_CH; - } - } - - const AudioChannelCnt ch_cnt_conv = [&]() - { - switch (ch_cnt) - { - default: - case 0: - case 2: - return AudioChannelCnt::STEREO; - case 4: - case 6: - return AudioChannelCnt::SURROUND_5_1; - case 8: - return AudioChannelCnt::SURROUND_7_1; - } - }(); - - return {result, ch_cnt_conv}; - } -}; - -struct generic_reply_cmd : public ps3av_cmd -{ - u16 get_size(vuart_av_thread& /*vuart*/, const void* /*pkt_buf*/) override - { - return 0; - } - - void execute(vuart_av_thread& vuart, const void* pkt_buf) override - { - const auto pkt = static_cast(pkt_buf); - - if (vuart.get_reply_buf_free_size() < sizeof(ps3av_pkt_reply_hdr)) - { - vuart.write_resp(pkt->cid, PS3AV_STATUS_BUFFER_OVERFLOW); - return; - } - - sys_uart.todo("Unimplemented cid=0x%08x", pkt->cid); - - vuart.write_resp(pkt->cid, PS3AV_STATUS_SUCCESS); - } -}; - -error_code sys_uart_initialize(ppu_thread& ppu) -{ - ppu.state += cpu_flag::wait; - - sys_uart.trace("sys_uart_initialize()"); - - if (!g_ps3_process_info.has_root_perm()) - { - return CELL_ENOSYS; - } - - auto& vuart_thread = g_fxo->get(); - - if (vuart_thread.initialized.test_and_set()) - { - return CELL_EPERM; - } - - return CELL_OK; -} - -error_code sys_uart_receive(ppu_thread& ppu, vm::ptr buffer, u64 size, u32 mode) -{ - sys_uart.trace("sys_uart_receive(buffer=*0x%x, size=0x%llx, mode=0x%x)", buffer, size, mode); - - if (!g_ps3_process_info.has_root_perm()) - { - return CELL_ENOSYS; - } - - if (!size) - { - return CELL_OK; - } - - if (mode & ~(BLOCKING_BIG_OP | NOT_BLOCKING_BIG_OP)) - { - return CELL_EINVAL; - } - - auto& vuart_thread = g_fxo->get(); - - if (!vuart_thread.initialized) - { - return CELL_ESRCH; - } - - if (size > 0x20000U) - { - // kmalloc restriction - fmt::throw_exception("Buffer is too big"); - } - - const std::unique_ptr data = std::make_unique(size); - u32 read_size = 0; - - auto vuart_read = [&](u8* buf, u32 buf_size) -> s32 - { - constexpr u32 ITER_SIZE = 4096; - std::unique_lock lock(vuart_thread.rx_mutex, std::defer_lock); - - if (!lock.try_lock()) - { - return CELL_EBUSY; - } - - u32 read_size = 0; - u32 remaining = buf_size; - - while (read_size < buf_size) - { - const u32 packet_size = std::min(remaining, ITER_SIZE); - const u32 nread = vuart_thread.read_rx_data(buf + read_size, packet_size); - read_size += nread; - remaining -= nread; - - if (nread < packet_size) - break; - } - - return read_size; - }; - - if (mode & BLOCKING_BIG_OP) - { - // Yield before checking for packets - lv2_obj::sleep(ppu); - - for (;;) - { - if (ppu.is_stopped()) - { - return {}; - } - - std::unique_lock lock(vuart_thread.rx_wake_m); - const s32 read_result = vuart_read(data.get(), static_cast(size)); - - if (read_result > CELL_OK) - { - read_size = read_result; - break; - } - - vuart_thread.rx_wake_c.wait_unlock(5000, lock); - } - - ppu.check_state(); - } - else // NOT_BLOCKING_BIG_OP - { - const s32 read_result = vuart_read(data.get(), static_cast(size)); - - if (read_result <= CELL_OK) - { - return read_result; - } - - read_size = read_result; - } - - if (!vm::check_addr(buffer.addr(), vm::page_writable, read_size)) - { - return CELL_EFAULT; - } - - memcpy(buffer.get_ptr(), data.get(), read_size); - return not_an_error(read_size); -} - -error_code sys_uart_send(ppu_thread& ppu, vm::cptr buffer, u64 size, u32 mode) -{ - sys_uart.trace("sys_uart_send(buffer=0x%x, size=0x%llx, mode=0x%x)", buffer, size, mode); - - if (!g_ps3_process_info.has_root_perm()) - { - return CELL_ENOSYS; - } - - if (!size) - { - return CELL_OK; - } - - if (mode & ~(BLOCKING_BIG_OP | NOT_BLOCKING_OP | NOT_BLOCKING_BIG_OP)) - { - return CELL_EINVAL; - } - - auto& vuart_thread = g_fxo->get(); - - if (!vuart_thread.initialized) - { - return CELL_ESRCH; - } - - if (size > 0x20000U) - { - // kmalloc restriction - fmt::throw_exception("Buffer is too big"); - } - - if (!vm::check_addr(buffer.addr(), vm::page_readable, static_cast(size))) - { - return CELL_EFAULT; - } - - const std::unique_ptr data = std::make_unique(size); - memcpy(data.get(), buffer.get_ptr(), size); - - std::unique_lock lock(vuart_thread.tx_mutex, std::defer_lock); - - constexpr u32 ITER_SIZE = 4096; - - if (mode & BLOCKING_BIG_OP) - { - // Yield before sending packets - lv2_obj::sleep(ppu); - - lock.lock(); - - auto vuart_send_all = [&](const u8* data, u32 data_sz) - { - u32 rem_size = data_sz; - - while (rem_size) - { - if (ppu.is_stopped()) - { - return false; - } - - std::unique_lock lock(vuart_thread.tx_rdy_m); - if (vuart_thread.get_tx_bytes() >= PS3AV_TX_BUF_SIZE) - { - vuart_thread.tx_rdy_c.wait_unlock(5000, lock); - } - else - { - lock.unlock(); - } - rem_size -= vuart_thread.enque_tx_data(data + data_sz - rem_size, rem_size); - } - - return true; - }; - - u32 sent_size = 0; - u32 remaining = static_cast(size); - - while (remaining) - { - const u32 packet_size = std::min(remaining, ITER_SIZE); - if (!vuart_send_all(data.get() + sent_size, packet_size)) - return {}; - sent_size += packet_size; - remaining -= packet_size; - } - - ppu.check_state(); - } - else if (mode & NOT_BLOCKING_OP) - { - if (!lock.try_lock()) - { - return CELL_EBUSY; - } - - if (PS3AV_TX_BUF_SIZE - vuart_thread.get_tx_bytes() < size) - { - return CELL_EAGAIN; - } - - return not_an_error(vuart_thread.enque_tx_data(data.get(), static_cast(size))); - } - else // NOT_BLOCKING_BIG_OP - { - if (!lock.try_lock()) - { - return CELL_EBUSY; - } - - u32 sent_size = 0; - u32 remaining = static_cast(size); - - while (sent_size < size) - { - const u32 packet_size = std::min(remaining, ITER_SIZE); - const u32 nsent = vuart_thread.enque_tx_data(data.get() + sent_size, packet_size); - remaining -= nsent; - - if (nsent < packet_size) - { - // Based on RE - if (sent_size == 0) - { - return not_an_error(packet_size); // First iteration - } - else if (sent_size + nsent < size) - { - return not_an_error(sent_size + nsent); - } - else - { - break; // Last iteration - } - } - - sent_size += nsent; - } - } - - return not_an_error(size); -} - -error_code sys_uart_get_params(vm::ptr buffer) -{ - sys_uart.trace("sys_uart_get_params(buffer=0x%x)", buffer); - - if (!g_ps3_process_info.has_root_perm()) - { - return CELL_ENOSYS; - } - - auto& vuart_thread = g_fxo->get(); - - if (!vuart_thread.initialized) - { - return CELL_ESRCH; - } - - if (!vm::check_addr(buffer.addr(), vm::page_writable, sizeof(vuart_params))) - { - return CELL_EFAULT; - } - - buffer->rx_buf_size = PS3AV_RX_BUF_SIZE; - buffer->tx_buf_size = PS3AV_TX_BUF_SIZE; - - return CELL_OK; -} - -void vuart_av_thread::operator()() -{ - while (thread_ctrl::state() != thread_state::aborting) - { - if (Emu.IsPausedOrReady()) - { - thread_ctrl::wait_for(5000); - continue; - } - - const u64 hdmi_event_dist[2] = {hdmi_event_handler[0].time_until_next(), hdmi_event_handler[1].time_until_next()}; - bool update_dist = false; - - if (hdmi_event_dist[0] == 0) - { - dispatch_hdmi_event(hdmi_event_handler[0].get_occured_event(), UartAudioAvport::HDMI_0); - update_dist |= hdmi_event_handler[0].events_available(); - } - - if (hdmi_event_dist[1] == 0) - { - dispatch_hdmi_event(hdmi_event_handler[1].get_occured_event(), UartAudioAvport::HDMI_1); - update_dist |= hdmi_event_handler[1].events_available(); - } - - if (update_dist) - { - continue; - } - - const u64 wait_time = [&]() - { - if (hdmi_event_dist[0] != 0 && hdmi_event_dist[1] != 0) - return std::min(hdmi_event_dist[0], hdmi_event_dist[1]); - else - return std::max(hdmi_event_dist[0], hdmi_event_dist[1]); - }(); - - std::unique_lock lock(tx_wake_m); - if (!tx_buf.get_used_size()) - { - tx_wake_c.wait_unlock(wait_time ? wait_time : -1, lock); - } - else - { - lock.unlock(); - } - - if (u32 read_size = read_tx_data(temp_tx_buf, PS3AV_TX_BUF_SIZE)) - { - parse_tx_buffer(read_size); - - // Give vsh some time - thread_ctrl::wait_for(1000 * 100 / g_cfg.core.clocks_scale); - - commit_rx_buf(false); - commit_rx_buf(true); - } - } -} - -void vuart_av_thread::parse_tx_buffer(u32 buf_size) -{ - if (buf_size >= PS3AV_TX_BUF_SIZE) - { - while (read_tx_data(temp_tx_buf, PS3AV_TX_BUF_SIZE) >= PS3AV_TX_BUF_SIZE) - ; - write_resp(reinterpret_cast*>(temp_tx_buf)[3], PS3AV_STATUS_BUFFER_OVERFLOW); - return; - } - - u32 read_ptr = 0; - - while (buf_size) - { - const ps3av_header* const hdr = reinterpret_cast(&temp_tx_buf[read_ptr]); - const u16 pkt_size = hdr->length + 4; - - if (hdr->length == 0xFFFCU) - { - write_resp(0xDEAD, PS3AV_STATUS_FAILURE); - return; - } - - if (hdr->version != PS3AV_VERSION) - { - if (hdr->version >= 0x100 && hdr->version < PS3AV_VERSION) - { - sys_uart.todo("Unimplemented AV version: 0x%04x", hdr->version); - } - - write_resp(static_cast(hdr->cid.get()), PS3AV_STATUS_INVALID_COMMAND); - return; - } - - const void* const pkt_storage = &temp_tx_buf[read_ptr]; - read_ptr += pkt_size; - buf_size = buf_size < pkt_size ? 0 : buf_size - pkt_size; - - auto cmd = get_cmd(hdr->cid); - - if (!cmd.get()) - { - sys_uart.error("Unknown AV cmd: 0x%08x", hdr->cid); - continue; - } - - const auto cmd_size = cmd->get_size(*this, pkt_storage); - - if (cmd_size != pkt_size && cmd_size) - { - sys_uart.error("Invalid size for cid=0x%x, expected=0x%x, got=0x%x", static_cast(pkt_storage)->cid, cmd_size, pkt_size); - write_resp(static_cast(hdr->cid.get()), PS3AV_STATUS_INVALID_SAMPLE_SIZE); - return; - } - - cmd->execute(*this, pkt_storage); - } -} - -vuart_av_thread& vuart_av_thread::operator=(thread_state) -{ - { - std::lock_guard lock(tx_wake_m); - } - tx_wake_c.notify_all(); - return *this; -} - -u32 vuart_av_thread::enque_tx_data(const void* data, u32 data_sz) -{ - std::unique_lock lock(tx_wake_m); - if (u32 size = static_cast(tx_buf.push(data, data_sz, true))) - { - lock.unlock(); - tx_wake_c.notify_all(); - return size; - } - - return 0; -} - -u32 vuart_av_thread::get_tx_bytes() -{ - return static_cast(tx_buf.get_used_size()); -} - -u32 vuart_av_thread::read_rx_data(void* data, u32 data_sz) -{ - return static_cast(rx_buf.pop(data, data_sz, true)); -} - -u32 vuart_av_thread::read_tx_data(void* data, u32 data_sz) -{ - std::unique_lock lock(tx_rdy_m); - if (u32 size = static_cast(tx_buf.pop(data, data_sz, true))) - { - lock.unlock(); - tx_rdy_c.notify_all(); - return size; - } - return 0; -} - -u32 vuart_av_thread::get_reply_buf_free_size() -{ - return sizeof(temp_rx_buf.buf) - temp_rx_buf.crnt_size; -} - -template -void vuart_av_thread::write_resp(u32 cid, u32 status, const void* data, u16 data_size) -{ - const ps3av_pkt_reply_hdr pkt_hdr = - { - PS3AV_VERSION, - data_size + 8U, - cid | PS3AV_REPLY_BIT, - status}; - - if (status != PS3AV_STATUS_SUCCESS) - { - sys_uart.error("Packet failed cid=0x%08x status=0x%02x", cid, status); - } - - temp_buf& buf = UseScBuffer ? temp_rx_sc_buf : temp_rx_buf; - const u32 total_size = sizeof(pkt_hdr) + data_size; - - if (buf.crnt_size + total_size <= sizeof(buf.buf)) - { - memcpy(&buf.buf[buf.crnt_size], &pkt_hdr, sizeof(pkt_hdr)); - memcpy(&buf.buf[buf.crnt_size + sizeof(pkt_hdr)], data, data_size); - buf.crnt_size += total_size; - } -} - -std::shared_ptr vuart_av_thread::get_cmd(u32 cid) -{ - switch (cid) - { - case PS3AV_CID_AV_CEC_MESSAGE: - case PS3AV_CID_AV_UNK11: - case PS3AV_CID_AV_UNK12: - return std::make_shared(); - - // AV commands - case PS3AV_CID_AV_INIT: return std::make_shared(); - case PS3AV_CID_AV_FIN: return std::make_shared(); - case PS3AV_CID_AV_GET_HW_CONF: return std::make_shared(); - case PS3AV_CID_AV_GET_MONITOR_INFO: return std::make_shared(); - case PS3AV_CID_AV_GET_BKSV_LIST: return std::make_shared(); - case PS3AV_CID_AV_ENABLE_EVENT: return std::make_shared(); - case PS3AV_CID_AV_DISABLE_EVENT: return std::make_shared(); - case PS3AV_CID_AV_TV_MUTE: return std::make_shared(); - case PS3AV_CID_AV_NULL_CMD: return std::make_shared(); - case PS3AV_CID_AV_GET_AKSV: return std::make_shared(); - case PS3AV_CID_AV_VIDEO_DISABLE_SIG: return std::make_shared(); - case PS3AV_CID_AV_VIDEO_YTRAPCONTROL: return std::make_shared(); - case PS3AV_CID_AV_AUDIO_MUTE: return std::make_shared(); - case PS3AV_CID_AV_ACP_CTRL: return std::make_shared(); - case PS3AV_CID_AV_SET_ACP_PACKET: return std::make_shared(); - case PS3AV_CID_AV_ADD_SIGNAL_CTL: return std::make_shared(); - case PS3AV_CID_AV_SET_CGMS_WSS: return std::make_shared(); - case PS3AV_CID_AV_HDMI_MODE: return std::make_shared(); - case PS3AV_CID_AV_GET_CEC_CONFIG: return std::make_shared(); - - // Video commands - case PS3AV_CID_VIDEO_INIT: return std::make_shared(); - case PS3AV_CID_VIDEO_ROUTE: return std::make_shared(); - case PS3AV_CID_VIDEO_FORMAT: return std::make_shared(); - case PS3AV_CID_VIDEO_PITCH: return std::make_shared(); - case PS3AV_CID_VIDEO_GET_HW_CONF: return std::make_shared(); - - // Audio commands - case PS3AV_CID_AUDIO_INIT: return std::make_shared(); - case PS3AV_CID_AUDIO_MODE: return std::make_shared(); - case PS3AV_CID_AUDIO_MUTE: return std::make_shared(); - case PS3AV_CID_AUDIO_ACTIVE: return std::make_shared(); - case PS3AV_CID_AUDIO_INACTIVE: return std::make_shared(); - case PS3AV_CID_AUDIO_SPDIF_BIT: return std::make_shared(); - case PS3AV_CID_AUDIO_CTRL: return std::make_shared(); - - // Multipacket - case PS3AV_CID_AVB_PARAM: return std::make_shared(); - - default: return {}; - } -} - -void vuart_av_thread::commit_rx_buf(bool syscon_buf) -{ - temp_buf& buf = syscon_buf ? temp_rx_sc_buf : temp_rx_buf; - - std::unique_lock lock(rx_wake_m); - rx_buf.push(buf.buf, buf.crnt_size, true); - buf.crnt_size = 0; - - if (rx_buf.get_used_size()) - { - lock.unlock(); - rx_wake_c.notify_all(); - } -} - -void vuart_av_thread::add_hdmi_events(UartHdmiEvent first_event, UartHdmiEvent last_event, bool hdmi_0, bool hdmi_1) -{ - if (hdmi_0) - { - hdmi_event_handler[0].set_target_state(first_event, last_event); - } - - if (hdmi_1 && g_cfg.core.debug_console_mode) - { - hdmi_event_handler[1].set_target_state(first_event, last_event); - } -} - -void vuart_av_thread::add_hdmi_events(UartHdmiEvent last_event, bool hdmi_0, bool hdmi_1) -{ - add_hdmi_events(last_event, last_event, hdmi_0, hdmi_1); -} - -void vuart_av_thread::dispatch_hdmi_event(UartHdmiEvent event, UartAudioAvport hdmi) -{ - const bool hdmi_0 = hdmi == UartAudioAvport::HDMI_0; - const bool hdmi_1 = hdmi == UartAudioAvport::HDMI_1; - - switch (event) - { - case UartHdmiEvent::UNPLUGGED: - { - add_unplug_event(hdmi_0, hdmi_1); - break; - } - case UartHdmiEvent::PLUGGED: - { - add_plug_event(hdmi_0, hdmi_1); - break; - } - case UartHdmiEvent::HDCP_DONE: - { - add_hdcp_done_event(hdmi_0, hdmi_1); - break; - } - default: break; - } -} - -RsxaudioAvportIdx vuart_av_thread::avport_to_idx(UartAudioAvport avport) -{ - switch (avport) - { - case UartAudioAvport::HDMI_0: - return RsxaudioAvportIdx::HDMI_0; - case UartAudioAvport::HDMI_1: - return RsxaudioAvportIdx::HDMI_1; - case UartAudioAvport::AVMULTI_0: - return RsxaudioAvportIdx::AVMULTI; - case UartAudioAvport::SPDIF_0: - return RsxaudioAvportIdx::SPDIF_0; - case UartAudioAvport::SPDIF_1: - return RsxaudioAvportIdx::SPDIF_1; - default: - ensure(false); - return RsxaudioAvportIdx::HDMI_0; - } -} - -void vuart_av_thread::add_unplug_event(bool hdmi_0, bool hdmi_1) -{ - if ((hdmi_events_bitmask & PS3AV_EVENT_BIT_UNPLUGGED) == 0) - return; - - ps3av_header pkt{}; - pkt.version = av_cmd_ver; - pkt.length = sizeof(ps3av_header) - 4; - pkt.cid = PS3AV_CID_EVENT_UNPLUGGED; - - if (hdmi_0) - { - g_fxo->get().update_av_mute_state(RsxaudioAvportIdx::HDMI_0, false, true); - hdcp_first_auth[0] = true; - commit_event_data(&pkt, sizeof(pkt)); - } - - if (hdmi_1) - { - g_fxo->get().update_av_mute_state(RsxaudioAvportIdx::HDMI_1, false, true); - hdcp_first_auth[1] = true; - pkt.cid |= 0x10000; - commit_event_data(&pkt, sizeof(pkt)); - } -} - -void vuart_av_thread::add_plug_event(bool hdmi_0, bool hdmi_1) -{ - if ((hdmi_events_bitmask & PS3AV_EVENT_BIT_PLUGGED) == 0) - return; - - ps3av_pkt_hdmi_plugged_event pkt{}; - pkt.hdr.version = av_cmd_ver; - pkt.hdr.length = sizeof(ps3av_pkt_hdmi_plugged_event) - 8; - pkt.hdr.cid = PS3AV_CID_EVENT_PLUGGED; - - if (hdmi_0) - { - g_fxo->get().update_av_mute_state(RsxaudioAvportIdx::HDMI_0, false, true); - av_get_monitor_info_cmd::set_hdmi_display_cfg(*this, pkt.minfo, static_cast(UartAudioAvport::HDMI_0)); - commit_event_data(&pkt, sizeof(pkt) - 4); - } - - if (hdmi_1) - { - g_fxo->get().update_av_mute_state(RsxaudioAvportIdx::HDMI_1, false, true); - memset(&pkt.minfo, 0, sizeof(pkt.minfo)); - pkt.hdr.cid |= 0x10000; - av_get_monitor_info_cmd::set_hdmi_display_cfg(*this, pkt.minfo, static_cast(UartAudioAvport::HDMI_1)); - commit_event_data(&pkt, sizeof(pkt) - 4); - } -} - -void vuart_av_thread::add_hdcp_done_event(bool hdmi_0, bool hdmi_1) -{ - u16 pkt_size = offsetof(ps3av_pkt_hdmi_hdcp_done_event, ksv_arr); - ps3av_pkt_hdmi_hdcp_done_event pkt{}; - pkt.hdr.version = av_cmd_ver; - - if (hdmi_behavior_mode == PS3AV_HDMI_BEHAVIOR_NORMAL || !(hdmi_behavior_mode & PS3AV_HDMI_BEHAVIOR_HDCP_OFF)) - { - pkt.ksv_cnt = 1; - memcpy(&pkt.ksv_arr[0], PS3AV_BKSV_VALUE, sizeof(PS3AV_BKSV_VALUE)); - pkt_size = (pkt_size + 5 * pkt.ksv_cnt + 3) & 0xFFFC; - } - - pkt.hdr.length = pkt_size - 4; - - if (hdmi_0) - { - g_fxo->get().update_av_mute_state(RsxaudioAvportIdx::HDMI_0, false, true, false); - - if (hdcp_first_auth[0]) - { - if (hdmi_events_bitmask & PS3AV_EVENT_BIT_HDCP_DONE) - { - hdcp_first_auth[0] = false; - pkt.hdr.cid = PS3AV_CID_EVENT_HDCP_DONE; - commit_event_data(&pkt, pkt_size); - } - } - else if (hdmi_events_bitmask & PS3AV_EVENT_BIT_HDCP_REAUTH) - { - pkt.hdr.cid = PS3AV_CID_EVENT_HDCP_REAUTH; - commit_event_data(&pkt, pkt_size); - } - } - - if (hdmi_1) - { - g_fxo->get().update_av_mute_state(RsxaudioAvportIdx::HDMI_1, false, true, false); - - if (hdcp_first_auth[1]) - { - if (hdmi_events_bitmask & PS3AV_EVENT_BIT_HDCP_DONE) - { - hdcp_first_auth[1] = false; - pkt.hdr.cid = PS3AV_CID_EVENT_HDCP_DONE | 0x10000; - commit_event_data(&pkt, pkt_size); - } - } - else if (hdmi_events_bitmask & PS3AV_EVENT_BIT_HDCP_REAUTH) - { - pkt.hdr.cid = PS3AV_CID_EVENT_HDCP_REAUTH | 0x10000; - commit_event_data(&pkt, pkt_size); - } - } -} - -void vuart_av_thread::commit_event_data(const void* data, u16 data_size) -{ - std::unique_lock lock(rx_wake_m); - rx_buf.push(data, data_size, true); - - if (rx_buf.get_used_size()) - { - lock.unlock(); - rx_wake_c.notify_all(); - } -} - -vuart_hdmi_event_handler::vuart_hdmi_event_handler(u64 time_offset) : time_offset(time_offset) -{ -} - -void vuart_hdmi_event_handler::set_target_state(UartHdmiEvent start_state, UartHdmiEvent end_state) -{ - ensure(start_state != UartHdmiEvent::NONE && static_cast(start_state) <= static_cast(end_state)); - - base_state = static_cast(std::min(static_cast(start_state) - 1, static_cast(current_to_state))); - target_state = end_state; - - if (!events_available()) - { - advance_state(); - } -} - -bool vuart_hdmi_event_handler::events_available() -{ - return time_of_next_event != 0; -} - -u64 vuart_hdmi_event_handler::time_until_next() -{ - const u64 current_time = get_system_time(); - - if (!events_available() || current_time + EVENT_TIME_THRESHOLD >= time_of_next_event) - { - return 0; - } - - return time_of_next_event - current_time; -} - -UartHdmiEvent vuart_hdmi_event_handler::get_occured_event() -{ - if (events_available() && time_until_next() == 0) - { - const UartHdmiEvent occured = current_to_state; - advance_state(); - - return occured; - } - - return UartHdmiEvent::NONE; -} - -void vuart_hdmi_event_handler::schedule_next() -{ - time_of_next_event = get_system_time() + (EVENT_TIME_DURATION + time_offset) * 100 / g_cfg.core.clocks_scale; -} - -void vuart_hdmi_event_handler::advance_state() -{ - current_state = current_to_state; - - while (base_state != target_state) - { - base_state = static_cast(static_cast(base_state) + 1); - - if (current_state == UartHdmiEvent::UNPLUGGED && base_state == UartHdmiEvent::UNPLUGGED) - { - continue; - } - - if (current_state == UartHdmiEvent::PLUGGED && base_state == UartHdmiEvent::PLUGGED) - { - continue; - } - - if (current_state == UartHdmiEvent::HDCP_DONE && base_state == UartHdmiEvent::PLUGGED) - { - continue; - } - - current_to_state = base_state; - schedule_next(); - return; - } - - time_of_next_event = 0; -} diff --git a/rpcs3/Emu/Cell/lv2/sys_uart.h b/rpcs3/Emu/Cell/lv2/sys_uart.h deleted file mode 100644 index b203dc91e..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_uart.h +++ /dev/null @@ -1,759 +0,0 @@ -#pragma once - -#include "Emu/Memory/vm_ptr.h" -#include "util/mutex.h" -#include "util/cond.h" -#include "util/simple_ringbuf.h" - -enum : u32 -{ - PS3AV_RX_BUF_SIZE = 0x800, - PS3AV_TX_BUF_SIZE = 0x800, - - PS3AV_VERSION = 0x205, - - PS3AV_CID_AV_INIT = 0x00000001, - PS3AV_CID_AV_FIN = 0x00000002, - PS3AV_CID_AV_GET_HW_CONF = 0x00000003, - PS3AV_CID_AV_GET_MONITOR_INFO = 0x00000004, - PS3AV_CID_AV_GET_BKSV_LIST = 0x00000005, - PS3AV_CID_AV_ENABLE_EVENT = 0x00000006, - PS3AV_CID_AV_DISABLE_EVENT = 0x00000007, - PS3AV_CID_AV_GET_PORT_STATE = 0x00000009, - PS3AV_CID_AV_TV_MUTE = 0x0000000A, - PS3AV_CID_AV_NULL_CMD = 0x0000000B, - PS3AV_CID_AV_GET_AKSV = 0x0000000C, - PS3AV_CID_AV_UNK4 = 0x0000000D, - PS3AV_CID_AV_UNK5 = 0x0000000E, - - PS3AV_CID_AV_VIDEO_MUTE = 0x00010002, - PS3AV_CID_AV_VIDEO_DISABLE_SIG = 0x00010003, - PS3AV_CID_AV_VIDEO_YTRAPCONTROL = 0x00010004, - PS3AV_CID_AV_VIDEO_UNK5 = 0x00010005, - PS3AV_CID_AV_VIDEO_UNK6 = 0x00010006, - PS3AV_CID_AV_AUDIO_MUTE = 0x00020002, - PS3AV_CID_AV_ACP_CTRL = 0x00020003, - PS3AV_CID_AV_SET_ACP_PACKET = 0x00020004, - PS3AV_CID_AV_ADD_SIGNAL_CTL = 0x00030001, - PS3AV_CID_AV_SET_CC_CODE = 0x00030002, - PS3AV_CID_AV_SET_CGMS_WSS = 0x00030003, - PS3AV_CID_AV_SET_MACROVISION = 0x00030004, - PS3AV_CID_AV_UNK7 = 0x00030005, - PS3AV_CID_AV_UNK8 = 0x00030006, - PS3AV_CID_AV_UNK9 = 0x00030007, - PS3AV_CID_AV_HDMI_MODE = 0x00040001, - PS3AV_CID_AV_UNK15 = 0x00050001, - - PS3AV_CID_AV_CEC_MESSAGE = 0x000A0001, - PS3AV_CID_AV_GET_CEC_CONFIG = 0x000A0002, - PS3AV_CID_AV_UNK11 = 0x000A0003, - PS3AV_CID_AV_UNK12 = 0x000A0004, - PS3AV_CID_AV_UNK13 = 0x000A0005, - PS3AV_CID_AV_UNK14 = 0x000A0006, - - PS3AV_CID_VIDEO_INIT = 0x01000001, - PS3AV_CID_VIDEO_MODE = 0x01000002, - PS3AV_CID_VIDEO_ROUTE = 0x01000003, - PS3AV_CID_VIDEO_FORMAT = 0x01000004, - PS3AV_CID_VIDEO_PITCH = 0x01000005, - PS3AV_CID_VIDEO_GET_HW_CONF = 0x01000006, - PS3AV_CID_VIDEO_GET_REG = 0x01000008, - PS3AV_CID_VIDEO_UNK = 0x01000009, - PS3AV_CID_VIDEO_UNK1 = 0x0100000A, - PS3AV_CID_VIDEO_UNK2 = 0x0100000B, - PS3AV_CID_VIDEO_UNK3 = 0x0100000C, - - PS3AV_CID_AUDIO_INIT = 0x02000001, - PS3AV_CID_AUDIO_MODE = 0x02000002, - PS3AV_CID_AUDIO_MUTE = 0x02000003, - PS3AV_CID_AUDIO_ACTIVE = 0x02000004, - PS3AV_CID_AUDIO_INACTIVE = 0x02000005, - PS3AV_CID_AUDIO_SPDIF_BIT = 0x02000006, - PS3AV_CID_AUDIO_CTRL = 0x02000007, - - PS3AV_CID_AVB_PARAM = 0x04000001, - - PS3AV_CID_EVENT_UNPLUGGED = 0x10000001, - PS3AV_CID_EVENT_PLUGGED = 0x10000002, - PS3AV_CID_EVENT_HDCP_DONE = 0x10000003, - PS3AV_CID_EVENT_HDCP_FAIL = 0x10000004, - PS3AV_CID_EVENT_HDCP_REAUTH = 0x10000005, - PS3AV_CID_EVENT_HDCP_ERROR = 0x10000006, - - PS3AV_REPLY_BIT = 0x80000000, - - PS3AV_RESBIT_720x480P = 0x0003, /* 0x0001 | 0x0002 */ - PS3AV_RESBIT_720x576P = 0x0003, /* 0x0001 | 0x0002 */ - PS3AV_RESBIT_1280x720P = 0x0004, - PS3AV_RESBIT_1920x1080I = 0x0008, - PS3AV_RESBIT_1920x1080P = 0x4000, - - PS3AV_MONITOR_TYPE_NONE = 0, - PS3AV_MONITOR_TYPE_HDMI = 1, - PS3AV_MONITOR_TYPE_DVI = 2, - PS3AV_MONITOR_TYPE_AVMULTI = 3, - - PS3AV_COLORIMETRY_xvYCC_601 = 1, - PS3AV_COLORIMETRY_xvYCC_709 = 2, - PS3AV_COLORIMETRY_MD0 = 1 << 4, - PS3AV_COLORIMETRY_MD1 = 1 << 5, - PS3AV_COLORIMETRY_MD2 = 1 << 6, - - PS3AV_CS_SUPPORTED = 1, - PS3AV_RGB_SELECTABLE_QAUNTIZATION_RANGE = 8, - PS3AV_12BIT_COLOR = 16, - - PS3AV_MON_INFO_AUDIO_BLK_MAX = 16, - - PS3AV_MON_INFO_AUDIO_TYPE_LPCM = 1, - PS3AV_MON_INFO_AUDIO_TYPE_AC3 = 2, - PS3AV_MON_INFO_AUDIO_TYPE_AAC = 6, - PS3AV_MON_INFO_AUDIO_TYPE_DTS = 7, - PS3AV_MON_INFO_AUDIO_TYPE_DDP = 10, - PS3AV_MON_INFO_AUDIO_TYPE_DTS_HD = 11, - PS3AV_MON_INFO_AUDIO_TYPE_DOLBY_THD = 12, - - PS3AV_HDMI_BEHAVIOR_HDCP_OFF = 0x01, - PS3AV_HDMI_BEHAVIOR_DVI = 0x40, - PS3AV_HDMI_BEHAVIOR_EDID_PASS = 0x80, - PS3AV_HDMI_BEHAVIOR_NORMAL = 0xFF, - - PS3AV_EVENT_BIT_UNPLUGGED = 0x01, - PS3AV_EVENT_BIT_PLUGGED = 0x02, - PS3AV_EVENT_BIT_HDCP_DONE = 0x04, - PS3AV_EVENT_BIT_HDCP_FAIL = 0x08, - PS3AV_EVENT_BIT_HDCP_REAUTH = 0x10, - PS3AV_EVENT_BIT_HDCP_TOPOLOGY = 0x20, - PS3AV_EVENT_BIT_UNK = 0x80000000, - - PS3AV_HEAD_A_HDMI = 0, - PS3AV_HEAD_B_ANALOG = 1, - - PS3AV_AUDIO_PORT_HDMI_0 = 1 << 0, - PS3AV_AUDIO_PORT_HDMI_1 = 1 << 1, - PS3AV_AUDIO_PORT_AVMULTI = 1 << 10, - PS3AV_AUDIO_PORT_SPDIF_0 = 1 << 20, - PS3AV_AUDIO_PORT_SPDIF_1 = 1 << 21, - - PS3AV_STATUS_SUCCESS = 0x00, - PS3AV_STATUS_RECEIVE_VUART_ERROR = 0x01, - PS3AV_STATUS_SYSCON_COMMUNICATE_FAIL = 0x02, - PS3AV_STATUS_INVALID_COMMAND = 0x03, - PS3AV_STATUS_INVALID_PORT = 0x04, - PS3AV_STATUS_INVALID_VID = 0x05, - PS3AV_STATUS_INVALID_COLOR_SPACE = 0x06, - PS3AV_STATUS_INVALID_FS = 0x07, - PS3AV_STATUS_INVALID_AUDIO_CH = 0x08, - PS3AV_STATUS_UNSUPPORTED_VERSION = 0x09, - PS3AV_STATUS_INVALID_SAMPLE_SIZE = 0x0A, - PS3AV_STATUS_FAILURE = 0x0B, - PS3AV_STATUS_UNSUPPORTED_COMMAND = 0x0C, - PS3AV_STATUS_BUFFER_OVERFLOW = 0x0D, - PS3AV_STATUS_INVALID_VIDEO_PARAM = 0x0E, - PS3AV_STATUS_NO_SEL = 0x0F, - PS3AV_STATUS_INVALID_AV_PARAM = 0x10, - PS3AV_STATUS_INVALID_AUDIO_PARAM = 0x11, - PS3AV_STATUS_UNSUPPORTED_HDMI_MODE = 0x12, - PS3AV_STATUS_NO_SYNC_HEAD = 0x13, - PS3AV_STATUS_UNK_0x14 = 0x14, -}; - -const u8 PS3AV_AKSV_VALUE[5] = {0x00, 0x00, 0x0F, 0xFF, 0xFF}; -const u8 PS3AV_BKSV_VALUE[5] = {0xFF, 0xFF, 0xF0, 0x00, 0x00}; - -enum PS3_AV_OP_MODE : u32 -{ - // BIG operation modes could send more then 4096 bytes - - NOT_BLOCKING_BIG_OP = 0, - BLOCKING_BIG_OP = 1, - NOT_BLOCKING_OP = 2, -}; - -enum class UartHdmiEvent : u8 -{ - NONE = 0, - UNPLUGGED = 1, - PLUGGED = 2, - HDCP_DONE = 3, -}; - -enum class UartAudioCtrlID : u32 -{ - DAC_RESET = 0, - DAC_DE_EMPHASIS = 1, - AVCLK = 2, -}; - -enum class UartAudioAvport : u8 -{ - HDMI_0 = 0x0, - HDMI_1 = 0x1, - AVMULTI_0 = 0x10, - AVMULTI_1 = 0x11, - SPDIF_0 = 0x20, - SPDIF_1 = 0x21, -}; - -enum class UartAudioSource : u32 -{ - SERIAL = 0, - SPDIF = 1, -}; - -enum class UartAudioFreq : u32 -{ - _32K = 1, - _44K = 2, - _48K = 3, - _88K = 4, - _96K = 5, - _176K = 6, - _192K = 7, -}; - -enum class UartAudioFormat : u32 -{ - PCM = 1, - BITSTREAM = 0xFF, -}; - -enum class UartAudioSampleSize : u32 -{ - _16BIT = 1, - _20BIT = 2, - _24BIT = 3, -}; - -class vuart_hdmi_event_handler -{ -public: - vuart_hdmi_event_handler(u64 time_offset = 0); - - void set_target_state(UartHdmiEvent start_state, UartHdmiEvent end_state); - bool events_available(); - - u64 time_until_next(); - UartHdmiEvent get_occured_event(); - -private: - static constexpr u64 EVENT_TIME_DURATION = 20000; - static constexpr u64 EVENT_TIME_THRESHOLD = 1000; - - u64 time_of_next_event = 0; - const u64 time_offset = 0; - - // Assume that syscon initialized hdmi to plugged state - UartHdmiEvent current_state = UartHdmiEvent::PLUGGED; - UartHdmiEvent current_to_state = UartHdmiEvent::PLUGGED; - - UartHdmiEvent base_state = UartHdmiEvent::NONE; - UartHdmiEvent target_state = UartHdmiEvent::NONE; - - void schedule_next(); - void advance_state(); -}; - -class vuart_av_thread; - -struct ps3av_cmd -{ - virtual u16 get_size(vuart_av_thread& vuart, const void* pkt_buf) = 0; - virtual void execute(vuart_av_thread& vuart, const void* pkt_buf) = 0; - virtual ~ps3av_cmd() {}; -}; - -class vuart_av_thread -{ -public: - atomic_t initialized{}; - - shared_mutex rx_mutex{}; - shared_mutex tx_mutex{}; - - shared_mutex tx_wake_m{}; - cond_variable tx_wake_c{}; - shared_mutex tx_rdy_m{}; - cond_variable tx_rdy_c{}; - shared_mutex rx_wake_m{}; - cond_variable rx_wake_c{}; - - bool head_b_initialized = false; - u8 hdmi_behavior_mode = PS3AV_HDMI_BEHAVIOR_NORMAL; - u16 av_cmd_ver = 0; - u32 hdmi_events_bitmask = 0; - bool hdmi_res_set[2]{false, false}; - - void operator()(); - void parse_tx_buffer(u32 buf_size); - vuart_av_thread& operator=(thread_state); - - u32 enque_tx_data(const void* data, u32 data_sz); - u32 get_tx_bytes(); - u32 read_rx_data(void* data, u32 data_sz); - - u32 get_reply_buf_free_size(); - - template - void write_resp(u32 cid, u32 status, const void* data = nullptr, u16 data_size = 0); - - void add_hdmi_events(UartHdmiEvent first_event, UartHdmiEvent last_event, bool hdmi_0, bool hdmi_1); - void add_hdmi_events(UartHdmiEvent last_event, bool hdmi_0, bool hdmi_1); - - static RsxaudioAvportIdx avport_to_idx(UartAudioAvport avport); - - static constexpr auto thread_name = "VUART AV Thread"sv; - -private: - struct temp_buf - { - u32 crnt_size = 0; - u8 buf[PS3AV_RX_BUF_SIZE]{}; - }; - - simple_ringbuf tx_buf{PS3AV_TX_BUF_SIZE}; - simple_ringbuf rx_buf{PS3AV_RX_BUF_SIZE}; - - // uart_mngr could sometimes read past the tx_buffer due to weird size checks in FW, - // but no further than size of largest packet - u8 temp_tx_buf[PS3AV_TX_BUF_SIZE * 2]{}; - temp_buf temp_rx_buf{}; - temp_buf temp_rx_sc_buf{}; - - vuart_hdmi_event_handler hdmi_event_handler[2]{0, 5000}; - bool hdcp_first_auth[2]{true, true}; - - u32 read_tx_data(void* data, u32 data_sz); - std::shared_ptr get_cmd(u32 cid); - void commit_rx_buf(bool syscon_buf); - - void add_unplug_event(bool hdmi_0, bool hdmi_1); - void add_plug_event(bool hdmi_0, bool hdmi_1); - void add_hdcp_done_event(bool hdmi_0, bool hdmi_1); - void commit_event_data(const void* data, u16 data_size); - void dispatch_hdmi_event(UartHdmiEvent event, UartAudioAvport hdmi); -}; - -using vuart_av = named_thread; - -struct vuart_params -{ - be_t rx_buf_size; - be_t tx_buf_size; -}; - -static_assert(sizeof(vuart_params) == 16); - -struct ps3av_pkt_reply_hdr -{ - be_t version; - be_t length; - be_t cid; - be_t status; -}; - -static_assert(sizeof(ps3av_pkt_reply_hdr) == 12); - -struct ps3av_header -{ - be_t version; - be_t length; - be_t cid; -}; - -static_assert(sizeof(ps3av_header) == 8); - -struct ps3av_info_resolution -{ - be_t res_bits; - be_t native; -}; - -struct ps3av_info_cs -{ - u8 rgb; - u8 yuv444; - u8 yuv422; - u8 colorimetry_data; -}; - -struct ps3av_info_color -{ - be_t red_x; - be_t red_y; - be_t green_x; - be_t green_y; - be_t blue_x; - be_t blue_y; - be_t white_x; - be_t white_y; - be_t gamma; -}; - -struct ps3av_info_audio -{ - u8 type; - u8 max_num_of_ch; - u8 fs; - u8 sbit; -}; - -struct ps3av_get_monitor_info_reply -{ - u8 avport; - u8 monitor_id[10]; - u8 monitor_type; - u8 monitor_name[16]; - ps3av_info_resolution res_60; - ps3av_info_resolution res_50; - ps3av_info_resolution res_other; - ps3av_info_resolution res_vesa; - ps3av_info_cs cs; - ps3av_info_color color; - u8 supported_ai; - u8 speaker_info; - be_t num_of_audio_block; - ps3av_info_audio audio_info[PS3AV_MON_INFO_AUDIO_BLK_MAX]; - be_t hor_screen_size; - be_t ver_screen_size; - u8 supported_content_types; - u8 reserved_1[3]; - ps3av_info_resolution res_60_packed_3D; - ps3av_info_resolution res_50_packed_3D; - ps3av_info_resolution res_other_3D; - ps3av_info_resolution res_60_sbs_3D; - ps3av_info_resolution res_50_sbs_3D; - u8 vendor_specific_flags; - u8 reserved_2[7]; -}; - -static_assert(sizeof(ps3av_get_monitor_info_reply) == 208); - -struct ps3av_get_monitor_info -{ - ps3av_header hdr; - be_t avport; - be_t reserved; -}; - -static_assert(sizeof(ps3av_get_monitor_info) == 12); - -struct ps3av_get_hw_info_reply -{ - be_t num_of_hdmi; - be_t num_of_avmulti; - be_t num_of_spdif; - be_t extra_bistream_support; -}; - -static_assert(sizeof(ps3av_get_hw_info_reply) == 8); - -struct ps3av_pkt_set_hdmi_mode -{ - ps3av_header hdr; - u8 mode; - u8 resv[3]; -}; - -static_assert(sizeof(ps3av_pkt_set_hdmi_mode) == 12); - -struct ps3av_pkt_audio_mode -{ - ps3av_header hdr; - UartAudioAvport avport; - u8 reserved0[3]; - be_t mask; - be_t audio_num_of_ch; - be_t audio_fs; - be_t audio_word_bits; - be_t audio_format; - be_t audio_source; - u8 audio_enable[4]; - u8 audio_swap[4]; - u8 audio_map[4]; - be_t audio_layout; - be_t audio_downmix; - be_t audio_downmix_level; - u8 audio_cs_info[8]; -}; - -static_assert(sizeof(ps3av_pkt_audio_mode) == 68); - -struct ps3av_pkt_audio_mute -{ - ps3av_header hdr; - UartAudioAvport avport; - u8 reserved0[3]; - u8 mute; -}; - -static_assert(sizeof(ps3av_pkt_audio_mute) == 13); - -struct ps3av_pkt_audio_set_active -{ - ps3av_header hdr; - be_t audio_port; -}; - -static_assert(sizeof(ps3av_pkt_audio_set_active) == 12); - -struct ps3av_pkt_audio_spdif_bit -{ - ps3av_header hdr; - UartAudioAvport avport; - u8 reserved0[3]; - be_t audio_port; - be_t spdif_bit_data[12]; -}; - -static_assert(sizeof(ps3av_pkt_audio_spdif_bit) == 64); - -struct ps3av_pkt_audio_ctrl -{ - ps3av_header hdr; - be_t audio_ctrl_id; - be_t audio_ctrl_data[4]; -}; - -static_assert(sizeof(ps3av_pkt_audio_ctrl) == 28); - -struct ps3av_pkt_hdmi_plugged_event -{ - ps3av_header hdr; - ps3av_get_monitor_info_reply minfo; -}; - -static_assert(sizeof(ps3av_pkt_hdmi_plugged_event) == 216); - -struct ps3av_pkt_hdmi_hdcp_done_event -{ - ps3av_header hdr; - be_t ksv_cnt; - u8 ksv_arr[20][5]; -}; - -static_assert(sizeof(ps3av_pkt_hdmi_hdcp_done_event) == 112); - -struct ps3av_pkt_av_init -{ - ps3av_header hdr; - be_t event_bit; -}; - -static_assert(sizeof(ps3av_pkt_av_init) == 12); - -struct ps3av_pkt_av_init_reply -{ - be_t unk; -}; - -static_assert(sizeof(ps3av_pkt_av_init_reply) == 4); - -struct ps3av_pkt_enable_event -{ - ps3av_header hdr; - be_t event_bit; -}; - -static_assert(sizeof(ps3av_pkt_enable_event) == 12); - -struct ps3av_pkt_get_bksv -{ - ps3av_header hdr; - be_t avport; - u8 resv[2]; -}; - -static_assert(sizeof(ps3av_pkt_get_bksv) == 12); - -struct ps3av_pkt_get_bksv_reply -{ - be_t avport; - u8 resv[2]; - be_t ksv_cnt; - u8 ksv_arr[20][5]; -}; - -static_assert(sizeof(ps3av_pkt_get_bksv_reply) == 108); - -struct ps3av_pkt_video_get_hw_cfg_reply -{ - be_t gx_available; -}; - -static_assert(sizeof(ps3av_pkt_video_get_hw_cfg_reply) == 4); - -struct ps3av_pkt_video_set_pitch -{ - ps3av_header hdr; - be_t video_head; - be_t pitch; -}; - -static_assert(sizeof(ps3av_pkt_video_set_pitch) == 16); - -struct ps3av_pkt_get_aksv_reply -{ - be_t ksv_size; - u8 ksv_arr[2][5]; - u8 resv[2]; -}; - -static_assert(sizeof(ps3av_pkt_get_aksv_reply) == 16); - -struct ps3av_pkt_inc_avset -{ - ps3av_header hdr; - be_t num_of_video_pkt; - be_t num_of_audio_pkt; - be_t num_of_av_video_pkt; - be_t num_of_av_audio_pkt; -}; - -static_assert(sizeof(ps3av_pkt_inc_avset) == 16); - -struct ps3av_pkt_av_audio_param -{ - ps3av_header hdr; - be_t avport; - be_t resv; - u8 mclk; - u8 ns[3]; - u8 enable; - u8 swaplr; - u8 fifomap; - u8 inputctrl; - u8 inputlen; - u8 layout; - u8 info[5]; - u8 chstat[5]; -}; - -static_assert(sizeof(ps3av_pkt_av_audio_param) == 32); - -struct ps3av_pkt_av_video_cs -{ - ps3av_header hdr; - be_t avport; - be_t av_vid; - be_t av_cs_out; - be_t av_cs_in; - u8 dither; - u8 bitlen_out; - u8 super_white; - u8 aspect; - u8 unk1; - u8 unk2; - u8 resv[2]; -}; - -static_assert(sizeof(ps3av_pkt_av_video_cs) == 24); - -struct ps3av_pkt_video_mode -{ - ps3av_header hdr; - be_t video_head; - be_t unk1; - be_t unk2; - be_t video_vid; - be_t width; - be_t height; - be_t pitch; - be_t video_out_format; - be_t video_format; - be_t unk3; - be_t video_order; - be_t unk4; -}; - -static_assert(sizeof(ps3av_pkt_video_mode) == 48); - -struct ps3av_pkt_av_video_ytrapcontrol -{ - ps3av_header hdr; - be_t unk1; - be_t unk2; -}; - -static_assert(sizeof(ps3av_pkt_av_video_ytrapcontrol) == 12); - -struct ps3av_pkt_av_get_cec_config_reply -{ - be_t cec_present; -}; - -struct ps3av_pkt_video_format -{ - ps3av_header hdr; - be_t video_head; - be_t video_format; - be_t unk; - be_t video_order; -}; - -static_assert(sizeof(ps3av_pkt_video_format) == 20); - -struct ps3av_pkt_av_set_cgms_wss -{ - ps3av_header hdr; - be_t avport; - u8 resv[2]; - be_t cgms_wss; -}; - -static_assert(sizeof(ps3av_pkt_av_set_cgms_wss) == 16); - -struct ps3av_pkt_set_acp_packet -{ - ps3av_header hdr; - u8 avport; - u8 pkt_type; - u8 resv[2]; - u8 pkt_data[32]; -}; - -static_assert(sizeof(ps3av_pkt_set_acp_packet) == 44); - -struct ps3av_pkt_acp_ctrl -{ - ps3av_header hdr; - u8 avport; - u8 packetctl; - u8 resv[2]; -}; - -static_assert(sizeof(ps3av_pkt_acp_ctrl) == 12); - -struct ps3av_pkt_add_signal_ctl -{ - ps3av_header hdr; - be_t avport; - be_t signal_ctl; -}; - -static_assert(sizeof(ps3av_pkt_add_signal_ctl) == 12); - -struct ps3av_pkt_av_audio_mute -{ - ps3av_header hdr; - be_t avport; - be_t mute; -}; - -static_assert(sizeof(ps3av_pkt_av_audio_mute) == 12); - -struct ps3av_pkt_video_disable_sig -{ - ps3av_header hdr; - be_t avport; - be_t resv; -}; - -static_assert(sizeof(ps3av_pkt_video_disable_sig) == 12); - -// SysCalls - -error_code sys_uart_initialize(ppu_thread& ppu); -error_code sys_uart_receive(ppu_thread& ppu, vm::ptr buffer, u64 size, u32 mode); -error_code sys_uart_send(ppu_thread& ppu, vm::cptr buffer, u64 size, u32 mode); -error_code sys_uart_get_params(vm::ptr buffer); diff --git a/rpcs3/Emu/Cell/lv2/sys_usbd.cpp b/rpcs3/Emu/Cell/lv2/sys_usbd.cpp deleted file mode 100644 index e7dc92beb..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_usbd.cpp +++ /dev/null @@ -1,1595 +0,0 @@ -#include "stdafx.h" -#include "sys_usbd.h" -#include "sys_ppu_thread.h" -#include "sys_sync.h" - -#include -#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" - -#include - -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::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 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 (*make_instance)(u32, const std::array&); - 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(); - } - - 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& arg1, vm::ptr& arg2, vm::ptr& arg3); - void add_event(u64 arg1, u64 arg2, u64 arg3); - - // Transfers related functions - std::pair get_free_transfer(); - std::pair get_transfer_status(u32 transfer_id); - std::pair get_isochronous_transfer_status(u32 transfer_id); - void push_fake_transfer(UsbTransfer* transfer); - - const std::array& get_new_location(); - void connect_usb_device(std::shared_ptr dev, bool update_usb_devices = false); - void disconnect_usb_device(std::shared_ptr dev, bool update_usb_devices = false); - - // Map of devices actively handled by the ps3(device_id, device) - std::map>> handled_devices; - std::map>> pad_to_usb; - - shared_mutex mutex; - atomic_t is_init = false; - - // sys_usbd_receive_event PPU Threads - shared_mutex mutex_sq; - ppu_thread* sq{}; - - atomic_t 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 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> ldds; - - const std::vector 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 - {0x046D, 0xC283, 0xC29B, "lgFF_c283_c29b", nullptr, nullptr}, - {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 open_pipes; - // Transfers infos - shared_mutex mutex_transfers; - std::array transfers; - std::vector fake_transfers; - - // Queue of pending usbd events - std::queue> usbd_events; - - // List of devices "connected" to the ps3 - std::array location{}; - std::vector> usb_devices; - std::unordered_map> usb_passthrough_devices; - - libusb_context* ctx = nullptr; - -#if LIBUSB_API_VERSION >= 0x01000102 - libusb_hotplug_callback_handle callback_handle{}; -#endif - - bool hotplug_supported = false; -}; - -void LIBUSB_CALL callback_transfer(struct libusb_transfer* transfer) -{ - auto& usbh = g_fxo->get>(); - - if (!usbh.is_init) - return; - - usbh.transfer_complete(transfer); -} - -#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 - -#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 seen_usb_devices; - - if (ndev < 0) - { - sys_usbd.error("Failed to get device list: %s", libusb_error_name(static_cast(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(desc.idVendor) << 48) | (static_cast(desc.idProduct) << 32) | (static_cast(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_dev = std::make_shared(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 options = { - std::move(log_lv_opt), - std::move(log_cb_opt)}; - - if (int res = libusb_init_context(&ctx, options.data(), static_cast(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_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT), static_cast(0), LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY, - LIBUSB_HOTPLUG_MATCH_ANY, static_cast(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 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_info, get_new_location())); - } - -#ifndef WITHOUT_RTMIDI - const std::vector 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(get_new_location(), device.name, false)); - break; - case midi_device_type::guitar_22fret: - usb_devices.push_back(std::make_shared(get_new_location(), device.name, true)); - break; - case midi_device_type::keyboard: - usb_devices.push_back(std::make_shared(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(get_new_location(), device.name)); - - sys_usbd.notice("RB3 drums config=\n", g_cfg_rb3drums.to_string()); - break; - } - } -#endif -} - -usb_handler_thread::~usb_handler_thread() -{ - // Ensures shared_ptr 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); - } - -#if LIBUSB_API_VERSION >= 0x01000102 - if (ctx && hotplug_supported) - libusb_hotplug_deregister_callback(ctx, callback_handle); -#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(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& arg1, vm::ptr& arg2, vm::ptr& 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(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 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 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 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& usb_handler_thread::get_new_location() -{ - location[0]++; - return location; -} - -void usb_handler_thread::connect_usb_device(std::shared_ptr 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(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 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>(); - 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 dev = std::make_shared(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 dev = std::make_shared(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 dev = std::make_shared(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 dev = std::make_shared(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>()) - { - usbh->usb_hotplug_timeout = get_system_time() + (connected ? 1'000'000ull : 0); - } -} - -error_code sys_usbd_initialize(ppu_thread& ppu, vm::ptr handle) -{ - ppu.state += cpu_flag::wait; - - sys_usbd.warning("sys_usbd_initialize(handle=*0x%x)", handle); - - auto& usbh = g_fxo->get>(); - - { - 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>(); - - std::lock_guard lock(usbh.mutex); - usbh.is_init = false; - - // Forcefully awake all waiters - while (auto cpu = lv2_obj::schedule(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 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>(); - - std::lock_guard lock(usbh.mutex); - if (!usbh.is_init) - return CELL_EINVAL; - - // TODO: was std::min - u32 i_tocopy = std::min(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 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>(); - - 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 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>(); - - 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>(); - - 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 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>(); - - 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(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 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> 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 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>(); - - 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(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>(); - - 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>(); - - 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 arg1, vm::ptr arg2, vm::ptr 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>(); - - { - 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 buf, u32 buf_size, vm::ptr 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>(); - - 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(+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 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>(); - - 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 result, vm::ptr 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>(); - - 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 request, vm::ptr 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>(); - - 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 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>(); - - 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>(); - - 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; -} diff --git a/rpcs3/Emu/Cell/lv2/sys_usbd.h b/rpcs3/Emu/Cell/lv2/sys_usbd.h deleted file mode 100644 index 81f39f29c..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_usbd.h +++ /dev/null @@ -1,92 +0,0 @@ -#pragma once - -#include "Emu/Memory/vm_ptr.h" -#include "Emu/Cell/ErrorCodes.h" -#include "Input/product_info.h" - -class ppu_thread; - -#define MAX_SYS_USBD_TRANSFERS 0x44 - -// PS3 internal codes -enum PS3StandardUsbErrors : u32 -{ - HC_CC_NOERR = 0x00, - EHCI_CC_MISSMF = 0x10, - EHCI_CC_XACT = 0x20, - EHCI_CC_BABBLE = 0x30, - EHCI_CC_DATABUF = 0x40, - EHCI_CC_HALTED = 0x50, -}; - -enum PS3IsochronousUsbErrors : u8 -{ - USBD_HC_CC_NOERR = 0x00, - USBD_HC_CC_MISSMF = 0x01, - USBD_HC_CC_XACT = 0x02, - USBD_HC_CC_BABBLE = 0x04, - USBD_HC_CC_DATABUF = 0x08, -}; - -enum SysUsbdEvents : u32 -{ - SYS_USBD_ATTACH = 0x01, - SYS_USBD_DETACH = 0x02, - SYS_USBD_TRANSFER_COMPLETE = 0x03, - SYS_USBD_TERMINATE = 0x04, -}; - -// PS3 internal structures -struct UsbInternalDevice -{ - u8 device_high; // System flag maybe (used in generating actual device number) - u8 device_low; // Just a number identifying the device (used in generating actual device number) - u8 unk3; // ? Seems to always be 2? - u8 unk4; // ? -}; - -struct UsbDeviceRequest -{ - u8 bmRequestType; - u8 bRequest; - be_t wValue; - be_t wIndex; - be_t wLength; -}; - -struct UsbDeviceIsoRequest -{ - vm::ptr buf; - be_t start_frame; - be_t num_packets; - be_t packets[8]; -}; - -error_code sys_usbd_initialize(ppu_thread& ppu, vm::ptr handle); -error_code sys_usbd_finalize(ppu_thread& ppu, u32 handle); -error_code sys_usbd_get_device_list(ppu_thread& ppu, u32 handle, vm::ptr device_list, u32 max_devices); -error_code sys_usbd_get_descriptor_size(ppu_thread& ppu, u32 handle, u32 device_handle); -error_code sys_usbd_get_descriptor(ppu_thread& ppu, u32 handle, u32 device_handle, vm::ptr descriptor, u32 desc_size); -error_code sys_usbd_register_ldd(ppu_thread& ppu, u32 handle, vm::cptr s_product, u16 slen_product); -error_code sys_usbd_unregister_ldd(ppu_thread& ppu, u32 handle, vm::cptr s_product, u16 slen_product); -error_code sys_usbd_open_pipe(ppu_thread& ppu, u32 handle, u32 device_handle, u32 unk1, u64 unk2, u64 unk3, u32 endpoint, u64 unk4); -error_code sys_usbd_open_default_pipe(ppu_thread& ppu, u32 handle, u32 device_handle); -error_code sys_usbd_close_pipe(ppu_thread& ppu, u32 handle, u32 pipe_handle); -error_code sys_usbd_receive_event(ppu_thread& ppu, u32 handle, vm::ptr arg1, vm::ptr arg2, vm::ptr arg3); -error_code sys_usbd_detect_event(ppu_thread& ppu); -error_code sys_usbd_attach(ppu_thread& ppu, u32 handle, u32 unk1, u32 unk2, u32 device_handle); -error_code sys_usbd_transfer_data(ppu_thread& ppu, u32 handle, u32 id_pipe, vm::ptr buf, u32 buf_size, vm::ptr request, u32 type_transfer); -error_code sys_usbd_isochronous_transfer_data(ppu_thread& ppu, u32 handle, u32 id_pipe, vm::ptr iso_request); -error_code sys_usbd_get_transfer_status(ppu_thread& ppu, u32 handle, u32 id_transfer, u32 unk1, vm::ptr result, vm::ptr count); -error_code sys_usbd_get_isochronous_transfer_status(ppu_thread& ppu, u32 handle, u32 id_transfer, u32 unk1, vm::ptr request, vm::ptr result); -error_code sys_usbd_get_device_location(ppu_thread& ppu, u32 handle, u32 device_handle, vm::ptr location); -error_code sys_usbd_send_event(ppu_thread& ppu); -error_code sys_usbd_event_port_send(ppu_thread& ppu, u32 handle, u64 arg1, u64 arg2, u64 arg3); -error_code sys_usbd_allocate_memory(ppu_thread& ppu); -error_code sys_usbd_free_memory(ppu_thread& ppu); -error_code sys_usbd_get_device_speed(ppu_thread& ppu); -error_code sys_usbd_register_extra_ldd(ppu_thread& ppu, u32 handle, vm::cptr s_product, u16 slen_product, u16 id_vendor, u16 id_product_min, u16 id_product_max); -error_code sys_usbd_unregister_extra_ldd(ppu_thread& ppu, u32 handle, vm::cptr s_product, u16 slen_product); - -void connect_usb_controller(u8 index, input::product_type); -void handle_hotplug_event(bool connected); diff --git a/rpcs3/Emu/Cell/lv2/sys_vm.cpp b/rpcs3/Emu/Cell/lv2/sys_vm.cpp deleted file mode 100644 index aa9ac9ff2..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_vm.cpp +++ /dev/null @@ -1,437 +0,0 @@ -#include "stdafx.h" -#include "sys_vm.h" -#include "sys_process.h" - -#include "Emu/IdManager.h" -#include "Emu/Cell/ErrorCodes.h" -#include "Emu/Cell/PPUThread.h" -#include "Emu/Cell/timers.hpp" - -sys_vm_t::sys_vm_t(u32 _addr, u32 vsize, lv2_memory_container* ct, u32 psize) - : ct(ct), addr(_addr), size(vsize), psize(psize) -{ - // Write ID - g_ids[addr >> 28].release(idm::last_id()); -} - -void sys_vm_t::save(utils::serial& ar) -{ - USING_SERIALIZATION_VERSION(lv2_vm); - ar(ct->id, addr, size, psize); -} - -sys_vm_t::~sys_vm_t() -{ - // Free ID - g_ids[addr >> 28].release(id_manager::id_traits::invalid); -} - -LOG_CHANNEL(sys_vm); - -struct sys_vm_global_t -{ - atomic_t total_vsize = 0; -}; - -sys_vm_t::sys_vm_t(utils::serial& ar) - : ct(lv2_memory_container::search(ar)), addr(ar), size(ar), psize(ar) -{ - g_ids[addr >> 28].release(idm::last_id()); - g_fxo->need(); - g_fxo->get().total_vsize += size; -} - -error_code sys_vm_memory_map(ppu_thread& ppu, u64 vsize, u64 psize, u32 cid, u64 flag, u64 policy, vm::ptr addr) -{ - ppu.state += cpu_flag::wait; - - sys_vm.warning("sys_vm_memory_map(vsize=0x%x, psize=0x%x, cid=0x%x, flags=0x%x, policy=0x%x, addr=*0x%x)", vsize, psize, cid, flag, policy, addr); - - if (!vsize || !psize || vsize % 0x200'0000 || vsize > 0x1000'0000 || psize % 0x1'0000 || policy != SYS_VM_POLICY_AUTO_RECOMMENDED) - { - return CELL_EINVAL; - } - - if (ppu.gpr[11] == 300 && psize < 0x10'0000) - { - return CELL_EINVAL; - } - - const auto idm_ct = idm::get_unlocked(cid); - - const auto ct = cid == SYS_MEMORY_CONTAINER_ID_INVALID ? &g_fxo->get() : idm_ct.get(); - - if (!ct) - { - return CELL_ESRCH; - } - - if (!g_fxo->get().total_vsize.fetch_op([vsize, has_root = g_ps3_process_info.has_root_perm()](u32& size) - { - // A single process can hold up to 256MB of virtual memory, even on DECR - // VSH can hold more - if ((has_root ? 0x1E000000 : 0x10000000) - size < vsize) - { - return false; - } - - size += static_cast(vsize); - return true; - }) - .second) - { - return CELL_EBUSY; - } - - if (!ct->take(psize)) - { - g_fxo->get().total_vsize -= static_cast(vsize); - return CELL_ENOMEM; - } - - // Look for unmapped space - if (const auto area = vm::find_map(0x10000000, 0x10000000, 2 | (flag & SYS_MEMORY_PAGE_SIZE_MASK))) - { - sys_vm.warning("sys_vm_memory_map(): Found VM 0x%x area (vsize=0x%x)", addr, vsize); - - // Alloc all memory (shall not fail) - ensure(area->alloc(static_cast(vsize))); - vm::lock_sudo(area->addr, static_cast(vsize)); - - idm::make(area->addr, static_cast(vsize), ct, static_cast(psize)); - - // Write a pointer for the allocated memory - ppu.check_state(); - *addr = area->addr; - return CELL_OK; - } - - ct->free(psize); - g_fxo->get().total_vsize -= static_cast(vsize); - return CELL_ENOMEM; -} - -error_code sys_vm_memory_map_different(ppu_thread& ppu, u64 vsize, u64 psize, u32 cid, u64 flag, u64 policy, vm::ptr addr) -{ - ppu.state += cpu_flag::wait; - - sys_vm.warning("sys_vm_memory_map_different(vsize=0x%x, psize=0x%x, cid=0x%x, flags=0x%llx, policy=0x%llx, addr=*0x%x)", vsize, psize, cid, flag, policy, addr); - // TODO: if needed implement different way to map memory, unconfirmed. - - return sys_vm_memory_map(ppu, vsize, psize, cid, flag, policy, addr); -} - -error_code sys_vm_unmap(ppu_thread& ppu, u32 addr) -{ - ppu.state += cpu_flag::wait; - - sys_vm.warning("sys_vm_unmap(addr=0x%x)", addr); - - // Special case, check if its a start address by alignment - if (addr % 0x10000000) - { - return CELL_EINVAL; - } - - // Free block and info - const auto vmo = idm::withdraw(sys_vm_t::find_id(addr), [&](sys_vm_t& vmo) - { - // Free block - ensure(vm::unmap(addr).second); - - // Return memory - vmo.ct->free(vmo.psize); - g_fxo->get().total_vsize -= vmo.size; - }); - - if (!vmo) - { - return CELL_EINVAL; - } - - return CELL_OK; -} - -error_code sys_vm_append_memory(ppu_thread& ppu, u32 addr, u64 size) -{ - ppu.state += cpu_flag::wait; - - sys_vm.warning("sys_vm_append_memory(addr=0x%x, size=0x%x)", addr, size); - - if (!size || size % 0x100000) - { - return CELL_EINVAL; - } - - const auto block = idm::check(sys_vm_t::find_id(addr), [&](sys_vm_t& vmo) -> CellError - { - if (vmo.addr != addr) - { - return CELL_EINVAL; - } - - if (!vmo.ct->take(size)) - { - return CELL_ENOMEM; - } - - vmo.psize += static_cast(size); - return {}; - }); - - if (!block) - { - return CELL_EINVAL; - } - - if (block.ret) - { - return block.ret; - } - - return CELL_OK; -} - -error_code sys_vm_return_memory(ppu_thread& ppu, u32 addr, u64 size) -{ - ppu.state += cpu_flag::wait; - - sys_vm.warning("sys_vm_return_memory(addr=0x%x, size=0x%x)", addr, size); - - if (!size || size % 0x100000) - { - return CELL_EINVAL; - } - - const auto block = idm::check(sys_vm_t::find_id(addr), [&](sys_vm_t& vmo) -> CellError - { - if (vmo.addr != addr) - { - return CELL_EINVAL; - } - - auto [_, ok] = vmo.psize.fetch_op([&](u32& value) - { - if (value <= size || value - size < 0x100000ull) - { - return false; - } - - value -= static_cast(size); - return true; - }); - - if (!ok) - { - return CELL_EBUSY; - } - - vmo.ct->free(size); - return {}; - }); - - if (!block) - { - return CELL_EINVAL; - } - - if (block.ret) - { - return block.ret; - } - - return CELL_OK; -} - -error_code sys_vm_lock(ppu_thread& ppu, u32 addr, u32 size) -{ - ppu.state += cpu_flag::wait; - - sys_vm.warning("sys_vm_lock(addr=0x%x, size=0x%x)", addr, size); - - if (!size) - { - return CELL_EINVAL; - } - - const auto block = idm::get_unlocked(sys_vm_t::find_id(addr)); - - if (!block || u64{addr} + size > u64{block->addr} + block->size) - { - return CELL_EINVAL; - } - - return CELL_OK; -} - -error_code sys_vm_unlock(ppu_thread& ppu, u32 addr, u32 size) -{ - ppu.state += cpu_flag::wait; - - sys_vm.warning("sys_vm_unlock(addr=0x%x, size=0x%x)", addr, size); - - if (!size) - { - return CELL_EINVAL; - } - - const auto block = idm::get_unlocked(sys_vm_t::find_id(addr)); - - if (!block || u64{addr} + size > u64{block->addr} + block->size) - { - return CELL_EINVAL; - } - - return CELL_OK; -} - -error_code sys_vm_touch(ppu_thread& ppu, u32 addr, u32 size) -{ - ppu.state += cpu_flag::wait; - - sys_vm.warning("sys_vm_touch(addr=0x%x, size=0x%x)", addr, size); - - if (!size) - { - return CELL_EINVAL; - } - - const auto block = idm::get_unlocked(sys_vm_t::find_id(addr)); - - if (!block || u64{addr} + size > u64{block->addr} + block->size) - { - return CELL_EINVAL; - } - - return CELL_OK; -} - -error_code sys_vm_flush(ppu_thread& ppu, u32 addr, u32 size) -{ - ppu.state += cpu_flag::wait; - - sys_vm.warning("sys_vm_flush(addr=0x%x, size=0x%x)", addr, size); - - if (!size) - { - return CELL_EINVAL; - } - - const auto block = idm::get_unlocked(sys_vm_t::find_id(addr)); - - if (!block || u64{addr} + size > u64{block->addr} + block->size) - { - return CELL_EINVAL; - } - - return CELL_OK; -} - -error_code sys_vm_invalidate(ppu_thread& ppu, u32 addr, u32 size) -{ - ppu.state += cpu_flag::wait; - - sys_vm.warning("sys_vm_invalidate(addr=0x%x, size=0x%x)", addr, size); - - if (!size) - { - return CELL_EINVAL; - } - - const auto block = idm::get_unlocked(sys_vm_t::find_id(addr)); - - if (!block || u64{addr} + size > u64{block->addr} + block->size) - { - return CELL_EINVAL; - } - - return CELL_OK; -} - -error_code sys_vm_store(ppu_thread& ppu, u32 addr, u32 size) -{ - ppu.state += cpu_flag::wait; - - sys_vm.warning("sys_vm_store(addr=0x%x, size=0x%x)", addr, size); - - if (!size) - { - return CELL_EINVAL; - } - - const auto block = idm::get_unlocked(sys_vm_t::find_id(addr)); - - if (!block || u64{addr} + size > u64{block->addr} + block->size) - { - return CELL_EINVAL; - } - - return CELL_OK; -} - -error_code sys_vm_sync(ppu_thread& ppu, u32 addr, u32 size) -{ - ppu.state += cpu_flag::wait; - - sys_vm.warning("sys_vm_sync(addr=0x%x, size=0x%x)", addr, size); - - if (!size) - { - return CELL_EINVAL; - } - - const auto block = idm::get_unlocked(sys_vm_t::find_id(addr)); - - if (!block || u64{addr} + size > u64{block->addr} + block->size) - { - return CELL_EINVAL; - } - - return CELL_OK; -} - -error_code sys_vm_test(ppu_thread& ppu, u32 addr, u32 size, vm::ptr result) -{ - ppu.state += cpu_flag::wait; - - sys_vm.warning("sys_vm_test(addr=0x%x, size=0x%x, result=*0x%x)", addr, size, result); - - const auto block = idm::get_unlocked(sys_vm_t::find_id(addr)); - - if (!block || u64{addr} + size > u64{block->addr} + block->size) - { - return CELL_EINVAL; - } - - ppu.check_state(); - *result = SYS_VM_STATE_ON_MEMORY; - - return CELL_OK; -} - -error_code sys_vm_get_statistics(ppu_thread& ppu, u32 addr, vm::ptr stat) -{ - ppu.state += cpu_flag::wait; - - sys_vm.warning("sys_vm_get_statistics(addr=0x%x, stat=*0x%x)", addr, stat); - - const auto block = idm::get_unlocked(sys_vm_t::find_id(addr)); - - if (!block || block->addr != addr) - { - return CELL_EINVAL; - } - - ppu.check_state(); - stat->page_fault_ppu = 0; - stat->page_fault_spu = 0; - stat->page_in = 0; - stat->page_out = 0; - stat->pmem_total = block->psize; - stat->pmem_used = 0; - stat->timestamp = get_timebased_time(); - - return CELL_OK; -} - -DECLARE(sys_vm_t::g_ids){}; diff --git a/rpcs3/Emu/Cell/lv2/sys_vm.h b/rpcs3/Emu/Cell/lv2/sys_vm.h deleted file mode 100644 index 9493eb967..000000000 --- a/rpcs3/Emu/Cell/lv2/sys_vm.h +++ /dev/null @@ -1,74 +0,0 @@ -#pragma once - -#include "Emu/Memory/vm_ptr.h" -#include "sys_memory.h" - -#include - -enum : u64 -{ - SYS_VM_STATE_INVALID = 0ull, - SYS_VM_STATE_UNUSED = 1ull, - SYS_VM_STATE_ON_MEMORY = 2ull, - SYS_VM_STATE_STORED = 4ull, - - SYS_VM_POLICY_AUTO_RECOMMENDED = 1ull, -}; - -struct sys_vm_statistics_t -{ - be_t page_fault_ppu; // Number of bad virtual memory accesses from a PPU thread. - be_t page_fault_spu; // Number of bad virtual memory accesses from a SPU thread. - be_t page_in; // Number of virtual memory backup reading operations. - be_t page_out; // Number of virtual memory backup writing operations. - be_t pmem_total; // Total physical memory allocated for the virtual memory area. - be_t pmem_used; // Physical memory in use by the virtual memory area. - be_t timestamp; -}; - -// Block info -struct sys_vm_t -{ - static const u32 id_base = 0x1; - static const u32 id_step = 0x1; - static const u32 id_count = 16; - - lv2_memory_container* const ct; - const u32 addr; - const u32 size; - atomic_t psize; - - sys_vm_t(u32 addr, u32 vsize, lv2_memory_container* ct, u32 psize); - ~sys_vm_t(); - - SAVESTATE_INIT_POS(10); - - sys_vm_t(utils::serial& ar); - void save(utils::serial& ar); - - static std::array, id_count> g_ids; - - static u32 find_id(u32 addr) - { - return g_ids[addr >> 28].load(); - } -}; - -// Aux -class ppu_thread; - -// SysCalls -error_code sys_vm_memory_map(ppu_thread& ppu, u64 vsize, u64 psize, u32 cid, u64 flag, u64 policy, vm::ptr addr); -error_code sys_vm_memory_map_different(ppu_thread& ppu, u64 vsize, u64 psize, u32 cid, u64 flag, u64 policy, vm::ptr addr); -error_code sys_vm_unmap(ppu_thread& ppu, u32 addr); -error_code sys_vm_append_memory(ppu_thread& ppu, u32 addr, u64 size); -error_code sys_vm_return_memory(ppu_thread& ppu, u32 addr, u64 size); -error_code sys_vm_lock(ppu_thread& ppu, u32 addr, u32 size); -error_code sys_vm_unlock(ppu_thread& ppu, u32 addr, u32 size); -error_code sys_vm_touch(ppu_thread& ppu, u32 addr, u32 size); -error_code sys_vm_flush(ppu_thread& ppu, u32 addr, u32 size); -error_code sys_vm_invalidate(ppu_thread& ppu, u32 addr, u32 size); -error_code sys_vm_store(ppu_thread& ppu, u32 addr, u32 size); -error_code sys_vm_sync(ppu_thread& ppu, u32 addr, u32 size); -error_code sys_vm_test(ppu_thread& ppu, u32 addr, u32 size, vm::ptr result); -error_code sys_vm_get_statistics(ppu_thread& ppu, u32 addr, vm::ptr stat); diff --git a/rpcs3/Emu/Io/Buzz.cpp b/rpcs3/Emu/Io/Buzz.cpp index 2d7549574..f6ee9207d 100644 --- a/rpcs3/Emu/Io/Buzz.cpp +++ b/rpcs3/Emu/Io/Buzz.cpp @@ -2,7 +2,7 @@ #include "stdafx.h" #include "Buzz.h" -#include "Emu/Cell/lv2/sys_usbd.h" +#include "cellos/sys_usbd.h" #include "Emu/Io/buzz_config.h" #include "Emu/system_config.h" #include "Input/pad_thread.h" diff --git a/rpcs3/Emu/Io/Dimensions.cpp b/rpcs3/Emu/Io/Dimensions.cpp index 158f310bd..1298361a4 100644 --- a/rpcs3/Emu/Io/Dimensions.cpp +++ b/rpcs3/Emu/Io/Dimensions.cpp @@ -3,7 +3,7 @@ #include -#include "Emu/Cell/lv2/sys_usbd.h" +#include "cellos/sys_usbd.h" LOG_CHANNEL(dimensions_log, "dimensions"); diff --git a/rpcs3/Emu/Io/GHLtar.cpp b/rpcs3/Emu/Io/GHLtar.cpp index c01a70f3b..a78da9ba3 100644 --- a/rpcs3/Emu/Io/GHLtar.cpp +++ b/rpcs3/Emu/Io/GHLtar.cpp @@ -2,7 +2,7 @@ #include "stdafx.h" #include "GHLtar.h" -#include "Emu/Cell/lv2/sys_usbd.h" +#include "cellos/sys_usbd.h" #include "Emu/Io/ghltar_config.h" #include "Emu/system_config.h" #include "Input/pad_thread.h" diff --git a/rpcs3/Emu/Io/GameTablet.cpp b/rpcs3/Emu/Io/GameTablet.cpp index f17a0980e..c0b42390c 100644 --- a/rpcs3/Emu/Io/GameTablet.cpp +++ b/rpcs3/Emu/Io/GameTablet.cpp @@ -2,7 +2,7 @@ #include "GameTablet.h" #include "MouseHandler.h" #include "Emu/IdManager.h" -#include "Emu/Cell/lv2/sys_usbd.h" +#include "cellos/sys_usbd.h" #include "Emu/system_config.h" #include "Input/pad_thread.h" diff --git a/rpcs3/Emu/Io/GunCon3.cpp b/rpcs3/Emu/Io/GunCon3.cpp index 5153f6f51..63afad25d 100644 --- a/rpcs3/Emu/Io/GunCon3.cpp +++ b/rpcs3/Emu/Io/GunCon3.cpp @@ -3,7 +3,7 @@ #include "MouseHandler.h" #include "Emu/IdManager.h" #include "Emu/Io/guncon3_config.h" -#include "Emu/Cell/lv2/sys_usbd.h" +#include "cellos/sys_usbd.h" #include "Emu/system_config.h" #include "Input/pad_thread.h" diff --git a/rpcs3/Emu/Io/RB3MidiDrums.cpp b/rpcs3/Emu/Io/RB3MidiDrums.cpp index d21efe528..a7e5bf420 100644 --- a/rpcs3/Emu/Io/RB3MidiDrums.cpp +++ b/rpcs3/Emu/Io/RB3MidiDrums.cpp @@ -4,7 +4,7 @@ #ifndef WITHOUT_RTMIDI #include "RB3MidiDrums.h" -#include "Emu/Cell/lv2/sys_usbd.h" +#include "cellos/sys_usbd.h" #include "Emu/Io/rb3drums_config.h" using namespace std::chrono_literals; diff --git a/rpcs3/Emu/Io/RB3MidiGuitar.cpp b/rpcs3/Emu/Io/RB3MidiGuitar.cpp index c98701c75..129596945 100644 --- a/rpcs3/Emu/Io/RB3MidiGuitar.cpp +++ b/rpcs3/Emu/Io/RB3MidiGuitar.cpp @@ -5,7 +5,7 @@ #ifndef WITHOUT_RTMIDI #include "RB3MidiGuitar.h" -#include "Emu/Cell/lv2/sys_usbd.h" +#include "cellos/sys_usbd.h" LOG_CHANNEL(rb3_midi_guitar_log); diff --git a/rpcs3/Emu/Io/RB3MidiKeyboard.cpp b/rpcs3/Emu/Io/RB3MidiKeyboard.cpp index 0a1bfcf6b..729f6e347 100644 --- a/rpcs3/Emu/Io/RB3MidiKeyboard.cpp +++ b/rpcs3/Emu/Io/RB3MidiKeyboard.cpp @@ -3,7 +3,7 @@ #ifndef WITHOUT_RTMIDI #include "RB3MidiKeyboard.h" -#include "Emu/Cell/lv2/sys_usbd.h" +#include "cellos/sys_usbd.h" LOG_CHANNEL(rb3_midi_keyboard_log); diff --git a/rpcs3/Emu/Io/Skylander.cpp b/rpcs3/Emu/Io/Skylander.cpp index ff9d9bf3e..e3b8089fd 100644 --- a/rpcs3/Emu/Io/Skylander.cpp +++ b/rpcs3/Emu/Io/Skylander.cpp @@ -1,6 +1,6 @@ #include "stdafx.h" #include "Skylander.h" -#include "Emu/Cell/lv2/sys_usbd.h" +#include "cellos/sys_usbd.h" LOG_CHANNEL(skylander_log, "skylander"); diff --git a/rpcs3/Emu/Io/TopShotElite.cpp b/rpcs3/Emu/Io/TopShotElite.cpp index fa87a4774..84bcbf21f 100644 --- a/rpcs3/Emu/Io/TopShotElite.cpp +++ b/rpcs3/Emu/Io/TopShotElite.cpp @@ -3,7 +3,7 @@ #include "MouseHandler.h" #include "Emu/IdManager.h" #include "Emu/Io/topshotelite_config.h" -#include "Emu/Cell/lv2/sys_usbd.h" +#include "cellos/sys_usbd.h" #include "Emu/system_config.h" #include "Input/pad_thread.h" diff --git a/rpcs3/Emu/Io/TopShotFearmaster.cpp b/rpcs3/Emu/Io/TopShotFearmaster.cpp index d2c3e0962..393d4e8d4 100644 --- a/rpcs3/Emu/Io/TopShotFearmaster.cpp +++ b/rpcs3/Emu/Io/TopShotFearmaster.cpp @@ -3,7 +3,7 @@ #include "MouseHandler.h" #include "Emu/IdManager.h" #include "Emu/Io/topshotfearmaster_config.h" -#include "Emu/Cell/lv2/sys_usbd.h" +#include "cellos/sys_usbd.h" #include "Emu/system_config.h" #include "Input/pad_thread.h" diff --git a/rpcs3/Emu/Io/Turntable.cpp b/rpcs3/Emu/Io/Turntable.cpp index b85666ea2..83379c46e 100644 --- a/rpcs3/Emu/Io/Turntable.cpp +++ b/rpcs3/Emu/Io/Turntable.cpp @@ -2,7 +2,7 @@ #include "stdafx.h" #include "Turntable.h" -#include "Emu/Cell/lv2/sys_usbd.h" +#include "cellos/sys_usbd.h" #include "Emu/Io/turntable_config.h" #include "Input/pad_thread.h" #include "Emu/system_config.h" diff --git a/rpcs3/Emu/Io/usb_device.cpp b/rpcs3/Emu/Io/usb_device.cpp index 42c517473..799c20272 100644 --- a/rpcs3/Emu/Io/usb_device.cpp +++ b/rpcs3/Emu/Io/usb_device.cpp @@ -1,7 +1,7 @@ #include "stdafx.h" #include "Emu/System.h" #include "Emu/Cell/timers.hpp" -#include "Emu/Cell/lv2/sys_usbd.h" +#include "cellos/sys_usbd.h" #include "Emu/Io/usb_device.h" #include "util/StrUtil.h" #include diff --git a/rpcs3/Emu/Io/usb_device.h b/rpcs3/Emu/Io/usb_device.h index d8884ffc8..ab2391464 100644 --- a/rpcs3/Emu/Io/usb_device.h +++ b/rpcs3/Emu/Io/usb_device.h @@ -15,7 +15,7 @@ #pragma GCC diagnostic pop #endif -#include "Emu/Cell/lv2/sys_usbd.h" +#include "cellos/sys_usbd.h" struct UsbTransfer; diff --git a/rpcs3/Emu/NP/fb_helpers.cpp b/rpcs3/Emu/NP/fb_helpers.cpp index 4f2abef99..2db91ec29 100644 --- a/rpcs3/Emu/NP/fb_helpers.cpp +++ b/rpcs3/Emu/NP/fb_helpers.cpp @@ -1,5 +1,5 @@ #include "stdafx.h" -#include "Emu/Cell/lv2/sys_process.h" +#include "cellos/sys_process.h" #include "fb_helpers.h" LOG_CHANNEL(rpcn_log, "rpcn"); diff --git a/rpcs3/Emu/NP/np_handler.cpp b/rpcs3/Emu/NP/np_handler.cpp index d7a34933c..867421784 100644 --- a/rpcs3/Emu/NP/np_handler.cpp +++ b/rpcs3/Emu/NP/np_handler.cpp @@ -14,8 +14,8 @@ #include "Emu/NP/np_helpers.h" #include "Emu/NP/signaling_handler.h" #include "Emu/RSX/Overlays/overlay_message.h" -#include "Emu/Cell/lv2/sys_net/network_context.h" -#include "Emu/Cell/lv2/sys_net/sys_net_helpers.h" +#include "cellos/sys_net/network_context.h" +#include "cellos/sys_net/sys_net_helpers.h" #ifdef _WIN32 #include diff --git a/rpcs3/Emu/NP/np_requests.cpp b/rpcs3/Emu/NP/np_requests.cpp index 3aa1a607f..43d82332f 100644 --- a/rpcs3/Emu/NP/np_requests.cpp +++ b/rpcs3/Emu/NP/np_requests.cpp @@ -4,7 +4,7 @@ #include "util/StrFmt.h" #include "stdafx.h" #include "Emu/Cell/PPUCallback.h" -#include "Emu/Cell/lv2/sys_sync.h" +#include "cellos/sys_sync.h" #include "Emu/system_config.h" #include "rpcsx/fw/ps3/cellSysutil.h" #include "Emu/Memory/vm_ptr.h" diff --git a/rpcs3/Emu/NP/rpcn_client.cpp b/rpcs3/Emu/NP/rpcn_client.cpp index 7f1359876..c2b367b92 100644 --- a/rpcs3/Emu/NP/rpcn_client.cpp +++ b/rpcs3/Emu/NP/rpcn_client.cpp @@ -1,5 +1,5 @@ #include "stdafx.h" -#include "Emu/Cell/lv2/sys_net/sys_net_helpers.h" +#include "cellos/sys_net/sys_net_helpers.h" #include "Emu/NP/ip_address.h" #include #include diff --git a/rpcs3/Emu/NP/vport0.h b/rpcs3/Emu/NP/vport0.h index c34cf8545..d1546e9f1 100644 --- a/rpcs3/Emu/NP/vport0.h +++ b/rpcs3/Emu/NP/vport0.h @@ -8,7 +8,7 @@ #include #endif -#include "Emu/Cell/lv2/sys_net/nt_p2p_port.h" +#include "cellos/sys_net/nt_p2p_port.h" bool send_packet_from_p2p_port_ipv4(const std::vector& data, const sockaddr_in& addr); bool send_packet_from_p2p_port_ipv6(const std::vector& data, const sockaddr_in6& addr); diff --git a/rpcs3/Emu/RSX/Capture/rsx_replay.cpp b/rpcs3/Emu/RSX/Capture/rsx_replay.cpp index 2c30b035f..96bc2b230 100644 --- a/rpcs3/Emu/RSX/Capture/rsx_replay.cpp +++ b/rpcs3/Emu/RSX/Capture/rsx_replay.cpp @@ -3,8 +3,8 @@ #include "Emu/System.h" #include "Emu/Cell/ErrorCodes.h" -#include "Emu/Cell/lv2/sys_rsx.h" -#include "Emu/Cell/lv2/sys_memory.h" +#include "cellos/sys_rsx.h" +#include "cellos/sys_memory.h" #include "Emu/RSX/RSXThread.h" #include "util/asm.hpp" diff --git a/rpcs3/Emu/RSX/Core/RSXContext.h b/rpcs3/Emu/RSX/Core/RSXContext.h index 7d611b8a6..9d385c5a1 100644 --- a/rpcs3/Emu/RSX/Core/RSXContext.h +++ b/rpcs3/Emu/RSX/Core/RSXContext.h @@ -1,7 +1,7 @@ #pragma once #include -#include "Emu/Cell/lv2/sys_rsx.h" +#include "cellos/sys_rsx.h" #include "Emu/RSX/GCM.h" #include "Emu/RSX/rsx_utils.h" #include "RSXIOMap.hpp" diff --git a/rpcs3/Emu/RSX/RSXFIFO.cpp b/rpcs3/Emu/RSX/RSXFIFO.cpp index 78843dff0..9b4c81146 100644 --- a/rpcs3/Emu/RSX/RSXFIFO.cpp +++ b/rpcs3/Emu/RSX/RSXFIFO.cpp @@ -6,7 +6,7 @@ #include "Capture/rsx_capture.h" #include "Core/RSXReservationLock.hpp" #include "Emu/Memory/vm_reservation.h" -#include "Emu/Cell/lv2/sys_rsx.h" +#include "cellos/sys_rsx.h" #include "NV47/HW/context.h" #include "util/asm.hpp" diff --git a/rpcs3/Emu/RSX/RSXThread.cpp b/rpcs3/Emu/RSX/RSXThread.cpp index 8c494fbd8..653a9e5cf 100644 --- a/rpcs3/Emu/RSX/RSXThread.cpp +++ b/rpcs3/Emu/RSX/RSXThread.cpp @@ -17,8 +17,8 @@ #include "Emu/System.h" #include "Emu/Cell/PPUThread.h" #include "Emu/Cell/timers.hpp" -#include "Emu/Cell/lv2/sys_event.h" -#include "Emu/Cell/lv2/sys_time.h" +#include "cellos/sys_event.h" +#include "cellos/sys_time.h" #include "rpcsx/fw/ps3/cellGcmSys.h" #include "util/serialization_ext.hpp" #include "Overlays/overlay_perf_metrics.h" diff --git a/rpcs3/Emu/RSX/RSXThread.h b/rpcs3/Emu/RSX/RSXThread.h index 359d55c76..6284a3694 100644 --- a/rpcs3/Emu/RSX/RSXThread.h +++ b/rpcs3/Emu/RSX/RSXThread.h @@ -19,7 +19,7 @@ #include "Capture/rsx_trace.h" #include "Capture/rsx_replay.h" -#include "Emu/Cell/lv2/sys_rsx.h" +#include "cellos/sys_rsx.h" #include "Emu/IdManager.h" #include "Core/RSXDisplay.h" diff --git a/rpcs3/Emu/RSX/rsx_methods.cpp b/rpcs3/Emu/RSX/rsx_methods.cpp index a8e732573..4ea28b7f2 100644 --- a/rpcs3/Emu/RSX/rsx_methods.cpp +++ b/rpcs3/Emu/RSX/rsx_methods.cpp @@ -2,7 +2,7 @@ #include "rsx_methods.h" #include "RSXThread.h" #include "Emu/Cell/PPUThread.h" -#include "Emu/Cell/lv2/sys_rsx.h" +#include "cellos/sys_rsx.h" #include "Emu/System.h" #include "Emu/RSX/NV47/HW/nv47.h" diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index 19d1b8ca8..a7864ffb0 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -26,11 +26,11 @@ #include "Emu/Cell/SPUThread.h" #include "Emu/Cell/SPURecompiler.h" #include "Emu/RSX/RSXThread.h" -#include "Emu/Cell/lv2/sys_process.h" -#include "Emu/Cell/lv2/sys_sync.h" -#include "Emu/Cell/lv2/sys_prx.h" -#include "Emu/Cell/lv2/sys_overlay.h" -#include "Emu/Cell/lv2/sys_spu.h" +#include "cellos/sys_process.h" +#include "cellos/sys_sync.h" +#include "cellos/sys_prx.h" +#include "cellos/sys_overlay.h" +#include "cellos/sys_spu.h" #include "rpcsx/fw/ps3/cellGame.h" #include "rpcsx/fw/ps3/cellSysutil.h" diff --git a/rpcs3/Emu/VFS.cpp b/rpcs3/Emu/VFS.cpp index 425e26d6d..0f5e6a14c 100644 --- a/rpcs3/Emu/VFS.cpp +++ b/rpcs3/Emu/VFS.cpp @@ -3,7 +3,7 @@ #include "System.h" #include "VFS.h" -#include "Cell/lv2/sys_fs.h" +#include "cellos/sys_fs.h" #include "util/mutex.h" #include "util/StrUtil.h" diff --git a/rpcs3/Emu/cache_utils.cpp b/rpcs3/Emu/cache_utils.cpp index bfdf59165..84bdf8a54 100644 --- a/rpcs3/Emu/cache_utils.cpp +++ b/rpcs3/Emu/cache_utils.cpp @@ -5,7 +5,7 @@ #include "system_utils.hpp" #include "system_config.h" #include "IdManager.h" -#include "Emu/Cell/lv2/sys_sync.h" +#include "cellos/sys_sync.h" #include "Emu/Cell/PPUAnalyser.h" #include "Emu/Cell/PPUThread.h" diff --git a/rpcs3/Input/pad_thread.cpp b/rpcs3/Input/pad_thread.cpp index fb9a43462..fc474fe5c 100644 --- a/rpcs3/Input/pad_thread.cpp +++ b/rpcs3/Input/pad_thread.cpp @@ -24,7 +24,7 @@ #include "Emu/system_config.h" #include "Emu/RSX/Overlays/HomeMenu/overlay_home_menu.h" #include "Emu/RSX/Overlays/overlay_message.h" -#include "Emu/Cell/lv2/sys_usbd.h" +#include "cellos/sys_usbd.h" #include "rpcsx/fw/ps3/cellGem.h" #include "Emu/Cell/timers.hpp" #include "util/Thread.h" diff --git a/rpcs3/stdafx.cpp b/rpcs3/stdafx.cpp index fb31308ed..1c5e94268 100644 --- a/rpcs3/stdafx.cpp +++ b/rpcs3/stdafx.cpp @@ -1,4 +1,4 @@ -#include "stdafx.h" // No BOM and only basic ASCII in this file, or a neko will die +#include "stdafx.h" static_assert(std::endian::native == std::endian::little || std::endian::native == std::endian::big); diff --git a/rpcs3/stdafx.h b/rpcs3/stdafx.h index 882f389a4..2b58efbc0 100644 --- a/rpcs3/stdafx.h +++ b/rpcs3/stdafx.h @@ -1,4 +1,4 @@ -#pragma once // No BOM and only basic ASCII in this header, or a neko will die +#pragma once #include "util/types.hpp" #include "util/atomic.hpp" diff --git a/rpcs3/util/Thread.cpp b/rpcs3/util/Thread.cpp index a222fb9d9..343bff14f 100644 --- a/rpcs3/util/Thread.cpp +++ b/rpcs3/util/Thread.cpp @@ -3,9 +3,9 @@ #include "Emu/System.h" #include "Emu/Cell/SPUThread.h" #include "Emu/Cell/PPUThread.h" -#include "Emu/Cell/lv2/sys_mmapper.h" -#include "Emu/Cell/lv2/sys_event.h" -#include "Emu/Cell/lv2/sys_process.h" +#include "cellos/sys_mmapper.h" +#include "cellos/sys_event.h" +#include "cellos/sys_process.h" #include "Thread.h" #include "util/JIT.h" #include diff --git a/rpcs3/util/atomic.cpp b/rpcs3/util/atomic.cpp index 631859f09..d12c8b8f7 100644 --- a/rpcs3/util/atomic.cpp +++ b/rpcs3/util/atomic.cpp @@ -1025,7 +1025,7 @@ atomic_wait_engine::wait(const void* data, u32 old_value, u64 timeout, atomic_wa // Can skip unqueue process if true #if defined(USE_FUTEX) || defined(USE_STD) - constexpr bool fallback = true; + [[maybe_unused]] constexpr bool fallback = true; #else bool fallback = false; #endif