blob: 499c0a94082bbbaa8f49844887cbec2b65a5e4b9 [file] [log] [blame]
/*
* 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,
};