Index | Thread | Search

From:
Jonathan Gray <jsg@jsg.id.au>
Subject:
Re: check minimum microcode versions
To:
tech@openbsd.org
Date:
Fri, 31 Oct 2025 13:28:40 +1100

Download raw body.

Thread
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;
 	}