]>
Commit | Line | Data |
---|---|---|
09e57483 SL |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Copyright (C) Microsoft Corporation | |
4 | * | |
5 | * Implements a firmware TPM as described here: | |
6 | * https://www.microsoft.com/en-us/research/publication/ftpm-software-implementation-tpm-chip/ | |
7 | * | |
8 | * A reference implementation is available here: | |
9 | * https://github.com/microsoft/ms-tpm-20-ref/tree/master/Samples/ARM32-FirmwareTPM/optee_ta/fTPM | |
10 | */ | |
11 | ||
12 | #include <linux/acpi.h> | |
13 | #include <linux/of.h> | |
14 | #include <linux/of_platform.h> | |
15 | #include <linux/platform_device.h> | |
16 | #include <linux/tee_drv.h> | |
17 | #include <linux/tpm.h> | |
18 | #include <linux/uuid.h> | |
19 | ||
20 | #include "tpm.h" | |
21 | #include "tpm_ftpm_tee.h" | |
22 | ||
23 | /* | |
24 | * TA_FTPM_UUID: BC50D971-D4C9-42C4-82CB-343FB7F37896 | |
25 | * | |
26 | * Randomly generated, and must correspond to the GUID on the TA side. | |
27 | * Defined here in the reference implementation: | |
28 | * https://github.com/microsoft/ms-tpm-20-ref/blob/master/Samples/ARM32-FirmwareTPM/optee_ta/fTPM/include/fTPM.h#L42 | |
29 | */ | |
30 | static const uuid_t ftpm_ta_uuid = | |
31 | UUID_INIT(0xBC50D971, 0xD4C9, 0x42C4, | |
32 | 0x82, 0xCB, 0x34, 0x3F, 0xB7, 0xF3, 0x78, 0x96); | |
33 | ||
34 | /** | |
35 | * ftpm_tee_tpm_op_recv - retrieve fTPM response. | |
36 | * @chip: the tpm_chip description as specified in driver/char/tpm/tpm.h. | |
37 | * @buf: the buffer to store data. | |
38 | * @count: the number of bytes to read. | |
39 | * | |
40 | * Return: | |
41 | * In case of success the number of bytes received. | |
42 | * On failure, -errno. | |
43 | */ | |
44 | static int ftpm_tee_tpm_op_recv(struct tpm_chip *chip, u8 *buf, size_t count) | |
45 | { | |
46 | struct ftpm_tee_private *pvt_data = dev_get_drvdata(chip->dev.parent); | |
47 | size_t len; | |
48 | ||
49 | len = pvt_data->resp_len; | |
50 | if (count < len) { | |
51 | dev_err(&chip->dev, | |
52 | "%s: Invalid size in recv: count=%zd, resp_len=%zd\n", | |
53 | __func__, count, len); | |
54 | return -EIO; | |
55 | } | |
56 | ||
57 | memcpy(buf, pvt_data->resp_buf, len); | |
58 | pvt_data->resp_len = 0; | |
59 | ||
60 | return len; | |
61 | } | |
62 | ||
63 | /** | |
64 | * ftpm_tee_tpm_op_send - send TPM commands through the TEE shared memory. | |
65 | * @chip: the tpm_chip description as specified in driver/char/tpm/tpm.h | |
66 | * @buf: the buffer to send. | |
67 | * @len: the number of bytes to send. | |
68 | * | |
69 | * Return: | |
70 | * In case of success, returns 0. | |
71 | * On failure, -errno | |
72 | */ | |
73 | static int ftpm_tee_tpm_op_send(struct tpm_chip *chip, u8 *buf, size_t len) | |
74 | { | |
75 | struct ftpm_tee_private *pvt_data = dev_get_drvdata(chip->dev.parent); | |
76 | size_t resp_len; | |
77 | int rc; | |
78 | u8 *temp_buf; | |
79 | struct tpm_header *resp_header; | |
80 | struct tee_ioctl_invoke_arg transceive_args; | |
81 | struct tee_param command_params[4]; | |
82 | struct tee_shm *shm = pvt_data->shm; | |
83 | ||
84 | if (len > MAX_COMMAND_SIZE) { | |
85 | dev_err(&chip->dev, | |
86 | "%s: len=%zd exceeds MAX_COMMAND_SIZE supported by fTPM TA\n", | |
87 | __func__, len); | |
88 | return -EIO; | |
89 | } | |
90 | ||
91 | memset(&transceive_args, 0, sizeof(transceive_args)); | |
92 | memset(command_params, 0, sizeof(command_params)); | |
93 | pvt_data->resp_len = 0; | |
94 | ||
95 | /* Invoke FTPM_OPTEE_TA_SUBMIT_COMMAND function of fTPM TA */ | |
96 | transceive_args = (struct tee_ioctl_invoke_arg) { | |
97 | .func = FTPM_OPTEE_TA_SUBMIT_COMMAND, | |
98 | .session = pvt_data->session, | |
99 | .num_params = 4, | |
100 | }; | |
101 | ||
102 | /* Fill FTPM_OPTEE_TA_SUBMIT_COMMAND parameters */ | |
103 | command_params[0] = (struct tee_param) { | |
104 | .attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT, | |
105 | .u.memref = { | |
106 | .shm = shm, | |
107 | .size = len, | |
108 | .shm_offs = 0, | |
109 | }, | |
110 | }; | |
111 | ||
112 | temp_buf = tee_shm_get_va(shm, 0); | |
113 | if (IS_ERR(temp_buf)) { | |
114 | dev_err(&chip->dev, "%s: tee_shm_get_va failed for transmit\n", | |
115 | __func__); | |
116 | return PTR_ERR(temp_buf); | |
117 | } | |
118 | memset(temp_buf, 0, (MAX_COMMAND_SIZE + MAX_RESPONSE_SIZE)); | |
119 | memcpy(temp_buf, buf, len); | |
120 | ||
121 | command_params[1] = (struct tee_param) { | |
122 | .attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT, | |
123 | .u.memref = { | |
124 | .shm = shm, | |
125 | .size = MAX_RESPONSE_SIZE, | |
126 | .shm_offs = MAX_COMMAND_SIZE, | |
127 | }, | |
128 | }; | |
129 | ||
130 | rc = tee_client_invoke_func(pvt_data->ctx, &transceive_args, | |
131 | command_params); | |
132 | if ((rc < 0) || (transceive_args.ret != 0)) { | |
133 | dev_err(&chip->dev, "%s: SUBMIT_COMMAND invoke error: 0x%x\n", | |
134 | __func__, transceive_args.ret); | |
135 | return (rc < 0) ? rc : transceive_args.ret; | |
136 | } | |
137 | ||
138 | temp_buf = tee_shm_get_va(shm, command_params[1].u.memref.shm_offs); | |
139 | if (IS_ERR(temp_buf)) { | |
140 | dev_err(&chip->dev, "%s: tee_shm_get_va failed for receive\n", | |
141 | __func__); | |
142 | return PTR_ERR(temp_buf); | |
143 | } | |
144 | ||
145 | resp_header = (struct tpm_header *)temp_buf; | |
146 | resp_len = be32_to_cpu(resp_header->length); | |
147 | ||
148 | /* sanity check resp_len */ | |
149 | if (resp_len < TPM_HEADER_SIZE) { | |
150 | dev_err(&chip->dev, "%s: tpm response header too small\n", | |
151 | __func__); | |
152 | return -EIO; | |
153 | } | |
154 | if (resp_len > MAX_RESPONSE_SIZE) { | |
155 | dev_err(&chip->dev, | |
156 | "%s: resp_len=%zd exceeds MAX_RESPONSE_SIZE\n", | |
157 | __func__, resp_len); | |
158 | return -EIO; | |
159 | } | |
160 | ||
161 | /* sanity checks look good, cache the response */ | |
162 | memcpy(pvt_data->resp_buf, temp_buf, resp_len); | |
163 | pvt_data->resp_len = resp_len; | |
164 | ||
165 | return 0; | |
166 | } | |
167 | ||
168 | static void ftpm_tee_tpm_op_cancel(struct tpm_chip *chip) | |
169 | { | |
170 | /* not supported */ | |
171 | } | |
172 | ||
173 | static u8 ftpm_tee_tpm_op_status(struct tpm_chip *chip) | |
174 | { | |
175 | return 0; | |
176 | } | |
177 | ||
178 | static bool ftpm_tee_tpm_req_canceled(struct tpm_chip *chip, u8 status) | |
179 | { | |
180 | return 0; | |
181 | } | |
182 | ||
183 | static const struct tpm_class_ops ftpm_tee_tpm_ops = { | |
184 | .flags = TPM_OPS_AUTO_STARTUP, | |
185 | .recv = ftpm_tee_tpm_op_recv, | |
186 | .send = ftpm_tee_tpm_op_send, | |
187 | .cancel = ftpm_tee_tpm_op_cancel, | |
188 | .status = ftpm_tee_tpm_op_status, | |
189 | .req_complete_mask = 0, | |
190 | .req_complete_val = 0, | |
191 | .req_canceled = ftpm_tee_tpm_req_canceled, | |
192 | }; | |
193 | ||
194 | /* | |
195 | * Check whether this driver supports the fTPM TA in the TEE instance | |
196 | * represented by the params (ver/data) to this function. | |
197 | */ | |
198 | static int ftpm_tee_match(struct tee_ioctl_version_data *ver, const void *data) | |
199 | { | |
200 | /* | |
201 | * Currently this driver only support GP Complaint OPTEE based fTPM TA | |
202 | */ | |
203 | if ((ver->impl_id == TEE_IMPL_ID_OPTEE) && | |
204 | (ver->gen_caps & TEE_GEN_CAP_GP)) | |
205 | return 1; | |
206 | else | |
207 | return 0; | |
208 | } | |
209 | ||
210 | /** | |
211 | * ftpm_tee_probe - initialize the fTPM | |
212 | * @pdev: the platform_device description. | |
213 | * | |
214 | * Return: | |
215 | * On success, 0. On failure, -errno. | |
216 | */ | |
217 | static int ftpm_tee_probe(struct platform_device *pdev) | |
218 | { | |
219 | int rc; | |
220 | struct tpm_chip *chip; | |
221 | struct device *dev = &pdev->dev; | |
222 | struct ftpm_tee_private *pvt_data = NULL; | |
223 | struct tee_ioctl_open_session_arg sess_arg; | |
224 | ||
225 | pvt_data = devm_kzalloc(dev, sizeof(struct ftpm_tee_private), | |
226 | GFP_KERNEL); | |
227 | if (!pvt_data) | |
228 | return -ENOMEM; | |
229 | ||
230 | dev_set_drvdata(dev, pvt_data); | |
231 | ||
232 | /* Open context with TEE driver */ | |
233 | pvt_data->ctx = tee_client_open_context(NULL, ftpm_tee_match, NULL, | |
234 | NULL); | |
235 | if (IS_ERR(pvt_data->ctx)) { | |
236 | if (PTR_ERR(pvt_data->ctx) == -ENOENT) | |
237 | return -EPROBE_DEFER; | |
238 | dev_err(dev, "%s: tee_client_open_context failed\n", __func__); | |
239 | return PTR_ERR(pvt_data->ctx); | |
240 | } | |
241 | ||
242 | /* Open a session with fTPM TA */ | |
243 | memset(&sess_arg, 0, sizeof(sess_arg)); | |
244 | memcpy(sess_arg.uuid, ftpm_ta_uuid.b, TEE_IOCTL_UUID_LEN); | |
245 | sess_arg.clnt_login = TEE_IOCTL_LOGIN_PUBLIC; | |
246 | sess_arg.num_params = 0; | |
247 | ||
248 | rc = tee_client_open_session(pvt_data->ctx, &sess_arg, NULL); | |
249 | if ((rc < 0) || (sess_arg.ret != 0)) { | |
250 | dev_err(dev, "%s: tee_client_open_session failed, err=%x\n", | |
251 | __func__, sess_arg.ret); | |
252 | rc = -EINVAL; | |
253 | goto out_tee_session; | |
254 | } | |
255 | pvt_data->session = sess_arg.session; | |
256 | ||
257 | /* Allocate dynamic shared memory with fTPM TA */ | |
258 | pvt_data->shm = tee_shm_alloc(pvt_data->ctx, | |
259 | MAX_COMMAND_SIZE + MAX_RESPONSE_SIZE, | |
260 | TEE_SHM_MAPPED | TEE_SHM_DMA_BUF); | |
261 | if (IS_ERR(pvt_data->shm)) { | |
262 | dev_err(dev, "%s: tee_shm_alloc failed\n", __func__); | |
263 | rc = -ENOMEM; | |
264 | goto out_shm_alloc; | |
265 | } | |
266 | ||
267 | /* Allocate new struct tpm_chip instance */ | |
268 | chip = tpm_chip_alloc(dev, &ftpm_tee_tpm_ops); | |
269 | if (IS_ERR(chip)) { | |
270 | dev_err(dev, "%s: tpm_chip_alloc failed\n", __func__); | |
271 | rc = PTR_ERR(chip); | |
272 | goto out_chip_alloc; | |
273 | } | |
274 | ||
275 | pvt_data->chip = chip; | |
276 | pvt_data->chip->flags |= TPM_CHIP_FLAG_TPM2; | |
277 | ||
278 | /* Create a character device for the fTPM */ | |
279 | rc = tpm_chip_register(pvt_data->chip); | |
280 | if (rc) { | |
281 | dev_err(dev, "%s: tpm_chip_register failed with rc=%d\n", | |
282 | __func__, rc); | |
283 | goto out_chip; | |
284 | } | |
285 | ||
286 | return 0; | |
287 | ||
288 | out_chip: | |
289 | put_device(&pvt_data->chip->dev); | |
290 | out_chip_alloc: | |
291 | tee_shm_free(pvt_data->shm); | |
292 | out_shm_alloc: | |
293 | tee_client_close_session(pvt_data->ctx, pvt_data->session); | |
294 | out_tee_session: | |
295 | tee_client_close_context(pvt_data->ctx); | |
296 | ||
297 | return rc; | |
298 | } | |
299 | ||
300 | /** | |
301 | * ftpm_tee_remove - remove the TPM device | |
302 | * @pdev: the platform_device description. | |
303 | * | |
304 | * Return: | |
305 | * 0 always. | |
306 | */ | |
307 | static int ftpm_tee_remove(struct platform_device *pdev) | |
308 | { | |
309 | struct ftpm_tee_private *pvt_data = dev_get_drvdata(&pdev->dev); | |
310 | ||
311 | /* Release the chip */ | |
312 | tpm_chip_unregister(pvt_data->chip); | |
313 | ||
314 | /* frees chip */ | |
315 | put_device(&pvt_data->chip->dev); | |
316 | ||
317 | /* Free the shared memory pool */ | |
318 | tee_shm_free(pvt_data->shm); | |
319 | ||
320 | /* close the existing session with fTPM TA*/ | |
321 | tee_client_close_session(pvt_data->ctx, pvt_data->session); | |
322 | ||
323 | /* close the context with TEE driver */ | |
324 | tee_client_close_context(pvt_data->ctx); | |
325 | ||
326 | /* memory allocated with devm_kzalloc() is freed automatically */ | |
327 | ||
328 | return 0; | |
329 | } | |
330 | ||
331 | static const struct of_device_id of_ftpm_tee_ids[] = { | |
332 | { .compatible = "microsoft,ftpm" }, | |
333 | { } | |
334 | }; | |
335 | MODULE_DEVICE_TABLE(of, of_ftpm_tee_ids); | |
336 | ||
337 | static struct platform_driver ftpm_tee_driver = { | |
338 | .driver = { | |
339 | .name = "ftpm-tee", | |
340 | .of_match_table = of_match_ptr(of_ftpm_tee_ids), | |
341 | }, | |
342 | .probe = ftpm_tee_probe, | |
343 | .remove = ftpm_tee_remove, | |
344 | }; | |
345 | ||
346 | module_platform_driver(ftpm_tee_driver); | |
347 | ||
348 | MODULE_AUTHOR("Thirupathaiah Annapureddy <thiruan@microsoft.com>"); | |
349 | MODULE_DESCRIPTION("TPM Driver for fTPM TA in TEE"); | |
350 | MODULE_LICENSE("GPL v2"); |