From b5b86f41a345ba19f2ff94b90cb4e47f59885236 Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Sat, 17 Jul 2021 17:10:12 +0100 Subject: [PATCH] Implement efficient template based IOCTL deserialisation This moves IOCTLs from frozen to a template based parser, this allows IOCTL lookup to be performed much more optimally and some IOCTLs to be inlined. --- .../devices/deserialisation/deserialisation.h | 71 +++++++++ .../nvdrv/devices/deserialisation/macro_def.h | 66 +++++++++ .../devices/deserialisation/macro_undef.h | 21 +++ .../nvdrv/devices/deserialisation/types.h | 140 ++++++++++++++++++ 4 files changed, 298 insertions(+) create mode 100644 app/src/main/cpp/skyline/services/nvdrv/devices/deserialisation/deserialisation.h create mode 100644 app/src/main/cpp/skyline/services/nvdrv/devices/deserialisation/macro_def.h create mode 100644 app/src/main/cpp/skyline/services/nvdrv/devices/deserialisation/macro_undef.h create mode 100644 app/src/main/cpp/skyline/services/nvdrv/devices/deserialisation/types.h diff --git a/app/src/main/cpp/skyline/services/nvdrv/devices/deserialisation/deserialisation.h b/app/src/main/cpp/skyline/services/nvdrv/devices/deserialisation/deserialisation.h new file mode 100644 index 00000000..5e6da190 --- /dev/null +++ b/app/src/main/cpp/skyline/services/nvdrv/devices/deserialisation/deserialisation.h @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: MIT OR MPL-2.0 +// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#pragma once + +#include +#include +#include "types.h" + +namespace skyline::service::nvdrv::deserialisation { + template requires (Desc::In && IsIn::value) + constexpr ArgType DecodeArgument(span buffer, size_t &offset) { + auto out{buffer.subspan(offset).template as()}; + offset += sizeof(ArgType); + return out; + } + + template requires (Desc::Out && Desc::In && IsInOut::value) + constexpr ArgType DecodeArgument(span buffer, size_t &offset) { + auto &out{buffer.subspan(offset).template as>()}; + offset += sizeof(RemoveInOut); + return out; + } + + template requires (Desc::Out && IsOut::value) + constexpr ArgType DecodeArgument(span buffer, size_t &offset) { + auto out{Out(buffer.subspan(offset).template as>())}; + offset += sizeof(RemoveOut); + return out; + } + + /** + * @brief Creates a reference preserving tuple of the given types + */ + template + constexpr auto make_ref_tuple(Ts&&...ts) { + return std::tuple{std::forward(ts)...}; + } + + template + constexpr auto DecodeArgumentsImpl(span buffer, size_t &offset) { + if constexpr (IsAutoSizeSpan::value) { + // AutoSizeSpan needs to be the last argument + static_assert(sizeof...(ArgTypes) == 0); + size_t bytes{buffer.size() - offset}; + size_t extent{bytes / sizeof(RemoveAutoSizeSpan)}; + return make_ref_tuple(buffer.subspan(offset, extent * sizeof(RemoveAutoSizeSpan)).template cast>()); + } else if constexpr (IsPad::value) { + offset += ArgType::Bytes; + return DecodeArgumentsImpl(buffer, offset); + } else { + if constexpr(sizeof...(ArgTypes) == 0) { + return make_ref_tuple(DecodeArgument(buffer, offset)); + } else { + return std::tuple_cat(make_ref_tuple(DecodeArgument(buffer, offset)), + DecodeArgumentsImpl(buffer, offset)); + } + } + } + + /** + * @brief This fancy thing takes a varadic template of argument types and uses it to deserialise the given buffer + * @tparam Desc A MetaIoctlDescriptor or MetaVariableIoctlDescriptor corresponding to the IOCTL that takes these arguments + * @return A tuple containing the arguments to be passed to the IOCTL's handler + */ + template + constexpr auto DecodeArguments(span buffer) { + size_t offset{}; + return DecodeArgumentsImpl(buffer, offset); + } +} \ No newline at end of file diff --git a/app/src/main/cpp/skyline/services/nvdrv/devices/deserialisation/macro_def.h b/app/src/main/cpp/skyline/services/nvdrv/devices/deserialisation/macro_def.h new file mode 100644 index 00000000..1a69a7f9 --- /dev/null +++ b/app/src/main/cpp/skyline/services/nvdrv/devices/deserialisation/macro_def.h @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: MIT OR MPL-2.0 +// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#include "deserialisation.h" + +#define VARIABLE_IOCTL_HANDLER_FUNC(name, cases, variableCases) \ + PosixResult name::Ioctl(IoctlDescriptor cmd, span buffer) { \ + using className = name; \ + switch (cmd.raw) { \ + cases; \ + default: \ + cmd.size = 0; \ + switch (cmd.raw) { \ + variableCases; \ + default: \ + return PosixResult::InappropriateIoctlForDevice; \ + } \ + } \ + } + +#define IOCTL_HANDLER_FUNC(name, cases) \ + PosixResult name::Ioctl(IoctlDescriptor cmd, span buffer) { \ + using className = name; \ + switch (cmd.raw) { \ + cases; \ + default: \ + return PosixResult::InappropriateIoctlForDevice; \ + } \ + } + +#define IOCTL_CASE_ARGS_I(out, in, size, magic, function, name, ...) \ + case MetaIoctlDescriptor::Raw(): { \ + using IoctlType = MetaIoctlDescriptor< out, in, size, magic, function>; \ + auto args = DecodeArguments(buffer.subspan<0, size>()); \ + return std::apply(&className::name, std::tuple_cat(std::make_tuple(this), args)); \ + } + +#define IOCTL_CASE_NOARGS_I(out, in, size, magic, function, name) \ + case MetaIoctlDescriptor::Raw(): \ + return className::name(); + +#define IOCTL_CASE_RESULT_I(out, in, size, magic, function, result) \ + case MetaIoctlDescriptor::Raw(): \ + return result; + +#define IOCTL_CASE_ARGS(...) IOCTL_CASE_ARGS_I(__VA_ARGS__) +#define IOCTL_CASE_NOARGS(...) IOCTL_CASE_NOARGS_I(__VA_ARGS__) +#define IOCTL_CASE_RESULT(...) IOCTL_CASE_RESULT_I(__VA_ARGS__) + +#define VARIABLE_IOCTL_CASE_ARGS_I(out, in, magic, function, name, ...) \ + case MetaVariableIoctlDescriptor::Raw(): { \ + using IoctlType = MetaVariableIoctlDescriptor; \ + auto args = DecodeArguments(buffer); \ + return std::apply(&className::name, std::tuple_cat(std::make_tuple(this), args)); \ + } + +#define VARIABLE_IOCTL_CASE_ARGS(...) VARIABLE_IOCTL_CASE_ARGS_I(__VA_ARGS__) + +#define IN false, true +#define OUT true, false +#define INOUT true, true +#define NONE false, false +#define SIZE(size) size +#define FUNC(func) func +#define MAGIC(magic) magic +#define ARGS(...) __VA_ARGS__ diff --git a/app/src/main/cpp/skyline/services/nvdrv/devices/deserialisation/macro_undef.h b/app/src/main/cpp/skyline/services/nvdrv/devices/deserialisation/macro_undef.h new file mode 100644 index 00000000..34af31c7 --- /dev/null +++ b/app/src/main/cpp/skyline/services/nvdrv/devices/deserialisation/macro_undef.h @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT OR MPL-2.0 +// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#undef IOCTL_HANDLER_FUNC +#undef VARIABLE_IOCTL_HANDLER_FUNC +#undef IOCTL_CASE_ARGS_I +#undef IOCTL_CASE_NOARGS_I +#undef IOCTL_CASE_RESULT_I +#undef IOCTL_CASE_ARGS +#undef IOCTL_CASE_NOARGS +#undef IOCTL_CASE_RESULT +#undef VARIABLE_IOCTL_CASE_ARGS_I +#undef VARIABLE_IOCTL_CASE_ARGS +#undef IN +#undef OUT +#undef INOUT +#undef NONE +#undef SIZE +#undef FUNC +#undef MAGIC +#undef ARGS diff --git a/app/src/main/cpp/skyline/services/nvdrv/devices/deserialisation/types.h b/app/src/main/cpp/skyline/services/nvdrv/devices/deserialisation/types.h new file mode 100644 index 00000000..91df017e --- /dev/null +++ b/app/src/main/cpp/skyline/services/nvdrv/devices/deserialisation/types.h @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: MIT OR MPL-2.0 +// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#pragma once + +#include +#include +#include + +namespace skyline::service::nvdrv::deserialisation { + // IOCTL parameters can't be pointers and must be trivially copyable + template + concept BufferData = std::is_trivially_copyable_v && !std::is_pointer_v; + + // Input IOCTL template types + template requires BufferData + using In = const T; + + template + struct IsIn : std::false_type {}; + + template + struct IsIn> : std::true_type {}; + + + // Input/Output IOCTL template types + template requires BufferData + using InOut = T &; + + template + struct IsInOut : std::false_type {}; + + template + struct IsInOut> : std::true_type {}; + + template requires IsInOut::value + using RemoveInOut = typename std::remove_reference::type; + + + // Output IOCTL template types + template requires BufferData + class Out { + private: + T &ref; + + public: + Out(T &ref) : ref(ref) {} + + using ValueType = T; + + Out& operator=(T val) { + ref = std::move(val); + return *this; + } + }; + + template + struct IsOut : std::false_type {}; + + template + struct IsOut> : std::true_type {}; + + template requires IsOut::value + using RemoveOut = typename T::ValueType; + + + template requires BufferData + using AutoSizeSpan = span; + + template + struct IsAutoSizeSpan : std::false_type {}; + + template + struct IsAutoSizeSpan> : std::true_type {}; + + template requires IsAutoSizeSpan::value + using RemoveAutoSizeSpan = typename T::element_type; + + // Padding template type + template requires BufferData + struct Pad { + static constexpr auto Bytes{Count * sizeof(T)}; + }; + + template + struct IsPad : std::false_type {}; + + template + struct IsPad> : std::true_type {}; + + /** + * @brief Describes an IOCTL as a type for use in deserialisation + */ + template + struct MetaIoctlDescriptor { + static constexpr auto Out{TOut}; + static constexpr auto In{TIn}; + static constexpr auto Size{TSize}; + static constexpr auto Magic{TMagic}; + static constexpr auto Function{TFunction}; + + constexpr operator IoctlDescriptor() const { + return {Out, In, Size, Magic, Function}; + } + + constexpr static u32 Raw() { + u32 raw{Function}; + + i8 offset{8}; + raw |= Magic << offset; offset += 8; + raw |= Size << offset; offset += 14; + raw |= In << offset; offset++; + raw |= Out << offset; offset++; + return raw; + } + }; + + /** + * @brief Describes a variable length IOCTL as a type for use in deserialisation + */ + template + struct MetaVariableIoctlDescriptor { + static constexpr auto Out{TOut}; + static constexpr auto In{TIn}; + static constexpr auto Size{std::dynamic_extent}; + static constexpr auto Magic{TMagic}; + static constexpr auto Function{TFunction}; + + constexpr static u32 Raw() { + u32 raw{Function}; + + i8 offset{8}; + raw |= Magic << offset; offset += 8; + offset += 14; // Use a 0 size for raw + raw |= In << offset; offset++; + raw |= Out << offset; offset++; + return raw; + } + }; +}