diff options
author | sherrym <none@none> | 2007-07-02 14:05:35 -0700 |
---|---|---|
committer | sherrym <none@none> | 2007-07-02 14:05:35 -0700 |
commit | 2449e17f82f6097fd2c665b64723e31ceecbeca6 (patch) | |
tree | 4adce4537b78e91f1ac4f87433c9dddb715fffd2 /usr/src/uts/i86pc/os/microcode.c | |
parent | 76bc40308a78598795fbedd14f726061bcd17cad (diff) | |
download | illumos-joyent-2449e17f82f6097fd2c665b64723e31ceecbeca6.tar.gz |
PSARC/2007/349 Intel Microcode Update Support
6558456 Need to support microcode update on Intel platforms
Diffstat (limited to 'usr/src/uts/i86pc/os/microcode.c')
-rw-r--r-- | usr/src/uts/i86pc/os/microcode.c | 665 |
1 files changed, 665 insertions, 0 deletions
diff --git a/usr/src/uts/i86pc/os/microcode.c b/usr/src/uts/i86pc/os/microcode.c new file mode 100644 index 0000000000..19da208118 --- /dev/null +++ b/usr/src/uts/i86pc/os/microcode.c @@ -0,0 +1,665 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <sys/asm_linkage.h> +#include <sys/bootconf.h> +#include <sys/cpuvar.h> +#include <sys/cmn_err.h> +#include <sys/controlregs.h> +#include <sys/debug.h> +#include <sys/kobj.h> +#include <sys/kobj_impl.h> +#include <sys/machsystm.h> +#include <sys/param.h> +#include <sys/machparam.h> +#include <sys/promif.h> +#include <sys/sysmacros.h> +#include <sys/systm.h> +#include <sys/types.h> +#include <sys/thread.h> +#include <sys/ucode.h> +#include <sys/x86_archext.h> +#include <sys/x_call.h> + +/* + * Microcode specific information per core + */ +struct cpu_ucode_info { + uint32_t cui_platid; /* platform id */ + uint32_t cui_rev; /* microcode revision */ +}; + +/* + * Data structure used for xcall + */ +struct ucode_update_struct { + uint32_t sig; /* signature */ + struct cpu_ucode_info info; /* ucode info */ + uint32_t expected_rev; + uint32_t new_rev; + uint8_t *ucodep; /* pointer to ucode body */ +}; + +/* + * mcpu_ucode_info for the boot CPU. Statically allocated. + */ +static struct cpu_ucode_info cpu_ucode_info0; + +static ucode_file_t ucodefile = { 0 }; + +static int ucode_capable(cpu_t *); +static void ucode_file_reset(ucode_file_t *, processorid_t); +static ucode_errno_t ucode_match(int, struct cpu_ucode_info *, + ucode_header_t *, ucode_ext_table_t *); +static ucode_errno_t ucode_locate(cpu_t *, struct cpu_ucode_info *, + ucode_file_t *); +static void ucode_update_intel(uint8_t *, struct cpu_ucode_info *); +static void ucode_read_rev(struct cpu_ucode_info *); + +static const char ucode_failure_fmt[] = + "cpu%d: failed to update microcode code from version 0x%x to 0x%x\n"; +static const char ucode_success_fmt[] = + "?cpu%d: microcode code has been updated from version 0x%x to 0x%x\n"; + +/* + * Force flag. If set, the first microcode binary that matches + * signature and platform id will be used for microcode update, + * regardless of version. Should only be used for debugging. + */ +int ucode_force_update = 0; + +/* + * Allocate space for mcpu_ucode_info in the machcpu structure + * for all non-boot CPUs. + */ +void +ucode_alloc_space(cpu_t *cp) +{ + ASSERT(cp->cpu_id != 0); + cp->cpu_m.mcpu_ucode_info = + kmem_zalloc(sizeof (*cp->cpu_m.mcpu_ucode_info), KM_SLEEP); +} + +void +ucode_free_space(cpu_t *cp) +{ + ASSERT(cp->cpu_id != 0); + kmem_free(cp->cpu_m.mcpu_ucode_info, + sizeof (*cp->cpu_m.mcpu_ucode_info)); +} + +/* + * Called when we are done with microcode update on all processors to free up + * space allocated for the microcode file. + */ +void +ucode_free() +{ + ucode_file_reset(&ucodefile, -1); +} + +/* + * Check whether or not a processor is capable of microcode operations + * Returns 1 if it is capable, 0 if not. + */ +static int +ucode_capable(cpu_t *cp) +{ + /* + * At this point we only support microcode update for Intel + * processors family 6 and above. + * + * We also assume that we don't support a mix of Intel and + * AMD processors in the same box. + */ + if (cpuid_getvendor(cp) != X86_VENDOR_Intel || + cpuid_getfamily(cp) < 6) + return (0); + else + return (1); +} + +/* + * Called when it is no longer necessary to keep the microcode around, + * or when the cached microcode doesn't match the CPU being processed. + */ +static void +ucode_file_reset(ucode_file_t *ucodefp, processorid_t id) +{ + int total_size, body_size; + + if (ucodefp == NULL) + return; + + total_size = UCODE_TOTAL_SIZE(ucodefp->uf_header.uh_total_size); + body_size = UCODE_BODY_SIZE(ucodefp->uf_header.uh_body_size); + if (ucodefp->uf_body) { + /* + * Space for the boot CPU is allocated with BOP_ALLOC() + * and does not require a free. + */ + if (id != 0) + kmem_free(ucodefp->uf_body, body_size); + ucodefp->uf_body = NULL; + } + + if (ucodefp->uf_ext_table) { + int size = total_size - body_size - UCODE_HEADER_SIZE; + /* + * Space for the boot CPU is allocated with BOP_ALLOC() + * and does not require a free. + */ + if (id != 0) + kmem_free(ucodefp->uf_ext_table, size); + ucodefp->uf_ext_table = NULL; + } + + bzero(&ucodefp->uf_header, UCODE_HEADER_SIZE); +} + +/* + * Populate the ucode file structure from microcode file corresponding to + * this CPU, if exists. + * + * Return EM_OK on success, corresponding error code on failure. + */ +static ucode_errno_t +ucode_locate(cpu_t *cp, struct cpu_ucode_info *uinfop, ucode_file_t *ucodefp) +{ + char name[MAXPATHLEN]; + intptr_t fd; + int count; + int header_size = UCODE_HEADER_SIZE; + int cpi_sig = cpuid_getsig(cp); + ucode_errno_t rc = EM_OK; + + /* + * If the microcode matches the CPU we are processing, use it. + */ + if (ucode_match(cpi_sig, uinfop, &ucodefp->uf_header, + ucodefp->uf_ext_table) == EM_OK && ucodefp->uf_body != NULL) { + return (EM_OK); + } + + /* + * Look for microcode file with the right name. + */ + (void) snprintf(name, MAXPATHLEN, "/%s/%s/%08X-%02X", + UCODE_INSTALL_PATH, cpuid_getvendorstr(cp), cpi_sig, + uinfop->cui_platid); + if ((fd = kobj_open(name)) == -1) { + return (EM_OPENFILE); + } + + /* + * We found a microcode file for the CPU we are processing, + * reset the microcode data structure and read in the new + * file. + */ + ucode_file_reset(ucodefp, cp->cpu_id); + + count = kobj_read(fd, (char *)&ucodefp->uf_header, header_size, 0); + + switch (count) { + case UCODE_HEADER_SIZE: { + + ucode_header_t *uhp = &ucodefp->uf_header; + uint32_t offset = header_size; + int total_size, body_size, ext_size; + uint32_t sum = 0; + + /* + * Make sure that the header contains valid fields. + */ + if ((rc = ucode_header_validate(uhp)) == EM_OK) { + total_size = UCODE_TOTAL_SIZE(uhp->uh_total_size); + body_size = UCODE_BODY_SIZE(uhp->uh_body_size); + if (cp->cpu_id != 0) { + if ((ucodefp->uf_body = kmem_zalloc(body_size, + KM_NOSLEEP)) == NULL) { + rc = EM_NOMEM; + break; + } + } else { + /* + * BOP_ALLOC() failure results in panic so we + * don't have to check for NULL return. + */ + ucodefp->uf_body = + (uint8_t *)BOP_ALLOC(bootops, + NULL, body_size, MMU_PAGESIZE); + } + + if (kobj_read(fd, (char *)ucodefp->uf_body, + body_size, offset) != body_size) + rc = EM_FILESIZE; + } + + if (rc) + break; + + sum = ucode_checksum(0, header_size, + (uint8_t *)&ucodefp->uf_header); + if (ucode_checksum(sum, body_size, ucodefp->uf_body)) { + rc = EM_CHECKSUM; + break; + } + + /* + * Check to see if there is extended signature table. + */ + offset = body_size + header_size; + ext_size = total_size - offset; + + if (ext_size <= 0) + break; + + if (cp->cpu_id != 0) { + if ((ucodefp->uf_ext_table = kmem_zalloc(ext_size, + KM_NOSLEEP)) == NULL) { + rc = EM_NOMEM; + break; + } + } else { + /* + * BOP_ALLOC() failure results in panic so we + * don't have to check for NULL return. + */ + ucodefp->uf_ext_table = + (ucode_ext_table_t *)BOP_ALLOC(bootops, NULL, + ext_size, MMU_PAGESIZE); + } + + if (kobj_read(fd, (char *)ucodefp->uf_ext_table, + ext_size, offset) != ext_size) { + rc = EM_FILESIZE; + } else if (ucode_checksum(0, ext_size, + (uint8_t *)(ucodefp->uf_ext_table))) { + rc = EM_CHECKSUM; + } else { + int i; + + ext_size -= UCODE_EXT_TABLE_SIZE; + for (i = 0; i < ucodefp->uf_ext_table->uet_count; + i++) { + if (ucode_checksum(0, UCODE_EXT_SIG_SIZE, + (uint8_t *)(&(ucodefp->uf_ext_table-> + uet_ext_sig[i])))) { + rc = EM_CHECKSUM; + break; + } + } + } + break; + } + + default: + rc = EM_FILESIZE; + break; + } + + kobj_close(fd); + + if (rc != EM_OK) + return (rc); + + rc = ucode_match(cpi_sig, uinfop, &ucodefp->uf_header, + ucodefp->uf_ext_table); + + return (rc); +} + + +/* + * Returns 1 if the microcode is for this processor; 0 otherwise. + */ +static ucode_errno_t +ucode_match(int cpi_sig, struct cpu_ucode_info *uinfop, + ucode_header_t *uhp, ucode_ext_table_t *uetp) +{ + ASSERT(uhp); + + if (UCODE_MATCH(cpi_sig, uhp->uh_signature, + uinfop->cui_platid, uhp->uh_proc_flags)) { + + if (uinfop->cui_rev >= uhp->uh_rev && !ucode_force_update) + return (EM_HIGHERREV); + + return (EM_OK); + } + + if (uetp != NULL) { + int i; + + for (i = 0; i < uetp->uet_count; i++) { + ucode_ext_sig_t *uesp; + + uesp = &uetp->uet_ext_sig[i]; + + if (UCODE_MATCH(cpi_sig, uesp->ues_signature, + uinfop->cui_platid, uesp->ues_proc_flags)) { + + if (uinfop->cui_rev >= uhp->uh_rev && + !ucode_force_update) + return (EM_HIGHERREV); + + return (EM_OK); + } + } + } + + return (EM_NOMATCH); +} + +/*ARGSUSED*/ +static int +ucode_write(xc_arg_t arg1, xc_arg_t unused2, xc_arg_t unused3) +{ + struct ucode_update_struct *uusp = (struct ucode_update_struct *)arg1; + struct cpu_ucode_info *uinfop = CPU->cpu_m.mcpu_ucode_info; + + ASSERT(uusp->ucodep); + + /* + * Check one more time to see if it is really necessary to update + * microcode just in case this is a hyperthreaded processor where + * the threads share the same microcode. + */ + if (!ucode_force_update) { + ucode_read_rev(uinfop); + uusp->new_rev = uinfop->cui_rev; + if (uinfop->cui_rev >= uusp->expected_rev) + return (0); + } + + wrmsr(MSR_INTC_UCODE_WRITE, + (uint64_t)(intptr_t)(uusp->ucodep)); + ucode_read_rev(uinfop); + uusp->new_rev = uinfop->cui_rev; + + return (0); +} + + +static void +ucode_update_intel(uint8_t *ucode_body, struct cpu_ucode_info *uinfop) +{ + kpreempt_disable(); + wrmsr(MSR_INTC_UCODE_WRITE, (uint64_t)(uintptr_t)ucode_body); + ucode_read_rev(uinfop); + kpreempt_enable(); +} + +static void +ucode_read_rev(struct cpu_ucode_info *uinfop) +{ + struct cpuid_regs crs; + + /* + * The Intel 64 and IA-32 Architecture Software Developer's Manual + * recommends that MSR_INTC_UCODE_REV be loaded with 0 first, then + * execute cpuid to guarantee the correct reading of this register. + */ + wrmsr(MSR_INTC_UCODE_REV, 0); + (void) __cpuid_insn(&crs); + uinfop->cui_rev = (rdmsr(MSR_INTC_UCODE_REV) >> INTC_UCODE_REV_SHIFT); +} + +/* + * Entry point to microcode update from the ucode_drv driver. + * + * Returns EM_OK on success, corresponding error code on failure. + */ +ucode_errno_t +ucode_update(uint8_t *ucodep, int size) +{ + uint32_t header_size = UCODE_HEADER_SIZE; + int remaining; + int found = 0; + processorid_t id; + struct ucode_update_struct cached = { 0 }; + struct ucode_update_struct *cachedp = NULL; + ucode_errno_t rc = EM_OK; + ucode_errno_t search_rc = EM_NOMATCH; /* search result */ + cpuset_t cpuset; + + ASSERT(ucodep); + + CPUSET_ZERO(cpuset); + + if (!ucode_capable(CPU)) + return (EM_NOTSUP); + + mutex_enter(&cpu_lock); + + for (id = 0; id < max_ncpus; id++) { + cpu_t *cpu; + struct ucode_update_struct uus = { 0 }; + struct ucode_update_struct *uusp = &uus; + + /* + * If there is no such CPU or it is not xcall ready, skip it. + */ + if ((cpu = cpu_get(id)) == NULL || + !(cpu->cpu_flags & CPU_READY)) + continue; + + uusp->sig = cpuid_getsig(cpu); + bcopy(cpu->cpu_m.mcpu_ucode_info, &uusp->info, + sizeof (uusp->info)); + + /* + * If the current CPU has the same signature and platform + * id as the previous one we processed, reuse the information. + */ + if (cachedp && cachedp->sig == cpuid_getsig(cpu) && + cachedp->info.cui_platid == uusp->info.cui_platid) { + uusp->ucodep = cachedp->ucodep; + uusp->expected_rev = cachedp->expected_rev; + /* + * Intuitively we should check here to see whether the + * running microcode rev is >= the expected rev, and + * quit if it is. But we choose to proceed with the + * xcall regardless of the running version so that + * the other threads in an HT processor can update + * the cpu_ucode_info structure in machcpu. + */ + } else { + /* + * Go through the whole buffer in case there are + * multiple versions of matching microcode for this + * processor. + */ + for (remaining = size; remaining > 0; ) { + int total_size, body_size, ext_size; + uint8_t *curbuf = &ucodep[size - remaining]; + ucode_header_t *uhp = (ucode_header_t *)curbuf; + ucode_ext_table_t *uetp = NULL; + ucode_errno_t tmprc; + + total_size = + UCODE_TOTAL_SIZE(uhp->uh_total_size); + body_size = UCODE_BODY_SIZE(uhp->uh_body_size); + ext_size = total_size - + (header_size + body_size); + + if (ext_size > 0) + uetp = (ucode_ext_table_t *) + &curbuf[header_size + body_size]; + + tmprc = ucode_match(uusp->sig, &uusp->info, + uhp, uetp); + + /* + * Since we are searching through a big file + * containing microcode for pretty much all the + * processors, we are bound to get EM_NOMATCH + * at one point. However, if we return + * EM_NOMATCH to users, it will really confuse + * them. Therefore, if we ever find a match of + * a lower rev, we will set return code to + * EM_HIGHERREV. + */ + if (tmprc == EM_HIGHERREV) + search_rc = EM_HIGHERREV; + + if (tmprc == EM_OK && + uusp->expected_rev < uhp->uh_rev) { + uusp->ucodep = &curbuf[header_size]; + uusp->expected_rev = uhp->uh_rev; + bcopy(uusp, &cached, sizeof (cached)); + cachedp = &cached; + found = 1; + } + + remaining -= total_size; + } + } + + /* Nothing to do */ + if (uusp->ucodep == NULL) + continue; + + CPUSET_ADD(cpuset, id); + kpreempt_disable(); + xc_sync((xc_arg_t)uusp, 0, 0, X_CALL_HIPRI, cpuset, + ucode_write); + kpreempt_enable(); + CPUSET_DEL(cpuset, id); + + if (uusp->expected_rev == uusp->new_rev) { + cmn_err(CE_CONT, ucode_success_fmt, + id, uusp->info.cui_rev, uusp->expected_rev); + } else { + cmn_err(CE_WARN, ucode_failure_fmt, + id, uusp->info.cui_rev, uusp->expected_rev); + rc = EM_UPDATE; + } + } + + mutex_exit(&cpu_lock); + + if (!found) + rc = search_rc; + + return (rc); +} + +/* + * Initialize mcpu_ucode_info, and perform microcode update if necessary. + * This is the entry point from boot path where pointer to CPU structure + * is available. + * + * cpuid_info must be initialized before ucode_check can be called. + */ +void +ucode_check(cpu_t *cp) +{ +#ifdef __xpv +{ + This needs to be ported. Only do ucode update from dom0. In + addition figure out how to bind to physical CPUs when doing + it in dom0. +} +#endif /* __xpv */ + + struct cpu_ucode_info *uinfop; + ucode_errno_t rc = EM_OK; + + ASSERT(cp); + if (cp->cpu_id == 0) + cp->cpu_m.mcpu_ucode_info = &cpu_ucode_info0; + + uinfop = cp->cpu_m.mcpu_ucode_info; + ASSERT(uinfop); + + if (!ucode_capable(cp)) + return; + + /* + * The MSR_INTC_PLATFORM_ID is supported in Celeron and Xeon + * (Family 6, model 5 and above) and all processors after. + */ + if ((cpuid_getmodel(cp) >= 5) || (cpuid_getfamily(cp) > 6)) { + uinfop->cui_platid = 1 << ((rdmsr(MSR_INTC_PLATFORM_ID) >> + INTC_PLATFORM_ID_SHIFT) & INTC_PLATFORM_ID_MASK); + } + + ucode_read_rev(uinfop); + + /* + * Check to see if we need ucode update + */ + if ((rc = ucode_locate(cp, uinfop, &ucodefile)) == EM_OK) { + ucode_update_intel(ucodefile.uf_body, uinfop); + + if (uinfop->cui_rev != ucodefile.uf_header.uh_rev) + cmn_err(CE_WARN, ucode_failure_fmt, cp->cpu_id, + uinfop->cui_rev, ucodefile.uf_header.uh_rev); + } + + /* + * If we fail to find a match for any reason, free the file structure + * just in case we have read in a partial file. + * + * Since the scratch memory for holding the microcode for the boot CPU + * came from BOP_ALLOC, we will reset the data structure as if we + * never did the allocation so we don't have to keep track of this + * special chunk of memory. We free the memory used for the rest + * of the CPUs in start_other_cpus(). + */ + if (rc != EM_OK || cp->cpu_id == 0) + ucode_file_reset(&ucodefile, cp->cpu_id); +} + +/* + * Returns microcode revision from the machcpu structure. + */ +ucode_errno_t +ucode_get_rev(uint32_t *revp) +{ + int i; + + ASSERT(revp); + + if (!ucode_capable(CPU)) + return (EM_NOTSUP); + + mutex_enter(&cpu_lock); + for (i = 0; i < max_ncpus; i++) { + cpu_t *cpu; + + if ((cpu = cpu_get(i)) == NULL) + continue; + + revp[i] = cpu->cpu_m.mcpu_ucode_info->cui_rev; + } + mutex_exit(&cpu_lock); + + return (EM_OK); +} |