From fb6c279a24965b87342a07893a7493aca36862f5 Mon Sep 17 00:00:00 2001 From: Augustin Cavalier Date: Fri, 22 Aug 2025 16:42:39 -0400 Subject: [PATCH] kernel/x86: Use MWAITX or TPAUSE in arch_debug_snooze, if available. These instructions are only available on more recent CPUs (MWAITX on AMD since around 2015 or so, TPAUSE on Intel since around 2020.) They allow idly waiting on the TSC even when interrupts are disabled. Most hypervisors do not have these available (KVM does provide them, though not on all configurations), but on bare metal this should make a nice difference to KDL power consumption: I tested with a Ryzen 3700X, according to my UPS (so including monitor and peripherals) the system used ~106 W at idle, 160 W in KDL before this patch, and 125 W in KDL after it. Change-Id: Id7a22ecd33f3fc005b2c312f945dc3cd364e96fa Reviewed-on: https://review.haiku-os.org/c/haiku/+/9604 Tested-by: Commit checker robot Reviewed-by: waddlesplash --- headers/private/kernel/arch/x86/arch_cpu.h | 6 ++- src/system/kernel/arch/x86/arch_cpu.cpp | 4 ++ src/system/kernel/arch/x86/arch_debug.cpp | 57 +++++++++++++++++++++- 3 files changed, 64 insertions(+), 3 deletions(-) diff --git a/headers/private/kernel/arch/x86/arch_cpu.h b/headers/private/kernel/arch/x86/arch_cpu.h index 9c6c3ae424..9d9a77affb 100644 --- a/headers/private/kernel/arch/x86/arch_cpu.h +++ b/headers/private/kernel/arch/x86/arch_cpu.h @@ -265,8 +265,9 @@ #define IA32_FEATURE_EXT_HYPERVISOR (1 << 31) // Running on a hypervisor // x86 features from cpuid eax 0x80000001, ecx register (AMD) -#define IA32_FEATURE_AMD_EXT_CMPLEGACY (1 << 1) // Core MP legacy mode -#define IA32_FEATURE_AMD_EXT_TOPOLOGY (1 << 22) // Topology extensions +#define IA32_FEATURE_AMD_EXT_CMPLEGACY (1 << 1) // Core MP legacy mode +#define IA32_FEATURE_AMD_EXT_TOPOLOGY (1 << 22) // Topology extensions +#define IA32_FEATURE_AMD_EXT_MWAITX (1 << 29) // MWAITX, MONITORX instructions // x86 features from cpuid eax 0x80000001, edx register (AMD) // only care about the ones that are unique to this register @@ -354,6 +355,7 @@ #define IA32_FEATURE_UMIP (1 << 2) // User-mode Instruction Prevention #define IA32_FEATURE_PKU (1 << 3) // Memory Protection Keys for User-mode pages #define IA32_FEATURE_OSPKE (1 << 4) // PKU enabled by OS +#define IA32_FEATURE_WAITPKG (1 << 5) // TPAUSE, UMONITOR, UMWAIT instructions #define IA32_FEATURE_AVX512VMBI2 (1 << 6) // AVX-512 Vector Bit Manipulation Instructions 2 #define IA32_FEATURE_GFNI (1 << 8) // Galois Field instructions #define IA32_FEATURE_VAES (1 << 9) // AES instruction set (VEX-256/EVEX) diff --git a/src/system/kernel/arch/x86/arch_cpu.cpp b/src/system/kernel/arch/x86/arch_cpu.cpp index 043f3d226a..c9fea50cdc 100644 --- a/src/system/kernel/arch/x86/arch_cpu.cpp +++ b/src/system/kernel/arch/x86/arch_cpu.cpp @@ -500,6 +500,8 @@ dump_feature_string(int currentCPU, cpu_ent* cpu) strlcat(features, "rdrnd ", sizeof(features)); if (cpu->arch.feature[FEATURE_EXT] & IA32_FEATURE_EXT_HYPERVISOR) strlcat(features, "hypervisor ", sizeof(features)); + if (cpu->arch.feature[FEATURE_EXT_AMD_ECX] & IA32_FEATURE_AMD_EXT_MWAITX) + strlcat(features, "mwaitx ", sizeof(features)); if (cpu->arch.feature[FEATURE_EXT_AMD] & IA32_FEATURE_AMD_EXT_SYSCALL) strlcat(features, "syscall ", sizeof(features)); if (cpu->arch.feature[FEATURE_EXT_AMD] & IA32_FEATURE_AMD_EXT_NX) @@ -624,6 +626,8 @@ dump_feature_string(int currentCPU, cpu_ent* cpu) strlcat(features, "pku ", sizeof(features)); if (cpu->arch.feature[FEATURE_7_ECX] & IA32_FEATURE_OSPKE) strlcat(features, "ospke ", sizeof(features)); + if (cpu->arch.feature[FEATURE_7_ECX] & IA32_FEATURE_WAITPKG) + strlcat(features, "waitpkg ", sizeof(features)); if (cpu->arch.feature[FEATURE_7_ECX] & IA32_FEATURE_AVX512VMBI2) strlcat(features, "avx512vmbi2 ", sizeof(features)); if (cpu->arch.feature[FEATURE_7_ECX] & IA32_FEATURE_GFNI) diff --git a/src/system/kernel/arch/x86/arch_debug.cpp b/src/system/kernel/arch/x86/arch_debug.cpp index 6a1edfb6dd..bf9d665abd 100644 --- a/src/system/kernel/arch/x86/arch_debug.cpp +++ b/src/system/kernel/arch/x86/arch_debug.cpp @@ -11,6 +11,7 @@ #include +#include #include #include @@ -1357,9 +1358,52 @@ arch_debug_gdb_get_registers(char* buffer, size_t bufferSize) } +static void (*sDebugSnooze)(bigtime_t) = NULL; +static uint64 sDebugSnoozeConversionFactor = 0; + + +static void +debug_snooze_mwaitx(bigtime_t duration) +{ + uint32 delay = (duration * sDebugSnoozeConversionFactor) / 1000; + if (delay == 0) + delay = 1; + + // monitorx (r/eax = pointer, ecx = extensions, edx = hints) + asm volatile(".byte 0x0f, 0x01, 0xfa;" + :: "a" (sDebugSnooze), "c" (0), "d" (0)); + + // mwaitx (eax = hints, ecx = extensions, ebx = timeout) + asm volatile(".byte 0x0f, 0x01, 0xfb;" + :: "a" (0xf0 /* disable C-states */), "c" (0x2 /* enable timer */), "b" (delay)); +} + + +static void +debug_snooze_tpause(bigtime_t duration) +{ + uint32 delay = (duration * sDebugSnoozeConversionFactor) / 1000; + if (delay == 0) + delay = 1; + + memory_read_barrier(); + uint64 target = __rdtsc() + delay; + + // tpause (ecx = options, eax = target [low 32], edx = target [high 32]) + uint32 low = target, high = target >> 32; + asm volatile(".byte 0x66, 0x0f, 0xae, 0xf1;" + :: "c" (0x0), "a" (low), "d" (high)); +} + + void arch_debug_snooze(bigtime_t duration) { + if (sDebugSnooze != NULL) { + sDebugSnooze(duration); + return; + } + spin(duration); } @@ -1367,7 +1411,18 @@ arch_debug_snooze(bigtime_t duration) status_t arch_debug_init(kernel_args* args) { - // at this stage, the debugger command system is alive + bool haveMWAITX = x86_check_feature(IA32_FEATURE_AMD_EXT_MWAITX, FEATURE_EXT_AMD_ECX), + haveTPAUSE = x86_check_feature(IA32_FEATURE_WAITPKG, FEATURE_7_ECX); + if (haveMWAITX || haveTPAUSE) { + // Store the TSC frequency in kHz. + sDebugSnoozeConversionFactor = + (uint64(1000) << 32) / args->arch_args.system_time_cv_factor; + + if (haveMWAITX) + sDebugSnooze = debug_snooze_mwaitx; + else if (haveTPAUSE) + sDebugSnooze = debug_snooze_tpause; + } add_debugger_command("where", &stack_trace, "Same as \"sc\""); add_debugger_command("bt", &stack_trace, "Same as \"sc\" (as in gdb)");