sb/intel/bd82x6x: Support ME Soft Temporary Disable Mode

- Add support for ME Soft Temporary Disable Mode. In this mode, ME
  doesn't load its kernel and freezes at Bring UP (BUP) phase. This mode
  is saved in ME NVRAM (and thus will remain for next reboots and
  poweroffs).

- Add support of new CMOS option for Sandy Bridge and Ivy Bridge
  ThinkPads.

HOW TO USE

To disable ME:
1. nvramtool -w me_state=Disabled
2. reboot

To enable it back:
1. nvramtool -w me_state=Normal
2. reboot

To check current status:
intelmetool -m

Tested on ThinkPad X230 and ThinkPad X220.

BACKGROUND

There's no Intel documentation that would explain how this should be
implemented, in public. Working binary sequence for MKHI command to put
ME in Soft Temporary Disable Mode, as well as a way to bring ME out of
it (by writing to H_GS register), was found and published by researchers
from PT Security:

1.  To disable ME, BIOS issues the disable command (before End of Post)
    and reboots. ME is supposed to be disabled on the next boot after
    DID (DRAM Init Done).

    My numerous tests show that issuing the command and rebooting is not
    enough. If we reboot too early, ME will not be disabled. Apparently,
    it is doing something in background after receiving the command. It
    works with a delay of 500-1000 ms.

    I also tried to dump all known (documented) registers, such as GMES
    and HFS, before and during the next 2 seconds after execution of the
    disable command to find a possible indication that something's
    changed in ME and we're ready to reboot. Found nothing
    unfortunately.

2.  To enable ME back, host writes value 0x20000000 to H_GS.

    PT slides don't contain any more information on it, but my tests
    show, that after writing this value, GMES[31:28] is changing from
    0x01 (BUP phase) to 0x03 (Policy Module) to 0x06 (Host
    Communication). Then, after some more time, fw_init_complete bit of
    HFS becomes 1.

    This means that ME starts loading its kernel immediately, without
    reboot.

    On the other hand, Lenovo BIOS clearly perform a reboot after
    enabling it (one reboot after saving the settings, then ThinkPad
    logo appears, and then one more reboot). I'm assuming we have to
    reset too.

Change-Id: Ic01526c9731cbef4e8552bbc352133a2415787c2
Signed-off-by: Evgeny Zinoviev <me@ch1p.io>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/37115
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Angel Pons <th3fanbus@gmail.com>
Reviewed-by: Nico Huber <nico.h@gmx.de>
diff --git a/src/mainboard/lenovo/l520/cmos.default b/src/mainboard/lenovo/l520/cmos.default
index 979f132..681c40e 100644
--- a/src/mainboard/lenovo/l520/cmos.default
+++ b/src/mainboard/lenovo/l520/cmos.default
@@ -14,3 +14,4 @@
 trackpoint=Enable
 backlight=Both
 usb_always_on=Disable
+me_state=Normal
diff --git a/src/mainboard/lenovo/l520/cmos.layout b/src/mainboard/lenovo/l520/cmos.layout
index e96915d..a3f5308 100644
--- a/src/mainboard/lenovo/l520/cmos.layout
+++ b/src/mainboard/lenovo/l520/cmos.layout
@@ -34,7 +34,9 @@
 421	1	e	9	sata_mode
 422	2	e	10	backlight
 
-# coreboot config options: cpu
+# coreboot config options: ME
+424	1	e	13	me_state
+425	2	h	0	me_state_prev
 
 # coreboot config options: northbridge
 432	3	e	11	gfx_uma_size
@@ -89,6 +91,8 @@
 12	0	Disable
 12	1	AC and battery
 12	2	AC only
+13	0	Normal
+13	1	Disabled
 
 # -----------------------------------------------------------------
 checksums
diff --git a/src/mainboard/lenovo/t420/cmos.default b/src/mainboard/lenovo/t420/cmos.default
index 467f965..8244071 100644
--- a/src/mainboard/lenovo/t420/cmos.default
+++ b/src/mainboard/lenovo/t420/cmos.default
@@ -14,3 +14,4 @@
 trackpoint=Enable
 hybrid_graphics_mode=Integrated Only
 usb_always_on=Disable
+me_state=Normal
diff --git a/src/mainboard/lenovo/t420/cmos.layout b/src/mainboard/lenovo/t420/cmos.layout
index e1d15be..daf569c 100644
--- a/src/mainboard/lenovo/t420/cmos.layout
+++ b/src/mainboard/lenovo/t420/cmos.layout
@@ -34,7 +34,9 @@
 421	1	e	9	sata_mode
 422	2	e	13	usb_always_on
 
-# coreboot config options: cpu
+# coreboot config options: ME
+424	1	e	14	me_state
+425	2	h	0	me_state_prev
 
 # coreboot config options: northbridge
 432	3	e	11	gfx_uma_size
@@ -97,6 +99,8 @@
 13	0	Disable
 13	1	AC and battery
 13	2	AC only
+14	0	Normal
+14	1	Disabled
 
 # -----------------------------------------------------------------
 checksums
diff --git a/src/mainboard/lenovo/t420s/cmos.default b/src/mainboard/lenovo/t420s/cmos.default
index 467f965..8244071 100644
--- a/src/mainboard/lenovo/t420s/cmos.default
+++ b/src/mainboard/lenovo/t420s/cmos.default
@@ -14,3 +14,4 @@
 trackpoint=Enable
 hybrid_graphics_mode=Integrated Only
 usb_always_on=Disable
+me_state=Normal
diff --git a/src/mainboard/lenovo/t420s/cmos.layout b/src/mainboard/lenovo/t420s/cmos.layout
index e1d15be..daf569c 100644
--- a/src/mainboard/lenovo/t420s/cmos.layout
+++ b/src/mainboard/lenovo/t420s/cmos.layout
@@ -34,7 +34,9 @@
 421	1	e	9	sata_mode
 422	2	e	13	usb_always_on
 
-# coreboot config options: cpu
+# coreboot config options: ME
+424	1	e	14	me_state
+425	2	h	0	me_state_prev
 
 # coreboot config options: northbridge
 432	3	e	11	gfx_uma_size
@@ -97,6 +99,8 @@
 13	0	Disable
 13	1	AC and battery
 13	2	AC only
+14	0	Normal
+14	1	Disabled
 
 # -----------------------------------------------------------------
 checksums
diff --git a/src/mainboard/lenovo/t430/cmos.default b/src/mainboard/lenovo/t430/cmos.default
index e65869b..26795fe 100644
--- a/src/mainboard/lenovo/t430/cmos.default
+++ b/src/mainboard/lenovo/t430/cmos.default
@@ -15,3 +15,4 @@
 backlight=Both
 usb_always_on=Disable
 hybrid_graphics_mode=Integrated Only
+me_state=Normal
diff --git a/src/mainboard/lenovo/t430/cmos.layout b/src/mainboard/lenovo/t430/cmos.layout
index dd51c36..3e48df5 100644
--- a/src/mainboard/lenovo/t430/cmos.layout
+++ b/src/mainboard/lenovo/t430/cmos.layout
@@ -34,7 +34,9 @@
 421	1	e	9	sata_mode
 422	2	e	10	backlight
 
-# coreboot config options: cpu
+# coreboot config options: ME
+424	1	e	14	me_state
+425	2	h	0	me_state_prev
 
 # coreboot config options: northbridge
 432	3	e	11	gfx_uma_size
@@ -96,6 +98,8 @@
 13	0	Disable
 13	1	AC and battery
 13	2	AC only
+14	0	Normal
+14	1	Disabled
 
 # -----------------------------------------------------------------
 checksums
diff --git a/src/mainboard/lenovo/t430s/cmos.default b/src/mainboard/lenovo/t430s/cmos.default
index a30c90d..52dbf70 100644
--- a/src/mainboard/lenovo/t430s/cmos.default
+++ b/src/mainboard/lenovo/t430s/cmos.default
@@ -16,3 +16,4 @@
 enable_dual_graphics=Disable
 usb_always_on=Disable
 f1_to_f12_as_primary=Enable
+me_state=Normal
diff --git a/src/mainboard/lenovo/t430s/cmos.layout b/src/mainboard/lenovo/t430s/cmos.layout
index 02c1ea7..14a21eb 100644
--- a/src/mainboard/lenovo/t430s/cmos.layout
+++ b/src/mainboard/lenovo/t430s/cmos.layout
@@ -35,7 +35,9 @@
 422	2	e	10	backlight
 424	1	e	1	f1_to_f12_as_primary
 
-# coreboot config options: cpu
+# coreboot config options: ME
+425	1	e	13	me_state
+426	2	h	0	me_state_prev
 
 # coreboot config options: northbridge
 432	3	e	11	gfx_uma_size
@@ -94,6 +96,8 @@
 12	0	Disable
 12	1	AC and battery
 12	2	AC only
+13	0	Normal
+13	1	Disabled
 
 # -----------------------------------------------------------------
 checksums
diff --git a/src/mainboard/lenovo/t520/cmos.default b/src/mainboard/lenovo/t520/cmos.default
index 6e61dae..cf79b39 100644
--- a/src/mainboard/lenovo/t520/cmos.default
+++ b/src/mainboard/lenovo/t520/cmos.default
@@ -15,3 +15,4 @@
 backlight=Both
 hybrid_graphics_mode=Integrated Only
 usb_always_on=Disable
+me_state=Normal
diff --git a/src/mainboard/lenovo/t520/cmos.layout b/src/mainboard/lenovo/t520/cmos.layout
index dd51c36..3e48df5 100644
--- a/src/mainboard/lenovo/t520/cmos.layout
+++ b/src/mainboard/lenovo/t520/cmos.layout
@@ -34,7 +34,9 @@
 421	1	e	9	sata_mode
 422	2	e	10	backlight
 
-# coreboot config options: cpu
+# coreboot config options: ME
+424	1	e	14	me_state
+425	2	h	0	me_state_prev
 
 # coreboot config options: northbridge
 432	3	e	11	gfx_uma_size
@@ -96,6 +98,8 @@
 13	0	Disable
 13	1	AC and battery
 13	2	AC only
+14	0	Normal
+14	1	Disabled
 
 # -----------------------------------------------------------------
 checksums
diff --git a/src/mainboard/lenovo/t530/cmos.default b/src/mainboard/lenovo/t530/cmos.default
index 6e61dae..cf79b39 100644
--- a/src/mainboard/lenovo/t530/cmos.default
+++ b/src/mainboard/lenovo/t530/cmos.default
@@ -15,3 +15,4 @@
 backlight=Both
 hybrid_graphics_mode=Integrated Only
 usb_always_on=Disable
+me_state=Normal
diff --git a/src/mainboard/lenovo/t530/cmos.layout b/src/mainboard/lenovo/t530/cmos.layout
index 6cd8ac0..d109a61 100644
--- a/src/mainboard/lenovo/t530/cmos.layout
+++ b/src/mainboard/lenovo/t530/cmos.layout
@@ -34,7 +34,9 @@
 421	1	e	9	sata_mode
 422	2	e	10	backlight
 
-# coreboot config options: cpu
+# coreboot config options: ME
+424	1	e	14	me_state
+425	2	h	0	me_state_prev
 
 # coreboot config options: northbridge
 432	3	e	11	gfx_uma_size
@@ -97,6 +99,8 @@
 13	0	Disable
 13	1	AC and battery
 13	2	AC only
+14	0	Normal
+14	1	Disabled
 
 # -----------------------------------------------------------------
 checksums
diff --git a/src/mainboard/lenovo/x220/cmos.default b/src/mainboard/lenovo/x220/cmos.default
index 42720a2..6d1d57a 100644
--- a/src/mainboard/lenovo/x220/cmos.default
+++ b/src/mainboard/lenovo/x220/cmos.default
@@ -13,3 +13,4 @@
 fn_ctrl_swap=Disable
 sticky_fn=Disable
 trackpoint=Enable
+me_state=Normal
diff --git a/src/mainboard/lenovo/x220/cmos.layout b/src/mainboard/lenovo/x220/cmos.layout
index f152b29..c63ed8c 100644
--- a/src/mainboard/lenovo/x220/cmos.layout
+++ b/src/mainboard/lenovo/x220/cmos.layout
@@ -34,7 +34,9 @@
 421	1	e	9	sata_mode
 422	2	e	12	usb_always_on
 
-# coreboot config options: cpu
+# coreboot config options: ME
+424	1	e	13	me_state
+425	2	h	0	me_state_prev
 
 # coreboot config options: northbridge
 432	3	e	11	gfx_uma_size
@@ -92,6 +94,8 @@
 12	0	Disable
 12	1	AC and battery
 12	2	AC only
+13	0	Normal
+13	1	Disabled
 
 # -----------------------------------------------------------------
 checksums
diff --git a/src/mainboard/lenovo/x230/cmos.default b/src/mainboard/lenovo/x230/cmos.default
index 5c19d0f..7314066 100644
--- a/src/mainboard/lenovo/x230/cmos.default
+++ b/src/mainboard/lenovo/x230/cmos.default
@@ -15,3 +15,4 @@
 backlight=Both
 usb_always_on=Disable
 f1_to_f12_as_primary=Enable
+me_state=Normal
diff --git a/src/mainboard/lenovo/x230/cmos.layout b/src/mainboard/lenovo/x230/cmos.layout
index 89891bf..2211018 100644
--- a/src/mainboard/lenovo/x230/cmos.layout
+++ b/src/mainboard/lenovo/x230/cmos.layout
@@ -35,7 +35,9 @@
 422	2	e	10	backlight
 424	1	e	1	f1_to_f12_as_primary
 
-# coreboot config options: cpu
+# coreboot config options: ME
+425	1	e	13	me_state
+426	2	h	0	me_state_prev
 
 # coreboot config options: northbridge
 432	3	e	11	gfx_uma_size
@@ -94,6 +96,8 @@
 12	0	Disable
 12	1	AC and battery
 12	2	AC only
+13	0	Normal
+13	1	Disabled
 
 # -----------------------------------------------------------------
 checksums
diff --git a/src/southbridge/intel/bd82x6x/me.c b/src/southbridge/intel/bd82x6x/me.c
index fe2a37c..c522d77 100644
--- a/src/southbridge/intel/bd82x6x/me.c
+++ b/src/southbridge/intel/bd82x6x/me.c
@@ -9,6 +9,7 @@
  */
 
 #include <acpi/acpi.h>
+#include <cf9_reset.h>
 #include <device/mmio.h>
 #include <device/device.h>
 #include <device/pci.h>
@@ -19,6 +20,9 @@
 #include <string.h>
 #include <delay.h>
 #include <elog.h>
+#include <halt.h>
+#include <option.h>
+#include <southbridge/intel/common/me.h>
 
 #include "me.h"
 #include "pch.h"
@@ -248,13 +252,20 @@
 static void intel_me_init(struct device *dev)
 {
 	me_bios_path path = intel_me_path(dev);
+	u8 me_state = 0, me_state_prev = 0;
+	bool need_reset = false;
+	struct me_hfs hfs;
 
 	/* Do initial setup and determine the BIOS path */
 	printk(BIOS_NOTICE, "ME: BIOS path: %s\n", me_get_bios_path_string(path));
 
+	get_option(&me_state, "me_state");
+	get_option(&me_state_prev, "me_state_prev");
+
+	printk(BIOS_DEBUG, "ME: me_state=%u, me_state_prev=%u\n", me_state, me_state_prev);
+
 	switch (path) {
 	case ME_S3WAKE_BIOS_PATH:
-	case ME_DISABLE_BIOS_PATH:
 #if CONFIG(HIDE_MEI_ON_ERROR)
 	case ME_ERROR_BIOS_PATH:
 #endif
@@ -277,12 +288,49 @@
 			mkhi_get_fwcaps();
 		}
 
+		/* Put ME in Software Temporary Disable Mode, if needed */
+		if (me_state == CMOS_ME_STATE_DISABLED
+				&& CMOS_ME_STATE(me_state_prev) == CMOS_ME_STATE_NORMAL) {
+			printk(BIOS_INFO, "ME: disabling ME\n");
+			if (enter_soft_temp_disable()) {
+				enter_soft_temp_disable_wait();
+				need_reset = true;
+			} else {
+				printk(BIOS_ERR, "ME: failed to enter Soft Temporary Disable mode\n");
+			}
+
+			break;
+		}
+
 		/*
 		 * Leave the ME unlocked in this path.
 		 * It will be locked via SMI command later.
 		 */
 		break;
 
+	case ME_DISABLE_BIOS_PATH:
+		/* Bring ME out of Soft Temporary Disable mode, if needed */
+		pci_read_dword_ptr(dev, &hfs, PCI_ME_HFS);
+		if (hfs.operation_mode == ME_HFS_MODE_DIS
+				&& me_state == CMOS_ME_STATE_NORMAL
+				&& (CMOS_ME_STATE(me_state_prev) == CMOS_ME_STATE_DISABLED
+					|| !CMOS_ME_CHANGED(me_state_prev))) {
+			printk(BIOS_INFO, "ME: re-enabling ME\n");
+
+			exit_soft_temp_disable(dev);
+			exit_soft_temp_disable_wait(dev);
+
+			/*
+			 * ME starts loading firmware immediately after writing to H_GS,
+			 * but Lenovo BIOS performs a reboot after bringing ME back to
+			 * Normal mode. Assume that global reset is needed.
+			 */
+			need_reset = true;
+		} else {
+			intel_me_hide(dev);
+		}
+		break;
+
 #if !CONFIG(HIDE_MEI_ON_ERROR)
 	case ME_ERROR_BIOS_PATH:
 #endif
@@ -290,6 +338,18 @@
 	case ME_FIRMWARE_UPDATE_BIOS_PATH:
 		break;
 	}
+
+	/* To avoid boot loops if ME fails to get back from disabled mode,
+	   set the 'changed' bit here. */
+	if (me_state != CMOS_ME_STATE(me_state_prev) || need_reset) {
+		u8 new_state = me_state | CMOS_ME_STATE_CHANGED;
+		set_option("me_state_prev", &new_state);
+	}
+
+	if (need_reset) {
+		set_global_reset(true);
+		full_reset();
+	}
 }
 
 static struct device_operations device_ops = {
diff --git a/src/southbridge/intel/bd82x6x/me.h b/src/southbridge/intel/bd82x6x/me.h
index 014fc1d..d99c452 100644
--- a/src/southbridge/intel/bd82x6x/me.h
+++ b/src/southbridge/intel/bd82x6x/me.h
@@ -171,6 +171,22 @@
 #define MKHI_GLOBAL_RESET	0x0b
 
 #define MKHI_FWCAPS_GET_RULE	0x02
+#define MKHI_FWCAPS_SET_RULE	0x03
+
+#define MKHI_DISABLE_RULE_ID	0x06
+
+#define CMOS_ME_STATE(state)	((state) & 0x1)
+#define CMOS_ME_CHANGED(state)	(((state) & 0x2) >> 1)
+#define CMOS_ME_STATE_NORMAL	0
+#define CMOS_ME_STATE_DISABLED	1
+#define CMOS_ME_STATE_CHANGED	2
+
+#define ME_ENABLE_TIMEOUT	20000
+
+struct me_disable {
+	u32 rule_id;
+	u16 data;
+} __packed;
 
 #define MKHI_MDES_ENABLE	0x09
 
@@ -228,6 +244,10 @@
 
 #ifndef __SIMPLE_DEVICE__
 void pci_read_dword_ptr(struct device *dev, void *ptr, int offset);
+bool enter_soft_temp_disable(void);
+void enter_soft_temp_disable_wait(void);
+void exit_soft_temp_disable(struct device *dev);
+void exit_soft_temp_disable_wait(struct device *dev);
 #endif
 
 void read_host_csr(struct mei_csr *csr);
diff --git a/src/southbridge/intel/bd82x6x/me_8.x.c b/src/southbridge/intel/bd82x6x/me_8.x.c
index f5a39ec..78c71aa 100644
--- a/src/southbridge/intel/bd82x6x/me_8.x.c
+++ b/src/southbridge/intel/bd82x6x/me_8.x.c
@@ -9,6 +9,7 @@
  */
 
 #include <acpi/acpi.h>
+#include <cf9_reset.h>
 #include <device/mmio.h>
 #include <device/device.h>
 #include <device/pci.h>
@@ -19,6 +20,9 @@
 #include <string.h>
 #include <delay.h>
 #include <elog.h>
+#include <halt.h>
+#include <option.h>
+#include <southbridge/intel/common/me.h>
 
 #include "me.h"
 #include "pch.h"
@@ -206,8 +210,7 @@
 
 	/* Check if the MBP is ready */
 	if (!gmes.mbp_rdy) {
-		printk(BIOS_CRIT, "%s: mbp is not ready!\n",
-		       __func__);
+		printk(BIOS_CRIT, "%s: mbp is not ready!\n", __func__);
 		path = ME_ERROR_BIOS_PATH;
 	}
 
@@ -236,13 +239,20 @@
 {
 	me_bios_path path = intel_me_path(dev);
 	me_bios_payload mbp_data;
+	u8 me_state = 0, me_state_prev = 0;
+	bool need_reset = false;
+	struct me_hfs hfs;
 
 	/* Do initial setup and determine the BIOS path */
 	printk(BIOS_NOTICE, "ME: BIOS path: %s\n", me_get_bios_path_string(path));
 
+	get_option(&me_state, "me_state");
+	get_option(&me_state_prev, "me_state_prev");
+
+	printk(BIOS_DEBUG, "ME: me_state=%u, me_state_prev=%u\n", me_state, me_state_prev);
+
 	switch (path) {
 	case ME_S3WAKE_BIOS_PATH:
-	case ME_DISABLE_BIOS_PATH:
 #if CONFIG(HIDE_MEI_ON_ERROR)
 	case ME_ERROR_BIOS_PATH:
 #endif
@@ -266,12 +276,49 @@
 			me_print_fwcaps(&mbp_data.fw_caps_sku);
 		}
 
+		/* Put ME in Software Temporary Disable Mode, if needed */
+		if (me_state == CMOS_ME_STATE_DISABLED
+				&& CMOS_ME_STATE(me_state_prev) == CMOS_ME_STATE_NORMAL) {
+			printk(BIOS_INFO, "ME: disabling ME\n");
+			if (enter_soft_temp_disable()) {
+				enter_soft_temp_disable_wait();
+				need_reset = true;
+			} else {
+				printk(BIOS_ERR, "ME: failed to enter Soft Temporary Disable mode\n");
+			}
+
+			break;
+		}
+
 		/*
 		 * Leave the ME unlocked in this path.
 		 * It will be locked via SMI command later.
 		 */
 		break;
 
+	case ME_DISABLE_BIOS_PATH:
+		/* Bring ME out of Soft Temporary Disable mode, if needed */
+		pci_read_dword_ptr(dev, &hfs, PCI_ME_HFS);
+		if (hfs.operation_mode == ME_HFS_MODE_DIS
+				&& me_state == CMOS_ME_STATE_NORMAL
+				&& (CMOS_ME_STATE(me_state_prev) == CMOS_ME_STATE_DISABLED
+					|| !CMOS_ME_CHANGED(me_state_prev))) {
+			printk(BIOS_INFO, "ME: re-enabling ME\n");
+
+			exit_soft_temp_disable(dev);
+			exit_soft_temp_disable_wait(dev);
+
+			/*
+			 * ME starts loading firmware immediately after writing to H_GS,
+			 * but Lenovo BIOS performs a reboot after bringing ME back to
+			 * Normal mode. Assume that global reset is needed.
+			 */
+			need_reset = true;
+		} else {
+			intel_me_hide(dev);
+		}
+		break;
+
 #if !CONFIG(HIDE_MEI_ON_ERROR)
 	case ME_ERROR_BIOS_PATH:
 #endif
@@ -279,6 +326,18 @@
 	case ME_FIRMWARE_UPDATE_BIOS_PATH:
 		break;
 	}
+
+	/* To avoid boot loops if ME fails to get back from disabled mode,
+	   set the 'changed' bit here. */
+	if (me_state != CMOS_ME_STATE(me_state_prev) || need_reset) {
+		u8 new_state = me_state | CMOS_ME_STATE_CHANGED;
+		set_option("me_state_prev", &new_state);
+	}
+
+	if (need_reset) {
+		set_global_reset(true);
+		full_reset();
+	}
 }
 
 static struct device_operations device_ops = {
diff --git a/src/southbridge/intel/bd82x6x/me_common.c b/src/southbridge/intel/bd82x6x/me_common.c
index 422c091..e229956 100644
--- a/src/southbridge/intel/bd82x6x/me_common.c
+++ b/src/southbridge/intel/bd82x6x/me_common.c
@@ -11,6 +11,7 @@
 #include <string.h>
 #include <delay.h>
 #include <halt.h>
+#include <timer.h>
 
 #include "me.h"
 #include "pch.h"
@@ -417,4 +418,78 @@
 	pch_enable(dev);
 }
 
+bool enter_soft_temp_disable(void)
+{
+	/* The binary sequence for the disable command was found by PT in some vendor BIOS */
+	struct me_disable message = {
+		.rule_id = MKHI_DISABLE_RULE_ID,
+		.data = 0x01,
+	};
+	struct mkhi_header mkhi = {
+		.group_id	= MKHI_GROUP_ID_FWCAPS,
+		.command	= MKHI_FWCAPS_SET_RULE,
+	};
+	struct mei_header mei = {
+		.is_complete	= 1,
+		.length		= sizeof(mkhi) + sizeof(message),
+		.host_address	= MEI_HOST_ADDRESS,
+		.client_address	= MEI_ADDRESS_MKHI,
+	};
+	u32 resp;
+
+	if (mei_sendrecv(&mei, &mkhi, &message, &resp, sizeof(resp)) < 0
+	    || resp != MKHI_DISABLE_RULE_ID) {
+		printk(BIOS_WARNING, "ME: disable command failed\n");
+		return false;
+	}
+
+	return true;
+}
+
+void enter_soft_temp_disable_wait(void)
+{
+	/*
+	 * TODO: Find smarter way to determine when we're ready to reboot.
+	 *
+	 * There has to be some bit in some register, or something, that indicates that ME has
+	 * finished doing its thing and we're ready to reboot.
+	 *
+	 * It was not found yet, though, and waiting for a response after the disable command is
+	 * not enough. If we reboot too early, ME will not be disabled on next boot. For now,
+	 * let's just wait for 1 second here.
+	 */
+	mdelay(1000);
+}
+
+void exit_soft_temp_disable(struct device *dev)
+{
+	/* To bring ME out of Soft Temporary Disable Mode, host writes 0x20000000 to H_GS */
+	pci_write_config32(dev, PCI_ME_H_GS, 0x2 << 28);
+}
+
+void exit_soft_temp_disable_wait(struct device *dev)
+{
+	struct me_hfs hfs;
+	struct stopwatch sw;
+
+	stopwatch_init_msecs_expire(&sw, ME_ENABLE_TIMEOUT);
+
+	/**
+	 * Wait for fw_init_complete. Check every 50 ms, give up after 20 sec.
+	 * This is what vendor BIOS does. Usually it takes 1.5 seconds or so.
+	 */
+	do {
+		mdelay(50);
+		pci_read_dword_ptr(dev, &hfs, PCI_ME_HFS);
+		if (hfs.fw_init_complete)
+			break;
+	} while (!stopwatch_expired(&sw));
+
+	if (!hfs.fw_init_complete)
+		printk(BIOS_ERR, "ME: giving up on waiting for fw_init_complete\n");
+	else
+		printk(BIOS_NOTICE, "ME: took %lums to complete initialization\n",
+		       stopwatch_duration_msecs(&sw));
+}
+
 #endif