/* * Copyright (C) 2024 Michael Brown . * * 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