git: 0daf5f02cfaf - main - bhyve/tpm: create crb thread for sending tpm commands

From: Corvin Köhne <corvink_at_FreeBSD.org>
Date: Tue, 20 Jun 2023 09:01:01 UTC
The branch main has been updated by corvink:

URL: https://cgit.FreeBSD.org/src/commit/?id=0daf5f02cfaf08a7fad75d934bbea50242c98ab3

commit 0daf5f02cfaf08a7fad75d934bbea50242c98ab3
Author:     Corvin Köhne <corvink@FreeBSD.org>
AuthorDate: 2022-08-29 10:10:44 +0000
Commit:     Corvin Köhne <corvink@FreeBSD.org>
CommitDate: 2023-06-20 08:59:00 +0000

    bhyve/tpm: create crb thread for sending tpm commands
    
    Commands send to a tpm are very slow. They can take up to several
    seconds for completion. For that reason, create a thread which issues
    the commands to the tpm device.
    
    Reviewed by:            markj
    MFC after:              1 week
    Sponsored by:           Beckhoff Automation GmbH & Co. KG
    Differential Revision:  https://reviews.freebsd.org/D40458
---
 usr.sbin/bhyve/tpm_device.c   |   2 +-
 usr.sbin/bhyve/tpm_emul.h     |   2 +
 usr.sbin/bhyve/tpm_intf.h     |   3 +-
 usr.sbin/bhyve/tpm_intf_crb.c | 102 +++++++++++++++++++++++++++++++++++++++++-
 4 files changed, 105 insertions(+), 4 deletions(-)

diff --git a/usr.sbin/bhyve/tpm_device.c b/usr.sbin/bhyve/tpm_device.c
index 94e9f1ffdc27..3ff480d5e6bb 100644
--- a/usr.sbin/bhyve/tpm_device.c
+++ b/usr.sbin/bhyve/tpm_device.c
@@ -141,7 +141,7 @@ tpm_device_create(struct tpm_device **const new_dev, struct vmctx *const vm_ctx,
 	}
 
 	if (dev->intf->init) {
-		error = dev->intf->init(&dev->intf_sc);
+		error = dev->intf->init(&dev->intf_sc, dev->emul, dev->emul_sc);
 		if (error)
 			goto err_out;
 	}
diff --git a/usr.sbin/bhyve/tpm_emul.h b/usr.sbin/bhyve/tpm_emul.h
index f75c0318d37e..24452c92d3b8 100644
--- a/usr.sbin/bhyve/tpm_emul.h
+++ b/usr.sbin/bhyve/tpm_emul.h
@@ -18,5 +18,7 @@ struct tpm_emul {
 
 	int (*init)(void **sc, nvlist_t *nvl);
 	void (*deinit)(void *sc);
+	int (*execute_cmd)(void *sc, void *cmd, uint32_t cmd_size, void *rsp,
+	    uint32_t rsp_size);
 };
 #define TPM_EMUL_SET(x) DATA_SET(tpm_emul_set, x)
diff --git a/usr.sbin/bhyve/tpm_intf.h b/usr.sbin/bhyve/tpm_intf.h
index 3003d8fbd754..020743c0c3f1 100644
--- a/usr.sbin/bhyve/tpm_intf.h
+++ b/usr.sbin/bhyve/tpm_intf.h
@@ -11,6 +11,7 @@
 
 #include "config.h"
 #include "tpm_device.h"
+#include "tpm_emul.h"
 
 #define TPM_INTF_TYPE_FIFO_PTP 0x0
 #define TPM_INTF_TYPE_CRB 0x1
@@ -30,7 +31,7 @@
 struct tpm_intf {
 	const char *name;
 
-	int (*init)(void **sc);
+	int (*init)(void **sc, struct tpm_emul *emul, void *emul_sc);
 	void (*deinit)(void *sc);
 	int (*build_acpi_table)(void *sc, struct vmctx *vm_ctx);
 };
diff --git a/usr.sbin/bhyve/tpm_intf_crb.c b/usr.sbin/bhyve/tpm_intf_crb.c
index b8ae33c5ec0a..a920eb5a67e6 100644
--- a/usr.sbin/bhyve/tpm_intf_crb.c
+++ b/usr.sbin/bhyve/tpm_intf_crb.c
@@ -26,6 +26,7 @@
 #include "config.h"
 #include "mem.h"
 #include "qemu_fwcfg.h"
+#include "tpm_device.h"
 #include "tpm_intf.h"
 
 #define TPM_CRB_ADDRESS 0xFED40000
@@ -45,6 +46,8 @@
 
 #define TPM_CRB_LOG_AREA_FWCFG_NAME "etc/tpm/log"
 
+#define TPM_CRB_INTF_NAME "crb"
+
 struct tpm_crb_regs {
 	union tpm_crb_reg_loc_state {
 		struct {
@@ -164,17 +167,82 @@ static_assert(sizeof(struct tpm_crb_regs) == TPM_CRB_REGS_SIZE,
 	} while (0)
 
 struct tpm_crb {
+	struct tpm_emul *emul;
+	void *emul_sc;
 	uint8_t tpm_log_area[TPM_CRB_LOG_AREA_MINIMUM_SIZE];
 	struct tpm_crb_regs regs;
+	pthread_t thread;
+	pthread_mutex_t mutex;
+	pthread_cond_t cond;
+	bool closing;
 };
 
+static void *
+tpm_crb_thread(void *const arg)
+{
+	struct tpm_crb *const crb = arg;
+
+	pthread_mutex_lock(&crb->mutex);
+	for (;;) {
+		pthread_cond_wait(&crb->cond, &crb->mutex);
+
+		if (crb->closing)
+			break;
+
+		const uint64_t cmd_addr = CRB_CMD_ADDR_READ(crb->regs);
+		const uint64_t rsp_addr = CRB_RSP_ADDR_READ(crb->regs);
+		const uint32_t cmd_size = CRB_CMD_SIZE_READ(crb->regs);
+		const uint32_t rsp_size = CRB_RSP_SIZE_READ(crb->regs);
+
+		const uint64_t cmd_off = cmd_addr - TPM_CRB_DATA_BUFFER_ADDRESS;
+		const uint64_t rsp_off = rsp_addr - TPM_CRB_DATA_BUFFER_ADDRESS;
+
+		if (cmd_off > TPM_CRB_DATA_BUFFER_SIZE ||
+		    cmd_off + cmd_size > TPM_CRB_DATA_BUFFER_SIZE ||
+		    rsp_off > TPM_CRB_DATA_BUFFER_SIZE ||
+		    rsp_off + rsp_size > TPM_CRB_DATA_BUFFER_SIZE) {
+			warnx(
+			    "%s: invalid cmd [%16lx, %16lx] --> [%16lx, %16lx]\n\r",
+			    __func__, cmd_addr, cmd_addr + cmd_size, rsp_addr,
+			    rsp_addr + rsp_size);
+			break;
+		}
+
+		/*
+		 * The command response buffer interface uses a single buffer
+		 * for sending a command to and receiving a response from the
+		 * tpm. To avoid reading old data from the command buffer which
+		 * might be a security issue, we zero out the command buffer
+		 * before writing the response into it. The rsp_size parameter
+		 * is controlled by the guest and it's not guaranteed that the
+		 * response has a size of rsp_size (e.g. if the tpm returned an
+		 * error, the response would have a different size than
+		 * expected). For that reason, use a second buffer for the
+		 * response.
+		 */
+		uint8_t rsp[TPM_CRB_DATA_BUFFER_SIZE] = { 0 };
+		crb->emul->execute_cmd(crb->emul_sc,
+		    &crb->regs.data_buffer[cmd_off], cmd_size, &rsp[rsp_off],
+		    rsp_size);
+
+		memset(crb->regs.data_buffer, 0, TPM_CRB_DATA_BUFFER_SIZE);
+		memcpy(&crb->regs.data_buffer[rsp_off], &rsp[rsp_off], rsp_size);
+
+		crb->regs.ctrl_start.start = false;
+	}
+	pthread_mutex_unlock(&crb->mutex);
+
+	return (NULL);
+}
+
 static int
-tpm_crb_init(void **sc)
+tpm_crb_init(void **sc, struct tpm_emul *emul, void *emul_sc)
 {
 	struct tpm_crb *crb = NULL;
 	int error;
 
 	assert(sc != NULL);
+	assert(emul != NULL);
 
 	crb = calloc(1, sizeof(struct tpm_crb));
 	if (crb == NULL) {
@@ -185,6 +253,9 @@ tpm_crb_init(void **sc)
 
 	memset(crb, 0, sizeof(*crb));
 
+	crb->emul = emul;
+	crb->emul_sc = emul_sc;
+
 	crb->regs.loc_state.tpm_req_valid_sts = true;
 	crb->regs.loc_state.tpm_established = true;
 
@@ -216,6 +287,26 @@ tpm_crb_init(void **sc)
 		goto err_out;
 	}
 
+	error = pthread_mutex_init(&crb->mutex, NULL);
+	if (error) {
+		warnc(error, "%s: failed to init mutex", __func__);
+		goto err_out;
+	}
+
+	error = pthread_cond_init(&crb->cond, NULL);
+	if (error) {
+		warnc(error, "%s: failed to init cond", __func__);
+		goto err_out;
+	}
+
+	error = pthread_create(&crb->thread, NULL, tpm_crb_thread, crb);
+	if (error) {
+		warnx("%s: failed to create thread\n", __func__);
+		goto err_out;
+	}
+
+	pthread_set_name_np(crb->thread, "tpm_intf_crb");
+
 	*sc = crb;
 
 	return (0);
@@ -237,6 +328,13 @@ tpm_crb_deinit(void *sc)
 
 	crb = sc;
 
+	crb->closing = true;
+	pthread_cond_signal(&crb->cond);
+	pthread_join(crb->thread, NULL);
+
+	pthread_cond_destroy(&crb->cond);
+	pthread_mutex_destroy(&crb->mutex);
+
 	free(crb);
 }
 
@@ -275,7 +373,7 @@ tpm_crb_build_acpi_table(void *sc __unused, struct vmctx *vm_ctx)
 }
 
 static struct tpm_intf tpm_intf_crb = {
-	.name = "crb",
+	.name = TPM_CRB_INTF_NAME,
 	.init = tpm_crb_init,
 	.deinit = tpm_crb_deinit,
 	.build_acpi_table = tpm_crb_build_acpi_table,