#include <sys/stdbool.h>
#include <sys/cmn_err.h>
#include <sys/controlregs.h>
#include <sys/kobj.h>
#include <sys/kobj_impl.h>
#include <sys/machparam.h>
#include <sys/ontrap.h>
#include <sys/sysmacros.h>
#include <sys/systm.h>
#include <sys/ucode.h>
#include <sys/ucode_amd.h>
#include <ucode/ucode_errno.h>
#include <ucode/ucode_utils_amd.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;
extern bool ucode_use_kmem;
static ucode_file_amd_t *amd_ucodef;
static size_t amd_ucodef_len, amd_ucodef_buflen;
static ucode_eqtbl_amd_t *ucode_eqtbl_amd;
static uint_t ucode_eqtbl_amd_entries;
static bool ucode_amd_fallback = false;
static bool
ucode_select_amd(cpu_t *cp)
{
if ((get_hwenv() & HW_VIRTUAL) != 0)
return (false);
return (cpuid_getvendor(cp) == X86_VENDOR_AMD);
}
static bool
ucode_capable_amd(cpu_t *cp)
{
return (cpuid_getfamily(cp) >= 0x10);
}
static void
ucode_file_reset_amd(void)
{
if (amd_ucodef == NULL)
return;
ucode_free(amd_ucodef, amd_ucodef_buflen);
amd_ucodef = NULL;
amd_ucodef_buflen = amd_ucodef_len = 0;
}
static ucode_errno_t
ucode_equiv_cpu_amd(cpu_t *cp, uint16_t *eq_sig)
{
char *name = NULL;
int cpi_sig = cpuid_getsig(cp);
ucode_errno_t ret = EM_OK;
if (ucode_eqtbl_amd == NULL) {
name = ucode_zalloc(MAXPATHLEN);
if (name == NULL)
return (EM_NOMEM);
(void) snprintf(name, MAXPATHLEN, "%s/%s/%s",
ucode_path(), cpuid_getvendorstr(cp),
UCODE_AMD_EQUIVALENCE_TABLE_NAME);
}
if (!ucode_use_kmem) {
ucode_eqtbl_amd_t eqtbl;
int count, offset = 0;
intptr_t fd;
ASSERT3P(name, !=, NULL);
if ((fd = kobj_open(name)) == -1) {
ret = EM_OPENFILE;
goto out;
}
do {
count = kobj_read(fd, (int8_t *)&eqtbl,
sizeof (eqtbl), offset);
if (count != sizeof (eqtbl)) {
(void) kobj_close(fd);
ret = EM_HIGHERREV;
goto out;
}
offset += count;
} while (eqtbl.ue_inst_cpu != 0 &&
eqtbl.ue_inst_cpu != cpi_sig);
(void) kobj_close(fd);
*eq_sig = eqtbl.ue_equiv_cpu;
} else {
ucode_eqtbl_amd_t *eqtbl;
if (ucode_eqtbl_amd == NULL) {
struct _buf *eq;
uint64_t size;
int count;
ASSERT3P(name, !=, NULL);
if ((eq = kobj_open_file(name)) == (struct _buf *)-1) {
ret = EM_OPENFILE;
goto out;
}
if (kobj_get_filesize(eq, &size) < 0) {
kobj_close_file(eq);
ret = EM_OPENFILE;
goto out;
}
if (size == 0 ||
size % sizeof (*ucode_eqtbl_amd) != 0) {
kobj_close_file(eq);
ret = EM_HIGHERREV;
goto out;
}
ucode_eqtbl_amd = kmem_zalloc(size, KM_NOSLEEP);
if (ucode_eqtbl_amd == NULL) {
kobj_close_file(eq);
ret = EM_NOMEM;
goto out;
}
count = kobj_read_file(eq, (char *)ucode_eqtbl_amd,
size, 0);
kobj_close_file(eq);
if (count != size) {
ucode_eqtbl_amd_entries = 0;
ret = EM_FILESIZE;
goto out;
}
ucode_eqtbl_amd_entries =
size / sizeof (*ucode_eqtbl_amd);
}
eqtbl = ucode_eqtbl_amd;
*eq_sig = 0;
for (uint_t i = 0; i < ucode_eqtbl_amd_entries; i++, eqtbl++) {
if (eqtbl->ue_inst_cpu == 0) {
ret = EM_HIGHERREV;
goto out;
}
if (eqtbl->ue_inst_cpu == cpi_sig) {
*eq_sig = eqtbl->ue_equiv_cpu;
ret = EM_OK;
goto out;
}
}
ret = EM_HIGHERREV;
}
out:
ucode_free(name, MAXPATHLEN);
return (ret);
}
static ucode_errno_t
ucode_match_amd(uint16_t eq_sig, cpu_ucode_info_t *uinfop,
ucode_file_amd_t *ucodefp, size_t size)
{
ucode_header_amd_t *uh;
if (ucodefp == NULL || size < sizeof (ucode_header_amd_t))
return (EM_NOMATCH);
uh = &ucodefp->uf_header;
if (uh->uh_cpu_rev < 0x5000 &&
size > offsetof(ucode_file_amd_t, uf_code_present) &&
ucodefp->uf_code_present) {
return (EM_NOMATCH);
}
if (eq_sig != uh->uh_cpu_rev)
return (EM_NOMATCH);
if (uh->uh_nb_id) {
cmn_err(CE_WARN, "ignoring northbridge-specific ucode: "
"chipset id %x, revision %x", uh->uh_nb_id, uh->uh_nb_rev);
return (EM_NOMATCH);
}
if (uh->uh_sb_id) {
cmn_err(CE_WARN, "ignoring southbridge-specific ucode: "
"chipset id %x, revision %x", uh->uh_sb_id, uh->uh_sb_rev);
return (EM_NOMATCH);
}
if (uh->uh_patch_id <= uinfop->cui_rev && !ucode_force_update)
return (EM_HIGHERREV);
return (EM_OK);
}
static ucode_errno_t
ucode_copy_amd(cpu_ucode_info_t *uinfop, const ucode_file_amd_t *ucodefp,
size_t size)
{
ASSERT3P(uinfop->cui_pending_ucode, ==, NULL);
ASSERT3U(size, <=, UCODE_AMD_MAXSIZE);
uinfop->cui_pending_ucode = ucode_zalloc(size);
if (uinfop->cui_pending_ucode == NULL)
return (EM_NOMEM);
(void) memcpy(uinfop->cui_pending_ucode, ucodefp, size);
uinfop->cui_pending_size = size;
uinfop->cui_pending_rev = ucodefp->uf_header.uh_patch_id;
return (EM_OK);
}
static ucode_errno_t
i_ucode_locate_amd(cpu_t *cp, cpu_ucode_info_t *uinfop, bool fallback)
{
uint16_t eq_sig;
ucode_errno_t rc;
eq_sig = 0;
if ((rc = ucode_equiv_cpu_amd(cp, &eq_sig)) != EM_OK)
return (rc);
if (amd_ucodef == NULL) {
size_t len = PAGESIZE;
amd_ucodef = ucode_zalloc(len);
if (amd_ucodef == NULL)
return (EM_NOMEM);
amd_ucodef_buflen = len;
} else {
rc = ucode_match_amd(eq_sig, uinfop, amd_ucodef,
amd_ucodef_len);
if (rc == EM_HIGHERREV)
return (rc);
if (rc == EM_OK) {
return (ucode_copy_amd(uinfop, amd_ucodef,
amd_ucodef_len));
}
}
for (uint_t i = 0; i < 0xff; i++) {
char name[MAXPATHLEN];
intptr_t fd;
int count;
uint_t size;
(void) snprintf(name, MAXPATHLEN, "%s/%s/%s%04X-%02X",
ucode_path(), cpuid_getvendorstr(cp),
fallback ? "fallback/" : "", eq_sig, i);
if ((fd = kobj_open(name)) == -1)
return (EM_NOMATCH);
size = 0;
do {
count = kobj_read(fd, (char *)amd_ucodef,
amd_ucodef_buflen, size);
if (count < 0) {
(void) kobj_close(fd);
return (EM_OPENFILE);
}
size += count;
} while (count == amd_ucodef_buflen &&
size <= UCODE_AMD_MAXSIZE);
if (size > UCODE_AMD_MAXSIZE) {
(void) kobj_close(fd);
cmn_err(CE_WARN, "ucode: microcode file %s is "
"too large (over 0x%x bytes)", name,
UCODE_AMD_MAXSIZE);
return (EM_FILESIZE);
}
if (size > amd_ucodef_buflen) {
size_t len = P2ROUNDUP(size, PAGESIZE);
ucode_file_reset_amd();
amd_ucodef = ucode_zalloc(len);
if (amd_ucodef == NULL) {
(void) kobj_close(fd);
return (EM_NOMEM);
}
amd_ucodef_buflen = len;
}
count = kobj_read(fd, (char *)amd_ucodef, amd_ucodef_buflen, 0);
(void) kobj_close(fd);
if (count < 0 || count != size)
return (EM_OPENFILE);
amd_ucodef_len = count;
rc = ucode_match_amd(eq_sig, uinfop, amd_ucodef,
amd_ucodef_len);
if (rc == EM_HIGHERREV)
return (rc);
if (rc == EM_OK) {
return (ucode_copy_amd(uinfop, amd_ucodef,
amd_ucodef_len));
}
}
return (EM_NOMATCH);
}
ucode_errno_t
ucode_locate_amd(cpu_t *cp, cpu_ucode_info_t *uinfop)
{
return (i_ucode_locate_amd(cp, uinfop, ucode_amd_fallback));
}
ucode_errno_t
ucode_locate_fallback_amd(cpu_t *cp, cpu_ucode_info_t *uinfop)
{
ucode_amd_fallback = true;
return (i_ucode_locate_amd(cp, uinfop, ucode_amd_fallback));
}
static void
ucode_read_rev_amd(cpu_ucode_info_t *uinfop)
{
uinfop->cui_rev = rdmsr(MSR_AMD_PATCHLEVEL);
}
static void
ucode_load_amd(cpu_ucode_info_t *uinfop)
{
ucode_file_amd_t *ucodefp = uinfop->cui_pending_ucode;
on_trap_data_t otd;
VERIFY3P(ucodefp, !=, NULL);
VERIFY3U(ucodefp->uf_header.uh_patch_id, ==, uinfop->cui_pending_rev);
kpreempt_disable();
if (on_trap(&otd, OT_DATA_ACCESS)) {
no_trap();
goto out;
}
wrmsr(MSR_AMD_PATCHLOADER, (uintptr_t)ucodefp);
no_trap();
out:
kpreempt_enable();
}
static ucode_errno_t
ucode_extract_amd(ucode_update_t *uusp, uint8_t *ucodep, size_t size)
{
uint32_t *ptr = (uint32_t *)ucodep;
ucode_eqtbl_amd_t *eqtbl;
ucode_file_amd_t *ufp;
uint32_t count;
bool higher = false;
ucode_errno_t rc = EM_NOMATCH;
uint16_t eq_sig;
ptr += 2; size -= 8;
count = *ptr++; size -= 4;
for (eqtbl = (ucode_eqtbl_amd_t *)ptr;
eqtbl->ue_inst_cpu && eqtbl->ue_inst_cpu != uusp->sig;
eqtbl++)
;
eq_sig = eqtbl->ue_equiv_cpu;
if (eq_sig == 0)
return (EM_HIGHERREV);
do {
ptr += count >> 2; size -= count;
if (size == 0)
return (higher ? EM_HIGHERREV : EM_NOMATCH);
ptr++; size -= 4;
count = *ptr++; size -= 4;
ufp = (ucode_file_amd_t *)ptr;
rc = ucode_match_amd(eq_sig, &uusp->info, ufp, count);
if (rc == EM_HIGHERREV)
higher = true;
} while (rc != EM_OK);
uusp->ucodep = (uint8_t *)ufp;
uusp->usize = count;
uusp->expected_rev = ufp->uf_header.uh_patch_id;
return (EM_OK);
}
static const ucode_source_t ucode_amd = {
.us_name = "AMD microcode updater",
.us_write_msr = MSR_AMD_PATCHLOADER,
.us_invalidate = false,
.us_select = ucode_select_amd,
.us_capable = ucode_capable_amd,
.us_file_reset = ucode_file_reset_amd,
.us_read_rev = ucode_read_rev_amd,
.us_load = ucode_load_amd,
.us_validate = ucode_validate_amd,
.us_extract = ucode_extract_amd,
.us_locate = ucode_locate_amd,
.us_locate_fallback = ucode_locate_fallback_amd
};
UCODE_SOURCE(ucode_amd);