diff options
Diffstat (limited to 'src/arch/x86')
-rw-r--r-- | src/arch/x86/core/mpcall.S | 197 | ||||
-rw-r--r-- | src/arch/x86/core/ucode_mp.S | 257 | ||||
-rw-r--r-- | src/arch/x86/image/ucode.c | 798 | ||||
-rw-r--r-- | src/arch/x86/include/bits/errfile.h | 1 | ||||
-rw-r--r-- | src/arch/x86/include/bits/mp.h | 14 | ||||
-rw-r--r-- | src/arch/x86/include/ipxe/bios_mp.h | 32 | ||||
-rw-r--r-- | src/arch/x86/include/ipxe/ucode.h | 223 | ||||
-rw-r--r-- | src/arch/x86/include/librm.h | 20 | ||||
-rw-r--r-- | src/arch/x86/interface/pcbios/bios_mp.c | 173 | ||||
-rw-r--r-- | src/arch/x86/transitions/librm.S | 67 | ||||
-rw-r--r-- | src/arch/x86/transitions/librm_mgmt.c | 26 |
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 ), ®s ); + + /* 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 ); |