| /* 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 (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 |