git: 34467bd762a7 - main - x86/ucode: add support for early loading of CPU ucode on AMD.
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Thu, 22 Feb 2024 18:07:14 UTC
The branch main has been updated by chs: URL: https://cgit.FreeBSD.org/src/commit/?id=34467bd762a77345915d07f4f0630c3fe0b53a23 commit 34467bd762a77345915d07f4f0630c3fe0b53a23 Author: Chuck Silvers <chs@FreeBSD.org> AuthorDate: 2024-02-22 18:03:53 +0000 Commit: Chuck Silvers <chs@FreeBSD.org> CommitDate: 2024-02-22 18:04:31 +0000 x86/ucode: add support for early loading of CPU ucode on AMD. Sponsored by: Netflix Reviewed by: markj Differential Revision: https://reviews.freebsd.org/D43318 --- sys/conf/files.x86 | 1 + sys/x86/include/ucode.h | 11 ++ sys/x86/x86/ucode.c | 55 ++++++++++ sys/x86/x86/ucode_subr.c | 239 +++++++++++++++++++++++++++++++++++++++++++ usr.sbin/cpucontrol/Makefile | 6 +- usr.sbin/cpucontrol/amd.h | 41 -------- usr.sbin/cpucontrol/amd10h.c | 141 +------------------------ 7 files changed, 316 insertions(+), 178 deletions(-) diff --git a/sys/conf/files.x86 b/sys/conf/files.x86 index 15781eea8fee..8bd58ab4d339 100644 --- a/sys/conf/files.x86 +++ b/sys/conf/files.x86 @@ -347,6 +347,7 @@ x86/x86/pvclock.c optional kvm_clock | xenhvm x86/x86/stack_machdep.c optional ddb | stack x86/x86/tsc.c standard x86/x86/ucode.c standard +x86/x86/ucode_subr.c standard x86/x86/delay.c standard x86/xen/hvm.c optional xenhvm x86/xen/xen_apic.c optional xenhvm smp diff --git a/sys/x86/include/ucode.h b/sys/x86/include/ucode.h index e97d41c89ed0..0338d48a0832 100644 --- a/sys/x86/include/ucode.h +++ b/sys/x86/include/ucode.h @@ -31,6 +31,12 @@ #ifndef _MACHINE_UCODE_H_ #define _MACHINE_UCODE_H_ +#ifdef _KERNEL +#include <sys/types.h> +#else +#include <stdbool.h> +#endif + struct ucode_intel_header { uint32_t header_version; int32_t update_revision; @@ -56,8 +62,13 @@ struct ucode_intel_extsig_table { } entries[0]; }; +const void *ucode_amd_find(const char *path, uint32_t signature, + uint32_t revision, const uint8_t *fw_data, size_t fw_size, + size_t *selected_sizep); int ucode_intel_load(const void *data, bool unsafe, uint64_t *nrevp, uint64_t *orevp); +int ucode_amd_load(const void *data, bool unsafe, + uint64_t *nrevp, uint64_t *orevp); size_t ucode_load_bsp(uintptr_t free); void ucode_load_ap(int cpu); void ucode_reload(void); diff --git a/sys/x86/x86/ucode.c b/sys/x86/x86/ucode.c index 8e9f8e113c40..923d9e31b44e 100644 --- a/sys/x86/x86/ucode.c +++ b/sys/x86/x86/ucode.c @@ -54,6 +54,8 @@ static const void *ucode_intel_match(const uint8_t *data, size_t *len); static int ucode_intel_verify(const struct ucode_intel_header *hdr, size_t resid); +static const void *ucode_amd_match(const uint8_t *data, size_t *len); + static struct ucode_ops { const char *vendor; int (*load)(const void *, bool, uint64_t *, uint64_t *); @@ -64,6 +66,11 @@ static struct ucode_ops { .load = ucode_intel_load, .match = ucode_intel_match, }, + { + .vendor = AMD_VENDOR_ID, + .load = ucode_amd_load, + .match = ucode_amd_match, + }, }; /* Selected microcode update data. */ @@ -225,6 +232,54 @@ ucode_intel_match(const uint8_t *data, size_t *len) return (NULL); } +int +ucode_amd_load(const void *data, bool unsafe, uint64_t *nrevp, uint64_t *orevp) +{ + uint64_t nrev, orev; + uint32_t cpuid[4]; + + orev = rdmsr(MSR_BIOS_SIGN); + + /* + * Perform update. + */ + if (unsafe) + wrmsr_safe(MSR_K8_UCODE_UPDATE, (uint64_t)(uintptr_t)data); + else + wrmsr(MSR_K8_UCODE_UPDATE, (uint64_t)(uintptr_t)data); + + /* + * Serialize instruction flow. + */ + do_cpuid(0, cpuid); + + /* + * Verify that the microcode revision changed. + */ + nrev = rdmsr(MSR_BIOS_SIGN); + if (nrevp != NULL) + *nrevp = nrev; + if (orevp != NULL) + *orevp = orev; + if (nrev <= orev) + return (EEXIST); + return (0); + +} + +static const void * +ucode_amd_match(const uint8_t *data, size_t *len) +{ + uint32_t signature, revision; + uint32_t regs[4]; + + do_cpuid(1, regs); + signature = regs[0]; + revision = rdmsr(MSR_BIOS_SIGN); + + return (ucode_amd_find("loader blob", signature, revision, data, *len, len)); +} + /* * Release any memory backing unused microcode blobs back to the system. * We copy the selected update and free the entire microcode file. diff --git a/sys/x86/x86/ucode_subr.c b/sys/x86/x86/ucode_subr.c new file mode 100644 index 000000000000..9e128ad2bf04 --- /dev/null +++ b/sys/x86/x86/ucode_subr.c @@ -0,0 +1,239 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2006, 2008 Stanislav Sedov <stas@FreeBSD.org>. + * All rights reserved. + * Copyright (c) 2012 Andriy Gapon <avg@FreeBSD.org>. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/param.h> +#include <sys/systm.h> + +#include <x86/ucode.h> + +#ifndef _KERNEL +#include <err.h> + +#include "cpucontrol.h" +#endif + + +#ifdef _KERNEL +#define WARNX(level, ...) \ + if (bootverbose) { \ + printf(__VA_ARGS__); \ + printf("\n"); \ + } +#endif + +/* + * AMD family 10h and later. + */ +typedef struct amd_10h_fw_header { + uint32_t data_code; + uint32_t patch_id; + uint16_t mc_patch_data_id; + uint8_t mc_patch_data_len; + uint8_t init_flag; + uint32_t mc_patch_data_checksum; + uint32_t nb_dev_id; + uint32_t sb_dev_id; + uint16_t processor_rev_id; + uint8_t nb_rev_id; + uint8_t sb_rev_id; + uint8_t bios_api_rev; + uint8_t reserved1[3]; + uint32_t match_reg[8]; +} amd_10h_fw_header_t; + +typedef struct equiv_cpu_entry { + uint32_t installed_cpu; + uint32_t fixed_errata_mask; + uint32_t fixed_errata_compare; + uint16_t equiv_cpu; + uint16_t res; +} equiv_cpu_entry_t; + +typedef struct section_header { + uint32_t type; + uint32_t size; +} section_header_t; + +typedef struct container_header { + uint32_t magic; +} container_header_t; + +#define AMD_10H_MAGIC 0x414d44 +#define AMD_10H_EQUIV_TABLE_TYPE 0 +#define AMD_10H_uCODE_TYPE 1 + +/* + * NB: the format of microcode update files is not documented by AMD. + * It has been reverse engineered from studying Coreboot, illumos and Linux + * source code. + */ +const void * +ucode_amd_find(const char *path, uint32_t signature, uint32_t revision, + const uint8_t *fw_data, size_t fw_size, size_t *selected_sizep) +{ + const amd_10h_fw_header_t *fw_header; + const amd_10h_fw_header_t *selected_fw; + const equiv_cpu_entry_t *equiv_cpu_table; + const section_header_t *section_header; + const container_header_t *container_header; + size_t selected_size; + uint16_t equiv_id; + int i; + + WARNX(1, "found cpu family %#x model %#x " + "stepping %#x extfamily %#x extmodel %#x.", + ((signature >> 8) & 0x0f) + ((signature >> 20) & 0xff), + (signature >> 4) & 0x0f, + (signature >> 0) & 0x0f, (signature >> 20) & 0xff, + (signature >> 16) & 0x0f); + WARNX(1, "microcode revision %#x", revision); + +nextfile: + WARNX(1, "checking %s for update.", path); + WARNX(3, "processing next container file"); + if (fw_size < + (sizeof(*container_header) + sizeof(*section_header))) { + WARNX(2, "file too short: %s", path); + return (NULL); + } + + container_header = (const container_header_t *)fw_data; + if (container_header->magic != AMD_10H_MAGIC) { + WARNX(2, "%s is not a valid amd firmware: bad magic", path); + return (NULL); + } + fw_data += sizeof(*container_header); + fw_size -= sizeof(*container_header); + + section_header = (const section_header_t *)fw_data; + if (section_header->type != AMD_10H_EQUIV_TABLE_TYPE) { + WARNX(2, "%s is not a valid amd firmware: " + "first section is not CPU equivalence table", path); + return (NULL); + } + if (section_header->size == 0) { + WARNX(2, "%s is not a valid amd firmware: " + "first section is empty", path); + return (NULL); + } + fw_data += sizeof(*section_header); + fw_size -= sizeof(*section_header); + + if (section_header->size > fw_size) { + WARNX(2, "%s is not a valid amd firmware: " + "file is truncated", path); + return (NULL); + } + if (section_header->size < sizeof(*equiv_cpu_table)) { + WARNX(2, "%s is not a valid amd firmware: " + "first section is too short", path); + return (NULL); + } + equiv_cpu_table = (const equiv_cpu_entry_t *)fw_data; + fw_data += section_header->size; + fw_size -= section_header->size; + + equiv_id = 0; + for (i = 0; equiv_cpu_table[i].installed_cpu != 0; i++) { + WARNX(3, "signature 0x%x i %d installed_cpu 0x%x equiv 0x%x", + signature, i, equiv_cpu_table[i].installed_cpu, + equiv_cpu_table[i].equiv_cpu); + if (signature == equiv_cpu_table[i].installed_cpu) { + equiv_id = equiv_cpu_table[i].equiv_cpu; + WARNX(3, "equiv_id: %x, signature %8x," + " equiv_cpu_table[%d] %8x", equiv_id, signature, + i, equiv_cpu_table[i].installed_cpu); + break; + } + } + if (equiv_id == 0) { + WARNX(2, "CPU is not found in the equivalence table"); + } + + while (fw_size >= sizeof(*section_header)) { + section_header = (const section_header_t *)fw_data; + if (section_header->type == AMD_10H_MAGIC) { + WARNX(2, "%s next section is actually a new container", + path); + if (selected_fw != NULL) + goto found; + else + goto nextfile; + } + fw_data += sizeof(*section_header); + fw_size -= sizeof(*section_header); + if (section_header->type != AMD_10H_uCODE_TYPE) { + WARNX(2, "%s is not a valid amd firmware: " + "section has incorrect type", path); + break; + } + if (section_header->size > fw_size) { + WARNX(2, "%s is not a valid amd firmware: " + "file is truncated", path); + break; + } + if (section_header->size < sizeof(*fw_header)) { + WARNX(2, "%s is not a valid amd firmware: " + "section is too short", path); + break; + } + fw_header = (const amd_10h_fw_header_t *)fw_data; + fw_data += section_header->size; + fw_size -= section_header->size; + + if (fw_header->processor_rev_id != equiv_id) { + WARNX(1, "firmware processor_rev_id %x, equiv_id %x", + fw_header->processor_rev_id, equiv_id); + continue; /* different cpu */ + } + if (fw_header->patch_id <= revision) { + WARNX(1, "patch_id %x, revision %x", + fw_header->patch_id, revision); + continue; /* not newer revision */ + } + if (fw_header->nb_dev_id != 0 || fw_header->sb_dev_id != 0) { + WARNX(2, "Chipset-specific microcode is not supported"); + } + + WARNX(3, "selecting revision: %x", fw_header->patch_id); + revision = fw_header->patch_id; + selected_fw = fw_header; + selected_size = section_header->size; + } + + if (fw_size != 0) { + WARNX(2, "%s is not a valid amd firmware: " + "file is truncated", path); + selected_fw = NULL; + } + +found: + *selected_sizep = selected_size; + return (selected_fw); +} diff --git a/usr.sbin/cpucontrol/Makefile b/usr.sbin/cpucontrol/Makefile index 41f573d02cc2..a468ca59b461 100644 --- a/usr.sbin/cpucontrol/Makefile +++ b/usr.sbin/cpucontrol/Makefile @@ -1,8 +1,12 @@ PROG= cpucontrol MAN= cpucontrol.8 -SRCS= cpucontrol.c intel.c amd.c amd10h.c via.c +SRCS= cpucontrol.c intel.c amd.c amd10h.c via.c ucode_subr.c + +.PATH: ${SRCTOP}/sys/x86/x86 NO_WCAST_ALIGN= +CFLAGS+= -I${.CURDIR} + .include <bsd.prog.mk> diff --git a/usr.sbin/cpucontrol/amd.h b/usr.sbin/cpucontrol/amd.h index 7e53048a81d7..8e4efe60dca6 100644 --- a/usr.sbin/cpucontrol/amd.h +++ b/usr.sbin/cpucontrol/amd.h @@ -48,45 +48,4 @@ typedef struct amd_fw_header { #define AMD_MAGIC 0xaaaaaa -/* - * AMD family 10h and later. - */ -typedef struct amd_10h_fw_header { - uint32_t data_code; - uint32_t patch_id; - uint16_t mc_patch_data_id; - uint8_t mc_patch_data_len; - uint8_t init_flag; - uint32_t mc_patch_data_checksum; - uint32_t nb_dev_id; - uint32_t sb_dev_id; - uint16_t processor_rev_id; - uint8_t nb_rev_id; - uint8_t sb_rev_id; - uint8_t bios_api_rev; - uint8_t reserved1[3]; - uint32_t match_reg[8]; -} amd_10h_fw_header_t; - -typedef struct equiv_cpu_entry { - uint32_t installed_cpu; - uint32_t fixed_errata_mask; - uint32_t fixed_errata_compare; - uint16_t equiv_cpu; - uint16_t res; -} equiv_cpu_entry_t; - -typedef struct section_header { - uint32_t type; - uint32_t size; -} section_header_t; - -typedef struct container_header { - uint32_t magic; -} container_header_t; - -#define AMD_10H_MAGIC 0x414d44 -#define AMD_10H_EQUIV_TABLE_TYPE 0 -#define AMD_10H_uCODE_TYPE 1 - #endif /* !AMD_H */ diff --git a/usr.sbin/cpucontrol/amd10h.c b/usr.sbin/cpucontrol/amd10h.c index aceb6bfa2ba5..4fda44f0b797 100644 --- a/usr.sbin/cpucontrol/amd10h.c +++ b/usr.sbin/cpucontrol/amd10h.c @@ -33,6 +33,8 @@ #include <machine/cpufunc.h> #include <machine/specialreg.h> +#include <x86/ucode.h> + #include <assert.h> #include <stdio.h> #include <stdlib.h> @@ -79,33 +81,21 @@ amd10h_probe(int fd) return (0); } -/* - * NB: the format of microcode update files is not documented by AMD. - * It has been reverse engineered from studying Coreboot, illumos and Linux - * source code. - */ void amd10h_update(const struct ucode_update_params *params) { cpuctl_cpuid_args_t idargs; cpuctl_msr_args_t msrargs; cpuctl_update_args_t args; - const amd_10h_fw_header_t *fw_header; - const amd_10h_fw_header_t *selected_fw; - const equiv_cpu_entry_t *equiv_cpu_table; - const section_header_t *section_header; - const container_header_t *container_header; - const uint8_t *fw_data; const uint8_t *fw_image; const char *dev, *path; + const void *selected_fw; size_t fw_size; size_t selected_size; uint32_t revision; uint32_t new_rev; uint32_t signature; - uint16_t equiv_id; int devfd; - unsigned int i; int error; dev = params->dev_path; @@ -133,129 +123,8 @@ amd10h_update(const struct ucode_update_params *params) } revision = (uint32_t)msrargs.data; - WARNX(1, "found cpu family %#x model %#x " - "stepping %#x extfamily %#x extmodel %#x.", - ((signature >> 8) & 0x0f) + ((signature >> 20) & 0xff), - (signature >> 4) & 0x0f, - (signature >> 0) & 0x0f, (signature >> 20) & 0xff, - (signature >> 16) & 0x0f); - WARNX(1, "microcode revision %#x", revision); - - /* - * Open the firmware file. - */ - WARNX(1, "checking %s for update.", path); - if (fw_size < - (sizeof(*container_header) + sizeof(*section_header))) { - WARNX(2, "file too short: %s", path); - goto done; - } - - /* - * mmap the whole image. - */ - fw_data = fw_image; - container_header = (const container_header_t *)fw_data; - if (container_header->magic != AMD_10H_MAGIC) { - WARNX(2, "%s is not a valid amd firmware: bad magic", path); - goto done; - } - fw_data += sizeof(*container_header); - fw_size -= sizeof(*container_header); - - section_header = (const section_header_t *)fw_data; - if (section_header->type != AMD_10H_EQUIV_TABLE_TYPE) { - WARNX(2, "%s is not a valid amd firmware: " - "first section is not CPU equivalence table", path); - goto done; - } - if (section_header->size == 0) { - WARNX(2, "%s is not a valid amd firmware: " - "first section is empty", path); - goto done; - } - fw_data += sizeof(*section_header); - fw_size -= sizeof(*section_header); - - if (section_header->size > fw_size) { - WARNX(2, "%s is not a valid amd firmware: " - "file is truncated", path); - goto done; - } - if (section_header->size < sizeof(*equiv_cpu_table)) { - WARNX(2, "%s is not a valid amd firmware: " - "first section is too short", path); - goto done; - } - equiv_cpu_table = (const equiv_cpu_entry_t *)fw_data; - fw_data += section_header->size; - fw_size -= section_header->size; - - equiv_id = 0; - for (i = 0; equiv_cpu_table[i].installed_cpu != 0; i++) { - if (signature == equiv_cpu_table[i].installed_cpu) { - equiv_id = equiv_cpu_table[i].equiv_cpu; - WARNX(3, "equiv_id: %x, signature %8x," - " equiv_cpu_table[%d] %8x", equiv_id, signature, - i, equiv_cpu_table[i].installed_cpu); - break; - } - } - if (equiv_id == 0) { - WARNX(2, "CPU is not found in the equivalence table"); - goto done; - } - - selected_fw = NULL; - selected_size = 0; - while (fw_size >= sizeof(*section_header)) { - section_header = (const section_header_t *)fw_data; - fw_data += sizeof(*section_header); - fw_size -= sizeof(*section_header); - if (section_header->type != AMD_10H_uCODE_TYPE) { - WARNX(2, "%s is not a valid amd firmware: " - "section has incorrect type", path); - goto done; - } - if (section_header->size > fw_size) { - WARNX(2, "%s is not a valid amd firmware: " - "file is truncated", path); - goto done; - } - if (section_header->size < sizeof(*fw_header)) { - WARNX(2, "%s is not a valid amd firmware: " - "section is too short", path); - goto done; - } - fw_header = (const amd_10h_fw_header_t *)fw_data; - fw_data += section_header->size; - fw_size -= section_header->size; - - if (fw_header->processor_rev_id != equiv_id) { - WARNX(1, "firmware processor_rev_id %x, equiv_id %x", - fw_header->processor_rev_id, equiv_id); - continue; /* different cpu */ - } - if (fw_header->patch_id <= revision) { - WARNX(1, "patch_id %x, revision %x", - fw_header->patch_id, revision); - continue; /* not newer revision */ - } - if (fw_header->nb_dev_id != 0 || fw_header->sb_dev_id != 0) { - WARNX(2, "Chipset-specific microcode is not supported"); - } - - WARNX(3, "selecting revision: %x", fw_header->patch_id); - revision = fw_header->patch_id; - selected_fw = fw_header; - selected_size = section_header->size; - } - - if (fw_size != 0) { - WARNX(2, "%s is not a valid amd firmware: " - "file is truncated", path); - goto done; - } + selected_fw = ucode_amd_find(path, signature, revision, fw_image, + fw_size, &selected_size); if (selected_fw != NULL) { WARNX(1, "selected ucode size is %zu", selected_size);