diff --git a/usr.sbin/bhyve/tpm_device.c b/usr.sbin/bhyve/tpm_device.c --- a/usr.sbin/bhyve/tpm_device.c +++ b/usr.sbin/bhyve/tpm_device.c @@ -141,7 +141,8 @@ } if (dev->intf->init) { - error = dev->intf->init(&dev->intf_sc, dev->emul, dev->emul_sc); + error = dev->intf->init(&dev->intf_sc, dev->emul, dev->emul_sc, + dev->acpi_dev); if (error) goto err_out; } diff --git a/usr.sbin/bhyve/tpm_intf.h b/usr.sbin/bhyve/tpm_intf.h --- a/usr.sbin/bhyve/tpm_intf.h +++ b/usr.sbin/bhyve/tpm_intf.h @@ -9,6 +9,7 @@ #include +#include "acpi_device.h" #include "config.h" #include "tpm_device.h" #include "tpm_emul.h" @@ -31,7 +32,8 @@ struct tpm_intf { const char *name; - int (*init)(void **sc, struct tpm_emul *emul, void *emul_sc); + int (*init)(void **sc, struct tpm_emul *emul, void *emul_sc, + struct acpi_device *acpi_dev); 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 --- a/usr.sbin/bhyve/tpm_intf_crb.c +++ b/usr.sbin/bhyve/tpm_intf_crb.c @@ -48,6 +48,11 @@ #define TPM_CRB_INTF_NAME "crb" +#define sizeof_field(type, field) (sizeof(((type *)0)->field)) + +#define is_offsetof(type, field) \ + offsetof(type, field) ... offsetof(type, field) + sizeof_field(type, field) - 1 + struct tpm_crb_regs { union tpm_crb_reg_loc_state { struct { @@ -246,7 +251,171 @@ } static int -tpm_crb_init(void **sc, struct tpm_emul *emul, void *emul_sc) +tpm_crb_mmiocpy(void *const dst, void *const src, const int size) +{ + switch (size) { + case 1: + *(uint8_t *)dst = *(uint8_t *)src; + break; + case 2: + *(uint16_t *)dst = *(uint16_t *)src; + break; + case 4: + *(uint32_t *)dst = *(uint32_t *)src; + break; + case 8: + *(uint64_t *)dst = *(uint64_t *)src; + break; + default: + warnx("%s: invalid size of %d", __func__, size); + return (EINVAL); + } + + return (0); +} + +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) +{ + struct tpm_crb *crb; + uint8_t *ptr; + uint64_t off, shift; + int error; + + if ((addr & (size - 1)) != 0) { + warnx("%s: unaligned %s access @ %16lx [size = %x]\n", __func__, + (dir == MEM_F_READ) ? "read" : "write", addr, size); + } + + crb = arg1; + + off = addr - TPM_CRB_ADDRESS; + if (off > TPM_CRB_REGS_SIZE || off + size >= TPM_CRB_REGS_SIZE) { + return (EINVAL); + } + + shift = 8 * (off & 3); + ptr = (uint8_t *)&crb->regs + off; + + if (dir == MEM_F_READ) { + error = tpm_crb_mmiocpy(val, ptr, size); + if (error) + goto err_out; + } else { + switch (off) { + case is_offsetof(struct tpm_crb_regs, loc_ctrl): { + union tpm_crb_reg_loc_ctrl loc_ctrl; + + if ((size_t)size > sizeof(loc_ctrl)) + goto err_out; + + *val = *val << shift; + tpm_crb_mmiocpy(&loc_ctrl, val, size); + + if (loc_ctrl.relinquish) { + crb->regs.loc_sts.granted = false; + crb->regs.loc_state.loc_assigned = false; + } else if (loc_ctrl.request_access) { + crb->regs.loc_sts.granted = true; + crb->regs.loc_state.loc_assigned = true; + } + + break; + } + case is_offsetof(struct tpm_crb_regs, ctrl_req): { + union tpm_crb_reg_ctrl_req req; + + if ((size_t)size > sizeof(req)) + goto err_out; + + *val = *val << shift; + tpm_crb_mmiocpy(&req, val, size); + + if (req.cmd_ready && !req.go_idle) { + crb->regs.ctrl_sts.tpm_idle = false; + } else if (!req.cmd_ready && req.go_idle) { + crb->regs.ctrl_sts.tpm_idle = true; + } + + break; + } + case is_offsetof(struct tpm_crb_regs, ctrl_cancel): { + /* TODO: cancel the tpm command */ + warnx( + "%s: cancelling a TPM command is not implemented yet\n", + __func__); + + break; + } + case is_offsetof(struct tpm_crb_regs, ctrl_start): { + union tpm_crb_reg_ctrl_start start; + + if ((size_t)size > sizeof(start)) + goto err_out; + + *val = *val << shift; + tpm_crb_mmiocpy(&start, val, size); + + if (!start.start || crb->regs.ctrl_start.start) + break; + + pthread_mutex_lock(&crb->mutex); + + crb->regs.ctrl_start.start = true; + + pthread_cond_signal(&crb->cond); + pthread_mutex_unlock(&crb->mutex); + + break; + } + case is_offsetof(struct tpm_crb_regs, cmd_size): + case is_offsetof(struct tpm_crb_regs, cmd_addr_lo): + case is_offsetof(struct tpm_crb_regs, cmd_addr_hi): + case is_offsetof(struct tpm_crb_regs, rsp_size): + case is_offsetof(struct tpm_crb_regs, rsp_addr): + case is_offsetof(struct tpm_crb_regs, data_buffer): + /* + * Those fields are used to execute a TPM commands. The + * crb_thread will access them. For that reason, we have + * to acquire the crb mutex in order to write them. + */ + pthread_mutex_lock(&crb->mutex); + error = tpm_crb_mmiocpy(ptr, val, size); + pthread_mutex_unlock(&crb->mutex); + if (error) + goto err_out; + break; + default: + /* + * The other fields are either readonly or we do not + * support writing them. + */ + goto err_out; + } + } + + return (0); + +err_out: + warnx("%s: invalid %s @ %16lx [size = %d]", __func__, + dir == MEM_F_READ ? "read" : "write", addr, size); + + return (0); +} + +static struct mem_range crb_mmio = { + .name = "crb-mmio", + .base = TPM_CRB_ADDRESS, + .size = TPM_CRB_LOCALITIES_MAX * TPM_CRB_CONTROL_AREA_SIZE, + .flags = MEM_F_RW, + .handler = tpm_crb_mem_handler, +}; + +static int +tpm_crb_init(void **sc, struct tpm_emul *emul, void *emul_sc, + struct acpi_device *acpi_dev) { struct tpm_crb *crb = NULL; int error; @@ -297,6 +466,20 @@ goto err_out; } + error = acpi_device_add_res_fixed_memory32(acpi_dev, false, + TPM_CRB_ADDRESS, TPM_CRB_CONTROL_AREA_SIZE); + if (error) { + warnx("%s: failed to add acpi resources\n", __func__); + goto err_out; + } + + crb_mmio.arg1 = crb; + error = register_mem(&crb_mmio); + if (error) { + warnx("%s: failed to register crb mmio", __func__); + goto err_out; + } + error = pthread_mutex_init(&crb->mutex, NULL); if (error) { warnc(error, "%s: failed to init mutex", __func__); @@ -331,6 +514,7 @@ tpm_crb_deinit(void *sc) { struct tpm_crb *crb; + int error; if (sc == NULL) { return; @@ -345,6 +529,9 @@ pthread_cond_destroy(&crb->cond); pthread_mutex_destroy(&crb->mutex); + error = unregister_mem(&crb_mmio); + assert(error == 0); + free(crb); }