diff --git a/sys/dev/cpuctl/cpuctl.c b/sys/dev/cpuctl/cpuctl.c index b0ab3467df69..deaabaaaa1fc 100644 --- a/sys/dev/cpuctl/cpuctl.c +++ b/sys/dev/cpuctl/cpuctl.c @@ -1,602 +1,602 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2006-2008 Stanislav Sedov * 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static d_open_t cpuctl_open; static d_ioctl_t cpuctl_ioctl; #define CPUCTL_VERSION 1 #ifdef CPUCTL_DEBUG # define DPRINTF(format,...) printf(format, __VA_ARGS__); #else # define DPRINTF(...) #endif #define UCODE_SIZE_MAX (4 * 1024 * 1024) static int cpuctl_do_msr(int cpu, cpuctl_msr_args_t *data, u_long cmd, struct thread *td); static int cpuctl_do_cpuid(int cpu, cpuctl_cpuid_args_t *data, struct thread *td); static int cpuctl_do_cpuid_count(int cpu, cpuctl_cpuid_count_args_t *data, struct thread *td); static int cpuctl_do_eval_cpu_features(int cpu, struct thread *td); static int cpuctl_do_update(int cpu, cpuctl_update_args_t *data, struct thread *td); static int update_intel(int cpu, cpuctl_update_args_t *args, struct thread *td); static int update_amd(int cpu, cpuctl_update_args_t *args, struct thread *td); static int update_via(int cpu, cpuctl_update_args_t *args, struct thread *td); static struct cdev **cpuctl_devs; static MALLOC_DEFINE(M_CPUCTL, "cpuctl", "CPUCTL buffer"); static struct cdevsw cpuctl_cdevsw = { .d_version = D_VERSION, .d_open = cpuctl_open, .d_ioctl = cpuctl_ioctl, .d_name = "cpuctl", }; /* * This function checks if specified cpu enabled or not. */ static int cpu_enabled(int cpu) { return (pmc_cpu_is_disabled(cpu) == 0); } /* * Check if the current thread is bound to a specific cpu. */ static int cpu_sched_is_bound(struct thread *td) { int ret; thread_lock(td); ret = sched_is_bound(td); thread_unlock(td); return (ret); } /* * Switch to target cpu to run. */ static void set_cpu(int cpu, struct thread *td) { KASSERT(cpu >= 0 && cpu <= mp_maxid && cpu_enabled(cpu), ("[cpuctl,%d]: bad cpu number %d", __LINE__, cpu)); thread_lock(td); sched_bind(td, cpu); thread_unlock(td); KASSERT(td->td_oncpu == cpu, ("[cpuctl,%d]: cannot bind to target cpu %d on cpu %d", __LINE__, cpu, td->td_oncpu)); } static void restore_cpu(int oldcpu, int is_bound, struct thread *td) { KASSERT(oldcpu >= 0 && oldcpu <= mp_maxid && cpu_enabled(oldcpu), ("[cpuctl,%d]: bad cpu number %d", __LINE__, oldcpu)); thread_lock(td); if (is_bound == 0) sched_unbind(td); else sched_bind(td, oldcpu); thread_unlock(td); } int cpuctl_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int flags, struct thread *td) { int cpu, ret; cpu = dev2unit(dev); if (cpu > mp_maxid || !cpu_enabled(cpu)) { DPRINTF("[cpuctl,%d]: bad cpu number %d\n", __LINE__, cpu); return (ENXIO); } /* Require write flag for "write" requests. */ if ((cmd == CPUCTL_MSRCBIT || cmd == CPUCTL_MSRSBIT || cmd == CPUCTL_UPDATE || cmd == CPUCTL_WRMSR || cmd == CPUCTL_EVAL_CPU_FEATURES) && (flags & FWRITE) == 0) return (EPERM); switch (cmd) { case CPUCTL_RDMSR: ret = cpuctl_do_msr(cpu, (cpuctl_msr_args_t *)data, cmd, td); break; case CPUCTL_MSRSBIT: case CPUCTL_MSRCBIT: case CPUCTL_WRMSR: ret = priv_check(td, PRIV_CPUCTL_WRMSR); if (ret != 0) goto fail; ret = cpuctl_do_msr(cpu, (cpuctl_msr_args_t *)data, cmd, td); break; case CPUCTL_CPUID: ret = cpuctl_do_cpuid(cpu, (cpuctl_cpuid_args_t *)data, td); break; case CPUCTL_UPDATE: ret = priv_check(td, PRIV_CPUCTL_UPDATE); if (ret != 0) goto fail; ret = cpuctl_do_update(cpu, (cpuctl_update_args_t *)data, td); break; case CPUCTL_CPUID_COUNT: ret = cpuctl_do_cpuid_count(cpu, (cpuctl_cpuid_count_args_t *)data, td); break; case CPUCTL_EVAL_CPU_FEATURES: ret = cpuctl_do_eval_cpu_features(cpu, td); break; default: ret = EINVAL; break; } fail: return (ret); } /* * Actually perform cpuid operation. */ static int cpuctl_do_cpuid_count(int cpu, cpuctl_cpuid_count_args_t *data, struct thread *td) { int is_bound = 0; int oldcpu; KASSERT(cpu >= 0 && cpu <= mp_maxid, ("[cpuctl,%d]: bad cpu number %d", __LINE__, cpu)); /* Explicitly clear cpuid data to avoid returning stale info. */ bzero(data->data, sizeof(data->data)); DPRINTF("[cpuctl,%d]: retrieving cpuid lev %#0x type %#0x for %d cpu\n", __LINE__, data->level, data->level_type, cpu); #ifdef __i386__ if (cpu_id == 0) return (ENODEV); #endif oldcpu = td->td_oncpu; is_bound = cpu_sched_is_bound(td); set_cpu(cpu, td); cpuid_count(data->level, data->level_type, data->data); restore_cpu(oldcpu, is_bound, td); return (0); } static int cpuctl_do_cpuid(int cpu, cpuctl_cpuid_args_t *data, struct thread *td) { cpuctl_cpuid_count_args_t cdata; int error; cdata.level = data->level; /* Override the level type. */ cdata.level_type = 0; error = cpuctl_do_cpuid_count(cpu, &cdata, td); bcopy(cdata.data, data->data, sizeof(data->data)); /* Ignore error */ return (error); } /* * Actually perform MSR operations. */ static int cpuctl_do_msr(int cpu, cpuctl_msr_args_t *data, u_long cmd, struct thread *td) { uint64_t reg; int is_bound = 0; int oldcpu; int ret; KASSERT(cpu >= 0 && cpu <= mp_maxid, ("[cpuctl,%d]: bad cpu number %d", __LINE__, cpu)); /* * Explicitly clear cpuid data to avoid returning stale * info */ DPRINTF("[cpuctl,%d]: operating on MSR %#0x for %d cpu\n", __LINE__, data->msr, cpu); #ifdef __i386__ if ((cpu_feature & CPUID_MSR) == 0) return (ENODEV); #endif oldcpu = td->td_oncpu; is_bound = cpu_sched_is_bound(td); set_cpu(cpu, td); if (cmd == CPUCTL_RDMSR) { data->data = 0; ret = rdmsr_safe(data->msr, &data->data); } else if (cmd == CPUCTL_WRMSR) { ret = wrmsr_safe(data->msr, data->data); } else if (cmd == CPUCTL_MSRSBIT) { critical_enter(); ret = rdmsr_safe(data->msr, ®); if (ret == 0) ret = wrmsr_safe(data->msr, reg | data->data); critical_exit(); } else if (cmd == CPUCTL_MSRCBIT) { critical_enter(); ret = rdmsr_safe(data->msr, ®); if (ret == 0) ret = wrmsr_safe(data->msr, reg & ~data->data); critical_exit(); } else panic("[cpuctl,%d]: unknown operation requested: %lu", __LINE__, cmd); restore_cpu(oldcpu, is_bound, td); return (ret); } /* * Actually perform microcode update. */ static int cpuctl_do_update(int cpu, cpuctl_update_args_t *data, struct thread *td) { cpuctl_cpuid_args_t args = { .level = 0, }; char vendor[13]; int ret; KASSERT(cpu >= 0 && cpu <= mp_maxid, ("[cpuctl,%d]: bad cpu number %d", __LINE__, cpu)); DPRINTF("[cpuctl,%d]: XXX %d", __LINE__, cpu); ret = cpuctl_do_cpuid(cpu, &args, td); if (ret != 0) return (ret); ((uint32_t *)vendor)[0] = args.data[1]; ((uint32_t *)vendor)[1] = args.data[3]; ((uint32_t *)vendor)[2] = args.data[2]; vendor[12] = '\0'; if (strncmp(vendor, INTEL_VENDOR_ID, sizeof(INTEL_VENDOR_ID)) == 0) ret = update_intel(cpu, data, td); else if(strncmp(vendor, AMD_VENDOR_ID, sizeof(AMD_VENDOR_ID)) == 0) ret = update_amd(cpu, data, td); else if(strncmp(vendor, CENTAUR_VENDOR_ID, sizeof(CENTAUR_VENDOR_ID)) == 0) ret = update_via(cpu, data, td); else ret = ENXIO; return (ret); } struct ucode_update_data { void *ptr; int cpu; int ret; }; static void ucode_intel_load_rv(void *arg) { struct ucode_update_data *d; d = arg; if (PCPU_GET(cpuid) == d->cpu) - d->ret = ucode_intel_load(d->ptr, true, NULL, NULL); + d->ret = ucode_intel_load(d->ptr, SAFE, NULL, NULL); } static int update_intel(int cpu, cpuctl_update_args_t *args, struct thread *td) { struct ucode_update_data d; void *ptr; int is_bound, oldcpu, ret; if (args->size == 0 || args->data == NULL) { DPRINTF("[cpuctl,%d]: zero-sized firmware image", __LINE__); return (EINVAL); } if (args->size > UCODE_SIZE_MAX) { DPRINTF("[cpuctl,%d]: firmware image too large", __LINE__); return (EINVAL); } /* * 16 byte alignment required. Rely on the fact that * malloc(9) always returns the pointer aligned at least on * the size of the allocation. */ ptr = malloc(args->size + 16, M_CPUCTL, M_WAITOK); if (copyin(args->data, ptr, args->size) != 0) { DPRINTF("[cpuctl,%d]: copyin %p->%p of %zd bytes failed", __LINE__, args->data, ptr, args->size); ret = EFAULT; goto out; } oldcpu = td->td_oncpu; is_bound = cpu_sched_is_bound(td); set_cpu(cpu, td); d.ptr = ptr; d.cpu = cpu; smp_rendezvous(NULL, ucode_intel_load_rv, NULL, &d); restore_cpu(oldcpu, is_bound, td); ret = d.ret; /* * Replace any existing update. This ensures that the new update * will be reloaded automatically during ACPI resume. */ if (ret == 0) ptr = ucode_update(ptr); out: free(ptr, M_CPUCTL); return (ret); } /* * NB: MSR 0xc0010020, MSR_K8_UCODE_UPDATE, is not documented by AMD. * Coreboot, illumos and Linux source code was used to understand * its workings. */ static void amd_ucode_wrmsr(void *arg) { struct ucode_update_data *d = arg; uint32_t tmp[4]; if (PCPU_GET(cpuid) == d->cpu) d->ret = wrmsr_safe(MSR_K8_UCODE_UPDATE, (uintptr_t)d->ptr); do_cpuid(0, tmp); } static int update_amd(int cpu, cpuctl_update_args_t *args, struct thread *td) { struct ucode_update_data d = { .cpu = cpu }; if (args->size == 0 || args->data == NULL) { DPRINTF("[cpuctl,%d]: zero-sized firmware image", __LINE__); return (EINVAL); } if (args->size > UCODE_SIZE_MAX) { DPRINTF("[cpuctl,%d]: firmware image too large", __LINE__); return (EINVAL); } /* * 16 byte alignment required. Rely on the fact that * malloc(9) always returns the pointer aligned at least on * the size of the allocation. */ d.ptr = malloc(args->size + 16, M_CPUCTL, M_ZERO | M_WAITOK); if (copyin(args->data, d.ptr, args->size) != 0) { DPRINTF("[cpuctl,%d]: copyin %p->%p of %zd bytes failed", __LINE__, args->data, ptr, args->size); d.ret = EFAULT; goto fail; } smp_rendezvous(NULL, amd_ucode_wrmsr, NULL, &d); fail: free(d.ptr, M_CPUCTL); return (d.ret); } static int update_via(int cpu, cpuctl_update_args_t *args, struct thread *td) { void *ptr; uint64_t rev0, rev1, res; uint32_t tmp[4]; int is_bound; int oldcpu; int ret; if (args->size == 0 || args->data == NULL) { DPRINTF("[cpuctl,%d]: zero-sized firmware image", __LINE__); return (EINVAL); } if (args->size > UCODE_SIZE_MAX) { DPRINTF("[cpuctl,%d]: firmware image too large", __LINE__); return (EINVAL); } /* * 4 byte alignment required. */ ptr = malloc(args->size, M_CPUCTL, M_WAITOK); if (copyin(args->data, ptr, args->size) != 0) { DPRINTF("[cpuctl,%d]: copyin %p->%p of %zd bytes failed", __LINE__, args->data, ptr, args->size); ret = EFAULT; goto fail; } oldcpu = td->td_oncpu; is_bound = cpu_sched_is_bound(td); set_cpu(cpu, td); critical_enter(); rdmsr_safe(MSR_BIOS_SIGN, &rev0); /* Get current microcode revision. */ /* * Perform update. */ wrmsr_safe(MSR_BIOS_UPDT_TRIG, (uintptr_t)(ptr)); do_cpuid(1, tmp); /* * Result are in low byte of MSR FCR5: * 0x00: No update has been attempted since RESET. * 0x01: The last attempted update was successful. * 0x02: The last attempted update was unsuccessful due to a bad * environment. No update was loaded and any preexisting * patches are still active. * 0x03: The last attempted update was not applicable to this processor. * No update was loaded and any preexisting patches are still * active. * 0x04: The last attempted update was not successful due to an invalid * update data block. No update was loaded and any preexisting * patches are still active */ rdmsr_safe(0x1205, &res); res &= 0xff; critical_exit(); rdmsr_safe(MSR_BIOS_SIGN, &rev1); /* Get new microcode revision. */ restore_cpu(oldcpu, is_bound, td); DPRINTF("[cpu,%d]: rev0=%x rev1=%x res=%x\n", __LINE__, (unsigned)(rev0 >> 32), (unsigned)(rev1 >> 32), (unsigned)res); if (res != 0x01) ret = EINVAL; else ret = 0; fail: free(ptr, M_CPUCTL); return (ret); } static int cpuctl_do_eval_cpu_features(int cpu, struct thread *td) { int is_bound = 0; int oldcpu; KASSERT(cpu >= 0 && cpu <= mp_maxid, ("[cpuctl,%d]: bad cpu number %d", __LINE__, cpu)); #ifdef __i386__ if (cpu_id == 0) return (ENODEV); #endif oldcpu = td->td_oncpu; is_bound = cpu_sched_is_bound(td); set_cpu(cpu, td); identify_cpu1(); identify_cpu2(); restore_cpu(oldcpu, is_bound, td); hw_ibrs_recalculate(true); hw_ssb_recalculate(true); #ifdef __amd64__ amd64_syscall_ret_flush_l1d_recalc(); pmap_allow_2m_x_ept_recalculate(); #endif hw_mds_recalculate(); x86_taa_recalculate(); x86_rngds_mitg_recalculate(true); zenbleed_check_and_apply(true); printcpuinfo(); return (0); } int cpuctl_open(struct cdev *dev, int flags, int fmt __unused, struct thread *td) { int ret = 0; int cpu; cpu = dev2unit(dev); if (cpu > mp_maxid || !cpu_enabled(cpu)) { DPRINTF("[cpuctl,%d]: incorrect cpu number %d\n", __LINE__, cpu); return (ENXIO); } if (flags & FWRITE) ret = securelevel_gt(td->td_ucred, 0); return (ret); } static int cpuctl_modevent(module_t mod __unused, int type, void *data __unused) { int cpu; switch(type) { case MOD_LOAD: if (bootverbose) printf("cpuctl: access to MSR registers/cpuid info.\n"); cpuctl_devs = malloc(sizeof(*cpuctl_devs) * (mp_maxid + 1), M_CPUCTL, M_WAITOK | M_ZERO); CPU_FOREACH(cpu) if (cpu_enabled(cpu)) cpuctl_devs[cpu] = make_dev(&cpuctl_cdevsw, cpu, UID_ROOT, GID_KMEM, 0640, "cpuctl%d", cpu); break; case MOD_UNLOAD: CPU_FOREACH(cpu) { if (cpuctl_devs[cpu] != NULL) destroy_dev(cpuctl_devs[cpu]); } free(cpuctl_devs, M_CPUCTL); break; case MOD_SHUTDOWN: break; default: return (EOPNOTSUPP); } return (0); } DEV_MODULE(cpuctl, cpuctl_modevent, NULL); MODULE_VERSION(cpuctl, CPUCTL_VERSION); diff --git a/sys/x86/include/ucode.h b/sys/x86/include/ucode.h index 75b9ff3afbd0..ea7cb07669a4 100644 --- a/sys/x86/include/ucode.h +++ b/sys/x86/include/ucode.h @@ -1,77 +1,79 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2018 The FreeBSD Foundation * * This software was developed by Mark Johnston under sponsorship from * the FreeBSD Foundation. * * 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. */ #ifndef _MACHINE_UCODE_H_ #define _MACHINE_UCODE_H_ #ifdef _KERNEL #include #else #include #endif struct ucode_intel_header { uint32_t header_version; int32_t update_revision; uint32_t dat; uint32_t processor_signature; uint32_t checksum; uint32_t loader_revision; uint32_t processor_flags; #define UCODE_INTEL_DEFAULT_DATA_SIZE 2000 uint32_t data_size; uint32_t total_size; uint32_t reserved[3]; }; struct ucode_intel_extsig_table { uint32_t signature_count; uint32_t signature_table_checksum; uint32_t reserved[3]; struct ucode_intel_extsig { uint32_t processor_signature; uint32_t processor_flags; uint32_t checksum; } entries[0]; }; +typedef enum { SAFE, UNSAFE, EARLY } ucode_load_how; + 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, +int ucode_intel_load(const void *data, ucode_load_how unsafe, uint64_t *nrevp, uint64_t *orevp); -int ucode_amd_load(const void *data, bool unsafe, +int ucode_amd_load(const void *data, ucode_load_how how, uint64_t *nrevp, uint64_t *orevp); size_t ucode_load_bsp(uintptr_t free); void ucode_load_ap(int cpu); void ucode_reload(void); void * ucode_update(void *data); #endif /* _MACHINE_UCODE_H_ */ diff --git a/sys/x86/x86/ucode.c b/sys/x86/x86/ucode.c index 1973047fafd1..72133de211f8 100644 --- a/sys/x86/x86/ucode.c +++ b/sys/x86/x86/ucode.c @@ -1,455 +1,485 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2018 The FreeBSD Foundation * * This software was developed by Mark Johnston under sponsorship from * the FreeBSD Foundation. * * 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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 #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include 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 *); + int (*load)(const void *, ucode_load_how how, uint64_t *, uint64_t *); const void *(*match)(const uint8_t *, size_t *); } loaders[] = { { .vendor = INTEL_VENDOR_ID, .load = ucode_intel_load, .match = ucode_intel_match, }, { .vendor = AMD_VENDOR_ID, .load = ucode_amd_load, .match = ucode_amd_match, }, }; /* Selected microcode update data. */ static const void *early_ucode_data; static const void *ucode_data; static struct ucode_ops *ucode_loader; /* Variables used for reporting success or failure. */ enum { NO_ERROR, NO_MATCH, VERIFICATION_FAILED, + LOAD_FAILED, } ucode_error = NO_ERROR; static uint64_t ucode_nrev, ucode_orev; static void log_msg(void *arg __unused) { if (ucode_nrev != 0) { printf("CPU microcode: updated from %#jx to %#jx\n", (uintmax_t)ucode_orev, (uintmax_t)ucode_nrev); return; } switch (ucode_error) { case NO_MATCH: printf("CPU microcode: no matching update found\n"); break; case VERIFICATION_FAILED: printf("CPU microcode: microcode verification failed\n"); break; + case LOAD_FAILED: + printf("CPU microcode load failed. BIOS update advised\n"); + break; default: break; } } SYSINIT(ucode_log, SI_SUB_CPU, SI_ORDER_FIRST, log_msg, NULL); int -ucode_intel_load(const void *data, bool unsafe, uint64_t *nrevp, uint64_t *orevp) +ucode_intel_load(const void *data, ucode_load_how how, uint64_t *nrevp, + uint64_t *orevp) { uint64_t nrev, orev; uint32_t cpuid[4]; orev = rdmsr(MSR_BIOS_SIGN) >> 32; /* * Perform update. Flush caches first to work around seemingly * undocumented errata applying to some Broadwell CPUs. */ wbinvd(); - if (unsafe) + switch (how) { + case SAFE: wrmsr_safe(MSR_BIOS_UPDT_TRIG, (uint64_t)(uintptr_t)data); - else + break; + case EARLY: +#ifdef __amd64__ + wrmsr_early_safe_start(); + if (wrmsr_early_safe(MSR_BIOS_UPDT_TRIG, + (uint64_t)(uintptr_t)data) != 0) + ucode_error = LOAD_FAILED; + wrmsr_early_safe_end(); + break; +#endif + case UNSAFE: wrmsr(MSR_BIOS_UPDT_TRIG, (uint64_t)(uintptr_t)data); + break; + } wrmsr(MSR_BIOS_SIGN, 0); /* * Serialize instruction flow. */ do_cpuid(0, cpuid); /* * Verify that the microcode revision changed. */ nrev = rdmsr(MSR_BIOS_SIGN) >> 32; if (nrevp != NULL) *nrevp = nrev; if (orevp != NULL) *orevp = orev; if (nrev <= orev) return (EEXIST); return (0); } static int ucode_intel_verify(const struct ucode_intel_header *hdr, size_t resid) { const uint32_t *data; uint32_t cksum, size; int i; if (resid < sizeof(struct ucode_intel_header)) return (1); size = hdr->total_size; if (size == 0) size = UCODE_INTEL_DEFAULT_DATA_SIZE + sizeof(struct ucode_intel_header); if (hdr->header_version != 1) return (1); if (size % 16 != 0) return (1); if (resid < size) return (1); cksum = 0; data = (const uint32_t *)hdr; for (i = 0; i < size / sizeof(uint32_t); i++) cksum += data[i]; if (cksum != 0) return (1); return (0); } static const void * ucode_intel_match(const uint8_t *data, size_t *len) { const struct ucode_intel_header *hdr; const struct ucode_intel_extsig_table *table; const struct ucode_intel_extsig *entry; uint64_t platformid; size_t resid; uint32_t data_size, flags, regs[4], sig, total_size; int i; do_cpuid(1, regs); sig = regs[0]; platformid = rdmsr(MSR_IA32_PLATFORM_ID); flags = 1 << ((platformid >> 50) & 0x7); for (resid = *len; resid > 0; data += total_size, resid -= total_size) { hdr = (const struct ucode_intel_header *)data; if (ucode_intel_verify(hdr, resid) != 0) { ucode_error = VERIFICATION_FAILED; break; } data_size = hdr->data_size; total_size = hdr->total_size; if (data_size == 0) data_size = UCODE_INTEL_DEFAULT_DATA_SIZE; if (total_size == 0) total_size = UCODE_INTEL_DEFAULT_DATA_SIZE + sizeof(struct ucode_intel_header); if (data_size > total_size + sizeof(struct ucode_intel_header)) table = (const struct ucode_intel_extsig_table *) ((const uint8_t *)(hdr + 1) + data_size); else table = NULL; if (hdr->processor_signature == sig) { if ((hdr->processor_flags & flags) != 0) { *len = data_size; return (hdr + 1); } } else if (table != NULL) { for (i = 0; i < table->signature_count; i++) { entry = &table->entries[i]; if (entry->processor_signature == sig && (entry->processor_flags & flags) != 0) { *len = data_size; return (hdr + 1); } } } } return (NULL); } int -ucode_amd_load(const void *data, bool unsafe, uint64_t *nrevp, uint64_t *orevp) +ucode_amd_load(const void *data, ucode_load_how how, uint64_t *nrevp, + uint64_t *orevp) { uint64_t nrev, orev; uint32_t cpuid[4]; orev = rdmsr(MSR_BIOS_SIGN); - /* - * Perform update. - */ - if (unsafe) + switch (how) { + case SAFE: wrmsr_safe(MSR_K8_UCODE_UPDATE, (uint64_t)(uintptr_t)data); - else + break; + case EARLY: +#ifdef __amd64__ + wrmsr_early_safe_start(); + if (wrmsr_early_safe(MSR_K8_UCODE_UPDATE, + (uint64_t)(uintptr_t)data) != 0) + ucode_error = LOAD_FAILED; + wrmsr_early_safe_end(); + break; +#endif + case UNSAFE: wrmsr(MSR_K8_UCODE_UPDATE, (uint64_t)(uintptr_t)data); + break; + } /* * 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. */ static void ucode_release(void *arg __unused) { char *name, *type; caddr_t file; int release; if (early_ucode_data == NULL) return; release = 1; TUNABLE_INT_FETCH("debug.ucode.release", &release); if (!release) return; restart: file = 0; for (;;) { file = preload_search_next_name(file); if (file == 0) break; type = (char *)preload_search_info(file, MODINFO_TYPE); if (type == NULL || strcmp(type, "cpu_microcode") != 0) continue; name = preload_search_info(file, MODINFO_NAME); preload_delete_name(name); goto restart; } } SYSINIT(ucode_release, SI_SUB_SMP + 1, SI_ORDER_ANY, ucode_release, NULL); void ucode_load_ap(int cpu) { #ifdef SMP KASSERT(cpu_info[cpu_apic_ids[cpu]].cpu_present, ("cpu %d not present", cpu)); if (cpu_info[cpu_apic_ids[cpu]].cpu_hyperthread) return; #endif - if (ucode_data != NULL) - (void)ucode_loader->load(ucode_data, false, NULL, NULL); + if (ucode_data != NULL && ucode_error != LOAD_FAILED) + (void)ucode_loader->load(ucode_data, UNSAFE, NULL, NULL); } static void * map_ucode(uintptr_t free, size_t len) { #ifdef __i386__ uintptr_t va; for (va = free; va < free + len; va += PAGE_SIZE) pmap_kenter(va, (vm_paddr_t)va); #else (void)len; #endif return ((void *)free); } static void unmap_ucode(uintptr_t free, size_t len) { #ifdef __i386__ uintptr_t va; for (va = free; va < free + len; va += PAGE_SIZE) pmap_kremove(va); #else (void)free; (void)len; #endif } /* * Search for an applicable microcode update, and load it. APs will load the * selected update once they come online. * * "free" is the address of the next free physical page. If a microcode update * is selected, it will be copied to this region prior to loading in order to * satisfy alignment requirements. */ size_t ucode_load_bsp(uintptr_t free) { union { uint32_t regs[4]; char vendor[13]; } cpuid; const uint8_t *fileaddr, *match; uint8_t *addr; char *type; uint64_t nrev, orev; caddr_t file; size_t i, len; int error; KASSERT(free % PAGE_SIZE == 0, ("unaligned boundary %p", (void *)free)); do_cpuid(0, cpuid.regs); cpuid.regs[0] = cpuid.regs[1]; cpuid.regs[1] = cpuid.regs[3]; cpuid.vendor[12] = '\0'; for (i = 0; i < nitems(loaders); i++) if (strcmp(cpuid.vendor, loaders[i].vendor) == 0) { ucode_loader = &loaders[i]; break; } if (ucode_loader == NULL) return (0); file = 0; fileaddr = match = NULL; for (;;) { file = preload_search_next_name(file); if (file == 0) break; type = (char *)preload_search_info(file, MODINFO_TYPE); if (type == NULL || strcmp(type, "cpu_microcode") != 0) continue; fileaddr = preload_fetch_addr(file); len = preload_fetch_size(file); match = ucode_loader->match(fileaddr, &len); if (match != NULL) { addr = map_ucode(free, len); /* We can't use memcpy() before ifunc resolution. */ memcpy_early(addr, match, len); match = addr; - error = ucode_loader->load(match, false, &nrev, &orev); + error = ucode_loader->load(match, EARLY, &nrev, &orev); if (error == 0) { ucode_data = early_ucode_data = match; ucode_nrev = nrev; ucode_orev = orev; return (len); } unmap_ucode(free, len); } } if (fileaddr != NULL && ucode_error == NO_ERROR) ucode_error = NO_MATCH; return (0); } /* * Reload microcode following an ACPI resume. */ void ucode_reload(void) { ucode_load_ap(PCPU_GET(cpuid)); } /* * Replace an existing microcode update. */ void * ucode_update(void *newdata) { newdata = (void *)atomic_swap_ptr((void *)&ucode_data, (uintptr_t)newdata); if (newdata == early_ucode_data) newdata = NULL; return (newdata); }