From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Ashok Raj <ashok.raj@intel.com>
Date: Thu, 19 Aug 2021 14:49:47 -0700
Subject: [PATCH] x86/microcode: Add an option to reload microcode even if
 revision is the same

This is POC to support rollback. This is a simple version, admin uses
echo 2 instead of echo 1 to reload. We don't do the version checks.

#echo 1 > /sys/devices/system/cpu/microcode/reload

The following usage, writing 2 to reload file is helpful to reload
the microcode again even if the revision is less than what is loaded.

#echo 2 > /sys/devices/system/cpu/microcode/reload

Signed-off-by: Ashok Raj <ashok.raj@intel.com>
---
 arch/x86/kernel/cpu/microcode/core.c  | 40 ++++++++++++++++++++++++++-
 arch/x86/kernel/cpu/microcode/intel.c | 14 ++++++----
 2 files changed, 47 insertions(+), 7 deletions(-)

diff --git a/arch/x86/kernel/cpu/microcode/core.c b/arch/x86/kernel/cpu/microcode/core.c
index 239ff5fcec6a..b096a43b2b9d 100644
--- a/arch/x86/kernel/cpu/microcode/core.c
+++ b/arch/x86/kernel/cpu/microcode/core.c
@@ -44,6 +44,8 @@
 
 static struct microcode_ops	*microcode_ops;
 static bool dis_ucode_ldr = true;
+bool ucode_rollback = false;
+int enable_rollback = 0;
 
 bool initrd_gone;
 
@@ -80,6 +82,26 @@ static u32 final_levels[] = {
 	0, /* T-101 terminator */
 };
 
+static int __init ucode_setup(char *str)
+{
+	if (!str)
+		return -EINVAL;
+
+	while (*str) {
+		if (!strncmp(str, "rollback", 8)) {
+			enable_rollback = 1;
+			pr_info("Microcode Rollback Enabled\n");
+		}
+		str += strcspn(str, ",");
+		while (*str == ',')
+			str++;
+	}
+	return 0;
+}
+
+__setup("ucode=", ucode_setup);
+
+
 /*
  * Check the current patch level on this CPU.
  *
@@ -600,6 +622,7 @@ static ssize_t reload_store(struct device *dev,
 			    struct device_attribute *attr,
 			    const char *buf, size_t size)
 {
+	struct cpuinfo_x86 *c = &boot_cpu_data;
 	enum ucode_state tmp_ret = UCODE_OK;
 	int bsp = boot_cpu_data.cpu_index;
 	unsigned long val;
@@ -609,7 +632,7 @@ static ssize_t reload_store(struct device *dev,
 	if (ret)
 		return ret;
 
-	if (val != 1)
+	if (!val || val > 2)
 		return size;
 
 	cpus_read_lock();
@@ -617,6 +640,20 @@ static ssize_t reload_store(struct device *dev,
 	ret = check_online_cpus();
 	if (ret)
 		goto put;
+	/*
+	 * Check if the vendor is Intel to permit reloading
+	 * microcode even if the revision is unchanged.
+	 * This is typically used during development of microcode
+	 * and changing rev is a pain.
+	 */
+	if ((val == 2) && ((c->x86_vendor != X86_VENDOR_INTEL) ||
+	     !enable_rollback))
+		return size;
+	else if (val == 2) {
+		mutex_lock(&microcode_mutex);
+		ucode_rollback = true;
+		mutex_unlock(&microcode_mutex);
+	}
 
 	tmp_ret = microcode_ops->request_microcode_fw(bsp, &microcode_pdev->dev, true);
 	if (tmp_ret != UCODE_NEW)
@@ -627,6 +664,7 @@ static ssize_t reload_store(struct device *dev,
 	mutex_unlock(&microcode_mutex);
 
 put:
+	ucode_rollback = false;
 	cpus_read_unlock();
 
 	if (ret == 0)
diff --git a/arch/x86/kernel/cpu/microcode/intel.c b/arch/x86/kernel/cpu/microcode/intel.c
index d28a9f8f3fec..02b506f52a13 100644
--- a/arch/x86/kernel/cpu/microcode/intel.c
+++ b/arch/x86/kernel/cpu/microcode/intel.c
@@ -44,6 +44,7 @@ static struct microcode_intel *intel_ucode_patch;
 
 /* last level cache size per core */
 static int llc_size_per_core;
+extern bool ucode_rollback;
 
 static inline bool cpu_signatures_match(unsigned int s1, unsigned int p1,
 					unsigned int s2, unsigned int p2)
@@ -94,7 +95,7 @@ static int has_newer_microcode(void *mc, unsigned int csig, int cpf, int new_rev
 {
 	struct microcode_header_intel *mc_hdr = mc;
 
-	if (mc_hdr->rev <= new_rev)
+	if (!ucode_rollback && mc_hdr->rev <= new_rev)
 		return 0;
 
 	return find_matching_signature(mc, csig, cpf);
@@ -134,7 +135,7 @@ static void save_microcode_patch(struct ucode_cpu_info *uci, void *data, unsigne
 		if (find_matching_signature(data, sig, pf)) {
 			prev_found = true;
 
-			if (mc_hdr->rev <= mc_saved_hdr->rev)
+			if (!ucode_rollback && mc_hdr->rev <= mc_saved_hdr->rev)
 				continue;
 
 			p = memdup_patch(data, size);
@@ -694,7 +695,7 @@ static struct microcode_intel *find_patch(struct ucode_cpu_info *uci)
 
 		phdr = (struct microcode_header_intel *)iter->data;
 
-		if (phdr->rev <= uci->cpu_sig.rev)
+		if (!ucode_rollback && phdr->rev <= uci->cpu_sig.rev)
 			continue;
 
 		if (!find_matching_signature(phdr,
@@ -779,10 +780,11 @@ static enum ucode_state apply_microcode_intel(int cpu)
 	 * already.
 	 */
 	rev = intel_get_microcode_revision();
-	if (rev >= mc->hdr.rev) {
+	if (!ucode_rollback && rev >= mc->hdr.rev) {
 		ret = UCODE_OK;
 		goto out;
-	}
+	} else if (ucode_rollback)
+		ret = UCODE_OK;
 
 	/*
 	 * Writeback and invalidate caches before updating microcode to avoid
@@ -801,7 +803,7 @@ static enum ucode_state apply_microcode_intel(int cpu)
 		return UCODE_ERROR;
 	}
 
-	if (bsp && rev != prev_rev) {
+	if (bsp && ((rev != prev_rev) || ucode_rollback)) {
 		pr_info("updated to revision 0x%x, date = %04x-%02x-%02x\n",
 			rev,
 			mc->hdr.date & 0xffff,
-- 
https://clearlinux.org