From: Jonathan Gray Subject: Re: check minimum microcode versions To: tech@openbsd.org Date: Fri, 31 Oct 2025 13:28:40 +1100 On Sun, Oct 26, 2025 at 11:45:09AM +1100, Jonathan Gray wrote: > Some AMD microcode now lists a minimum version for performing an > MSR based update. If the microcode is not at that level it seems a > protection fault will be generated. Only some server (Epyc) parts have > a minimum version at the moment. And not in the version currently in ports. > > https://gitlab.com/kernel-firmware/linux-firmware/-/blob/main/amd-ucode/README?ref_type=heads > "Minimum base ucode version for loading" > > "#GP fault will occur if attempting to hot load microcode on older BIOS." > https://www.amd.com/en/resources/product-security/bulletin/amd-sb-7033.html The latest family 19h microcode now has multiple patches for some parts. A patch with no minimum version and a patch with a minimum version. Specifically for: 19-01-01 Milan-B1 19-01-02 Milan-X B2 19-11-01 Genoa-B1 19-11-02 Genoa-X B2 19-a0-02 Bergamo A1 Index: sys/arch/amd64/amd64/ucode.c =================================================================== RCS file: /cvs/src/sys/arch/amd64/amd64/ucode.c,v diff -u -p -r1.9 ucode.c --- sys/arch/amd64/amd64/ucode.c 3 Apr 2024 02:01:21 -0000 1.9 +++ sys/arch/amd64/amd64/ucode.c 31 Oct 2025 02:23:10 -0000 @@ -48,7 +48,9 @@ struct intel_ucode_header { uint32_t processor_flags; uint32_t data_size; uint32_t total_size; - uint32_t reserved[3]; + uint32_t metadata_size; + uint32_t min_revision; + uint32_t reserved; }; struct intel_ucode_ext_sig_header { @@ -127,21 +129,26 @@ struct amd_equiv { struct amd_patch { uint32_t type; uint32_t len; - uint32_t a; + uint32_t date; uint32_t level; - uint8_t c[16]; + uint16_t data_id; + uint8_t data_len; + uint8_t flag; + uint32_t data_val; + uint32_t nb_dev; + uint32_t sb_dev; uint16_t eid; } __packed; void cpu_ucode_amd_apply(struct cpu_info *ci) { - uint64_t level; + uint64_t cur_level; uint32_t magic, tlen, i; uint16_t eid = 0; uint32_t sig, ebx, ecx, edx; uint64_t start = 0; - uint32_t patch_len = 0; + uint32_t patch_len = 0, new_level = 0; if (cpu_ucode_data == NULL || cpu_ucode_size == 0) { DPRINTF(("%s: no microcode provided\n", __func__)); @@ -156,8 +163,8 @@ cpu_ucode_amd_apply(struct cpu_info *ci) CPUID(1, sig, ebx, ecx, edx); - level = rdmsr(MSR_PATCH_LEVEL); - DPRINTF(("%s: cur patch level 0x%llx\n", __func__, level)); + cur_level = rdmsr(MSR_PATCH_LEVEL); + DPRINTF(("%s: cur patch level 0x%llx\n", __func__, cur_level)); memcpy(&magic, cpu_ucode_data, 4); if (magic != AMD_MAGIC) { @@ -180,7 +187,10 @@ cpu_ucode_amd_apply(struct cpu_info *ci) eid = ae.eid; } - /* look for newer patch with the equivalence id */ + /* + * Look for newer patch with the equivalence id. There can be + * multiple patches for a given equivalence id. + */ while (i < cpu_ucode_size) { struct amd_patch ap; if (i + sizeof(ap) > cpu_ucode_size) { @@ -188,9 +198,18 @@ cpu_ucode_amd_apply(struct cpu_info *ci) goto out; } memcpy(&ap, &cpu_ucode_data[i], sizeof(ap)); - if (ap.type == 1 && ap.eid == eid && ap.level > level) { - start = (uint64_t)&cpu_ucode_data[i + 8]; - patch_len = ap.len; + if (ap.type == 1 && ap.eid == eid && ap.level > cur_level && + ap.level > new_level) { + + /* check minimum version for fam >= 0x19 */ + if (ci->ci_family < 0x19 || cur_level >= ap.data_val) { + start = (uint64_t)&cpu_ucode_data[i + 8]; + new_level = ap.level; + patch_len = ap.len; + } else { + DPRINTF(("%s: ucode %#llx < min %#x\n", + __func__, cur_level, ap.data_val)); + } } if (i + ap.len + 8 > cpu_ucode_size) { DPRINTF(("%s: truncated patch\n", __func__)); @@ -207,8 +226,8 @@ cpu_ucode_amd_apply(struct cpu_info *ci) memcpy(p, (uint8_t *)start, patch_len); start = (uint64_t)p; wrmsr(MSR_PATCH_LOADER, start); - level = rdmsr(MSR_PATCH_LEVEL); - DPRINTF(("%s: new patch level 0x%llx\n", __func__, level)); + cur_level = rdmsr(MSR_PATCH_LEVEL); + DPRINTF(("%s: new patch level 0x%llx\n", __func__, cur_level)); free(p, M_TEMP, patch_len); } out: @@ -244,6 +263,11 @@ cpu_ucode_intel_apply(struct cpu_info *c } if (update->update_revision == old_rev) { DPRINTF(("%s: microcode already up-to-date\n", __func__)); + goto out; + } + if (old_rev < update->min_revision) { + DPRINTF(("%s: cur %#x < min %#x\n", __func__, old_rev, + update->min_revision)); goto out; } Index: sys/arch/i386/i386/ucode.c =================================================================== RCS file: /cvs/src/sys/arch/i386/i386/ucode.c,v diff -u -p -r1.6 ucode.c --- sys/arch/i386/i386/ucode.c 10 Sep 2023 09:32:31 -0000 1.6 +++ sys/arch/i386/i386/ucode.c 31 Oct 2025 02:23:21 -0000 @@ -48,7 +48,9 @@ struct intel_ucode_header { uint32_t processor_flags; uint32_t data_size; uint32_t total_size; - uint32_t reserved[3]; + uint32_t metadata_size; + uint32_t min_revision; + uint32_t reserved; }; struct intel_ucode_ext_sig_header { @@ -150,21 +152,26 @@ struct amd_equiv { struct amd_patch { uint32_t type; uint32_t len; - uint32_t a; + uint32_t date; uint32_t level; - uint8_t c[16]; + uint16_t data_id; + uint8_t data_len; + uint8_t flag; + uint32_t data_val; + uint32_t nb_dev; + uint32_t sb_dev; uint16_t eid; } __packed; void cpu_ucode_amd_apply(struct cpu_info *ci) { - uint64_t level; + uint64_t cur_level; uint32_t magic, tlen, i; uint16_t eid = 0; uint32_t sig, ebx, ecx, edx; uint64_t start = 0; - uint32_t patch_len = 0; + uint32_t patch_len = 0, new_level = 0; if (cpu_ucode_data == NULL || cpu_ucode_size == 0) { DPRINTF(("%s: no microcode provided\n", __func__)); @@ -179,8 +186,8 @@ cpu_ucode_amd_apply(struct cpu_info *ci) CPUID(1, sig, ebx, ecx, edx); - level = rdmsr(MSR_PATCH_LEVEL); - DPRINTF(("%s: cur patch level 0x%llx\n", __func__, level)); + cur_level = rdmsr(MSR_PATCH_LEVEL); + DPRINTF(("%s: cur patch level 0x%llx\n", __func__, cur_level)); memcpy(&magic, cpu_ucode_data, 4); if (magic != AMD_MAGIC) { @@ -203,7 +210,10 @@ cpu_ucode_amd_apply(struct cpu_info *ci) eid = ae.eid; } - /* look for newer patch with the equivalence id */ + /* + * Look for newer patch with the equivalence id. There can be + * multiple patches for a given equivalence id. + */ while (i < cpu_ucode_size) { struct amd_patch ap; if (i + sizeof(ap) > cpu_ucode_size) { @@ -211,9 +221,18 @@ cpu_ucode_amd_apply(struct cpu_info *ci) goto out; } memcpy(&ap, &cpu_ucode_data[i], sizeof(ap)); - if (ap.type == 1 && ap.eid == eid && ap.level > level) { - start = (uint64_t)&cpu_ucode_data[i + 8]; - patch_len = ap.len; + if (ap.type == 1 && ap.eid == eid && ap.level > cur_level && + ap.level > new_level) { + + /* check minimum version for fam >= 0x19 */ + if (ci->ci_family < 0x19 || cur_level >= ap.data_val) { + start = (uint64_t)&cpu_ucode_data[i + 8]; + new_level = ap.level; + patch_len = ap.len; + } else { + DPRINTF(("%s: ucode %#llx < min %#x\n", + __func__, cur_level, ap.data_val)); + } } if (i + ap.len + 8 > cpu_ucode_size) { DPRINTF(("%s: truncated patch\n", __func__)); @@ -230,8 +249,8 @@ cpu_ucode_amd_apply(struct cpu_info *ci) memcpy(p, (uint8_t *)start, patch_len); start = (uint64_t)p; wrmsr(MSR_PATCH_LOADER, start); - level = rdmsr(MSR_PATCH_LEVEL); - DPRINTF(("%s: new patch level 0x%llx\n", __func__, level)); + cur_level = rdmsr(MSR_PATCH_LEVEL); + DPRINTF(("%s: new patch level 0x%llx\n", __func__, cur_level)); free(p, M_TEMP, patch_len); } out: @@ -267,6 +286,11 @@ cpu_ucode_intel_apply(struct cpu_info *c } if (update->update_revision == old_rev) { DPRINTF(("%s: microcode already up-to-date\n", __func__)); + goto out; + } + if (old_rev < update->min_revision) { + DPRINTF(("%s: cur %#x < min %#x\n", __func__, old_rev, + update->min_revision)); goto out; }