summaryrefslogtreecommitdiffstats
path: root/src/arch/x86/core/ucode_mp.S
diff options
context:
space:
mode:
authorMichael Brown2024-03-15 18:43:49 +0100
committerMichael Brown2024-03-15 18:43:49 +0100
commit17882e76afc0e69a0d4ed142aa33b94017ae4e58 (patch)
tree43b4c027ad128fc52dffa463599274a6ea660a16 /src/arch/x86/core/ucode_mp.S
parent[bios] Provide a multiprocessor API for BIOS (diff)
downloadipxe-17882e76afc0e69a0d4ed142aa33b94017ae4e58.tar.gz
ipxe-17882e76afc0e69a0d4ed142aa33b94017ae4e58.tar.xz
ipxe-17882e76afc0e69a0d4ed142aa33b94017ae4e58.zip
[ucode] Add support for updating x86 microcode
Intel and AMD distribute microcode updates, which are typically applied by the BIOS and/or the booted operating system. BIOS updates can be difficult to obtain and cumbersome to apply, and are often neglected. Operating system updates may be subject to strict change control processes, particularly for production workloads. There is therefore value in being able to update the microcode at boot time using a freshly downloaded microcode update file, particularly in scenarios where the physical hardware and the installed operating system are controlled by different parties (such as in a public cloud infrastructure). Add support for parsing Intel and AMD microcode update images, and for applying the updates to all CPUs in the system. Signed-off-by: Michael Brown <mcb30@ipxe.org>
Diffstat (limited to 'src/arch/x86/core/ucode_mp.S')
-rw-r--r--src/arch/x86/core/ucode_mp.S257
1 files changed, 257 insertions, 0 deletions
diff --git a/src/arch/x86/core/ucode_mp.S b/src/arch/x86/core/ucode_mp.S
new file mode 100644
index 00000000..808e881e
--- /dev/null
+++ b/src/arch/x86/core/ucode_mp.S
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2024 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ * You can also choose to distribute this program under the terms of
+ * the Unmodified Binary Distribution Licence (as given in the file
+ * COPYING.UBDL), provided that you have satisfied its requirements.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+/** @file
+ *
+ * Microcode updates
+ *
+ */
+
+ .section ".note.GNU-stack", "", @progbits
+ .text
+
+/* Selectively assemble code for 32-bit/64-bit builds */
+#if defined ( __x86_64__ ) && ! defined ( PLATFORM_pcbios )
+#define codemp code64
+#define AX rax
+#define BX rbx
+#define CX rcx
+#define DX rdx
+#define SI rsi
+#define DI rdi
+#define BP rbp
+#define SP rsp
+#define if32 if 0
+#define if64 if 1
+#else
+#define codemp code32
+#define AX eax
+#define BX ebx
+#define CX ecx
+#define DX edx
+#define SI esi
+#define DI edi
+#define BP ebp
+#define SP esp
+#define if32 if 1
+#define if64 if 0
+#endif
+
+/* Standard features CPUID leaf */
+#define CPUID_FEATURES 0x00000001
+
+/* BIOS update signature MSR */
+#define MSR_BIOS_SIGN_ID 0x0000008b
+
+/** Microcode update control layout
+ *
+ * This must match the layout of struct ucode_control.
+ */
+ .struct 0
+CONTROL_DESC:
+ .space 8
+CONTROL_STATUS:
+ .space 8
+CONTROL_TRIGGER_MSR:
+ .space 4
+CONTROL_APIC_MAX:
+ .space 4
+CONTROL_APIC_UNEXPECTED:
+ .space 4
+CONTROL_APIC_MASK:
+ .space 4
+CONTROL_APIC_TEST:
+ .space 4
+CONTROL_VER_CLEAR:
+ .space 1
+CONTROL_VER_HIGH:
+ .space 1
+CONTROL_LEN:
+
+/* We use register %ebp/%rbp to hold the address of the update control */
+#define CONTROL BP
+
+/* Microcode update descriptor layout
+ *
+ * This must match the layout of struct ucode_descriptor.
+ */
+ .struct 0
+DESC_SIGNATURE:
+ .space 4
+DESC_VERSION:
+ .space 4
+DESC_ADDRESS:
+ .space 8
+DESC_LEN:
+
+/* We use register %esi/%rsi to hold the address of the descriptor */
+#define DESC SI
+
+/** Microcode update status report layout
+ *
+ * This must match the layout of struct ucode_status.
+ */
+ .struct 0
+STATUS_SIGNATURE:
+ .space 4
+STATUS_ID:
+ .space 4
+STATUS_BEFORE:
+ .space 4
+STATUS_AFTER:
+ .space 4
+STATUS_LEN:
+ .equ LOG2_STATUS_LEN, 4
+ .if ( 1 << LOG2_STATUS_LEN ) - STATUS_LEN
+ .error "LOG2_STATUS_LEN value is incorrect"
+ .endif
+
+/* We use register %edi/%rdi to hold the address of the status report */
+#define STATUS DI
+
+/*
+ * Update microcode
+ *
+ * Parameters:
+ * %eax/%rdi Microcode update structure
+ * %edx/%rsi CPU identifier (APIC ID)
+ * %esp/%rsp Stack, or NULL to halt AP upon completion
+ *
+ * This code may run with no stack on an application processor (AP).
+ * All values must be held in registers, and no subroutine calls are
+ * possible. No firmware routines may be called.
+ *
+ * Since cpuid/rdmsr/wrmsr require the use of %eax, %ebx, %ecx, and
+ * %edx, we have essentially only three registers available for
+ * long-term state.
+ */
+ .text
+ .globl ucode_update
+ .codemp
+ .section ".text.ucode_update", "ax", @progbits
+ucode_update:
+
+.if64 /* Get input parameters */
+ movq %rdi, %CONTROL
+ movl %esi, %edx
+.else
+ movl %eax, %CONTROL
+.endif
+ /* Check against maximum expected APIC ID */
+ cmpl CONTROL_APIC_MAX(%CONTROL), %edx
+ jbe 1f
+ movl %edx, CONTROL_APIC_UNEXPECTED(%CONTROL)
+ jmp done
+1:
+ /* Calculate per-CPU status report buffer address */
+ mov %DX, %STATUS
+ shl $LOG2_STATUS_LEN, %STATUS
+ add CONTROL_STATUS(%CONTROL), %STATUS
+
+ /* Report APIC ID */
+ movl %edx, STATUS_ID(%STATUS)
+
+ /* Get and report CPU signature */
+ movl $CPUID_FEATURES, %eax
+ cpuid
+ movl %eax, STATUS_SIGNATURE(%STATUS)
+
+ /* Check APIC ID mask */
+ movl STATUS_ID(%STATUS), %eax
+ andl CONTROL_APIC_MASK(%CONTROL), %eax
+ cmpl CONTROL_APIC_TEST(%CONTROL), %eax
+ jne done
+
+ /* Clear BIOS_SIGN_ID MSR if applicable */
+ movl $MSR_BIOS_SIGN_ID, %ecx
+ xorl %eax, %eax
+ xorl %edx, %edx
+ testb $0xff, CONTROL_VER_CLEAR(%CONTROL)
+ jz 1f
+ wrmsr
+1:
+ /* Get CPU signature to repopulate BIOS_SIGN_ID MSR (for Intel) */
+ movl $CPUID_FEATURES, %eax
+ cpuid
+
+ /* Get initial microcode version */
+ movl $MSR_BIOS_SIGN_ID, %ecx
+ rdmsr
+ testb $0xff, CONTROL_VER_HIGH(%CONTROL)
+ jz 1f
+ movl %edx, %eax
+1: movl %eax, STATUS_BEFORE(%STATUS)
+
+ /* Get start of descriptor list */
+ mov CONTROL_DESC(%CONTROL), %DESC
+ sub $DESC_LEN, %DESC
+
+1: /* Walk update descriptor list to find a matching CPU signature */
+ add $DESC_LEN, %DESC
+ movl DESC_SIGNATURE(%DESC), %eax
+ testl %eax, %eax
+ jz noload
+ cmpl STATUS_SIGNATURE(%STATUS), %eax
+ jne 1b
+
+ /* Compare (signed) microcode versions */
+ movl STATUS_BEFORE(%STATUS), %eax
+ cmpl DESC_VERSION(%DESC), %eax
+ jge noload
+
+ /* Load microcode update */
+ movl CONTROL_TRIGGER_MSR(%CONTROL), %ecx
+ movl (DESC_ADDRESS + 0)(%DESC), %eax
+ movl (DESC_ADDRESS + 4)(%DESC), %edx
+ wrmsr
+
+noload: /* Clear BIOS_SIGN_ID MSR if applicable */
+ movl $MSR_BIOS_SIGN_ID, %ecx
+ xorl %eax, %eax
+ xorl %edx, %edx
+ testb $0xff, CONTROL_VER_CLEAR(%CONTROL)
+ jz 1f
+ wrmsr
+1:
+ /* Get CPU signature to repopulate BIOS_SIGN_ID MSR (for Intel) */
+ movl $CPUID_FEATURES, %eax
+ cpuid
+
+ /* Get and report final microcode version */
+ movl $MSR_BIOS_SIGN_ID, %ecx
+ rdmsr
+ testb $0xff, CONTROL_VER_HIGH(%CONTROL)
+ jz 1f
+ movl %edx, %eax
+1: movl %eax, STATUS_AFTER(%STATUS)
+
+done: /* Return to caller (if stack exists), or halt application processor */
+ test %SP, %SP
+ jz 1f
+ ret
+1: cli
+ hlt
+ jmp 1b
+ .size ucode_update, . - ucode_update