/* * 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 * */ #include #include #include #include #include #include #include #include #include #include #include #include /** * 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, };