#include <sys/param.h>
#include <sys/cpuset.h>
#include <sys/kernel.h>
#include <sys/linker.h>
#include <sys/malloc.h>
#include <sys/pcpu.h>
#include <sys/smp.h>
#include <sys/stdarg.h>
#include <sys/systm.h>
#include <machine/atomic.h>
#include <machine/cpufunc.h>
#include <machine/md_var.h>
#include <x86/specialreg.h>
#include <x86/ucode.h>
#include <x86/x86_smp.h>
#include <vm/vm.h>
#include <vm/pmap.h>
#include <vm/vm_extern.h>
#include <vm/vm_kern.h>
#include <vm/vm_param.h>
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 *, 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,
},
};
static const void *early_ucode_data;
static const void *ucode_data;
static struct ucode_ops *ucode_loader;
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, ucode_load_how how, uint64_t *nrevp,
uint64_t *orevp)
{
uint64_t nrev, orev;
uint32_t cpuid[4];
orev = rdmsr(MSR_BIOS_SIGN) >> 32;
wbinvd();
switch (how) {
case SAFE:
wrmsr_safe(MSR_BIOS_UPDT_TRIG, (uint64_t)(uintptr_t)data);
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);
do_cpuid(0, cpuid);
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, ucode_load_how how, uint64_t *nrevp,
uint64_t *orevp)
{
uint64_t nrev, orev;
uint32_t cpuid[4];
orev = rdmsr(MSR_BIOS_SIGN);
switch (how) {
case SAFE:
wrmsr_safe(MSR_K8_UCODE_UPDATE, (uint64_t)(uintptr_t)data);
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;
}
do_cpuid(0, cpuid);
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));
}
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 && 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
}
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);
memcpy_early(addr, match, len);
match = addr;
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);
}
void
ucode_reload(void)
{
ucode_load_ap(PCPU_GET(cpuid));
}
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);
}