Work around invalid values in CNTFRQ_EL0 register

Exynos SoCs have a bug where the `CNTFRQ_EL0` register is either set to 0 or contain incoherent values. With this patch, the frequency value is loaded into a static variable and used instead of reading the register. The value will be initialised to the correct value for affected SoCs, while unaffected ones will use the value from the register.
This commit is contained in:
lynxnb 2022-11-17 19:28:48 +01:00 committed by Niccolò Betto
parent 54d0246ca6
commit 70109f8fbd
4 changed files with 77 additions and 40 deletions

View File

@ -8,6 +8,7 @@
#include <span>
#include <frozen/unordered_map.h>
#include <frozen/string.h>
#include <sys/system_properties.h>
#include <type_traits>
#include <xxhash.h>
#include "base.h"
@ -20,13 +21,38 @@ namespace skyline::util {
template<typename T>
concept TrivialObject = std::is_trivially_copyable_v<T> && !requires(T v) { v.data(); };
namespace detail {
/**
* @brief Retrieves the system counter clock frequency
* @note Some devices report an incorrect value so they need special handling
*/
inline u64 InitFrequency() {
char buffer[PROP_VALUE_MAX];
int len{__system_property_get("ro.product.board", buffer)};
std::string_view board{buffer, static_cast<size_t>(len)};
u64 frequency;
if (board == "s5e9925") // Exynos 2200
frequency = 25600000;
else if (board == "exynos2100") // Exynos 2100
frequency = 26000000;
else if (board == "exynos9810") // Exynos 9810
frequency = 26000000;
else
asm volatile("MRS %0, CNTFRQ_EL0" : "=r"(frequency));
return frequency;
}
}
inline const u64 ClockFrequency{detail::InitFrequency()}; //!< The system counter clock frequency in Hz
/**
* @brief Returns the current time in nanoseconds
* @return The current time in nanoseconds
*/
inline i64 GetTimeNs() {
u64 frequency;
asm("MRS %0, CNTFRQ_EL0" : "=r"(frequency));
u64 frequency{ClockFrequency};
u64 ticks;
asm volatile("MRS %0, CNTVCT_EL0" : "=r"(ticks));
return static_cast<i64>(((ticks / frequency) * constant::NsInSecond) + (((ticks % frequency) * constant::NsInSecond + (frequency / 2)) / frequency));
@ -245,7 +271,7 @@ namespace skyline::util {
};
namespace detail {
static thread_local std::mt19937_64 generator{GetTimeTicks()};
inline thread_local std::mt19937_64 generator{GetTimeTicks()};
}
/**
@ -326,7 +352,6 @@ namespace skyline::util {
using Type = T;
};
template<typename T>
struct IsIncrementingT : std::false_type {};

View File

@ -297,6 +297,50 @@ namespace skyline::nce {
return code;
}
constexpr size_t RescaleClockSize{17}; //!< The size of the RescaleClock function in 32-bit ARMv8 instructions
/**
* @brief Writes instructions to rescale the host clock to Tegra X1 levels
* @note Output is on stack with the stack pointer offset 32B from the initial point
*/
u32 *WriteRescaleClock(u32 *code) {
/* Reserve 32B of stack */
/* Save working registers */
*code++ = 0xD10083FF; // SUB SP, SP, #32
*code++ = 0xA90107E0; // STP X0, X1, [SP, #16]
/* Load magic constant */
*code++ = 0xD28F0860; // MOV X0, #30787
*code++ = 0xF2AE3680; // MOVK X0, #29108, LSL #16
*code++ = 0xF2CB5880; // MOVK X0, #23236, LSL #32
*code++ = 0xF2E14F80; // MOVK X0, #2684, LSL #48
/* Load clock frequency value */
for (const auto &mov : instructions::MoveRegister(registers::X1, util::ClockFrequency))
if (mov)
*code++ = mov;
/* Multiply clock frequency by magic constant */
*code++ = 0xD345FC21; // LSR X1, X1, #5
*code++ = 0x9BC07C21; // UMULH X1, X1, X0
*code++ = 0xD347FC21; // LSR X1, X1, #7
/* Load counter value */
*code++ = 0xD53BE040; // MRS X0, CNTVCT_EL0
/* Rescale counter value */
*code++ = 0x9AC10801; // UDIV X1, X0, X1
*code++ = 0x8B010421; // ADD X1, X1, X1, LSL #1
*code++ = 0xD37AE420; // LSL X0, X1, #6
/* Store result */
/* Restore registers */
*code++ = 0xF90003E0; // STR X0, [SP, #0]
*code++ = 0xA94107E0; // LDP X0, X1, [SP, #16]
return code;
}
constexpr u32 TpidrEl0{0x5E82}; // ID of TPIDR_EL0 in MRS
constexpr u32 TpidrroEl0{0x5E83}; // ID of TPIDRRO_EL0 in MRS
constexpr u32 CntfrqEl0{0x5F00}; // ID of CNTFRQ_EL0 in MRS
@ -308,9 +352,7 @@ namespace skyline::nce {
size_t size{guest::SaveCtxSize + guest::LoadCtxSize + TrampolineSize};
std::vector<size_t> offsets;
u64 frequency;
asm("MRS %0, CNTFRQ_EL0" : "=r"(frequency));
bool rescaleClock{frequency != TegraX1Freq};
bool rescaleClock{util::ClockFrequency != TegraX1Freq};
auto start{reinterpret_cast<const u32 *>(text.data())}, end{reinterpret_cast<const u32 *>(text.data() + text.size())};
for (const u32 *instruction{start}; instruction < end; instruction++) {
@ -329,7 +371,7 @@ namespace skyline::nce {
} else {
if (rescaleClock) {
if (mrs.srcReg == CntpctEl0) {
size += guest::RescaleClockSize + 3;
size += RescaleClockSize + 3;
offsets.push_back(instructionOffset);
} else if (mrs.srcReg == CntfrqEl0) {
size += 3;
@ -359,9 +401,7 @@ namespace skyline::nce {
std::memcpy(patch, reinterpret_cast<void *>(&guest::LoadCtx), guest::LoadCtxSize * sizeof(u32));
patch += guest::LoadCtxSize;
u64 frequency;
asm("MRS %0, CNTFRQ_EL0" : "=r"(frequency));
bool rescaleClock{frequency != TegraX1Freq};
bool rescaleClock{util::ClockFrequency != TegraX1Freq};
for (auto offset : offsets) {
u32 *instruction{reinterpret_cast<u32 *>(text.data()) + offset};
@ -424,8 +464,7 @@ namespace skyline::nce {
*instruction = instructions::B(static_cast<i32>(endOffset() + offset), true).raw;
/* Rescale host clock */
std::memcpy(patch, reinterpret_cast<void *>(&guest::RescaleClock), guest::RescaleClockSize * sizeof(u32));
patch += guest::RescaleClockSize;
patch = WriteRescaleClock(patch);
/* Load result from stack into destination register */
instructions::Ldr ldr(0xF94003E0); // LDR XOUT, [SP]

View File

@ -93,23 +93,3 @@ LoadCtx:
/* Restore Scratch Register */
LDR LR, [SP, #8]
RET
.global RescaleClock
RescaleClock:
SUB SP, SP, #32
STP X0, X1, [SP, #16]
MOV X0, #30787
MOVK X0, #29108, LSL #16
MOVK X0, #23236, LSL #32
MOVK X0, #2684, LSL #48
MRS X1, CNTFRQ_EL0
LSR X1, X1, #5
UMULH X1, X1, X0
LSR X1, X1, #7
MRS X0, CNTVCT_EL0
UDIV X1, X0, X1
ADD X1, X1, X1, LSL #1
LSL X0, X1, #6
STR X0, [SP, #0]
LDP X0, X1, [SP, #16]

View File

@ -105,7 +105,6 @@ namespace skyline {
namespace guest {
constexpr size_t SaveCtxSize{34}; //!< The size of the SaveCtx function in 32-bit ARMv8 instructions
constexpr size_t LoadCtxSize{34}; //!< The size of the LoadCtx function in 32-bit ARMv8 instructions
constexpr size_t RescaleClockSize{16}; //!< The size of the RescaleClock function in 32-bit ARMv8 instructions
/**
* @brief Saves the context from CPU registers into TLS
@ -118,12 +117,6 @@ namespace skyline {
* @note Assumes that 8B is reserved at an offset of 8B from SP
*/
extern "C" void LoadCtx(void);
/**
* @brief Rescales the host clock to Tegra X1 levels
* @note Output is on stack with the stack pointer offset 32B from the initial point
*/
extern "C" __noreturn void RescaleClock(void);
}
}
}