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