#include <sys/stdbool.h>
#include <sys/cmn_err.h>
#include <sys/controlregs.h>
#include <sys/kobj.h>
#include <sys/kobj_impl.h>
#include <sys/ontrap.h>
#include <sys/sysmacros.h>
#include <sys/systm.h>
#include <sys/ucode.h>
#include <sys/ucode_intel.h>
#include <ucode/ucode_errno.h>
#include <ucode/ucode_utils_intel.h>
#include <sys/x86_archext.h>
extern void *ucode_zalloc(size_t);
extern void ucode_free(void *, size_t);
extern const char *ucode_path(void);
extern int ucode_force_update;
static ucode_file_intel_t intel_ucodef;
static bool
ucode_select_intel(cpu_t *cp)
{
if ((get_hwenv() & HW_VIRTUAL) != 0)
return (false);
return (cpuid_getvendor(cp) == X86_VENDOR_Intel);
}
static bool
ucode_capable_intel(cpu_t *cp)
{
return (cpuid_getfamily(cp) >= 6);
}
static void
ucode_file_reset_intel(void)
{
ucode_file_intel_t *ucodefp = &intel_ucodef;
int total_size, body_size;
if (ucodefp->uf_header == NULL)
return;
total_size = UCODE_TOTAL_SIZE_INTEL(ucodefp->uf_header->uh_total_size);
body_size = UCODE_BODY_SIZE_INTEL(ucodefp->uf_header->uh_body_size);
if (ucodefp->uf_body != NULL) {
ucode_free(ucodefp->uf_body, body_size);
ucodefp->uf_body = NULL;
}
if (ucodefp->uf_ext_table != NULL) {
int size = total_size - body_size - UCODE_HEADER_SIZE_INTEL;
ucode_free(ucodefp->uf_ext_table, size);
ucodefp->uf_ext_table = NULL;
}
ucode_free(ucodefp->uf_header, UCODE_HEADER_SIZE_INTEL);
ucodefp->uf_header = NULL;
}
static ucode_errno_t
ucode_match_intel(int cpi_sig, cpu_ucode_info_t *uinfop,
ucode_header_intel_t *uhp, ucode_ext_table_intel_t *uetp)
{
if (uhp == NULL)
return (EM_NOMATCH);
if (UCODE_MATCH_INTEL(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) {
for (uint_t i = 0; i < uetp->uet_count; i++) {
ucode_ext_sig_intel_t *uesp;
uesp = &uetp->uet_ext_sig[i];
if (UCODE_MATCH_INTEL(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);
}
static ucode_errno_t
ucode_copy_intel(const ucode_file_intel_t *ucodefp, cpu_ucode_info_t *uinfop)
{
ASSERT3P(ucodefp->uf_header, !=, NULL);
ASSERT3P(ucodefp->uf_body, !=, NULL);
ASSERT3P(uinfop->cui_pending_ucode, ==, NULL);
size_t sz = UCODE_BODY_SIZE_INTEL(ucodefp->uf_header->uh_body_size);
uinfop->cui_pending_ucode = ucode_zalloc(sz);
if (uinfop->cui_pending_ucode == NULL)
return (EM_NOMEM);
memcpy(uinfop->cui_pending_ucode, ucodefp->uf_body, sz);
uinfop->cui_pending_size = sz;
uinfop->cui_pending_rev = ucodefp->uf_header->uh_rev;
return (EM_OK);
}
static ucode_errno_t
ucode_locate_intel(cpu_t *cp, cpu_ucode_info_t *uinfop)
{
char name[MAXPATHLEN];
intptr_t fd;
int count;
int header_size = UCODE_HEADER_SIZE_INTEL;
int cpi_sig = cpuid_getsig(cp);
ucode_errno_t rc = EM_OK;
ucode_file_intel_t *ucodefp = &intel_ucodef;
if (ucode_match_intel(cpi_sig, uinfop, ucodefp->uf_header,
ucodefp->uf_ext_table) == EM_OK && ucodefp->uf_body != NULL) {
return (ucode_copy_intel(ucodefp, uinfop));
}
(void) snprintf(name, MAXPATHLEN, "%s/%s/%08X-%02X",
ucode_path(), cpuid_getvendorstr(cp), cpi_sig,
uinfop->cui_platid);
if ((fd = kobj_open(name)) == -1) {
return (EM_OPENFILE);
}
ucode_file_reset_intel();
ucodefp->uf_header = ucode_zalloc(header_size);
if (ucodefp->uf_header == NULL)
return (EM_NOMEM);
count = kobj_read(fd, (char *)ucodefp->uf_header, header_size, 0);
switch (count) {
case UCODE_HEADER_SIZE_INTEL: {
ucode_header_intel_t *uhp = ucodefp->uf_header;
uint32_t offset = header_size;
int total_size, body_size, ext_size;
uint32_t sum = 0;
if ((rc = ucode_header_validate_intel(uhp)) == EM_OK) {
total_size = UCODE_TOTAL_SIZE_INTEL(uhp->uh_total_size);
body_size = UCODE_BODY_SIZE_INTEL(uhp->uh_body_size);
ucodefp->uf_body = ucode_zalloc(body_size);
if (ucodefp->uf_body == NULL) {
rc = EM_NOMEM;
break;
}
if (kobj_read(fd, (char *)ucodefp->uf_body,
body_size, offset) != body_size)
rc = EM_FILESIZE;
}
if (rc)
break;
sum = ucode_checksum_intel(0, header_size,
(uint8_t *)ucodefp->uf_header);
if (ucode_checksum_intel(sum, body_size, ucodefp->uf_body)) {
rc = EM_CHECKSUM;
break;
}
offset = body_size + header_size;
ext_size = total_size - offset;
if (ext_size <= 0)
break;
ucodefp->uf_ext_table = ucode_zalloc(ext_size);
if (ucodefp->uf_ext_table == NULL) {
rc = EM_NOMEM;
break;
}
if (kobj_read(fd, (char *)ucodefp->uf_ext_table,
ext_size, offset) != ext_size) {
rc = EM_FILESIZE;
} else if (ucode_checksum_intel(0, ext_size,
(uint8_t *)(ucodefp->uf_ext_table))) {
rc = EM_EXTCHECKSUM;
} else {
int i;
for (i = 0; i < ucodefp->uf_ext_table->uet_count; i++) {
ucode_ext_sig_intel_t *sig;
sig = &ucodefp->uf_ext_table->uet_ext_sig[i];
if (ucode_checksum_intel_extsig(uhp,
sig) != 0) {
rc = EM_SIGCHECKSUM;
break;
}
}
}
break;
}
default:
rc = EM_FILESIZE;
break;
}
kobj_close(fd);
if (rc != EM_OK)
return (rc);
rc = ucode_match_intel(cpi_sig, uinfop, ucodefp->uf_header,
ucodefp->uf_ext_table);
if (rc == EM_OK) {
return (ucode_copy_intel(ucodefp, uinfop));
}
return (rc);
}
static void
ucode_read_rev_intel(cpu_ucode_info_t *uinfop)
{
struct cpuid_regs crs;
wrmsr(MSR_INTC_UCODE_REV, 0);
(void) __cpuid_insn(&crs);
uinfop->cui_rev = (rdmsr(MSR_INTC_UCODE_REV) >> INTC_UCODE_REV_SHIFT);
if ((cpuid_getmodel(CPU) >= 5 || cpuid_getfamily(CPU) > 6)) {
uinfop->cui_platid = 1 << ((rdmsr(MSR_INTC_PLATFORM_ID) >>
INTC_PLATFORM_ID_SHIFT) & INTC_PLATFORM_ID_MASK);
}
}
static void
ucode_load_intel(cpu_ucode_info_t *uinfop)
{
VERIFY3P(uinfop->cui_pending_ucode, !=, NULL);
kpreempt_disable();
invalidate_cache();
wrmsr(MSR_INTC_UCODE_WRITE, (uintptr_t)uinfop->cui_pending_ucode);
kpreempt_enable();
}
static ucode_errno_t
ucode_extract_intel(ucode_update_t *uusp, uint8_t *ucodep, size_t size)
{
uint32_t header_size = UCODE_HEADER_SIZE_INTEL;
size_t remaining;
bool found = false;
ucode_errno_t search_rc = EM_NOMATCH;
for (remaining = size; remaining > 0; ) {
uint32_t total_size, body_size, ext_size;
uint8_t *curbuf = &ucodep[size - remaining];
ucode_header_intel_t *uhp = (ucode_header_intel_t *)curbuf;
ucode_ext_table_intel_t *uetp = NULL;
ucode_errno_t tmprc;
total_size = UCODE_TOTAL_SIZE_INTEL(uhp->uh_total_size);
body_size = UCODE_BODY_SIZE_INTEL(uhp->uh_body_size);
ext_size = total_size - (header_size + body_size);
if (ext_size > 0) {
uetp = (ucode_ext_table_intel_t *)
&curbuf[header_size + body_size];
}
tmprc = ucode_match_intel(uusp->sig, &uusp->info, uhp, uetp);
if (tmprc == EM_HIGHERREV)
search_rc = EM_HIGHERREV;
if (tmprc == EM_OK &&
uusp->expected_rev < uhp->uh_rev) {
uusp->ucodep = (uint8_t *)&curbuf[header_size];
uusp->usize =
UCODE_TOTAL_SIZE_INTEL(uhp->uh_total_size);
uusp->expected_rev = uhp->uh_rev;
found = true;
}
remaining -= total_size;
}
if (!found)
return (search_rc);
return (EM_OK);
}
static const ucode_source_t ucode_intel = {
.us_name = "Intel microcode updater",
.us_write_msr = MSR_INTC_UCODE_WRITE,
.us_invalidate = true,
.us_select = ucode_select_intel,
.us_capable = ucode_capable_intel,
.us_file_reset = ucode_file_reset_intel,
.us_read_rev = ucode_read_rev_intel,
.us_load = ucode_load_intel,
.us_validate = ucode_validate_intel,
.us_extract = ucode_extract_intel,
.us_locate = ucode_locate_intel
};
UCODE_SOURCE(ucode_intel);