rpcsx/rpcs3/Emu/Cell/lv2/sys_config.cpp
Elad 575a245f8d
IDM: Implement lock-free smart pointers (#16403)
Replaces `std::shared_pointer` with `stx::atomic_ptr` and `stx::shared_ptr`.

Notes to programmers:

* This pr kills the use of `dynamic_cast`, `std::dynamic_pointer_cast` and `std::weak_ptr` on IDM objects, possible replacement is to save the object ID on the base object, then use idm::check/get_unlocked to the destination type via the saved ID which may be null. Null pointer check is how you can tell type mismatch (as dynamic cast) or object destruction (as weak_ptr locking).
* Double-inheritance on IDM objects should be used with care, `stx::shared_ptr` does not support constant-evaluated pointer offsetting to parent/child type.
* `idm::check/get_unlocked` can now be used anywhere.

Misc fixes:
* Fixes some segfaults with RPCN with interaction with IDM.
* Fix deadlocks in access violation handler due locking recursion.
* Fixes race condition in process exit-spawn on memory containers read.
* Fix bug that theoretically can prevent RPCS3 from booting - fix `id_manager::typeinfo` comparison to compare members instead of `memcmp` which can fail spuriously on padding bytes.
* Ensure all IDM inherited types of base, either has `id_base` or `id_type` defined locally, this allows to make getters such as `idm::get_unlocked<lv2_socket, lv2_socket_raw>()` which were broken before. (requires save-states invalidation)
* Removes broken operator[] overload of `stx::shared_ptr` and `stx::single_ptr` for non-array types.
2024-12-22 20:59:48 +02:00

448 lines
11 KiB
C++

#include "stdafx.h"
#include "Emu/System.h"
#include "Emu/Memory/vm.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)
{
std::lock_guard lock(m_mutex);
events.erase(id);
}
// 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;
}