]>
Commit | Line | Data |
---|---|---|
ed3f5a30 SG |
1 | /* |
2 | * Copyright (c) 2013 Google, Inc | |
3 | * | |
4 | * SPDX-License-Identifier: GPL-2.0+ | |
5 | */ | |
6 | ||
7 | #include <common.h> | |
21baf15b SG |
8 | #include <dm.h> |
9 | #include <tpm.h> | |
ed3f5a30 SG |
10 | #include <asm/state.h> |
11 | #include <asm/unaligned.h> | |
12 | #include <linux/crc8.h> | |
13 | ||
14 | /* TPM NVRAM location indices. */ | |
15 | #define FIRMWARE_NV_INDEX 0x1007 | |
16 | #define KERNEL_NV_INDEX 0x1008 | |
17 | ||
18 | #define NV_DATA_PUBLIC_PERMISSIONS_OFFSET 60 | |
19 | ||
20 | /* Kernel TPM space - KERNEL_NV_INDEX, locked with physical presence */ | |
21 | #define ROLLBACK_SPACE_KERNEL_VERSION 2 | |
22 | #define ROLLBACK_SPACE_KERNEL_UID 0x4752574C /* 'GRWL' */ | |
23 | ||
24 | struct rollback_space_kernel { | |
25 | /* Struct version, for backwards compatibility */ | |
26 | uint8_t struct_version; | |
27 | /* Unique ID to detect space redefinition */ | |
28 | uint32_t uid; | |
29 | /* Kernel versions */ | |
30 | uint32_t kernel_versions; | |
31 | /* Reserved for future expansion */ | |
32 | uint8_t reserved[3]; | |
33 | /* Checksum (v2 and later only) */ | |
34 | uint8_t crc8; | |
35 | } __packed rollback_space_kernel; | |
36 | ||
37 | /* | |
38 | * These numbers derive from adding the sizes of command fields as shown in | |
39 | * the TPM commands manual. | |
40 | */ | |
41 | #define TPM_REQUEST_HEADER_LENGTH 10 | |
42 | #define TPM_RESPONSE_HEADER_LENGTH 10 | |
43 | ||
44 | /* These are the different non-volatile spaces that we emulate */ | |
45 | enum { | |
46 | NV_GLOBAL_LOCK, | |
47 | NV_SEQ_FIRMWARE, | |
48 | NV_SEQ_KERNEL, | |
49 | NV_SEQ_COUNT, | |
50 | }; | |
51 | ||
52 | /* Size of each non-volatile space */ | |
53 | #define NV_DATA_SIZE 0x20 | |
54 | ||
55 | /* | |
56 | * Information about our TPM emulation. This is preserved in the sandbox | |
57 | * state file if enabled. | |
58 | */ | |
59 | static struct tpm_state { | |
60 | uint8_t nvdata[NV_SEQ_COUNT][NV_DATA_SIZE]; | |
21baf15b | 61 | } g_state; |
ed3f5a30 SG |
62 | |
63 | /** | |
64 | * sandbox_tpm_read_state() - read the sandbox EC state from the state file | |
65 | * | |
66 | * If data is available, then blob and node will provide access to it. If | |
67 | * not this function sets up an empty TPM. | |
68 | * | |
69 | * @blob: Pointer to device tree blob, or NULL if no data to read | |
70 | * @node: Node offset to read from | |
71 | */ | |
72 | static int sandbox_tpm_read_state(const void *blob, int node) | |
73 | { | |
74 | const char *prop; | |
75 | int len; | |
76 | int i; | |
77 | ||
78 | if (!blob) | |
79 | return 0; | |
80 | ||
81 | for (i = 0; i < NV_SEQ_COUNT; i++) { | |
82 | char prop_name[20]; | |
83 | ||
84 | sprintf(prop_name, "nvdata%d", i); | |
85 | prop = fdt_getprop(blob, node, prop_name, &len); | |
86 | if (prop && len == NV_DATA_SIZE) | |
21baf15b | 87 | memcpy(g_state.nvdata[i], prop, NV_DATA_SIZE); |
ed3f5a30 SG |
88 | } |
89 | ||
90 | return 0; | |
91 | } | |
92 | ||
93 | /** | |
94 | * cros_ec_write_state() - Write out our state to the state file | |
95 | * | |
96 | * The caller will ensure that there is a node ready for the state. The node | |
97 | * may already contain the old state, in which case it is overridden. | |
98 | * | |
99 | * @blob: Device tree blob holding state | |
100 | * @node: Node to write our state into | |
101 | */ | |
102 | static int sandbox_tpm_write_state(void *blob, int node) | |
103 | { | |
104 | int i; | |
105 | ||
106 | /* | |
107 | * We are guaranteed enough space to write basic properties. | |
108 | * We could use fdt_add_subnode() to put each set of data in its | |
109 | * own node - perhaps useful if we add access informaiton to each. | |
110 | */ | |
111 | for (i = 0; i < NV_SEQ_COUNT; i++) { | |
112 | char prop_name[20]; | |
113 | ||
114 | sprintf(prop_name, "nvdata%d", i); | |
21baf15b | 115 | fdt_setprop(blob, node, prop_name, g_state.nvdata[i], |
ed3f5a30 SG |
116 | NV_DATA_SIZE); |
117 | } | |
118 | ||
119 | return 0; | |
120 | } | |
121 | ||
122 | SANDBOX_STATE_IO(sandbox_tpm, "google,sandbox-tpm", sandbox_tpm_read_state, | |
123 | sandbox_tpm_write_state); | |
124 | ||
125 | static int index_to_seq(uint32_t index) | |
126 | { | |
127 | switch (index) { | |
128 | case FIRMWARE_NV_INDEX: | |
129 | return NV_SEQ_FIRMWARE; | |
130 | case KERNEL_NV_INDEX: | |
131 | return NV_SEQ_KERNEL; | |
132 | case 0: | |
133 | return NV_GLOBAL_LOCK; | |
134 | } | |
135 | ||
136 | printf("Invalid nv index %#x\n", index); | |
137 | return -1; | |
138 | } | |
139 | ||
21baf15b SG |
140 | static int sandbox_tpm_xfer(struct udevice *dev, const uint8_t *sendbuf, |
141 | size_t send_size, uint8_t *recvbuf, | |
142 | size_t *recv_len) | |
ed3f5a30 | 143 | { |
21baf15b | 144 | struct tpm_state *tpm = dev_get_priv(dev); |
ed3f5a30 SG |
145 | uint32_t code, index, length, type; |
146 | uint8_t *data; | |
147 | int seq; | |
148 | ||
149 | code = get_unaligned_be32(sendbuf + sizeof(uint16_t) + | |
150 | sizeof(uint32_t)); | |
151 | printf("tpm: %zd bytes, recv_len %zd, cmd = %x\n", send_size, | |
152 | *recv_len, code); | |
153 | print_buffer(0, sendbuf, 1, send_size, 0); | |
154 | switch (code) { | |
155 | case 0x65: /* get flags */ | |
156 | type = get_unaligned_be32(sendbuf + 14); | |
157 | switch (type) { | |
158 | case 4: | |
159 | index = get_unaligned_be32(sendbuf + 18); | |
160 | printf("Get flags index %#02x\n", index); | |
161 | *recv_len = 22; | |
162 | memset(recvbuf, '\0', *recv_len); | |
163 | put_unaligned_be32(22, recvbuf + | |
164 | TPM_RESPONSE_HEADER_LENGTH); | |
165 | data = recvbuf + TPM_RESPONSE_HEADER_LENGTH + | |
166 | sizeof(uint32_t); | |
167 | switch (index) { | |
168 | case FIRMWARE_NV_INDEX: | |
169 | break; | |
170 | case KERNEL_NV_INDEX: | |
171 | /* TPM_NV_PER_PPWRITE */ | |
172 | put_unaligned_be32(1, data + | |
173 | NV_DATA_PUBLIC_PERMISSIONS_OFFSET); | |
174 | break; | |
175 | } | |
176 | break; | |
177 | case 0x11: /* TPM_CAP_NV_INDEX */ | |
178 | index = get_unaligned_be32(sendbuf + 18); | |
179 | printf("Get cap nv index %#02x\n", index); | |
180 | put_unaligned_be32(22, recvbuf + | |
181 | TPM_RESPONSE_HEADER_LENGTH); | |
182 | break; | |
183 | default: | |
184 | printf(" ** Unknown 0x65 command type %#02x\n", | |
185 | type); | |
186 | return -1; | |
187 | } | |
188 | break; | |
189 | case 0xcd: /* nvwrite */ | |
190 | index = get_unaligned_be32(sendbuf + 10); | |
191 | length = get_unaligned_be32(sendbuf + 18); | |
192 | seq = index_to_seq(index); | |
193 | if (seq < 0) | |
194 | return -1; | |
195 | printf("tpm: nvwrite index=%#02x, len=%#02x\n", index, length); | |
2c30af8f | 196 | memcpy(&tpm->nvdata[seq], sendbuf + 22, length); |
ed3f5a30 SG |
197 | *recv_len = 12; |
198 | memset(recvbuf, '\0', *recv_len); | |
199 | break; | |
200 | case 0xcf: /* nvread */ | |
201 | index = get_unaligned_be32(sendbuf + 10); | |
202 | length = get_unaligned_be32(sendbuf + 18); | |
203 | seq = index_to_seq(index); | |
204 | if (seq < 0) | |
205 | return -1; | |
206 | printf("tpm: nvread index=%#02x, len=%#02x\n", index, length); | |
207 | *recv_len = TPM_RESPONSE_HEADER_LENGTH + sizeof(uint32_t) + | |
208 | length; | |
209 | memset(recvbuf, '\0', *recv_len); | |
210 | put_unaligned_be32(length, recvbuf + | |
211 | TPM_RESPONSE_HEADER_LENGTH); | |
212 | if (seq == NV_SEQ_KERNEL) { | |
213 | struct rollback_space_kernel rsk; | |
214 | ||
215 | data = recvbuf + TPM_RESPONSE_HEADER_LENGTH + | |
216 | sizeof(uint32_t); | |
7e019daf | 217 | memset(&rsk, 0, sizeof(struct rollback_space_kernel)); |
ed3f5a30 SG |
218 | rsk.struct_version = 2; |
219 | rsk.uid = ROLLBACK_SPACE_KERNEL_UID; | |
456ecd08 | 220 | rsk.crc8 = crc8(0, (unsigned char *)&rsk, |
ed3f5a30 SG |
221 | offsetof(struct rollback_space_kernel, |
222 | crc8)); | |
223 | memcpy(data, &rsk, sizeof(rsk)); | |
224 | } else { | |
225 | memcpy(recvbuf + TPM_RESPONSE_HEADER_LENGTH + | |
226 | sizeof(uint32_t), &tpm->nvdata[seq], length); | |
227 | } | |
228 | break; | |
229 | case 0x14: /* tpm extend */ | |
230 | case 0x15: /* pcr read */ | |
231 | case 0x5d: /* force clear */ | |
232 | case 0x6f: /* physical enable */ | |
233 | case 0x72: /* physical set deactivated */ | |
234 | case 0x99: /* startup */ | |
235 | case 0x4000000a: /* assert physical presence */ | |
236 | *recv_len = 12; | |
237 | memset(recvbuf, '\0', *recv_len); | |
238 | break; | |
239 | default: | |
240 | printf("Unknown tpm command %02x\n", code); | |
241 | return -1; | |
242 | } | |
243 | ||
244 | return 0; | |
245 | } | |
246 | ||
21baf15b | 247 | static int sandbox_tpm_get_desc(struct udevice *dev, char *buf, int size) |
ed3f5a30 | 248 | { |
21baf15b SG |
249 | if (size < 15) |
250 | return -ENOSPC; | |
251 | ||
252 | return snprintf(buf, size, "sandbox TPM"); | |
253 | } | |
254 | ||
255 | static int sandbox_tpm_probe(struct udevice *dev) | |
256 | { | |
257 | struct tpm_state *tpm = dev_get_priv(dev); | |
258 | ||
259 | memcpy(tpm, &g_state, sizeof(*tpm)); | |
260 | ||
ed3f5a30 SG |
261 | return 0; |
262 | } | |
263 | ||
21baf15b | 264 | static int sandbox_tpm_open(struct udevice *dev) |
ed3f5a30 | 265 | { |
ed3f5a30 SG |
266 | return 0; |
267 | } | |
268 | ||
21baf15b | 269 | static int sandbox_tpm_close(struct udevice *dev) |
ed3f5a30 | 270 | { |
ed3f5a30 SG |
271 | return 0; |
272 | } | |
21baf15b SG |
273 | |
274 | static const struct tpm_ops sandbox_tpm_ops = { | |
275 | .open = sandbox_tpm_open, | |
276 | .close = sandbox_tpm_close, | |
277 | .get_desc = sandbox_tpm_get_desc, | |
278 | .xfer = sandbox_tpm_xfer, | |
279 | }; | |
280 | ||
281 | static const struct udevice_id sandbox_tpm_ids[] = { | |
282 | { .compatible = "google,sandbox-tpm" }, | |
283 | { } | |
284 | }; | |
285 | ||
286 | U_BOOT_DRIVER(sandbox_tpm) = { | |
287 | .name = "sandbox_tpm", | |
288 | .id = UCLASS_TPM, | |
289 | .of_match = sandbox_tpm_ids, | |
290 | .ops = &sandbox_tpm_ops, | |
291 | .probe = sandbox_tpm_probe, | |
292 | .priv_auto_alloc_size = sizeof(struct tpm_state), | |
293 | }; |