summaryrefslogtreecommitdiff
path: root/src/vendorcode/google/chromeos/cr50_enable_update.c
blob: 660fe2e86fc9e1ccfd97a6623d055390499699b1 (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
158
159
160
/*
 * This file is part of the coreboot project.
 *
 * Copyright 2017 Google Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#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 C50_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 was reset.
 */
static int cr50_reset_if_needed(uint16_t timeout_ms)
{
	int ret;
	int cr50_must_reset = 0;
	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.
		 */
		cr50_must_reset = 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);
		cr50_must_reset = 1;
	}

	/* If TPM state is okay, no reset needed. */
	if (!cr50_must_reset)
		return 0;

	ret = tlcl_cr50_immediate_reset(timeout_ms);

	if (ret != TPM_SUCCESS) {
		/* TPM command failed, continue booting. */
		printk(BIOS_ERR,
		       "ERROR: Attempt to reset CR50 failed: %x\n",
		       ret);
		return 0;
	}

	/* Cr50 is about to be reset, caller needs to prepare */
	return 1;
}

static void enable_update(void *unused)
{
	int ret;
	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(C50_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 was not reset, continue booting.
		 */
		if (!cr50_reset_if_needed(C50_RESET_DELAY_MS))
			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);

	if (IS_ENABLED(CONFIG_POWER_OFF_ON_CR50_UPDATE))
		poweroff();
	halt();
}
BOOT_STATE_INIT_ENTRY(BS_PAYLOAD_LOAD, BS_ON_ENTRY, enable_update, NULL);