diff --git a/sys/x86/include/ucode.h b/sys/x86/include/ucode.h --- a/sys/x86/include/ucode.h +++ b/sys/x86/include/ucode.h @@ -58,6 +58,8 @@ int ucode_intel_load(void *data, bool unsafe, uint64_t *nrevp, uint64_t *orevp); +int ucode_amd_load(void *data, bool unsafe, + uint64_t *nrevp, uint64_t *orevp); size_t ucode_load_bsp(uintptr_t free); void ucode_load_ap(int cpu); void ucode_reload(void); diff --git a/sys/x86/x86/ucode.c b/sys/x86/x86/ucode.c --- a/sys/x86/x86/ucode.c +++ b/sys/x86/x86/ucode.c @@ -50,10 +50,53 @@ #include #include +/* + * AMD family 10h and later. + */ +typedef struct amd_10h_fw_header { + uint32_t data_code; + uint32_t patch_id; + uint16_t mc_patch_data_id; + uint8_t mc_patch_data_len; + uint8_t init_flag; + uint32_t mc_patch_data_checksum; + uint32_t nb_dev_id; + uint32_t sb_dev_id; + uint16_t processor_rev_id; + uint8_t nb_rev_id; + uint8_t sb_rev_id; + uint8_t bios_api_rev; + uint8_t reserved1[3]; + uint32_t match_reg[8]; +} amd_10h_fw_header_t; + +typedef struct equiv_cpu_entry { + uint32_t installed_cpu; + uint32_t fixed_errata_mask; + uint32_t fixed_errata_compare; + uint16_t equiv_cpu; + uint16_t res; +} equiv_cpu_entry_t; + +typedef struct section_header { + uint32_t type; + uint32_t size; +} section_header_t; + +typedef struct container_header { + uint32_t magic; +} container_header_t; + +#define AMD_10H_MAGIC 0x414d44 +#define AMD_10H_EQUIV_TABLE_TYPE 0 +#define AMD_10H_uCODE_TYPE 1 + static void *ucode_intel_match(uint8_t *data, size_t *len); static int ucode_intel_verify(struct ucode_intel_header *hdr, size_t resid); +static void *ucode_amd_match(uint8_t *data, size_t *len); + static struct ucode_ops { const char *vendor; int (*load)(void *, bool, uint64_t *, uint64_t *); @@ -64,6 +107,11 @@ .load = ucode_intel_load, .match = ucode_intel_match, }, + { + .vendor = AMD_VENDOR_ID, + .load = ucode_amd_load, + .match = ucode_amd_match, + }, }; /* Selected microcode update data. */ @@ -224,6 +272,210 @@ return (NULL); } +int +ucode_amd_load(void *data, bool unsafe, uint64_t *nrevp, uint64_t *orevp) +{ + uint64_t nrev, orev; + uint32_t cpuid[4]; + + orev = rdmsr(MSR_BIOS_SIGN); + + /* + * Perform update. + */ + if (unsafe) + wrmsr_safe(MSR_K8_UCODE_UPDATE, (uint64_t)(uintptr_t)data); + else + wrmsr(MSR_K8_UCODE_UPDATE, (uint64_t)(uintptr_t)data); + + /* + * 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 void * +ucode_amd_match(uint8_t *data, size_t *len) +{ + const amd_10h_fw_header_t *fw_header; + const amd_10h_fw_header_t *selected_fw; + const equiv_cpu_entry_t *equiv_cpu_table; + const section_header_t *section_header; + const container_header_t *container_header; + const uint8_t *fw_data; + size_t fw_size; + size_t selected_size; + uint32_t revision; + uint32_t signature; + uint16_t equiv_id; + uint32_t regs[4]; + int i; + + const char *path = "loader blob"; + + fw_data = data; + fw_size = *len; + + do_cpuid(1, regs); + signature = regs[0]; + + revision = rdmsr(MSR_BIOS_SIGN); + +#define WARNX(level, ...) \ +{ \ + printf(__VA_ARGS__); \ + printf("\n"); \ +} + + WARNX(1, "found cpu family %#x model %#x " + "stepping %#x extfamily %#x extmodel %#x.", + ((signature >> 8) & 0x0f) + ((signature >> 20) & 0xff), + (signature >> 4) & 0x0f, + (signature >> 0) & 0x0f, (signature >> 20) & 0xff, + (signature >> 16) & 0x0f); + WARNX(1, "microcode revision %#x", revision); + +nextfile: + WARNX(3, "processing next container file"); + if (fw_size < + (sizeof(*container_header) + sizeof(*section_header))) { + WARNX(2, "file too short: %s", path); + goto done; + } + + container_header = (const container_header_t *)fw_data; + if (container_header->magic != AMD_10H_MAGIC) { + WARNX(2, "%s is not a valid amd firmware: bad magic", path); + goto done; + } + fw_data += sizeof(*container_header); + fw_size -= sizeof(*container_header); + + section_header = (const section_header_t *)fw_data; + if (section_header->type != AMD_10H_EQUIV_TABLE_TYPE) { + WARNX(2, "%s is not a valid amd firmware: " + "first section is not CPU equivalence table", path); + goto done; + } + if (section_header->size == 0) { + WARNX(2, "%s is not a valid amd firmware: " + "first section is empty", path); + goto done; + } + fw_data += sizeof(*section_header); + fw_size -= sizeof(*section_header); + + if (section_header->size > fw_size) { + WARNX(2, "%s is not a valid amd firmware: " + "file is truncated", path); + goto done; + } + if (section_header->size < sizeof(*equiv_cpu_table)) { + WARNX(2, "%s is not a valid amd firmware: " + "first section is too short", path); + goto done; + } + equiv_cpu_table = (const equiv_cpu_entry_t *)fw_data; + fw_data += section_header->size; + fw_size -= section_header->size; + + equiv_id = 0; + for (i = 0; equiv_cpu_table[i].installed_cpu != 0; i++) { + WARNX(3, "signature 0x%x i %d installed_cpu 0x%x equiv 0x%x", + signature, i, equiv_cpu_table[i].installed_cpu, + equiv_cpu_table[i].equiv_cpu); + if (signature == equiv_cpu_table[i].installed_cpu) { + equiv_id = equiv_cpu_table[i].equiv_cpu; + WARNX(3, "equiv_id: %x, signature %8x," + " equiv_cpu_table[%d] %8x", equiv_id, signature, + i, equiv_cpu_table[i].installed_cpu); + break; + } + } + if (equiv_id == 0) { + WARNX(2, "CPU is not found in the equivalence table"); + } + + selected_fw = NULL; + selected_size = 0; + while (fw_size >= sizeof(*section_header)) { + section_header = (const section_header_t *)fw_data; + if (section_header->type == AMD_10H_MAGIC) { + WARNX(2, "%s next section is actually a new container", + path); + if (selected_fw != NULL) + goto found; + else + goto nextfile; + } + fw_data += sizeof(*section_header); + fw_size -= sizeof(*section_header); + if (section_header->type != AMD_10H_uCODE_TYPE) { + WARNX(2, "%s is not a valid amd firmware: " + "section has incorrect type", path); + goto done; + } + if (section_header->size > fw_size) { + WARNX(2, "%s is not a valid amd firmware: " + "file is truncated", path); + goto done; + } + if (section_header->size < sizeof(*fw_header)) { + WARNX(2, "%s is not a valid amd firmware: " + "section is too short", path); + goto done; + } + fw_header = (const amd_10h_fw_header_t *)fw_data; + fw_data += section_header->size; + fw_size -= section_header->size; + + if (fw_header->processor_rev_id != equiv_id) { + WARNX(1, "firmware processor_rev_id %x, equiv_id %x", + fw_header->processor_rev_id, equiv_id); + continue; /* different cpu */ + } + if (fw_header->patch_id <= revision) { + WARNX(1, "patch_id %x, revision %x", + fw_header->patch_id, revision); + continue; /* not newer revision */ + } + if (fw_header->nb_dev_id != 0 || fw_header->sb_dev_id != 0) { + WARNX(2, "Chipset-specific microcode is not supported"); + } + + WARNX(3, "selecting revision: %x", fw_header->patch_id); + revision = fw_header->patch_id; + selected_fw = fw_header; + selected_size = section_header->size; + } + + if (fw_size != 0) { + WARNX(2, "%s is not a valid amd firmware: " + "file is truncated", path); + goto done; + } + +found: + *len = selected_size; + return (__DECONST(void *, selected_fw)); + +done: + return (NULL); +} + /* * Release any memory backing unused microcode blobs back to the system. * We copy the selected update and free the entire microcode file.