/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2012 Nexenta Systems, Inc. All rights reserved. * Copyright (c) 2018, Joyent, Inc. * Copyright 2021 OmniOS Community Edition (OmniOSce) Association. * Copyright 2023 Oxide Computer Company */ #include #include #include #include #include #include #include #include #include #include #include #include extern void *ucode_zalloc(processorid_t, size_t); extern void ucode_free(processorid_t, void *, size_t); extern const char *ucode_path(void); extern int ucode_force_update; static ucode_file_intel_t intel_ucodef; /* * Check whether this module can be used for microcode updates on this * platform. */ static bool ucode_select_intel(cpu_t *cp) { if ((get_hwenv() & HW_VIRTUAL) != 0) return (false); return (cpuid_getvendor(cp) == X86_VENDOR_Intel); } /* * Check whether or not a processor is capable of microcode operations * * At this point we only support microcode update for: * - Intel processors family 6 and above. */ static bool ucode_capable_intel(cpu_t *cp) { return (cpuid_getfamily(cp) >= 6); } static void ucode_file_reset_intel(processorid_t id) { 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(id, 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(id, ucodefp->uf_ext_table, size); ucodefp->uf_ext_table = NULL; } ucode_free(id, ucodefp->uf_header, UCODE_HEADER_SIZE_INTEL); ucodefp->uf_header = NULL; } /* * Checks if the microcode is for this processor. */ 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_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 the microcode matches the CPU we are processing, use it. */ if (ucode_match_intel(cpi_sig, uinfop, ucodefp->uf_header, ucodefp->uf_ext_table) == EM_OK && ucodefp->uf_body != NULL) { return (EM_OK); } /* * Look for microcode file with the right name. */ (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); } /* * We found a microcode file for the CPU we are processing, * reset the microcode data structure and read in the new * file. */ ucode_file_reset_intel(cp->cpu_id); ucodefp->uf_header = ucode_zalloc(cp->cpu_id, 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; /* * Make sure that the header contains valid fields. */ 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(cp->cpu_id, 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; } /* * Check to see if there is extended signature table. */ offset = body_size + header_size; ext_size = total_size - offset; if (ext_size <= 0) break; ucodefp->uf_ext_table = ucode_zalloc(cp->cpu_id, 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); return (rc); } static void ucode_read_rev_intel(cpu_ucode_info_t *uinfop) { struct cpuid_regs crs; /* * The Intel 64 and IA-32 Architecture Software Developer's Manual * recommends that MSR_INTC_UCODE_REV be loaded with 0 first, then * execute cpuid to guarantee the correct reading of this register. */ wrmsr(MSR_INTC_UCODE_REV, 0); (void) __cpuid_insn(&crs); uinfop->cui_rev = (rdmsr(MSR_INTC_UCODE_REV) >> INTC_UCODE_REV_SHIFT); /* * The MSR_INTC_PLATFORM_ID is supported in Celeron and Xeon * (Family 6, model 5 and above) and all processors after. */ 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 uint32_t ucode_load_intel(cpu_ucode_info_t *uinfop) { ucode_file_intel_t *ucodefp = &intel_ucodef; kpreempt_disable(); /* * On some platforms a cache invalidation is required for the * ucode update to be successful due to the parts of the * processor that the microcode is updating. */ invalidate_cache(); wrmsr(MSR_INTC_UCODE_WRITE, (uintptr_t)ucodefp->uf_body); ucode_read_rev_intel(uinfop); kpreempt_enable(); return (ucodefp->uf_header->uh_rev); } static ucode_errno_t ucode_extract_intel(ucode_update_t *uusp, uint8_t *ucodep, int size) { uint32_t header_size = UCODE_HEADER_SIZE_INTEL; int remaining; int found = 0; ucode_errno_t search_rc = EM_NOMATCH; /* search result */ /* * Go through the whole buffer in case there are * multiple versions of matching microcode for this * processor. */ for (remaining = size; remaining > 0; ) { int 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); /* * Since we are searching through a big file * containing microcode for pretty much all the * processors, we are bound to get EM_NOMATCH * at one point. However, if we return * EM_NOMATCH to users, it will really confuse * them. Therefore, if we ever find a match of * a lower rev, we will set return code to * EM_HIGHERREV. */ 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 = 1; } 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);