#include <sys/bootconf.h>
#include <sys/cmn_err.h>
#include <sys/controlregs.h>
#include <sys/utsname.h>
#include <sys/debug.h>
#include <sys/kobj.h>
#include <sys/kobj_impl.h>
#include <sys/ontrap.h>
#include <sys/stdbool.h>
#include <sys/systeminfo.h>
#include <sys/systm.h>
#include <sys/ucode.h>
#include <sys/x86_archext.h>
#include <sys/x_call.h>
static struct cpu_ucode_info cpu_ucode_info0;
static const ucode_source_t *ucode;
static char *ucodepath;
static kmutex_t ucode_lock;
static bool ucode_cleanup_done = false;
bool ucode_use_kmem = false;
static const char ucode_failure_fmt[] =
"cpu%d: failed to update microcode from version 0x%x to 0x%x";
static const char ucode_success_fmt[] =
"?cpu%d: microcode has been updated from version 0x%x to 0x%x\n";
static const char ucode_fallback_fmt[] =
"?cpu%d: using older fallback microcode; update the system firmware";
static const char ucode_path_fmt[] = "/platform/%s/ucode";
SET_DECLARE(ucode_source_set, ucode_source_t);
int ucode_force_update = 0;
void
ucode_init(void)
{
ucode_source_t **src;
mutex_init(&ucode_lock, NULL, MUTEX_DEFAULT, NULL);
SET_FOREACH(src, ucode_source_set) {
if ((*src)->us_select(CPU)) {
ucode = *src;
break;
}
}
if (ucode == NULL)
return;
#ifdef DEBUG
cmn_err(CE_CONT, "?ucode: selected %s\n", ucode->us_name);
if (!ucode->us_capable(CPU)) {
cmn_err(CE_CONT,
"?ucode: microcode update not supported on CPU\n");
return;
}
#endif
}
void
ucode_alloc_space(cpu_t *cp)
{
ASSERT(cp->cpu_id != 0);
ASSERT(cp->cpu_m.mcpu_ucode_info == NULL);
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_m.mcpu_ucode_info != NULL);
ASSERT(cp->cpu_m.mcpu_ucode_info != &cpu_ucode_info0);
kmem_free(cp->cpu_m.mcpu_ucode_info,
sizeof (*cp->cpu_m.mcpu_ucode_info));
cp->cpu_m.mcpu_ucode_info = NULL;
}
const char *
ucode_path(void)
{
ASSERT(ucodepath != NULL);
return (ucodepath);
}
void *
ucode_zalloc(size_t size)
{
if (ucode_use_kmem)
return (kmem_zalloc(size, KM_NOSLEEP));
return (BOP_ALLOC(bootops, NULL, size, MMU_PAGESIZE));
}
void
ucode_free(void *buf, size_t size)
{
if (ucode_use_kmem && buf != NULL)
kmem_free(buf, size);
}
void
ucode_cleanup(void)
{
mutex_enter(&ucode_lock);
if (ucode != NULL)
ucode->us_file_reset();
ucode_cleanup_done = true;
mutex_exit(&ucode_lock);
}
static int
ucode_write(xc_arg_t arg1, xc_arg_t unused2, xc_arg_t unused3)
{
ucode_update_t *uusp = (ucode_update_t *)arg1;
cpu_ucode_info_t *uinfop = CPU->cpu_m.mcpu_ucode_info;
on_trap_data_t otd;
ASSERT(ucode != NULL);
ASSERT(uusp->ucodep != NULL);
if (!ucode_force_update) {
ucode->us_read_rev(uinfop);
uusp->new_rev = uinfop->cui_rev;
if (uinfop->cui_rev >= uusp->expected_rev)
return (0);
}
if (!on_trap(&otd, OT_DATA_ACCESS)) {
if (ucode->us_invalidate) {
invalidate_cache();
}
wrmsr(ucode->us_write_msr, (uintptr_t)uusp->ucodep);
}
no_trap();
ucode->us_read_rev(uinfop);
uusp->new_rev = uinfop->cui_rev;
return (0);
}
ucode_errno_t
ucode_validate(uint8_t *ucodep, size_t size)
{
if (ucode == NULL)
return (EM_NOTSUP);
return (ucode->us_validate(ucodep, size));
}
ucode_errno_t
ucode_update(uint8_t *ucodep, size_t size)
{
bool found = false;
ucode_update_t cached = { 0 };
ucode_update_t *cachedp = NULL;
ucode_errno_t rc = EM_OK;
ucode_errno_t search_rc = EM_NOMATCH;
cpuset_t cpuset;
ASSERT(ucode != 0);
ASSERT(ucodep != 0);
CPUSET_ZERO(cpuset);
if (!ucode->us_capable(CPU))
return (EM_NOTSUP);
mutex_enter(&cpu_lock);
for (processorid_t id = 0; id < max_ncpus; id++) {
cpu_t *cpu;
ucode_update_t uus = { 0 };
ucode_update_t *uusp = &uus;
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 (cachedp && cachedp->sig == cpuid_getsig(cpu) &&
cachedp->info.cui_platid == uusp->info.cui_platid) {
uusp->ucodep = cachedp->ucodep;
uusp->expected_rev = cachedp->expected_rev;
} else if ((search_rc = ucode->us_extract(uusp, ucodep, size))
== EM_OK) {
bcopy(uusp, &cached, sizeof (cached));
cachedp = &cached;
found = true;
}
if (uusp->ucodep == NULL)
continue;
CPUSET_ADD(cpuset, id);
kpreempt_disable();
xc_sync((xc_arg_t)uusp, 0, 0, CPUSET2BV(cpuset), ucode_write);
kpreempt_enable();
CPUSET_DEL(cpuset, id);
if (uusp->new_rev != 0 && uusp->info.cui_rev == uusp->new_rev &&
!ucode_force_update) {
rc = EM_HIGHERREV;
} else if ((uusp->new_rev == 0) || (uusp->expected_rev != 0 &&
uusp->expected_rev != uusp->new_rev)) {
cmn_err(CE_WARN, ucode_failure_fmt,
id, uusp->info.cui_rev, uusp->expected_rev);
rc = EM_UPDATE;
} else {
cmn_err(CE_CONT, ucode_success_fmt,
id, uusp->info.cui_rev, uusp->new_rev);
}
}
mutex_exit(&cpu_lock);
if (!found) {
rc = search_rc;
} else if (rc == EM_OK) {
cpuid_post_ucodeadm();
}
return (rc);
}
void
ucode_read_rev(cpu_t *cp)
{
cpu_ucode_info_t *uinfop;
ASSERT3P(cp, !=, NULL);
if (ucode == NULL || !ucode->us_capable(cp))
return;
uinfop = cp->cpu_m.mcpu_ucode_info;
ASSERT3P(uinfop, !=, NULL);
ucode->us_read_rev(uinfop);
}
void
ucode_locate(cpu_t *cp)
{
cpu_ucode_info_t *uinfop;
ucode_errno_t rc;
size_t sz;
ASSERT3P(cp, !=, NULL);
ASSERT(ucode_use_kmem);
mutex_enter(&ucode_lock);
if (ucode == NULL || !ucode->us_capable(cp))
goto out;
if (ucodepath == NULL) {
sz = snprintf(NULL, 0, ucode_path_fmt, platform) + 1;
ucodepath = kmem_zalloc(sz, KM_NOSLEEP);
if (ucodepath == NULL) {
cmn_err(CE_WARN,
"ucode: could not allocate memory for path");
goto out;
}
(void) snprintf(ucodepath, sz, ucode_path_fmt, platform);
}
uinfop = cp->cpu_m.mcpu_ucode_info;
ASSERT3P(uinfop, !=, NULL);
rc = ucode->us_locate(cp, uinfop);
if ((rc != EM_OK && rc != EM_HIGHERREV) || ucode_cleanup_done)
ucode->us_file_reset();
out:
mutex_exit(&ucode_lock);
}
void
ucode_apply(cpu_t *cp)
{
cpu_ucode_info_t *uinfop;
ASSERT3P(cp, !=, NULL);
if (ucode == NULL || !ucode->us_capable(cp))
return;
uinfop = cp->cpu_m.mcpu_ucode_info;
ASSERT3P(uinfop, !=, NULL);
if (uinfop->cui_pending_ucode == NULL)
return;
uinfop->cui_boot_rev = uinfop->cui_rev;
ucode->us_load(uinfop);
ucode->us_read_rev(uinfop);
}
void
ucode_finish(cpu_t *cp)
{
cpu_ucode_info_t *uinfop;
uint32_t old_rev, new_rev;
ASSERT3P(cp, !=, NULL);
if (ucode == NULL || !ucode->us_capable(cp))
return;
uinfop = cp->cpu_m.mcpu_ucode_info;
ASSERT3P(uinfop, !=, NULL);
if (uinfop->cui_pending_ucode == NULL)
return;
old_rev = uinfop->cui_boot_rev;
new_rev = uinfop->cui_pending_rev;
if (uinfop->cui_rev != new_rev) {
ASSERT3U(uinfop->cui_rev, ==, old_rev);
cmn_err(CE_WARN, ucode_failure_fmt, cp->cpu_id, old_rev,
new_rev);
} else {
cmn_err(CE_CONT, ucode_success_fmt, cp->cpu_id, old_rev,
new_rev);
}
ucode_free(uinfop->cui_pending_ucode, uinfop->cui_pending_size);
uinfop->cui_pending_ucode = NULL;
uinfop->cui_pending_size = 0;
uinfop->cui_pending_rev = 0;
}
void
ucode_check_boot(void)
{
cpu_t *cp = CPU;
cpu_ucode_info_t *uinfop;
const char *prop;
char *plat;
int prop_len;
size_t path_len;
ASSERT3U(cp->cpu_id, ==, 0);
ASSERT(!ucode_use_kmem);
mutex_enter(&ucode_lock);
ASSERT3P(cp->cpu_m.mcpu_ucode_info, ==, NULL);
uinfop = cp->cpu_m.mcpu_ucode_info = &cpu_ucode_info0;
if (ucode == NULL || !ucode->us_capable(cp))
goto out;
ASSERT3P(ucodepath, ==, NULL);
prop = "impl-arch-name";
prop_len = BOP_GETPROPLEN(bootops, prop);
if (prop_len <= 0) {
cmn_err(CE_WARN, "ucode: could not find %s property", prop);
goto out;
}
plat = BOP_ALLOC(bootops, NULL, prop_len + 1, MMU_PAGESIZE);
(void) BOP_GETPROP(bootops, prop, plat);
if (plat[0] == '\0') {
cmn_err(CE_WARN, "ucode: could not determine arch");
goto out;
}
path_len = snprintf(NULL, 0, ucode_path_fmt, plat) + 1;
ucodepath = BOP_ALLOC(bootops, NULL, path_len, MMU_PAGESIZE);
(void) snprintf(ucodepath, path_len, ucode_path_fmt, plat);
ucode->us_read_rev(uinfop);
if (ucode->us_locate(cp, uinfop) == EM_OK) {
uint32_t old_rev, new_rev;
bool fallback = false;
old_rev = uinfop->cui_boot_rev = uinfop->cui_rev;
retry:
new_rev = uinfop->cui_pending_rev;
ucode->us_load(uinfop);
ucode->us_read_rev(uinfop);
if (uinfop->cui_rev != new_rev) {
ASSERT3U(uinfop->cui_rev, ==, old_rev);
cmn_err(CE_WARN, ucode_failure_fmt, cp->cpu_id,
old_rev, new_rev);
if (!fallback && ucode->us_locate_fallback != NULL) {
ucode->us_file_reset();
uinfop->cui_pending_ucode = NULL;
uinfop->cui_pending_size = 0;
uinfop->cui_pending_rev = 0;
if (ucode->us_locate_fallback(cp, uinfop) ==
EM_OK) {
cmn_err(CE_WARN, ucode_fallback_fmt,
cp->cpu_id);
fallback = true;
goto retry;
}
}
} else {
cmn_err(CE_CONT, ucode_success_fmt, cp->cpu_id,
old_rev, new_rev);
}
}
ucode->us_file_reset();
uinfop->cui_pending_ucode = NULL;
uinfop->cui_pending_size = 0;
uinfop->cui_pending_rev = 0;
out:
ucodepath = NULL;
ucode_use_kmem = true;
mutex_exit(&ucode_lock);
}
ucode_errno_t
ucode_get_rev(uint32_t *revp)
{
int i;
ASSERT(revp != NULL);
if (ucode == NULL || !ucode->us_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);
}