On 01/19/2018 11:11 AM, Marc-André Lureau wrote: > tpm_crb is a device for TPM 2.0 Command Response Buffer (CRB) > Interface as defined in TCG PC Client Platform TPM Profile (PTP) > Specification Family “2.0” Level 00 Revision 01.03 v22. > > The PTP allows device implementation to switch between TIS and CRB > model at run time, but given that CRB is a simpler device to > implement, I chose to implement it as a different device. > > The device doesn't implement other locality than 0 for now (my laptop > TPM doesn't either, so I assume this isn't so bad) > > The command/reply memory region is statically allocated after the CRB > registers address TPM_CRB_ADDR_BASE + sizeof(struct crb_regs) (I > wonder if the BIOS could or should allocate it instead, or what size > to use, again this seems to fit well expectations) > > The PTP doesn't specify a particular bus to put the device. So I added > it on the system bus directly, so it could hopefully be used easily on > a different platform than x86. Currently, it fails to init on piix, > because error_on_sysbus_device() check. The check may be changed in a > near future, see discussion on the qemu-devel ML. > > Tested with some success with Linux upstream and Windows 10, seabios & > modified ovmf. The device is recognized and correctly transmit > command/response with passthrough & emu. However, we are missing PPI > ACPI part atm. > > Signed-off-by: Marc-André Lureau <marcandre.lur...@redhat.com> > Signed-off-by: Stefan Berger <stef...@linux.vnet.ibm.com> > --- > qapi/tpm.json | 5 +- > include/hw/acpi/tpm.h | 72 ++++++++ > include/sysemu/tpm.h | 3 + > hw/i386/acpi-build.c | 34 +++- > hw/tpm/tpm_crb.c | 327 > +++++++++++++++++++++++++++++++++++++ > default-configs/i386-softmmu.mak | 1 + > default-configs/x86_64-softmmu.mak | 1 + > hw/tpm/Makefile.objs | 1 + > 8 files changed, 434 insertions(+), 10 deletions(-) > create mode 100644 hw/tpm/tpm_crb.c > > diff --git a/qapi/tpm.json b/qapi/tpm.json > index 7093f268fb..d50deef5e9 100644 > --- a/qapi/tpm.json > +++ b/qapi/tpm.json > @@ -11,10 +11,11 @@ > # An enumeration of TPM models > # > # @tpm-tis: TPM TIS model > +# @tpm-crb: TPM CRB model (since 2.12) > # > # Since: 1.5 > ## > -{ 'enum': 'TpmModel', 'data': [ 'tpm-tis' ] } > +{ 'enum': 'TpmModel', 'data': [ 'tpm-tis', 'tpm-crb' ] } > > ## > # @query-tpm-models: > @@ -28,7 +29,7 @@ > # Example: > # > # -> { "execute": "query-tpm-models" } > -# <- { "return": [ "tpm-tis" ] } > +# <- { "return": [ "tpm-tis", "tpm-crb" ] } > # > ## > { 'command': 'query-tpm-models', 'returns': ['TpmModel'] } > diff --git a/include/hw/acpi/tpm.h b/include/hw/acpi/tpm.h > index 6d516c6a7f..b0048515fa 100644 > --- a/include/hw/acpi/tpm.h > +++ b/include/hw/acpi/tpm.h > @@ -16,11 +16,82 @@ > #ifndef HW_ACPI_TPM_H > #define HW_ACPI_TPM_H > > +#include "qemu/osdep.h" > + > #define TPM_TIS_ADDR_BASE 0xFED40000 > #define TPM_TIS_ADDR_SIZE 0x5000 > > #define TPM_TIS_IRQ 5 > > +struct crb_regs { > + union { > + uint32_t reg; > + struct { > + unsigned tpm_established:1; > + unsigned loc_assigned:1; > + unsigned active_locality:3; > + unsigned reserved:2; > + unsigned tpm_reg_valid_sts:1; > + } bits;
I suppose this is little-endian layout, so this won't work on big-endian hosts. Can you add a qtest? > + } loc_state; > + uint32_t reserved1; > + uint32_t loc_ctrl; > + union { > + uint32_t reg; > + struct { > + unsigned granted:1; > + unsigned been_seized:1; > + } bits; This is unclear where you expect those bits (right/left aligned). Can you add an unnamed field to be more explicit? i.e. without using struct if left alignment expected: unsigned granted:1, been_seized:1, :30; > + } loc_sts; > + uint8_t reserved2[32]; > + union { > + uint64_t reg; > + struct { > + unsigned type:4; > + unsigned version:4; > + unsigned cap_locality:1; > + unsigned cap_crb_idle_bypass:1; > + unsigned reserved1:1; > + unsigned cap_data_xfer_size_support:2; > + unsigned cap_fifo:1; > + unsigned cap_crb:1; > + unsigned cap_if_res:2; > + unsigned if_selector:2; > + unsigned if_selector_lock:1; > + unsigned reserved2:4; > + unsigned rid:8; > + unsigned vid:16; > + unsigned did:16; > + } bits; > + } intf_id; > + uint64_t ctrl_ext; > + > + uint32_t ctrl_req; > + union { > + uint32_t reg; > + struct { > + unsigned tpm_sts:1; > + unsigned tpm_idle:1; > + unsigned reserved:30; Oh here you use 'reserved' to left align. > + } bits; > + } ctrl_sts; > + uint32_t ctrl_cancel; > + uint32_t ctrl_start; > + uint32_t ctrl_int_enable; > + uint32_t ctrl_int_sts; > + uint32_t ctrl_cmd_size; > + uint32_t ctrl_cmd_pa_low; > + uint32_t ctrl_cmd_pa_high; > + uint32_t ctrl_rsp_size; > + uint64_t ctrl_rsp_pa; > + uint8_t reserved3[0x10]; > +} QEMU_PACKED; > + > +#define TPM_CRB_ADDR_BASE 0xFED40000 > +#define TPM_CRB_ADDR_SIZE 0x1000 > +#define TPM_CRB_ADDR_CTRL \ > + (TPM_CRB_ADDR_BASE + offsetof(struct crb_regs, ctrl_req)) > + > #define TPM_LOG_AREA_MINIMUM_SIZE (64 * 1024) > > #define TPM_TCPA_ACPI_CLASS_CLIENT 0 > @@ -30,5 +101,6 @@ > #define TPM2_ACPI_CLASS_SERVER 1 > > #define TPM2_START_METHOD_MMIO 6 > +#define TPM2_START_METHOD_CRB 7 > > #endif /* HW_ACPI_TPM_H */ > diff --git a/include/sysemu/tpm.h b/include/sysemu/tpm.h > index ac04a9d4a2..233b1a3fc3 100644 > --- a/include/sysemu/tpm.h > +++ b/include/sysemu/tpm.h > @@ -46,9 +46,12 @@ typedef struct TPMIfClass { > } TPMIfClass; > > #define TYPE_TPM_TIS "tpm-tis" > +#define TYPE_TPM_CRB "tpm-crb" > > #define TPM_IS_TIS(chr) \ > object_dynamic_cast(OBJECT(chr), TYPE_TPM_TIS) > +#define TPM_IS_CRB(chr) \ > + object_dynamic_cast(OBJECT(chr), TYPE_TPM_CRB) > > /* returns NULL unless there is exactly one TPM device */ > static inline TPMIf *tpm_find(void) > diff --git a/hw/i386/acpi-build.c b/hw/i386/acpi-build.c > index dc4b2b9ffe..ed78c4ed9f 100644 > --- a/hw/i386/acpi-build.c > +++ b/hw/i386/acpi-build.c > @@ -2224,6 +2224,22 @@ build_dsdt(GArray *table_data, BIOSLinker *linker, > aml_append(sb_scope, scope); > } > } > + > + if (TPM_IS_CRB(tpm_find())) { > + dev = aml_device("TPM"); > + aml_append(dev, aml_name_decl("_HID", aml_string("MSFT0101"))); > + crs = aml_resource_template(); > + aml_append(crs, aml_memory32_fixed(TPM_CRB_ADDR_BASE, > + TPM_CRB_ADDR_SIZE, > AML_READ_WRITE)); > + aml_append(dev, aml_name_decl("_CRS", crs)); > + > + method = aml_method("_STA", 0, AML_NOTSERIALIZED); > + aml_append(method, aml_return(aml_int(0x0f))); > + aml_append(dev, method); > + > + aml_append(sb_scope, dev); > + } > + > aml_append(dsdt, sb_scope); > > /* copy AML table into ACPI tables blob and patch header there */ > @@ -2285,18 +2301,20 @@ build_tpm2(GArray *table_data, BIOSLinker *linker, > GArray *tcpalog) > if (TPM_IS_TIS(tpm_find())) { > tpm2_ptr->control_area_address = cpu_to_le64(0); > tpm2_ptr->start_method = cpu_to_le32(TPM2_START_METHOD_MMIO); > - > - tpm2_ptr->log_area_minimum_length = > - cpu_to_le32(TPM_LOG_AREA_MINIMUM_SIZE); > - > - /* log area start address to be filled by Guest linker */ > - bios_linker_loader_add_pointer(linker, > - ACPI_BUILD_TABLE_FILE, log_addr_offset, log_addr_size, > - ACPI_BUILD_TPMLOG_FILE, 0); > + } else if (TPM_IS_CRB(tpm_find())) { > + tpm2_ptr->control_area_address = cpu_to_le64(TPM_CRB_ADDR_CTRL); > + tpm2_ptr->start_method = cpu_to_le32(TPM2_START_METHOD_CRB); > } else { > g_warn_if_reached(); > } > > + tpm2_ptr->log_area_minimum_length = > + cpu_to_le32(TPM_LOG_AREA_MINIMUM_SIZE); > + > + /* log area start address to be filled by Guest linker */ > + bios_linker_loader_add_pointer(linker, ACPI_BUILD_TABLE_FILE, > + log_addr_offset, log_addr_size, > + ACPI_BUILD_TPMLOG_FILE, 0); > build_header(linker, table_data, > (void *)tpm2_ptr, "TPM2", sizeof(*tpm2_ptr), 4, NULL, NULL); > } > diff --git a/hw/tpm/tpm_crb.c b/hw/tpm/tpm_crb.c > new file mode 100644 > index 0000000000..908ca18d92 > --- /dev/null > +++ b/hw/tpm/tpm_crb.c > @@ -0,0 +1,327 @@ > +/* > + * tpm_crb.c - QEMU's TPM CRB interface emulator > + * > + * Copyright (c) 2017 Red Hat, Inc. > + * > + * Authors: > + * Marc-André Lureau <marcandre.lur...@redhat.com> > + * > + * This work is licensed under the terms of the GNU GPL, version 2 or later. > + * See the COPYING file in the top-level directory. > + * > + * tpm_crb is a device for TPM 2.0 Command Response Buffer (CRB) Interface > + * as defined in TCG PC Client Platform TPM Profile (PTP) Specification > + * Family “2.0” Level 00 Revision 01.03 v22 > + */ > + > +#include "qemu/osdep.h" > + > +#include "qemu-common.h" > +#include "qapi/error.h" > +#include "hw/sysbus.h" > +#include "exec/address-spaces.h" > + > +#include "hw/pci/pci_ids.h" > +#include "hw/acpi/tpm.h" > +#include "sysemu/tpm_backend.h" > +#include "tpm_int.h" > +#include "tpm_util.h" > + > +typedef struct CRBState { > + SysBusDevice parent_obj; > + > + TPMBackend *tpmbe; > + TPMBackendCmd cmd; > + struct crb_regs regs; > + MemoryRegion mmio; > + MemoryRegion cmdmem; > + > + size_t be_buffer_size; > +} CRBState; > + > +#define CRB(obj) OBJECT_CHECK(CRBState, (obj), TYPE_TPM_CRB) > + > +#define DEBUG_CRB 0 > + > +#define DPRINTF(fmt, ...) do { \ > + if (DEBUG_CRB) { \ > + printf(fmt, ## __VA_ARGS__); \ > + } \ > + } while (0) > + > +#define CRB_ADDR_LOC_STATE offsetof(struct crb_regs, loc_state) > +#define CRB_ADDR_LOC_CTRL offsetof(struct crb_regs, loc_ctrl) > +#define CRB_ADDR_CTRL_REQ offsetof(struct crb_regs, ctrl_req) > +#define CRB_ADDR_CTRL_CANCEL offsetof(struct crb_regs, ctrl_cancel) > +#define CRB_ADDR_CTRL_START offsetof(struct crb_regs, ctrl_start) > + > +#define CRB_INTF_TYPE_CRB_ACTIVE 0b1 > +#define CRB_INTF_VERSION_CRB 0b1 > +#define CRB_INTF_CAP_LOCALITY_0_ONLY 0b0 > +#define CRB_INTF_CAP_IDLE_FAST 0b0 > +#define CRB_INTF_CAP_XFER_SIZE_64 0b11 > +#define CRB_INTF_CAP_FIFO_NOT_SUPPORTED 0b0 > +#define CRB_INTF_CAP_CRB_SUPPORTED 0b1 > +#define CRB_INTF_IF_SELECTOR_CRB 0b1 > +#define CRB_INTF_IF_SELECTOR_UNLOCKED 0b0 > + > +#define CRB_CTRL_CMD_SIZE (TPM_CRB_ADDR_SIZE - sizeof(struct crb_regs)) > + > +enum crb_loc_ctrl { > + CRB_LOC_CTRL_REQUEST_ACCESS = BIT(0), > + CRB_LOC_CTRL_RELINQUISH = BIT(1), > + CRB_LOC_CTRL_SEIZE = BIT(2), > + CRB_LOC_CTRL_RESET_ESTABLISHMENT_BIT = BIT(3), > +}; > + > +enum crb_ctrl_req { > + CRB_CTRL_REQ_CMD_READY = BIT(0), > + CRB_CTRL_REQ_GO_IDLE = BIT(1), > +}; > + > +enum crb_start { > + CRB_START_INVOKE = BIT(0), > +}; > + > +enum crb_cancel { > + CRB_CANCEL_INVOKE = BIT(0), > +}; > + > +static const char *addr_desc(unsigned off) > +{ > + struct crb_regs crb; > + > + switch (off) { > +#define CASE(field) \ > + case offsetof(struct crb_regs, field) ... \ > + offsetof(struct crb_regs, field) + sizeof(crb.field) - 1: \ > + return G_STRINGIFY(field); > + CASE(loc_state); > + CASE(reserved1); > + CASE(loc_ctrl); > + CASE(loc_sts); > + CASE(reserved2); > + CASE(intf_id); > + CASE(ctrl_ext); > + CASE(ctrl_req); > + CASE(ctrl_sts); > + CASE(ctrl_cancel); > + CASE(ctrl_start); > + CASE(ctrl_int_enable); > + CASE(ctrl_int_sts); > + CASE(ctrl_cmd_size); > + CASE(ctrl_cmd_pa_low); > + CASE(ctrl_cmd_pa_high); > + CASE(ctrl_rsp_size); > + CASE(ctrl_rsp_pa); > +#undef CASE > + } > + return NULL; > +} > + > +static uint64_t tpm_crb_mmio_read(void *opaque, hwaddr addr, > + unsigned size) > +{ > + CRBState *s = CRB(opaque); > + DPRINTF("CRB read 0x%lx:%s len:%u\n", > + addr, addr_desc(addr), size); > + void *regs = (void *)&s->regs + addr; > + > + switch (size) { > + case 1: > + return *(uint8_t *)regs; > + case 2: > + return *(uint16_t *)regs; > + case 4: > + return *(uint32_t *)regs; > + default: > + g_return_val_if_reached(-1); > + } > +} > + > +static void tpm_crb_mmio_write(void *opaque, hwaddr addr, > + uint64_t val, unsigned size) > +{ > + CRBState *s = CRB(opaque); > + DPRINTF("CRB write 0x%lx:%s len:%u val:%lu\n", > + addr, addr_desc(addr), size, val); > + > + switch (addr) { > + case CRB_ADDR_CTRL_REQ: > + switch (val) { > + case CRB_CTRL_REQ_CMD_READY: > + s->regs.ctrl_sts.bits.tpm_idle = 0; > + break; > + case CRB_CTRL_REQ_GO_IDLE: > + s->regs.ctrl_sts.bits.tpm_idle = 1; > + break; > + } > + break; > + case CRB_ADDR_CTRL_CANCEL: > + if (val == CRB_CANCEL_INVOKE && s->regs.ctrl_start & > CRB_START_INVOKE) { > + tpm_backend_cancel_cmd(s->tpmbe); > + } > + break; > + case CRB_ADDR_CTRL_START: > + if (val == CRB_START_INVOKE && > + !(s->regs.ctrl_start & CRB_START_INVOKE)) { > + void *mem = memory_region_get_ram_ptr(&s->cmdmem); > + > + s->regs.ctrl_start |= CRB_START_INVOKE; > + s->cmd = (TPMBackendCmd) { > + .in = mem, > + .in_len = MIN(tpm_cmd_get_size(mem), s->be_buffer_size), > + .out = mem, > + .out_len = s->be_buffer_size, > + }; > + > + tpm_backend_deliver_request(s->tpmbe, &s->cmd); > + } > + break; > + case CRB_ADDR_LOC_CTRL: > + switch (val) { > + case CRB_LOC_CTRL_RESET_ESTABLISHMENT_BIT: > + /* not loc 3 or 4 */ > + break; > + case CRB_LOC_CTRL_RELINQUISH: > + break; > + case CRB_LOC_CTRL_REQUEST_ACCESS: > + s->regs.loc_sts.bits.granted = 1; > + s->regs.loc_sts.bits.been_seized = 0; > + s->regs.loc_state.bits.loc_assigned = 1; > + s->regs.loc_state.bits.tpm_reg_valid_sts = 1; > + break; > + } > + break; > + } > +} > + > +static const MemoryRegionOps tpm_crb_memory_ops = { > + .read = tpm_crb_mmio_read, > + .write = tpm_crb_mmio_write, > + .endianness = DEVICE_LITTLE_ENDIAN, > + .valid = { > + .min_access_size = 1, > + .max_access_size = 4, > + }, > +}; > + > +static void tpm_crb_reset(DeviceState *dev) > +{ > + CRBState *s = CRB(dev); > + > + tpm_backend_reset(s->tpmbe); > + > + s->be_buffer_size = MIN(tpm_backend_get_buffer_size(s->tpmbe), > + CRB_CTRL_CMD_SIZE); > + > + s->regs = (struct crb_regs) { > + .intf_id.bits = { > + .type = CRB_INTF_TYPE_CRB_ACTIVE, > + .version = CRB_INTF_VERSION_CRB, > + .cap_locality = CRB_INTF_CAP_LOCALITY_0_ONLY, > + .cap_crb_idle_bypass = CRB_INTF_CAP_IDLE_FAST, > + .cap_data_xfer_size_support = CRB_INTF_CAP_XFER_SIZE_64, > + .cap_fifo = CRB_INTF_CAP_FIFO_NOT_SUPPORTED, > + .cap_crb = CRB_INTF_CAP_CRB_SUPPORTED, > + .cap_if_res = 0b0, > + .if_selector = CRB_INTF_IF_SELECTOR_CRB, > + .if_selector_lock = CRB_INTF_IF_SELECTOR_UNLOCKED, > + .rid = 0b0001, > + .vid = PCI_VENDOR_ID_IBM, > + .did = 0b0001, > + }, > + .ctrl_cmd_size = CRB_CTRL_CMD_SIZE, > + .ctrl_cmd_pa_low = TPM_CRB_ADDR_BASE + sizeof(struct crb_regs), > + .ctrl_rsp_size = CRB_CTRL_CMD_SIZE, > + .ctrl_rsp_pa = TPM_CRB_ADDR_BASE + sizeof(struct crb_regs), > + }; > + > + tpm_backend_startup_tpm(s->tpmbe, s->be_buffer_size); > +} > + > +static void tpm_crb_request_completed(TPMIf *ti, int ret) > +{ > + CRBState *s = CRB(ti); > + > + s->regs.ctrl_start &= ~CRB_START_INVOKE; > + if (ret != 0) { > + s->regs.ctrl_sts.bits.tpm_sts = 1; /* fatal error */ > + } > +} > + > +static enum TPMVersion tpm_crb_get_version(TPMIf *ti) > +{ > + CRBState *s = CRB(ti); > + > + return tpm_backend_get_tpm_version(s->tpmbe); > +} > + > +static const VMStateDescription vmstate_tpm_crb = { > + .name = "tpm-crb", > + .unmigratable = 1, > +}; > + > +static Property tpm_crb_properties[] = { > + DEFINE_PROP_TPMBE("tpmdev", CRBState, tpmbe), > + DEFINE_PROP_END_OF_LIST(), > +}; > + > +static void tpm_crb_realizefn(DeviceState *dev, Error **errp) > +{ > + CRBState *s = CRB(dev); > + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); > + > + if (!tpm_find()) { > + error_setg(errp, "at most one TPM device is permitted"); > + return; > + } > + if (!s->tpmbe) { > + error_setg(errp, "'tpmdev' property is required"); > + return; > + } > + > + memory_region_init_io(&s->mmio, OBJECT(s), &tpm_crb_memory_ops, s, > + "tpm-crb-mmio", sizeof(struct crb_regs)); > + memory_region_init_ram(&s->cmdmem, OBJECT(s), > + "tpm-crb-cmd", CRB_CTRL_CMD_SIZE, errp); > + > + sysbus_init_mmio(sbd, &s->mmio); > + sysbus_mmio_map(sbd, 0, TPM_CRB_ADDR_BASE); > + /* allocate ram in bios instead? */ > + memory_region_add_subregion(get_system_memory(), > + TPM_CRB_ADDR_BASE + sizeof(struct crb_regs), &s->cmdmem); > +} > + > +static void tpm_crb_class_init(ObjectClass *klass, void *data) > +{ > + DeviceClass *dc = DEVICE_CLASS(klass); > + TPMIfClass *tc = TPM_IF_CLASS(klass); > + > + dc->realize = tpm_crb_realizefn; > + dc->props = tpm_crb_properties; > + dc->reset = tpm_crb_reset; > + dc->vmsd = &vmstate_tpm_crb; > + dc->user_creatable = true; > + tc->model = TPM_MODEL_TPM_CRB; > + tc->get_version = tpm_crb_get_version; > + tc->request_completed = tpm_crb_request_completed; > +} > + > +static const TypeInfo tpm_crb_info = { > + .name = TYPE_TPM_CRB, > + .parent = TYPE_SYS_BUS_DEVICE, > + .instance_size = sizeof(CRBState), > + .class_init = tpm_crb_class_init, > + .interfaces = (InterfaceInfo[]) { > + { TYPE_TPM_IF }, > + { } > + } > +}; > + > +static void tpm_crb_register(void) > +{ > + type_register_static(&tpm_crb_info); > +} > + > +type_init(tpm_crb_register) > diff --git a/default-configs/i386-softmmu.mak > b/default-configs/i386-softmmu.mak > index 95ac4b464a..ac27700e79 100644 > --- a/default-configs/i386-softmmu.mak > +++ b/default-configs/i386-softmmu.mak > @@ -37,6 +37,7 @@ CONFIG_APPLESMC=y > CONFIG_I8259=y > CONFIG_PFLASH_CFI01=y > CONFIG_TPM_TIS=$(CONFIG_TPM) > +CONFIG_TPM_CRB=$(CONFIG_TPM) > CONFIG_MC146818RTC=y > CONFIG_PCI_PIIX=y > CONFIG_WDT_IB700=y > diff --git a/default-configs/x86_64-softmmu.mak > b/default-configs/x86_64-softmmu.mak > index 0221236825..b2104ade19 100644 > --- a/default-configs/x86_64-softmmu.mak > +++ b/default-configs/x86_64-softmmu.mak > @@ -37,6 +37,7 @@ CONFIG_APPLESMC=y > CONFIG_I8259=y > CONFIG_PFLASH_CFI01=y > CONFIG_TPM_TIS=$(CONFIG_TPM) > +CONFIG_TPM_CRB=$(CONFIG_TPM) > CONFIG_MC146818RTC=y > CONFIG_PCI_PIIX=y > CONFIG_WDT_IB700=y > diff --git a/hw/tpm/Makefile.objs b/hw/tpm/Makefile.objs > index 7a93b24636..1dc9f8bf2c 100644 > --- a/hw/tpm/Makefile.objs > +++ b/hw/tpm/Makefile.objs > @@ -1,4 +1,5 @@ > common-obj-y += tpm_util.o > common-obj-$(CONFIG_TPM_TIS) += tpm_tis.o > +common-obj-$(CONFIG_TPM_CRB) += tpm_crb.o > common-obj-$(CONFIG_TPM_PASSTHROUGH) += tpm_passthrough.o > common-obj-$(CONFIG_TPM_EMULATOR) += tpm_emulator.o >