diff --git a/xen/arch/x86/cpu/microcode/intel.c b/xen/arch/x86/cpu/microcode/intel.c index c45b00c6b0..425bfc1045 100644 --- a/xen/arch/x86/cpu/microcode/intel.c +++ b/xen/arch/x86/cpu/microcode/intel.c @@ -27,6 +27,7 @@ #include #include +#include #include #include #include @@ -273,6 +274,44 @@ static bool microcode_fits_cpu(const struct microcode_patch *mc) return false; } +static bool microcode_safe_to_load(const struct microcode_patch *mc) +{ + const struct cpu_signature *cpu_sig = &this_cpu(cpu_sig); + + /* + * Treat pre-production as always safe - anyone using pre-production + * microcode knows what they are doing, and can keep any resulting pieces. + */ + if ( (int)cpu_sig->rev < 0 || mc->rev < 0 ) + return true; + + /* + * GNR98 states that Granite Rapids systems hang when loading new ucode on + * sufficiently old firmware. GNR101 retroactively states that one ucode + * had an incorrect minimum revision field, in light of discovering GNR98. + * + * Both are incomplete statements of the problem. + * + * At the time of writing (August 2026), the believed safe sequence is: + * previous -> [0x01000380...0x010003f3] -> 0x01000405 -> any later + * + * Disallow known-unsafe loads while permitting believed-safe loads. For + * GNR, this allows multi-hop loading to get up to the latest. + */ + if ( boot_cpu_data.vfm == INTEL_GRANITERAPIDS_X && + boot_cpu_data.stepping == 1 && (cpu_sig->pf & 0x95) && + ((cpu_sig->rev < 0x01000380 && mc->rev >= 0x01000405) || + (cpu_sig->rev < 0x01000405 && mc->rev > 0x01000405)) ) + { + printk_once(XENLOG_WARNING "microcode: Granite Rapids erratum GNR98 detected. Skipping ucode 0x%08x\n" + XENLOG_WARNING "microcode: Firmware update recommended\n", + mc->rev); + return false; + } + + return true; +} + static int cf_check intel_compare( const struct microcode_patch *old, const struct microcode_patch *new) { @@ -365,6 +404,7 @@ static struct microcode_patch *cf_check intel_ucode_parse( * one with higher revision. */ if ( microcode_fits_cpu(mc) && + microcode_safe_to_load(mc) && (!saved || compare_revisions(saved->rev, mc->rev) == NEW_UCODE) ) saved = mc;