mirror of
https://github.com/RPCSX/rpcsx.git
synced 2026-04-04 14:08:37 +00:00
Move rpcs3/Emu/Cell/lv2 to kernel/cellos
This commit is contained in:
parent
fce4127c2e
commit
dbfa5002e5
282 changed files with 40062 additions and 41342 deletions
|
|
@ -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 "$<IF:$<CXX_COMPILER_ID:MSVC>,/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}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
|
|
@ -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 <cstddef>
|
||||
#include <rx/format.hpp>
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -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<void> 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;
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "Emu/Memory/vm_ptr.h"
|
||||
#include "Emu/Cell/ErrorCodes.h"
|
||||
|
||||
// SysCalls
|
||||
|
||||
error_code sys_bdemu_send_command(u64 cmd, u64 a2, u64 a3, vm::ptr<void> buf, u64 buf_len);
|
||||
|
|
@ -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<void> msg)
|
||||
{
|
||||
sys_btsetting.todo("sys_btsetting_if(cmd=0x%llx, msg=*0x%x)", cmd, msg);
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "Emu/Memory/vm_ptr.h"
|
||||
#include "Emu/Cell/ErrorCodes.h"
|
||||
|
||||
// SysCalls
|
||||
|
||||
error_code sys_btsetting_if(u64 cmd, vm::ptr<void> msg);
|
||||
|
|
@ -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<lv2_obj, lv2_mutex>(mtx_id)), _mutex(idm::get_unlocked<lv2_obj, lv2_mutex>(mtx_id)) // May be nullptr
|
||||
{
|
||||
}
|
||||
|
||||
lv2_cond::lv2_cond(u64 key, u64 name, u32 mtx_id, shared_ptr<lv2_obj> mutex0) noexcept
|
||||
: key(key), name(name), mtx_id(mtx_id), mutex(static_cast<lv2_mutex*>(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<shared_ptr<lv2_obj>>(ensure(idm::get_unlocked<lv2_obj, lv2_mutex>(mtx_id)));
|
||||
}
|
||||
|
||||
// Defer function
|
||||
ensure(CellError{} == do_it(this));
|
||||
});
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::function<void(void*)> lv2_cond::load(utils::serial& ar)
|
||||
{
|
||||
return load_func(make_shared<lv2_cond>(exact_t<utils::serial&>(ar)));
|
||||
}
|
||||
|
||||
void lv2_cond::save(utils::serial& ar)
|
||||
{
|
||||
ar(key, name, mtx_id);
|
||||
}
|
||||
|
||||
error_code sys_cond_create(ppu_thread& ppu, vm::ptr<u32> cond_id, u32 mutex_id, vm::ptr<sys_cond_attribute_t> 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<lv2_obj, lv2_mutex>(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<lv2_cond>(_attr.pshared, ipc_key, _attr.flags, [&]
|
||||
{
|
||||
return make_single<lv2_cond>(
|
||||
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<lv2_obj, lv2_cond>(cond_id, [&](lv2_cond& cond) -> CellError
|
||||
{
|
||||
std::lock_guard lock(cond.mutex->mutex);
|
||||
|
||||
if (atomic_storage<ppu_thread*>::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<lv2_obj, lv2_cond>(cond_id, [&, notify = lv2_obj::notify_all_t()](lv2_cond& cond)
|
||||
{
|
||||
if (atomic_storage<ppu_thread*>::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<ppu_thread>(cond.sq, cond.mutex->protocol))
|
||||
{
|
||||
if (static_cast<ppu_thread*>(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<lv2_obj, lv2_cond>(cond_id, [&, notify = lv2_obj::notify_all_t()](lv2_cond& cond)
|
||||
{
|
||||
if (atomic_storage<ppu_thread*>::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<ppu_thread*>::release(cond.sq, nullptr);
|
||||
|
||||
while (const auto cpu = cond.schedule<ppu_thread>(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<lv2_obj, lv2_cond>(cond_id, [&, notify = lv2_obj::notify_all_t()](lv2_cond& cond)
|
||||
{
|
||||
if (!idm::check_unlocked<named_thread<ppu_thread>>(thread_id))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (atomic_storage<ppu_thread*>::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<ppu_thread*>(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<lv2_obj, lv2_cond>(cond_id, [&, notify = lv2_obj::notify_all_t()](lv2_cond& cond) -> s64
|
||||
{
|
||||
if (!ppu.loaded_from_savestate && atomic_storage<u32>::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<u64>().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<u32>(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<ppu_thread>())
|
||||
{
|
||||
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<ppu_thread*>::load(cond->sq); cpu; cpu = cpu->next_cpu)
|
||||
{
|
||||
if (cpu == &ppu)
|
||||
{
|
||||
cond_sleep = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto cpu = atomic_storage<ppu_thread*>::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<u32>(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<u32>::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<u32>::load(cond->mutex->control.raw().owner) == ppu.id);
|
||||
|
||||
// Restore the recursive value
|
||||
cond->mutex->lock_count.release(static_cast<u32>(cond.ret));
|
||||
|
||||
return not_an_error(ppu.gpr[3]);
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "sys_sync.h"
|
||||
#include "sys_mutex.h"
|
||||
|
||||
struct lv2_mutex;
|
||||
|
||||
struct sys_cond_attribute_t
|
||||
{
|
||||
be_t<u32> pshared;
|
||||
be_t<s32> flags;
|
||||
be_t<u64> ipc_key;
|
||||
|
||||
union
|
||||
{
|
||||
nse_t<u64, 1> 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<lv2_obj> _mutex;
|
||||
ppu_thread* sq{};
|
||||
|
||||
lv2_cond(u64 key, u64 name, u32 mtx_id, shared_ptr<lv2_obj> mutex0) noexcept;
|
||||
|
||||
lv2_cond(utils::serial& ar) noexcept;
|
||||
static std::function<void(void*)> 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<u32> cond_id, u32 mutex_id, vm::ptr<sys_cond_attribute_t> 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);
|
||||
|
|
@ -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<sys_config_service_id>::format(std::string& out, u64 id)
|
||||
{
|
||||
const s64 s_id = static_cast<s64>(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<sys_config_service_listener_type>::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<u8>& 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<lv2_config_service_event> event)
|
||||
{
|
||||
std::lock_guard lock(m_mutex);
|
||||
events.emplace(event->id, std::move(event));
|
||||
}
|
||||
|
||||
void lv2_config::remove_service_event(u32 id)
|
||||
{
|
||||
shared_ptr<lv2_config_service_event> 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<lv2_config>())
|
||||
{
|
||||
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<lv2_config_service_event>& event)
|
||||
{
|
||||
service_events.emplace_back(event);
|
||||
return event->notify();
|
||||
}
|
||||
|
||||
bool lv2_config_service_listener::notify(const shared_ptr<lv2_config_service>& 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<shared_ptr<lv2_config_service>> services;
|
||||
|
||||
// Grab all events
|
||||
idm::select<lv2_config_service>([&](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<lv2_config_service>& s1, const shared_ptr<lv2_config_service>& 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<lv2_config_service>(idm_id);
|
||||
}
|
||||
|
||||
void lv2_config_service::notify() const
|
||||
{
|
||||
std::vector<shared_ptr<lv2_config_service_listener>> listeners;
|
||||
|
||||
const shared_ptr<lv2_config_service> sptr = get_shared_ptr();
|
||||
|
||||
idm::select<lv2_config_service_listener>([&](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<u64>(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<u32>(size);
|
||||
memcpy(dst->data, service->data.data(), size);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Syscalls
|
||||
*/
|
||||
error_code sys_config_open(u32 equeue_hdl, vm::ptr<u32> 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<lv2_obj, lv2_event_queue>(equeue_hdl);
|
||||
if (!queue)
|
||||
{
|
||||
return CELL_ESRCH;
|
||||
}
|
||||
|
||||
// Initialize lv2_config global state
|
||||
auto& global = g_fxo->get<lv2_config>();
|
||||
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<lv2_config_handle>(config_hdl))
|
||||
{
|
||||
return CELL_ESRCH;
|
||||
}
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
|
||||
error_code sys_config_get_service_event(u32 config_hdl, u32 event_id, vm::ptr<sys_config_service_event_t> 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<lv2_config_handle>(config_hdl);
|
||||
if (!cfg)
|
||||
{
|
||||
return CELL_ESRCH;
|
||||
}
|
||||
|
||||
// Find service_event object
|
||||
const auto event = g_fxo->get<lv2_config>().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<void> in, u64 size, sys_config_service_listener_type type, vm::ptr<u32> 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<lv2_config_handle>(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<u8*>(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<lv2_config_service_listener>(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<u8> data_buf, u64 size, vm::ptr<u32> 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<lv2_config_handle>(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<lv2_config_service>(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<void> 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;
|
||||
}
|
||||
|
|
@ -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<s64>(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<u32> service_listener_handle;
|
||||
|
||||
// 1 if this service is currently registered or unregistered
|
||||
be_t<u32> registered;
|
||||
|
||||
// Service ID that triggered this event
|
||||
be_t<u64> 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<u64> user_id;
|
||||
|
||||
/* if added==0, the structure ends here */
|
||||
|
||||
// Verbosity of this service event (provided to sys_config_register_event)
|
||||
be_t<u64> verbosity;
|
||||
|
||||
// Size of 'data'
|
||||
be_t<u32> data_size;
|
||||
|
||||
// Ignored, seems to be simply 32-bits of padding
|
||||
be_t<u32> 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<u16> unk[5]; // hid device type ?
|
||||
be_t<u16> vid;
|
||||
be_t<u16> pid;
|
||||
be_t<u16> unk2[6]; // bluetooth address?
|
||||
};
|
||||
static_assert(sizeof(sys_config_padmanager_data_t) == 26);
|
||||
|
||||
/*
|
||||
* Global sys_config state
|
||||
*/
|
||||
|
||||
class lv2_config
|
||||
{
|
||||
atomic_t<u32> m_state = 0;
|
||||
|
||||
// LV2 Config mutex
|
||||
shared_mutex m_mutex;
|
||||
|
||||
// Map of LV2 Service Events
|
||||
std::unordered_map<u32, shared_ptr<lv2_config_service_event>> events;
|
||||
|
||||
public:
|
||||
void initialize();
|
||||
|
||||
// Service Events
|
||||
void add_service_event(shared_ptr<lv2_config_service_event> event);
|
||||
void remove_service_event(u32 id);
|
||||
|
||||
shared_ptr<lv2_config_service_event> 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<lv2_event_queue> 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<lv2_event_queue> _queue) noexcept
|
||||
: queue(std::move(_queue))
|
||||
{
|
||||
}
|
||||
|
||||
// Factory
|
||||
template <typename... Args>
|
||||
static shared_ptr<lv2_config_handle> create(Args&&... args)
|
||||
{
|
||||
if (auto cfg = idm::make_ptr<lv2_config_handle>(std::forward<Args>(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<u8> 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 <typename... Args>
|
||||
static shared_ptr<lv2_config_service> create(Args&&... args)
|
||||
{
|
||||
if (auto service = idm::make_ptr<lv2_config_service>(std::forward<Args>(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<lv2_config_service> get_shared_ptr() const
|
||||
{
|
||||
return stx::make_shared_from_this<lv2_config_service>(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<shared_ptr<lv2_config_service_event>> service_events;
|
||||
shared_ptr<lv2_config_handle> handle;
|
||||
|
||||
bool notify(const shared_ptr<lv2_config_service_event>& event);
|
||||
|
||||
public:
|
||||
const sys_config_service_id service_id;
|
||||
const u64 min_verbosity;
|
||||
const sys_config_service_listener_type type;
|
||||
|
||||
const std::vector<u8> data;
|
||||
|
||||
// Constructors (should not be used directly)
|
||||
lv2_config_service_listener(shared_ptr<lv2_config_handle> _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 <typename... Args>
|
||||
static shared_ptr<lv2_config_service_listener> create(Args&&... args)
|
||||
{
|
||||
if (auto listener = idm::make_ptr<lv2_config_service_listener>(std::forward<Args>(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<lv2_config_service>& service);
|
||||
|
||||
// (Re-)notify about all still-registered past events
|
||||
void notify_all();
|
||||
|
||||
// Utilities
|
||||
u32 get_id() const
|
||||
{
|
||||
return idm_id;
|
||||
}
|
||||
shared_ptr<lv2_config_service_listener> get_shared_ptr() const
|
||||
{
|
||||
return stx::make_shared_from_this<lv2_config_service_listener>(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<u32> next_id = 0;
|
||||
};
|
||||
|
||||
return g_fxo->get<service_event_id>().next_id++;
|
||||
}
|
||||
|
||||
atomic_t<bool> 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<lv2_config_handle> handle;
|
||||
const shared_ptr<lv2_config_service> service;
|
||||
const lv2_config_service_listener& listener;
|
||||
|
||||
// Constructors (should not be used directly)
|
||||
lv2_config_service_event(shared_ptr<lv2_config_handle> _handle, shared_ptr<lv2_config_service> _service, const lv2_config_service_listener& _listener) noexcept
|
||||
: id(get_next_id()), handle(std::move(_handle)), service(std::move(_service)), listener(_listener)
|
||||
{
|
||||
}
|
||||
|
||||
// Factory
|
||||
template <typename... Args>
|
||||
static shared_ptr<lv2_config_service_event> create(Args&&... args)
|
||||
{
|
||||
auto ev = make_shared<lv2_config_service_event>(std::forward<Args>(args)...);
|
||||
|
||||
g_fxo->get<lv2_config>().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<u32> 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<sys_config_service_event_t> 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<void> in, u64 size, sys_config_service_listener_type type, vm::ptr<u32> 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<u8> data_buf, u64 size, vm::ptr<u32> 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<void> 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);
|
||||
|
|
@ -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<char> buf, u32 len)
|
||||
{
|
||||
sys_console.todo("sys_console_write(buf=*0x%x, len=0x%x)", buf, len);
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "Emu/Memory/vm_ptr.h"
|
||||
|
||||
// SysCalls
|
||||
|
||||
error_code sys_console_write(vm::cptr<char> buf, u32 len);
|
||||
constexpr auto sys_console_write2 = sys_console_write;
|
||||
|
|
@ -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<u32> 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<void> 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;
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "Emu/Memory/vm_ptr.h"
|
||||
#include "Emu/Cell/ErrorCodes.h"
|
||||
|
||||
// SysCalls
|
||||
|
||||
error_code sys_crypto_engine_create(vm::ptr<u32> id);
|
||||
error_code sys_crypto_engine_destroy(u32 id);
|
||||
error_code sys_crypto_engine_random_generate(vm::ptr<void> buffer, u64 buffer_size);
|
||||
|
|
@ -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<void> 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<void> 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<const u8*>(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<u32>(utils::align<u32>(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;
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "Emu/Memory/vm_ptr.h"
|
||||
#include "Emu/Cell/ErrorCodes.h"
|
||||
|
||||
// Syscalls
|
||||
|
||||
error_code sys_dbg_read_process_memory(s32 pid, u32 address, u32 size, vm::ptr<void> data);
|
||||
error_code sys_dbg_write_process_memory(s32 pid, u32 address, u32 size, vm::cptr<void> data);
|
||||
|
|
@ -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<u8>(protocol)}, type(static_cast<u8>(type)), size(static_cast<u8>(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<void(void*)> lv2_event_queue::load(utils::serial& ar)
|
||||
{
|
||||
auto queue = make_shared<lv2_event_queue>(exact_t<utils::serial&>(ar));
|
||||
return [ptr = lv2_obj::load(queue->key, queue)](void* storage)
|
||||
{
|
||||
*static_cast<atomic_ptr<lv2_obj>*>(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> lv2_event_queue::load_ptr(utils::serial& ar, shared_ptr<lv2_event_queue>& queue, std::string_view msg)
|
||||
{
|
||||
const u32 id = ar.pop<u32>();
|
||||
|
||||
if (!id)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
if (auto q = idm::get_unlocked<lv2_obj, lv2_event_queue>(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<lv2_obj, lv2_event_queue>(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> lv2_event_queue::find(u64 ipc_key)
|
||||
{
|
||||
if (ipc_key == SYS_EVENT_QUEUE_LOCAL)
|
||||
{
|
||||
// Invalid IPC key
|
||||
return {};
|
||||
}
|
||||
|
||||
return g_fxo->get<ipc_manager<lv2_event_queue, u64>>().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<ppu_thread&>(*schedule<ppu_thread>(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<ppu_thread>())->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<spu_thread&>(*schedule<spu_thread>(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<u32>(std::get<1>(event));
|
||||
const u32 data2 = static_cast<u32>(std::get<2>(event));
|
||||
const u32 data3 = static_cast<u32>(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<u32> equeue_id, vm::ptr<sys_event_queue_attribute_t> 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<lv2_event_queue>(pshared, ipc_key, flags, [&]()
|
||||
{
|
||||
return make_shared<lv2_event_queue>(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<lv2_event> events;
|
||||
|
||||
std::unique_lock<shared_mutex> qlock;
|
||||
|
||||
cpu_thread* head{};
|
||||
|
||||
const auto queue = idm::withdraw<lv2_obj, lv2_event_queue>(equeue_id, [&](lv2_event_queue& queue) -> CellError
|
||||
{
|
||||
qlock = std::unique_lock{queue.mutex};
|
||||
|
||||
head = queue.type == SYS_PPU_QUEUE ? static_cast<cpu_thread*>(+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<ppu_thread*>::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<spu_thread*>::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<sys_event_t> event_array, s32 size, vm::ptr<u32> 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<lv2_obj, lv2_event_queue>(equeue_id);
|
||||
|
||||
if (!queue)
|
||||
{
|
||||
return CELL_ESRCH;
|
||||
}
|
||||
|
||||
if (queue->type != SYS_PPU_QUEUE)
|
||||
{
|
||||
return CELL_EINVAL;
|
||||
}
|
||||
|
||||
std::array<sys_event_t, 127> 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<sys_event_t> 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<lv2_obj, lv2_event_queue>(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<ppu_thread*>::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<lv2_obj, lv2_event_queue>(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<u32> 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<lv2_obj, lv2_event_port>(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<lv2_obj, lv2_event_port>(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<lv2_obj, lv2_event_port>(eport_id);
|
||||
|
||||
if (!port || !idm::check_unlocked<lv2_obj, lv2_event_queue>(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<lv2_obj, lv2_event_queue>(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<lv2_obj, lv2_event_port>(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<lv2_obj, lv2_event_port>(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<ppu_thread>() : 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<lv2_obj, lv2_event_port>(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;
|
||||
}
|
||||
|
|
@ -1,153 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "sys_sync.h"
|
||||
|
||||
#include "Emu/Memory/vm_ptr.h"
|
||||
|
||||
#include <deque>
|
||||
|
||||
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<u32> protocol; // SYS_SYNC_PRIORITY or SYS_SYNC_FIFO
|
||||
be_t<s32> type; // SYS_PPU_QUEUE or SYS_SPU_QUEUE
|
||||
|
||||
union
|
||||
{
|
||||
nse_t<u64, 1> name_u64;
|
||||
char name[sizeof(u64)];
|
||||
};
|
||||
};
|
||||
|
||||
struct sys_event_t
|
||||
{
|
||||
be_t<u64> source;
|
||||
be_t<u64> data1;
|
||||
be_t<u64> data2;
|
||||
be_t<u64> data3;
|
||||
};
|
||||
|
||||
// Source, data1, data2, data3
|
||||
using lv2_event = std::tuple<u64, u64, u64, u64>;
|
||||
|
||||
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<lv2_event> 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<void(void*)> load(utils::serial& ar);
|
||||
void save(utils::serial& ar);
|
||||
static void save_ptr(utils::serial&, lv2_event_queue*);
|
||||
static shared_ptr<lv2_event_queue> load_ptr(utils::serial& ar, shared_ptr<lv2_event_queue>& 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<lv2_event_queue> 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<usz> is_busy = 0; // Counts threads waiting on event sending
|
||||
shared_ptr<lv2_event_queue> 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<u32> equeue_id, vm::ptr<sys_event_queue_attribute_t> 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<sys_event_t> dummy_event, u64 timeout);
|
||||
error_code sys_event_queue_tryreceive(ppu_thread& ppu, u32 equeue_id, vm::ptr<sys_event_t> event_array, s32 size, vm::ptr<u32> 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<u32> 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);
|
||||
|
|
@ -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<void(void*)> lv2_event_flag::load(utils::serial& ar)
|
||||
{
|
||||
return load_func(make_shared<lv2_event_flag>(exact_t<utils::serial&>(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<u32> id, vm::ptr<sys_event_flag_attribute_t> 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<lv2_event_flag>(_attr.pshared, ipc_key, _attr.flags, [&]
|
||||
{
|
||||
return make_shared<lv2_event_flag>(
|
||||
_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<lv2_obj, lv2_event_flag>(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<u64> 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<u64> 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<lv2_obj, lv2_event_flag>(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<ppu_thread*>::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<u64> 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<u64> 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<lv2_obj, lv2_event_flag>(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<lv2_obj, lv2_event_flag>(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<ppu_thread*>::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<lv2_obj, lv2_event_flag>(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<u32> 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<lv2_obj, lv2_event_flag>(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<ppu_thread>(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<void>(ppu.test_stopped());
|
||||
|
||||
if (num)
|
||||
*num = value;
|
||||
return CELL_OK;
|
||||
}
|
||||
|
||||
error_code sys_event_flag_get(ppu_thread& ppu, u32 id, vm::ptr<u64> 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<lv2_obj, lv2_event_flag>(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;
|
||||
}
|
||||
|
|
@ -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<u32> protocol;
|
||||
be_t<u32> pshared;
|
||||
be_t<u64> ipc_key;
|
||||
be_t<s32> flags;
|
||||
be_t<s32> type;
|
||||
|
||||
union
|
||||
{
|
||||
nse_t<u64, 1> 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<u64> pattern;
|
||||
ppu_thread* sq{};
|
||||
|
||||
lv2_event_flag(u32 protocol, u64 key, s32 type, u64 name, u64 pattern) noexcept
|
||||
: protocol{static_cast<u8>(protocol)}, key(key), type(type), name(name), pattern(pattern)
|
||||
{
|
||||
}
|
||||
|
||||
lv2_event_flag(utils::serial& ar);
|
||||
static std::function<void(void*)> 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<u32> id, vm::ptr<sys_event_flag_attribute_t> 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<u64> result, u64 timeout);
|
||||
error_code sys_event_flag_trywait(ppu_thread& ppu, u32 id, u64 bitptn, u32 mode, vm::ptr<u64> 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<u32> num);
|
||||
error_code sys_event_flag_get(ppu_thread& ppu, u32 id, vm::ptr<u64> flags);
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -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 <string>
|
||||
|
||||
// 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<s32> mode;
|
||||
be_t<s32> uid;
|
||||
be_t<s32> gid;
|
||||
be_t<s64, 4> atime;
|
||||
be_t<s64, 4> mtime;
|
||||
be_t<s64, 4> ctime;
|
||||
be_t<u64, 4> size;
|
||||
be_t<u64, 4> blksize;
|
||||
};
|
||||
|
||||
CHECK_SIZE_ALIGN(CellFsStat, 52, 4);
|
||||
|
||||
struct CellFsDirectoryEntry
|
||||
{
|
||||
CellFsStat attribute;
|
||||
CellFsDirent entry_name;
|
||||
};
|
||||
|
||||
struct CellFsUtimbuf
|
||||
{
|
||||
be_t<s64, 4> actime;
|
||||
be_t<s64, 4> modtime;
|
||||
};
|
||||
|
||||
CHECK_SIZE_ALIGN(CellFsUtimbuf, 16, 4);
|
||||
|
||||
// MSelf file structs
|
||||
struct FsMselfHeader
|
||||
{
|
||||
be_t<u32> m_magic;
|
||||
be_t<u32> m_format_version;
|
||||
be_t<u64> m_file_size;
|
||||
be_t<u32> m_entry_num;
|
||||
be_t<u32> m_entry_size;
|
||||
u8 m_reserve[40];
|
||||
};
|
||||
|
||||
struct FsMselfEntry
|
||||
{
|
||||
char m_name[32];
|
||||
be_t<u64> m_offset;
|
||||
be_t<u64> 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<lv2_mp_flag> 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 <typename... Args>
|
||||
bool add(Args&&... args)
|
||||
{
|
||||
return map.try_emplace(std::forward<Args>(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<std::string, lv2_fs_mount_info, fmt::string_hash, std::equal_to<>> 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<char, 0x420> 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<lv2_fs_mount_info_map>().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<char, 0x420> get_name(std::string_view filename)
|
||||
{
|
||||
std::array<char, 0x420> 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<u32> 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<void> buf, u64 size, u64 opt_pos = umax);
|
||||
|
||||
u64 op_read(vm::ptr<void> 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<void> buf, u64 size);
|
||||
|
||||
u64 op_write(vm::cptr<void> 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<lv2_file>& _file, u64 offset);
|
||||
};
|
||||
|
||||
struct lv2_dir final : lv2_fs_object
|
||||
{
|
||||
static constexpr u32 id_type = 2;
|
||||
|
||||
const std::vector<fs::dir_entry> entries;
|
||||
|
||||
// Current reading position
|
||||
atomic_t<u64> pos{0};
|
||||
|
||||
lv2_dir(std::string_view filename, std::vector<fs::dir_entry>&& 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<void>(vm::ptrb<lv2_file_op>)> get_data;
|
||||
vm::bptrb<u32(vm::ptrb<lv2_file_op>)> get_size;
|
||||
vm::bptrb<void(vm::ptrb<lv2_file_op>)> _dtor1;
|
||||
vm::bptrb<void(vm::ptrb<lv2_file_op>)> _dtor2;
|
||||
};
|
||||
} // namespace vtable
|
||||
|
||||
// sys_fs_fcntl: read with offset, write with offset
|
||||
struct lv2_file_op_rw : lv2_file_op
|
||||
{
|
||||
vm::bptrb<vtable::lv2_file_op> _vtable;
|
||||
|
||||
be_t<u32> op;
|
||||
be_t<u32> _x8; // ???
|
||||
be_t<u32> _xc; // ???
|
||||
|
||||
be_t<u32> fd; // File descriptor (3..255)
|
||||
vm::bptrb<void> buf; // Buffer for data
|
||||
be_t<u64> offset; // File offset
|
||||
be_t<u64> size; // Access size
|
||||
|
||||
be_t<s32> out_code; // Op result
|
||||
be_t<u64> 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::lv2_file_op> _vtable;
|
||||
|
||||
be_t<u32> op;
|
||||
be_t<u32> _x8;
|
||||
be_t<u32> _xc;
|
||||
|
||||
be_t<u32> fd;
|
||||
be_t<u64> offset;
|
||||
be_t<u32> _vtabl2;
|
||||
be_t<u32> arg1; // 0x180
|
||||
be_t<u32> arg2; // 0x10
|
||||
be_t<u32> arg_size; // 6th arg
|
||||
be_t<u32> arg_ptr; // 5th arg
|
||||
|
||||
be_t<u32> _x34;
|
||||
be_t<s32> out_code;
|
||||
be_t<u32> out_fd;
|
||||
};
|
||||
|
||||
CHECK_SIZE(lv2_file_op_09, 0x40);
|
||||
|
||||
struct lv2_file_e0000025 : lv2_file_op
|
||||
{
|
||||
be_t<u32> size; // 0x30
|
||||
be_t<u32> _x4; // 0x10
|
||||
be_t<u32> _x8; // 0x28 - offset of out_code
|
||||
be_t<u32> name_size;
|
||||
vm::bcptr<char> name;
|
||||
be_t<u32> _x14;
|
||||
be_t<u32> _x18; // 0
|
||||
be_t<u32> _x1c; // 0
|
||||
be_t<u32> _x20; // 16
|
||||
be_t<u32> _x24; // unk, seems to be memory location
|
||||
be_t<u32> out_code; // out_code
|
||||
be_t<u32> 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<s32> _code; // Op result
|
||||
be_t<u32> _size; // Number of entries written
|
||||
vm::bptrb<CellFsDirectoryEntry> ptr;
|
||||
be_t<u32> max;
|
||||
};
|
||||
|
||||
CHECK_SIZE(dir_info, 0x10);
|
||||
|
||||
vm::bptrb<vtable::lv2_file_op> _vtable;
|
||||
|
||||
be_t<u32> op;
|
||||
be_t<u32> _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::lv2_file_op> _vtable;
|
||||
|
||||
be_t<u32> op;
|
||||
be_t<u32> _x8;
|
||||
vm::bcptr<char> path;
|
||||
be_t<u32> _x10; // 0
|
||||
be_t<u32> _x14;
|
||||
|
||||
be_t<u32> out_code; // CELL_ENOSYS
|
||||
be_t<u32> out_block_size;
|
||||
be_t<u64> 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<u32> size; // 0x20
|
||||
be_t<u32> _x4; // 0x10
|
||||
be_t<u32> _x8; // 0x18 - offset of out_code
|
||||
be_t<u32> name_size;
|
||||
vm::bcptr<char> name;
|
||||
be_t<u32> _x14; // 0
|
||||
be_t<u32> out_code; // 0x80010003
|
||||
be_t<u32> 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<u32> out_code; // set to 0
|
||||
vm::bcptr<char> device; // CELL_FS_IOS:ATA_HDD
|
||||
be_t<u32> device_size; // 0x14
|
||||
vm::bptr<char> model;
|
||||
be_t<u32> model_size; // 0x29
|
||||
vm::bptr<char> serial;
|
||||
be_t<u32> serial_size; // 0x15
|
||||
};
|
||||
|
||||
CHECK_SIZE(lv2_file_c0000007, 0x1c);
|
||||
|
||||
struct lv2_file_c0000008 : lv2_file_op
|
||||
{
|
||||
u8 _x0[4];
|
||||
be_t<u32> op; // 0xC0000008
|
||||
u8 _x8[8];
|
||||
be_t<u64> container_id;
|
||||
be_t<u32> size;
|
||||
be_t<u32> 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<u32> out_code;
|
||||
u8 _x24[4];
|
||||
};
|
||||
|
||||
CHECK_SIZE(lv2_file_c0000008, 0x28);
|
||||
|
||||
struct lv2_file_c0000015 : lv2_file_op
|
||||
{
|
||||
be_t<u32> size; // 0x20
|
||||
be_t<u32> _x4; // 0x10
|
||||
be_t<u32> _x8; // 0x18 - offset of out_code
|
||||
be_t<u32> path_size;
|
||||
vm::bcptr<char> path;
|
||||
be_t<u32> _x14; //
|
||||
be_t<u16> vendorID;
|
||||
be_t<u16> productID;
|
||||
be_t<u32> out_code; // set to 0
|
||||
};
|
||||
|
||||
CHECK_SIZE(lv2_file_c0000015, 0x20);
|
||||
|
||||
struct lv2_file_c000001a : lv2_file_op
|
||||
{
|
||||
be_t<u32> 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<u32> _x4; // 0
|
||||
be_t<u32> _x8; // 0x000186A0
|
||||
be_t<u32> _xC; // 0
|
||||
be_t<u32> _x10; // 0
|
||||
be_t<u32> _x14; // 0
|
||||
};
|
||||
|
||||
CHECK_SIZE(lv2_file_c000001a, 0x18);
|
||||
|
||||
struct lv2_file_c000001c : lv2_file_op
|
||||
{
|
||||
be_t<u32> size; // 0x60
|
||||
be_t<u32> _x4; // 0x10
|
||||
be_t<u32> _x8; // 0x18 - offset of out_code
|
||||
be_t<u32> path_size;
|
||||
vm::bcptr<char> path;
|
||||
be_t<u32> unk1;
|
||||
be_t<u16> vendorID;
|
||||
be_t<u16> productID;
|
||||
be_t<u32> out_code; // set to 0
|
||||
be_t<u16> serial[32];
|
||||
};
|
||||
|
||||
CHECK_SIZE(lv2_file_c000001c, 0x60);
|
||||
|
||||
// sys_fs_fcntl: cellFsAllocateFileAreaWithoutZeroFill
|
||||
struct lv2_file_e0000017 : lv2_file_op
|
||||
{
|
||||
be_t<u32> size; // 0x28
|
||||
be_t<u32> _x4; // 0x10, offset
|
||||
be_t<u32> _x8; // 0x20, offset
|
||||
be_t<u32> _xc; // -
|
||||
vm::bcptr<char> file_path;
|
||||
be_t<u64> file_size;
|
||||
be_t<u32> 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<u32> 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<u32> arg3, u32 arg4, vm::ptr<char> buf, u32 buf_size);
|
||||
error_code sys_fs_open(ppu_thread& ppu, vm::cptr<char> path, s32 flags, vm::ptr<u32> fd, s32 mode, vm::cptr<void> arg, u64 size);
|
||||
error_code sys_fs_read(ppu_thread& ppu, u32 fd, vm::ptr<void> buf, u64 nbytes, vm::ptr<u64> nread);
|
||||
error_code sys_fs_write(ppu_thread& ppu, u32 fd, vm::cptr<void> buf, u64 nbytes, vm::ptr<u64> nwrite);
|
||||
error_code sys_fs_close(ppu_thread& ppu, u32 fd);
|
||||
error_code sys_fs_opendir(ppu_thread& ppu, vm::cptr<char> path, vm::ptr<u32> fd);
|
||||
error_code sys_fs_readdir(ppu_thread& ppu, u32 fd, vm::ptr<CellFsDirent> dir, vm::ptr<u64> nread);
|
||||
error_code sys_fs_closedir(ppu_thread& ppu, u32 fd);
|
||||
error_code sys_fs_stat(ppu_thread& ppu, vm::cptr<char> path, vm::ptr<CellFsStat> sb);
|
||||
error_code sys_fs_fstat(ppu_thread& ppu, u32 fd, vm::ptr<CellFsStat> sb);
|
||||
error_code sys_fs_link(ppu_thread& ppu, vm::cptr<char> from, vm::cptr<char> to);
|
||||
error_code sys_fs_mkdir(ppu_thread& ppu, vm::cptr<char> path, s32 mode);
|
||||
error_code sys_fs_rename(ppu_thread& ppu, vm::cptr<char> from, vm::cptr<char> to);
|
||||
error_code sys_fs_rmdir(ppu_thread& ppu, vm::cptr<char> path);
|
||||
error_code sys_fs_unlink(ppu_thread& ppu, vm::cptr<char> path);
|
||||
error_code sys_fs_access(ppu_thread& ppu, vm::cptr<char> path, s32 mode);
|
||||
error_code sys_fs_fcntl(ppu_thread& ppu, u32 fd, u32 op, vm::ptr<void> arg, u32 size);
|
||||
error_code sys_fs_lseek(ppu_thread& ppu, u32 fd, s64 offset, s32 whence, vm::ptr<u64> 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<u64> sector_size, vm::ptr<u64> block_size, vm::ptr<u64> arg4, vm::ptr<s32> out_flags);
|
||||
error_code sys_fs_get_block_size(ppu_thread& ppu, vm::cptr<char> path, vm::ptr<u64> sector_size, vm::ptr<u64> block_size, vm::ptr<u64> arg4);
|
||||
error_code sys_fs_truncate(ppu_thread& ppu, vm::cptr<char> 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<char> target, vm::cptr<char> linkpath);
|
||||
error_code sys_fs_chmod(ppu_thread& ppu, vm::cptr<char> path, s32 mode);
|
||||
error_code sys_fs_chown(ppu_thread& ppu, vm::cptr<char> path, s32 uid, s32 gid);
|
||||
error_code sys_fs_disk_free(ppu_thread& ppu, vm::cptr<char> path, vm::ptr<u64> total_free, vm::ptr<u64> avail_free);
|
||||
error_code sys_fs_utime(ppu_thread& ppu, vm::cptr<char> path, vm::cptr<CellFsUtimbuf> timep);
|
||||
error_code sys_fs_acl_read(ppu_thread& ppu, vm::cptr<char> path, vm::ptr<void>);
|
||||
error_code sys_fs_acl_write(ppu_thread& ppu, vm::cptr<char> path, vm::ptr<void>);
|
||||
error_code sys_fs_lsn_get_cda_size(ppu_thread& ppu, u32 fd, vm::ptr<u64> ptr);
|
||||
error_code sys_fs_lsn_get_cda(ppu_thread& ppu, u32 fd, vm::ptr<void>, u64, vm::ptr<u64>);
|
||||
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<void>, u64);
|
||||
error_code sys_fs_lsn_write(ppu_thread& ppu, u32 fd, vm::cptr<void>, u64);
|
||||
error_code sys_fs_mapped_allocate(ppu_thread& ppu, u32 fd, u64, vm::pptr<void> out_ptr);
|
||||
error_code sys_fs_mapped_free(ppu_thread& ppu, u32 fd, vm::ptr<void> ptr);
|
||||
error_code sys_fs_truncate2(ppu_thread& ppu, u32 fd, u64 size);
|
||||
error_code sys_fs_newfs(ppu_thread& ppu, vm::cptr<char> dev_name, vm::cptr<char> file_system, s32 unk1, vm::cptr<char> str1);
|
||||
error_code sys_fs_mount(ppu_thread& ppu, vm::cptr<char> dev_name, vm::cptr<char> file_system, vm::cptr<char> path, s32 unk1, s32 prot, s32 unk2, vm::cptr<char> str1, u32 str_len);
|
||||
error_code sys_fs_unmount(ppu_thread& ppu, vm::cptr<char> path, s32 unk1, s32 force);
|
||||
error_code sys_fs_get_mount_info_size(ppu_thread& ppu, vm::ptr<u64> len);
|
||||
error_code sys_fs_get_mount_info(ppu_thread& ppu, vm::ptr<CellFsMountInfo> info, u64 len, vm::ptr<u64> out_len);
|
||||
|
|
@ -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<usz>(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<u64> 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<be_t<v128>>(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<v128> 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_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<named_thread<watchdog_t>>())
|
||||
{
|
||||
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<named_thread<watchdog_t>>().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<named_thread<watchdog_t>>().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<named_thread<watchdog_t>>().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<system_sw_version>().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<system_sw_version>().version;
|
||||
}
|
||||
|
||||
error_code _sys_game_board_storage_read(vm::ptr<u8> buffer, vm::ptr<u8> 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<board_storage>().read(buffer.get_ptr()) ? 0x00 : 0xFF;
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
|
||||
error_code _sys_game_board_storage_write(vm::ptr<u8> buffer, vm::ptr<u8> 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<board_storage>().write(buffer.get_ptr()) ? 0x00 : 0xFF;
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
|
||||
error_code _sys_game_get_rtc_status(vm::ptr<s32> status)
|
||||
{
|
||||
sys_game.trace("sys_game_get_rtc_status(status=*0x%x)", status);
|
||||
|
||||
if (!status)
|
||||
{
|
||||
return CELL_EFAULT;
|
||||
}
|
||||
|
||||
*status = 0;
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
void abort_lv2_watchdog();
|
||||
|
||||
error_code _sys_game_watchdog_start(u32 timeout);
|
||||
error_code _sys_game_watchdog_stop();
|
||||
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<u8> buffer, vm::ptr<u8> status);
|
||||
error_code _sys_game_board_storage_write(vm::ptr<u8> buffer, vm::ptr<u8> status);
|
||||
error_code _sys_game_get_rtc_status(vm::ptr<s32> status);
|
||||
|
|
@ -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<u8> in, vm::ptr<u8> 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<u8> in, vm::ptr<u8> 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<u8> in, vm::ptr<u8> 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<u8> in, vm::ptr<u8> 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<u8> in, vm::ptr<u8> 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<u8> in, vm::ptr<u8> 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<u8> in, vm::ptr<u8> 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<u8> in, vm::ptr<u8> 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<u8> in, vm::ptr<u8> 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<u8> in, vm::ptr<u8> 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<u8> in, vm::ptr<u8> 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;
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "Emu/Memory/vm_ptr.h"
|
||||
|
||||
// Syscalls
|
||||
|
||||
u32 sys_gamepad_ycon_if(u8 packet_id, vm::ptr<u8> in, vm::ptr<u8> out);
|
||||
|
|
@ -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<u64> 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;
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "Emu/Memory/vm_ptr.h"
|
||||
#include "Emu/Cell/ErrorCodes.h"
|
||||
|
||||
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<u64> value);
|
||||
error_code sys_gpio_set(u64 device_id, u64 mask, u64 value);
|
||||
|
|
@ -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<u32> 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<u32>(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<void> 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<sys_hid_info_2>(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<sys_hid_info_5>(buf);
|
||||
info->vid = 0x054C;
|
||||
info->pid = 0x0268;
|
||||
}
|
||||
// pkg_id == 6 == setpressmode?
|
||||
else if (pkg_id == 0x68)
|
||||
{
|
||||
[[maybe_unused]] auto info = vm::static_ptr_cast<sys_hid_ioctl_68>(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<void> 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<void> 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<u8>(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<sys_hid_manager_514_pkg_d>(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<u32> 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<void> 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<u16[64]>(buf);
|
||||
// todo: use handle and dont call cellpad here
|
||||
vm::var<CellPadData> tmpData;
|
||||
if ((cellPadGetData(0, +tmpData) == CELL_OK) && tmpData->len > 0)
|
||||
{
|
||||
u64 cpySize = std::min(static_cast<u64>(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<CellPadData> tmpData;
|
||||
if ((cellPadGetData(0, +tmpData) == CELL_OK) && tmpData->len > 0)
|
||||
{
|
||||
u64 cpySize = std::min(static_cast<u64>(tmpData->len) * sizeof(u16), buf_size * sizeof(u16));
|
||||
memcpy(buf.get_ptr(), &tmpData->button, cpySize);
|
||||
return not_an_error(cpySize / 2);
|
||||
}
|
||||
}
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
|
|
@ -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<u16> vid;
|
||||
le_t<u16> pid;
|
||||
u8 status;
|
||||
// todo: more in this, not sure what tho
|
||||
};
|
||||
|
||||
struct sys_hid_info_2
|
||||
{
|
||||
be_t<u16> vid;
|
||||
be_t<u16> pid;
|
||||
u8 unk[17];
|
||||
};
|
||||
|
||||
struct sys_hid_ioctl_68
|
||||
{
|
||||
u8 unk;
|
||||
u8 unk2;
|
||||
};
|
||||
|
||||
// unk
|
||||
struct sys_hid_manager_514_pkg_d
|
||||
{
|
||||
be_t<u32> unk1;
|
||||
u8 unk2;
|
||||
};
|
||||
|
||||
// SysCalls
|
||||
|
||||
error_code sys_hid_manager_open(ppu_thread& ppu, u64 device_type, u64 port_no, vm::ptr<u32> handle);
|
||||
error_code sys_hid_manager_ioctl(u32 hid_handle, u32 pkg_id, vm::ptr<void> buf, u64 buf_size);
|
||||
error_code sys_hid_manager_add_hot_key_observer(u32 event_queue, vm::ptr<u32> 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<void> buf, u64 buf_size);
|
||||
error_code sys_hid_manager_514(u32 pkg_id, vm::ptr<void> buf, u64 buf_size);
|
||||
error_code sys_hid_manager_read(u32 handle, u32 pkg_id, vm::ptr<void> buf, u64 buf_size);
|
||||
|
|
@ -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<lv2_obj, lv2_int_serv>(id);
|
||||
|
||||
if (!ptr && id)
|
||||
{
|
||||
Emu.PostponeInitCode([id, &handler = this->handler]()
|
||||
{
|
||||
handler = ensure(idm::get_unlocked<lv2_obj, lv2_int_serv>(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<named_thread<ppu_thread>> 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<named_thread<ppu_thread>>(ar)), arg1(ar), arg2(ar)
|
||||
{
|
||||
}
|
||||
|
||||
void lv2_int_serv::save(utils::serial& ar)
|
||||
{
|
||||
ar(thread && idm::check_unlocked<named_thread<ppu_thread>>(thread->id) ? thread->id : 0, arg1, arg2);
|
||||
}
|
||||
|
||||
void ppu_interrupt_thread_entry(ppu_thread&, ppu_opcode_t, be_t<u32>*, 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<u64>(&ppu_interrupt_thread_entry)});
|
||||
}
|
||||
|
||||
void ppu_thread_exit(ppu_thread&, ppu_opcode_t, be_t<u32>*, struct ppu_intrp_func*);
|
||||
|
||||
void lv2_int_serv::join() const
|
||||
{
|
||||
thread->cmd_list({{ppu_cmd::ptr_call, 0},
|
||||
std::bit_cast<u64>(&ppu_thread_exit)});
|
||||
|
||||
thread->cmd_notify.store(1);
|
||||
thread->cmd_notify.notify_one();
|
||||
(*thread)();
|
||||
|
||||
idm::remove_verify<named_thread<ppu_thread>>(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<lv2_obj, lv2_int_tag>(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<u32> 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 <lv2_obj, lv2_int_serv>([&]()
|
||||
{
|
||||
shared_ptr<lv2_int_serv> result;
|
||||
|
||||
// Get interrupt tag
|
||||
const auto tag = idm::check_unlocked<lv2_obj, lv2_int_tag>(intrtag);
|
||||
|
||||
if (!tag)
|
||||
{
|
||||
error = CELL_ESRCH;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Get interrupt thread
|
||||
const auto it = idm::get_unlocked<named_thread<ppu_thread>>(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<lv2_int_serv>(it, arg1, arg2);
|
||||
tag->handler = result;
|
||||
|
||||
it->cmd_list({{ppu_cmd::ptr_call, 0},
|
||||
std::bit_cast<u64>(&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<u64> 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<lv2_obj, lv2_int_serv>(ih, [](lv2_obj& obj)
|
||||
{
|
||||
obj.exists.release(0);
|
||||
});
|
||||
|
||||
if (!handler)
|
||||
{
|
||||
if (const auto thread = idm::withdraw<named_thread<ppu_thread>>(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<u32>*, struct ppu_intrp_func*)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
shared_ptr<lv2_int_serv> serv = null_ptr;
|
||||
|
||||
// Loop endlessly trying to invoke an interrupt if required
|
||||
idm::select<named_thread<spu_thread>>([&](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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<struct lv2_int_serv> 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<named_thread<ppu_thread>> thread;
|
||||
const u64 arg1;
|
||||
const u64 arg2;
|
||||
|
||||
lv2_int_serv(shared_ptr<named_thread<ppu_thread>> 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<u32> ih, u32 intrtag, u32 intrthread, u64 arg1, u64 arg2);
|
||||
error_code _sys_interrupt_thread_disestablish(ppu_thread& ppu, u32 ih, vm::ptr<u64> r13);
|
||||
void sys_interrupt_thread_eoi(ppu_thread& ppu);
|
||||
|
|
@ -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<u32> 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<lv2_io_buf>(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<lv2_io_buf>(handle);
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
|
||||
error_code sys_io_buffer_allocate(u32 handle, vm::ptr<u32> 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<lv2_io_buf>(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<lv2_io_buf>(handle);
|
||||
|
||||
if (!io)
|
||||
{
|
||||
return CELL_ESRCH;
|
||||
}
|
||||
|
||||
vm::dealloc(block);
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
|
|
@ -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<u32> handle);
|
||||
error_code sys_io_buffer_destroy(u32 handle);
|
||||
error_code sys_io_buffer_allocate(u32 handle, vm::ptr<u32> block);
|
||||
error_code sys_io_buffer_free(u32 handle, u32 block);
|
||||
|
|
@ -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<be_t<u64>>()), lwid(ar), protocol(ar), control(ar.pop<decltype(control)>())
|
||||
{
|
||||
}
|
||||
|
||||
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<u32> lwcond_id, u32 lwmutex_id, vm::ptr<sys_lwcond_t> 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<be_t<u64>>(name)});
|
||||
|
||||
u32 protocol;
|
||||
|
||||
// Extract protocol from lwmutex
|
||||
if (!idm::check<lv2_obj, lv2_lwmutex>(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<lv2_obj, lv2_lwcond>(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<lv2_lwcond> _cond;
|
||||
|
||||
while (true)
|
||||
{
|
||||
s32 old_val = 0;
|
||||
|
||||
auto [ptr, ret] = idm::withdraw<lv2_obj, lv2_lwcond>(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<ppu_thread*>::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<lv2_obj, lv2_lwcond>(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<named_thread<ppu_thread>>(static_cast<u32>(ppu_thread_id));
|
||||
|
||||
if (!cpu)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
lv2_lwmutex* mutex = nullptr;
|
||||
|
||||
if (mode != 2)
|
||||
{
|
||||
mutex = idm::check_unlocked<lv2_obj, lv2_lwmutex>(lwmutex_id);
|
||||
|
||||
if (!mutex)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (atomic_storage<ppu_thread*>::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<ppu_thread*>(cpu)->state & cpu_flag::again)
|
||||
{
|
||||
ppu.state += cpu_flag::again;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
auto result = cpu ? cond.unqueue(cond.sq, cpu) :
|
||||
cond.schedule<ppu_thread>(cond.sq, cond.protocol);
|
||||
|
||||
if (result)
|
||||
{
|
||||
if (static_cast<ppu_thread*>(result)->state & cpu_flag::again)
|
||||
{
|
||||
ppu.state += cpu_flag::again;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (mode == 2)
|
||||
{
|
||||
static_cast<ppu_thread*>(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<ppu_thread>();
|
||||
|
||||
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<lv2_obj, lv2_lwcond>(lwcond_id, [&, notify = lv2_obj::notify_all_t()](lv2_lwcond& cond) -> int
|
||||
{
|
||||
lv2_lwmutex* mutex{};
|
||||
|
||||
if (mode != 2)
|
||||
{
|
||||
mutex = idm::check_unlocked<lv2_obj, lv2_lwmutex>(lwmutex_id);
|
||||
|
||||
if (!mutex)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (atomic_storage<ppu_thread*>::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<ppu_thread*>::release(cond.sq, nullptr);
|
||||
|
||||
while (const auto cpu = cond.schedule<ppu_thread>(sq, cond.protocol))
|
||||
{
|
||||
if (mode == 2)
|
||||
{
|
||||
static_cast<ppu_thread*>(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<lv2_lwmutex> mutex;
|
||||
|
||||
auto& sstate = *ppu.optional_savestate_state;
|
||||
|
||||
const auto cond = idm::get<lv2_obj, lv2_lwcond>(lwcond_id, [&, notify = lv2_obj::notify_all_t()](lv2_lwcond& cond)
|
||||
{
|
||||
mutex = idm::get_unlocked<lv2_obj, lv2_lwmutex>(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<bool>().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<ppu_thread>())
|
||||
{
|
||||
if (static_cast<ppu_thread*>(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<ppu_thread*>::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<ppu_thread*>(data.sq);
|
||||
|
||||
const bool retval = &ppu == sq;
|
||||
|
||||
if (!mutex->unqueue<false>(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]);
|
||||
}
|
||||
|
|
@ -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<u64, 1> name_u64;
|
||||
char name[sizeof(u64)];
|
||||
};
|
||||
};
|
||||
|
||||
struct sys_lwcond_t
|
||||
{
|
||||
vm::bptr<sys_lwmutex_t> lwmutex;
|
||||
be_t<u32> lwcond_queue; // lwcond pseudo-id
|
||||
};
|
||||
|
||||
struct lv2_lwcond final : lv2_obj
|
||||
{
|
||||
static const u32 id_base = 0x97000000;
|
||||
|
||||
const be_t<u64> name;
|
||||
const u32 lwid;
|
||||
const lv2_protocol protocol;
|
||||
vm::ptr<sys_lwcond_t> control;
|
||||
|
||||
shared_mutex mutex;
|
||||
ppu_thread* sq{};
|
||||
|
||||
atomic_t<s32> lwmutex_waiters = 0;
|
||||
|
||||
lv2_lwcond(u64 name, u32 lwid, u32 protocol, vm::ptr<sys_lwcond_t> control) noexcept
|
||||
: name(std::bit_cast<be_t<u64>>(name)), lwid(lwid), protocol{static_cast<u8>(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<u32> lwcond_id, u32 lwmutex_id, vm::ptr<sys_lwcond_t> 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);
|
||||
|
|
@ -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<decltype(control)>()), name(ar.pop<be_t<u64>>())
|
||||
{
|
||||
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<u32> lwmutex_id, u32 protocol, vm::ptr<sys_lwmutex_t> 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<be_t<u64>>(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<lv2_obj, lv2_lwmutex>(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<lv2_lwmutex> _mutex;
|
||||
|
||||
while (true)
|
||||
{
|
||||
s32 old_val = 0;
|
||||
|
||||
auto [ptr, ret] = idm::withdraw<lv2_obj, lv2_lwmutex>(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<lv2_obj, lv2_lwmutex>(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<ppu_thread*>(data.sq);
|
||||
|
||||
const bool retval = &ppu == sq;
|
||||
|
||||
if (!mutex->unqueue<false>(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<lv2_obj, lv2_lwmutex>(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<lv2_obj, lv2_lwmutex>(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<ppu_thread>())
|
||||
{
|
||||
if (static_cast<ppu_thread*>(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<lv2_obj, lv2_lwmutex>(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<ppu_thread>(true))
|
||||
{
|
||||
if (static_cast<ppu_thread*>(cpu)->state & cpu_flag::again)
|
||||
{
|
||||
ppu.state += cpu_flag::again;
|
||||
return;
|
||||
}
|
||||
|
||||
static_cast<ppu_thread*>(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;
|
||||
}
|
||||
|
|
@ -1,205 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "sys_sync.h"
|
||||
|
||||
#include "Emu/Memory/vm_ptr.h"
|
||||
|
||||
struct sys_lwmutex_attribute_t
|
||||
{
|
||||
be_t<u32> protocol;
|
||||
be_t<u32> recursive;
|
||||
|
||||
union
|
||||
{
|
||||
nse_t<u64, 1> 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<u32> owner;
|
||||
be_t<u32> waiter;
|
||||
};
|
||||
|
||||
union
|
||||
{
|
||||
atomic_t<sync_var_t> lock_var;
|
||||
|
||||
struct
|
||||
{
|
||||
atomic_be_t<u32> owner;
|
||||
atomic_be_t<u32> waiter;
|
||||
} vars;
|
||||
|
||||
atomic_be_t<u64> all_info;
|
||||
};
|
||||
|
||||
be_t<u32> attribute;
|
||||
be_t<u32> recursive_count;
|
||||
be_t<u32> sleep_queue; // lwmutex pseudo-id
|
||||
be_t<u32> pad;
|
||||
};
|
||||
|
||||
struct lv2_lwmutex final : lv2_obj
|
||||
{
|
||||
static const u32 id_base = 0x95000000;
|
||||
|
||||
const lv2_protocol protocol;
|
||||
const vm::ptr<sys_lwmutex_t> control;
|
||||
const be_t<u64> name;
|
||||
|
||||
shared_mutex mutex;
|
||||
atomic_t<s32> lwcond_waiters{0};
|
||||
|
||||
struct alignas(16) control_data_t
|
||||
{
|
||||
s32 signaled{0};
|
||||
u32 reserved{};
|
||||
ppu_thread* sq{};
|
||||
};
|
||||
|
||||
atomic_t<control_data_t> lv2_control{};
|
||||
|
||||
lv2_lwmutex(u32 protocol, vm::ptr<sys_lwmutex_t> control, u64 name) noexcept
|
||||
: protocol{static_cast<u8>(protocol)}, control(control), name(std::bit_cast<be_t<u64>>(name))
|
||||
{
|
||||
}
|
||||
|
||||
lv2_lwmutex(utils::serial& ar);
|
||||
void save(utils::serial& ar);
|
||||
|
||||
ppu_thread* load_sq() const
|
||||
{
|
||||
return atomic_storage<ppu_thread*>::load(lv2_control.raw().sq);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
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<decltype(T::prio)>& 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<s32>::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 <typename T>
|
||||
T* reown(bool unlock2 = false)
|
||||
{
|
||||
T* res = nullptr;
|
||||
|
||||
lv2_control.fetch_op([&](control_data_t& data)
|
||||
{
|
||||
res = nullptr;
|
||||
|
||||
if (auto sq = static_cast<T*>(data.sq))
|
||||
{
|
||||
res = schedule<T>(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<u32> lwmutex_id, u32 protocol, vm::ptr<sys_lwmutex_t> 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);
|
||||
|
|
@ -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<void(void*)> lv2_memory_container::load(utils::serial& ar)
|
||||
{
|
||||
// Use idm::last_id() only for the instances at IDM
|
||||
return [ptr = make_shared<lv2_memory_container>(exact_t<utils::serial&>(ar), true)](void* storage)
|
||||
{
|
||||
*static_cast<atomic_ptr<lv2_memory_container>*>(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<lv2_memory_container>(id);
|
||||
}
|
||||
|
||||
return &g_fxo->get<lv2_memory_container>();
|
||||
}
|
||||
|
||||
struct sys_memory_address_table
|
||||
{
|
||||
atomic_t<lv2_memory_container*> addrs[65536]{};
|
||||
|
||||
sys_memory_address_table() = default;
|
||||
|
||||
SAVESTATE_INIT_POS(id_manager::id_map<lv2_memory_container>::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<u16, u32> 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<u16, u32> mm;
|
||||
|
||||
for (auto& ctr : addrs)
|
||||
{
|
||||
if (const auto ptr = +ctr)
|
||||
{
|
||||
mm[static_cast<u16>(&ctr - addrs)] = ptr->id;
|
||||
}
|
||||
}
|
||||
|
||||
ar(mm);
|
||||
}
|
||||
};
|
||||
|
||||
std::shared_ptr<vm::block_t> 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<u32> 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<lv2_memory_container>();
|
||||
|
||||
// Try to get "physical memory"
|
||||
if (!dct.take(size))
|
||||
{
|
||||
return {CELL_ENOMEM, dct.size - dct.used};
|
||||
}
|
||||
|
||||
if (const auto area = reserve_map(static_cast<u32>(size), align))
|
||||
{
|
||||
if (const u32 addr = area->alloc(static_cast<u32>(size), nullptr, align))
|
||||
{
|
||||
ensure(!g_fxo->get<sys_memory_address_table>().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<u32>(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<u32> 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<lv2_memory_container>(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<u32>(size), align))
|
||||
{
|
||||
if (const u32 addr = area->alloc(static_cast<u32>(size)))
|
||||
{
|
||||
ensure(!g_fxo->get<sys_memory_address_table>().addrs[addr >> 16].exchange(ct.ptr.get()));
|
||||
|
||||
if (alloc_addr)
|
||||
{
|
||||
vm::lock_sudo(addr, static_cast<u32>(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<sys_memory_address_table>().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<sys_page_attr_t> 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<sys_memory_info_t> 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<lv2_memory_container>();
|
||||
|
||||
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<lv2_memory_container>([&](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<sys_memory_user_memory_stat_t> 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<u32> 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<lv2_memory_container>();
|
||||
|
||||
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<lv2_memory_container>(static_cast<u32>(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<lv2_memory_container>(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<lv2_memory_container>().free(ct->size);
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
|
||||
error_code sys_memory_container_get_size(cpu_thread& cpu, vm::ptr<sys_memory_info_t> 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<lv2_memory_container>(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<u32> 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);
|
||||
}
|
||||
|
|
@ -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<u32> total_user_memory;
|
||||
be_t<u32> available_user_memory;
|
||||
};
|
||||
|
||||
struct sys_page_attr_t
|
||||
{
|
||||
be_t<u64> attribute;
|
||||
be_t<u64> access_right;
|
||||
be_t<u32> page_size;
|
||||
be_t<u32> 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<u32> 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<void(void*)> 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<u32>(amount);
|
||||
return static_cast<u32>(amount);
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
u32 free(u64 amount)
|
||||
{
|
||||
auto [_, result] = used.fetch_op([&](u32& value) -> u32
|
||||
{
|
||||
if (value >= amount)
|
||||
{
|
||||
value -= static_cast<u32>(amount);
|
||||
return static_cast<u32>(amount);
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
// Sanity check
|
||||
ensure(result == amount);
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
struct sys_memory_user_memory_stat_t
|
||||
{
|
||||
be_t<u32> a; // 0x0
|
||||
be_t<u32> b; // 0x4
|
||||
be_t<u32> c; // 0x8
|
||||
be_t<u32> d; // 0xc
|
||||
be_t<u32> e; // 0x10
|
||||
be_t<u32> f; // 0x14
|
||||
be_t<u32> g; // 0x18
|
||||
};
|
||||
|
||||
// SysCalls
|
||||
error_code sys_memory_allocate(cpu_thread& cpu, u64 size, u64 flags, vm::ptr<u32> alloc_addr);
|
||||
error_code sys_memory_allocate_from_container(cpu_thread& cpu, u64 size, u32 cid, u64 flags, vm::ptr<u32> 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<sys_page_attr_t> attr);
|
||||
error_code sys_memory_get_user_memory_size(cpu_thread& cpu, vm::ptr<sys_memory_info_t> mem_info);
|
||||
error_code sys_memory_get_user_memory_stat(cpu_thread& cpu, vm::ptr<sys_memory_user_memory_stat_t> mem_stat);
|
||||
error_code sys_memory_container_create(cpu_thread& cpu, vm::ptr<u32> 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<sys_memory_info_t> mem_info, u32 cid);
|
||||
error_code sys_memory_container_destroy_parent_with_childs(cpu_thread& cpu, u32 cid, u32 must_0, vm::ptr<u32> mc_child);
|
||||
|
|
@ -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 <span>
|
||||
|
||||
#include "util/vm.hpp"
|
||||
|
||||
LOG_CHANNEL(sys_mmapper);
|
||||
|
||||
template <>
|
||||
void fmt_class_string<lv2_mem_container_id>::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<utils::shm>(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<u32>())), shm([&](u32 addr)
|
||||
{
|
||||
if (addr)
|
||||
{
|
||||
return ensure(vm::get(vm::any, addr)->peek(addr).second);
|
||||
}
|
||||
|
||||
const auto _shm = std::make_shared<utils::shm>(size, 1);
|
||||
ar(std::span(_shm->map_self(), size));
|
||||
return _shm;
|
||||
}(ar.pop<u32>())),
|
||||
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<void(void*)> lv2_memory::load(utils::serial& ar)
|
||||
{
|
||||
auto mem = make_shared<lv2_memory>(exact_t<utils::serial&>(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 <bool exclusive = false>
|
||||
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<lv2_memory>(_pshared, ipc_key, exclusive ? SYS_SYNC_NEWLY_CREATED : SYS_SYNC_NOT_CARE, [&]()
|
||||
{
|
||||
return make_shared<lv2_memory>(
|
||||
static_cast<u32>(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<u32> 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<u32>(size), static_cast<u32>(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<u32> 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<lv2_memory_container>();
|
||||
|
||||
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<u32> 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<lv2_memory_container>(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<mmapper_unk_entry_struct0> entries, s32 entry_count, vm::ptr<u32> 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<lv2_memory_container>();
|
||||
|
||||
if (auto error = create_lv2_shm<true>(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<mmapper_unk_entry_struct0> entries, s32 entry_count, vm::ptr<u32> 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<lv2_memory_container>(cid);
|
||||
|
||||
if (!ct)
|
||||
{
|
||||
return CELL_ESRCH;
|
||||
}
|
||||
|
||||
if (auto error = create_lv2_shm<true>(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<page_fault_event_entries>();
|
||||
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<page_fault_notification_entries>();
|
||||
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<lv2_obj, lv2_memory>(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<lv2_obj, lv2_memory>(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<u32> 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<lv2_obj, lv2_memory>(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<u32> 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<lv2_obj, lv2_memory>([&](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<lv2_obj, lv2_event_queue>(event_queue_id);
|
||||
|
||||
if (!queue)
|
||||
{ // Can't connect the queue if it doesn't exist.
|
||||
return CELL_ESRCH;
|
||||
}
|
||||
|
||||
vm::var<u32> 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<page_fault_notification_entries>();
|
||||
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<page_fault_event_entries>();
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
|
@ -1,115 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "sys_sync.h"
|
||||
|
||||
#include "Emu/Memory/vm_ptr.h"
|
||||
#include "Emu/Cell/ErrorCodes.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
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<utils::shm> shm;
|
||||
|
||||
atomic_t<u32> 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<void(void*)> 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<page_fault_notification_entry> 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<class cpu_thread*, u32> events;
|
||||
shared_mutex pf_mutex;
|
||||
};
|
||||
|
||||
struct mmapper_unk_entry_struct0
|
||||
{
|
||||
be_t<u32> a; // 0x0
|
||||
be_t<u32> b; // 0x4
|
||||
be_t<u32> c; // 0x8
|
||||
be_t<u32> d; // 0xc
|
||||
be_t<u64> 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<u32> 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<u32> mem_id);
|
||||
error_code sys_mmapper_allocate_shared_memory_from_container(ppu_thread&, u64 ipc_key, u64 size, u32 cid, u64 flags, vm::ptr<u32> mem_id);
|
||||
error_code sys_mmapper_allocate_shared_memory_ext(ppu_thread&, u64 ipc_key, u64 size, u32 flags, vm::ptr<mmapper_unk_entry_struct0> entries, s32 entry_count, vm::ptr<u32> 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<mmapper_unk_entry_struct0> entries, s32 entry_count, vm::ptr<u32> 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<u32> alloc_addr);
|
||||
error_code sys_mmapper_unmap_shared_memory(ppu_thread&, u32 addr, vm::ptr<u32> mem_id);
|
||||
error_code sys_mmapper_enable_page_fault_notification(ppu_thread&, u32 start_addr, u32 event_queue_id);
|
||||
|
|
@ -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<void(void*)> lv2_mutex::load(utils::serial& ar)
|
||||
{
|
||||
return load_func(make_shared<lv2_mutex>(exact_t<utils::serial&>(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<u32> mutex_id, vm::ptr<sys_mutex_attribute_t> 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<lv2_mutex>(_attr.pshared, _attr.ipc_key, _attr.flags, [&]()
|
||||
{
|
||||
return make_shared<lv2_mutex>(
|
||||
_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<lv2_obj, lv2_mutex>(mutex_id, [](lv2_mutex& mutex) -> CellError
|
||||
{
|
||||
std::lock_guard lock(mutex.mutex);
|
||||
|
||||
if (atomic_storage<u32>::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<lv2_obj, lv2_mutex>(mutex_id, [&, notify = lv2_obj::notify_all_t()](lv2_mutex& mutex)
|
||||
{
|
||||
CellError result = mutex.try_lock(ppu);
|
||||
|
||||
if (result == CELL_EBUSY && !atomic_storage<ppu_thread*>::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<ppu_thread*>::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<ppu_thread*>::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<ppu_thread*>::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<ppu_thread*>(data.sq);
|
||||
|
||||
const bool retval = &ppu == sq;
|
||||
|
||||
if (!mutex->unqueue<false>(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<lv2_obj, lv2_mutex>(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<lv2_obj, lv2_mutex>(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<ppu_thread>())
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
|
@ -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<u32> protocol; // SYS_SYNC_FIFO, SYS_SYNC_PRIORITY or SYS_SYNC_PRIORITY_INHERIT
|
||||
be_t<u32> recursive; // SYS_SYNC_RECURSIVE or SYS_SYNC_NOT_RECURSIVE
|
||||
be_t<u32> pshared;
|
||||
be_t<u32> adaptive;
|
||||
be_t<u64> ipc_key;
|
||||
be_t<s32> flags;
|
||||
be_t<u32> pad;
|
||||
|
||||
union
|
||||
{
|
||||
nse_t<u64, 1> 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<u32> lock_count{0}; // Recursive Locks
|
||||
|
||||
struct alignas(16) control_data_t
|
||||
{
|
||||
u32 owner{};
|
||||
u32 reserved{};
|
||||
ppu_thread* sq{};
|
||||
};
|
||||
|
||||
atomic_t<control_data_t> control{};
|
||||
|
||||
lv2_mutex(u32 protocol, u32 recursive, u32 adaptive, u64 key, u64 name) noexcept
|
||||
: protocol{static_cast<u8>(protocol)}, recursive(recursive), adaptive(adaptive), key(key), name(name)
|
||||
{
|
||||
}
|
||||
|
||||
lv2_mutex(utils::serial& ar);
|
||||
static std::function<void(void*)> load(utils::serial& ar);
|
||||
void save(utils::serial& ar);
|
||||
|
||||
template <typename T>
|
||||
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 <typename T>
|
||||
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<decltype(T::prio)>& 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 <typename T>
|
||||
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 <typename T>
|
||||
T* reown()
|
||||
{
|
||||
T* res{};
|
||||
|
||||
control.fetch_op([&](control_data_t& data)
|
||||
{
|
||||
res = nullptr;
|
||||
|
||||
if (auto sq = static_cast<T*>(data.sq))
|
||||
{
|
||||
res = schedule<T>(data.sq, protocol, false);
|
||||
|
||||
if (sq == data.sq)
|
||||
{
|
||||
atomic_storage<u32>::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<u32> mutex_id, vm::ptr<sys_mutex_attribute_t> 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);
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -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 <vector>
|
||||
#include <utility>
|
||||
#include <functional>
|
||||
#include <queue>
|
||||
|
||||
// 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<u32> 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<char> h_name;
|
||||
vm::bpptr<char> h_aliases;
|
||||
be_t<s32> h_addrtype;
|
||||
be_t<s32> h_length;
|
||||
vm::bpptr<char> h_addr_list;
|
||||
};
|
||||
|
||||
// in_addr prefixed with sys_net_
|
||||
struct sys_net_in_addr
|
||||
{
|
||||
be_t<u32> _s_addr;
|
||||
};
|
||||
|
||||
// iovec prefixed with sys_net_
|
||||
struct sys_net_iovec
|
||||
{
|
||||
be_t<s32> zero1;
|
||||
vm::bptr<void> iov_base;
|
||||
be_t<s32> zero2;
|
||||
be_t<u32> iov_len;
|
||||
};
|
||||
|
||||
// ip_mreq prefixed with sys_net_
|
||||
struct sys_net_ip_mreq
|
||||
{
|
||||
be_t<u32> imr_multiaddr;
|
||||
be_t<u32> imr_interface;
|
||||
};
|
||||
|
||||
// msghdr prefixed with sys_net_
|
||||
struct sys_net_msghdr
|
||||
{
|
||||
be_t<s32> zero1;
|
||||
vm::bptr<void> msg_name;
|
||||
be_t<u32> msg_namelen;
|
||||
be_t<s32> pad1;
|
||||
be_t<s32> zero2;
|
||||
vm::bptr<sys_net_iovec> msg_iov;
|
||||
be_t<s32> msg_iovlen;
|
||||
be_t<s32> pad2;
|
||||
be_t<s32> zero3;
|
||||
vm::bptr<void> msg_control;
|
||||
be_t<u32> msg_controllen;
|
||||
be_t<s32> msg_flags;
|
||||
};
|
||||
|
||||
// pollfd prefixed with sys_net_
|
||||
struct sys_net_pollfd
|
||||
{
|
||||
be_t<s32> fd;
|
||||
be_t<s16> events;
|
||||
be_t<s16> 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<u16> 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<u16> sin_port;
|
||||
be_t<u32> sin_addr;
|
||||
be_t<u64> 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<u16> sin_port;
|
||||
be_t<u32> sin_addr;
|
||||
be_t<u16> sin_vport;
|
||||
char sin_zero[6];
|
||||
};
|
||||
|
||||
// timeval prefixed with sys_net_
|
||||
struct sys_net_timeval
|
||||
{
|
||||
be_t<s64> tv_sec;
|
||||
be_t<s64> tv_usec;
|
||||
};
|
||||
|
||||
// linger prefixed with sys_net_
|
||||
struct sys_net_linger
|
||||
{
|
||||
be_t<s32> l_onoff;
|
||||
be_t<s32> l_linger;
|
||||
};
|
||||
|
||||
class ppu_thread;
|
||||
|
||||
// Syscalls
|
||||
|
||||
error_code sys_net_bnet_accept(ppu_thread&, s32 s, vm::ptr<sys_net_sockaddr> addr, vm::ptr<u32> paddrlen);
|
||||
error_code sys_net_bnet_bind(ppu_thread&, s32 s, vm::cptr<sys_net_sockaddr> addr, u32 addrlen);
|
||||
error_code sys_net_bnet_connect(ppu_thread&, s32 s, vm::ptr<sys_net_sockaddr> addr, u32 addrlen);
|
||||
error_code sys_net_bnet_getpeername(ppu_thread&, s32 s, vm::ptr<sys_net_sockaddr> addr, vm::ptr<u32> paddrlen);
|
||||
error_code sys_net_bnet_getsockname(ppu_thread&, s32 s, vm::ptr<sys_net_sockaddr> addr, vm::ptr<u32> paddrlen);
|
||||
error_code sys_net_bnet_getsockopt(ppu_thread&, s32 s, s32 level, s32 optname, vm::ptr<void> optval, vm::ptr<u32> 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<void> buf, u32 len, s32 flags, vm::ptr<sys_net_sockaddr> addr, vm::ptr<u32> paddrlen);
|
||||
error_code sys_net_bnet_recvmsg(ppu_thread&, s32 s, vm::ptr<sys_net_msghdr> msg, s32 flags);
|
||||
error_code sys_net_bnet_sendmsg(ppu_thread&, s32 s, vm::cptr<sys_net_msghdr> msg, s32 flags);
|
||||
error_code sys_net_bnet_sendto(ppu_thread&, s32 s, vm::cptr<void> buf, u32 len, s32 flags, vm::cptr<sys_net_sockaddr> addr, u32 addrlen);
|
||||
error_code sys_net_bnet_setsockopt(ppu_thread&, s32 s, s32 level, s32 optname, vm::cptr<void> 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<sys_net_pollfd> fds, s32 nfds, s32 ms);
|
||||
error_code sys_net_bnet_select(ppu_thread&, s32 nfds, vm::ptr<sys_net_fd_set> readfds, vm::ptr<sys_net_fd_set> writefds, vm::ptr<sys_net_fd_set> exceptfds, vm::ptr<sys_net_timeval> 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<void> buf, s32 len, vm::ptr<s32> pflags);
|
||||
error_code _sys_net_close_dump(ppu_thread&, s32 id, vm::ptr<s32> pflags);
|
||||
error_code _sys_net_write_dump(ppu_thread&, s32 id, vm::cptr<void> 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<void> arg);
|
||||
error_code sys_net_control(ppu_thread&, u32 arg1, s32 arg2, vm::ptr<void> 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<void> arg4, u32 arg5, u32 arg6);
|
||||
error_code sys_net_eurus_post_command(ppu_thread&, s32 arg1, u32 arg2, u32 arg3);
|
||||
|
|
@ -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<shared_mutex> 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::poll_t> lv2_socket::get_events() const
|
||||
{
|
||||
return events.load();
|
||||
}
|
||||
|
||||
void lv2_socket::set_poll_event(bs_t<lv2_socket::poll_t> event)
|
||||
{
|
||||
events += event;
|
||||
}
|
||||
|
||||
void lv2_socket::poll_queue(shared_ptr<ppu_thread> ppu, bs_t<lv2_socket::poll_t> event, std::function<bool(bs_t<lv2_socket::poll_t>)> 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<network_context>();
|
||||
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<network_context>().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<lv2_socket::poll_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<network_context>().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<network_context>().add_ppu_to_awake(ppu);
|
||||
break;
|
||||
case SYS_NET_SOCK_DGRAM_P2P:
|
||||
case SYS_NET_SOCK_STREAM_P2P:
|
||||
g_fxo->get<p2p_context>().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
|
||||
{
|
||||
}
|
||||
|
|
@ -1,156 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
|
||||
#include "util/mutex.h"
|
||||
#include "Emu/IdManager.h"
|
||||
#include "Emu/Cell/lv2/sys_net.h"
|
||||
#include "Emu/NP/ip_address.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <WS2tcpip.h>
|
||||
#else
|
||||
#ifdef __clang__
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wold-style-cast"
|
||||
#endif
|
||||
#include <poll.h>
|
||||
#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<s32> _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<void(void*)> 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<shared_mutex> lock();
|
||||
|
||||
void set_lv2_id(u32 id);
|
||||
bs_t<poll_t> get_events() const;
|
||||
void set_poll_event(bs_t<poll_t> event);
|
||||
void poll_queue(shared_ptr<ppu_thread> ppu, bs_t<poll_t> event, std::function<bool(bs_t<poll_t>)> 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<bool, s32, shared_ptr<lv2_socket>, sys_net_sockaddr> accept(bool is_lock = true) = 0;
|
||||
virtual s32 bind(const sys_net_sockaddr& addr) = 0;
|
||||
|
||||
virtual std::optional<s32> connect(const sys_net_sockaddr& addr) = 0;
|
||||
virtual s32 connect_followup() = 0;
|
||||
|
||||
virtual std::pair<s32, sys_net_sockaddr> getpeername() = 0;
|
||||
virtual std::pair<s32, sys_net_sockaddr> getsockname() = 0;
|
||||
|
||||
virtual std::tuple<s32, sockopt_data, u32> getsockopt(s32 level, s32 optname, u32 len) = 0;
|
||||
virtual s32 setsockopt(s32 level, s32 optname, const std::vector<u8>& optval) = 0;
|
||||
|
||||
virtual s32 listen(s32 backlog) = 0;
|
||||
|
||||
virtual std::optional<std::tuple<s32, std::vector<u8>, sys_net_sockaddr>> recvfrom(s32 flags, u32 len, bool is_lock = true) = 0;
|
||||
virtual std::optional<s32> sendto(s32 flags, const std::vector<u8>& buf, std::optional<sys_net_sockaddr> opt_sn_addr, bool is_lock = true) = 0;
|
||||
virtual std::optional<s32> 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<bool, bool, bool> select(bs_t<poll_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<poll_t> events{};
|
||||
|
||||
// Event processing workload (pair of thread id and the processing function)
|
||||
std::vector<std::pair<shared_ptr<ppu_thread>, std::function<bool(bs_t<poll_t>)>>> 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;
|
||||
};
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,75 +0,0 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <WS2tcpip.h>
|
||||
#else
|
||||
#ifdef __clang__
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wold-style-cast"
|
||||
#endif
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/ip.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <unistd.h>
|
||||
#include <poll.h>
|
||||
#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<bool, s32, shared_ptr<lv2_socket>, sys_net_sockaddr> accept(bool is_lock = true) override;
|
||||
s32 bind(const sys_net_sockaddr& addr) override;
|
||||
|
||||
std::optional<s32> connect(const sys_net_sockaddr& addr) override;
|
||||
s32 connect_followup() override;
|
||||
|
||||
std::pair<s32, sys_net_sockaddr> getpeername() override;
|
||||
std::pair<s32, sys_net_sockaddr> getsockname() override;
|
||||
std::tuple<s32, sockopt_data, u32> getsockopt(s32 level, s32 optname, u32 len) override;
|
||||
s32 setsockopt(s32 level, s32 optname, const std::vector<u8>& optval) override;
|
||||
std::optional<std::tuple<s32, std::vector<u8>, sys_net_sockaddr>> recvfrom(s32 flags, u32 len, bool is_lock = true) override;
|
||||
std::optional<s32> sendto(s32 flags, const std::vector<u8>& buf, std::optional<sys_net_sockaddr> opt_sn_addr, bool is_lock = true) override;
|
||||
std::optional<s32> 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<bool, bool, bool> select(bs_t<poll_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
|
||||
};
|
||||
|
|
@ -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<u64>(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<std::deque<std::pair<sys_net_sockaddr_in_p2p, std::vector<u8>>>>();
|
||||
|
||||
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<std::pair<sys_net_sockaddr_in_p2p, std::vector<u8>>> 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<u8> 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<lv2_socket::poll_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<bool, s32, shared_ptr<lv2_socket>, 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<s32> 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<s32, sys_net_sockaddr> 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<const sys_net_sockaddr_in_p2p*>(&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<u32>(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<p2p_context>();
|
||||
{
|
||||
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<s32> 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<s32, sys_net_sockaddr> 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<sys_net_sockaddr_in_p2p*>(&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<s32, lv2_socket::sockopt_data, u32> lv2_socket_p2p::getsockopt(s32 level, s32 optname, u32 len)
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
|
||||
const u64 key = (static_cast<u64>(level) << 32) | static_cast<u64>(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<u8>& optval)
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
|
||||
int native_int = *reinterpret_cast<const be_t<s32>*>(optval.data());
|
||||
|
||||
if (level == SYS_NET_SOL_SOCKET && optname == SYS_NET_SO_NBIO)
|
||||
{
|
||||
so_nbio = native_int;
|
||||
}
|
||||
|
||||
const u64 key = (static_cast<u64>(level) << 32) | static_cast<u64>(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<std::tuple<s32, std::vector<u8>, sys_net_sockaddr>> lv2_socket_p2p::recvfrom(s32 flags, u32 len, bool is_lock)
|
||||
{
|
||||
std::unique_lock<shared_mutex> 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<u8> res_buf(len);
|
||||
|
||||
const auto& p2p_data = data.front();
|
||||
s32 native_result = std::min(len, static_cast<u32>(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<s32> lv2_socket_p2p::sendto(s32 flags, const std::vector<u8>& buf, std::optional<sys_net_sockaddr> opt_sn_addr, bool is_lock)
|
||||
{
|
||||
std::unique_lock<shared_mutex> 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<usz>(65535 - VPORT_P2P_HEADER_SIZE)); // catch games using full payload for future fragmentation implementation if necessary
|
||||
const u16 p2p_port = reinterpret_cast<const sys_net_sockaddr_in*>(&*opt_sn_addr)->sin_port;
|
||||
const u16 p2p_vport = reinterpret_cast<const sys_net_sockaddr_in_p2p*>(&*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<u8> p2p_data(buf.size() + VPORT_P2P_HEADER_SIZE);
|
||||
const le_t<u16> p2p_vport_le = p2p_vport;
|
||||
const le_t<u16> src_vport_le = vport;
|
||||
const le_t<u16> 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<const char*>(p2p_data.data()), ::size32(p2p_data), &native_addr, native_flags);
|
||||
|
||||
if (native_result >= 0)
|
||||
{
|
||||
return {std::max<s32>(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<s32> 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<p2p_context>())
|
||||
{
|
||||
auto& nc = g_fxo->get<p2p_context>();
|
||||
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<bool, bool, bool> lv2_socket_p2p::select(bs_t<lv2_socket::poll_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};
|
||||
}
|
||||
|
|
@ -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<bool, s32, shared_ptr<lv2_socket>, sys_net_sockaddr> accept(bool is_lock = true) override;
|
||||
s32 bind(const sys_net_sockaddr& addr) override;
|
||||
|
||||
std::optional<s32> connect(const sys_net_sockaddr& addr) override;
|
||||
s32 connect_followup() override;
|
||||
|
||||
std::pair<s32, sys_net_sockaddr> getpeername() override;
|
||||
std::pair<s32, sys_net_sockaddr> getsockname() override;
|
||||
|
||||
std::tuple<s32, sockopt_data, u32> getsockopt(s32 level, s32 optname, u32 len) override;
|
||||
s32 setsockopt(s32 level, s32 optname, const std::vector<u8>& optval) override;
|
||||
|
||||
s32 listen(s32 backlog) override;
|
||||
|
||||
std::optional<std::tuple<s32, std::vector<u8>, sys_net_sockaddr>> recvfrom(s32 flags, u32 len, bool is_lock = true) override;
|
||||
std::optional<s32> sendto(s32 flags, const std::vector<u8>& buf, std::optional<sys_net_sockaddr> opt_sn_addr, bool is_lock = true) override;
|
||||
std::optional<s32> 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<bool, bool, bool> select(bs_t<poll_t> selected, pollfd& native_pfd) override;
|
||||
|
||||
void handle_new_data(sys_net_sockaddr_in_p2p p2p_addr, std::vector<u8> 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<std::pair<sys_net_sockaddr_in_p2p, std::vector<u8>>> data{};
|
||||
// List of sock options
|
||||
std::map<u64, sockopt_cache> sockopts;
|
||||
};
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,114 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <WS2tcpip.h>
|
||||
#else
|
||||
#ifdef __clang__
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wold-style-cast"
|
||||
#endif
|
||||
#include <netinet/in.h>
|
||||
#ifdef __clang__
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include "lv2_socket_p2p.h"
|
||||
|
||||
struct nt_p2p_port;
|
||||
|
||||
constexpr be_t<u32> P2PS_U2S_SIG = (static_cast<u32>('U') << 24 | static_cast<u32>('2') << 16 | static_cast<u32>('S') << 8 | static_cast<u32>('0'));
|
||||
|
||||
struct p2ps_encapsulated_tcp
|
||||
{
|
||||
be_t<u32> signature = P2PS_U2S_SIG; // Signature to verify it's P2P Stream data
|
||||
be_t<u32> length = 0; // Length of data
|
||||
be_t<u64> seq = 0; // This should be u32 but changed to u64 for simplicity
|
||||
be_t<u64> ack = 0;
|
||||
be_t<u16> src_port = 0; // fake source tcp port
|
||||
be_t<u16> dst_port = 0; // fake dest tcp port(should be == vport)
|
||||
be_t<u16> 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<u16>* buffer, usz size);
|
||||
std::vector<u8> 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<u8> data, const ::sockaddr_in* dst, u64 seq, bool require_ack);
|
||||
void close_stream();
|
||||
|
||||
std::tuple<bool, s32, shared_ptr<lv2_socket>, sys_net_sockaddr> accept(bool is_lock = true) override;
|
||||
s32 bind(const sys_net_sockaddr& addr) override;
|
||||
|
||||
std::optional<s32> connect(const sys_net_sockaddr& addr) override;
|
||||
|
||||
std::pair<s32, sys_net_sockaddr> getpeername() override;
|
||||
std::pair<s32, sys_net_sockaddr> getsockname() override;
|
||||
|
||||
s32 listen(s32 backlog) override;
|
||||
|
||||
std::optional<std::tuple<s32, std::vector<u8>, sys_net_sockaddr>> recvfrom(s32 flags, u32 len, bool is_lock = true) override;
|
||||
std::optional<s32> sendto(s32 flags, const std::vector<u8>& buf, std::optional<sys_net_sockaddr> opt_sn_addr, bool is_lock = true) override;
|
||||
std::optional<s32> 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<bool, bool, bool> select(bs_t<poll_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<s32> 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<u64, std::vector<u8>> received_data; // holds seq/data of data received
|
||||
|
||||
u64 cur_seq = 0; // SEQ of next packet to be sent
|
||||
};
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
#include "stdafx.h"
|
||||
#include "lv2_socket_raw.h"
|
||||
#include "Emu/NP/vport0.h"
|
||||
|
||||
LOG_CHANNEL(sys_net);
|
||||
|
||||
template <typename T>
|
||||
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<bool> logged = false;
|
||||
};
|
||||
|
||||
#define LOG_ONCE(raw_var, message) \
|
||||
if (!g_fxo->get<socket_raw_logging<class raw_var>>().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<bool, s32, shared_ptr<lv2_socket>, 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<s32> 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<s32, sys_net_sockaddr> 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<s32, sys_net_sockaddr> lv2_socket_raw::getsockname()
|
||||
{
|
||||
LOG_ONCE(raw_getsockname, "lv2_socket_raw::getsockname");
|
||||
return {};
|
||||
}
|
||||
|
||||
std::tuple<s32, lv2_socket::sockopt_data, u32> 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<u8>& optval)
|
||||
{
|
||||
LOG_ONCE(raw_setsockopt, "lv2_socket_raw::setsockopt");
|
||||
|
||||
// TODO
|
||||
int native_int = *reinterpret_cast<const be_t<s32>*>(optval.data());
|
||||
|
||||
if (level == SYS_NET_SOL_SOCKET && optname == SYS_NET_SO_NBIO)
|
||||
{
|
||||
so_nbio = native_int;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<std::tuple<s32, std::vector<u8>, 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<s32> lv2_socket_raw::sendto([[maybe_unused]] s32 flags, [[maybe_unused]] const std::vector<u8>& buf, [[maybe_unused]] std::optional<sys_net_sockaddr> opt_sn_addr, [[maybe_unused]] bool is_lock)
|
||||
{
|
||||
LOG_ONCE(raw_sendto, "lv2_socket_raw::sendto");
|
||||
return ::size32(buf);
|
||||
}
|
||||
|
||||
std::optional<s32> 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<bool, bool, bool> lv2_socket_raw::select([[maybe_unused]] bs_t<lv2_socket::poll_t> selected, [[maybe_unused]] pollfd& native_pfd)
|
||||
{
|
||||
LOG_ONCE(raw_select, "lv2_socket_raw::select");
|
||||
return {};
|
||||
}
|
||||
|
|
@ -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<bool, s32, shared_ptr<lv2_socket>, sys_net_sockaddr> accept(bool is_lock = true) override;
|
||||
s32 bind(const sys_net_sockaddr& addr) override;
|
||||
|
||||
std::optional<s32> connect(const sys_net_sockaddr& addr) override;
|
||||
s32 connect_followup() override;
|
||||
|
||||
std::pair<s32, sys_net_sockaddr> getpeername() override;
|
||||
std::pair<s32, sys_net_sockaddr> getsockname() override;
|
||||
|
||||
std::tuple<s32, sockopt_data, u32> getsockopt(s32 level, s32 optname, u32 len) override;
|
||||
s32 setsockopt(s32 level, s32 optname, const std::vector<u8>& optval) override;
|
||||
|
||||
s32 listen(s32 backlog) override;
|
||||
|
||||
std::optional<std::tuple<s32, std::vector<u8>, sys_net_sockaddr>> recvfrom(s32 flags, u32 len, bool is_lock = true) override;
|
||||
std::optional<s32> sendto(s32 flags, const std::vector<u8>& buf, std::optional<sys_net_sockaddr> opt_sn_addr, bool is_lock = true) override;
|
||||
std::optional<s32> 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<bool, bool, bool> select(bs_t<poll_t> selected, pollfd& native_pfd) override;
|
||||
};
|
||||
|
|
@ -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<u8>& data, const sockaddr_in& addr)
|
||||
{
|
||||
auto& nc = g_fxo->get<p2p_context>();
|
||||
{
|
||||
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<const char*>(data.data()), ::size32(data), 0, reinterpret_cast<const sockaddr*>(&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<const char*>(data.data()), ::size32(data), 0, reinterpret_cast<const sockaddr*>(&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<u8>& data, const sockaddr_in6& addr)
|
||||
{
|
||||
auto& nc = g_fxo->get<p2p_context>();
|
||||
{
|
||||
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<const char*>(data.data()), ::size32(data), 0, reinterpret_cast<const sockaddr*>(&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<std::vector<u8>> get_rpcn_msgs()
|
||||
{
|
||||
std::vector<std::vector<u8>> msgs;
|
||||
auto& nc = g_fxo->get<p2p_context>();
|
||||
{
|
||||
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<signaling_message> get_sign_msgs()
|
||||
{
|
||||
std::vector<signaling_message> msgs;
|
||||
auto& nc = g_fxo->get<p2p_context>();
|
||||
{
|
||||
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<shared_ptr<lv2_socket>> 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<bool> connecting(lv2_socket::id_count);
|
||||
std::vector<bool> 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<lv2_socket>([&](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<lv2_socket>(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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#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_thread*> ppu_to_awake;
|
||||
|
||||
void wake_threads();
|
||||
};
|
||||
|
||||
struct network_thread : base_network_thread
|
||||
{
|
||||
shared_mutex mutex_thread_loop;
|
||||
atomic_t<u32> 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<u16, nt_p2p_port> list_p2p_ports;
|
||||
atomic_t<u32> 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<network_thread>;
|
||||
using p2p_context = named_thread<p2p_thread>;
|
||||
|
|
@ -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<s32>& sock_ids)
|
||||
{
|
||||
for (const s32 sock_id : sock_ids)
|
||||
{
|
||||
const auto [_, reusable] = idm::check<lv2_socket>(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<const char*>(&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<u16, be_t<u16>>(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<const char*>(&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<sockaddr*>(&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<sockaddr*>(&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<named_thread<np::np_handler>>();
|
||||
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<lv2_socket>(sock_id, [&](lv2_socket& sock) -> bool
|
||||
{
|
||||
ensure(sock.get_type() == SYS_NET_SOCK_STREAM_P2P);
|
||||
auto& sock_p2ps = reinterpret_cast<lv2_socket_p2ps&>(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<lv2_socket>(sock_id);
|
||||
if (!sock)
|
||||
return false;
|
||||
|
||||
auto& sock_p2ps = reinterpret_cast<lv2_socket_p2ps&>(*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<char*>(p2p_recv_data.data()), ::size32(p2p_recv_data), 0, reinterpret_cast<struct sockaddr*>(&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<s32>(sizeof(u16)))
|
||||
{
|
||||
sys_net.error("Received badly formed packet on P2P port(no vport)!");
|
||||
return true;
|
||||
}
|
||||
|
||||
u16 dst_vport = reinterpret_cast<le_t<u16>&>(p2p_recv_data[0]);
|
||||
|
||||
if (is_ipv6)
|
||||
{
|
||||
const auto* addr_ipv6 = reinterpret_cast<sockaddr_in6*>(&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<u8> 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<struct sockaddr_in*>(&native_addr)->sin_addr.s_addr;
|
||||
msg.src_port = std::bit_cast<u16, be_t<u16>>(reinterpret_cast<struct sockaddr_in*>(&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<named_thread<signaling_handler>>();
|
||||
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<le_t<u16>*>(p2p_recv_data.data() + sizeof(u16));
|
||||
const u16 vport_flags = *reinterpret_cast<le_t<u16>*>(p2p_recv_data.data() + sizeof(u16) + sizeof(u16));
|
||||
std::vector<u8> 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<be_t<u32>, u32>(reinterpret_cast<struct sockaddr_in*>(&native_addr)->sin_addr.s_addr);
|
||||
p2p_addr.sin_vport = src_vport;
|
||||
p2p_addr.sin_port = std::bit_cast<be_t<u16>, u16>(reinterpret_cast<struct sockaddr_in*>(&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<lv2_socket>(sock_id, [&](lv2_socket& sock)
|
||||
{
|
||||
ensure(sock.get_type() == SYS_NET_SOCK_DGRAM_P2P);
|
||||
auto& sock_p2p = reinterpret_cast<lv2_socket_p2p&>(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<p2ps_encapsulated_tcp*>(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<const le_t<u16>*>(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<struct sockaddr_in*>(&native_addr)->sin_addr.s_addr) | (static_cast<u64>(tcp_header->src_port) << 48) | (static_cast<u64>(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<char*>(packet.data()), ::size32(packet), reinterpret_cast<const sockaddr_in*>(&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;
|
||||
}
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <set>
|
||||
|
||||
#include "lv2_socket_p2ps.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <WS2tcpip.h>
|
||||
#else
|
||||
#ifdef __clang__
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wold-style-cast"
|
||||
#endif
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#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<u8> data;
|
||||
};
|
||||
|
||||
namespace sys_net_helpers
|
||||
{
|
||||
bool all_reusable(const std::set<s32>& 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<u16, std::set<s32>> bound_p2p_vports{};
|
||||
// For STREAM_P2P sockets (vport, sock_ids)
|
||||
std::map<u16, std::set<s32>> 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<u64, s32> bound_p2p_streams{};
|
||||
// Current free port index
|
||||
u16 binding_port = 30000;
|
||||
|
||||
// Queued messages from RPCN
|
||||
shared_mutex s_rpcn_mutex;
|
||||
std::vector<std::vector<u8>> rpcn_msgs{};
|
||||
// Queued signaling messages
|
||||
shared_mutex s_sign_mutex;
|
||||
std::vector<signaling_message> sign_msgs{};
|
||||
|
||||
std::array<u8, 65535> 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();
|
||||
};
|
||||
|
|
@ -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<sys_net_sockaddr_in*>(&sn_addr);
|
||||
*paddr = {};
|
||||
|
||||
paddr->sin_len = sizeof(sys_net_sockaddr_in);
|
||||
paddr->sin_family = SYS_NET_AF_INET;
|
||||
paddr->sin_port = std::bit_cast<be_t<u16>, u16>(reinterpret_cast<const sockaddr_in*>(&native_addr)->sin_port);
|
||||
paddr->sin_addr = std::bit_cast<be_t<u32>, u32>(reinterpret_cast<const sockaddr_in*>(&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<const sys_net_sockaddr_in*>(&sn_addr);
|
||||
|
||||
::sockaddr_in native_addr{};
|
||||
native_addr.sin_family = AF_INET;
|
||||
native_addr.sin_port = std::bit_cast<u16>(psa_in->sin_port);
|
||||
native_addr.sin_addr.s_addr = std::bit_cast<u32>(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<u32, be_t<u32>>(0x7F000001);
|
||||
}
|
||||
#endif
|
||||
|
||||
return native_addr;
|
||||
}
|
||||
|
||||
bool is_ip_public_address(const ::sockaddr_in& addr)
|
||||
{
|
||||
const u8* ip = reinterpret_cast<const u8*>(&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<lv2_socket>([&](u32, lv2_socket& sock)
|
||||
{
|
||||
cleared += sock.clear_queue(&ppu);
|
||||
});
|
||||
|
||||
return cleared;
|
||||
}
|
||||
|
||||
void clear_ppu_to_awake(ppu_thread& ppu)
|
||||
{
|
||||
g_fxo->get<network_context>().del_ppu_to_awake(&ppu);
|
||||
g_fxo->get<p2p_context>().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<pollfd>& fds, unsigned long nfds, int timeout, std::vector<bool>& 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<unsigned long>(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<char*>(&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
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <WS2tcpip.h>
|
||||
#else
|
||||
#ifdef __clang__
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wold-style-cast"
|
||||
#endif
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#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<pollfd>& fds, unsigned long nfds, int timeout, std::vector<bool>& connecting);
|
||||
#endif
|
||||
|
|
@ -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<shared_ptr<lv2_overlay>, 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<lv2_obj>&, bool check_only = false, u64 file_size = 0);
|
||||
extern void ppu_finalize(const ppu_module<lv2_obj>& info, bool force_mem_release = false);
|
||||
|
||||
LOG_CHANNEL(sys_overlay);
|
||||
|
||||
static error_code overlay_load_module(vm::ptr<u32> ovlmid, const std::string& vpath, u64 /*flags*/, vm::ptr<u32> 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<loaded_npdrm_keys>().last_key();
|
||||
|
||||
src = decrypt_self(std::move(src), reinterpret_cast<u8*>(&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<void(void*)> lv2_overlay::load(utils::serial& ar)
|
||||
{
|
||||
const std::string vpath = ar.pop<std::string>();
|
||||
const std::string path = vfs::get(vpath);
|
||||
const s64 offset = ar.pop<s64>();
|
||||
|
||||
sys_overlay.success("lv2_overlay::load(): vpath='%s', path='%s', offset=0x%x", vpath, path, offset);
|
||||
|
||||
shared_ptr<lv2_overlay> ovlm;
|
||||
|
||||
fs::file file{path.substr(0, path.size() - (offset ? fmt::format("_x%x", offset).size() : 0))};
|
||||
|
||||
if (file)
|
||||
{
|
||||
u128 klic = g_fxo->get<loaded_npdrm_keys>().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<u8*>(&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<atomic_ptr<lv2_obj>*>(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<u32> ovlmid, vm::cptr<char> path, u64 flags, vm::ptr<u32> 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<u32> ovlmid, u32 fd, u64 offset, u64 flags, vm::ptr<u32> 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<s64>(offset) < 0)
|
||||
{
|
||||
return CELL_EINVAL;
|
||||
}
|
||||
|
||||
const auto file = idm::get_unlocked<lv2_fs_object, lv2_file>(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<lv2_obj, lv2_overlay>(ovlmid);
|
||||
|
||||
if (!_main)
|
||||
{
|
||||
return CELL_ESRCH;
|
||||
}
|
||||
|
||||
for (auto& seg : _main->segs)
|
||||
{
|
||||
vm::dealloc(seg.addr);
|
||||
}
|
||||
|
||||
ppu_finalize(*_main);
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "Emu/Cell/PPUAnalyser.h"
|
||||
#include "Emu/Memory/vm_ptr.h"
|
||||
#include "sys_sync.h"
|
||||
|
||||
struct lv2_overlay final : ppu_module<lv2_obj>
|
||||
{
|
||||
static const u32 id_base = 0x25000000;
|
||||
|
||||
u32 entry{};
|
||||
u32 seg0_code_end{};
|
||||
|
||||
lv2_overlay() = default;
|
||||
lv2_overlay(utils::serial&) {}
|
||||
static std::function<void(void*)> load(utils::serial& ar);
|
||||
void save(utils::serial& ar);
|
||||
};
|
||||
|
||||
error_code sys_overlay_load_module(vm::ptr<u32> ovlmid, vm::cptr<char> path, u64 flags, vm::ptr<u32> entry);
|
||||
error_code sys_overlay_load_module_by_fd(vm::ptr<u32> ovlmid, u32 fd, u64 offset, u64 flags, vm::ptr<u32> entry);
|
||||
error_code sys_overlay_unload_module(u32 ovlmid);
|
||||
// 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);//
|
||||
// error_code sys_overlay_get_sdk_version(); //2 params
|
||||
// error_code sys_overlay_get_module_dbg_info(); //3 params?
|
||||
|
||||
// error_code _sys_prx_load_module(vm::ps3::cptr<char> path, u64 flags, vm::ps3::ptr<sys_prx_load_module_option_t> pOpt);
|
||||
|
|
@ -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 <thread>
|
||||
|
||||
LOG_CHANNEL(sys_ppu_thread);
|
||||
|
||||
// Simple structure to cleanup previous thread, because can't remove its own thread
|
||||
struct ppu_thread_cleaner
|
||||
{
|
||||
shared_ptr<named_thread<ppu_thread>> old;
|
||||
|
||||
shared_ptr<named_thread<ppu_thread>> clean(shared_ptr<named_thread<ppu_thread>> 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<named_thread<ppu_thread>*>(old.get()) = state;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
void ppu_thread_exit(ppu_thread& ppu, ppu_opcode_t, be_t<u32>*, 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<lv2_memory_container>())
|
||||
{
|
||||
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<named_thread<ppu_thread>> 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<named_thread<ppu_thread>>(static_cast<u32>(old_status)));
|
||||
}
|
||||
|
||||
if (old_status != ppu_join_status::joinable)
|
||||
{
|
||||
// Remove self ID from IDM, move owning ptr
|
||||
old_ppu = g_fxo->get<ppu_thread_cleaner>().clean(idm::withdraw<named_thread<ppu_thread>>(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<u64> 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<named_thread<ppu_thread>>(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<void>(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<named_thread<ppu_thread>>(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<named_thread<ppu_thread>>(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<s32> 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<named_thread<ppu_thread>>(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<s32> 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<named_thread<ppu_thread>>(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<sys_ppu_thread_stack_t> 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<named_thread<ppu_thread>>(thread_id, [](named_thread<ppu_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<u64> thread_id, vm::ptr<ppu_thread_param_t> param, u64 arg, u64 unk, s32 prio, u32 _stacksz, u64 flags, vm::cptr<char> 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<u32>(std::max<u32>(_stacksz, 4096), 4096);
|
||||
|
||||
auto& dct = g_fxo->get<lv2_memory_container>();
|
||||
|
||||
// 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 <named_thread<ppu_thread>>([&]()
|
||||
{
|
||||
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<named_thread<ppu_thread>>(p, ppu_name, prio, 1 - static_cast<int>(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<named_thread<ppu_thread>>(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<char> 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<named_thread<ppu_thread>>(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::string>(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<named_thread<ppu_thread>>(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<sys_ppu_thread_icontext_t> 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<named_thread<ppu_thread>>(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<page_fault_event_entries>();
|
||||
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;
|
||||
}
|
||||
|
|
@ -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<u32> pst_addr;
|
||||
be_t<u32> pst_size;
|
||||
};
|
||||
|
||||
struct ppu_thread_param_t
|
||||
{
|
||||
vm::bptr<void(u64)> entry;
|
||||
be_t<u32> tls; // vm::bptr<void>
|
||||
};
|
||||
|
||||
struct sys_ppu_thread_icontext_t
|
||||
{
|
||||
be_t<u64> gpr[32];
|
||||
be_t<u32> cr;
|
||||
be_t<u32> rsv1;
|
||||
be_t<u64> xer;
|
||||
be_t<u64> lr;
|
||||
be_t<u64> ctr;
|
||||
be_t<u64> 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<u64> 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<s32> 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<s32> priop);
|
||||
error_code sys_ppu_thread_get_stack_information(ppu_thread& ppu, vm::ptr<sys_ppu_thread_stack_t> 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<u64> thread_id, vm::ptr<ppu_thread_param_t> param, u64 arg, u64 arg4, s32 prio, u32 stacksize, u64 flags, vm::cptr<char> 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<char> 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<sys_ppu_thread_icontext_t> ctxt);
|
||||
|
|
@ -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 <typename T, typename Get>
|
||||
u32 idm_get_count()
|
||||
{
|
||||
return idm::select<T, Get>([&](u32, Get&) {});
|
||||
}
|
||||
|
||||
error_code sys_process_get_number_of_object(u32 object, vm::ptr<u32> 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<lv2_obj, lv2_memory>(); break;
|
||||
case SYS_MUTEX_OBJECT: *nump = idm_get_count<lv2_obj, lv2_mutex>(); break;
|
||||
case SYS_COND_OBJECT: *nump = idm_get_count<lv2_obj, lv2_cond>(); break;
|
||||
case SYS_RWLOCK_OBJECT: *nump = idm_get_count<lv2_obj, lv2_rwlock>(); break;
|
||||
case SYS_INTR_TAG_OBJECT: *nump = idm_get_count<lv2_obj, lv2_int_tag>(); break;
|
||||
case SYS_INTR_SERVICE_HANDLE_OBJECT: *nump = idm_get_count<lv2_obj, lv2_int_serv>(); break;
|
||||
case SYS_EVENT_QUEUE_OBJECT: *nump = idm_get_count<lv2_obj, lv2_event_queue>(); break;
|
||||
case SYS_EVENT_PORT_OBJECT: *nump = idm_get_count<lv2_obj, lv2_event_port>(); 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<lv2_obj, lv2_spu_image>(); break;
|
||||
case SYS_PRX_OBJECT: *nump = idm_get_count<lv2_obj, lv2_prx>(); 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<lv2_obj, lv2_overlay>(); break;
|
||||
case SYS_LWMUTEX_OBJECT: *nump = idm_get_count<lv2_obj, lv2_lwmutex>(); break;
|
||||
case SYS_TIMER_OBJECT: *nump = idm_get_count<lv2_obj, lv2_timer>(); break;
|
||||
case SYS_SEMAPHORE_OBJECT: *nump = idm_get_count<lv2_obj, lv2_sema>(); break;
|
||||
case SYS_FS_FD_OBJECT: *nump = idm_get_count<lv2_fs_object, lv2_fs_object>(); break;
|
||||
case SYS_LWCOND_OBJECT: *nump = idm_get_count<lv2_obj, lv2_lwcond>(); break;
|
||||
case SYS_EVENT_FLAG_OBJECT: *nump = idm_get_count<lv2_obj, lv2_event_flag>(); break;
|
||||
|
||||
default:
|
||||
{
|
||||
return CELL_EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
|
||||
#include <set>
|
||||
|
||||
template <typename T, typename Get>
|
||||
void idm_get_set(std::set<u32>& out)
|
||||
{
|
||||
idm::select<T, Get>([&](u32 id, Get&)
|
||||
{
|
||||
out.emplace(id);
|
||||
});
|
||||
}
|
||||
|
||||
static error_code process_get_id(u32 object, vm::ptr<u32> buffer, u32 size, vm::ptr<u32> set_size)
|
||||
{
|
||||
std::set<u32> objects;
|
||||
|
||||
switch (object)
|
||||
{
|
||||
case SYS_MEM_OBJECT: idm_get_set<lv2_obj, lv2_memory>(objects); break;
|
||||
case SYS_MUTEX_OBJECT: idm_get_set<lv2_obj, lv2_mutex>(objects); break;
|
||||
case SYS_COND_OBJECT: idm_get_set<lv2_obj, lv2_cond>(objects); break;
|
||||
case SYS_RWLOCK_OBJECT: idm_get_set<lv2_obj, lv2_rwlock>(objects); break;
|
||||
case SYS_INTR_TAG_OBJECT: idm_get_set<lv2_obj, lv2_int_tag>(objects); break;
|
||||
case SYS_INTR_SERVICE_HANDLE_OBJECT: idm_get_set<lv2_obj, lv2_int_serv>(objects); break;
|
||||
case SYS_EVENT_QUEUE_OBJECT: idm_get_set<lv2_obj, lv2_event_queue>(objects); break;
|
||||
case SYS_EVENT_PORT_OBJECT: idm_get_set<lv2_obj, lv2_event_port>(objects); break;
|
||||
case SYS_TRACE_OBJECT: fmt::throw_exception("SYS_TRACE_OBJECT");
|
||||
case SYS_SPUIMAGE_OBJECT: idm_get_set<lv2_obj, lv2_spu_image>(objects); break;
|
||||
case SYS_PRX_OBJECT: idm_get_set<lv2_obj, lv2_prx>(objects); break;
|
||||
case SYS_OVERLAY_OBJECT: idm_get_set<lv2_obj, lv2_overlay>(objects); break;
|
||||
case SYS_LWMUTEX_OBJECT: idm_get_set<lv2_obj, lv2_lwmutex>(objects); break;
|
||||
case SYS_TIMER_OBJECT: idm_get_set<lv2_obj, lv2_timer>(objects); break;
|
||||
case SYS_SEMAPHORE_OBJECT: idm_get_set<lv2_obj, lv2_sema>(objects); break;
|
||||
case SYS_FS_FD_OBJECT: idm_get_set<lv2_fs_object, lv2_fs_object>(objects); break;
|
||||
case SYS_LWCOND_OBJECT: idm_get_set<lv2_obj, lv2_lwcond>(objects); break;
|
||||
case SYS_EVENT_FLAG_OBJECT: idm_get_set<lv2_obj, lv2_event_flag>(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<s32>(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<u32> buffer, u32 size, vm::ptr<u32> 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<u32> buffer, u32 size, vm::ptr<u32> 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>(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<char> 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<usz>(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<s32> 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<u32> 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<sys_exit2_param> 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<std::string> argv;
|
||||
std::vector<std::string> 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<u8> 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<std::string>& argv, std::vector<std::string>& envp, std::vector<u8>& 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<loaded_npdrm_keys>().last_key();
|
||||
|
||||
using namespace id_manager;
|
||||
|
||||
shared_ptr<utils::serial> idm_capture = make_shared<utils::serial>();
|
||||
|
||||
if (!is_real_reboot)
|
||||
{
|
||||
reader_lock rlock{id_manager::g_mutex};
|
||||
g_fxo->get<id_map<lv2_memory_container>>().save(*idm_capture);
|
||||
stx::serial_breathe_and_tag(*idm_capture, "id_map<lv2_memory_container>", false);
|
||||
}
|
||||
|
||||
idm_capture->set_reading_state();
|
||||
|
||||
auto func = [is_real_reboot, old_size = g_fxo->get<lv2_memory_container>().size, idm_capture](u32 sdk_suggested_mem) mutable
|
||||
{
|
||||
if (is_real_reboot)
|
||||
{
|
||||
// Do not save containers on actual reboot
|
||||
ensure(g_fxo->init<id_map<lv2_memory_container>>());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Save LV2 memory containers
|
||||
ensure(g_fxo->init<id_map<lv2_memory_container>>(*idm_capture));
|
||||
}
|
||||
|
||||
// Empty the containers, accumulate their total size
|
||||
u32 total_size = 0;
|
||||
idm::select<lv2_memory_container>([&](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<lv2_memory_container>(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<u32> pid, u32 primary_prio, u64 flags, vm::ptr<void> stack, u32 stack_size, u32 mem_id, vm::ptr<void> param_sfo, vm::ptr<void> 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;
|
||||
}
|
||||
|
|
@ -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<u64> x0; // 0x85
|
||||
be_t<u64> this_size; // 0x30
|
||||
be_t<u64> next_size;
|
||||
be_t<s64> prio;
|
||||
be_t<u64> flags;
|
||||
vm::bpptr<char, u64, u64> 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<std::string>& argv, std::vector<std::string>& envp, std::vector<u8>& 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<u32> nump);
|
||||
error_code sys_process_get_id(u32 object, vm::ptr<u32> buffer, u32 size, vm::ptr<u32> set_size);
|
||||
error_code sys_process_get_id2(u32 object, vm::ptr<u32> buffer, u32 size, vm::ptr<u32> set_size);
|
||||
error_code _sys_process_get_paramsfo(vm::ptr<char> buffer);
|
||||
error_code sys_process_get_sdk_version(u32 pid, vm::ptr<s32> 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<u32> 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<sys_exit2_param> arg, u32 arg_size, u32 arg4);
|
||||
void sys_process_exit3(ppu_thread& ppu, s32 status);
|
||||
error_code sys_process_spawns_a_self2(vm::ptr<u32> pid, u32 primary_prio, u64 flags, vm::ptr<void> stack, u32 stack_size, u32 mem_id, vm::ptr<void> param_sfo, vm::ptr<void> dbg_data);
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -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<u64> size;
|
||||
vm::ptr<void> base;
|
||||
};
|
||||
|
||||
struct sys_prx_load_module_option_t
|
||||
{
|
||||
be_t<u64> size;
|
||||
vm::bptr<void> base_addr;
|
||||
};
|
||||
|
||||
struct sys_prx_segment_info_t
|
||||
{
|
||||
be_t<u64> base;
|
||||
be_t<u64> filesz;
|
||||
be_t<u64> memsz;
|
||||
be_t<u64> index;
|
||||
be_t<u64> type;
|
||||
};
|
||||
|
||||
struct sys_prx_module_info_t
|
||||
{
|
||||
be_t<u64> size; // 0
|
||||
char name[30]; // 8
|
||||
char version[2]; // 0x26
|
||||
be_t<u32> modattribute; // 0x28
|
||||
be_t<u32> start_entry; // 0x2c
|
||||
be_t<u32> stop_entry; // 0x30
|
||||
be_t<u32> all_segments_num; // 0x34
|
||||
vm::bptr<char> filename; // 0x38
|
||||
be_t<u32> filename_size; // 0x3c
|
||||
vm::bptr<sys_prx_segment_info_t> segments; // 0x40
|
||||
be_t<u32> segments_num; // 0x44
|
||||
};
|
||||
|
||||
struct sys_prx_module_info_v2_t : sys_prx_module_info_t
|
||||
{
|
||||
be_t<u32> exports_addr; // 0x48
|
||||
be_t<u32> exports_size; // 0x4C
|
||||
be_t<u32> imports_addr; // 0x50
|
||||
be_t<u32> imports_size; // 0x54
|
||||
};
|
||||
|
||||
struct sys_prx_module_info_option_t
|
||||
{
|
||||
be_t<u64> size; // 0x10
|
||||
union
|
||||
{
|
||||
vm::bptr<sys_prx_module_info_t> info;
|
||||
vm::bptr<sys_prx_module_info_v2_t> info_v2;
|
||||
};
|
||||
};
|
||||
|
||||
struct sys_prx_start_module_option_t
|
||||
{
|
||||
be_t<u64> size;
|
||||
};
|
||||
|
||||
struct sys_prx_stop_module_option_t
|
||||
{
|
||||
be_t<u64> size;
|
||||
};
|
||||
|
||||
struct sys_prx_start_stop_module_option_t
|
||||
{
|
||||
be_t<u64> size;
|
||||
be_t<u64> cmd;
|
||||
vm::bptr<s32(u32 argc, vm::ptr<void> argv), u64> entry;
|
||||
be_t<u64> res;
|
||||
vm::bptr<s32(vm::ptr<s32(u32, vm::ptr<void>), u64>, u32 argc, vm::ptr<void> argv), u64> entry2;
|
||||
};
|
||||
|
||||
struct sys_prx_unload_module_option_t
|
||||
{
|
||||
be_t<u64> size;
|
||||
};
|
||||
|
||||
struct sys_prx_get_module_list_t
|
||||
{
|
||||
be_t<u64> size;
|
||||
be_t<u32> max;
|
||||
be_t<u32> count;
|
||||
vm::bptr<u32> idlist;
|
||||
};
|
||||
|
||||
struct sys_prx_get_module_list_option_t
|
||||
{
|
||||
be_t<u64> size; // 0x20
|
||||
be_t<u32> pad;
|
||||
be_t<u32> max;
|
||||
be_t<u32> count;
|
||||
vm::bptr<u32> idlist;
|
||||
be_t<u32> unk; // 0
|
||||
};
|
||||
|
||||
struct sys_prx_register_module_0x20_t
|
||||
{
|
||||
be_t<u64> size; // 0x0
|
||||
be_t<u32> toc; // 0x8
|
||||
be_t<u32> toc_size; // 0xC
|
||||
vm::bptr<void> stubs_ea; // 0x10
|
||||
be_t<u32> stubs_size; // 0x14
|
||||
vm::bptr<void> error_handler; // 0x18
|
||||
char pad[4]; // 0x1C
|
||||
};
|
||||
|
||||
struct sys_prx_register_module_0x30_type_1_t
|
||||
{
|
||||
be_t<u64> size; // 0x0
|
||||
be_t<u64> type; // 0x8
|
||||
be_t<u32> unk3; // 0x10
|
||||
be_t<u32> unk4; // 0x14
|
||||
vm::bptr<void> lib_entries_ea; // 0x18
|
||||
be_t<u32> lib_entries_size; // 0x1C
|
||||
vm::bptr<void> lib_stub_ea; // 0x20
|
||||
be_t<u32> lib_stub_size; // 0x24
|
||||
vm::bptr<void> 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<lv2_obj>
|
||||
{
|
||||
static const u32 id_base = 0x23000000;
|
||||
|
||||
atomic_t<u32> state = PRX_STATE_INITIALIZED;
|
||||
shared_mutex mutex;
|
||||
|
||||
std::unordered_map<u32, u32> specials;
|
||||
|
||||
vm::ptr<s32(u32 argc, vm::ptr<void> argv)> start = vm::null;
|
||||
vm::ptr<s32(u32 argc, vm::ptr<void> argv)> stop = vm::null;
|
||||
vm::ptr<s32(u64 callback, u64 argc, vm::ptr<void, u64> argv)> prologue = vm::null;
|
||||
vm::ptr<s32(u64 callback, u64 argc, vm::ptr<void, u64> argv)> epilogue = vm::null;
|
||||
vm::ptr<s32()> exit = vm::null;
|
||||
|
||||
char module_info_name[28]{};
|
||||
u8 module_info_version[2]{};
|
||||
be_t<u16> module_info_attributes{};
|
||||
|
||||
u32 imports_start = umax;
|
||||
u32 imports_end = 0;
|
||||
|
||||
u32 exports_start = umax;
|
||||
u32 exports_end = 0;
|
||||
|
||||
std::basic_string<char> m_loaded_flags;
|
||||
std::basic_string<char> 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<void(void*)> 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<sys_prx_load_module_option_t> 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<sys_prx_load_module_option_t> pOpt);
|
||||
error_code _sys_prx_load_module_list(ppu_thread& ppu, s32 count, vm::cpptr<char, u32, u64> path_list, u64 flags, vm::ptr<sys_prx_load_module_option_t> pOpt, vm::ptr<u32> id_list);
|
||||
error_code _sys_prx_load_module_list_on_memcontainer(ppu_thread& ppu, s32 count, vm::cpptr<char, u32, u64> path_list, u32 mem_ct, u64 flags, vm::ptr<sys_prx_load_module_option_t> pOpt, vm::ptr<u32> id_list);
|
||||
error_code _sys_prx_load_module_on_memcontainer(ppu_thread& ppu, vm::cptr<char> path, u32 mem_ct, u64 flags, vm::ptr<sys_prx_load_module_option_t> pOpt);
|
||||
error_code _sys_prx_load_module(ppu_thread& ppu, vm::cptr<char> path, u64 flags, vm::ptr<sys_prx_load_module_option_t> pOpt);
|
||||
error_code _sys_prx_start_module(ppu_thread& ppu, u32 id, u64 flags, vm::ptr<sys_prx_start_stop_module_option_t> pOpt);
|
||||
error_code _sys_prx_stop_module(ppu_thread& ppu, u32 id, u64 flags, vm::ptr<sys_prx_start_stop_module_option_t> pOpt);
|
||||
error_code _sys_prx_unload_module(ppu_thread& ppu, u32 id, u64 flags, vm::ptr<sys_prx_unload_module_option_t> pOpt);
|
||||
error_code _sys_prx_register_module(ppu_thread& ppu, vm::cptr<char> name, vm::ptr<void> opt);
|
||||
error_code _sys_prx_query_module(ppu_thread& ppu);
|
||||
error_code _sys_prx_register_library(ppu_thread& ppu, vm::ptr<void> library);
|
||||
error_code _sys_prx_unregister_library(ppu_thread& ppu, vm::ptr<void> 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<sys_prx_get_module_list_option_t> pInfo);
|
||||
error_code _sys_prx_get_module_info(ppu_thread& ppu, u32 id, u64 flags, vm::ptr<sys_prx_module_info_option_t> pOpt);
|
||||
error_code _sys_prx_get_module_id_by_name(ppu_thread& ppu, vm::cptr<char> name, u64 flags, vm::ptr<sys_prx_get_module_id_by_name_option_t> 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);
|
||||
|
|
@ -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<sys_rsx_error>::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<RsxDriverInfo>(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<u32> mem_handle, vm::ptr<u64> 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<RsxDriverInfo>(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<u32> context_id, vm::ptr<u64> lpar_dma_control, vm::ptr<u64> lpar_driver_info, vm::ptr<u64> 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<RsxReports>(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<RsxDriverInfo>(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<u32>(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<RsxDmaControl>(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<sys_event_queue_attribute_t, vm::page_allocator<>> 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<rsx::vblank_thread>() = thread_state::finished;
|
||||
|
||||
const u32 queue_id = vm::_ptr<RsxDriverInfo>(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<true> 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>(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<RsxDriverInfo>(render->driver_info);
|
||||
switch (package_id)
|
||||
{
|
||||
case 0x001: // FIFO
|
||||
{
|
||||
const u64 get = static_cast<u32>(a3);
|
||||
const u64 put = static_cast<u32>(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<u32>(a4);
|
||||
driverInfo.head[a3].flipFlags |= 0x40000000 | (1 << a4);
|
||||
|
||||
render->on_frame_end(static_cast<u32>(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<u32>(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<u32>& flipStatus)
|
||||
{
|
||||
flipStatus = (flipStatus & static_cast<u32>(a4)) | static_cast<u32>(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<u32>(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<u32>(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<atomic_t<u128>>(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<ppu_thread>())
|
||||
{
|
||||
// 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<u64>& time)
|
||||
{
|
||||
a4 = std::max<u64>(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<u32>(current_time);
|
||||
driverInfo.head[a3].lastVTimeHigh = static_cast<u32>(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<u32>(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<u64> dev_addr, vm::ptr<u64> 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;
|
||||
}
|
||||
|
|
@ -1,142 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "Emu/Memory/vm_ptr.h"
|
||||
#include "Emu/Cell/ErrorCodes.h"
|
||||
|
||||
class cpu_thread;
|
||||
|
||||
struct RsxDriverInfo
|
||||
{
|
||||
be_t<u32> version_driver; // 0x0
|
||||
be_t<u32> version_gpu; // 0x4
|
||||
be_t<u32> memory_size; // 0x8
|
||||
be_t<u32> hardware_channel; // 0xC
|
||||
be_t<u32> nvcore_frequency; // 0x10
|
||||
be_t<u32> memory_frequency; // 0x14
|
||||
be_t<u32> unk1[4]; // 0x18 - 0x24
|
||||
be_t<u32> unk2; // 0x28 -- pgraph stuff
|
||||
be_t<u32> reportsNotifyOffset; // 0x2C offset to notify memory
|
||||
be_t<u32> reportsOffset; // 0x30 offset to reports memory
|
||||
be_t<u32> reportsReportOffset; // 0x34 offset to reports in reports memory
|
||||
be_t<u32> unk3[6]; // 0x38-0x54
|
||||
be_t<u32> systemModeFlags; // 0x54
|
||||
u8 unk4[0x1064]; // 0x10B8
|
||||
|
||||
struct Head
|
||||
{
|
||||
be_t<u64> lastFlipTime; // 0x0 last flip time
|
||||
atomic_be_t<u32> flipFlags; // 0x8 flags to handle flip/queue
|
||||
be_t<u32> offset; // 0xC
|
||||
be_t<u32> flipBufferId; // 0x10
|
||||
be_t<u32> 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<u32> unk3; // 0x18
|
||||
be_t<u32> lastVTimeLow; // 0x1C last time for first vhandler freq (low 32-bits)
|
||||
atomic_be_t<u64> lastSecondVTime; // 0x20 last time for second vhandler freq
|
||||
be_t<u64> unk4; // 0x28
|
||||
atomic_be_t<u64> vBlankCount; // 0x30
|
||||
be_t<u32> unk; // 0x38 possible u32, 'flip field', top/bottom for interlaced
|
||||
be_t<u32> lastVTimeHigh; // 0x3C last time for first vhandler freq (high 32-bits)
|
||||
} head[8]; // size = 0x40, 0x200
|
||||
|
||||
be_t<u32> unk7; // 0x12B8
|
||||
be_t<u32> unk8; // 0x12BC
|
||||
atomic_be_t<u32> handlers; // 0x12C0 -- flags showing which handlers are set
|
||||
be_t<u32> unk9; // 0x12C4
|
||||
be_t<u32> unk10; // 0x12C8
|
||||
be_t<u32> userCmdParam; // 0x12CC
|
||||
be_t<u32> handler_queue; // 0x12D0
|
||||
be_t<u32> unk11; // 0x12D4
|
||||
be_t<u32> unk12; // 0x12D8
|
||||
be_t<u32> unk13; // 0x12DC
|
||||
be_t<u32> unk14; // 0x12E0
|
||||
be_t<u32> unk15; // 0x12E4
|
||||
be_t<u32> unk16; // 0x12E8
|
||||
be_t<u32> unk17; // 0x12F0
|
||||
be_t<u32> 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<u32> put;
|
||||
atomic_be_t<u32> get;
|
||||
atomic_be_t<u32> ref;
|
||||
be_t<u32> unk[2];
|
||||
be_t<u32> unk1;
|
||||
};
|
||||
|
||||
struct RsxSemaphore
|
||||
{
|
||||
atomic_be_t<u32> val;
|
||||
};
|
||||
|
||||
struct alignas(16) RsxNotify
|
||||
{
|
||||
be_t<u64> timestamp;
|
||||
be_t<u64> zero;
|
||||
};
|
||||
|
||||
struct alignas(16) RsxReport
|
||||
{
|
||||
be_t<u64> timestamp;
|
||||
be_t<u32> val;
|
||||
be_t<u32> pad;
|
||||
};
|
||||
|
||||
struct RsxReports
|
||||
{
|
||||
RsxSemaphore semaphore[1024];
|
||||
RsxNotify notify[64];
|
||||
RsxReport report[2048];
|
||||
};
|
||||
|
||||
struct RsxDisplayInfo
|
||||
{
|
||||
be_t<u32> offset{0};
|
||||
be_t<u32> pitch{0};
|
||||
be_t<u32> width{0};
|
||||
be_t<u32> 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<u32> mem_handle, vm::ptr<u64> 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<u32> context_id, vm::ptr<u64> lpar_dma_control, vm::ptr<u64> lpar_driver_info, vm::ptr<u64> 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<u64> dev_addr, vm::ptr<u64> 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);
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -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 <sys/param.h>
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#elif defined(BSD) || defined(__APPLE__)
|
||||
#include <sys/event.h>
|
||||
#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<u32> valid{};
|
||||
be_t<u32> unk1{};
|
||||
be_t<u64> audio_blk_idx{};
|
||||
be_t<u64> timestamp{};
|
||||
be_t<u32> buf_addr{};
|
||||
be_t<u32> dma_addr{};
|
||||
};
|
||||
|
||||
be_t<u32> active{};
|
||||
be_t<u32> unk2{};
|
||||
be_t<s32> read_idx{};
|
||||
be_t<u32> write_idx{};
|
||||
be_t<s32> rw_max_idx{};
|
||||
be_t<s32> queue_notify_idx{};
|
||||
be_t<s32> queue_notify_step{};
|
||||
be_t<u32> unk6{};
|
||||
be_t<u32> dma_silence_addr{};
|
||||
be_t<u32> unk7{};
|
||||
be_t<u64> next_blk_idx{};
|
||||
|
||||
entry_t entries[16]{};
|
||||
};
|
||||
|
||||
struct uf_event_t
|
||||
{
|
||||
be_t<u64> unk1{};
|
||||
be_t<u32> uf_event_cnt{};
|
||||
u8 unk2[244]{};
|
||||
};
|
||||
|
||||
struct ctrl_t
|
||||
{
|
||||
ringbuf_t ringbuf[SYS_RSXAUDIO_PORT_CNT]{};
|
||||
|
||||
be_t<u32> unk1{};
|
||||
be_t<u32> event_queue_1_id{};
|
||||
u8 unk2[16]{};
|
||||
be_t<u32> event_queue_2_id{};
|
||||
be_t<u32> spdif_ch0_channel_data_lo{};
|
||||
be_t<u32> spdif_ch0_channel_data_hi{};
|
||||
be_t<u32> spdif_ch0_channel_data_tx_cycles{};
|
||||
be_t<u32> unk3{};
|
||||
be_t<u32> event_queue_3_id{};
|
||||
be_t<u32> spdif_ch1_channel_data_lo{};
|
||||
be_t<u32> spdif_ch1_channel_data_hi{};
|
||||
be_t<u32> spdif_ch1_channel_data_tx_cycles{};
|
||||
be_t<u32> unk4{};
|
||||
be_t<u32> intr_thread_prio{};
|
||||
be_t<u32> 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<shared_ptr<lv2_event_queue>, SYS_RSXAUDIO_PORT_CNT> event_queue{};
|
||||
|
||||
// lv2 uses port memory addresses for their names
|
||||
static constexpr std::array<u64, SYS_RSXAUDIO_PORT_CNT> 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<rsxaudio_shmem*>(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<void()>& 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<vtimer, VTIMER_MAX> 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<u8, 6> 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<u8, SYS_RSXAUDIO_SERIAL_MAX_CH> 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<u8, 5> info_frame{}; // TODO: check chstat and info_frame for info on audio layout, add default values
|
||||
std::array<u8, 5> 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<RsxaudioPort, SYS_RSXAUDIO_AVPORT_CNT> 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<f32, SYS_RSXAUDIO_STREAM_SAMPLE_CNT * 2>;
|
||||
|
||||
class rsxaudio_data_container
|
||||
{
|
||||
public:
|
||||
struct buf_t
|
||||
{
|
||||
std::array<ra_stream_blk_t, SYS_RSXAUDIO_SERIAL_MAX_CH> serial{};
|
||||
std::array<ra_stream_blk_t, SYS_RSXAUDIO_SPDIF_MAX_CH> spdif[SYS_RSXAUDIO_SPDIF_CNT]{};
|
||||
};
|
||||
|
||||
using data_blk_t = std::array<f32, SYS_RSXAUDIO_STREAM_SAMPLE_CNT * SYS_RSXAUDIO_SERIAL_MAX_CH * 2>;
|
||||
|
||||
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<bool, 5> 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 <usz output_ch_cnt, usz input_ch_cnt>
|
||||
requires(output_ch_cnt > 0 && output_ch_cnt <= 8 && input_ch_cnt > 0)
|
||||
constexpr void mix(const std::array<u8, 8>& ch_map, RsxaudioSampleSize sample_size, const std::array<ra_stream_blk_t, input_ch_cnt>& input_channels, data_blk_t& data_out)
|
||||
{
|
||||
const ra_stream_blk_t silent_channel{};
|
||||
|
||||
// Build final map
|
||||
std::array<const ra_stream_blk_t*, output_ch_cnt> 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<port_config, SYS_RSXAUDIO_AVPORT_CNT>& 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_config, SYS_RSXAUDIO_AVPORT_CNT> 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<u8>(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<u8> thread_tmp_buf{};
|
||||
std::vector<f32> callback_tmp_buf{};
|
||||
bool use_aux_ringbuf = false;
|
||||
shared_mutex ringbuf_mutex{};
|
||||
|
||||
std::shared_ptr<AudioBackend> backend{};
|
||||
backend_config backend_current_cfg{{}, new_emu_cfg.avport};
|
||||
atomic_t<callback_config> 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<bool> rsxaudio_ctx_allocated = false;
|
||||
|
||||
shared_mutex rsxaudio_obj_upd_m{};
|
||||
shared_ptr<lv2_rsxaudio> rsxaudio_obj_ptr{};
|
||||
|
||||
void operator()();
|
||||
rsxaudio_data_thread& operator=(thread_state state);
|
||||
|
||||
rsxaudio_data_thread();
|
||||
|
||||
void update_hw_param(std::function<void(rsxaudio_hw_param_t&)> 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<rsxaudio_hw_param_t> hw_param_ts{std::make_shared<universal_pool>(), std::make_shared<rsxaudio_hw_param_t>()};
|
||||
rsxaudio_periodic_tmr timer{};
|
||||
|
||||
void advance_all_timers();
|
||||
void extract_audio_data();
|
||||
static std::pair<bool /*data_present*/, void* /*addr*/> 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<rsxaudio_backend_thread>;
|
||||
using rsx_audio_data = named_thread<rsxaudio_data_thread>;
|
||||
|
||||
// SysCalls
|
||||
|
||||
error_code sys_rsxaudio_initialize(vm::ptr<u32> handle);
|
||||
error_code sys_rsxaudio_finalize(u32 handle);
|
||||
error_code sys_rsxaudio_import_shared_memory(u32 handle, vm::ptr<u64> addr);
|
||||
error_code sys_rsxaudio_unimport_shared_memory(u32 handle, vm::ptr<u64> 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<u64> out);
|
||||
|
|
@ -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<void(void*)> lv2_rwlock::load(utils::serial& ar)
|
||||
{
|
||||
return load_func(make_shared<lv2_rwlock>(exact_t<utils::serial&>(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<u32> rw_lock_id, vm::ptr<sys_rwlock_attribute_t> 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<lv2_rwlock>(_attr.pshared, ipc_key, _attr.flags, [&]
|
||||
{
|
||||
return make_shared<lv2_rwlock>(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<lv2_obj, lv2_rwlock>(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<lv2_obj, lv2_rwlock>(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<ppu_thread*>::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<lv2_obj, lv2_rwlock>(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<lv2_obj, lv2_rwlock>(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<ppu_thread>(rwlock->wq, rwlock->protocol))
|
||||
{
|
||||
if (static_cast<ppu_thread*>(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<lv2_obj, lv2_rwlock>(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<ppu_thread>(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<lv2_obj, lv2_rwlock>(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<lv2_obj, lv2_rwlock>(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<ppu_thread>(rwlock->wq, rwlock->protocol))
|
||||
{
|
||||
if (static_cast<ppu_thread*>(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<ppu_thread>(rwlock->rq, SYS_SYNC_FIFO))
|
||||
{
|
||||
size++;
|
||||
rwlock->append(cpu);
|
||||
}
|
||||
|
||||
rwlock->owner.release(-2 * static_cast<s64>(size));
|
||||
lv2_obj::awake_all();
|
||||
}
|
||||
else
|
||||
{
|
||||
rwlock->owner = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "sys_sync.h"
|
||||
|
||||
#include "Emu/Memory/vm_ptr.h"
|
||||
|
||||
struct sys_rwlock_attribute_t
|
||||
{
|
||||
be_t<u32> protocol;
|
||||
be_t<u32> pshared;
|
||||
be_t<u64> ipc_key;
|
||||
be_t<s32> flags;
|
||||
be_t<u32> pad;
|
||||
|
||||
union
|
||||
{
|
||||
nse_t<u64, 1> 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<s64> owner{0};
|
||||
ppu_thread* rq{};
|
||||
ppu_thread* wq{};
|
||||
|
||||
lv2_rwlock(u32 protocol, u64 key, u64 name) noexcept
|
||||
: protocol{static_cast<u8>(protocol)}, key(key), name(name)
|
||||
{
|
||||
}
|
||||
|
||||
lv2_rwlock(utils::serial& ar);
|
||||
static std::function<void(void*)> load(utils::serial& ar);
|
||||
void save(utils::serial& ar);
|
||||
};
|
||||
|
||||
// Aux
|
||||
class ppu_thread;
|
||||
|
||||
// Syscalls
|
||||
|
||||
error_code sys_rwlock_create(ppu_thread& ppu, vm::ptr<u32> rw_lock_id, vm::ptr<sys_rwlock_attribute_t> 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;
|
||||
|
|
@ -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<void(void*)> lv2_sema::load(utils::serial& ar)
|
||||
{
|
||||
return load_func(make_shared<lv2_sema>(exact_t<utils::serial&>(ar)));
|
||||
}
|
||||
|
||||
void lv2_sema::save(utils::serial& ar)
|
||||
{
|
||||
USING_SERIALIZATION_VERSION(lv2_sync);
|
||||
ar(protocol, key, name, max, std::max<s32>(+val, 0));
|
||||
}
|
||||
|
||||
error_code sys_semaphore_create(ppu_thread& ppu, vm::ptr<u32> sem_id, vm::ptr<sys_semaphore_attribute_t> 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<lv2_sema>(_attr.pshared, ipc_key, _attr.flags, [&]
|
||||
{
|
||||
return make_shared<lv2_sema>(protocol, ipc_key, _attr.name_u64, max_val, initial_val);
|
||||
}))
|
||||
{
|
||||
return error;
|
||||
}
|
||||
|
||||
static_cast<void>(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<lv2_obj, lv2_sema>(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<lv2_obj, lv2_sema>(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<lv2_obj, lv2_sema>(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<lv2_obj, lv2_sema>(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<ppu_thread*>(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<s32>(-std::min<s32>(val, 0), count);
|
||||
|
||||
for (s32 i = 0; i < to_awake; i++)
|
||||
{
|
||||
sem->append((ensure(sem->schedule<ppu_thread>(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<s32> 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<lv2_obj, lv2_sema>(sem_id, [](lv2_sema& sema)
|
||||
{
|
||||
return std::max<s32>(0, sema.val);
|
||||
});
|
||||
|
||||
if (!sema)
|
||||
{
|
||||
return CELL_ESRCH;
|
||||
}
|
||||
|
||||
if (!count)
|
||||
{
|
||||
return CELL_EFAULT;
|
||||
}
|
||||
|
||||
static_cast<void>(ppu.test_stopped());
|
||||
|
||||
*count = sema.ret;
|
||||
return CELL_OK;
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "sys_sync.h"
|
||||
|
||||
#include "Emu/Memory/vm_ptr.h"
|
||||
|
||||
struct sys_semaphore_attribute_t
|
||||
{
|
||||
be_t<u32> protocol;
|
||||
be_t<u32> pshared;
|
||||
be_t<u64> ipc_key;
|
||||
be_t<s32> flags;
|
||||
be_t<u32> pad;
|
||||
|
||||
union
|
||||
{
|
||||
nse_t<u64, 1> 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<s32> val;
|
||||
ppu_thread* sq{};
|
||||
|
||||
lv2_sema(u32 protocol, u64 key, u64 name, s32 max, s32 value) noexcept
|
||||
: protocol{static_cast<u8>(protocol)}, key(key), name(name), max(max), val(value)
|
||||
{
|
||||
}
|
||||
|
||||
lv2_sema(utils::serial& ar);
|
||||
static std::function<void(void*)> load(utils::serial& ar);
|
||||
void save(utils::serial& ar);
|
||||
};
|
||||
|
||||
// Aux
|
||||
class ppu_thread;
|
||||
|
||||
// Syscalls
|
||||
|
||||
error_code sys_semaphore_create(ppu_thread& ppu, vm::ptr<u32> sem_id, vm::ptr<sys_semaphore_attribute_t> 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<s32> count);
|
||||
|
|
@ -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<u8> a, vm::ptr<u8> b, vm::ptr<u32> c, vm::ptr<u64> 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<u64> a1, vm::ptr<u64> a2, vm::ptr<u64> 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<void> 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;
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "Emu/Memory/vm_ptr.h"
|
||||
#include "Emu/Cell/ErrorCodes.h"
|
||||
|
||||
// SysCalls
|
||||
|
||||
error_code sys_sm_get_ext_event2(vm::ptr<u64> a1, vm::ptr<u64> a2, vm::ptr<u64> a3, u64 a4);
|
||||
error_code sys_sm_shutdown(ppu_thread& ppu, u16 op, vm::ptr<void> param, u64 size);
|
||||
error_code sys_sm_get_params(vm::ptr<u8> a, vm::ptr<u8> b, vm::ptr<u32> c, vm::ptr<u64> 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);
|
||||
constexpr auto sys_sm_ring_buzzer2 = sys_sm_ring_buzzer;
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -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 <span>
|
||||
|
||||
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<u32> nsize; // name length including NULL terminator
|
||||
vm::bcptr<char> name;
|
||||
be_t<s32> type;
|
||||
be_t<u32> 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<char> name;
|
||||
be_t<u32> name_len;
|
||||
be_t<u32> option;
|
||||
};
|
||||
|
||||
struct sys_spu_thread_argument
|
||||
{
|
||||
be_t<u64> arg1;
|
||||
be_t<u64> arg2;
|
||||
be_t<u64> arg3;
|
||||
be_t<u64> arg4;
|
||||
};
|
||||
|
||||
struct sys_spu_segment
|
||||
{
|
||||
ENABLE_BITWISE_SERIALIZATION;
|
||||
|
||||
be_t<s32> type; // copy, fill, info
|
||||
be_t<u32> ls; // local storage address
|
||||
be_t<u32> size;
|
||||
|
||||
union
|
||||
{
|
||||
be_t<u32> 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<u32> type; // user, kernel
|
||||
be_t<u32> entry_point; // Note: in kernel mode it's used to store id
|
||||
vm::bptr<sys_spu_segment> segs;
|
||||
be_t<s32> nsegs;
|
||||
|
||||
template <bool CountInfo = true, typename Phdrs>
|
||||
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 <bool WriteInfo = true, typename Phdrs>
|
||||
static s32 fill(vm::ptr<sys_spu_segment> 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<u32>(phdr.p_vaddr);
|
||||
seg->size = static_cast<u32>(phdr.p_filesz);
|
||||
seg->addr = static_cast<u32>(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<u32>(phdr.p_vaddr + phdr.p_filesz);
|
||||
seg->size = static_cast<u32>(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<u32>(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<const sys_spu_segment> 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<sys_spu_segment> segs;
|
||||
const s32 nsegs;
|
||||
|
||||
lv2_spu_image(u32 entry, vm::ptr<sys_spu_segment> 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<u32> deadlineMeetCounter; // From cellSpursGetInfo
|
||||
be_t<u32> deadlineMissCounter; // Same
|
||||
be_t<u64> timestamp;
|
||||
be_t<u64> _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<u32, u32> 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<u32> init; // Initialization Counter
|
||||
atomic_t<typename spu_thread::spu_prio_t> prio{}; // SPU Thread Group Priority
|
||||
atomic_t<spu_group_status> run_state; // SPU Thread Group State
|
||||
atomic_t<s32> exit_status; // SPU Thread Group Exit Status
|
||||
atomic_t<u32> join_state; // flags used to detect exit cause and signal
|
||||
atomic_t<u32> running = 0; // Number of running threads
|
||||
atomic_t<u32> spurs_running = 0;
|
||||
atomic_t<u32> stop_count = 0;
|
||||
atomic_t<u32> 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<shared_ptr<named_thread<spu_thread>>, 8> threads; // SPU Threads
|
||||
std::array<s8, 256> threads_map; // SPU Threads map based number
|
||||
std::array<std::pair<u32, std::vector<sys_spu_segment>>, 8> imgs; // Entry points, SPU image segments
|
||||
std::array<std::array<u64, 4>, 8> args; // SPU Thread Arguments
|
||||
|
||||
shared_ptr<lv2_event_queue> ep_run; // port for SYS_SPU_THREAD_GROUP_EVENT_RUN events
|
||||
shared_ptr<lv2_event_queue> ep_exception; // TODO: SYS_SPU_THREAD_GROUP_EVENT_EXCEPTION
|
||||
shared_ptr<lv2_event_queue> 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<named_thread<spu_thread>*, shared_ptr<lv2_spu_group>> 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<sys_spu_image> img, vm::ptr<u32> entry_point, vm::ptr<s32> nsegs);
|
||||
error_code sys_spu_image_open(ppu_thread&, vm::ptr<sys_spu_image> img, vm::cptr<char> path);
|
||||
error_code _sys_spu_image_import(ppu_thread&, vm::ptr<sys_spu_image> img, u32 src, u32 size, u32 arg4);
|
||||
error_code _sys_spu_image_close(ppu_thread&, vm::ptr<sys_spu_image> img);
|
||||
error_code _sys_spu_image_get_segments(ppu_thread&, vm::ptr<sys_spu_image> img, vm::ptr<sys_spu_segment> segments, s32 nseg);
|
||||
error_code sys_spu_thread_initialize(ppu_thread&, vm::ptr<u32> thread, u32 group, u32 spu_num, vm::ptr<sys_spu_image>, vm::ptr<sys_spu_thread_attribute>, vm::ptr<sys_spu_thread_argument>);
|
||||
error_code sys_spu_thread_set_argument(ppu_thread&, u32 id, vm::ptr<sys_spu_thread_argument> arg);
|
||||
error_code sys_spu_thread_group_create(ppu_thread&, vm::ptr<u32> id, u32 num, s32 prio, vm::ptr<sys_spu_thread_group_attribute> 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<u32> cause, vm::ptr<u32> 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<s32> 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<u8> 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<sys_spu_thread_group_syscall_253_info> info);
|
||||
error_code sys_spu_thread_group_log(ppu_thread&, s32 command, vm::ptr<s32> 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<u64> 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<u64> 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<s32> status);
|
||||
error_code sys_spu_thread_recover_page_fault(ppu_thread&, u32 id);
|
||||
|
||||
error_code sys_raw_spu_create(ppu_thread&, vm::ptr<u32> id, vm::ptr<void> 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<u32> 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<u64> 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<u64> stat);
|
||||
error_code sys_raw_spu_read_puint_mb(ppu_thread&, u32 id, vm::ptr<u32> 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<u32> value);
|
||||
error_code sys_raw_spu_recover_page_fault(ppu_thread&, u32 id);
|
||||
|
||||
error_code sys_isolated_spu_create(ppu_thread&, vm::ptr<u32> id, vm::ptr<void> 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<u32> 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<u64> 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<u64> stat);
|
||||
error_code sys_isolated_spu_read_puint_mb(ppu_thread&, u32 id, vm::ptr<u32> 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<u32> value);
|
||||
|
|
@ -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 <charconv>
|
||||
#include <shared_mutex>
|
||||
#include <unordered_set>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <bcrypt.h>
|
||||
#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<u32, u8> 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<u32> 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<sys_ss_rng_error>::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<void> 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<u8[]> temp(new u8[size]);
|
||||
|
||||
#ifdef _WIN32
|
||||
if (auto ret = BCryptGenRandom(nullptr, temp.get(), static_cast<ULONG>(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<u64>(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<u8> 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<CellSsOpenPSID> 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<u8> 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<u64> 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<u8> 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<u32>(a3), get_timebased_time());
|
||||
// a4 is a pointer to status, non 0 on error
|
||||
vm::write64(::narrow<u32>(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<u64> 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<u64> 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<lv2_update_manager>();
|
||||
|
||||
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<u32>(a1);
|
||||
const auto info_ptr = ::narrow<u32>(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<u32>(a1);
|
||||
const auto value_ptr = ::narrow<u32>(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<u32>(a1);
|
||||
const auto value = ::narrow<u8>(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<u32>(a1);
|
||||
const auto addr_ptr = ::narrow<u32>(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<u32>(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<u32>(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<u32>(a1);
|
||||
const auto size = ::narrow<u32>(a2);
|
||||
const auto addr_ptr = ::narrow<u32>(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<u64> 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<u64>(a5) = a4; // Write back size of buffer
|
||||
break;
|
||||
}
|
||||
// Get EID size
|
||||
case 0x17001: *out_size = 0x100; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "Emu/Memory/vm_ptr.h"
|
||||
#include "Emu/Cell/ErrorCodes.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,
|
||||
};
|
||||
|
||||
struct CellSsOpenPSID
|
||||
{
|
||||
be_t<u64> high;
|
||||
be_t<u64> low;
|
||||
};
|
||||
|
||||
error_code sys_ss_random_number_generator(u64 pkg_id, vm::ptr<void> 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<u8> buf);
|
||||
error_code sys_ss_get_open_psid(vm::ptr<CellSsOpenPSID> psid);
|
||||
error_code sys_ss_appliance_info_manager(u32 code, vm::ptr<u8> buffer);
|
||||
error_code sys_ss_get_cache_of_product_mode(vm::ptr<u8> 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<u64> flag);
|
||||
error_code sys_ss_get_boot_device(vm::ptr<u64> 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<u64> out_size, u64 a4, u64 a5, u64 a6);
|
||||
|
|
@ -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<lv2_event_queue> asyncequeue;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
error_code sys_storage_open(u64 device, u64 mode, vm::ptr<u32> 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<lv2_storage>(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<lv2_storage>(fd);
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
|
||||
error_code sys_storage_read(u32 fd, u32 mode, u32 start_sector, u32 num_sectors, vm::ptr<void> bounce_buf, vm::ptr<u32> 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<lv2_storage>(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<u32>(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<void> data, vm::ptr<u32> 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<lv2_storage>(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<void> in, u64 inlen, vm::ptr<void> 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<storage_manager>();
|
||||
|
||||
if (auto queue = idm::get_unlocked<lv2_obj, lv2_event_queue>(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<void> in, u64 inlen, vm::ptr<void> 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<storage_manager>();
|
||||
|
||||
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<StorageDeviceInfo> 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<u32> storages, vm::ptr<u32> 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<u64> 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<u64, 0x11> all_devs = []
|
||||
{
|
||||
std::array<u64, 0x11> all_devs{};
|
||||
all_devs[0] = 0x10300000000000A;
|
||||
|
||||
for (int i = 0; i < 7; ++i)
|
||||
{
|
||||
all_devs[i + 1] = 0x100000000000001 | (static_cast<u64>(i) << 32);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; ++i)
|
||||
{
|
||||
all_devs[i + 8] = 0x101000000000007 | (static_cast<u64>(i) << 32);
|
||||
}
|
||||
|
||||
all_devs[11] = 0x101000000000006;
|
||||
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
all_devs[i + 12] = 0x100000000000004 | (static_cast<u64>(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<char> cmdbuf, u64 cmdbuf_size, vm::ptr<char> databuf, u64 databuf_size, vm::ptr<u32> 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;
|
||||
}
|
||||
|
|
@ -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<u32> zero; // 0x20
|
||||
be_t<u32> zero2; // 0x24
|
||||
be_t<u64> sector_count; // 0x28
|
||||
be_t<u32> sector_size; // 0x30
|
||||
be_t<u32> 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<u32> 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<void> bounce_buf, vm::ptr<u32> sectors_read, u64 flags);
|
||||
error_code sys_storage_write(u32 fd, u32 mode, u32 start_sector, u32 num_sectors, vm::ptr<void> data, vm::ptr<u32> sectors_wrote, u64 flags);
|
||||
error_code sys_storage_send_device_command(u32 dev_handle, u64 cmd, vm::ptr<void> in, u64 inlen, vm::ptr<void> 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<StorageDeviceInfo> buffer);
|
||||
error_code sys_storage_get_device_config(vm::ptr<u32> storages, vm::ptr<u32> devices);
|
||||
error_code sys_storage_report_devices(u32 storages, u32 start, u32 devices, vm::ptr<u64> 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<char> cmdbuf, u64 cmdbuf_size, vm::ptr<char> databuf, u64 databuf_size, vm::ptr<u32> 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<void> in, u64 inlen, vm::ptr<void> out, u64 outlen, u64 unk);
|
||||
error_code sys_storage_get_region_offset();
|
||||
error_code sys_storage_set_emulated_speed();
|
||||
|
|
@ -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<u32, u32> 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<u32> exists = 0;
|
||||
|
||||
template <typename Ptr>
|
||||
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 <bool ModifyNode = true, typename T>
|
||||
static T* unqueue(T*& first, T* object, T* T::* mem_ptr = &T::next_cpu)
|
||||
{
|
||||
auto it = +first;
|
||||
|
||||
if (it == object)
|
||||
{
|
||||
atomic_storage<T*>::release(first, it->*mem_ptr);
|
||||
|
||||
if constexpr (ModifyNode)
|
||||
{
|
||||
atomic_storage<T*>::release(it->*mem_ptr, nullptr);
|
||||
}
|
||||
|
||||
return it;
|
||||
}
|
||||
|
||||
for (; it;)
|
||||
{
|
||||
const auto next = it->*mem_ptr + 0;
|
||||
|
||||
if (next == object)
|
||||
{
|
||||
atomic_storage<T*>::release(it->*mem_ptr, next->*mem_ptr);
|
||||
|
||||
if constexpr (ModifyNode)
|
||||
{
|
||||
atomic_storage<T*>::release(next->*mem_ptr, nullptr);
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
it = next;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// Remove an object from the linked set according to the protocol
|
||||
template <typename E, typename T>
|
||||
static E* schedule(T& first, u32 protocol, bool modify_node = true)
|
||||
{
|
||||
auto it = static_cast<E*>(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<T>::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<E*>(node);
|
||||
|
||||
if (!next)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
const auto _prio = static_cast<E*>(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<T>::release(*parent_found, found->next_cpu);
|
||||
|
||||
if (modify_node)
|
||||
{
|
||||
atomic_storage<T>::release(found->next_cpu, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static void emplace(T& first, T object)
|
||||
{
|
||||
atomic_storage<T>::release(object->next_cpu, first);
|
||||
atomic_storage<T>::release(first, object);
|
||||
|
||||
object->prio.atomic_op([order = ++g_priority_order_tag](std::common_type_t<decltype(std::declval<T>()->prio.load())>& prio)
|
||||
{
|
||||
if constexpr (requires { +std::declval<decltype(prio)>().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_thread_status, u32> 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 <typename T>
|
||||
static inline u64 get_key(const T& attr)
|
||||
{
|
||||
return (attr.pshared == SYS_SYNC_PROCESS_SHARED ? +attr.ipc_key : 0);
|
||||
}
|
||||
|
||||
template <typename T, typename F>
|
||||
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 <lv2_obj, T>([&]() -> shared_ptr<T>
|
||||
{
|
||||
shared_ptr<T> result = make();
|
||||
|
||||
auto finalize_construct = [&]() -> shared_ptr<T>
|
||||
{
|
||||
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<ipc_manager<T, u64>>();
|
||||
|
||||
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 <typename T>
|
||||
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<ipc_manager<T, u64>>().remove(ipc_key);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static shared_ptr<T> load(u64 ipc_key, shared_ptr<T> make, u64 pshared = umax)
|
||||
{
|
||||
if (pshared == umax ? ipc_key != 0 : pshared != 0)
|
||||
{
|
||||
g_fxo->need<ipc_manager<T, u64>>();
|
||||
|
||||
g_fxo->get<ipc_manager<T, u64>>().add(ipc_key, [&]()
|
||||
{
|
||||
return make;
|
||||
});
|
||||
}
|
||||
|
||||
// Ensure no error
|
||||
ensure(!make->on_id_create());
|
||||
return make;
|
||||
}
|
||||
|
||||
template <typename T, typename Storage = lv2_obj>
|
||||
static std::function<void(void*)> load_func(shared_ptr<T> make, u64 pshared = umax)
|
||||
{
|
||||
const u64 key = make->key;
|
||||
return [ptr = load<T>(key, make, pshared)](void* storage)
|
||||
{
|
||||
*static_cast<atomic_ptr<Storage>*>(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<const decltype(cpu_thread::state)*>(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<u64> g_priority_order_tag;
|
||||
|
||||
private:
|
||||
// Pending list of threads to run
|
||||
static thread_local std::vector<class cpu_thread*> 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<const void> 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);
|
||||
};
|
||||
|
|
@ -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 <windows.h>
|
||||
|
||||
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<u64>(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 <sys/_types/_timespec.h> for compatability
|
||||
// struct timespec { time_t tv_sec; long tv_nsec; };
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/_types/_timespec.h>
|
||||
#include <mach/mach.h>
|
||||
#include <mach/clock.h>
|
||||
#include <mach/mach_time.h>
|
||||
#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 <sys/time.h>
|
||||
|
||||
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<u64>(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<u64>(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<u64>(ts.tv_sec) * g_timebase_freq + static_cast<u64>(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<u64>(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<u64>(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<u64>(ts.tv_sec) * 1000000ull + static_cast<u64>(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<s32> timezone, vm::ptr<s32> 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<s16>(-::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<s16>((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<s16>(-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<s16>(test.tm_gmtoff / 60 + tz.tz_minuteswest) : 0;
|
||||
}();
|
||||
#endif
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
|
||||
error_code sys_time_get_current_time(vm::ptr<s64> sec, vm::ptr<s64> 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<u64> rtc)
|
||||
{
|
||||
sys_time.todo("sys_time_get_rtc(rtc=*0x%x)", rtc);
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "Emu/Memory/vm_ptr.h"
|
||||
#include "Emu/Cell/ErrorCodes.h"
|
||||
|
||||
// SysCalls
|
||||
|
||||
error_code sys_time_set_timezone(s32 timezone, s32 summertime);
|
||||
error_code sys_time_get_timezone(vm::ptr<s32> timezone, vm::ptr<s32> summertime);
|
||||
error_code sys_time_get_current_time(vm::ptr<s64> sec, vm::ptr<s64> nsec);
|
||||
error_code sys_time_set_current_time(s64 sec, s64 nsec);
|
||||
u64 sys_time_get_timebase_frequency();
|
||||
error_code sys_time_get_rtc(vm::ptr<u64> rtc);
|
||||
|
||||
extern u64 g_timebase_offs;
|
||||
|
|
@ -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 <thread>
|
||||
#include <deque>
|
||||
|
||||
LOG_CHANNEL(sys_timer);
|
||||
|
||||
struct lv2_timer_thread
|
||||
{
|
||||
shared_mutex mutex;
|
||||
std::deque<shared_ptr<lv2_timer>> 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<u64>(next, period);
|
||||
expire.release(expire0);
|
||||
return utils::sub_saturate<u64>(expire0, _now);
|
||||
}
|
||||
|
||||
// Stop after oneshot
|
||||
state.release(SYS_TIMER_STATE_STOP);
|
||||
return umax;
|
||||
}
|
||||
|
||||
lv2_timer_thread::lv2_timer_thread()
|
||||
{
|
||||
Emu.PostponeInitCode([this]()
|
||||
{
|
||||
idm::select<lv2_obj, lv2_timer>([&](u32 id, lv2_timer&)
|
||||
{
|
||||
timers.emplace_back(idm::get_unlocked<lv2_obj, lv2_timer>(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<u32> 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<lv2_obj, lv2_timer>())
|
||||
{
|
||||
auto& thread = g_fxo->get<named_thread<lv2_timer_thread>>();
|
||||
{
|
||||
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<lv2_obj, lv2_timer>(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<named_thread<lv2_timer_thread>>();
|
||||
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<sys_timer_information_t> 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<lv2_obj, lv2_timer>(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<lv2_obj, lv2_timer>(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<named_thread<lv2_timer_thread>>()([] {});
|
||||
|
||||
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<lv2_obj, lv2_timer>(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<lv2_obj, lv2_timer>(timer_id, [&](lv2_timer& timer) -> CellError
|
||||
{
|
||||
auto found = idm::get_unlocked<lv2_obj, lv2_event_queue>(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<lv2_obj, lv2_timer>(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<u64>(sleep_time, add_time);
|
||||
}
|
||||
else
|
||||
{
|
||||
sleep_time = std::max<u64>(1, utils::sub_saturate<u64>(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;
|
||||
}
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "sys_event.h"
|
||||
#include "Emu/Memory/vm_ptr.h"
|
||||
|
||||
// Timer State
|
||||
enum : u32
|
||||
{
|
||||
SYS_TIMER_STATE_STOP = 0,
|
||||
SYS_TIMER_STATE_RUN = 1,
|
||||
};
|
||||
|
||||
struct sys_timer_information_t
|
||||
{
|
||||
be_t<u64> next_expire; // system_time_t
|
||||
be_t<u64> period;
|
||||
be_t<u32> timer_state;
|
||||
be_t<u32> pad;
|
||||
};
|
||||
|
||||
struct lv2_timer : lv2_obj
|
||||
{
|
||||
static const u32 id_base = 0x11000000;
|
||||
|
||||
shared_mutex mutex;
|
||||
atomic_t<u32> state{SYS_TIMER_STATE_STOP};
|
||||
|
||||
shared_ptr<lv2_event_queue> port;
|
||||
u64 source;
|
||||
u64 data1;
|
||||
u64 data2;
|
||||
|
||||
atomic_t<u64> expire{0}; // Next expiration time
|
||||
atomic_t<u64> period{0}; // Period (oneshot if 0)
|
||||
|
||||
u64 check(u64 _now) noexcept;
|
||||
u64 check_unlocked(u64 _now) noexcept;
|
||||
|
||||
lv2_timer() noexcept
|
||||
: lv2_obj(1)
|
||||
{
|
||||
}
|
||||
|
||||
void get_information(sys_timer_information_t& info) const
|
||||
{
|
||||
if (state == SYS_TIMER_STATE_RUN)
|
||||
{
|
||||
info.timer_state = state;
|
||||
info.next_expire = expire;
|
||||
info.period = period;
|
||||
}
|
||||
else
|
||||
{
|
||||
info.timer_state = SYS_TIMER_STATE_STOP;
|
||||
info.next_expire = 0;
|
||||
info.period = 0;
|
||||
}
|
||||
}
|
||||
|
||||
lv2_timer(utils::serial& ar);
|
||||
void save(utils::serial& ar);
|
||||
};
|
||||
|
||||
class ppu_thread;
|
||||
|
||||
// Syscalls
|
||||
|
||||
error_code sys_timer_create(ppu_thread&, vm::ptr<u32> timer_id);
|
||||
error_code sys_timer_destroy(ppu_thread&, u32 timer_id);
|
||||
error_code sys_timer_get_information(ppu_thread&, u32 timer_id, vm::ptr<sys_timer_information_t> info);
|
||||
error_code _sys_timer_start(ppu_thread&, u32 timer_id, u64 basetime, u64 period); // basetime type changed from s64
|
||||
error_code sys_timer_stop(ppu_thread&, u32 timer_id);
|
||||
error_code sys_timer_connect_event_queue(ppu_thread&, u32 timer_id, u32 queue_id, u64 name, u64 data1, u64 data2);
|
||||
error_code sys_timer_disconnect_event_queue(ppu_thread&, u32 timer_id);
|
||||
error_code sys_timer_sleep(ppu_thread&, u32 sleep_time);
|
||||
error_code sys_timer_usleep(ppu_thread&, u64 sleep_time);
|
||||
|
|
@ -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;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue