blob: 265fe04bbc4a19e4718234540c72d50a511f2ea7 [file] [log] [blame]
/* SPDX-License-Identifier: GPL-2.0-only */
#include <acpi/acpi.h>
#include <bootstate.h>
#include <console/console.h>
#include <intelblocks/cse.h>
#include <intelblocks/pmc_ipc.h>
#include <security/vboot/vboot_common.h>
#include <soc/intel/common/reset.h>
#include <soc/pci_devs.h>
#include <timestamp.h>
#include <types.h>
#define CSE_MAX_RETRY_CMD 3
enum cse_cmd_result {
CSE_CMD_RESULT_GLOBAL_RESET_REQUESTED,
CSE_CMD_RESULT_SUCCESS,
CSE_CMD_RESULT_ERROR,
CSE_CMD_RESULT_DISABLED,
CSE_CMD_RESULT_RETRY,
};
static enum cse_cmd_result decode_heci_send_receive_error(enum cse_tx_rx_status ret)
{
switch (ret) {
case CSE_TX_ERR_CSE_NOT_READY:
case CSE_RX_ERR_CSE_NOT_READY:
case CSE_RX_ERR_RESP_LEN_MISMATCH:
case CSE_RX_ERR_TIMEOUT:
return CSE_CMD_RESULT_RETRY;
default:
return CSE_CMD_RESULT_ERROR;
}
}
static enum cse_cmd_result cse_disable_mei_bus(void)
{
struct bus_disable_message {
uint8_t command;
uint8_t reserved[3];
} __packed msg = {
.command = MEI_BUS_DISABLE_COMMAND,
};
struct bus_disable_resp {
uint8_t command;
uint8_t status;
uint8_t reserved[2];
} __packed reply = {};
size_t reply_sz = sizeof(reply);
enum cse_tx_rx_status ret;
printk(BIOS_DEBUG, "HECI, Sending MEI BIOS DISABLE command\n");
ret = heci_send_receive(&msg, sizeof(msg), &reply, &reply_sz, HECI_MEI_ADDR);
if (ret) {
printk(BIOS_ERR, "HECI: Failed to Disable MEI bus\n");
return decode_heci_send_receive_error(ret);
}
if (reply.status) {
printk(BIOS_ERR, "HECI: MEI_Bus_Disable Failed (status: %d)\n", reply.status);
return CSE_CMD_RESULT_ERROR;
}
return CSE_CMD_RESULT_SUCCESS;
}
static enum cse_cmd_result cse_receive_eop(void)
{
enum {
EOP_REQUESTED_ACTION_CONTINUE = 0,
EOP_REQUESTED_ACTION_GLOBAL_RESET = 1,
};
enum cse_tx_rx_status ret;
struct end_of_post_resp {
struct mkhi_hdr hdr;
uint32_t requested_actions;
} __packed resp = {};
size_t resp_size = sizeof(resp);
ret = heci_receive(&resp, &resp_size);
if (ret)
return decode_heci_send_receive_error(ret);
if (resp.hdr.group_id != MKHI_GROUP_ID_GEN ||
resp.hdr.command != MKHI_END_OF_POST) {
printk(BIOS_ERR, "HECI: EOP Unexpected response group or command.\n");
if (CONFIG(SOC_INTEL_CSE_SEND_EOP_ASYNC))
printk(BIOS_ERR, "HECI: It could be a HECI command conflict.\n");
return CSE_CMD_RESULT_ERROR;
}
if (resp.hdr.result) {
printk(BIOS_ERR, "HECI: EOP Resp Failed: %u\n", resp.hdr.result);
return CSE_CMD_RESULT_ERROR;
}
printk(BIOS_INFO, "CSE: EOP requested action: ");
switch (resp.requested_actions) {
case EOP_REQUESTED_ACTION_GLOBAL_RESET:
printk(BIOS_INFO, "global reset\n");
return CSE_CMD_RESULT_GLOBAL_RESET_REQUESTED;
case EOP_REQUESTED_ACTION_CONTINUE:
printk(BIOS_INFO, "continue boot\n");
return CSE_CMD_RESULT_SUCCESS;
default:
printk(BIOS_INFO, "unknown %u\n", resp.requested_actions);
return CSE_CMD_RESULT_ERROR;
}
}
static enum cse_cmd_result cse_send_eop(void)
{
enum cse_tx_rx_status ret;
struct end_of_post_msg {
struct mkhi_hdr hdr;
} __packed msg = {
.hdr = {
.group_id = MKHI_GROUP_ID_GEN,
.command = MKHI_END_OF_POST,
},
};
/*
* Prerequisites:
* 1) HFSTS1 CWS is Normal
* 2) HFSTS1 COM is Normal
* 3) Only sent after DID (accomplished by compiling this into ramstage)
*/
if (cse_is_hfs1_com_soft_temp_disable()) {
printk(BIOS_ERR, "HECI: Prerequisites not met for sending EOP\n");
if (CONFIG(SOC_INTEL_CSE_LITE_SKU))
return CSE_CMD_RESULT_ERROR;
return CSE_CMD_RESULT_DISABLED;
}
if (!cse_is_hfs1_cws_normal() || !cse_is_hfs1_com_normal()) {
printk(BIOS_ERR, "HECI: Prerequisites not met for sending EOP\n");
return CSE_CMD_RESULT_ERROR;
}
printk(BIOS_INFO, "HECI: Sending End-of-Post\n");
ret = heci_send(&msg, sizeof(msg), BIOS_HOST_ADDR, HECI_MKHI_ADDR);
if (ret)
return decode_heci_send_receive_error(ret);
return CSE_CMD_RESULT_SUCCESS;
}
static enum cse_cmd_result cse_send_and_receive_eop(void)
{
enum cse_cmd_result ret;
ret = cse_send_eop();
if (ret != CSE_CMD_RESULT_SUCCESS)
return ret;
return cse_receive_eop();
}
static enum cse_cmd_result cse_send_cmd_retries(enum cse_cmd_result (*cse_send_command)(void))
{
size_t retry;
enum cse_cmd_result ret;
for (retry = 0; retry < CSE_MAX_RETRY_CMD; retry++) {
ret = cse_send_command();
if (ret != CSE_CMD_RESULT_RETRY)
break;
}
return ret;
}
/*
* On EOP error, the BIOS is required to send an MEI bus disable message to the
* CSE, followed by disabling all MEI devices. After successfully completing
* this, it is safe to boot.
*/
static void cse_handle_eop_error(void)
{
if (cse_send_cmd_retries(cse_disable_mei_bus))
die("Failed to disable MEI bus while recovering from EOP error\n"
"Preventing system from booting into an insecure state.\n");
if (!cse_disable_mei_devices())
die("Error disabling MEI devices while recovering from EOP error\n"
"Preventing system from booting into an insecure state.\n");
}
static void handle_cse_eop_result(enum cse_cmd_result result)
{
switch (result) {
case CSE_CMD_RESULT_GLOBAL_RESET_REQUESTED:
printk(BIOS_INFO, "CSE requested global reset in EOP response, resetting...\n");
do_global_reset();
break;
case CSE_CMD_RESULT_SUCCESS:
printk(BIOS_INFO, "CSE EOP successful, continuing boot\n");
break;
case CSE_CMD_RESULT_DISABLED:
printk(BIOS_INFO, "CSE is disabled, continuing boot\n");
break;
case CSE_CMD_RESULT_ERROR: /* fallthrough */
default:
printk(BIOS_ERR, "Failed to send EOP to CSE, %d\n", result);
/* For vboot, trigger recovery mode if applicable, as there is
likely something very broken in this case. */
if (CONFIG(VBOOT) && !vboot_recovery_mode_enabled())
cse_trigger_vboot_recovery(CSE_EOP_FAIL);
/* In non-vboot builds or recovery mode, follow the BWG in order
to continue to boot securely. */
cse_handle_eop_error();
break;
}
}
static void do_send_end_of_post(bool wait_for_completion)
{
static bool eop_sent = false, eop_complete = false;
enum cse_cmd_result ret;
if (eop_complete) {
printk(BIOS_WARNING, "EOP already sent\n");
return;
}
if (acpi_get_sleep_type() == ACPI_S3) {
printk(BIOS_INFO, "Skip sending EOP during S3 resume\n");
return;
}
/*
* If CSE is already hidden then accessing CSE registers would be wrong and will
* receive junk, hence, return as CSE is already disabled.
*/
if (!is_cse_enabled()) {
printk(BIOS_DEBUG, "CSE is disabled, cannot send End-of-Post (EOP) message\n");
return;
}
if (!eop_sent) {
set_cse_device_state(PCH_DEVFN_CSE, DEV_ACTIVE);
timestamp_add_now(TS_ME_END_OF_POST_START);
ret = cse_send_cmd_retries(cse_send_eop);
if (ret == CSE_CMD_RESULT_SUCCESS)
eop_sent = true;
}
if (!wait_for_completion)
return;
set_cse_device_state(PCH_DEVFN_CSE, DEV_ACTIVE);
ret = cse_receive_eop();
if (ret != CSE_CMD_RESULT_SUCCESS) {
ret = cse_send_cmd_retries(cse_send_and_receive_eop);
handle_cse_eop_result(ret);
}
timestamp_add_now(TS_ME_END_OF_POST_END);
set_cse_device_state(PCH_DEVFN_CSE, DEV_IDLE);
eop_complete = true;
}
/*
* Don't send EOP if the following conditions are met:
* CSE Lite:
* 1. "The platform is running CSE-Lite SKU" AND
* 2. 'The CSE is running the RO FW" AND
* 3. "The board is in recovery mode"
*
* Other CSE Type:
* 1. "The board is in recovery mode"
*
* The above conditions summarize that the CSE is in "SOFT TEMP DISABLE" state,
* hence, don't send the EOP command to CSE.
*/
static bool is_cse_eop_supported(void)
{
/* CSE Lite */
if ((CONFIG(SOC_INTEL_CSE_LITE_SKU) && vboot_recovery_mode_enabled()) &&
cse_is_hfs1_com_soft_temp_disable()) {
printk(BIOS_INFO, "HECI: coreboot in recovery mode; found CSE Lite in expected "
"SOFT TEMP DISABLE state, skipping EOP\n");
return false;
}
/* Other CSE Type */
if (cse_is_hfs1_com_soft_temp_disable()) {
printk(BIOS_INFO, "HECI: coreboot in recovery mode; found CSE in expected "
"SOFT TEMP DISABLE state, skipping EOP\n");
return false;
}
return true;
}
void cse_send_end_of_post(void)
{
if (!is_cse_eop_supported())
return;
return do_send_end_of_post(!CONFIG(SOC_INTEL_CSE_SEND_EOP_ASYNC));
}
static void send_cse_eop_with_late_finalize(void *unused)
{
if (CONFIG(SOC_INTEL_CSE_SEND_EOP_BY_PAYLOAD)) {
printk(BIOS_INFO, "Deferring CSE EOP to payload\n");
return;
}
if (is_cse_eop_supported())
do_send_end_of_post(true);
if (CONFIG(SOC_INTEL_CSE_SEND_EOP_LATE) ||
CONFIG(SOC_INTEL_CSE_SEND_EOP_ASYNC))
cse_late_finalize();
}
/*
* Ideally, to give coreboot maximum flexibility, sending EOP would be done as
* late possible. If HECI_DISABLE_USING_SMM is selected, then sending EOP must
* be performed before the HECI bus is disabled, so these boards use
* BS_PAYLOAD_LOAD, which happens before the HECI_DISABLE_USING_SMM Kconfig takes
* effect (EOP is sent using the HECI bus).
* Otherwise, EOP can be pushed a little later, and can be performed in
* BS_PAYLOAD_BOOT instead.
*/
#if !CONFIG(HECI_DISABLE_USING_SMM)
BOOT_STATE_INIT_ENTRY(BS_PAYLOAD_BOOT, BS_ON_ENTRY, send_cse_eop_with_late_finalize, NULL);
#else
BOOT_STATE_INIT_ENTRY(BS_PAYLOAD_LOAD, BS_ON_ENTRY, send_cse_eop_with_late_finalize, NULL);
#endif