diff options
author | Evgeny Zinoviev <me@ch1p.io> | 2019-11-21 21:47:31 +0300 |
---|---|---|
committer | Evgeny Zinoviev <me@ch1p.io> | 2021-02-05 23:29:17 +0300 |
commit | cc5f7de34a857bc55e74e2d63093badd856919e8 (patch) | |
tree | 281767fdc81c778c5aac58a65245854b1d50a2af | |
parent | ae1d122372c0c0393b8278ee6d7e6c61ebf364ce (diff) |
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>
26 files changed, 306 insertions, 52 deletions
diff --git a/src/mainboard/lenovo/l520/cmos.default b/src/mainboard/lenovo/l520/cmos.default index 979f132863..681c40e78b 100644 --- a/src/mainboard/lenovo/l520/cmos.default +++ b/src/mainboard/lenovo/l520/cmos.default @@ -14,3 +14,4 @@ sticky_fn=Disable 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 e96915d2d1..a3f5308aa4 100644 --- a/src/mainboard/lenovo/l520/cmos.layout +++ b/src/mainboard/lenovo/l520/cmos.layout @@ -34,7 +34,9 @@ entries 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 @@ enumerations 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 467f965829..8244071b8a 100644 --- a/src/mainboard/lenovo/t420/cmos.default +++ b/src/mainboard/lenovo/t420/cmos.default @@ -14,3 +14,4 @@ sticky_fn=Disable 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 e1d15be56b..daf569c0af 100644 --- a/src/mainboard/lenovo/t420/cmos.layout +++ b/src/mainboard/lenovo/t420/cmos.layout @@ -34,7 +34,9 @@ entries 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 @@ enumerations 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 467f965829..8244071b8a 100644 --- a/src/mainboard/lenovo/t420s/cmos.default +++ b/src/mainboard/lenovo/t420s/cmos.default @@ -14,3 +14,4 @@ sticky_fn=Disable 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 e1d15be56b..daf569c0af 100644 --- a/src/mainboard/lenovo/t420s/cmos.layout +++ b/src/mainboard/lenovo/t420s/cmos.layout @@ -34,7 +34,9 @@ entries 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 @@ enumerations 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 e65869b332..26795fe5cf 100644 --- a/src/mainboard/lenovo/t430/cmos.default +++ b/src/mainboard/lenovo/t430/cmos.default @@ -15,3 +15,4 @@ trackpoint=Enable 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 dd51c36854..3e48df5584 100644 --- a/src/mainboard/lenovo/t430/cmos.layout +++ b/src/mainboard/lenovo/t430/cmos.layout @@ -34,7 +34,9 @@ entries 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 @@ enumerations 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 a30c90dc1f..52dbf70377 100644 --- a/src/mainboard/lenovo/t430s/cmos.default +++ b/src/mainboard/lenovo/t430s/cmos.default @@ -16,3 +16,4 @@ backlight=Both 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 02c1ea78df..14a21eb84e 100644 --- a/src/mainboard/lenovo/t430s/cmos.layout +++ b/src/mainboard/lenovo/t430s/cmos.layout @@ -35,7 +35,9 @@ entries 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 @@ enumerations 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 6e61dae340..cf79b391e2 100644 --- a/src/mainboard/lenovo/t520/cmos.default +++ b/src/mainboard/lenovo/t520/cmos.default @@ -15,3 +15,4 @@ trackpoint=Enable 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 dd51c36854..3e48df5584 100644 --- a/src/mainboard/lenovo/t520/cmos.layout +++ b/src/mainboard/lenovo/t520/cmos.layout @@ -34,7 +34,9 @@ entries 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 @@ enumerations 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 6e61dae340..cf79b391e2 100644 --- a/src/mainboard/lenovo/t530/cmos.default +++ b/src/mainboard/lenovo/t530/cmos.default @@ -15,3 +15,4 @@ trackpoint=Enable 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 6cd8ac066b..d109a61b4e 100644 --- a/src/mainboard/lenovo/t530/cmos.layout +++ b/src/mainboard/lenovo/t530/cmos.layout @@ -34,7 +34,9 @@ entries 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 @@ enumerations 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 42720a27bd..6d1d57a795 100644 --- a/src/mainboard/lenovo/x220/cmos.default +++ b/src/mainboard/lenovo/x220/cmos.default @@ -13,3 +13,4 @@ usb_always_on=Disable 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 f152b2982a..c63ed8c22c 100644 --- a/src/mainboard/lenovo/x220/cmos.layout +++ b/src/mainboard/lenovo/x220/cmos.layout @@ -34,7 +34,9 @@ entries 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 @@ enumerations 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 5c19d0f4a5..7314066c2b 100644 --- a/src/mainboard/lenovo/x230/cmos.default +++ b/src/mainboard/lenovo/x230/cmos.default @@ -15,3 +15,4 @@ trackpoint=Enable 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 89891bf0b0..914e8ff5f6 100644 --- a/src/mainboard/lenovo/x230/cmos.layout +++ b/src/mainboard/lenovo/x230/cmos.layout @@ -35,7 +35,9 @@ entries 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 @@ enumerations 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/Makefile.inc b/src/southbridge/intel/bd82x6x/Makefile.inc index a591aabea0..fdf9b87e64 100644 --- a/src/southbridge/intel/bd82x6x/Makefile.inc +++ b/src/southbridge/intel/bd82x6x/Makefile.inc @@ -31,6 +31,7 @@ smm-y += smihandler.c me.c me_8.x.c pch.c me_common.c romstage-y += me_status.c romstage-y += early_rcba.c romstage-y += early_pch.c +romstage-y += me_common.c ifeq ($(CONFIG_USE_NATIVE_RAMINIT),y) romstage-y += early_thermal.c early_me.c early_usb.c diff --git a/src/southbridge/intel/bd82x6x/early_me.c b/src/southbridge/intel/bd82x6x/early_me.c index 6320d2ea9f..b7a3b44d06 100644 --- a/src/southbridge/intel/bd82x6x/early_me.c +++ b/src/southbridge/intel/bd82x6x/early_me.c @@ -91,22 +91,6 @@ int intel_early_me_uma_size(void) return 0; } -static inline void set_global_reset(int enable) -{ - u32 etr3 = pci_read_config32(PCH_LPC_DEV, ETR3); - - /* Clear CF9 Without Resume Well Reset Enable */ - etr3 &= ~ETR3_CWORWRE; - - /* CF9GR indicates a Global Reset */ - if (enable) - etr3 |= ETR3_CF9GR; - else - etr3 &= ~ETR3_CF9GR; - - pci_write_config32(PCH_LPC_DEV, ETR3, etr3); -} - int intel_early_me_init_done(u8 status) { u8 reset, errorcode, opmode; @@ -163,7 +147,7 @@ int intel_early_me_init_done(u8 status) if ((me_fws2 & 0x80) == 0x80) { printk(BIOS_NOTICE, "CPU was replaced & warm reset required...\n"); pci_and_config16(PCI_DEV(0, 31, 0), 0xa2, ~0x80); - set_global_reset(0); + set_global_reset(false); system_reset(); } @@ -232,17 +216,17 @@ int intel_early_me_init_done(u8 status) return 0; case ME_HFS_ACK_RESET: /* Non-power cycle reset */ - set_global_reset(0); + set_global_reset(false); reset |= 0x06; break; case ME_HFS_ACK_PWR_CYCLE: /* Power cycle reset */ - set_global_reset(0); + set_global_reset(false); reset |= 0x0e; break; case ME_HFS_ACK_GBL_RESET: /* Global reset */ - set_global_reset(1); + set_global_reset(true); reset |= 0x0e; break; case ME_HFS_ACK_S3: diff --git a/src/southbridge/intel/bd82x6x/early_me_mrc.c b/src/southbridge/intel/bd82x6x/early_me_mrc.c index 0b11fd0e81..847c708f42 100644 --- a/src/southbridge/intel/bd82x6x/early_me_mrc.c +++ b/src/southbridge/intel/bd82x6x/early_me_mrc.c @@ -96,22 +96,6 @@ int intel_early_me_uma_size(void) return 0; } -static inline void set_global_reset(int enable) -{ - u32 etr3 = pci_read_config32(PCH_LPC_DEV, ETR3); - - /* Clear CF9 Without Resume Well Reset Enable */ - etr3 &= ~ETR3_CWORWRE; - - /* CF9GR indicates a Global Reset */ - if (enable) - etr3 |= ETR3_CF9GR; - else - etr3 &= ~ETR3_CF9GR; - - pci_write_config32(PCH_LPC_DEV, ETR3, etr3); -} - int intel_early_me_init_done(u8 status) { u8 reset; @@ -160,17 +144,17 @@ int intel_early_me_init_done(u8 status) return 0; case ME_HFS_ACK_RESET: /* Non-power cycle reset */ - set_global_reset(0); + set_global_reset(false); reset = 0x06; break; case ME_HFS_ACK_PWR_CYCLE: /* Power cycle reset */ - set_global_reset(0); + set_global_reset(false); reset = 0x0e; break; case ME_HFS_ACK_GBL_RESET: /* Global reset */ - set_global_reset(1); + set_global_reset(true); reset = 0x0e; break; case ME_HFS_ACK_S3: diff --git a/src/southbridge/intel/bd82x6x/me.c b/src/southbridge/intel/bd82x6x/me.c index fe2a37c849..558ee91e6a 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,8 @@ #include <string.h> #include <delay.h> #include <elog.h> +#include <halt.h> +#include <option.h> #include "me.h" #include "pch.h" @@ -248,13 +251,20 @@ static me_bios_path intel_me_path(struct device *dev) 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 +287,49 @@ static void intel_me_init(struct device *dev) 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 +337,18 @@ static void intel_me_init(struct device *dev) 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 014fc1d9ea..dfe2eb0bbf 100644 --- a/src/southbridge/intel/bd82x6x/me.h +++ b/src/southbridge/intel/bd82x6x/me.h @@ -171,6 +171,22 @@ struct mei_header { #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; + u32 data; +} __packed; #define MKHI_MDES_ENABLE 0x09 @@ -228,6 +244,10 @@ void mei_write_dword_ptr(void *ptr, int offset); #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); @@ -247,6 +267,8 @@ int intel_mei_setup(struct device *dev); int intel_me_extend_valid(struct device *dev); void intel_me_hide(struct device *dev); +void set_global_reset(bool enable); + /* Defined in me_status.c for both romstage and ramstage */ void intel_me_status(struct me_hfs *hfs, struct me_gmes *gmes); diff --git a/src/southbridge/intel/bd82x6x/me_8.x.c b/src/southbridge/intel/bd82x6x/me_8.x.c index f5a39ecfa6..3d9ebd6026 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,8 @@ #include <string.h> #include <delay.h> #include <elog.h> +#include <halt.h> +#include <option.h> #include "me.h" #include "pch.h" @@ -206,8 +209,7 @@ static me_bios_path intel_me_path(struct device *dev) /* 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 +238,20 @@ static void intel_me_init(struct device *dev) { 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 +275,49 @@ static void intel_me_init(struct device *dev) 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 +325,18 @@ static void intel_me_init(struct device *dev) 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 422c091001..1b25d146be 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,103 @@ void intel_me_hide(struct device *dev) 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 + +void set_global_reset(bool enable) +{ +#ifdef __SIMPLE_DEVICE__ + u32 etr3 = pci_read_config32(PCH_LPC_DEV, ETR3); +#else + struct device *lpc = pcidev_on_root(0x1f, 0); + u32 etr3 = pci_read_config32(lpc, ETR3); +#endif + + /* Clear CF9 Without Resume Well Reset Enable */ + etr3 &= ~ETR3_CWORWRE; + + /* CF9GR indicates a Global Reset */ + if (enable) + etr3 |= ETR3_CF9GR; + else + etr3 &= ~ETR3_CF9GR; + +#ifdef __SIMPLE_DEVICE__ + pci_write_config32(PCH_LPC_DEV, ETR3, etr3); +#else + pci_write_config32(lpc, ETR3, etr3); #endif +} diff --git a/src/southbridge/intel/ibexpeak/Makefile.inc b/src/southbridge/intel/ibexpeak/Makefile.inc index c745b0a552..da33778454 100644 --- a/src/southbridge/intel/ibexpeak/Makefile.inc +++ b/src/southbridge/intel/ibexpeak/Makefile.inc @@ -30,6 +30,7 @@ smm-y += smihandler.c romstage-y += early_pch.c romstage-y +=../bd82x6x/early_me.c romstage-y +=../bd82x6x/me_status.c +romstage-y +=../bd82x6x/me_common.c romstage-y += early_thermal.c romstage-y += ../bd82x6x/early_rcba.c romstage-y += early_cir.c |