xref: /aosp_15_r20/external/coreboot/src/soc/intel/common/block/cse/cse_eop.c (revision b9411a12aaaa7e1e6a6fb7c5e057f44ee179a49c)
1 /* SPDX-License-Identifier: GPL-2.0-only */
2 
3 #include <acpi/acpi.h>
4 #include <bootstate.h>
5 #include <console/console.h>
6 #include <intelblocks/cse.h>
7 #include <intelblocks/pmc_ipc.h>
8 #include <security/vboot/vboot_common.h>
9 #include <soc/intel/common/reset.h>
10 #include <soc/pci_devs.h>
11 #include <timestamp.h>
12 #include <types.h>
13 
14 #define CSE_MAX_RETRY_CMD	3
15 
16 enum cse_cmd_result {
17 	CSE_CMD_RESULT_GLOBAL_RESET_REQUESTED,
18 	CSE_CMD_RESULT_SUCCESS,
19 	CSE_CMD_RESULT_ERROR,
20 	CSE_CMD_RESULT_DISABLED,
21 	CSE_CMD_RESULT_RETRY,
22 };
23 
decode_heci_send_receive_error(enum cse_tx_rx_status ret)24 static enum cse_cmd_result decode_heci_send_receive_error(enum cse_tx_rx_status ret)
25 {
26 	switch (ret) {
27 	case CSE_TX_ERR_CSE_NOT_READY:
28 	case CSE_RX_ERR_CSE_NOT_READY:
29 	case CSE_RX_ERR_RESP_LEN_MISMATCH:
30 	case CSE_RX_ERR_TIMEOUT:
31 		return CSE_CMD_RESULT_RETRY;
32 	default:
33 		return CSE_CMD_RESULT_ERROR;
34 	}
35 }
36 
cse_disable_mei_bus(void)37 static enum cse_cmd_result cse_disable_mei_bus(void)
38 {
39 	struct bus_disable_message {
40 		uint8_t command;
41 		uint8_t reserved[3];
42 	} __packed msg = {
43 		.command = MEI_BUS_DISABLE_COMMAND,
44 	};
45 	struct bus_disable_resp {
46 		uint8_t command;
47 		uint8_t status;
48 		uint8_t reserved[2];
49 	} __packed reply = {};
50 
51 	size_t reply_sz = sizeof(reply);
52 	enum cse_tx_rx_status ret;
53 
54 	printk(BIOS_DEBUG, "HECI, Sending MEI BIOS DISABLE command\n");
55 	ret = heci_send_receive(&msg, sizeof(msg), &reply, &reply_sz, HECI_MEI_ADDR);
56 
57 	if (ret) {
58 		printk(BIOS_ERR, "HECI: Failed to Disable MEI bus\n");
59 		return decode_heci_send_receive_error(ret);
60 	}
61 
62 	if (reply.status) {
63 		printk(BIOS_ERR, "HECI: MEI_Bus_Disable Failed (status: %d)\n", reply.status);
64 		return CSE_CMD_RESULT_ERROR;
65 	}
66 
67 	return CSE_CMD_RESULT_SUCCESS;
68 }
69 
cse_receive_eop(void)70 static enum cse_cmd_result cse_receive_eop(void)
71 {
72 	enum {
73 		EOP_REQUESTED_ACTION_CONTINUE = 0,
74 		EOP_REQUESTED_ACTION_GLOBAL_RESET = 1,
75 	};
76 	enum cse_tx_rx_status ret;
77 	struct end_of_post_resp {
78 		struct mkhi_hdr hdr;
79 		uint32_t requested_actions;
80 	} __packed resp = {};
81 	size_t resp_size = sizeof(resp);
82 
83 	ret = heci_receive(&resp, &resp_size);
84 	if (ret)
85 		return decode_heci_send_receive_error(ret);
86 
87 	if (resp.hdr.group_id != MKHI_GROUP_ID_GEN ||
88 	    resp.hdr.command != MKHI_END_OF_POST) {
89 		printk(BIOS_ERR, "HECI: EOP Unexpected response group or command.\n");
90 		if (CONFIG(SOC_INTEL_CSE_SEND_EOP_ASYNC))
91 			printk(BIOS_ERR, "HECI: It could be a HECI command conflict.\n");
92 		return CSE_CMD_RESULT_ERROR;
93 	}
94 
95 	if (resp.hdr.result) {
96 		printk(BIOS_ERR, "HECI: EOP Resp Failed: %u\n", resp.hdr.result);
97 		return CSE_CMD_RESULT_ERROR;
98 	}
99 
100 	printk(BIOS_INFO, "CSE: EOP requested action: ");
101 
102 	switch (resp.requested_actions) {
103 	case EOP_REQUESTED_ACTION_GLOBAL_RESET:
104 		printk(BIOS_INFO, "global reset\n");
105 		return CSE_CMD_RESULT_GLOBAL_RESET_REQUESTED;
106 	case EOP_REQUESTED_ACTION_CONTINUE:
107 		printk(BIOS_INFO, "continue boot\n");
108 		return CSE_CMD_RESULT_SUCCESS;
109 	default:
110 		printk(BIOS_INFO, "unknown %u\n", resp.requested_actions);
111 		return CSE_CMD_RESULT_ERROR;
112 	}
113 }
114 
cse_send_eop(void)115 static enum cse_cmd_result cse_send_eop(void)
116 {
117 	enum cse_tx_rx_status ret;
118 	struct end_of_post_msg {
119 		struct mkhi_hdr hdr;
120 	} __packed msg = {
121 		.hdr = {
122 			.group_id = MKHI_GROUP_ID_GEN,
123 			.command = MKHI_END_OF_POST,
124 		},
125 	};
126 
127 	/*
128 	 * Prerequisites:
129 	 * 1) HFSTS1 CWS is Normal
130 	 * 2) HFSTS1 COM is Normal
131 	 * 3) Only sent after DID (accomplished by compiling this into ramstage)
132 	 */
133 
134 	if (cse_is_hfs1_com_soft_temp_disable()) {
135 		printk(BIOS_ERR, "HECI: Prerequisites not met for sending EOP\n");
136 		if (CONFIG(SOC_INTEL_CSE_LITE_SKU))
137 			return CSE_CMD_RESULT_ERROR;
138 		return CSE_CMD_RESULT_DISABLED;
139 	}
140 
141 	if (!cse_is_hfs1_cws_normal() || !cse_is_hfs1_com_normal()) {
142 		printk(BIOS_ERR, "HECI: Prerequisites not met for sending EOP\n");
143 		return CSE_CMD_RESULT_ERROR;
144 	}
145 
146 	printk(BIOS_INFO, "HECI: Sending End-of-Post\n");
147 
148 	ret = heci_send(&msg, sizeof(msg), BIOS_HOST_ADDR, HECI_MKHI_ADDR);
149 	if (ret)
150 		return decode_heci_send_receive_error(ret);
151 
152 	return CSE_CMD_RESULT_SUCCESS;
153 }
154 
cse_send_and_receive_eop(void)155 static enum cse_cmd_result cse_send_and_receive_eop(void)
156 {
157 	enum cse_cmd_result ret;
158 
159 	ret = cse_send_eop();
160 	if (ret != CSE_CMD_RESULT_SUCCESS)
161 		return ret;
162 
163 	return cse_receive_eop();
164 }
165 
cse_send_cmd_retries(enum cse_cmd_result (* cse_send_command)(void))166 static enum cse_cmd_result cse_send_cmd_retries(enum cse_cmd_result (*cse_send_command)(void))
167 {
168 	size_t retry;
169 	enum cse_cmd_result ret;
170 	for (retry = 0; retry < CSE_MAX_RETRY_CMD; retry++) {
171 		ret = cse_send_command();
172 		if (ret != CSE_CMD_RESULT_RETRY)
173 			break;
174 	}
175 	return ret;
176 }
177 
178 /*
179  * On EOP error, the BIOS is required to send an MEI bus disable message to the
180  * CSE, followed by disabling all MEI devices. After successfully completing
181  * this, it is safe to boot.
182  */
cse_handle_eop_error(void)183 static void cse_handle_eop_error(void)
184 {
185 	if (cse_send_cmd_retries(cse_disable_mei_bus))
186 		die("Failed to disable MEI bus while recovering from EOP error\n"
187 		    "Preventing system from booting into an insecure state.\n");
188 
189 	if (!cse_disable_mei_devices())
190 		die("Error disabling MEI devices while recovering from EOP error\n"
191 		    "Preventing system from booting into an insecure state.\n");
192 }
193 
handle_cse_eop_result(enum cse_cmd_result result)194 static void handle_cse_eop_result(enum cse_cmd_result result)
195 {
196 	switch (result) {
197 	case CSE_CMD_RESULT_GLOBAL_RESET_REQUESTED:
198 		printk(BIOS_INFO, "CSE requested global reset in EOP response, resetting...\n");
199 		do_global_reset();
200 		break;
201 	case CSE_CMD_RESULT_SUCCESS:
202 		printk(BIOS_INFO, "CSE EOP successful, continuing boot\n");
203 		break;
204 	case CSE_CMD_RESULT_DISABLED:
205 		printk(BIOS_INFO, "CSE is disabled, continuing boot\n");
206 		break;
207 	case CSE_CMD_RESULT_ERROR: /* fallthrough */
208 	default:
209 		printk(BIOS_ERR, "Failed to send EOP to CSE, %d\n", result);
210 		/* For vboot, trigger recovery mode if applicable, as there is
211 		   likely something very broken in this case. */
212 		if (CONFIG(VBOOT) && !vboot_recovery_mode_enabled())
213 			cse_trigger_vboot_recovery(CSE_EOP_FAIL);
214 
215 		/* In non-vboot builds or recovery mode, follow the BWG in order
216 		   to continue to boot securely. */
217 		cse_handle_eop_error();
218 		break;
219 	}
220 }
221 
do_send_end_of_post(bool wait_for_completion)222 static void do_send_end_of_post(bool wait_for_completion)
223 {
224 	static bool eop_sent = false, eop_complete = false;
225 	enum cse_cmd_result ret;
226 
227 	if (eop_complete) {
228 		printk(BIOS_WARNING, "EOP already sent\n");
229 		return;
230 	}
231 
232 	if (acpi_get_sleep_type() == ACPI_S3) {
233 		printk(BIOS_INFO, "Skip sending EOP during S3 resume\n");
234 		return;
235 	}
236 
237 	/*
238 	 * If CSE is already hidden then accessing CSE registers would be wrong and will
239 	 * receive junk, hence, return as CSE is already disabled.
240 	 */
241 	if (!is_cse_enabled()) {
242 		printk(BIOS_DEBUG, "CSE is disabled, cannot send End-of-Post (EOP) message\n");
243 		return;
244 	}
245 
246 	if (!eop_sent) {
247 		set_cse_device_state(PCH_DEVFN_CSE, DEV_ACTIVE);
248 		timestamp_add_now(TS_ME_END_OF_POST_START);
249 		ret = cse_send_cmd_retries(cse_send_eop);
250 		if (ret == CSE_CMD_RESULT_SUCCESS)
251 			eop_sent = true;
252 	}
253 
254 	if (!wait_for_completion)
255 		return;
256 
257 	set_cse_device_state(PCH_DEVFN_CSE, DEV_ACTIVE);
258 	ret = cse_receive_eop();
259 	if (ret != CSE_CMD_RESULT_SUCCESS) {
260 		ret = cse_send_cmd_retries(cse_send_and_receive_eop);
261 		handle_cse_eop_result(ret);
262 	}
263 	timestamp_add_now(TS_ME_END_OF_POST_END);
264 
265 	set_cse_device_state(PCH_DEVFN_CSE, DEV_IDLE);
266 
267 	eop_complete = true;
268 }
269 
270 /*
271  * Don't send EOP if the following conditions are met:
272  * CSE Lite:
273  * 1. "The platform is running CSE-Lite SKU" AND
274  * 2. 'The CSE is running the RO FW" AND
275  * 3. "The board is in recovery mode"
276  *
277  * Other CSE Type:
278  * 1. "The board is in recovery mode"
279  *
280  * The above conditions summarize that the CSE is in "SOFT TEMP DISABLE" state,
281  * hence, don't send the EOP command to CSE.
282  */
is_cse_eop_supported(void)283 static bool is_cse_eop_supported(void)
284 {
285 	/* CSE Lite */
286 	if ((CONFIG(SOC_INTEL_CSE_LITE_SKU) && vboot_recovery_mode_enabled()) &&
287 		cse_is_hfs1_com_soft_temp_disable()) {
288 		printk(BIOS_INFO, "HECI: coreboot in recovery mode; found CSE Lite in expected "
289 		       "SOFT TEMP DISABLE state, skipping EOP\n");
290 		return false;
291 	}
292 	/* Other CSE Type */
293 	if (cse_is_hfs1_com_soft_temp_disable()) {
294 		printk(BIOS_INFO, "HECI: coreboot in recovery mode; found CSE in expected "
295 		       "SOFT TEMP DISABLE state, skipping EOP\n");
296 		return false;
297 	}
298 
299 	return true;
300 }
301 
cse_send_end_of_post(void)302 void cse_send_end_of_post(void)
303 {
304 	if (!is_cse_eop_supported())
305 		return;
306 
307 	return do_send_end_of_post(!CONFIG(SOC_INTEL_CSE_SEND_EOP_ASYNC));
308 }
309 
send_cse_eop_with_late_finalize(void * unused)310 static void send_cse_eop_with_late_finalize(void *unused)
311 {
312 	if (CONFIG(SOC_INTEL_CSE_SEND_EOP_BY_PAYLOAD)) {
313 		printk(BIOS_INFO, "Deferring CSE EOP to payload\n");
314 		return;
315 	}
316 
317 	if (is_cse_eop_supported())
318 		do_send_end_of_post(true);
319 
320 	if (CONFIG(SOC_INTEL_CSE_SEND_EOP_LATE) ||
321 	    CONFIG(SOC_INTEL_CSE_SEND_EOP_ASYNC))
322 		cse_late_finalize();
323 }
324 
325 /*
326  * Ideally, to give coreboot maximum flexibility, sending EOP would be done as
327  * late possible. If HECI_DISABLE_USING_SMM is selected, then sending EOP must
328  * be performed before the HECI bus is disabled, so these boards use
329  * BS_PAYLOAD_LOAD, which happens before the HECI_DISABLE_USING_SMM Kconfig takes
330  * effect (EOP is sent using the HECI bus).
331  * Otherwise, EOP can be pushed a little later, and can be performed in
332  * BS_PAYLOAD_BOOT instead.
333  */
334 #if !CONFIG(HECI_DISABLE_USING_SMM)
335 BOOT_STATE_INIT_ENTRY(BS_PAYLOAD_BOOT, BS_ON_ENTRY, send_cse_eop_with_late_finalize, NULL);
336 #else
337 BOOT_STATE_INIT_ENTRY(BS_PAYLOAD_LOAD, BS_ON_ENTRY, send_cse_eop_with_late_finalize, NULL);
338 #endif
339