Preserve the two guest tick margin in SVC timeout deadlines (#338)

This commit is contained in:
Bruno 2026-10-03 13:32:41 +02:00 committed by GitHub
parent 4e3a9cbbdb
commit ba84dd878a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 35 additions and 70 deletions

View File

@ -3,7 +3,10 @@
#pragma once #pragma once
#include <limits>
#include "core/hle/kernel/k_hardware_timer_base.h" #include "core/hle/kernel/k_hardware_timer_base.h"
#include "yuzu_common/wall_clock.h"
namespace Core::Timing { namespace Core::Timing {
struct EventType; struct EventType;
@ -13,6 +16,12 @@ namespace Kernel {
class KHardwareTimer : /* public KInterruptTask, */ public KHardwareTimerBase { class KHardwareTimer : /* public KInterruptTask, */ public KHardwareTimerBase {
public: public:
// SVC timeouts include two guest timer ticks. Our deadlines remain in
// nanoseconds, so round this margin up rather than adding two nanoseconds.
static constexpr s64 DefaultTimeIncrementNanoseconds =
(2 * 1'000'000'000LL + Common::WallClock::CNTFRQ - 1) / Common::WallClock::CNTFRQ;
static_assert(DefaultTimeIncrementNanoseconds == 105);
explicit KHardwareTimer(KernelCore& kernel) : KHardwareTimerBase{kernel} {} explicit KHardwareTimer(KernelCore& kernel) : KHardwareTimerBase{kernel} {}
// Public API. // Public API.
@ -21,6 +30,27 @@ public:
s64 GetTick() const; s64 GetTick() const;
s64 GetTimeoutDeadlineNs(s64 timeout_ns) const {
// Preserve immediate polling and infinite waits without reading the clock.
if (timeout_ns <= 0) {
return timeout_ns;
}
constexpr s64 max_deadline = (std::numeric_limits<s64>::max)();
if (timeout_ns > max_deadline - DefaultTimeIncrementNanoseconds) {
return max_deadline;
}
const s64 offset_ns = timeout_ns + DefaultTimeIncrementNanoseconds;
const s64 now_ns = GetTick();
// The timer is nonnegative. Check the remaining range before adding;
// a check after signed overflow would already be undefined behavior.
if (static_cast<u64>(now_ns) > static_cast<u64>(max_deadline - offset_ns)) {
return max_deadline;
}
return now_ns + offset_ns;
}
void RegisterAbsoluteTask(KTimerTask* task, s64 task_time) { void RegisterAbsoluteTask(KTimerTask* task, s64 task_time) {
KScopedDisableDispatch dd{m_kernel}; KScopedDisableDispatch dd{m_kernel};
KScopedSpinLock lk{this->GetLock()}; KScopedSpinLock lk{this->GetLock()};

View File

@ -48,21 +48,7 @@ Result WaitForAddress(Core::System& system, u64 address, ArbitrationType arb_typ
R_UNLESS(Common::IsAligned(address, sizeof(s32)), ResultInvalidAddress); R_UNLESS(Common::IsAligned(address, sizeof(s32)), ResultInvalidAddress);
R_UNLESS(IsValidArbitrationType(arb_type), ResultInvalidEnumValue); R_UNLESS(IsValidArbitrationType(arb_type), ResultInvalidEnumValue);
// Convert timeout from nanoseconds to ticks. const s64 timeout = system.Kernel().HardwareTimer().GetTimeoutDeadlineNs(timeout_ns);
s64 timeout{};
if (timeout_ns > 0) {
const s64 offset_tick(timeout_ns);
if (offset_tick > 0) {
timeout = system.Kernel().HardwareTimer().GetTick() + offset_tick + 2;
if (timeout <= 0) {
timeout = std::numeric_limits<s64>::max();
}
} else {
timeout = std::numeric_limits<s64>::max();
}
} else {
timeout = timeout_ns;
}
R_RETURN( R_RETURN(
GetCurrentProcess(system.Kernel()).WaitAddressArbiter(address, arb_type, value, timeout)); GetCurrentProcess(system.Kernel()).WaitAddressArbiter(address, arb_type, value, timeout));

View File

@ -21,21 +21,7 @@ Result WaitProcessWideKeyAtomic(Core::System& system, u64 address, u64 cv_key, u
R_UNLESS(!IsKernelAddress(address), ResultInvalidCurrentMemory); R_UNLESS(!IsKernelAddress(address), ResultInvalidCurrentMemory);
R_UNLESS(Common::IsAligned(address, sizeof(s32)), ResultInvalidAddress); R_UNLESS(Common::IsAligned(address, sizeof(s32)), ResultInvalidAddress);
// Convert timeout from nanoseconds to ticks. const s64 timeout = system.Kernel().HardwareTimer().GetTimeoutDeadlineNs(timeout_ns);
s64 timeout{};
if (timeout_ns > 0) {
const s64 offset_tick(timeout_ns);
if (offset_tick > 0) {
timeout = system.Kernel().HardwareTimer().GetTick() + offset_tick + 2;
if (timeout <= 0) {
timeout = std::numeric_limits<s64>::max();
}
} else {
timeout = std::numeric_limits<s64>::max();
}
} else {
timeout = timeout_ns;
}
// Wait on the condition variable. // Wait on the condition variable.
R_RETURN( R_RETURN(

View File

@ -53,22 +53,7 @@ Result ReplyAndReceiveImpl(KernelCore& kernel, int32_t* out_index, uintptr_t mes
// Receive a message. // Receive a message.
{ {
// Convert the timeout from nanoseconds to ticks. const s64 timeout = kernel.HardwareTimer().GetTimeoutDeadlineNs(timeout_ns);
// NOTE: Nintendo does not use this conversion logic in WaitSynchronization...
s64 timeout;
if (timeout_ns > 0) {
const s64 offset_tick(timeout_ns);
if (offset_tick > 0) {
timeout = kernel.HardwareTimer().GetTick() + offset_tick + 2;
if (timeout <= 0) {
timeout = std::numeric_limits<s64>::max();
}
} else {
timeout = std::numeric_limits<s64>::max();
}
} else {
timeout = timeout_ns;
}
// Wait for a message. // Wait for a message.
while (true) { while (true) {

View File

@ -84,17 +84,7 @@ Result WaitSynchronization(Core::System& system, int32_t* out_index, u64 user_ha
} }
}; };
// Convert the timeout from nanoseconds to ticks. const s64 timeout = kernel.HardwareTimer().GetTimeoutDeadlineNs(timeout_ns);
s64 timeout;
if (timeout_ns > 0) {
u64 ticks = kernel.HardwareTimer().GetTick();
ticks += timeout_ns;
ticks += 2;
timeout = ticks;
} else {
timeout = timeout_ns;
}
// Wait on the objects. // Wait on the objects.
Result res = KSynchronizationObject::Wait(kernel, out_index, objs.data(), num_handles, timeout); Result res = KSynchronizationObject::Wait(kernel, out_index, objs.data(), num_handles, timeout);

View File

@ -111,19 +111,7 @@ void SleepThread(Core::System& system, s64 ns) {
// When the input tick is positive, sleep. // When the input tick is positive, sleep.
if (ns > 0) { if (ns > 0) {
// Convert the timeout from nanoseconds to ticks. const s64 timeout = kernel.HardwareTimer().GetTimeoutDeadlineNs(ns);
// NOTE: Nintendo does not use this conversion logic in WaitSynchronization...
s64 timeout;
const s64 offset_tick(ns);
if (offset_tick > 0) {
timeout = kernel.HardwareTimer().GetTick() + offset_tick + 2;
if (timeout <= 0) {
timeout = std::numeric_limits<s64>::max();
}
} else {
timeout = std::numeric_limits<s64>::max();
}
// Sleep. // Sleep.
// NOTE: Nintendo does not check the result of this sleep. // NOTE: Nintendo does not check the result of this sleep.