| /* | |
| * 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" | |
| .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 |