mikage-dev/source/ipc.hpp
2024-03-08 10:54:13 +01:00

450 lines
18 KiB
C++

#pragma once
#include "framework/meta_tools.hpp"
#include "platform/ipc.hpp"
#include "os.hpp"
#include "bit_field.h"
#include <cstdint>
namespace HLE {
namespace OS {
class Thread;
struct Handle;
using Result = uint32_t;
}
namespace IPC {
using namespace Platform::IPC;
/// Dummy structure used for IPC command tags that don't need any actual data
struct EmptyValue {
};
struct MappedBuffer {
/*VAddr*/uint32_t addr;
uint32_t size;
uint32_t EndAddr() const {
return addr + size;
}
};
struct StaticBuffer {
uint32_t addr;
uint32_t size;
uint32_t id;
};
template<typename T>
struct NormalParamToCommandTagHelper { using type = CommandTags::serialized_tag<T>; };
template<>
struct NormalParamToCommandTagHelper<uint32_t> { using type = CommandTags::uint32_tag; };
template<>
struct NormalParamToCommandTagHelper<uint64_t> { using type = CommandTags::uint64_tag; };
template<typename T>
using NormalParamToCommandTag = typename NormalParamToCommandTagHelper<T>::type;
template<typename F, typename T, typename ArgsTuple, size_t... Idxs>
auto DoDeserialize(F& f, std::index_sequence<Idxs...>) {
return Meta::CallWithSequentialEvaluation<decltype(detail::GetDeserializer<T>()(f(NormalParamToCommandTag<std::tuple_element_t<Idxs, ArgsTuple>>{})...))> {
detail::GetDeserializer<T>(),
f(NormalParamToCommandTag<std::tuple_element_t<Idxs, ArgsTuple>>{})...
}.GetResult();
}
/// Utility class to parse IPC messages from TLS
class TLSReader {
OS::Thread& thread;
// Skip header
uint32_t offset = 0x84;
OS::Handle ParseHandle();
template<std::size_t Num>
using HandleReturnType = std::conditional_t<Num == 1, OS::Handle, std::array<OS::Handle, Num>>;
public:
TLSReader(OS::Thread& thread);
TLSReader(const TLSReader&) = delete;
TLSReader(TLSReader&&) = default;
OS::Result operator()(const IPC::CommandTags::result_tag& /* unused */);
uint32_t operator()(const IPC::CommandTags::uint32_tag& /* unused */);
uint64_t operator()(const IPC::CommandTags::uint64_tag& /* unused */);
StaticBuffer operator()(const IPC::CommandTags::static_buffer_tag& /* unused */);
StaticBuffer operator()(const IPC::CommandTags::pxi_buffer_tag<false>& /* unused */);
StaticBuffer operator()(const IPC::CommandTags::pxi_buffer_tag<true>& /* unused */);
/*ProcessId*/uint32_t operator()(const IPC::CommandTags::process_id_tag& /* unused */);
MappedBuffer operator()(const IPC::CommandTags::map_buffer_r_tag& /* unused */);
MappedBuffer operator()(const IPC::CommandTags::map_buffer_w_tag& /* unused */);
template<typename T>
T operator()(const IPC::CommandTags::serialized_tag<T>& /* unused */) {
using arg_list = typename Meta::function_traits<decltype(detail::GetDeserializer<T>())>::args;
return DoDeserialize<TLSReader, T, arg_list>(*this, std::make_index_sequence<std::tuple_size<arg_list>::value>{});
}
template<HandleType Type, std::size_t Num>
HandleReturnType<Num> operator()(const IPC::CommandTags::handle_close_tag<Type, Num>& /* unused */) {
return (*this)(IPC::CommandTags::handle_tag<Type, Num> { });
}
template<HandleType Type, std::size_t Num>
HandleReturnType<Num> operator()(const IPC::CommandTags::handle_tag<Type, Num>& /* unused */) {
offset += 4; // Move past descriptor
if constexpr (Num == 1) {
return ParseHandle();
} else {
HandleReturnType<Num> ret;
for (auto& handle : ret) {
handle = ParseHandle();
}
return ret;
}
}
};
template<typename F, typename DataTuple, typename Data, size_t... Idxs>
inline void DoWrite(F& f, const Data& data, std::index_sequence<Idxs...>) {
(f(NormalParamToCommandTag<std::tuple_element_t<Idxs, DataTuple>>{}, std::get<Idxs>(data)), ...);
}
/// Utility class to construct IPC messages
class TLSWriter {
OS::Thread& thread;
// Initial offset (response header omitted)
uint32_t offset = 0x84;
void WriteHandleDescriptor(const OS::Handle* data, size_t num, bool close);
public:
TLSWriter(OS::Thread& thread);
TLSWriter(const TLSWriter& tls) = delete;
TLSWriter(TLSWriter&& tls) = default;
void operator()(IPC::CommandTags::result_tag, OS::Result data);
void operator()(IPC::CommandTags::uint32_tag, uint32_t data);
void operator()(IPC::CommandTags::uint64_tag, uint64_t data);
void operator()(IPC::CommandTags::map_buffer_r_tag, const MappedBuffer& buffer);
void operator()(IPC::CommandTags::map_buffer_w_tag, const MappedBuffer& buffer);
void operator()(IPC::CommandTags::static_buffer_tag, const StaticBuffer& buffer);
void operator()(IPC::CommandTags::process_id_tag, const EmptyValue&);
void operator()(IPC::CommandTags::pxi_buffer_tag<false>, const StaticBuffer& buffer);
void operator()(IPC::CommandTags::pxi_buffer_tag<true>, const StaticBuffer& buffer);
template<HandleType Type>
void operator()(IPC::CommandTags::handle_close_tag<Type>, OS::Handle data) {
WriteHandleDescriptor(&data, 1, true);
}
template<HandleType Type>
void operator()(IPC::CommandTags::handle_tag<Type>, OS::Handle data) {
WriteHandleDescriptor(&data, 1, false);
}
template<HandleType Type, size_t num>
void operator()(IPC::CommandTags::handle_close_tag<Type,num>, const std::array<OS::Handle,num>& data) {
WriteHandleDescriptor(data.data(), num, true);
}
template<HandleType Type, size_t num>
void operator()(IPC::CommandTags::handle_tag<Type,num>, const std::array<OS::Handle,num>& data) {
WriteHandleDescriptor(data.data(), num, false);
}
template<typename T>
void operator()(IPC::CommandTags::serialized_tag<T>, T& data) {
auto parameters = detail::Serialize<T>(data);
constexpr auto tuple_size = std::tuple_size<decltype(parameters)>::value;
DoWrite<TLSWriter, decltype(parameters)>(*this, parameters, std::make_index_sequence<tuple_size>{});
}
};
namespace detail {
template<bool NoError>
struct CHECK_EQUALITY_VERBOSELY_DEFAULT_ERROR { static_assert(NoError, "Given integers are not equal!"); };
}
/**
* Utility structure for asserting that the compile-time constants A and B
* are equal. If they aren't, the type Error<true> will be instantiated, which
* is supposed to abort compilation due to a static_assert failure.
*
* The error message can be customized by passing an own Error template, which
* is expected to have a static_assert fail if its instantiated with a "true"
* argument. This is useful to give helpful error messages instead of cryptic
* "5 != 3" messages.
*
* @todo Move this into a utility header
*/
template<int A, int B,
template<bool Err> class Error = detail::CHECK_EQUALITY_VERBOSELY_DEFAULT_ERROR>
struct CHECK_EQUALITY_VERBOSELY
{
// Force Error<A==B> to be instantiated and error out if A!=B
// If A==B, use sizeof to form the value "true".
static constexpr bool value = sizeof(Error<A==B>);
};
/// Exception class thrown by SendIPCRequest on error
struct IPCError {
IPCError(uint32_t response_header, OS::Result result) noexcept : header(response_header), result(result) {
}
~IPCError() noexcept = default;
uint32_t header;
OS::Result result;
};
template<typename F, typename Tuple, size_t... Idxs>
auto TransformTupleSequentiallyHelper(F&& f, Tuple&& tuple, std::index_sequence<Idxs...>)
{
return std::tuple<std::invoke_result_t<F, std::tuple_element_t<Idxs, Tuple>>...> { f(std::get<Idxs>(tuple))... };
}
template<typename F, typename Tuple>
auto TransformTupleSequentially(F&& f, Tuple&& tuple)
{
return TransformTupleSequentiallyHelper(std::forward<F>(f), std::forward<Tuple>(tuple), std::make_index_sequence<std::tuple_size<Tuple>::value>{});
}
namespace detail {
template<bool NoError>
struct IPCArgumentMatchError { static_assert(NoError, "Given number of function arguments doesn't match the expected number per the IPC message descriptor"); };
template<typename ResponseList>
struct IPCResponseChecker;
template<typename... Parameters>
struct IPCResponseChecker<boost::mp11::mp_list<Parameters...>> {
void Check(Parameters...) {
// Do nothing, just syntax-check this is a valid function call
}
};
template<typename T>
struct TagToTypeHelper;
template<>
struct TagToTypeHelper<CommandTags::uint32_tag> : boost::mp11::mp_identity<uint32_t> {};
template<>
struct TagToTypeHelper<CommandTags::uint64_tag> : boost::mp11::mp_identity<uint64_t> {};
template<>
struct TagToTypeHelper<CommandTags::result_tag> : boost::mp11::mp_identity<OS::Result> {};
template<HandleType T>
struct TagToTypeHelper<CommandTags::handle_tag<T, 1>> : boost::mp11::mp_identity<OS::Handle> {};
template<HandleType T>
struct TagToTypeHelper<CommandTags::handle_close_tag<T, 1>> : boost::mp11::mp_identity<OS::Handle> {};
template<HandleType T, std::size_t num>
struct TagToTypeHelper<CommandTags::handle_tag<T, num>> : boost::mp11::mp_identity<std::array<OS::Handle, num>> {};
template<HandleType T, std::size_t num>
struct TagToTypeHelper<CommandTags::handle_close_tag<T, num>> : boost::mp11::mp_identity<std::array<OS::Handle, num>> {};
template<>
struct TagToTypeHelper<CommandTags::map_buffer_r_tag> : boost::mp11::mp_identity<MappedBuffer> {};
template<>
struct TagToTypeHelper<CommandTags::map_buffer_w_tag> : boost::mp11::mp_identity<MappedBuffer> {};
template<>
struct TagToTypeHelper<CommandTags::map_buffer_rw_tag> : boost::mp11::mp_identity<MappedBuffer> {};
template<>
struct TagToTypeHelper<CommandTags::static_buffer_tag> : boost::mp11::mp_identity<StaticBuffer> {};
template<typename T>
struct TagToTypeHelper<CommandTags::serialized_tag<T>> : boost::mp11::mp_identity<T> {};
template<bool read_only>
struct TagToTypeHelper<CommandTags::pxi_buffer_tag<read_only>> : boost::mp11::mp_identity<StaticBuffer> {};
template<>
struct TagToTypeHelper<CommandTags::process_id_tag> : boost::mp11::mp_identity<uint32_t> {};
template<typename T>
using TagToType = typename TagToTypeHelper<T>::type;
template<typename Command, bool IsResponse, typename Thread, typename ArgTagsTuple>
struct IPCMessageWriter;
template<typename Command, bool IsResponse, typename Thread, typename... ArgTags>
struct IPCMessageWriter< Command, IsResponse, Thread, boost::mp11::mp_list<ArgTags...>> {
Thread& thread;
using TypeList = std::conditional_t<IsResponse, typename Command::response_list, typename Command::request_list>;
template<typename... Data>
auto operator()(Data... data) {
if constexpr (IsResponse) {
// Check if the returned value matches the command definition
// TODO: Enable this check for requests, too
using paramlist = boost::mp11::mp_transform<TagToType, TypeList>;
IPCResponseChecker<paramlist>{}.Check(data...);
}
thread.WriteTLS(0x80, (IsResponse ? Command::response_header : Command::request_header));
IPC::TLSWriter writer(thread);
(writer(ArgTags { }, data), ...);
}
};
} // namespace detail
template<typename Command, typename Thread, typename... Data>
void WriteIPCReplyFromTuple(Thread& thread, std::tuple<Data...> data) {
std::apply(detail::IPCMessageWriter<Command, true, Thread, typename Command::response_list> { thread }, data);
}
template<typename Func, typename MainArgs, typename... ExtraArgs>
struct IPCMessageResultsHelper;
template<typename Func, typename... MainArgs, typename... ExtraArgs>
struct IPCMessageResultsHelper<Func, boost::mp11::mp_list<MainArgs...>, ExtraArgs...>
: std::invoke_result< Func,
std::invoke_result_t<IPC::TLSReader, MainArgs>...,
ExtraArgs...> {
};
template<typename Func, typename Command, typename... ExtraArgs>
using IPCMessageResults = IPCMessageResultsHelper<Func, typename Command::request_list, ExtraArgs...>;
template< typename Func, typename... ExtraArgs, typename... ArgTags>
auto DispatchIPCMessageHelper(IPC::TLSReader reader, Func&& handler, ExtraArgs&&... args, boost::mp11::mp_list<ArgTags...>) {
// Read request data from TLS (via ArgTags) and invoke request handler
// TODO: The following may perform implicit conversion of uint64_t
// parameters to uint32_t (or vice versa), hence it actually provides
// less compile-time safety that we would like it to, currently.
if constexpr (std::is_invocable_v< Func, ExtraArgs...,
std::invoke_result_t<IPC::TLSReader, ArgTags>...>) {
// Use CallWithSequentialEvaluation to get well-defined execution order
// TODO: We should static assert for handlers returning void, but this somehow doesn't always compile currently
// static_assert( !std::is_invocable_r_v<void, Func, ExtraArgs..., std::invoke_result_t<IPC::TLSReader, ArgTags>...>,
// "Handler must have non-void return type");
// std::move is needed to work around a clang bug with class template argument deduction
return std::move(Meta::CallWithSequentialEvaluation {
std::forward<Func>(handler),
std::forward<ExtraArgs>(args)...,
reader(ArgTags{})... }).GetResult();
} else {
// handler is not invocable with the given arguments:
// Use a regular function call to get a good compiler diagnostic
return std::forward<Func>(handler)(
std::forward<ExtraArgs>(args)...,
reader(ArgTags{})...);
}
}
/**
* Read IPC request data from the thread's TLS and forward the decoded
* arguments to the given handler.
*/
template<typename Command, typename Func, typename Thread, typename... ExtraArgs>
auto DispatchIPCMessage(Func&& handler, Thread& thread, ExtraArgs&&... args) {
if (thread.ReadTLS(0x80) != Command::request_header)
throw std::runtime_error(fmt::format("Expected command header {:#x}, but got {:#x}", Command::request_header, thread.ReadTLS(0x80)));
return DispatchIPCMessageHelper<Func, ExtraArgs...>(IPC::TLSReader(thread), std::forward<Func>(handler), std::forward<ExtraArgs>(args)..., typename Command::request_list { });
}
/**
* Read IPC request data from the thread's TLS, forward the decoded
* arguments to the given handler, and write results back to TLS
*/
template<typename Command, typename Func, typename Thread, typename... ExtraArgs>
void HandleIPCCommand(Func&& handler, Thread& thread, ExtraArgs&&... args) {
// Decode request data from TLS and invoke handler
auto output_data = DispatchIPCMessage<Command>(std::forward<Func>(handler), thread, std::forward<ExtraArgs>(args)...);
// Write response to TLS
// TODO: Do not pass output_data per copy!
WriteIPCReplyFromTuple<Command>(thread, output_data);
}
namespace detail {
/**
* Unwrap small tuples:
* For single-element tuples, this returns the contained element.
* For zero-element tuples, this returns void.
* Otherwise, this just returns the unmodified tuple.
*/
template<typename... T>
inline auto UnwrapTuple(std::tuple<T...>&& tuple) {
return tuple;
}
template<typename... T>
inline decltype(auto) UnwrapTuple(std::tuple<T...>& tuple) {
return tuple;
}
template<typename T>
inline decltype(auto) UnwrapTuple(std::tuple<T>&& tuple) {
return std::get<0>(tuple);
}
template<typename T>
inline decltype(auto) UnwrapTuple(std::tuple<T>& tuple) {
return std::get<0>(tuple);
}
inline void UnwrapTuple(std::tuple<>&&) {
}
inline void UnwrapTuple(std::tuple<>&) {
}
}
/**
* Submit an IPC request from the given thread through the given session
* handle. The request parameters are taken from the parameter pack, which is
* expected to be compatible with the parameter list inferred from the Command
* type.
* @return A tuple of return values inferred from the given Command. This does
* not include the return code. If only a single value is returned, the
* tuple is unwrapped to the value itself. If no value is returned,
* this function returns void.
* @throws IPCError if sending the IPC request was unsuccessful, if the
* response indicates a failure, and if the response header
* is different from the one indicated by Command.
* @todo Consider changing "t..." to use (universal) reference semantics
* @todo Currently, things like the process_id tag require an EmptyValue
* dummy argument, despite not actually needing any input data
*/
template<typename Command, typename Thread, typename... T>
auto SendIPCRequest(Thread& thread, OS::Handle session_handle, T... t) {
// Write request data to TLS
detail::IPCMessageWriter<Command, false, Thread, typename Command::request_list> { thread } (t...);
// Send IPC request and wait for response
// TODO: The command header returned here shouldn't be read from TLS but
// rather constructed on-the-fly!
OS::Result result = std::get<0>(thread.CallSVC(&OS::OS::SVCSendSyncRequest, session_handle));
if (result != 0 /* RESULT_OK */)
throw IPCError(thread.ReadTLS(0x080), result);
// Read result code of the response
auto reader = TLSReader(thread);
result = reader(CommandTags::result_tag{});
if (result != 0)
throw IPCError{thread.ReadTLS(0x80), result};
// TODO: Currently, we build the response header incorrectly and hence
// cannot actually perform this important check
//assert(thread.ReadTLS(0x80) == Command::response_header);
// Read results, but before that drop the result code (if it doesn't
// indicate success, we aborted before anyway)
using ResponseTypeList = boost::mp11::mp_rename<typename Command::response_list, std::tuple>;
// TODO: Filter out EmptyValue values!
using FixedResponseTypeList = boost::mp11::mp_pop_front<ResponseTypeList>;
auto result_data = TransformTupleSequentially(reader, FixedResponseTypeList{});
// Return data: Decay to "void" or an unwrapped type if the data tuple is
// empty or a singleton, respectively
return detail::UnwrapTuple(result_data);
}
} // namespace IPC
} // namespace HLE