// Copyright 2014 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #pragma once #include #include #include #include "common/common_types.h" namespace Kernel { /* * This class defines the SVC ABI, and provides a wrapper template for translating between ARM * registers and SVC parameters and return value. The SVC context class should inherit from this * class using CRTP (`class SVC : public SVCWrapper {...}`), and use the Wrap() template to * convert a SVC function interface to a void()-type function that interacts with registers * interface GetReg() and SetReg(). */ template class SVCWrapper { protected: template void Wrap() { WrapHelper::Call(*static_cast(this), F); }; private: // A special param index represents the return value static constexpr std::size_t INDEX_RETURN = ~(std::size_t)0; struct ParamSlot { // whether the slot is used for a param bool used; // index of a param in the function signature, starting from 0, or special value // INDEX_RETURN std::size_t param_index; // index of a 32-bit word within the param std::size_t word_index; }; // Size in words of the given type in ARM ABI template static constexpr std::size_t WordSize() { static_assert(std::is_trivially_copyable_v); if constexpr (std::is_pointer_v) { return 1; } else if constexpr (std::is_same_v) { return 1; } else { return (sizeof(T) - 1) / 4 + 1; } } // Size in words of the given type in ARM ABI with pointer removed template static constexpr std::size_t OutputWordSize() { if constexpr (std::is_pointer_v) { return WordSize>(); } else { return 0; } } // Describes the register assignments of a SVC struct SVCABI { static constexpr std::size_t RegCount = 8; // Register assignments for input params. // For example, in[0] records which input param and word r0 stores std::array in{}; // Register assignments for output params. // For example, out[0] records which output param (with pointer removed) and word r0 stores std::array out{}; // Search the register assignments for a word of a param constexpr std::size_t GetRegisterIndex(std::size_t param_index, std::size_t word_index) const { for (std::size_t slot = 0; slot < RegCount; ++slot) { if (in[slot].used && in[slot].param_index == param_index && in[slot].word_index == word_index) { return slot; } if (out[slot].used && out[slot].param_index == param_index && out[slot].word_index == word_index) { return slot; } } // should throw, but gcc bugs out here return 0x12345678; } }; // Generates ABI info for a given SVC signature R(Context::)(T...) template static constexpr SVCABI GetSVCABI() { constexpr std::size_t param_count = sizeof...(T); std::array param_is_output{{std::is_pointer_v...}}; std::array param_size{{WordSize()...}}; std::array output_param_size{{OutputWordSize()...}}; std::array param_need_align{ {(std::is_same_v || std::is_same_v)...}}; // derives ARM ABI, which assigns all params to r0~r3 and then stack std::array armabi_reg{}; std::array armabi_stack{}; std::size_t reg_pos = 0; std::size_t stack_pos = 0; for (std::size_t i = 0; i < param_count; ++i) { if (param_need_align[i]) { reg_pos += reg_pos % 2; } for (std::size_t j = 0; j < param_size[i]; ++j) { if (reg_pos == 4) { armabi_stack[stack_pos++] = ParamSlot{true, i, j}; } else { armabi_reg[reg_pos++] = ParamSlot{true, i, j}; } } } // now derives SVC ABI which is a modified version of ARM ABI SVCABI mod_abi{}; std::size_t out_pos = 0; // next available output register // assign return value to output registers if constexpr (!std::is_void_v) { for (std::size_t j = 0; j < WordSize(); ++j) { mod_abi.out[out_pos++] = {true, INDEX_RETURN, j}; } } // assign output params (pointer-type params) to output registers for (std::size_t i = 0; i < param_count; ++i) { if (param_is_output[i]) { for (std::size_t j = 0; j < output_param_size[i]; ++j) { mod_abi.out[out_pos++] = ParamSlot{true, i, j}; } } } // assign input params (non-pointer-type params) to input registers stack_pos = 0; // next available stack param for (std::size_t k = 0; k < mod_abi.in.size(); ++k) { if (k < armabi_reg.size() && armabi_reg[k].used && !param_is_output[armabi_reg[k].param_index]) { // If this is within the ARM ABI register range and it is a used input param, use // the same register position mod_abi.in[k] = armabi_reg[k]; } else { // Otherwise, assign it with the next available stack input // If all stack inputs have been allocated, this would do nothing // and leaves the slot unused. // Loop until an input stack param is found while (stack_pos < armabi_stack.size() && (!armabi_stack[stack_pos].used || param_is_output[armabi_stack[stack_pos].param_index])) { ++stack_pos; } // Reassign the stack param to the free register if (stack_pos < armabi_stack.size()) { mod_abi.in[k] = armabi_stack[stack_pos++]; } } } return mod_abi; } template static constexpr std::array GetRegIndicesImpl( SVCABI abi, std::index_sequence) { return {{abi.GetRegisterIndex(param_index, indices)...}}; } // Gets assigned register index for the param_index-th param of word_size in a function with // signature R(Context::)(Ts...) template static constexpr std::array GetRegIndices() { constexpr SVCABI abi = GetSVCABI(); return GetRegIndicesImpl(abi, std::make_index_sequence{}); } // Gets the value for the param_index-th param of word_size in a function with signature // R(Context::)(Ts...) // T is the param type, which must be a non-pointer as it is an input param. // Must hold: Ts[param_index] == T template static void GetParam(Context& context, T& value) { static_assert(!std::is_pointer_v, "T should not be a pointer"); constexpr auto regi = GetRegIndices(), R, Ts...>(); if constexpr (std::is_class_v || std::is_union_v) { std::array()> buf; for (std::size_t i = 0; i < WordSize(); ++i) { buf[i] = context.GetReg(regi[i]); } std::memcpy(&value, &buf, sizeof(T)); } else if constexpr (WordSize() == 2) { u64 l = context.GetReg(regi[0]); u64 h = context.GetReg(regi[1]); value = static_cast(l | (h << 32)); } else if constexpr (std::is_same_v) { // TODO: Is this correct or should only test the lowest byte? value = context.GetReg(regi[0]) != 0; } else { value = static_cast(context.GetReg(regi[0])); } } // Sets the value for the param_index-th param of word_size in a function with signature // R(Context::)(Ts...) // T is the param type with pointer removed, which was originally a pointer-type output param // Must hold: (Ts[param_index] == T*) || (R==T && param_index == INDEX_RETURN) template static void SetParam(Context& context, const T& value) { static_assert(!std::is_pointer_v, "T should have pointer removed before passing in"); constexpr auto regi = GetRegIndices(), R, Ts...>(); if constexpr (std::is_class_v || std::is_union_v) { std::array()> buf; std::memcpy(&buf, &value, sizeof(T)); for (std::size_t i = 0; i < WordSize(); ++i) { context.SetReg(regi[i], buf[i]); } } else if constexpr (WordSize() == 2) { u64 u = static_cast(value); context.SetReg(regi[0], static_cast(u & 0xFFFFFFFF)); context.SetReg(regi[1], static_cast(u >> 32)); } else { u32 u = static_cast(value); context.SetReg(regi[0], u); } } template struct WrapPass; template struct WrapPass { // Do I/O for the param T in function R(Context::svc)(Us..., T, Ts...) and then move on to // the next param. // Us are params whose I/O is already handled. // T is the current param to do I/O. // Ts are params whose I/O is not handled yet. template static void Call(Context& context, SVCT svc, Us... u) { static_assert(std::is_same_v); constexpr std::size_t current_param_index = sizeof...(Us); if constexpr (std::is_pointer_v) { using OutputT = std::remove_pointer_t; OutputT output; WrapPass::Call(context, svc, u..., &output); SetParam(context, output); } else { T input; GetParam(context, input); WrapPass::Call(context, svc, u..., input); } } }; template struct WrapPass { // Call function R(Context::svc)(Us...) and transfer the return value to registers template static void Call(Context& context, SVCT svc, Us... u) { static_assert(std::is_same_v); if constexpr (std::is_void_v) { (context.*svc)(u...); } else { R r = (context.*svc)(u...); SetParam(context, r); } } }; template struct WrapHelper; template struct WrapHelper { static void Call(Context& context, R (Context::*svc)(T...)) { WrapPass::Call(context, svc /*Empty for Us*/); } }; }; } // namespace Kernel