summaryrefslogtreecommitdiffstats
path: root/src/arch/x86
diff options
context:
space:
mode:
Diffstat (limited to 'src/arch/x86')
-rw-r--r--src/arch/x86/core/mpcall.S197
-rw-r--r--src/arch/x86/core/ucode_mp.S257
-rw-r--r--src/arch/x86/image/ucode.c798
-rw-r--r--src/arch/x86/include/bits/errfile.h1
-rw-r--r--src/arch/x86/include/bits/mp.h14
-rw-r--r--src/arch/x86/include/ipxe/bios_mp.h32
-rw-r--r--src/arch/x86/include/ipxe/ucode.h223
-rw-r--r--src/arch/x86/include/librm.h20
-rw-r--r--src/arch/x86/interface/pcbios/bios_mp.c173
-rw-r--r--src/arch/x86/transitions/librm.S67
-rw-r--r--src/arch/x86/transitions/librm_mgmt.c26
11 files changed, 1808 insertions, 0 deletions
diff --git a/src/arch/x86/core/mpcall.S b/src/arch/x86/core/mpcall.S
new file mode 100644
index 00000000..f2a3bf90
--- /dev/null
+++ b/src/arch/x86/core/mpcall.S
@@ -0,0 +1,197 @@
+/*
+ * 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
+ *
+ * Multiprocessor functions
+ *
+ */
+
+ .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 DI rdi
+#define SP rsp
+#define if32 if 0
+#define if64 if 1
+#else
+#define codemp code32
+#define DI edi
+#define SP esp
+#define if32 if 1
+#define if64 if 0
+#endif
+
+/* Standard features CPUID leaf */
+#define CPUID_FEATURES 0x00000001
+
+/* x2APIC is supported */
+#define CPUID_FEATURES_ECX_X2APIC 0x00200000
+
+/* Extended topology enumeration CPUID leaf */
+#define CPUID_XT_ENUM 0x0000000b
+
+/*
+ * Call multiprocessor function from C code
+ *
+ * Parameters:
+ * 4(%esp)/%rdi Multiprocessor function
+ * 8(%esp)/%rsi Opaque data pointer
+ */
+ .section ".text.mp_call", "ax", @progbits
+ .codemp
+ .globl mp_call
+mp_call:
+.if64 /* Preserve registers, load incoming parameters into registers */
+ pushq %rax
+ pushq %rcx
+ pushq %rdx
+ pushq %rbx
+ pushq %rsp
+ pushq %rbp
+ pushq %rsi
+ pushq %rdi
+ pushq %r8
+ pushq %r9
+ pushq %r10
+ pushq %r11
+ pushq %r12
+ pushq %r13
+ pushq %r14
+ pushq %r15
+.else
+ pushal
+ movl 36(%esp), %eax
+ movl 40(%esp), %edx
+.endif
+ /* Call multiprocessor function */
+ call mp_jump
+
+.if64 /* Restore registers and return */
+ popq %r15
+ popq %r14
+ popq %r13
+ popq %r12
+ popq %r11
+ popq %r10
+ popq %r9
+ popq %r8
+ popq %rdi
+ popq %rsi
+ popq %rbp
+ leaq 8(%rsp), %rsp /* discard */
+ popq %rbx
+ popq %rdx
+ popq %rcx
+ popq %rax
+.else
+ popal
+.endif
+ ret
+ .size mp_call, . - mp_call
+
+/*
+ * Jump to multiprocessor function
+ *
+ * Parameters:
+ * %eax/%rdi Multiprocessor function
+ * %edx/%rsi Opaque data pointer
+ * %esp/%rsp Stack, or NULL to halt AP upon completion
+ *
+ * Obtain the CPU identifier (i.e. the APIC ID) and perform a tail
+ * call into the specified multiprocessor function.
+ *
+ * This code may run with no stack on an application processor.
+ */
+ .section ".text.mp_jump", "ax", @progbits
+ .codemp
+ .globl mp_jump
+mp_jump:
+.if32 /* Move function parameters to available registers */
+ movl %eax, %edi
+ movl %edx, %esi
+.endif
+
+ /* Get 8-bit APIC ID and x2APIC feature bit */
+ movl $CPUID_FEATURES, %eax
+ cpuid
+ shrl $24, %ebx
+ movl %ebx, %edx
+
+ /* Get 32-bit x2APIC ID if applicable */
+ testl $CPUID_FEATURES_ECX_X2APIC, %ecx
+ jz 1f
+ movl $CPUID_XT_ENUM, %eax
+ xorl %ecx, %ecx
+ cpuid
+1:
+
+.if64 /* Tail call to function */
+ movq %rdi, %rax
+ movq %rsi, %rdi
+ movl %edx, %esi
+ jmp *%rax
+.else
+ movl %esi, %eax
+ jmp *%edi
+.endif
+ .size mp_jump, . - mp_jump
+
+/*
+ * Update maximum CPU identifier
+ *
+ * Parameters:
+ * %eax/%rdi Pointer to shared maximum APIC ID
+ * %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.
+ */
+ .section ".text.mp_update_max_cpuid", "ax", @progbits
+ .codemp
+ .globl mp_update_max_cpuid
+mp_update_max_cpuid:
+.if32 /* Move function parameters to available registers */
+ movl %eax, %edi
+ movl %edx, %esi
+.endif
+ /* Update maximum APIC ID (atomically) */
+ movl (%DI), %eax
+1: cmpl %esi, %eax
+ jae 2f
+ lock cmpxchgl %esi, (%DI)
+ jnz 1b
+2:
+ /* Return to caller (if stack exists), or halt application processor */
+ test %SP, %SP
+ jz 3f
+ ret
+3: cli
+ hlt
+ jmp 3b
+ .size mp_update_max_cpuid, . - mp_update_max_cpuid
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
diff --git a/src/arch/x86/image/ucode.c b/src/arch/x86/image/ucode.c
new file mode 100644
index 00000000..499c0a94
--- /dev/null
+++ b/src/arch/x86/image/ucode.c
@@ -0,0 +1,798 @@
+/*
+ * 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
+ *
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <assert.h>
+#include <errno.h>
+#include <ipxe/uaccess.h>
+#include <ipxe/umalloc.h>
+#include <ipxe/image.h>
+#include <ipxe/cpuid.h>
+#include <ipxe/msr.h>
+#include <ipxe/mp.h>
+#include <ipxe/timer.h>
+#include <ipxe/ucode.h>
+
+/**
+ * Maximum number of hyperthread siblings
+ *
+ * Microcode updates must not be performed on hyperthread siblings at
+ * the same time, since they share microcode storage.
+ *
+ * Hyperthread siblings are always the lowest level of the CPU
+ * topology and correspond to the least significant bits of the APIC
+ * ID. We may therefore avoid collisions by performing the microcode
+ * updates in batches, with each batch targeting just one value for
+ * the least significant N bits of the APIC ID.
+ *
+ * We assume that no CPUs exist with more than this number of
+ * hyperthread siblings. (This must be a power of two.)
+ */
+#define UCODE_MAX_HT 8
+
+/** Time to wait for a microcode update to complete */
+#define UCODE_WAIT_MS 10
+
+/** A CPU vendor string */
+union ucode_vendor_id {
+ /** CPUID registers */
+ uint32_t dword[3];
+ /** Human-readable string */
+ uint8_t string[12];
+};
+
+/** A CPU vendor */
+struct ucode_vendor {
+ /** Vendor string */
+ union ucode_vendor_id id;
+ /** Microcode load trigger MSR */
+ uint32_t trigger_msr;
+ /** Microcode version requires manual clear */
+ uint8_t ver_clear;
+ /** Microcode version is reported via high dword */
+ uint8_t ver_high;
+};
+
+/** A microcode update */
+struct ucode_update {
+ /** CPU vendor, if known */
+ struct ucode_vendor *vendor;
+ /** Boot processor CPU signature */
+ uint32_t signature;
+ /** Platform ID */
+ uint32_t platform;
+ /** Number of potentially relevant signatures found */
+ unsigned int count;
+ /** Update descriptors (if being populated) */
+ struct ucode_descriptor *desc;
+};
+
+/** A microcode update summary */
+struct ucode_summary {
+ /** Number of CPUs processed */
+ unsigned int count;
+ /** Lowest observed microcode version */
+ int32_t low;
+ /** Highest observed microcode version */
+ int32_t high;
+};
+
+/** Intel CPU vendor */
+static struct ucode_vendor ucode_intel = {
+ .id = { .string = "GenuineIntel" },
+ .ver_clear = 1,
+ .ver_high = 1,
+ .trigger_msr = MSR_UCODE_TRIGGER_INTEL,
+};
+
+/** AMD CPU vendor */
+static struct ucode_vendor ucode_amd = {
+ .id = { .string = "AuthenticAMD" },
+ .trigger_msr = MSR_UCODE_TRIGGER_AMD,
+};
+
+/** List of known CPU vendors */
+static struct ucode_vendor *ucode_vendors[] = {
+ &ucode_intel,
+ &ucode_amd,
+};
+
+/**
+ * Get CPU vendor name (for debugging)
+ *
+ * @v vendor CPU vendor
+ * @ret name Name
+ */
+static const char * ucode_vendor_name ( const union ucode_vendor_id *vendor ) {
+ static union {
+ union ucode_vendor_id vendor;
+ char text[ sizeof ( *vendor ) + 1 /* NUL */ ];
+ } u;
+
+ /* Construct name */
+ memcpy ( &u.vendor, vendor, sizeof ( u.vendor ) );
+ u.text[ sizeof ( u.text ) - 1 ] = '\0';
+ return u.text;
+}
+
+/**
+ * Check status report
+ *
+ * @v update Microcode update
+ * @v control Microcode update control
+ * @v summary Microcode update summary
+ * @v id APIC ID
+ * @v optional Status report is optional
+ * @ret rc Return status code
+ */
+static int ucode_status ( struct ucode_update *update,
+ struct ucode_control *control,
+ struct ucode_summary *summary,
+ unsigned int id, int optional ) {
+ struct ucode_status status;
+ struct ucode_descriptor *desc;
+
+ /* Sanity check */
+ assert ( id <= control->apic_max );
+
+ /* Read status report */
+ copy_from_user ( &status, phys_to_user ( control->status ),
+ ( id * sizeof ( status ) ), sizeof ( status ) );
+
+ /* Ignore empty optional status reports */
+ if ( optional && ( ! status.signature ) )
+ return 0;
+ DBGC ( update, "UCODE %#08x signature %#08x ucode %#08x->%#08x\n",
+ id, status.signature, status.before, status.after );
+
+ /* Check CPU signature */
+ if ( ! status.signature ) {
+ DBGC2 ( update, "UCODE %#08x has no signature\n", id );
+ return -ENOENT;
+ }
+
+ /* Check APIC ID is correct */
+ if ( status.id != id ) {
+ DBGC ( update, "UCODE %#08x wrong APIC ID %#08x\n",
+ id, status.id );
+ return -EINVAL;
+ }
+
+ /* Check that maximum APIC ID was not exceeded */
+ if ( control->apic_unexpected ) {
+ DBGC ( update, "UCODE %#08x saw unexpected APIC ID %#08x\n",
+ id, control->apic_unexpected );
+ return -ERANGE;
+ }
+
+ /* Check microcode was not downgraded */
+ if ( status.after < status.before ) {
+ DBGC ( update, "UCODE %#08x was downgraded %#08x->%#08x\n",
+ id, status.before, status.after );
+ return -ENOTTY;
+ }
+
+ /* Check that expected updates (if any) were applied */
+ for ( desc = update->desc ; desc->signature ; desc++ ) {
+ if ( ( desc->signature == status.signature ) &&
+ ( status.after < desc->version ) ) {
+ DBGC ( update, "UCODE %#08x failed update %#08x->%#08x "
+ "(wanted %#08x)\n", id, status.before,
+ status.after, desc->version );
+ return -EIO;
+ }
+ }
+
+ /* Update summary */
+ summary->count++;
+ if ( status.before < summary->low )
+ summary->low = status.before;
+ if ( status.after > summary->high )
+ summary->high = status.after;
+
+ return 0;
+}
+
+/**
+ * Update microcode on all CPUs
+ *
+ * @v image Microcode image
+ * @v update Microcode update
+ * @v summary Microcode update summary to fill in
+ * @ret rc Return status code
+ */
+static int ucode_update_all ( struct image *image,
+ struct ucode_update *update,
+ struct ucode_summary *summary ) {
+ struct ucode_control control;
+ struct ucode_vendor *vendor;
+ userptr_t status;
+ unsigned int max;
+ unsigned int i;
+ size_t len;
+ int rc;
+
+ /* Initialise summary */
+ summary->count = 0;
+ summary->low = UCODE_VERSION_MAX;
+ summary->high = UCODE_VERSION_MIN;
+
+ /* Allocate status reports */
+ max = mp_max_cpuid();
+ len = ( ( max + 1 ) * sizeof ( struct ucode_status ) );
+ status = umalloc ( len );
+ if ( ! status ) {
+ DBGC ( image, "UCODE %s could not allocate %d status reports\n",
+ image->name, ( max + 1 ) );
+ rc = -ENOMEM;
+ goto err_alloc;
+ }
+ memset_user ( status, 0, 0, len );
+
+ /* Construct control structure */
+ memset ( &control, 0, sizeof ( control ) );
+ control.desc = virt_to_phys ( update->desc );
+ control.status = user_to_phys ( status, 0 );
+ vendor = update->vendor;
+ if ( vendor ) {
+ control.ver_clear = vendor->ver_clear;
+ control.ver_high = vendor->ver_high;
+ control.trigger_msr = vendor->trigger_msr;
+ } else {
+ assert ( update->count == 0 );
+ }
+ control.apic_max = max;
+
+ /* Update microcode on boot processor */
+ mp_exec_boot ( ucode_update, &control );
+ if ( ( rc = ucode_status ( update, &control, summary,
+ mp_boot_cpuid(), 0 ) ) != 0 ) {
+ DBGC ( image, "UCODE %s failed on boot processor: %s\n",
+ image->name, strerror ( rc ) );
+ goto err_boot;
+ }
+
+ /* Update microcode on application processors, avoiding
+ * simultaneous updates on hyperthread siblings.
+ */
+ build_assert ( ( UCODE_MAX_HT & ( UCODE_MAX_HT - 1 ) ) == 0 );
+ control.apic_mask = ( UCODE_MAX_HT - 1 );
+ for ( ; control.apic_test <= control.apic_mask ; control.apic_test++ ) {
+ mp_start_all ( ucode_update, &control );
+ mdelay ( UCODE_WAIT_MS );
+ }
+
+ /* Check status reports */
+ summary->count = 0;
+ for ( i = 0 ; i <= max ; i++ ) {
+ if ( ( rc = ucode_status ( update, &control, summary,
+ i, 1 ) ) != 0 ) {
+ goto err_status;
+ }
+ }
+
+ /* Success */
+ rc = 0;
+
+ err_status:
+ err_boot:
+ ufree ( status );
+ err_alloc:
+ return rc;
+}
+
+/**
+ * Add descriptor to list (if applicable)
+ *
+ * @v image Microcode image
+ * @v start Starting offset within image
+ * @v vendor CPU vendor
+ * @v desc Microcode descriptor
+ * @v platforms Supported platforms, or 0 for all platforms
+ * @v update Microcode update
+ */
+static void ucode_describe ( struct image *image, size_t start,
+ const struct ucode_vendor *vendor,
+ const struct ucode_descriptor *desc,
+ uint32_t platforms, struct ucode_update *update ) {
+
+ /* Dump descriptor information */
+ DBGC2 ( image, "UCODE %s+%#04zx %s %#08x", image->name, start,
+ ucode_vendor_name ( &vendor->id ), desc->signature );
+ if ( platforms )
+ DBGC2 ( image, " (%#02x)", platforms );
+ DBGC2 ( image, " version %#08x\n", desc->version );
+
+ /* Check applicability */
+ if ( vendor != update->vendor )
+ return;
+ if ( ( desc->signature ^ update->signature ) & UCODE_SIGNATURE_MASK )
+ return;
+ if ( platforms && ( ! ( platforms & update->platform ) ) )
+ return;
+
+ /* Add descriptor, if applicable */
+ if ( update->desc ) {
+ memcpy ( &update->desc[update->count], desc, sizeof ( *desc ) );
+ DBGC ( image, "UCODE %s+%#04zx found %s %#08x version %#08x\n",
+ image->name, start, ucode_vendor_name ( &vendor->id ),
+ desc->signature, desc->version );
+ }
+ update->count++;
+}
+
+/**
+ * Verify checksum
+ *
+ * @v image Microcode image
+ * @v start Starting offset
+ * @v len Length
+ * @ret rc Return status code
+ */
+static int ucode_verify ( struct image *image, size_t start, size_t len ) {
+ uint32_t checksum = 0;
+ uint32_t dword;
+ size_t offset;
+
+ /* Check length is a multiple of dwords */
+ if ( ( len % sizeof ( dword ) ) != 0 ) {
+ DBGC ( image, "UCODE %s+%#04zx invalid length %#zx\n",
+ image->name, start, len );
+ return -EINVAL;
+ }
+
+ /* Calculate checksum */
+ for ( offset = start ; len ;
+ offset += sizeof ( dword ), len -= sizeof ( dword ) ) {
+ copy_from_user ( &dword, image->data, offset,
+ sizeof ( dword ) );
+ checksum += dword;
+ }
+ if ( checksum != 0 ) {
+ DBGC ( image, "UCODE %s+%#04zx bad checksum %#08x\n",
+ image->name, start, checksum );
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/**
+ * Parse Intel microcode image
+ *
+ * @v image Microcode image
+ * @v start Starting offset within image
+ * @v update Microcode update
+ * @ret len Length consumed, or negative error
+ */
+static int ucode_parse_intel ( struct image *image, size_t start,
+ struct ucode_update *update ) {
+ struct intel_ucode_header hdr;
+ struct intel_ucode_ext_header exthdr;
+ struct intel_ucode_ext ext;
+ struct ucode_descriptor desc;
+ size_t remaining;
+ size_t offset;
+ size_t data_len;
+ size_t len;
+ unsigned int i;
+ int rc;
+
+ /* Read header */
+ remaining = ( image->len - start );
+ if ( remaining < sizeof ( hdr ) ) {
+ DBGC ( image, "UCODE %s+%#04zx too small for Intel header\n",
+ image->name, start );
+ return -ENOEXEC;
+ }
+ copy_from_user ( &hdr, image->data, start, sizeof ( hdr ) );
+
+ /* Determine lengths */
+ data_len = hdr.data_len;
+ if ( ! data_len )
+ data_len = INTEL_UCODE_DATA_LEN;
+ len = hdr.len;
+ if ( ! len )
+ len = ( sizeof ( hdr ) + data_len );
+
+ /* Verify a selection of fields */
+ if ( ( hdr.hver != INTEL_UCODE_HVER ) ||
+ ( hdr.lver != INTEL_UCODE_LVER ) ||
+ ( len < sizeof ( hdr ) ) ||
+ ( len > remaining ) ||
+ ( data_len > ( len - sizeof ( hdr ) ) ) ||
+ ( ( data_len % sizeof ( uint32_t ) ) != 0 ) ||
+ ( ( len % INTEL_UCODE_ALIGN ) != 0 ) ) {
+ DBGC2 ( image, "UCODE %s+%#04zx is not an Intel update\n",
+ image->name, start );
+ return -EINVAL;
+ }
+ DBGC2 ( image, "UCODE %s+%#04zx is an Intel update\n",
+ image->name, start );
+
+ /* Verify checksum */
+ if ( ( rc = ucode_verify ( image, start, len ) ) != 0 )
+ return rc;
+
+ /* Populate descriptor */
+ desc.signature = hdr.signature;
+ desc.version = hdr.version;
+ desc.address = user_to_phys ( image->data,
+ ( start + sizeof ( hdr ) ) );
+
+ /* Add non-extended descriptor, if applicable */
+ ucode_describe ( image, start, &ucode_intel, &desc, hdr.platforms,
+ update );
+
+ /* Construct extended descriptors, if applicable */
+ offset = ( sizeof ( hdr ) + data_len );
+ if ( offset <= ( len - sizeof ( exthdr ) ) ) {
+
+ /* Read extended header */
+ copy_from_user ( &exthdr, image->data, ( start + offset ),
+ sizeof ( exthdr ) );
+ offset += sizeof ( exthdr );
+
+ /* Read extended signatures */
+ for ( i = 0 ; i < exthdr.count ; i++ ) {
+
+ /* Read extended signature */
+ if ( offset > ( len - sizeof ( ext ) ) ) {
+ DBGC ( image, "UCODE %s+%#04zx extended "
+ "signature overrun\n",
+ image->name, start );
+ return -EINVAL;
+ }
+ copy_from_user ( &ext, image->data, ( start + offset ),
+ sizeof ( ext ) );
+ offset += sizeof ( ext );
+
+ /* Avoid duplicating non-extended descriptor */
+ if ( ( ext.signature == hdr.signature ) &&
+ ( ext.platforms == hdr.platforms ) ) {
+ continue;
+ }
+
+ /* Construct descriptor, if applicable */
+ desc.signature = ext.signature;
+ ucode_describe ( image, start, &ucode_intel, &desc,
+ ext.platforms, update );
+ }
+ }
+
+ return len;
+}
+
+/**
+ * Parse AMD microcode image
+ *
+ * @v image Microcode image
+ * @v start Starting offset within image
+ * @v update Microcode update
+ * @ret len Length consumed, or negative error
+ */
+static int ucode_parse_amd ( struct image *image, size_t start,
+ struct ucode_update *update ) {
+ struct amd_ucode_header hdr;
+ struct amd_ucode_equivalence equiv;
+ struct amd_ucode_patch_header phdr;
+ struct amd_ucode_patch patch;
+ struct ucode_descriptor desc;
+ size_t remaining;
+ size_t offset;
+ unsigned int count;
+ unsigned int used;
+ unsigned int i;
+
+ /* Read header */
+ remaining = ( image->len - start );
+ if ( remaining < sizeof ( hdr ) ) {
+ DBGC ( image, "UCODE %s+%#04zx too small for AMD header\n",
+ image->name, start );
+ return -ENOEXEC;
+ }
+ copy_from_user ( &hdr, image->data, start, sizeof ( hdr ) );
+
+ /* Check header */
+ if ( hdr.magic != AMD_UCODE_MAGIC ) {
+ DBGC2 ( image, "UCODE %s+%#04zx is not an AMD update\n",
+ image->name, start );
+ return -ENOEXEC;
+ }
+ DBGC2 ( image, "UCODE %s+%#04zx is an AMD update\n",
+ image->name, start );
+ if ( hdr.type != AMD_UCODE_EQUIV_TYPE ) {
+ DBGC ( image, "UCODE %s+%#04zx unsupported equivalence table "
+ "type %d\n", image->name, start, hdr.type );
+ return -ENOTSUP;
+ }
+ if ( hdr.len > ( remaining - sizeof ( hdr ) ) ) {
+ DBGC ( image, "UCODE %s+%#04zx truncated equivalence table\n",
+ image->name, start );
+ return -EINVAL;
+ }
+
+ /* Count number of equivalence table entries */
+ offset = sizeof ( hdr );
+ for ( count = 0 ; offset < ( sizeof ( hdr ) + hdr.len ) ;
+ count++, offset += sizeof ( equiv ) ) {
+ copy_from_user ( &equiv, image->data, ( start + offset ),
+ sizeof ( equiv ) );
+ if ( ! equiv.signature )
+ break;
+ }
+ DBGC2 ( image, "UCODE %s+%#04zx has %d equivalence table entries\n",
+ image->name, start, count );
+
+ /* Parse available updates */
+ offset = ( sizeof ( hdr ) + hdr.len );
+ used = 0;
+ while ( used < count ) {
+
+ /* Read patch header */
+ if ( ( offset + sizeof ( phdr ) ) > remaining ) {
+ DBGC ( image, "UCODE %s+%#04zx truncated patch "
+ "header\n", image->name, start );
+ return -EINVAL;
+ }
+ copy_from_user ( &phdr, image->data, ( start + offset ),
+ sizeof ( phdr ) );
+ offset += sizeof ( phdr );
+
+ /* Validate patch header */
+ if ( phdr.type != AMD_UCODE_PATCH_TYPE ) {
+ DBGC ( image, "UCODE %s+%#04zx unsupported patch type "
+ "%d\n", image->name, start, phdr.type );
+ return -ENOTSUP;
+ }
+ if ( phdr.len < sizeof ( patch ) ) {
+ DBGC ( image, "UCODE %s+%#04zx underlength patch\n",
+ image->name, start );
+ return -EINVAL;
+ }
+ if ( phdr.len > ( remaining - offset ) ) {
+ DBGC ( image, "UCODE %s+%#04zx truncated patch\n",
+ image->name, start );
+ return -EINVAL;
+ }
+
+ /* Read patch and construct descriptor */
+ copy_from_user ( &patch, image->data, ( start + offset ),
+ sizeof ( patch ) );
+ desc.version = patch.version;
+ desc.address = user_to_phys ( image->data, ( start + offset ) );
+ offset += phdr.len;
+
+ /* Parse equivalence table to find matching signatures */
+ for ( i = 0 ; i < count ; i++ ) {
+ copy_from_user ( &equiv, image->data,
+ ( start + sizeof ( hdr ) +
+ ( i * ( sizeof ( equiv ) ) ) ),
+ sizeof ( equiv ) );
+ if ( patch.id == equiv.id ) {
+ desc.signature = equiv.signature;
+ ucode_describe ( image, start, &ucode_amd,
+ &desc, 0, update );
+ used++;
+ }
+ }
+ }
+
+ return offset;
+}
+
+/**
+ * Parse microcode image
+ *
+ * @v image Microcode image
+ * @v update Microcode update
+ * @ret rc Return status code
+ */
+static int ucode_parse ( struct image *image, struct ucode_update *update ) {
+ size_t start;
+ int len;
+
+ /* Attempt to parse concatenated microcode updates */
+ for ( start = 0 ; start < image->len ; start += len ) {
+
+ /* Attempt to parse as Intel microcode */
+ len = ucode_parse_intel ( image, start, update );
+ if ( len > 0 )
+ continue;
+
+ /* Attempt to parse as AMD microcode */
+ len = ucode_parse_amd ( image, start, update );
+ if ( len > 0 )
+ continue;
+
+ /* Not a recognised microcode format */
+ DBGC ( image, "UCODE %s+%zx not recognised\n",
+ image->name, start );
+ return -ENOEXEC;
+ }
+
+ return 0;
+}
+
+/**
+ * Execute microcode update
+ *
+ * @v image Microcode image
+ * @ret rc Return status code
+ */
+static int ucode_exec ( struct image *image ) {
+ struct ucode_update update;
+ struct ucode_vendor *vendor;
+ struct ucode_summary summary;
+ union ucode_vendor_id id;
+ uint64_t platform_id;
+ uint32_t discard_a;
+ uint32_t discard_b;
+ uint32_t discard_c;
+ uint32_t discard_d;
+ unsigned int check;
+ unsigned int i;
+ size_t len;
+ int rc;
+
+ /* Initialise update */
+ memset ( &update, 0, sizeof ( update ) );
+ cpuid ( CPUID_VENDOR_ID, 0, &discard_a, &id.dword[0], &id.dword[2],
+ &id.dword[1] );
+ cpuid ( CPUID_FEATURES, 0, &update.signature, &discard_b,
+ &discard_c, &discard_d );
+
+ /* Identify CPU vendor, if recognised */
+ for ( i = 0 ; i < ( sizeof ( ucode_vendors ) /
+ sizeof ( ucode_vendors[0] ) ) ; i++ ) {
+ vendor = ucode_vendors[i];
+ if ( memcmp ( &id, &vendor->id, sizeof ( id ) ) == 0 )
+ update.vendor = vendor;
+ }
+
+ /* Identify platform, if applicable */
+ if ( update.vendor == &ucode_intel ) {
+ platform_id = rdmsr ( MSR_PLATFORM_ID );
+ update.platform =
+ ( 1 << MSR_PLATFORM_ID_VALUE ( platform_id ) );
+ }
+
+ /* Count number of matching update descriptors */
+ DBGC ( image, "UCODE %s applying to %s %#08x",
+ image->name, ucode_vendor_name ( &id ), update.signature );
+ if ( update.platform )
+ DBGC ( image, " (%#02x)", update.platform );
+ DBGC ( image, "\n" );
+ if ( ( rc = ucode_parse ( image, &update ) ) != 0 )
+ goto err_count;
+ DBGC ( image, "UCODE %s found %d matching update(s)\n",
+ image->name, update.count );
+
+ /* Allocate descriptors */
+ len = ( ( update.count + 1 /* terminator */ ) *
+ sizeof ( update.desc[0] ) );
+ update.desc = zalloc ( len );
+ if ( ! update.desc ) {
+ rc = -ENOMEM;
+ goto err_alloc;
+ }
+
+ /* Populate descriptors */
+ check = update.count;
+ update.count = 0;
+ if ( ( rc = ucode_parse ( image, &update ) ) != 0 )
+ goto err_parse;
+ assert ( check == update.count );
+
+ /* Perform update */
+ if ( ( rc = ucode_update_all ( image, &update, &summary ) ) != 0 )
+ goto err_update;
+
+ /* Print summary if directed to do so */
+ if ( image->cmdline && ( strstr ( image->cmdline, "-v" ) ) ) {
+ printf ( "Microcode: " );
+ if ( summary.low == summary.high ) {
+ printf ( "already version %#x", summary.low );
+ } else {
+ printf ( "updated version %#x->%#x",
+ summary.low, summary.high );
+ }
+ printf ( " (x%d)\n", summary.count );
+ }
+
+ err_update:
+ err_parse:
+ free ( update.desc );
+ err_alloc:
+ err_count:
+ return rc;
+}
+
+/**
+ * Probe microcode update image
+ *
+ * @v image Microcode image
+ * @ret rc Return status code
+ */
+static int ucode_probe ( struct image *image ) {
+ union {
+ struct intel_ucode_header intel;
+ struct amd_ucode_header amd;
+ } header;
+
+ /* Sanity check */
+ if ( image->len < sizeof ( header ) ) {
+ DBGC ( image, "UCODE %s too short\n", image->name );
+ return -ENOEXEC;
+ }
+
+ /* Read first microcode image header */
+ copy_from_user ( &header, image->data, 0, sizeof ( header ) );
+
+ /* Check for something that looks like an Intel update
+ *
+ * Intel updates unfortunately have no magic signatures or
+ * other easily verifiable fields. We check a small selection
+ * of header fields that can be easily verified.
+ *
+ * We do not attempt to fully parse the update, since we want
+ * errors to be reported at the point of attempting to execute
+ * the image, and do not want to have a microcode image
+ * erroneously treated as a PXE boot executable.
+ */
+ if ( ( header.intel.hver == INTEL_UCODE_HVER ) &&
+ ( header.intel.lver == INTEL_UCODE_LVER ) &&
+ ( ( header.intel.date.century == 0x19 ) ||
+ ( ( header.intel.date.century >= 0x20 ) &&
+ ( header.intel.date.century <= 0x29 ) ) ) ) {
+ DBGC ( image, "UCODE %s+%#04zx looks like an Intel update\n",
+ image->name, ( ( size_t ) 0 ) );
+ return 0;
+ }
+
+ /* Check for AMD update signature */
+ if ( ( header.amd.magic == AMD_UCODE_MAGIC ) &&
+ ( header.amd.type == AMD_UCODE_EQUIV_TYPE ) ) {
+ DBGC ( image, "UCODE %s+%#04zx looks like an AMD update\n",
+ image->name, ( ( size_t ) 0 ) );
+ return 0;
+ }
+
+ return -ENOEXEC;
+}
+
+/** Microcode update image type */
+struct image_type ucode_image_type __image_type ( PROBE_NORMAL ) = {
+ .name = "ucode",
+ .probe = ucode_probe,
+ .exec = ucode_exec,
+};
diff --git a/src/arch/x86/include/bits/errfile.h b/src/arch/x86/include/bits/errfile.h
index b5316a58..78b3dea1 100644
--- a/src/arch/x86/include/bits/errfile.h
+++ b/src/arch/x86/include/bits/errfile.h
@@ -44,6 +44,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#define ERRFILE_sdi ( ERRFILE_ARCH | ERRFILE_IMAGE | 0x000b0000 )
#define ERRFILE_initrd ( ERRFILE_ARCH | ERRFILE_IMAGE | 0x000c0000 )
#define ERRFILE_pxe_call ( ERRFILE_ARCH | ERRFILE_IMAGE | 0x000d0000 )
+#define ERRFILE_ucode ( ERRFILE_ARCH | ERRFILE_IMAGE | 0x000e0000 )
#define ERRFILE_undi ( ERRFILE_ARCH | ERRFILE_NET | 0x00000000 )
#define ERRFILE_undiload ( ERRFILE_ARCH | ERRFILE_NET | 0x00010000 )
diff --git a/src/arch/x86/include/bits/mp.h b/src/arch/x86/include/bits/mp.h
new file mode 100644
index 00000000..4541aca3
--- /dev/null
+++ b/src/arch/x86/include/bits/mp.h
@@ -0,0 +1,14 @@
+#ifndef _BITS_MP_H
+#define _BITS_MP_H
+
+/** @file
+ *
+ * x86-specific multiprocessor API implementation
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+#include <ipxe/bios_mp.h>
+
+#endif /* _BITS_MP_H */
diff --git a/src/arch/x86/include/ipxe/bios_mp.h b/src/arch/x86/include/ipxe/bios_mp.h
new file mode 100644
index 00000000..e2e83a15
--- /dev/null
+++ b/src/arch/x86/include/ipxe/bios_mp.h
@@ -0,0 +1,32 @@
+#ifndef _IPXE_BIOS_MP_H
+#define _IPXE_BIOS_MP_H
+
+/** @file
+ *
+ * BIOS multiprocessor API implementation
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+#include <ipxe/io.h>
+
+#ifdef MPAPI_PCBIOS
+#define MPAPI_PREFIX_pcbios
+#else
+#define MPAPI_PREFIX_pcbios __pcbios_
+#endif
+
+/**
+ * Calculate address as seen by a multiprocessor function
+ *
+ * @v address Address in boot processor address space
+ * @ret address Address in application processor address space
+ */
+static inline __attribute__ (( always_inline )) mp_addr_t
+MPAPI_INLINE ( pcbios, mp_address ) ( void *address ) {
+
+ return virt_to_phys ( address );
+}
+
+#endif /* _IPXE_BIOS_MP_H */
diff --git a/src/arch/x86/include/ipxe/ucode.h b/src/arch/x86/include/ipxe/ucode.h
new file mode 100644
index 00000000..964e8d7b
--- /dev/null
+++ b/src/arch/x86/include/ipxe/ucode.h
@@ -0,0 +1,223 @@
+#ifndef _IPXE_UCODE_H
+#define _IPXE_UCODE_H
+
+/** @file
+ *
+ * Microcode updates
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+#include <stdint.h>
+#include <ipxe/mp.h>
+
+/** Platform ID MSR */
+#define MSR_PLATFORM_ID 0x00000017UL
+
+/** Extract platform ID from MSR value */
+#define MSR_PLATFORM_ID_VALUE( value ) ( ( (value) >> 50 ) & 0x7 )
+
+/** Intel microcode load trigger MSR */
+#define MSR_UCODE_TRIGGER_INTEL 0x00000079UL
+
+/** AMD microcode load trigger MSR */
+#define MSR_UCODE_TRIGGER_AMD 0xc0010020UL
+
+/** CPUID signature applicability mask
+ *
+ * We assume that only steppings may vary between the boot CPU and any
+ * application processors.
+ */
+#define UCODE_SIGNATURE_MASK 0xfffffff0UL
+
+/** Minimum possible microcode version */
+#define UCODE_VERSION_MIN -0x80000000L
+
+/** Maximum possible microcode version */
+#define UCODE_VERSION_MAX 0x7fffffffL
+
+/** A microcode update control
+ *
+ * This must match the layout as used by the assembly code in
+ * ucode_mp.S.
+ */
+struct ucode_control {
+ /** Microcode descriptor list physical address */
+ uint64_t desc;
+ /** Microcode status array physical address */
+ uint64_t status;
+ /** Microcode load trigger MSR */
+ uint32_t trigger_msr;
+ /** Maximum expected APIC ID */
+ uint32_t apic_max;
+ /** Unexpected APIC ID
+ *
+ * Any application processor may set this to indicate that its
+ * APIC ID was higher than the maximum expected APIC ID.
+ */
+ uint32_t apic_unexpected;
+ /** APIC ID eligibility mask bits */
+ uint32_t apic_mask;
+ /** APIC ID eligibility test bits */
+ uint32_t apic_test;
+ /** Microcode version requires manual clear */
+ uint8_t ver_clear;
+ /** Microcode version is reported via high dword */
+ uint8_t ver_high;
+} __attribute__ (( packed ));
+
+/** A microcode update descriptor
+ *
+ * This must match the layout as used by the assembly code in
+ * ucode_mp.S.
+ */
+struct ucode_descriptor {
+ /** CPUID signature (or 0 to terminate list) */
+ uint32_t signature;
+ /** Microcode version */
+ int32_t version;
+ /** Microcode physical address */
+ uint64_t address;
+} __attribute__ (( packed ));
+
+/** A microcode update status report
+ *
+ * This must match the layout as used by the assembly code in
+ * ucode_mp.S.
+ */
+struct ucode_status {
+ /** CPU signature */
+ uint32_t signature;
+ /** APIC ID (for sanity checking) */
+ uint32_t id;
+ /** Initial microcode version */
+ int32_t before;
+ /** Final microcode version */
+ int32_t after;
+} __attribute__ (( packed ));
+
+/** A microcode date */
+struct ucode_date {
+ /** Year (BCD) */
+ uint8_t year;
+ /** Century (BCD) */
+ uint8_t century;
+ /** Day (BCD) */
+ uint8_t day;
+ /** Month (BCD) */
+ uint8_t month;
+} __attribute__ (( packed ));
+
+/** An Intel microcode update file header */
+struct intel_ucode_header {
+ /** Header version number */
+ uint32_t hver;
+ /** Microcode version */
+ int32_t version;
+ /** Date */
+ struct ucode_date date;
+ /** CPUID signature */
+ uint32_t signature;
+ /** Checksum */
+ uint32_t checksum;
+ /** Loader version */
+ uint32_t lver;
+ /** Supported platforms */
+ uint32_t platforms;
+ /** Microcode data size (or 0 to indicate 2000 bytes) */
+ uint32_t data_len;
+ /** Total size (or 0 to indicate 2048 bytes) */
+ uint32_t len;
+ /** Reserved */
+ uint8_t reserved[12];
+} __attribute__ (( packed ));
+
+/** Intel microcode header version number */
+#define INTEL_UCODE_HVER 0x00000001UL
+
+/** Intel microcode loader version number */
+#define INTEL_UCODE_LVER 0x00000001UL
+
+/** Intel microcode default data length */
+#define INTEL_UCODE_DATA_LEN 2000
+
+/** Intel microcode file alignment */
+#define INTEL_UCODE_ALIGN 1024
+
+/** An Intel microcode update file extended header */
+struct intel_ucode_ext_header {
+ /** Extended signature count */
+ uint32_t count;
+ /** Extended checksum */
+ uint32_t checksum;
+ /** Reserved */
+ uint8_t reserved[12];
+} __attribute__ (( packed ));
+
+/** An Intel microcode extended signature */
+struct intel_ucode_ext {
+ /** CPUID signature */
+ uint32_t signature;
+ /** Supported platforms */
+ uint32_t platforms;
+ /** Checksum */
+ uint32_t checksum;
+} __attribute__ (( packed ));
+
+/** An AMD microcode update file header */
+struct amd_ucode_header {
+ /** Magic signature */
+ uint32_t magic;
+ /** Equivalence table type */
+ uint32_t type;
+ /** Equivalence table length */
+ uint32_t len;
+} __attribute__ (( packed ));
+
+/** AMD microcode magic signature */
+#define AMD_UCODE_MAGIC ( ( 'A' << 16 ) | ( 'M' << 8 ) | ( 'D' << 0 ) )
+
+/** AMD microcode equivalence table type */
+#define AMD_UCODE_EQUIV_TYPE 0x00000000UL
+
+/** An AMD microcode equivalence table entry */
+struct amd_ucode_equivalence {
+ /** CPU signature */
+ uint32_t signature;
+ /** Reserved */
+ uint8_t reserved_a[8];
+ /** Equivalence ID */
+ uint16_t id;
+ /** Reserved */
+ uint8_t reserved_b[2];
+} __attribute__ (( packed ));
+
+/** An AMD microcode patch header */
+struct amd_ucode_patch_header {
+ /** Patch type */
+ uint32_t type;
+ /** Patch length */
+ uint32_t len;
+} __attribute__ (( packed ));
+
+/** An AMD microcode patch */
+struct amd_ucode_patch {
+ /** Date */
+ struct ucode_date date;
+ /** Microcode version */
+ int32_t version;
+ /** Reserved */
+ uint8_t reserved_a[16];
+ /** Equivalence ID */
+ uint16_t id;
+ /** Reserved */
+ uint8_t reserved_b[14];
+} __attribute__ (( packed ));
+
+/** AMD patch type */
+#define AMD_UCODE_PATCH_TYPE 0x00000001UL
+
+extern mp_func_t ucode_update;
+
+#endif /* _IPXE_UCODE_H */
diff --git a/src/arch/x86/include/librm.h b/src/arch/x86/include/librm.h
index 40f07543..84b345d3 100644
--- a/src/arch/x86/include/librm.h
+++ b/src/arch/x86/include/librm.h
@@ -474,6 +474,26 @@ extern struct page_table io_pages;
*/
#define IO_BASE ( ( void * ) 0x100000000ULL )
+/** Startup IPI real-mode handler */
+extern char __text16_array ( sipi, [] );
+#define sipi __use_text16 ( sipi )
+
+/** Length of startup IPI real-mode handler */
+extern char sipi_len[];
+
+/** Startup IPI real-mode handler copy of real-mode data segment */
+extern uint16_t __text16 ( sipi_ds );
+#define sipi_ds __use_text16 ( sipi_ds )
+
+/** Startup IPI protected-mode handler (physical address) */
+extern uint32_t sipi_handler;
+
+/** Startup IPI register state */
+extern struct i386_regs sipi_regs;
+
+extern void setup_sipi ( unsigned int vector, uint32_t handler,
+ struct i386_regs *regs );
+
#endif /* ASSEMBLY */
#endif /* LIBRM_H */
diff --git a/src/arch/x86/interface/pcbios/bios_mp.c b/src/arch/x86/interface/pcbios/bios_mp.c
new file mode 100644
index 00000000..9e1179cc
--- /dev/null
+++ b/src/arch/x86/interface/pcbios/bios_mp.c
@@ -0,0 +1,173 @@
+/*
+ * 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
+ *
+ * BIOS multiprocessor API implementation
+ *
+ */
+
+#include <registers.h>
+#include <ipxe/uaccess.h>
+#include <ipxe/timer.h>
+#include <ipxe/msr.h>
+#include <ipxe/mp.h>
+
+/** Local APIC base address MSR */
+#define MSR_APIC_BASE 0x0000001b
+
+/** Local APIC is in x2APIC mode */
+#define MSR_APIC_BASE_X2APIC 0x400
+
+/** Local APIC base address mask */
+#define MSR_APIC_BASE_MASK ( ~0xfffULL )
+
+/** Interrupt command register */
+#define APIC_ICR 0x0300
+
+/** Interrupt command register (x2APIC) */
+#define MSR_X2APIC_ICR 0x830
+
+/** Interrupt command register: send to all excluding self */
+#define APIC_ICR_ALL_NOT_SELF 0x000c0000
+
+/** Interrupt command register: level mode */
+#define APIC_ICR_LEVEL 0x00008000
+
+/** Interrupt command register: level asserted */
+#define APIC_ICR_LEVEL_ASSERT 0x00004000
+
+/** Interrupt command register: INIT */
+#define APIC_ICR_INIT 0x00000500
+
+/** Interrupt command register: SIPI */
+#define APIC_ICR_SIPI( vector ) ( 0x00000600 | (vector) )
+
+/** Time to wait for an IPI to complete */
+#define IPI_WAIT_MS 10
+
+/**
+ * Startup IPI vector
+ *
+ * The real-mode startup IPI code must be copied to a page boundary in
+ * base memory. We fairly arbitrarily choose to place this at 0x8000.
+ */
+#define SIPI_VECTOR 0x08
+
+/** Protected-mode startup IPI handler */
+extern void __asmcall mp_jump ( mp_addr_t func, mp_addr_t opaque );
+
+/**
+ * Execute a multiprocessor function on the boot processor
+ *
+ * @v func Multiprocessor function
+ * @v opaque Opaque data pointer
+ */
+static void bios_mp_exec_boot ( mp_func_t func, void *opaque ) {
+
+ /* Call multiprocessor function with physical addressing */
+ __asm__ __volatile__ ( PHYS_CODE ( "pushl %k2\n\t"
+ "pushl %k1\n\t"
+ "call *%k0\n\t"
+ "addl $8, %%esp\n\t" )
+ : : "r" ( mp_address ( mp_call ) ),
+ "r" ( mp_address ( func ) ),
+ "r" ( mp_address ( opaque ) ) );
+}
+
+/**
+ * Send an interprocessor interrupt
+ *
+ * @v apic APIC base address
+ * @v x2apic x2APIC mode enabled
+ * @v icr Interrupt control register value
+ */
+static void bios_mp_ipi ( void *apic, int x2apic, uint32_t icr ) {
+
+ /* Write ICR according to APIC/x2APIC mode */
+ DBGC ( MSR_APIC_BASE, "BIOSMP sending IPI %#08x\n", icr );
+ if ( x2apic ) {
+ wrmsr ( MSR_X2APIC_ICR, icr );
+ } else {
+ writel ( icr, ( apic + APIC_ICR ) );
+ }
+
+ /* Allow plenty of time for delivery to complete */
+ mdelay ( IPI_WAIT_MS );
+}
+
+/**
+ * Start a multiprocessor function on all application processors
+ *
+ * @v func Multiprocessor function
+ * @v opaque Opaque data pointer
+ */
+static void bios_mp_start_all ( mp_func_t func, void *opaque ) {
+ struct i386_regs regs;
+ uint64_t base;
+ uint32_t ipi;
+ void *apic;
+ int x2apic;
+
+ /* Prepare SIPI handler */
+ regs.eax = mp_address ( func );
+ regs.edx = mp_address ( opaque );
+ setup_sipi ( SIPI_VECTOR, virt_to_phys ( mp_jump ), &regs );
+
+ /* Get local APIC base address and mode */
+ base = rdmsr ( MSR_APIC_BASE );
+ x2apic = ( base & MSR_APIC_BASE_X2APIC );
+ DBGC ( MSR_APIC_BASE, "BIOSMP local %sAPIC base %#llx\n",
+ ( x2apic ? "x2" : "" ), ( ( unsigned long long ) base ) );
+
+ /* Map local APIC */
+ apic = ioremap ( ( base & MSR_APIC_BASE_MASK ), PAGE_SIZE );
+ if ( ! apic )
+ goto err_ioremap;
+
+ /* Assert INIT IPI */
+ ipi = ( APIC_ICR_ALL_NOT_SELF | APIC_ICR_LEVEL |
+ APIC_ICR_LEVEL_ASSERT | APIC_ICR_INIT );
+ bios_mp_ipi ( apic, x2apic, ipi );
+
+ /* Clear INIT IPI */
+ ipi &= ~APIC_ICR_LEVEL_ASSERT;
+ bios_mp_ipi ( apic, x2apic, ipi );
+
+ /* Send SIPI */
+ ipi = ( APIC_ICR_ALL_NOT_SELF | APIC_ICR_SIPI ( SIPI_VECTOR ) );
+ bios_mp_ipi ( apic, x2apic, ipi );
+
+ iounmap ( apic );
+ err_ioremap:
+ /* No way to handle errors: caller must check that
+ * multiprocessor function executed as expected.
+ */
+ return;
+}
+
+PROVIDE_MPAPI_INLINE ( pcbios, mp_address );
+PROVIDE_MPAPI ( pcbios, mp_exec_boot, bios_mp_exec_boot );
+PROVIDE_MPAPI ( pcbios, mp_start_all, bios_mp_start_all );
diff --git a/src/arch/x86/transitions/librm.S b/src/arch/x86/transitions/librm.S
index 39431324..a93b0251 100644
--- a/src/arch/x86/transitions/librm.S
+++ b/src/arch/x86/transitions/librm.S
@@ -1632,3 +1632,70 @@ init_pages:
/* Return */
ret
+
+/****************************************************************************
+ * sipi (real-mode jump)
+ *
+ * Handle Startup IPI
+ *
+ * This code must be copied to a page-aligned boundary in base memory.
+ * It will be entered with %cs:0000 pointing to the start of the code.
+ * The stack pointer is undefined and so no stack space can be used.
+ *
+ ****************************************************************************
+ */
+ .section ".text16.sipi", "ax", @progbits
+ .code16
+ .globl sipi
+sipi:
+ /* Retrieve rm_ds from copy */
+ movw %cs:( sipi_ds - sipi ), %ax
+ movw %ax, %ds
+
+ /* Load GDT and switch to protected mode */
+ data32 lgdt gdtr
+ movl %cr0, %eax
+ orb $CR0_PE, %al
+ movl %eax, %cr0
+ data32 ljmp $VIRTUAL_CS, $VIRTUAL(1f)
+
+ /* Copy of rm_ds required to access GDT */
+ .globl sipi_ds
+sipi_ds:
+ .word 0
+
+ /* Length of real-mode SIPI handler to be copied */
+ .globl sipi_len
+ .equ sipi_len, . - sipi
+
+ .section ".text.sipi", "ax", @progbits
+ .code32
+1: /* Set up protected-mode segment registers (with no stack) */
+ movw $VIRTUAL_DS, %ax
+ movw %ax, %ds
+ movw %ax, %ss
+ movw $PHYSICAL_DS, %ax
+ movw %ax, %es
+ movw %ax, %fs
+ movw %ax, %gs
+
+ /* Load register state and clear stack pointer */
+ movl $VIRTUAL(sipi_regs), %esp
+ popal
+
+ /* Switch to flat physical addressing */
+ movw $PHYSICAL_DS, %sp
+ movw %sp, %ds
+ movw %sp, %ss
+
+ /* Clear stack pointer */
+ xorl %esp, %esp
+
+ /* Jump to protected-mode SIPI handler */
+ ljmp %cs:*VIRTUAL(sipi_handler)
+
+ /* Protected-mode SIPI handler vector */
+ .section ".data.sipi_handler", "aw", @progbits
+ .globl sipi_handler
+sipi_handler:
+ .long 0, PHYSICAL_CS
diff --git a/src/arch/x86/transitions/librm_mgmt.c b/src/arch/x86/transitions/librm_mgmt.c
index da221e8b..b3820589 100644
--- a/src/arch/x86/transitions/librm_mgmt.c
+++ b/src/arch/x86/transitions/librm_mgmt.c
@@ -45,6 +45,9 @@ struct idtr64 idtr64 = {
.limit = ( sizeof ( idt64 ) - 1 ),
};
+/** Startup IPI register state */
+struct i386_regs sipi_regs;
+
/** Length of stack dump */
#define STACK_DUMP_LEN 128
@@ -402,6 +405,29 @@ __asmcall void check_fxsr ( struct i386_all_regs *regs ) {
( ( regs->flags & CF ) ? " not" : "" ) );
}
+/**
+ * Set up startup IPI handler
+ *
+ * @v vector Startup IPI vector
+ * @v handler Protected-mode startup IPI handler physical address
+ * @v regs Initial register state
+ */
+void setup_sipi ( unsigned int vector, uint32_t handler,
+ struct i386_regs *regs ) {
+
+ /* Record protected-mode handler */
+ sipi_handler = handler;
+
+ /* Update copy of rm_ds */
+ sipi_ds = rm_ds;
+
+ /* Save register state */
+ memcpy ( &sipi_regs, regs, sizeof ( sipi_regs ) );
+
+ /* Copy real-mode handler */
+ copy_to_real ( ( vector << 8 ), 0, sipi, ( ( size_t ) sipi_len ) );
+}
+
PROVIDE_UACCESS_INLINE ( librm, phys_to_user );
PROVIDE_UACCESS_INLINE ( librm, user_to_phys );
PROVIDE_UACCESS_INLINE ( librm, virt_to_user );