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.
This commit is contained in:
Billy Laws 2021-07-17 17:10:12 +01:00 committed by ◱ Mark
parent 75a67dcfa5
commit 3340c74332
4 changed files with 298 additions and 0 deletions

View File

@ -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 <type_traits>
#include <common.h>
#include "types.h"
namespace skyline::service::nvdrv::deserialisation {
template<typename Desc, typename ArgType> requires (Desc::In && IsIn<ArgType>::value)
constexpr ArgType DecodeArgument(span<u8, Desc::Size> buffer, size_t &offset) {
auto out{buffer.subspan(offset).template as<ArgType>()};
offset += sizeof(ArgType);
return out;
}
template<typename Desc, typename ArgType> requires (Desc::Out && Desc::In && IsInOut<ArgType>::value)
constexpr ArgType DecodeArgument(span<u8, Desc::Size> buffer, size_t &offset) {
auto &out{buffer.subspan(offset).template as<RemoveInOut<ArgType>>()};
offset += sizeof(RemoveInOut<ArgType>);
return out;
}
template<typename Desc, typename ArgType> requires (Desc::Out && IsOut<ArgType>::value)
constexpr ArgType DecodeArgument(span<u8, Desc::Size> buffer, size_t &offset) {
auto out{Out(buffer.subspan(offset).template as<RemoveOut<ArgType>>())};
offset += sizeof(RemoveOut<ArgType>);
return out;
}
/**
* @brief Creates a reference preserving tuple of the given types
*/
template <typename...Ts>
constexpr auto make_ref_tuple(Ts&&...ts) {
return std::tuple<Ts...>{std::forward<Ts>(ts)...};
}
template<typename Desc, typename ArgType, typename... ArgTypes>
constexpr auto DecodeArgumentsImpl(span<u8, Desc::Size> buffer, size_t &offset) {
if constexpr (IsAutoSizeSpan<ArgType>::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<ArgType>)};
return make_ref_tuple(buffer.subspan(offset, extent * sizeof(RemoveAutoSizeSpan<ArgType>)).template cast<RemoveAutoSizeSpan<ArgType>>());
} else if constexpr (IsPad<ArgType>::value) {
offset += ArgType::Bytes;
return DecodeArgumentsImpl<Desc, ArgTypes...>(buffer, offset);
} else {
if constexpr(sizeof...(ArgTypes) == 0) {
return make_ref_tuple(DecodeArgument<Desc, ArgType>(buffer, offset));
} else {
return std::tuple_cat(make_ref_tuple(DecodeArgument<Desc, ArgType>(buffer, offset)),
DecodeArgumentsImpl<Desc, ArgTypes...>(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<typename Desc, typename... ArgTypes>
constexpr auto DecodeArguments(span<u8, Desc::Size> buffer) {
size_t offset{};
return DecodeArgumentsImpl<Desc, ArgTypes...>(buffer, offset);
}
}

View File

@ -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<u8> 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<u8> 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<out, in, size, magic, function>::Raw(): { \
using IoctlType = MetaIoctlDescriptor< out, in, size, magic, function>; \
auto args = DecodeArguments<IoctlType, __VA_ARGS__>(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<out, in, size, magic, function>::Raw(): \
return className::name();
#define IOCTL_CASE_RESULT_I(out, in, size, magic, function, result) \
case MetaIoctlDescriptor<out, in, size, magic, function>::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<out, in, magic, function>::Raw(): { \
using IoctlType = MetaVariableIoctlDescriptor<out, in, magic, function>; \
auto args = DecodeArguments<IoctlType, __VA_ARGS__>(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__

View File

@ -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

View File

@ -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 <concepts>
#include <common.h>
#include <services/nvdrv/types.h>
namespace skyline::service::nvdrv::deserialisation {
// IOCTL parameters can't be pointers and must be trivially copyable
template<typename T>
concept BufferData = std::is_trivially_copyable_v<T> && !std::is_pointer_v<T>;
// Input IOCTL template types
template<typename T> requires BufferData<T>
using In = const T;
template<typename>
struct IsIn : std::false_type {};
template<typename T>
struct IsIn<In<T>> : std::true_type {};
// Input/Output IOCTL template types
template<typename T> requires BufferData<T>
using InOut = T &;
template<typename>
struct IsInOut : std::false_type {};
template<typename T>
struct IsInOut<InOut<T>> : std::true_type {};
template<typename T> requires IsInOut<T>::value
using RemoveInOut = typename std::remove_reference<T>::type;
// Output IOCTL template types
template<typename T> requires BufferData<T>
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<typename>
struct IsOut : std::false_type {};
template<typename T>
struct IsOut<Out<T>> : std::true_type {};
template<typename T> requires IsOut<T>::value
using RemoveOut = typename T::ValueType;
template<typename T> requires BufferData<T>
using AutoSizeSpan = span<T>;
template<typename>
struct IsAutoSizeSpan : std::false_type {};
template<typename T>
struct IsAutoSizeSpan<AutoSizeSpan<T>> : std::true_type {};
template<typename T> requires IsAutoSizeSpan<T>::value
using RemoveAutoSizeSpan = typename T::element_type;
// Padding template type
template<typename T, size_t Count = 1> requires BufferData<T>
struct Pad {
static constexpr auto Bytes{Count * sizeof(T)};
};
template<typename>
struct IsPad : std::false_type {};
template<typename T, size_t TCount>
struct IsPad<Pad<T, TCount>> : std::true_type {};
/**
* @brief Describes an IOCTL as a type for use in deserialisation
*/
template <bool TOut, bool TIn, u16 TSize, i8 TMagic, u8 TFunction>
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 <bool TOut, bool TIn, i8 TMagic, u8 TFunction>
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;
}
};
}