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