summaryrefslogtreecommitdiffstats
path: root/src/vendorcode/google/chromeos/cr50_enable_update.c
blob: fa562a2403a69597f886b45c7a6d3294066cf33c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
/* SPDX-License-Identifier: GPL-2.0-only */
/* This file is part of the coreboot project. */

#include <bootstate.h>
#include <console/console.h>
#include <ec/google/chromeec/ec.h>
#include <elog.h>
#include <halt.h>
#include <security/tpm/tss.h>
#include <vb2_api.h>
#include <security/vboot/vboot_common.h>
#include <vendorcode/google/chromeos/chromeos.h>

#define CR50_RESET_DELAY_MS 1000

void __weak mainboard_prepare_cr50_reset(void) {}

/**
 * Check if the Cr50 TPM state requires a chip reset of the Cr50 device.
 *
 * Returns 0 if the Cr50 TPM state is good or if the TPM_MODE command is
 * unsupported.  Returns 1 if the Cr50 requires a reset.
 */
static int cr50_is_reset_needed(void)
{
	int ret;
	uint8_t tpm_mode;

	ret = tlcl_cr50_get_tpm_mode(&tpm_mode);

	if (ret == TPM_E_NO_SUCH_COMMAND) {
		printk(BIOS_INFO,
		       "Cr50 does not support TPM mode command\n");
		/* Older Cr50 firmware, assume no Cr50 reset is required */
		return 0;
	}

	if (ret == TPM_E_MUST_REBOOT) {
		/*
		 * Cr50 indicated a reboot is required to restore TPM
		 * functionality.
		 */
		return 1;
	} else if (ret != TPM_SUCCESS)	{
		/* TPM command failed, continue booting. */
		printk(BIOS_ERR,
		       "ERROR: Attempt to get CR50 TPM mode failed: %x\n", ret);
		return 0;
	}

	/*
	 * If the TPM mode is not enabled-tentative, then the TPM mode is locked
	 * and cannot be changed.  Perform a Cr50 reset because vboot may need
	 * to disable TPM as part of booting an untrusted OS.
	 *
	 * This is not an expected state, as the Cr50 always sets the TPM mode
	 * to TPM_MODE_ENABLED_TENTATIVE during any TPM reset action.
	 */
	if (tpm_mode != TPM_MODE_ENABLED_TENTATIVE) {
		printk(BIOS_NOTICE,
		       "NOTICE: Unexpected Cr50 TPM mode (%d). "
		       "A Cr50 reset is required.\n", tpm_mode);
		return 1;
	}

	/* If TPM state is okay, no reset needed. */
	return 0;
}

static void enable_update(void *unused)
{
	int ret;
	int cr50_reset_reqd = 0;
	uint8_t num_restored_headers;

	/* Nothing to do on recovery mode. */
	if (vboot_recovery_mode_enabled())
		return;

	ret = tlcl_lib_init();

	if (ret != VB2_SUCCESS) {
		printk(BIOS_ERR,
		       "ERROR: tlcl_lib_init() failed for CR50 update: %x\n",
		       ret);
		return;
	}

	/* Reboot in 1000 ms if necessary. */
	ret = tlcl_cr50_enable_update(CR50_RESET_DELAY_MS,
				      &num_restored_headers);

	if (ret != TPM_SUCCESS) {
		printk(BIOS_ERR,
		       "ERROR: Attempt to enable CR50 update failed: %x\n",
		       ret);
		return;
	}

	if (!num_restored_headers) {
		/* If no headers were restored there is no reset forthcoming due
		 * to a Cr50 firmware update.  Also check if the Cr50 TPM mode
		 * requires a reset.
		 *
		 * TODO: to eliminate a TPM command during every boot, the
		 * TURN_UPDATE_ON command could be enhanced/replaced in the Cr50
		 * firmware to perform the TPM mode/key-ladder check in addition
		 * to the FW version check.
		 */

		/*
		 * If the Cr50 doesn't requires a reset, continue booting.
		 */
		cr50_reset_reqd = cr50_is_reset_needed();
		if (!cr50_reset_reqd)
			return;

		printk(BIOS_INFO, "Waiting for CR50 reset to enable TPM.\n");
		elog_add_event(ELOG_TYPE_CR50_NEED_RESET);
	} else {
		printk(BIOS_INFO,
		       "Waiting for CR50 reset to pick up update.\n");
		elog_add_event(ELOG_TYPE_CR50_UPDATE);
	}

	/* Give mainboard a chance to take action */
	mainboard_prepare_cr50_reset();

	/* clear current post code avoid chatty eventlog on subsequent boot*/
	post_code(0);

	/*
	 * Older Cr50 firmware doesn't support the timeout parameter for the
	 * immediate reset request, so the reset request must be sent after
	 * the mainboard specific code runs.
	 */
	if (cr50_reset_reqd) {
		ret = tlcl_cr50_immediate_reset(CR50_RESET_DELAY_MS);

		if (ret != TPM_SUCCESS) {
			/*
			 * Reset request failed due to TPM error, continue
			 * booting but the current boot will likely end up at
			 * the recovery screen.
			 */
			printk(BIOS_ERR,
			       "ERROR: Attempt to reset CR50 failed: %x\n",
			       ret);
			return;
		}
	}

	if (CONFIG(POWER_OFF_ON_CR50_UPDATE))
		poweroff();
	halt();
}
BOOT_STATE_INIT_ENTRY(BS_PAYLOAD_LOAD, BS_ON_ENTRY, enable_update, NULL);