diff options
Diffstat (limited to 'src/southbridge')
-rw-r--r-- | src/southbridge/intel/bd82x6x/me.c | 62 | ||||
-rw-r--r-- | src/southbridge/intel/bd82x6x/me.h | 20 | ||||
-rw-r--r-- | src/southbridge/intel/bd82x6x/me_8.x.c | 65 | ||||
-rw-r--r-- | src/southbridge/intel/bd82x6x/me_common.c | 75 |
4 files changed, 218 insertions, 4 deletions
diff --git a/src/southbridge/intel/bd82x6x/me.c b/src/southbridge/intel/bd82x6x/me.c index fe2a37c849..c522d77a11 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 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 +288,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 +338,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..d99c452669 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; + u16 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); diff --git a/src/southbridge/intel/bd82x6x/me_8.x.c b/src/southbridge/intel/bd82x6x/me_8.x.c index f5a39ecfa6..78c71aa368 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 @@ 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 +239,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 +276,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 +326,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..e229956607 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 @@ 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 |