diff options
Diffstat (limited to 'src/arch/x86/core')
-rw-r--r-- | src/arch/x86/core/ucode_mp.S | 257 |
1 files changed, 257 insertions, 0 deletions
diff --git a/src/arch/x86/core/ucode_mp.S b/src/arch/x86/core/ucode_mp.S new file mode 100644 index 0000000..808e881 --- /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 |