xref: /illumos-gate/usr/src/cmd/bhyve/tpm_intf_crb.c (revision 32640292)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2022 Beckhoff Automation GmbH & Co. KG
5  * Author: Corvin Köhne <c.koehne@beckhoff.com>
6  */
7 
8 #include <sys/cdefs.h>
9 #include <sys/types.h>
10 #include <sys/param.h>
11 #include <sys/linker_set.h>
12 
13 #include <machine/vmm.h>
14 
15 #include <assert.h>
16 #include <err.h>
17 #include <errno.h>
18 #include <pthread.h>
19 #include <pthread_np.h>
20 #include <stddef.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <vmmapi.h>
24 
25 #include "basl.h"
26 #include "config.h"
27 #include "mem.h"
28 #include "qemu_fwcfg.h"
29 #include "tpm_device.h"
30 #include "tpm_intf.h"
31 
32 #define TPM_CRB_ADDRESS 0xFED40000
33 #define TPM_CRB_REGS_SIZE 0x1000
34 
35 #define TPM_CRB_CONTROL_AREA_ADDRESS \
36 	(TPM_CRB_ADDRESS + offsetof(struct tpm_crb_regs, ctrl_req))
37 #define TPM_CRB_CONTROL_AREA_SIZE TPM_CRB_REGS_SIZE
38 
39 #define TPM_CRB_DATA_BUFFER_ADDRESS \
40 	(TPM_CRB_ADDRESS + offsetof(struct tpm_crb_regs, data_buffer))
41 #define TPM_CRB_DATA_BUFFER_SIZE 0xF80
42 
43 #define TPM_CRB_LOCALITIES_MAX 5
44 
45 #define TPM_CRB_LOG_AREA_MINIMUM_SIZE (64 * 1024)
46 
47 #define TPM_CRB_LOG_AREA_FWCFG_NAME "etc/tpm/log"
48 
49 #define TPM_CRB_INTF_NAME "crb"
50 
51 struct tpm_crb_regs {
52 	union tpm_crb_reg_loc_state {
53 		struct {
54 			uint32_t tpm_established : 1;
55 			uint32_t loc_assigned : 1;
56 			uint32_t active_locality : 3;
57 			uint32_t _reserved : 2;
58 			uint32_t tpm_req_valid_sts : 1;
59 		};
60 		uint32_t val;
61 	} loc_state;	       /* 0h */
62 	uint8_t _reserved1[4]; /* 4h */
63 	union tpm_crb_reg_loc_ctrl {
64 		struct {
65 			uint32_t request_access : 1;
66 			uint32_t relinquish : 1;
67 			uint32_t seize : 1;
68 			uint32_t reset_establishment_bit : 1;
69 		};
70 		uint32_t val;
71 	} loc_ctrl; /* 8h */
72 	union tpm_crb_reg_loc_sts {
73 		struct {
74 			uint32_t granted : 1;
75 			uint32_t been_seized : 1;
76 		};
77 		uint32_t val;
78 	} loc_sts;		  /* Ch */
79 	uint8_t _reserved2[0x20]; /* 10h */
80 	union tpm_crb_reg_intf_id {
81 		struct {
82 			uint64_t interface_type : 4;
83 			uint64_t interface_version : 4;
84 			uint64_t cap_locality : 1;
85 			uint64_t cap_crb_idle_bypass : 1;
86 			uint64_t _reserved1 : 1;
87 			uint64_t cap_data_xfer_size_support : 2;
88 			uint64_t cap_fifo : 1;
89 			uint64_t cap_crb : 1;
90 			uint64_t _reserved2 : 2;
91 			uint64_t interface_selector : 2;
92 			uint64_t intf_sel_lock : 1;
93 			uint64_t _reserved3 : 4;
94 			uint64_t rid : 8;
95 			uint64_t vid : 16;
96 			uint64_t did : 16;
97 		};
98 		uint64_t val;
99 	} intf_id; /* 30h */
100 	union tpm_crb_reg_ctrl_ext {
101 		struct {
102 			uint32_t clear;
103 			uint32_t remaining_bytes;
104 		};
105 		uint64_t val;
106 	} ctrl_ext; /* 38 */
107 	union tpm_crb_reg_ctrl_req {
108 		struct {
109 			uint32_t cmd_ready : 1;
110 			uint32_t go_idle : 1;
111 		};
112 		uint32_t val;
113 	} ctrl_req; /* 40h */
114 	union tpm_crb_reg_ctrl_sts {
115 		struct {
116 			uint32_t tpm_sts : 1;
117 			uint32_t tpm_idle : 1;
118 		};
119 		uint32_t val;
120 	} ctrl_sts; /* 44h */
121 	union tpm_crb_reg_ctrl_cancel {
122 		struct {
123 			uint32_t cancel : 1;
124 		};
125 		uint32_t val;
126 	} ctrl_cancel; /* 48h */
127 	union tpm_crb_reg_ctrl_start {
128 		struct {
129 			uint32_t start : 1;
130 		};
131 		uint32_t val;
132 	} ctrl_start;				       /* 4Ch*/
133 	uint32_t int_enable;			       /* 50h */
134 	uint32_t int_sts;			       /* 54h */
135 	uint32_t cmd_size;			       /* 58h */
136 	uint32_t cmd_addr_lo;			       /* 5Ch */
137 	uint32_t cmd_addr_hi;			       /* 60h */
138 	uint32_t rsp_size;			       /* 64h */
139 	uint64_t rsp_addr;			       /* 68h */
140 	uint8_t _reserved3[0x10];		       /* 70h */
141 	uint8_t data_buffer[TPM_CRB_DATA_BUFFER_SIZE]; /* 80h */
142 } __packed;
143 static_assert(sizeof(struct tpm_crb_regs) == TPM_CRB_REGS_SIZE,
144     "Invalid size of tpm_crb");
145 
146 #define CRB_CMD_SIZE_READ(regs) (regs.cmd_size)
147 #define CRB_CMD_SIZE_WRITE(regs, val) \
148 	do {                          \
149 		regs.cmd_size = val;  \
150 	} while (0)
151 #define CRB_CMD_ADDR_READ(regs) \
152 	(((uint64_t)regs.cmd_addr_hi << 32) | regs.cmd_addr_lo)
153 #define CRB_CMD_ADDR_WRITE(regs, val)                \
154 	do {                                         \
155 		regs.cmd_addr_lo = val & 0xFFFFFFFF; \
156 		regs.cmd_addr_hi = val >> 32;        \
157 	} while (0)
158 #define CRB_RSP_SIZE_READ(regs) (regs.rsp_size)
159 #define CRB_RSP_SIZE_WRITE(regs, val) \
160 	do {                          \
161 		regs.rsp_size = val;  \
162 	} while (0)
163 #define CRB_RSP_ADDR_READ(regs) (regs.rsp_addr)
164 #define CRB_RSP_ADDR_WRITE(regs, val) \
165 	do {                          \
166 		regs.rsp_addr = val;  \
167 	} while (0)
168 
169 struct tpm_crb {
170 	struct tpm_emul *emul;
171 	void *emul_sc;
172 	uint8_t tpm_log_area[TPM_CRB_LOG_AREA_MINIMUM_SIZE];
173 	struct tpm_crb_regs regs;
174 	pthread_t thread;
175 	pthread_mutex_t mutex;
176 	pthread_cond_t cond;
177 	bool closing;
178 };
179 
180 static void *
tpm_crb_thread(void * const arg)181 tpm_crb_thread(void *const arg)
182 {
183 	struct tpm_crb *const crb = arg;
184 
185 	pthread_mutex_lock(&crb->mutex);
186 	for (;;) {
187 		/*
188 		 * We're releasing the lock after wake up. Therefore, we have to
189 		 * check the closing condition before and after going to sleep.
190 		 */
191 		if (crb->closing)
192 			break;
193 
194 		pthread_cond_wait(&crb->cond, &crb->mutex);
195 
196 		if (crb->closing)
197 			break;
198 
199 		const uint64_t cmd_addr = CRB_CMD_ADDR_READ(crb->regs);
200 		const uint64_t rsp_addr = CRB_RSP_ADDR_READ(crb->regs);
201 		const uint32_t cmd_size = CRB_CMD_SIZE_READ(crb->regs);
202 		const uint32_t rsp_size = CRB_RSP_SIZE_READ(crb->regs);
203 
204 		const uint64_t cmd_off = cmd_addr - TPM_CRB_DATA_BUFFER_ADDRESS;
205 		const uint64_t rsp_off = rsp_addr - TPM_CRB_DATA_BUFFER_ADDRESS;
206 
207 		if (cmd_off > TPM_CRB_DATA_BUFFER_SIZE ||
208 		    cmd_off + cmd_size > TPM_CRB_DATA_BUFFER_SIZE ||
209 		    rsp_off > TPM_CRB_DATA_BUFFER_SIZE ||
210 		    rsp_off + rsp_size > TPM_CRB_DATA_BUFFER_SIZE) {
211 			warnx(
212 			    "%s: invalid cmd [%16lx, %16lx] --> [%16lx, %16lx]\n\r",
213 			    __func__, cmd_addr, cmd_addr + cmd_size, rsp_addr,
214 			    rsp_addr + rsp_size);
215 			break;
216 		}
217 
218 		uint8_t cmd[TPM_CRB_DATA_BUFFER_SIZE];
219 		memcpy(cmd, crb->regs.data_buffer, TPM_CRB_DATA_BUFFER_SIZE);
220 
221 		/*
222 		 * A TPM command can take multiple seconds to execute. As we've
223 		 * copied all required values and buffers at this point, we can
224 		 * release the mutex.
225 		 */
226 		pthread_mutex_unlock(&crb->mutex);
227 
228 		/*
229 		 * The command response buffer interface uses a single buffer
230 		 * for sending a command to and receiving a response from the
231 		 * tpm. To avoid reading old data from the command buffer which
232 		 * might be a security issue, we zero out the command buffer
233 		 * before writing the response into it. The rsp_size parameter
234 		 * is controlled by the guest and it's not guaranteed that the
235 		 * response has a size of rsp_size (e.g. if the tpm returned an
236 		 * error, the response would have a different size than
237 		 * expected). For that reason, use a second buffer for the
238 		 * response.
239 		 */
240 		uint8_t rsp[TPM_CRB_DATA_BUFFER_SIZE] = { 0 };
241 		crb->emul->execute_cmd(crb->emul_sc, &cmd[cmd_off], cmd_size,
242 		    &rsp[rsp_off], rsp_size);
243 
244 		pthread_mutex_lock(&crb->mutex);
245 		memset(crb->regs.data_buffer, 0, TPM_CRB_DATA_BUFFER_SIZE);
246 		memcpy(&crb->regs.data_buffer[rsp_off], &rsp[rsp_off], rsp_size);
247 
248 		crb->regs.ctrl_start.start = false;
249 	}
250 	pthread_mutex_unlock(&crb->mutex);
251 
252 	return (NULL);
253 }
254 
255 static int
tpm_crb_mmiocpy(void * const dst,void * const src,const int size)256 tpm_crb_mmiocpy(void *const dst, void *const src, const int size)
257 {
258 	if (!(size == 1 || size == 2 || size == 4 || size == 8))
259 		return (EINVAL);
260 	memcpy(dst, src, size);
261 
262 	return (0);
263 }
264 
265 static int
tpm_crb_mem_handler(struct vcpu * vcpu __unused,const int dir,const uint64_t addr,const int size,uint64_t * const val,void * const arg1,const long arg2 __unused)266 tpm_crb_mem_handler(struct vcpu *vcpu __unused, const int dir,
267     const uint64_t addr, const int size, uint64_t *const val, void *const arg1,
268     const long arg2 __unused)
269 {
270 	struct tpm_crb *crb;
271 	uint8_t *ptr;
272 	uint64_t off, shift;
273 	int error = 0;
274 
275 	if ((addr & (size - 1)) != 0) {
276 		warnx("%s: unaligned %s access @ %16lx [size = %x]", __func__,
277 		    (dir == MEM_F_READ) ? "read" : "write", addr, size);
278 		return (EINVAL);
279 	}
280 
281 	crb = arg1;
282 
283 	off = addr - TPM_CRB_ADDRESS;
284 	if (off > TPM_CRB_REGS_SIZE || off + size >= TPM_CRB_REGS_SIZE) {
285 		return (EINVAL);
286 	}
287 
288 	shift = 8 * (off & 3);
289 	ptr = (uint8_t *)&crb->regs + off;
290 
291 	if (dir == MEM_F_READ) {
292 		error = tpm_crb_mmiocpy(val, ptr, size);
293 		if (error)
294 			goto err_out;
295 	} else {
296 		switch (off & ~0x3) {
297 		case offsetof(struct tpm_crb_regs, loc_ctrl): {
298 			union tpm_crb_reg_loc_ctrl loc_ctrl;
299 
300 			if ((size_t)size > sizeof(loc_ctrl))
301 				goto err_out;
302 
303 			*val = *val << shift;
304 			tpm_crb_mmiocpy(&loc_ctrl, val, size);
305 
306 			if (loc_ctrl.relinquish) {
307 				crb->regs.loc_sts.granted = false;
308 				crb->regs.loc_state.loc_assigned = false;
309 			} else if (loc_ctrl.request_access) {
310 				crb->regs.loc_sts.granted = true;
311 				crb->regs.loc_state.loc_assigned = true;
312 			}
313 
314 			break;
315 		}
316 		case offsetof(struct tpm_crb_regs, ctrl_req): {
317 			union tpm_crb_reg_ctrl_req req;
318 
319 			if ((size_t)size > sizeof(req))
320 				goto err_out;
321 
322 			*val = *val << shift;
323 			tpm_crb_mmiocpy(&req, val, size);
324 
325 			if (req.cmd_ready && !req.go_idle) {
326 				crb->regs.ctrl_sts.tpm_idle = false;
327 			} else if (!req.cmd_ready && req.go_idle) {
328 				crb->regs.ctrl_sts.tpm_idle = true;
329 			}
330 
331 			break;
332 		}
333 		case offsetof(struct tpm_crb_regs, ctrl_cancel): {
334 			/* TODO: cancel the tpm command */
335 			warnx(
336 			    "%s: cancelling a TPM command is not implemented yet",
337 			    __func__);
338 
339 			break;
340 		}
341 		case offsetof(struct tpm_crb_regs, ctrl_start): {
342 			union tpm_crb_reg_ctrl_start start;
343 
344 			if ((size_t)size > sizeof(start))
345 				goto err_out;
346 
347 			*val = *val << shift;
348 
349 			pthread_mutex_lock(&crb->mutex);
350 			tpm_crb_mmiocpy(&start, val, size);
351 
352 			if (!start.start || crb->regs.ctrl_start.start)
353 				break;
354 
355 			crb->regs.ctrl_start.start = true;
356 
357 			pthread_cond_signal(&crb->cond);
358 			pthread_mutex_unlock(&crb->mutex);
359 
360 			break;
361 		}
362 		case offsetof(struct tpm_crb_regs, cmd_size):
363 		case offsetof(struct tpm_crb_regs, cmd_addr_lo):
364 		case offsetof(struct tpm_crb_regs, cmd_addr_hi):
365 		case offsetof(struct tpm_crb_regs, rsp_size):
366 		case offsetof(struct tpm_crb_regs,
367 		    rsp_addr) ... offsetof(struct tpm_crb_regs, rsp_addr) +
368 		    4:
369 		case offsetof(struct tpm_crb_regs,
370 		    data_buffer) ... offsetof(struct tpm_crb_regs, data_buffer) +
371 		    TPM_CRB_DATA_BUFFER_SIZE / 4:
372 			/*
373 			 * Those fields are used to execute a TPM command. The
374 			 * crb_thread will access them. For that reason, we have
375 			 * to acquire the crb mutex in order to write them.
376 			 */
377 			pthread_mutex_lock(&crb->mutex);
378 			error = tpm_crb_mmiocpy(ptr, val, size);
379 			pthread_mutex_unlock(&crb->mutex);
380 			if (error)
381 				goto err_out;
382 			break;
383 		default:
384 			/*
385 			 * The other fields are either readonly or we do not
386 			 * support writing them.
387 			 */
388 			error = EINVAL;
389 			goto err_out;
390 		}
391 	}
392 
393 	return (0);
394 
395 err_out:
396 	warnx("%s: invalid %s @ %16lx [size = %d]", __func__,
397 	    dir == MEM_F_READ ? "read" : "write", addr, size);
398 
399 	return (error);
400 }
401 
402 static int
tpm_crb_modify_mmio_registration(const bool registration,void * const arg1)403 tpm_crb_modify_mmio_registration(const bool registration, void *const arg1)
404 {
405 	struct mem_range crb_mmio = {
406 		.name = "crb-mmio",
407 		.base = TPM_CRB_ADDRESS,
408 		.size = TPM_CRB_LOCALITIES_MAX * TPM_CRB_CONTROL_AREA_SIZE,
409 		.flags = MEM_F_RW,
410 		.arg1 = arg1,
411 		.handler = tpm_crb_mem_handler,
412 	};
413 
414 	if (registration)
415 		return (register_mem(&crb_mmio));
416 	else
417 		return (unregister_mem(&crb_mmio));
418 }
419 
420 static int
tpm_crb_init(void ** sc,struct tpm_emul * emul,void * emul_sc,struct acpi_device * acpi_dev)421 tpm_crb_init(void **sc, struct tpm_emul *emul, void *emul_sc,
422     struct acpi_device *acpi_dev)
423 {
424 	struct tpm_crb *crb = NULL;
425 	int error;
426 
427 	assert(sc != NULL);
428 	assert(emul != NULL);
429 
430 	crb = calloc(1, sizeof(struct tpm_crb));
431 	if (crb == NULL) {
432 		warnx("%s: failed to allocate tpm crb", __func__);
433 		error = ENOMEM;
434 		goto err_out;
435 	}
436 
437 	memset(crb, 0, sizeof(*crb));
438 
439 	crb->emul = emul;
440 	crb->emul_sc = emul_sc;
441 
442 	crb->regs.loc_state.tpm_req_valid_sts = true;
443 	crb->regs.loc_state.tpm_established = true;
444 
445 	crb->regs.intf_id.interface_type = TPM_INTF_TYPE_CRB;
446 	crb->regs.intf_id.interface_version = TPM_INTF_VERSION_CRB;
447 	crb->regs.intf_id.cap_locality = false;
448 	crb->regs.intf_id.cap_crb_idle_bypass = false;
449 	crb->regs.intf_id.cap_data_xfer_size_support =
450 	    TPM_INTF_CAP_CRB_DATA_XFER_SIZE_64;
451 	crb->regs.intf_id.cap_fifo = false;
452 	crb->regs.intf_id.cap_crb = true;
453 	crb->regs.intf_id.interface_selector = TPM_INTF_SELECTOR_CRB;
454 	crb->regs.intf_id.intf_sel_lock = false;
455 	crb->regs.intf_id.rid = 0;
456 	crb->regs.intf_id.vid = 0x1014; /* IBM */
457 	crb->regs.intf_id.did = 0x1014; /* IBM */
458 
459 	crb->regs.ctrl_sts.tpm_idle = true;
460 
461 	CRB_CMD_SIZE_WRITE(crb->regs, TPM_CRB_DATA_BUFFER_SIZE);
462 	CRB_CMD_ADDR_WRITE(crb->regs, TPM_CRB_DATA_BUFFER_ADDRESS);
463 	CRB_RSP_SIZE_WRITE(crb->regs, TPM_CRB_DATA_BUFFER_SIZE);
464 	CRB_RSP_ADDR_WRITE(crb->regs, TPM_CRB_DATA_BUFFER_ADDRESS);
465 
466 	error = qemu_fwcfg_add_file(TPM_CRB_LOG_AREA_FWCFG_NAME,
467 	    TPM_CRB_LOG_AREA_MINIMUM_SIZE, crb->tpm_log_area);
468 	if (error) {
469 		warnx("%s: failed to add fwcfg file", __func__);
470 		goto err_out;
471 	}
472 
473 	error = acpi_device_add_res_fixed_memory32(acpi_dev, false,
474 	    TPM_CRB_ADDRESS, TPM_CRB_CONTROL_AREA_SIZE);
475 	if (error) {
476 		warnx("%s: failed to add acpi resources\n", __func__);
477 		goto err_out;
478 	}
479 
480 	error = tpm_crb_modify_mmio_registration(true, crb);
481 	if (error) {
482 		warnx("%s: failed to register crb mmio", __func__);
483 		goto err_out;
484 	}
485 
486 	error = pthread_mutex_init(&crb->mutex, NULL);
487 	if (error) {
488 		warnc(error, "%s: failed to init mutex", __func__);
489 		goto err_out;
490 	}
491 
492 	error = pthread_cond_init(&crb->cond, NULL);
493 	if (error) {
494 		warnc(error, "%s: failed to init cond", __func__);
495 		goto err_out;
496 	}
497 
498 	error = pthread_create(&crb->thread, NULL, tpm_crb_thread, crb);
499 	if (error) {
500 		warnx("%s: failed to create thread\n", __func__);
501 		goto err_out;
502 	}
503 
504 	pthread_set_name_np(crb->thread, "tpm_intf_crb");
505 
506 	*sc = crb;
507 
508 	return (0);
509 
510 err_out:
511 	free(crb);
512 
513 	return (error);
514 }
515 
516 static void
tpm_crb_deinit(void * sc)517 tpm_crb_deinit(void *sc)
518 {
519 	struct tpm_crb *crb;
520 	int error;
521 
522 	if (sc == NULL) {
523 		return;
524 	}
525 
526 	crb = sc;
527 
528 	crb->closing = true;
529 	pthread_cond_signal(&crb->cond);
530 	pthread_join(crb->thread, NULL);
531 
532 	pthread_cond_destroy(&crb->cond);
533 	pthread_mutex_destroy(&crb->mutex);
534 
535 	error = tpm_crb_modify_mmio_registration(false, NULL);
536 	assert(error == 0);
537 
538 	free(crb);
539 }
540 
541 static int
tpm_crb_build_acpi_table(void * sc __unused,struct vmctx * vm_ctx)542 tpm_crb_build_acpi_table(void *sc __unused, struct vmctx *vm_ctx)
543 {
544 	struct basl_table *table;
545 
546 	BASL_EXEC(basl_table_create(&table, vm_ctx, ACPI_SIG_TPM2,
547 	    BASL_TABLE_ALIGNMENT));
548 
549 	/* Header */
550 	BASL_EXEC(basl_table_append_header(table, ACPI_SIG_TPM2, 4, 1));
551 	/* Platform Class */
552 	BASL_EXEC(basl_table_append_int(table, 0, 2));
553 	/* Reserved */
554 	BASL_EXEC(basl_table_append_int(table, 0, 2));
555 	/* Control Address */
556 	BASL_EXEC(
557 	    basl_table_append_int(table, TPM_CRB_CONTROL_AREA_ADDRESS, 8));
558 	/* Start Method == (7) Command Response Buffer */
559 	BASL_EXEC(basl_table_append_int(table, 7, 4));
560 	/* Start Method Specific Parameters */
561 	uint8_t parameters[12] = { 0 };
562 	BASL_EXEC(basl_table_append_bytes(table, parameters, 12));
563 	/* Log Area Minimum Length */
564 	BASL_EXEC(
565 	    basl_table_append_int(table, TPM_CRB_LOG_AREA_MINIMUM_SIZE, 4));
566 	/* Log Area Start Address */
567 #ifdef	__FreeBSD__
568 	BASL_EXEC(
569 	    basl_table_append_fwcfg(table, TPM_CRB_LOG_AREA_FWCFG_NAME, 1, 8));
570 #else
571 	BASL_EXEC(
572 	    basl_table_append_fwcfg(table,
573 	    (const uint8_t *)TPM_CRB_LOG_AREA_FWCFG_NAME, 1, 8));
574 #endif
575 
576 	BASL_EXEC(basl_table_register_to_rsdt(table));
577 
578 	return (0);
579 }
580 
581 static struct tpm_intf tpm_intf_crb = {
582 	.name = TPM_CRB_INTF_NAME,
583 	.init = tpm_crb_init,
584 	.deinit = tpm_crb_deinit,
585 	.build_acpi_table = tpm_crb_build_acpi_table,
586 };
587 TPM_INTF_SET(tpm_intf_crb);
588