aboutsummaryrefslogtreecommitdiffstats
path: root/hw/tpm
diff options
context:
space:
mode:
Diffstat (limited to 'hw/tpm')
-rw-r--r--hw/tpm/Kconfig25
-rw-r--r--hw/tpm/meson.build8
-rw-r--r--hw/tpm/tpm_crb.c345
-rw-r--r--hw/tpm/tpm_ppi.c57
-rw-r--r--hw/tpm/tpm_ppi.h45
-rw-r--r--hw/tpm/tpm_prop.h33
-rw-r--r--hw/tpm/tpm_spapr.c430
-rw-r--r--hw/tpm/tpm_tis.h90
-rw-r--r--hw/tpm/tpm_tis_common.c867
-rw-r--r--hw/tpm/tpm_tis_isa.c173
-rw-r--r--hw/tpm/tpm_tis_sysbus.c162
-rw-r--r--hw/tpm/trace-events38
-rw-r--r--hw/tpm/trace.h1
13 files changed, 2274 insertions, 0 deletions
diff --git a/hw/tpm/Kconfig b/hw/tpm/Kconfig
new file mode 100644
index 000000000..29e82f3c9
--- /dev/null
+++ b/hw/tpm/Kconfig
@@ -0,0 +1,25 @@
+config TPM_TIS_ISA
+ bool
+ depends on TPM && ISA_BUS
+ select TPM_TIS
+
+config TPM_TIS_SYSBUS
+ bool
+ depends on TPM
+ select TPM_TIS
+
+config TPM_TIS
+ bool
+ depends on TPM
+ select TPM_BACKEND
+
+config TPM_CRB
+ bool
+ depends on TPM && PC
+ select TPM_BACKEND
+
+config TPM_SPAPR
+ bool
+ default y
+ depends on TPM && PSERIES
+ select TPM_BACKEND
diff --git a/hw/tpm/meson.build b/hw/tpm/meson.build
new file mode 100644
index 000000000..1c68d81d6
--- /dev/null
+++ b/hw/tpm/meson.build
@@ -0,0 +1,8 @@
+softmmu_ss.add(when: 'CONFIG_TPM_TIS', if_true: files('tpm_tis_common.c'))
+softmmu_ss.add(when: 'CONFIG_TPM_TIS_ISA', if_true: files('tpm_tis_isa.c'))
+softmmu_ss.add(when: 'CONFIG_TPM_TIS_SYSBUS', if_true: files('tpm_tis_sysbus.c'))
+softmmu_ss.add(when: 'CONFIG_TPM_CRB', if_true: files('tpm_crb.c'))
+
+specific_ss.add(when: ['CONFIG_SOFTMMU', 'CONFIG_TPM_TIS'], if_true: files('tpm_ppi.c'))
+specific_ss.add(when: ['CONFIG_SOFTMMU', 'CONFIG_TPM_CRB'], if_true: files('tpm_ppi.c'))
+specific_ss.add(when: 'CONFIG_TPM_SPAPR', if_true: files('tpm_spapr.c'))
diff --git a/hw/tpm/tpm_crb.c b/hw/tpm/tpm_crb.c
new file mode 100644
index 000000000..58ebd1469
--- /dev/null
+++ b/hw/tpm/tpm_crb.c
@@ -0,0 +1,345 @@
+/*
+ * tpm_crb.c - QEMU's TPM CRB interface emulator
+ *
+ * Copyright (c) 2018 Red Hat, Inc.
+ *
+ * Authors:
+ * Marc-André Lureau <marcandre.lureau@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/module.h"
+#include "qapi/error.h"
+#include "hw/qdev-properties.h"
+#include "hw/pci/pci_ids.h"
+#include "hw/acpi/tpm.h"
+#include "migration/vmstate.h"
+#include "sysemu/tpm_backend.h"
+#include "sysemu/tpm_util.h"
+#include "sysemu/reset.h"
+#include "tpm_prop.h"
+#include "tpm_ppi.h"
+#include "trace.h"
+#include "qom/object.h"
+
+struct CRBState {
+ DeviceState parent_obj;
+
+ TPMBackend *tpmbe;
+ TPMBackendCmd cmd;
+ uint32_t regs[TPM_CRB_R_MAX];
+ MemoryRegion mmio;
+ MemoryRegion cmdmem;
+
+ size_t be_buffer_size;
+
+ bool ppi_enabled;
+ TPMPPI ppi;
+};
+typedef struct CRBState CRBState;
+
+DECLARE_INSTANCE_CHECKER(CRBState, CRB,
+ TYPE_TPM_CRB)
+
+#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_CTRL_CMD_SIZE (TPM_CRB_ADDR_SIZE - A_CRB_DATA_BUFFER)
+
+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),
+};
+
+#define TPM_CRB_NO_LOCALITY 0xff
+
+static uint64_t tpm_crb_mmio_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ CRBState *s = CRB(opaque);
+ void *regs = (void *)&s->regs + (addr & ~3);
+ unsigned offset = addr & 3;
+ uint32_t val = *(uint32_t *)regs >> (8 * offset);
+
+ switch (addr) {
+ case A_CRB_LOC_STATE:
+ val |= !tpm_backend_get_tpm_established_flag(s->tpmbe);
+ break;
+ }
+
+ trace_tpm_crb_mmio_read(addr, size, val);
+
+ return val;
+}
+
+static uint8_t tpm_crb_get_active_locty(CRBState *s)
+{
+ if (!ARRAY_FIELD_EX32(s->regs, CRB_LOC_STATE, locAssigned)) {
+ return TPM_CRB_NO_LOCALITY;
+ }
+ return ARRAY_FIELD_EX32(s->regs, CRB_LOC_STATE, activeLocality);
+}
+
+static void tpm_crb_mmio_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ CRBState *s = CRB(opaque);
+ uint8_t locty = addr >> 12;
+
+ trace_tpm_crb_mmio_write(addr, size, val);
+
+ switch (addr) {
+ case A_CRB_CTRL_REQ:
+ switch (val) {
+ case CRB_CTRL_REQ_CMD_READY:
+ ARRAY_FIELD_DP32(s->regs, CRB_CTRL_STS,
+ tpmIdle, 0);
+ break;
+ case CRB_CTRL_REQ_GO_IDLE:
+ ARRAY_FIELD_DP32(s->regs, CRB_CTRL_STS,
+ tpmIdle, 1);
+ break;
+ }
+ break;
+ case A_CRB_CTRL_CANCEL:
+ if (val == CRB_CANCEL_INVOKE &&
+ s->regs[R_CRB_CTRL_START] & CRB_START_INVOKE) {
+ tpm_backend_cancel_cmd(s->tpmbe);
+ }
+ break;
+ case A_CRB_CTRL_START:
+ if (val == CRB_START_INVOKE &&
+ !(s->regs[R_CRB_CTRL_START] & CRB_START_INVOKE) &&
+ tpm_crb_get_active_locty(s) == locty) {
+ void *mem = memory_region_get_ram_ptr(&s->cmdmem);
+
+ s->regs[R_CRB_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 A_CRB_LOC_CTRL:
+ switch (val) {
+ case CRB_LOC_CTRL_RESET_ESTABLISHMENT_BIT:
+ /* not loc 3 or 4 */
+ break;
+ case CRB_LOC_CTRL_RELINQUISH:
+ ARRAY_FIELD_DP32(s->regs, CRB_LOC_STATE,
+ locAssigned, 0);
+ ARRAY_FIELD_DP32(s->regs, CRB_LOC_STS,
+ Granted, 0);
+ break;
+ case CRB_LOC_CTRL_REQUEST_ACCESS:
+ ARRAY_FIELD_DP32(s->regs, CRB_LOC_STS,
+ Granted, 1);
+ ARRAY_FIELD_DP32(s->regs, CRB_LOC_STS,
+ beenSeized, 0);
+ ARRAY_FIELD_DP32(s->regs, CRB_LOC_STATE,
+ locAssigned, 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_request_completed(TPMIf *ti, int ret)
+{
+ CRBState *s = CRB(ti);
+
+ s->regs[R_CRB_CTRL_START] &= ~CRB_START_INVOKE;
+ if (ret != 0) {
+ ARRAY_FIELD_DP32(s->regs, CRB_CTRL_STS,
+ tpmSts, 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 int tpm_crb_pre_save(void *opaque)
+{
+ CRBState *s = opaque;
+
+ tpm_backend_finish_sync(s->tpmbe);
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_tpm_crb = {
+ .name = "tpm-crb",
+ .pre_save = tpm_crb_pre_save,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, CRBState, TPM_CRB_R_MAX),
+ VMSTATE_END_OF_LIST(),
+ }
+};
+
+static Property tpm_crb_properties[] = {
+ DEFINE_PROP_TPMBE("tpmdev", CRBState, tpmbe),
+ DEFINE_PROP_BOOL("ppi", CRBState, ppi_enabled, true),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void tpm_crb_reset(void *dev)
+{
+ CRBState *s = CRB(dev);
+
+ if (s->ppi_enabled) {
+ tpm_ppi_reset(&s->ppi);
+ }
+ tpm_backend_reset(s->tpmbe);
+
+ memset(s->regs, 0, sizeof(s->regs));
+
+ ARRAY_FIELD_DP32(s->regs, CRB_LOC_STATE,
+ tpmRegValidSts, 1);
+ ARRAY_FIELD_DP32(s->regs, CRB_CTRL_STS,
+ tpmIdle, 1);
+ ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID,
+ InterfaceType, CRB_INTF_TYPE_CRB_ACTIVE);
+ ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID,
+ InterfaceVersion, CRB_INTF_VERSION_CRB);
+ ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID,
+ CapLocality, CRB_INTF_CAP_LOCALITY_0_ONLY);
+ ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID,
+ CapCRBIdleBypass, CRB_INTF_CAP_IDLE_FAST);
+ ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID,
+ CapDataXferSizeSupport, CRB_INTF_CAP_XFER_SIZE_64);
+ ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID,
+ CapFIFO, CRB_INTF_CAP_FIFO_NOT_SUPPORTED);
+ ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID,
+ CapCRB, CRB_INTF_CAP_CRB_SUPPORTED);
+ ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID,
+ InterfaceSelector, CRB_INTF_IF_SELECTOR_CRB);
+ ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID,
+ RID, 0b0000);
+ ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID2,
+ VID, PCI_VENDOR_ID_IBM);
+
+ s->regs[R_CRB_CTRL_CMD_SIZE] = CRB_CTRL_CMD_SIZE;
+ s->regs[R_CRB_CTRL_CMD_LADDR] = TPM_CRB_ADDR_BASE + A_CRB_DATA_BUFFER;
+ s->regs[R_CRB_CTRL_RSP_SIZE] = CRB_CTRL_CMD_SIZE;
+ s->regs[R_CRB_CTRL_RSP_ADDR] = TPM_CRB_ADDR_BASE + A_CRB_DATA_BUFFER;
+
+ s->be_buffer_size = MIN(tpm_backend_get_buffer_size(s->tpmbe),
+ CRB_CTRL_CMD_SIZE);
+
+ if (tpm_backend_startup_tpm(s->tpmbe, s->be_buffer_size) < 0) {
+ exit(1);
+ }
+}
+
+static void tpm_crb_realize(DeviceState *dev, Error **errp)
+{
+ CRBState *s = CRB(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(s->regs));
+ memory_region_init_ram(&s->cmdmem, OBJECT(s),
+ "tpm-crb-cmd", CRB_CTRL_CMD_SIZE, errp);
+
+ memory_region_add_subregion(get_system_memory(),
+ TPM_CRB_ADDR_BASE, &s->mmio);
+ memory_region_add_subregion(get_system_memory(),
+ TPM_CRB_ADDR_BASE + sizeof(s->regs), &s->cmdmem);
+
+ if (s->ppi_enabled) {
+ tpm_ppi_init(&s->ppi, get_system_memory(),
+ TPM_PPI_ADDR_BASE, OBJECT(s));
+ }
+
+ qemu_register_reset(tpm_crb_reset, dev);
+}
+
+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_realize;
+ device_class_set_props(dc, tpm_crb_properties);
+ 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;
+
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+}
+
+static const TypeInfo tpm_crb_info = {
+ .name = TYPE_TPM_CRB,
+ /* could be TYPE_SYS_BUS_DEVICE (or LPC etc) */
+ .parent = TYPE_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/hw/tpm/tpm_ppi.c b/hw/tpm/tpm_ppi.c
new file mode 100644
index 000000000..274e9aa4b
--- /dev/null
+++ b/hw/tpm/tpm_ppi.c
@@ -0,0 +1,57 @@
+/*
+ * tpm_ppi.c - TPM Physical Presence Interface
+ *
+ * Copyright (C) 2018 IBM Corporation
+ *
+ * Authors:
+ * Stefan Berger <stefanb@us.ibm.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.
+ *
+ */
+
+#include "qemu/osdep.h"
+
+#include "qapi/error.h"
+#include "cpu.h"
+#include "sysemu/memory_mapping.h"
+#include "migration/vmstate.h"
+#include "hw/acpi/tpm.h"
+#include "tpm_ppi.h"
+#include "trace.h"
+
+void tpm_ppi_reset(TPMPPI *tpmppi)
+{
+ if (tpmppi->buf[0x15a /* movv, docs/specs/tpm.rst */] & 0x1) {
+ GuestPhysBlockList guest_phys_blocks;
+ GuestPhysBlock *block;
+
+ guest_phys_blocks_init(&guest_phys_blocks);
+ guest_phys_blocks_append(&guest_phys_blocks);
+ QTAILQ_FOREACH(block, &guest_phys_blocks.head, next) {
+ hwaddr mr_offs = block->host_addr -
+ (uint8_t *)memory_region_get_ram_ptr(block->mr);
+
+ trace_tpm_ppi_memset(block->host_addr,
+ block->target_end - block->target_start);
+ memset(block->host_addr, 0,
+ block->target_end - block->target_start);
+ memory_region_set_dirty(block->mr, mr_offs,
+ block->target_end - block->target_start);
+ }
+ guest_phys_blocks_free(&guest_phys_blocks);
+ }
+}
+
+void tpm_ppi_init(TPMPPI *tpmppi, struct MemoryRegion *m,
+ hwaddr addr, Object *obj)
+{
+ tpmppi->buf = qemu_memalign(qemu_real_host_page_size,
+ HOST_PAGE_ALIGN(TPM_PPI_ADDR_SIZE));
+ memory_region_init_ram_device_ptr(&tpmppi->ram, obj, "tpm-ppi",
+ TPM_PPI_ADDR_SIZE, tpmppi->buf);
+ vmstate_register_ram(&tpmppi->ram, DEVICE(obj));
+
+ memory_region_add_subregion(m, addr, &tpmppi->ram);
+}
diff --git a/hw/tpm/tpm_ppi.h b/hw/tpm/tpm_ppi.h
new file mode 100644
index 000000000..6f773c25a
--- /dev/null
+++ b/hw/tpm/tpm_ppi.h
@@ -0,0 +1,45 @@
+/*
+ * TPM Physical Presence Interface
+ *
+ * Copyright (C) 2018 IBM Corporation
+ *
+ * Authors:
+ * Stefan Berger <stefanb@us.ibm.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.
+ */
+#ifndef TPM_TPM_PPI_H
+#define TPM_TPM_PPI_H
+
+#include "exec/address-spaces.h"
+
+typedef struct TPMPPI {
+ MemoryRegion ram;
+ uint8_t *buf;
+} TPMPPI;
+
+/**
+ * tpm_ppi_init:
+ * @tpmppi: a TPMPPI
+ * @m: the address-space / MemoryRegion to use
+ * @addr: the address of the PPI region
+ * @obj: the owner object
+ *
+ * Register the TPM PPI memory region at @addr on the given address
+ * space for the object @obj.
+ **/
+void tpm_ppi_init(TPMPPI *tpmppi, struct MemoryRegion *m,
+ hwaddr addr, Object *obj);
+
+/**
+ * tpm_ppi_reset:
+ * @tpmppi: a TPMPPI
+ *
+ * Function to call on machine reset. It will check if the "Memory
+ * overwrite" variable is set, and perform a memory clear on volatile
+ * memory if requested.
+ **/
+void tpm_ppi_reset(TPMPPI *tpmppi);
+
+#endif /* TPM_TPM_PPI_H */
diff --git a/hw/tpm/tpm_prop.h b/hw/tpm/tpm_prop.h
new file mode 100644
index 000000000..bbd4225d6
--- /dev/null
+++ b/hw/tpm/tpm_prop.h
@@ -0,0 +1,33 @@
+/*
+ * TPM utility functions
+ *
+ * Copyright (c) 2010 - 2015 IBM Corporation
+ * Authors:
+ * Stefan Berger <stefanb@us.ibm.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>
+ */
+
+#ifndef HW_TPM_PROP_H
+#define HW_TPM_PROP_H
+
+#include "sysemu/tpm_backend.h"
+#include "hw/qdev-properties.h"
+
+extern const PropertyInfo qdev_prop_tpm;
+
+#define DEFINE_PROP_TPMBE(_n, _s, _f) \
+ DEFINE_PROP(_n, _s, _f, qdev_prop_tpm, TPMBackend *)
+
+#endif /* HW_TPM_PROP_H */
diff --git a/hw/tpm/tpm_spapr.c b/hw/tpm/tpm_spapr.c
new file mode 100644
index 000000000..dea7b1333
--- /dev/null
+++ b/hw/tpm/tpm_spapr.c
@@ -0,0 +1,430 @@
+/*
+ * QEMU PowerPC pSeries Logical Partition (aka sPAPR) hardware System Emulator
+ *
+ * PAPR Virtual TPM
+ *
+ * Copyright (c) 2015, 2017, 2019 IBM Corporation.
+ *
+ * Authors:
+ * Stefan Berger <stefanb@linux.vnet.ibm.com>
+ *
+ * This code is licensed under the GPL version 2 or later. See the
+ * COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/error-report.h"
+#include "qapi/error.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+
+#include "sysemu/tpm_backend.h"
+#include "sysemu/tpm_util.h"
+#include "tpm_prop.h"
+
+#include "hw/ppc/spapr.h"
+#include "hw/ppc/spapr_vio.h"
+#include "trace.h"
+#include "qom/object.h"
+
+#define DEBUG_SPAPR 0
+
+typedef struct SpaprTpmState SpaprTpmState;
+DECLARE_INSTANCE_CHECKER(SpaprTpmState, VIO_SPAPR_VTPM,
+ TYPE_TPM_SPAPR)
+
+typedef struct TpmCrq {
+ uint8_t valid; /* 0x80: cmd; 0xc0: init crq */
+ /* 0x81-0x83: CRQ message response */
+ uint8_t msg; /* see below */
+ uint16_t len; /* len of TPM request; len of TPM response */
+ uint32_t data; /* rtce_dma_handle when sending TPM request */
+ uint64_t reserved;
+} TpmCrq;
+
+#define SPAPR_VTPM_VALID_INIT_CRQ_COMMAND 0xC0
+#define SPAPR_VTPM_VALID_COMMAND 0x80
+#define SPAPR_VTPM_MSG_RESULT 0x80
+
+/* msg types for valid = SPAPR_VTPM_VALID_INIT_CRQ */
+#define SPAPR_VTPM_INIT_CRQ_RESULT 0x1
+#define SPAPR_VTPM_INIT_CRQ_COMPLETE_RESULT 0x2
+
+/* msg types for valid = SPAPR_VTPM_VALID_CMD */
+#define SPAPR_VTPM_GET_VERSION 0x1
+#define SPAPR_VTPM_TPM_COMMAND 0x2
+#define SPAPR_VTPM_GET_RTCE_BUFFER_SIZE 0x3
+#define SPAPR_VTPM_PREPARE_TO_SUSPEND 0x4
+
+/* response error messages */
+#define SPAPR_VTPM_VTPM_ERROR 0xff
+
+/* error codes */
+#define SPAPR_VTPM_ERR_COPY_IN_FAILED 0x3
+#define SPAPR_VTPM_ERR_COPY_OUT_FAILED 0x4
+
+#define TPM_SPAPR_BUFFER_MAX 4096
+
+struct SpaprTpmState {
+ SpaprVioDevice vdev;
+
+ TpmCrq crq; /* track single TPM command */
+
+ uint8_t state;
+#define SPAPR_VTPM_STATE_NONE 0
+#define SPAPR_VTPM_STATE_EXECUTION 1
+#define SPAPR_VTPM_STATE_COMPLETION 2
+
+ unsigned char *buffer;
+
+ uint32_t numbytes; /* number of bytes to deliver on resume */
+
+ TPMBackendCmd cmd;
+
+ TPMBackend *be_driver;
+ TPMVersion be_tpm_version;
+
+ size_t be_buffer_size;
+};
+
+/*
+ * Send a request to the TPM.
+ */
+static void tpm_spapr_tpm_send(SpaprTpmState *s)
+{
+ tpm_util_show_buffer(s->buffer, s->be_buffer_size, "To TPM");
+
+ s->state = SPAPR_VTPM_STATE_EXECUTION;
+ s->cmd = (TPMBackendCmd) {
+ .locty = 0,
+ .in = s->buffer,
+ .in_len = MIN(tpm_cmd_get_size(s->buffer), s->be_buffer_size),
+ .out = s->buffer,
+ .out_len = s->be_buffer_size,
+ };
+
+ tpm_backend_deliver_request(s->be_driver, &s->cmd);
+}
+
+static int tpm_spapr_process_cmd(SpaprTpmState *s, uint64_t dataptr)
+{
+ long rc;
+
+ /* a max. of be_buffer_size bytes can be transported */
+ rc = spapr_vio_dma_read(&s->vdev, dataptr,
+ s->buffer, s->be_buffer_size);
+ if (rc) {
+ error_report("tpm_spapr_got_payload: DMA read failure");
+ }
+ /* let vTPM handle any malformed request */
+ tpm_spapr_tpm_send(s);
+
+ return rc;
+}
+
+static inline int spapr_tpm_send_crq(struct SpaprVioDevice *dev, TpmCrq *crq)
+{
+ return spapr_vio_send_crq(dev, (uint8_t *)crq);
+}
+
+static int tpm_spapr_do_crq(struct SpaprVioDevice *dev, uint8_t *crq_data)
+{
+ SpaprTpmState *s = VIO_SPAPR_VTPM(dev);
+ TpmCrq local_crq;
+ TpmCrq *crq = &s->crq; /* requests only */
+ int rc;
+ uint8_t valid = crq_data[0];
+ uint8_t msg = crq_data[1];
+
+ trace_tpm_spapr_do_crq(valid, msg);
+
+ switch (valid) {
+ case SPAPR_VTPM_VALID_INIT_CRQ_COMMAND: /* Init command/response */
+
+ /* Respond to initialization request */
+ switch (msg) {
+ case SPAPR_VTPM_INIT_CRQ_RESULT:
+ trace_tpm_spapr_do_crq_crq_result();
+ memset(&local_crq, 0, sizeof(local_crq));
+ local_crq.valid = SPAPR_VTPM_VALID_INIT_CRQ_COMMAND;
+ local_crq.msg = SPAPR_VTPM_INIT_CRQ_RESULT;
+ spapr_tpm_send_crq(dev, &local_crq);
+ break;
+
+ case SPAPR_VTPM_INIT_CRQ_COMPLETE_RESULT:
+ trace_tpm_spapr_do_crq_crq_complete_result();
+ memset(&local_crq, 0, sizeof(local_crq));
+ local_crq.valid = SPAPR_VTPM_VALID_INIT_CRQ_COMMAND;
+ local_crq.msg = SPAPR_VTPM_INIT_CRQ_COMPLETE_RESULT;
+ spapr_tpm_send_crq(dev, &local_crq);
+ break;
+ }
+
+ break;
+ case SPAPR_VTPM_VALID_COMMAND: /* Payloads */
+ switch (msg) {
+ case SPAPR_VTPM_TPM_COMMAND:
+ trace_tpm_spapr_do_crq_tpm_command();
+ if (s->state == SPAPR_VTPM_STATE_EXECUTION) {
+ return H_BUSY;
+ }
+ memcpy(crq, crq_data, sizeof(*crq));
+
+ rc = tpm_spapr_process_cmd(s, be32_to_cpu(crq->data));
+
+ if (rc == H_SUCCESS) {
+ crq->valid = be16_to_cpu(0);
+ } else {
+ local_crq.valid = SPAPR_VTPM_MSG_RESULT;
+ local_crq.msg = SPAPR_VTPM_VTPM_ERROR;
+ local_crq.len = cpu_to_be16(0);
+ local_crq.data = cpu_to_be32(SPAPR_VTPM_ERR_COPY_IN_FAILED);
+ spapr_tpm_send_crq(dev, &local_crq);
+ }
+ break;
+
+ case SPAPR_VTPM_GET_RTCE_BUFFER_SIZE:
+ trace_tpm_spapr_do_crq_tpm_get_rtce_buffer_size(s->be_buffer_size);
+ local_crq.valid = SPAPR_VTPM_VALID_COMMAND;
+ local_crq.msg = SPAPR_VTPM_GET_RTCE_BUFFER_SIZE |
+ SPAPR_VTPM_MSG_RESULT;
+ local_crq.len = cpu_to_be16(s->be_buffer_size);
+ spapr_tpm_send_crq(dev, &local_crq);
+ break;
+
+ case SPAPR_VTPM_GET_VERSION:
+ local_crq.valid = SPAPR_VTPM_VALID_COMMAND;
+ local_crq.msg = SPAPR_VTPM_GET_VERSION | SPAPR_VTPM_MSG_RESULT;
+ local_crq.len = cpu_to_be16(0);
+ switch (s->be_tpm_version) {
+ case TPM_VERSION_1_2:
+ local_crq.data = cpu_to_be32(1);
+ break;
+ case TPM_VERSION_2_0:
+ local_crq.data = cpu_to_be32(2);
+ break;
+ default:
+ g_assert_not_reached();
+ break;
+ }
+ trace_tpm_spapr_do_crq_get_version(be32_to_cpu(local_crq.data));
+ spapr_tpm_send_crq(dev, &local_crq);
+ break;
+
+ case SPAPR_VTPM_PREPARE_TO_SUSPEND:
+ trace_tpm_spapr_do_crq_prepare_to_suspend();
+ local_crq.valid = SPAPR_VTPM_VALID_COMMAND;
+ local_crq.msg = SPAPR_VTPM_PREPARE_TO_SUSPEND |
+ SPAPR_VTPM_MSG_RESULT;
+ spapr_tpm_send_crq(dev, &local_crq);
+ break;
+
+ default:
+ trace_tpm_spapr_do_crq_unknown_msg_type(crq->msg);
+ }
+ break;
+ default:
+ trace_tpm_spapr_do_crq_unknown_crq(valid, msg);
+ };
+
+ return H_SUCCESS;
+}
+
+static void tpm_spapr_request_completed(TPMIf *ti, int ret)
+{
+ SpaprTpmState *s = VIO_SPAPR_VTPM(ti);
+ TpmCrq *crq = &s->crq;
+ uint32_t len;
+ int rc;
+
+ s->state = SPAPR_VTPM_STATE_COMPLETION;
+
+ /* a max. of be_buffer_size bytes can be transported */
+ len = MIN(tpm_cmd_get_size(s->buffer), s->be_buffer_size);
+
+ if (runstate_check(RUN_STATE_FINISH_MIGRATE)) {
+ trace_tpm_spapr_caught_response(len);
+ /* defer delivery of response until .post_load */
+ s->numbytes = len;
+ return;
+ }
+
+ rc = spapr_vio_dma_write(&s->vdev, be32_to_cpu(crq->data),
+ s->buffer, len);
+
+ tpm_util_show_buffer(s->buffer, len, "From TPM");
+
+ crq->valid = SPAPR_VTPM_MSG_RESULT;
+ if (rc == H_SUCCESS) {
+ crq->msg = SPAPR_VTPM_TPM_COMMAND | SPAPR_VTPM_MSG_RESULT;
+ crq->len = cpu_to_be16(len);
+ } else {
+ error_report("%s: DMA write failure", __func__);
+ crq->msg = SPAPR_VTPM_VTPM_ERROR;
+ crq->len = cpu_to_be16(0);
+ crq->data = cpu_to_be32(SPAPR_VTPM_ERR_COPY_OUT_FAILED);
+ }
+
+ rc = spapr_tpm_send_crq(&s->vdev, crq);
+ if (rc) {
+ error_report("%s: Error sending response", __func__);
+ }
+}
+
+static int tpm_spapr_do_startup_tpm(SpaprTpmState *s, size_t buffersize)
+{
+ return tpm_backend_startup_tpm(s->be_driver, buffersize);
+}
+
+static const char *tpm_spapr_get_dt_compatible(SpaprVioDevice *dev)
+{
+ SpaprTpmState *s = VIO_SPAPR_VTPM(dev);
+
+ switch (s->be_tpm_version) {
+ case TPM_VERSION_1_2:
+ return "IBM,vtpm";
+ case TPM_VERSION_2_0:
+ return "IBM,vtpm20";
+ default:
+ g_assert_not_reached();
+ }
+}
+
+static void tpm_spapr_reset(SpaprVioDevice *dev)
+{
+ SpaprTpmState *s = VIO_SPAPR_VTPM(dev);
+
+ s->state = SPAPR_VTPM_STATE_NONE;
+ s->numbytes = 0;
+
+ s->be_tpm_version = tpm_backend_get_tpm_version(s->be_driver);
+
+ s->be_buffer_size = MIN(tpm_backend_get_buffer_size(s->be_driver),
+ TPM_SPAPR_BUFFER_MAX);
+
+ tpm_backend_reset(s->be_driver);
+
+ if (tpm_spapr_do_startup_tpm(s, s->be_buffer_size) < 0) {
+ exit(1);
+ }
+}
+
+static enum TPMVersion tpm_spapr_get_version(TPMIf *ti)
+{
+ SpaprTpmState *s = VIO_SPAPR_VTPM(ti);
+
+ if (tpm_backend_had_startup_error(s->be_driver)) {
+ return TPM_VERSION_UNSPEC;
+ }
+
+ return tpm_backend_get_tpm_version(s->be_driver);
+}
+
+/* persistent state handling */
+
+static int tpm_spapr_pre_save(void *opaque)
+{
+ SpaprTpmState *s = opaque;
+
+ tpm_backend_finish_sync(s->be_driver);
+ /*
+ * we cannot deliver the results to the VM since DMA would touch VM memory
+ */
+
+ return 0;
+}
+
+static int tpm_spapr_post_load(void *opaque, int version_id)
+{
+ SpaprTpmState *s = opaque;
+
+ if (s->numbytes) {
+ trace_tpm_spapr_post_load();
+ /* deliver the results to the VM via DMA */
+ tpm_spapr_request_completed(TPM_IF(s), 0);
+ s->numbytes = 0;
+ }
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_spapr_vtpm = {
+ .name = "tpm-spapr",
+ .pre_save = tpm_spapr_pre_save,
+ .post_load = tpm_spapr_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_SPAPR_VIO(vdev, SpaprTpmState),
+
+ VMSTATE_UINT8(state, SpaprTpmState),
+ VMSTATE_UINT32(numbytes, SpaprTpmState),
+ VMSTATE_VBUFFER_UINT32(buffer, SpaprTpmState, 0, NULL, numbytes),
+ /* remember DMA address */
+ VMSTATE_UINT32(crq.data, SpaprTpmState),
+ VMSTATE_END_OF_LIST(),
+ }
+};
+
+static Property tpm_spapr_properties[] = {
+ DEFINE_SPAPR_PROPERTIES(SpaprTpmState, vdev),
+ DEFINE_PROP_TPMBE("tpmdev", SpaprTpmState, be_driver),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void tpm_spapr_realizefn(SpaprVioDevice *dev, Error **errp)
+{
+ SpaprTpmState *s = VIO_SPAPR_VTPM(dev);
+
+ if (!tpm_find()) {
+ error_setg(errp, "at most one TPM device is permitted");
+ return;
+ }
+
+ dev->crq.SendFunc = tpm_spapr_do_crq;
+
+ if (!s->be_driver) {
+ error_setg(errp, "'tpmdev' property is required");
+ return;
+ }
+ s->buffer = g_malloc(TPM_SPAPR_BUFFER_MAX);
+}
+
+static void tpm_spapr_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SpaprVioDeviceClass *k = VIO_SPAPR_DEVICE_CLASS(klass);
+ TPMIfClass *tc = TPM_IF_CLASS(klass);
+
+ k->realize = tpm_spapr_realizefn;
+ k->reset = tpm_spapr_reset;
+ k->dt_name = "vtpm";
+ k->dt_type = "IBM,vtpm";
+ k->get_dt_compatible = tpm_spapr_get_dt_compatible;
+ k->signal_mask = 0x00000001;
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+ device_class_set_props(dc, tpm_spapr_properties);
+ k->rtce_window_size = 0x10000000;
+ dc->vmsd = &vmstate_spapr_vtpm;
+
+ tc->model = TPM_MODEL_TPM_SPAPR;
+ tc->get_version = tpm_spapr_get_version;
+ tc->request_completed = tpm_spapr_request_completed;
+}
+
+static const TypeInfo tpm_spapr_info = {
+ .name = TYPE_TPM_SPAPR,
+ .parent = TYPE_VIO_SPAPR_DEVICE,
+ .instance_size = sizeof(SpaprTpmState),
+ .class_init = tpm_spapr_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_TPM_IF },
+ { }
+ }
+};
+
+static void tpm_spapr_register_types(void)
+{
+ type_register_static(&tpm_spapr_info);
+}
+
+type_init(tpm_spapr_register_types)
diff --git a/hw/tpm/tpm_tis.h b/hw/tpm/tpm_tis.h
new file mode 100644
index 000000000..f6b5872ba
--- /dev/null
+++ b/hw/tpm/tpm_tis.h
@@ -0,0 +1,90 @@
+/*
+ * tpm_tis.h - QEMU's TPM TIS common header
+ *
+ * Copyright (C) 2006,2010-2013 IBM Corporation
+ *
+ * Authors:
+ * Stefan Berger <stefanb@us.ibm.com>
+ * David Safford <safford@us.ibm.com>
+ *
+ * Xen 4 support: Andrease Niederl <andreas.niederl@iaik.tugraz.at>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ * Implementation of the TIS interface according to specs found at
+ * http://www.trustedcomputinggroup.org. This implementation currently
+ * supports version 1.3, 21 March 2013
+ * In the developers menu choose the PC Client section then find the TIS
+ * specification.
+ *
+ * TPM TIS for TPM 2 implementation following TCG PC Client Platform
+ * TPM Profile (PTP) Specification, Familiy 2.0, Revision 00.43
+ */
+#ifndef TPM_TPM_TIS_H
+#define TPM_TPM_TIS_H
+
+#include "sysemu/tpm_backend.h"
+#include "tpm_ppi.h"
+
+#define TPM_TIS_NUM_LOCALITIES 5 /* per spec */
+#define TPM_TIS_LOCALITY_SHIFT 12
+#define TPM_TIS_NO_LOCALITY 0xff
+
+#define TPM_TIS_IS_VALID_LOCTY(x) ((x) < TPM_TIS_NUM_LOCALITIES)
+
+#define TPM_TIS_BUFFER_MAX 4096
+
+typedef enum {
+ TPM_TIS_STATE_IDLE = 0,
+ TPM_TIS_STATE_READY,
+ TPM_TIS_STATE_COMPLETION,
+ TPM_TIS_STATE_EXECUTION,
+ TPM_TIS_STATE_RECEPTION,
+} TPMTISState;
+
+/* locality data -- all fields are persisted */
+typedef struct TPMLocality {
+ TPMTISState state;
+ uint8_t access;
+ uint32_t sts;
+ uint32_t iface_id;
+ uint32_t inte;
+ uint32_t ints;
+} TPMLocality;
+
+typedef struct TPMState {
+ MemoryRegion mmio;
+
+ unsigned char buffer[TPM_TIS_BUFFER_MAX];
+ uint16_t rw_offset;
+
+ uint8_t active_locty;
+ uint8_t aborting_locty;
+ uint8_t next_locty;
+
+ TPMLocality loc[TPM_TIS_NUM_LOCALITIES];
+
+ qemu_irq irq;
+ uint32_t irq_num;
+
+ TPMBackendCmd cmd;
+
+ TPMBackend *be_driver;
+ TPMVersion be_tpm_version;
+
+ size_t be_buffer_size;
+
+ bool ppi_enabled;
+ TPMPPI ppi;
+} TPMState;
+
+extern const VMStateDescription vmstate_locty;
+extern const MemoryRegionOps tpm_tis_memory_ops;
+
+int tpm_tis_pre_save(TPMState *s);
+void tpm_tis_reset(TPMState *s);
+enum TPMVersion tpm_tis_get_tpm_version(TPMState *s);
+void tpm_tis_request_completed(TPMState *s, int ret);
+
+#endif /* TPM_TPM_TIS_H */
diff --git a/hw/tpm/tpm_tis_common.c b/hw/tpm/tpm_tis_common.c
new file mode 100644
index 000000000..e700d8218
--- /dev/null
+++ b/hw/tpm/tpm_tis_common.c
@@ -0,0 +1,867 @@
+/*
+ * tpm_tis_common.c - QEMU's TPM TIS interface emulator
+ * device agnostic functions
+ *
+ * Copyright (C) 2006,2010-2013 IBM Corporation
+ *
+ * Authors:
+ * Stefan Berger <stefanb@us.ibm.com>
+ * David Safford <safford@us.ibm.com>
+ *
+ * Xen 4 support: Andrease Niederl <andreas.niederl@iaik.tugraz.at>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ * Implementation of the TIS interface according to specs found at
+ * http://www.trustedcomputinggroup.org. This implementation currently
+ * supports version 1.3, 21 March 2013
+ * In the developers menu choose the PC Client section then find the TIS
+ * specification.
+ *
+ * TPM TIS for TPM 2 implementation following TCG PC Client Platform
+ * TPM Profile (PTP) Specification, Familiy 2.0, Revision 00.43
+ */
+#include "qemu/osdep.h"
+#include "hw/irq.h"
+#include "hw/isa/isa.h"
+#include "qapi/error.h"
+#include "qemu/module.h"
+
+#include "hw/acpi/tpm.h"
+#include "hw/pci/pci_ids.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "sysemu/tpm_backend.h"
+#include "sysemu/tpm_util.h"
+#include "tpm_ppi.h"
+#include "trace.h"
+
+#include "tpm_tis.h"
+
+#define DEBUG_TIS 0
+
+/* local prototypes */
+
+static uint64_t tpm_tis_mmio_read(void *opaque, hwaddr addr,
+ unsigned size);
+
+/* utility functions */
+
+static uint8_t tpm_tis_locality_from_addr(hwaddr addr)
+{
+ return (uint8_t)((addr >> TPM_TIS_LOCALITY_SHIFT) & 0x7);
+}
+
+
+/*
+ * Set the given flags in the STS register by clearing the register but
+ * preserving the SELFTEST_DONE and TPM_FAMILY_MASK flags and then setting
+ * the new flags.
+ *
+ * The SELFTEST_DONE flag is acquired from the backend that determines it by
+ * peeking into TPM commands.
+ *
+ * A VM suspend/resume will preserve the flag by storing it into the VM
+ * device state, but the backend will not remember it when QEMU is started
+ * again. Therefore, we cache the flag here. Once set, it will not be unset
+ * except by a reset.
+ */
+static void tpm_tis_sts_set(TPMLocality *l, uint32_t flags)
+{
+ l->sts &= TPM_TIS_STS_SELFTEST_DONE | TPM_TIS_STS_TPM_FAMILY_MASK;
+ l->sts |= flags;
+}
+
+/*
+ * Send a request to the TPM.
+ */
+static void tpm_tis_tpm_send(TPMState *s, uint8_t locty)
+{
+ tpm_util_show_buffer(s->buffer, s->be_buffer_size, "To TPM");
+
+ /*
+ * rw_offset serves as length indicator for length of data;
+ * it's reset when the response comes back
+ */
+ s->loc[locty].state = TPM_TIS_STATE_EXECUTION;
+
+ s->cmd = (TPMBackendCmd) {
+ .locty = locty,
+ .in = s->buffer,
+ .in_len = s->rw_offset,
+ .out = s->buffer,
+ .out_len = s->be_buffer_size,
+ };
+
+ tpm_backend_deliver_request(s->be_driver, &s->cmd);
+}
+
+/* raise an interrupt if allowed */
+static void tpm_tis_raise_irq(TPMState *s, uint8_t locty, uint32_t irqmask)
+{
+ if (!TPM_TIS_IS_VALID_LOCTY(locty)) {
+ return;
+ }
+
+ if ((s->loc[locty].inte & TPM_TIS_INT_ENABLED) &&
+ (s->loc[locty].inte & irqmask)) {
+ trace_tpm_tis_raise_irq(irqmask);
+ qemu_irq_raise(s->irq);
+ s->loc[locty].ints |= irqmask;
+ }
+}
+
+static uint32_t tpm_tis_check_request_use_except(TPMState *s, uint8_t locty)
+{
+ uint8_t l;
+
+ for (l = 0; l < TPM_TIS_NUM_LOCALITIES; l++) {
+ if (l == locty) {
+ continue;
+ }
+ if ((s->loc[l].access & TPM_TIS_ACCESS_REQUEST_USE)) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static void tpm_tis_new_active_locality(TPMState *s, uint8_t new_active_locty)
+{
+ bool change = (s->active_locty != new_active_locty);
+ bool is_seize;
+ uint8_t mask;
+
+ if (change && TPM_TIS_IS_VALID_LOCTY(s->active_locty)) {
+ is_seize = TPM_TIS_IS_VALID_LOCTY(new_active_locty) &&
+ s->loc[new_active_locty].access & TPM_TIS_ACCESS_SEIZE;
+
+ if (is_seize) {
+ mask = ~(TPM_TIS_ACCESS_ACTIVE_LOCALITY);
+ } else {
+ mask = ~(TPM_TIS_ACCESS_ACTIVE_LOCALITY|
+ TPM_TIS_ACCESS_REQUEST_USE);
+ }
+ /* reset flags on the old active locality */
+ s->loc[s->active_locty].access &= mask;
+
+ if (is_seize) {
+ s->loc[s->active_locty].access |= TPM_TIS_ACCESS_BEEN_SEIZED;
+ }
+ }
+
+ s->active_locty = new_active_locty;
+
+ trace_tpm_tis_new_active_locality(s->active_locty);
+
+ if (TPM_TIS_IS_VALID_LOCTY(new_active_locty)) {
+ /* set flags on the new active locality */
+ s->loc[new_active_locty].access |= TPM_TIS_ACCESS_ACTIVE_LOCALITY;
+ s->loc[new_active_locty].access &= ~(TPM_TIS_ACCESS_REQUEST_USE |
+ TPM_TIS_ACCESS_SEIZE);
+ }
+
+ if (change) {
+ tpm_tis_raise_irq(s, s->active_locty, TPM_TIS_INT_LOCALITY_CHANGED);
+ }
+}
+
+/* abort -- this function switches the locality */
+static void tpm_tis_abort(TPMState *s)
+{
+ s->rw_offset = 0;
+
+ trace_tpm_tis_abort(s->next_locty);
+
+ /*
+ * Need to react differently depending on who's aborting now and
+ * which locality will become active afterwards.
+ */
+ if (s->aborting_locty == s->next_locty) {
+ s->loc[s->aborting_locty].state = TPM_TIS_STATE_READY;
+ tpm_tis_sts_set(&s->loc[s->aborting_locty],
+ TPM_TIS_STS_COMMAND_READY);
+ tpm_tis_raise_irq(s, s->aborting_locty, TPM_TIS_INT_COMMAND_READY);
+ }
+
+ /* locality after abort is another one than the current one */
+ tpm_tis_new_active_locality(s, s->next_locty);
+
+ s->next_locty = TPM_TIS_NO_LOCALITY;
+ /* nobody's aborting a command anymore */
+ s->aborting_locty = TPM_TIS_NO_LOCALITY;
+}
+
+/* prepare aborting current command */
+static void tpm_tis_prep_abort(TPMState *s, uint8_t locty, uint8_t newlocty)
+{
+ uint8_t busy_locty;
+
+ assert(TPM_TIS_IS_VALID_LOCTY(newlocty));
+
+ s->aborting_locty = locty; /* may also be TPM_TIS_NO_LOCALITY */
+ s->next_locty = newlocty; /* locality after successful abort */
+
+ /*
+ * only abort a command using an interrupt if currently executing
+ * a command AND if there's a valid connection to the vTPM.
+ */
+ for (busy_locty = 0; busy_locty < TPM_TIS_NUM_LOCALITIES; busy_locty++) {
+ if (s->loc[busy_locty].state == TPM_TIS_STATE_EXECUTION) {
+ /*
+ * request the backend to cancel. Some backends may not
+ * support it
+ */
+ tpm_backend_cancel_cmd(s->be_driver);
+ return;
+ }
+ }
+
+ tpm_tis_abort(s);
+}
+
+/*
+ * Callback from the TPM to indicate that the response was received.
+ */
+void tpm_tis_request_completed(TPMState *s, int ret)
+{
+ uint8_t locty = s->cmd.locty;
+ uint8_t l;
+
+ assert(TPM_TIS_IS_VALID_LOCTY(locty));
+
+ if (s->cmd.selftest_done) {
+ for (l = 0; l < TPM_TIS_NUM_LOCALITIES; l++) {
+ s->loc[l].sts |= TPM_TIS_STS_SELFTEST_DONE;
+ }
+ }
+
+ /* FIXME: report error if ret != 0 */
+ tpm_tis_sts_set(&s->loc[locty],
+ TPM_TIS_STS_VALID | TPM_TIS_STS_DATA_AVAILABLE);
+ s->loc[locty].state = TPM_TIS_STATE_COMPLETION;
+ s->rw_offset = 0;
+
+ tpm_util_show_buffer(s->buffer, s->be_buffer_size, "From TPM");
+
+ if (TPM_TIS_IS_VALID_LOCTY(s->next_locty)) {
+ tpm_tis_abort(s);
+ }
+
+ tpm_tis_raise_irq(s, locty,
+ TPM_TIS_INT_DATA_AVAILABLE | TPM_TIS_INT_STS_VALID);
+}
+
+/*
+ * Read a byte of response data
+ */
+static uint32_t tpm_tis_data_read(TPMState *s, uint8_t locty)
+{
+ uint32_t ret = TPM_TIS_NO_DATA_BYTE;
+ uint16_t len;
+
+ if ((s->loc[locty].sts & TPM_TIS_STS_DATA_AVAILABLE)) {
+ len = MIN(tpm_cmd_get_size(&s->buffer),
+ s->be_buffer_size);
+
+ ret = s->buffer[s->rw_offset++];
+ if (s->rw_offset >= len) {
+ /* got last byte */
+ tpm_tis_sts_set(&s->loc[locty], TPM_TIS_STS_VALID);
+ tpm_tis_raise_irq(s, locty, TPM_TIS_INT_STS_VALID);
+ }
+ trace_tpm_tis_data_read(ret, s->rw_offset - 1);
+ }
+
+ return ret;
+}
+
+#ifdef DEBUG_TIS
+static void tpm_tis_dump_state(TPMState *s, hwaddr addr)
+{
+ static const unsigned regs[] = {
+ TPM_TIS_REG_ACCESS,
+ TPM_TIS_REG_INT_ENABLE,
+ TPM_TIS_REG_INT_VECTOR,
+ TPM_TIS_REG_INT_STATUS,
+ TPM_TIS_REG_INTF_CAPABILITY,
+ TPM_TIS_REG_STS,
+ TPM_TIS_REG_DID_VID,
+ TPM_TIS_REG_RID,
+ 0xfff};
+ int idx;
+ uint8_t locty = tpm_tis_locality_from_addr(addr);
+ hwaddr base = addr & ~0xfff;
+
+ printf("tpm_tis: active locality : %d\n"
+ "tpm_tis: state of locality %d : %d\n"
+ "tpm_tis: register dump:\n",
+ s->active_locty,
+ locty, s->loc[locty].state);
+
+ for (idx = 0; regs[idx] != 0xfff; idx++) {
+ printf("tpm_tis: 0x%04x : 0x%08x\n", regs[idx],
+ (int)tpm_tis_mmio_read(s, base + regs[idx], 4));
+ }
+
+ printf("tpm_tis: r/w offset : %d\n"
+ "tpm_tis: result buffer : ",
+ s->rw_offset);
+ for (idx = 0;
+ idx < MIN(tpm_cmd_get_size(&s->buffer), s->be_buffer_size);
+ idx++) {
+ printf("%c%02x%s",
+ s->rw_offset == idx ? '>' : ' ',
+ s->buffer[idx],
+ ((idx & 0xf) == 0xf) ? "\ntpm_tis: " : "");
+ }
+ printf("\n");
+}
+#endif
+
+/*
+ * Read a register of the TIS interface
+ * See specs pages 33-63 for description of the registers
+ */
+static uint64_t tpm_tis_mmio_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ TPMState *s = opaque;
+ uint16_t offset = addr & 0xffc;
+ uint8_t shift = (addr & 0x3) * 8;
+ uint32_t val = 0xffffffff;
+ uint8_t locty = tpm_tis_locality_from_addr(addr);
+ uint32_t avail;
+ uint8_t v;
+
+ if (tpm_backend_had_startup_error(s->be_driver)) {
+ return 0;
+ }
+
+ switch (offset) {
+ case TPM_TIS_REG_ACCESS:
+ /* never show the SEIZE flag even though we use it internally */
+ val = s->loc[locty].access & ~TPM_TIS_ACCESS_SEIZE;
+ /* the pending flag is always calculated */
+ if (tpm_tis_check_request_use_except(s, locty)) {
+ val |= TPM_TIS_ACCESS_PENDING_REQUEST;
+ }
+ val |= !tpm_backend_get_tpm_established_flag(s->be_driver);
+ break;
+ case TPM_TIS_REG_INT_ENABLE:
+ val = s->loc[locty].inte;
+ break;
+ case TPM_TIS_REG_INT_VECTOR:
+ val = s->irq_num;
+ break;
+ case TPM_TIS_REG_INT_STATUS:
+ val = s->loc[locty].ints;
+ break;
+ case TPM_TIS_REG_INTF_CAPABILITY:
+ switch (s->be_tpm_version) {
+ case TPM_VERSION_UNSPEC:
+ val = 0;
+ break;
+ case TPM_VERSION_1_2:
+ val = TPM_TIS_CAPABILITIES_SUPPORTED1_3;
+ break;
+ case TPM_VERSION_2_0:
+ val = TPM_TIS_CAPABILITIES_SUPPORTED2_0;
+ break;
+ }
+ break;
+ case TPM_TIS_REG_STS:
+ if (s->active_locty == locty) {
+ if ((s->loc[locty].sts & TPM_TIS_STS_DATA_AVAILABLE)) {
+ val = TPM_TIS_BURST_COUNT(
+ MIN(tpm_cmd_get_size(&s->buffer),
+ s->be_buffer_size)
+ - s->rw_offset) | s->loc[locty].sts;
+ } else {
+ avail = s->be_buffer_size - s->rw_offset;
+ /*
+ * byte-sized reads should not return 0x00 for 0x100
+ * available bytes.
+ */
+ if (size == 1 && avail > 0xff) {
+ avail = 0xff;
+ }
+ val = TPM_TIS_BURST_COUNT(avail) | s->loc[locty].sts;
+ }
+ }
+ break;
+ case TPM_TIS_REG_DATA_FIFO:
+ case TPM_TIS_REG_DATA_XFIFO ... TPM_TIS_REG_DATA_XFIFO_END:
+ if (s->active_locty == locty) {
+ if (size > 4 - (addr & 0x3)) {
+ /* prevent access beyond FIFO */
+ size = 4 - (addr & 0x3);
+ }
+ val = 0;
+ shift = 0;
+ while (size > 0) {
+ switch (s->loc[locty].state) {
+ case TPM_TIS_STATE_COMPLETION:
+ v = tpm_tis_data_read(s, locty);
+ break;
+ default:
+ v = TPM_TIS_NO_DATA_BYTE;
+ break;
+ }
+ val |= (v << shift);
+ shift += 8;
+ size--;
+ }
+ shift = 0; /* no more adjustments */
+ }
+ break;
+ case TPM_TIS_REG_INTERFACE_ID:
+ val = s->loc[locty].iface_id;
+ break;
+ case TPM_TIS_REG_DID_VID:
+ val = (TPM_TIS_TPM_DID << 16) | TPM_TIS_TPM_VID;
+ break;
+ case TPM_TIS_REG_RID:
+ val = TPM_TIS_TPM_RID;
+ break;
+#ifdef DEBUG_TIS
+ case TPM_TIS_REG_DEBUG:
+ tpm_tis_dump_state(s, addr);
+ break;
+#endif
+ }
+
+ if (shift) {
+ val >>= shift;
+ }
+
+ trace_tpm_tis_mmio_read(size, addr, val);
+
+ return val;
+}
+
+/*
+ * Write a value to a register of the TIS interface
+ * See specs pages 33-63 for description of the registers
+ */
+static void tpm_tis_mmio_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ TPMState *s = opaque;
+ uint16_t off = addr & 0xffc;
+ uint8_t shift = (addr & 0x3) * 8;
+ uint8_t locty = tpm_tis_locality_from_addr(addr);
+ uint8_t active_locty, l;
+ int c, set_new_locty = 1;
+ uint16_t len;
+ uint32_t mask = (size == 1) ? 0xff : ((size == 2) ? 0xffff : ~0);
+
+ trace_tpm_tis_mmio_write(size, addr, val);
+
+ if (locty == 4) {
+ trace_tpm_tis_mmio_write_locty4();
+ return;
+ }
+
+ if (tpm_backend_had_startup_error(s->be_driver)) {
+ return;
+ }
+
+ val &= mask;
+
+ if (shift) {
+ val <<= shift;
+ mask <<= shift;
+ }
+
+ mask ^= 0xffffffff;
+
+ switch (off) {
+ case TPM_TIS_REG_ACCESS:
+
+ if ((val & TPM_TIS_ACCESS_SEIZE)) {
+ val &= ~(TPM_TIS_ACCESS_REQUEST_USE |
+ TPM_TIS_ACCESS_ACTIVE_LOCALITY);
+ }
+
+ active_locty = s->active_locty;
+
+ if ((val & TPM_TIS_ACCESS_ACTIVE_LOCALITY)) {
+ /* give up locality if currently owned */
+ if (s->active_locty == locty) {
+ trace_tpm_tis_mmio_write_release_locty(locty);
+
+ uint8_t newlocty = TPM_TIS_NO_LOCALITY;
+ /* anybody wants the locality ? */
+ for (c = TPM_TIS_NUM_LOCALITIES - 1; c >= 0; c--) {
+ if ((s->loc[c].access & TPM_TIS_ACCESS_REQUEST_USE)) {
+ trace_tpm_tis_mmio_write_locty_req_use(c);
+ newlocty = c;
+ break;
+ }
+ }
+ trace_tpm_tis_mmio_write_next_locty(newlocty);
+
+ if (TPM_TIS_IS_VALID_LOCTY(newlocty)) {
+ set_new_locty = 0;
+ tpm_tis_prep_abort(s, locty, newlocty);
+ } else {
+ active_locty = TPM_TIS_NO_LOCALITY;
+ }
+ } else {
+ /* not currently the owner; clear a pending request */
+ s->loc[locty].access &= ~TPM_TIS_ACCESS_REQUEST_USE;
+ }
+ }
+
+ if ((val & TPM_TIS_ACCESS_BEEN_SEIZED)) {
+ s->loc[locty].access &= ~TPM_TIS_ACCESS_BEEN_SEIZED;
+ }
+
+ if ((val & TPM_TIS_ACCESS_SEIZE)) {
+ /*
+ * allow seize if a locality is active and the requesting
+ * locality is higher than the one that's active
+ * OR
+ * allow seize for requesting locality if no locality is
+ * active
+ */
+ while ((TPM_TIS_IS_VALID_LOCTY(s->active_locty) &&
+ locty > s->active_locty) ||
+ !TPM_TIS_IS_VALID_LOCTY(s->active_locty)) {
+ bool higher_seize = false;
+
+ /* already a pending SEIZE ? */
+ if ((s->loc[locty].access & TPM_TIS_ACCESS_SEIZE)) {
+ break;
+ }
+
+ /* check for ongoing seize by a higher locality */
+ for (l = locty + 1; l < TPM_TIS_NUM_LOCALITIES; l++) {
+ if ((s->loc[l].access & TPM_TIS_ACCESS_SEIZE)) {
+ higher_seize = true;
+ break;
+ }
+ }
+
+ if (higher_seize) {
+ break;
+ }
+
+ /* cancel any seize by a lower locality */
+ for (l = 0; l < locty; l++) {
+ s->loc[l].access &= ~TPM_TIS_ACCESS_SEIZE;
+ }
+
+ s->loc[locty].access |= TPM_TIS_ACCESS_SEIZE;
+
+ trace_tpm_tis_mmio_write_locty_seized(locty, s->active_locty);
+ trace_tpm_tis_mmio_write_init_abort();
+
+ set_new_locty = 0;
+ tpm_tis_prep_abort(s, s->active_locty, locty);
+ break;
+ }
+ }
+
+ if ((val & TPM_TIS_ACCESS_REQUEST_USE)) {
+ if (s->active_locty != locty) {
+ if (TPM_TIS_IS_VALID_LOCTY(s->active_locty)) {
+ s->loc[locty].access |= TPM_TIS_ACCESS_REQUEST_USE;
+ } else {
+ /* no locality active -> make this one active now */
+ active_locty = locty;
+ }
+ }
+ }
+
+ if (set_new_locty) {
+ tpm_tis_new_active_locality(s, active_locty);
+ }
+
+ break;
+ case TPM_TIS_REG_INT_ENABLE:
+ if (s->active_locty != locty) {
+ break;
+ }
+
+ s->loc[locty].inte &= mask;
+ s->loc[locty].inte |= (val & (TPM_TIS_INT_ENABLED |
+ TPM_TIS_INT_POLARITY_MASK |
+ TPM_TIS_INTERRUPTS_SUPPORTED));
+ break;
+ case TPM_TIS_REG_INT_VECTOR:
+ /* hard wired -- ignore */
+ break;
+ case TPM_TIS_REG_INT_STATUS:
+ if (s->active_locty != locty) {
+ break;
+ }
+
+ /* clearing of interrupt flags */
+ if (((val & TPM_TIS_INTERRUPTS_SUPPORTED)) &&
+ (s->loc[locty].ints & TPM_TIS_INTERRUPTS_SUPPORTED)) {
+ s->loc[locty].ints &= ~val;
+ if (s->loc[locty].ints == 0) {
+ qemu_irq_lower(s->irq);
+ trace_tpm_tis_mmio_write_lowering_irq();
+ }
+ }
+ s->loc[locty].ints &= ~(val & TPM_TIS_INTERRUPTS_SUPPORTED);
+ break;
+ case TPM_TIS_REG_STS:
+ if (s->active_locty != locty) {
+ break;
+ }
+
+ if (s->be_tpm_version == TPM_VERSION_2_0) {
+ /* some flags that are only supported for TPM 2 */
+ if (val & TPM_TIS_STS_COMMAND_CANCEL) {
+ if (s->loc[locty].state == TPM_TIS_STATE_EXECUTION) {
+ /*
+ * request the backend to cancel. Some backends may not
+ * support it
+ */
+ tpm_backend_cancel_cmd(s->be_driver);
+ }
+ }
+
+ if (val & TPM_TIS_STS_RESET_ESTABLISHMENT_BIT) {
+ if (locty == 3 || locty == 4) {
+ tpm_backend_reset_tpm_established_flag(s->be_driver, locty);
+ }
+ }
+ }
+
+ val &= (TPM_TIS_STS_COMMAND_READY | TPM_TIS_STS_TPM_GO |
+ TPM_TIS_STS_RESPONSE_RETRY);
+
+ if (val == TPM_TIS_STS_COMMAND_READY) {
+ switch (s->loc[locty].state) {
+
+ case TPM_TIS_STATE_READY:
+ s->rw_offset = 0;
+ break;
+
+ case TPM_TIS_STATE_IDLE:
+ tpm_tis_sts_set(&s->loc[locty], TPM_TIS_STS_COMMAND_READY);
+ s->loc[locty].state = TPM_TIS_STATE_READY;
+ tpm_tis_raise_irq(s, locty, TPM_TIS_INT_COMMAND_READY);
+ break;
+
+ case TPM_TIS_STATE_EXECUTION:
+ case TPM_TIS_STATE_RECEPTION:
+ /* abort currently running command */
+ trace_tpm_tis_mmio_write_init_abort();
+ tpm_tis_prep_abort(s, locty, locty);
+ break;
+
+ case TPM_TIS_STATE_COMPLETION:
+ s->rw_offset = 0;
+ /* shortcut to ready state with C/R set */
+ s->loc[locty].state = TPM_TIS_STATE_READY;
+ if (!(s->loc[locty].sts & TPM_TIS_STS_COMMAND_READY)) {
+ tpm_tis_sts_set(&s->loc[locty],
+ TPM_TIS_STS_COMMAND_READY);
+ tpm_tis_raise_irq(s, locty, TPM_TIS_INT_COMMAND_READY);
+ }
+ s->loc[locty].sts &= ~(TPM_TIS_STS_DATA_AVAILABLE);
+ break;
+
+ }
+ } else if (val == TPM_TIS_STS_TPM_GO) {
+ switch (s->loc[locty].state) {
+ case TPM_TIS_STATE_RECEPTION:
+ if ((s->loc[locty].sts & TPM_TIS_STS_EXPECT) == 0) {
+ tpm_tis_tpm_send(s, locty);
+ }
+ break;
+ default:
+ /* ignore */
+ break;
+ }
+ } else if (val == TPM_TIS_STS_RESPONSE_RETRY) {
+ switch (s->loc[locty].state) {
+ case TPM_TIS_STATE_COMPLETION:
+ s->rw_offset = 0;
+ tpm_tis_sts_set(&s->loc[locty],
+ TPM_TIS_STS_VALID|
+ TPM_TIS_STS_DATA_AVAILABLE);
+ break;
+ default:
+ /* ignore */
+ break;
+ }
+ }
+ break;
+ case TPM_TIS_REG_DATA_FIFO:
+ case TPM_TIS_REG_DATA_XFIFO ... TPM_TIS_REG_DATA_XFIFO_END:
+ /* data fifo */
+ if (s->active_locty != locty) {
+ break;
+ }
+
+ if (s->loc[locty].state == TPM_TIS_STATE_IDLE ||
+ s->loc[locty].state == TPM_TIS_STATE_EXECUTION ||
+ s->loc[locty].state == TPM_TIS_STATE_COMPLETION) {
+ /* drop the byte */
+ } else {
+ trace_tpm_tis_mmio_write_data2send(val, size);
+ if (s->loc[locty].state == TPM_TIS_STATE_READY) {
+ s->loc[locty].state = TPM_TIS_STATE_RECEPTION;
+ tpm_tis_sts_set(&s->loc[locty],
+ TPM_TIS_STS_EXPECT | TPM_TIS_STS_VALID);
+ }
+
+ val >>= shift;
+ if (size > 4 - (addr & 0x3)) {
+ /* prevent access beyond FIFO */
+ size = 4 - (addr & 0x3);
+ }
+
+ while ((s->loc[locty].sts & TPM_TIS_STS_EXPECT) && size > 0) {
+ if (s->rw_offset < s->be_buffer_size) {
+ s->buffer[s->rw_offset++] =
+ (uint8_t)val;
+ val >>= 8;
+ size--;
+ } else {
+ tpm_tis_sts_set(&s->loc[locty], TPM_TIS_STS_VALID);
+ }
+ }
+
+ /* check for complete packet */
+ if (s->rw_offset > 5 &&
+ (s->loc[locty].sts & TPM_TIS_STS_EXPECT)) {
+ /* we have a packet length - see if we have all of it */
+ bool need_irq = !(s->loc[locty].sts & TPM_TIS_STS_VALID);
+
+ len = tpm_cmd_get_size(&s->buffer);
+ if (len > s->rw_offset) {
+ tpm_tis_sts_set(&s->loc[locty],
+ TPM_TIS_STS_EXPECT | TPM_TIS_STS_VALID);
+ } else {
+ /* packet complete */
+ tpm_tis_sts_set(&s->loc[locty], TPM_TIS_STS_VALID);
+ }
+ if (need_irq) {
+ tpm_tis_raise_irq(s, locty, TPM_TIS_INT_STS_VALID);
+ }
+ }
+ }
+ break;
+ case TPM_TIS_REG_INTERFACE_ID:
+ if (val & TPM_TIS_IFACE_ID_INT_SEL_LOCK) {
+ for (l = 0; l < TPM_TIS_NUM_LOCALITIES; l++) {
+ s->loc[l].iface_id |= TPM_TIS_IFACE_ID_INT_SEL_LOCK;
+ }
+ }
+ break;
+ }
+}
+
+const MemoryRegionOps tpm_tis_memory_ops = {
+ .read = tpm_tis_mmio_read,
+ .write = tpm_tis_mmio_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ },
+};
+
+/*
+ * Get the TPMVersion of the backend device being used
+ */
+enum TPMVersion tpm_tis_get_tpm_version(TPMState *s)
+{
+ if (tpm_backend_had_startup_error(s->be_driver)) {
+ return TPM_VERSION_UNSPEC;
+ }
+
+ return tpm_backend_get_tpm_version(s->be_driver);
+}
+
+/*
+ * This function is called when the machine starts, resets or due to
+ * S3 resume.
+ */
+void tpm_tis_reset(TPMState *s)
+{
+ int c;
+
+ s->be_tpm_version = tpm_backend_get_tpm_version(s->be_driver);
+ s->be_buffer_size = MIN(tpm_backend_get_buffer_size(s->be_driver),
+ TPM_TIS_BUFFER_MAX);
+
+ if (s->ppi_enabled) {
+ tpm_ppi_reset(&s->ppi);
+ }
+ tpm_backend_reset(s->be_driver);
+
+ s->active_locty = TPM_TIS_NO_LOCALITY;
+ s->next_locty = TPM_TIS_NO_LOCALITY;
+ s->aborting_locty = TPM_TIS_NO_LOCALITY;
+
+ for (c = 0; c < TPM_TIS_NUM_LOCALITIES; c++) {
+ s->loc[c].access = TPM_TIS_ACCESS_TPM_REG_VALID_STS;
+ switch (s->be_tpm_version) {
+ case TPM_VERSION_UNSPEC:
+ break;
+ case TPM_VERSION_1_2:
+ s->loc[c].sts = TPM_TIS_STS_TPM_FAMILY1_2;
+ s->loc[c].iface_id = TPM_TIS_IFACE_ID_SUPPORTED_FLAGS1_3;
+ break;
+ case TPM_VERSION_2_0:
+ s->loc[c].sts = TPM_TIS_STS_TPM_FAMILY2_0;
+ s->loc[c].iface_id = TPM_TIS_IFACE_ID_SUPPORTED_FLAGS2_0;
+ break;
+ }
+ s->loc[c].inte = TPM_TIS_INT_POLARITY_LOW_LEVEL;
+ s->loc[c].ints = 0;
+ s->loc[c].state = TPM_TIS_STATE_IDLE;
+
+ s->rw_offset = 0;
+ }
+
+ if (tpm_backend_startup_tpm(s->be_driver, s->be_buffer_size) < 0) {
+ exit(1);
+ }
+}
+
+/* persistent state handling */
+
+int tpm_tis_pre_save(TPMState *s)
+{
+ uint8_t locty = s->active_locty;
+
+ trace_tpm_tis_pre_save(locty, s->rw_offset);
+
+ if (DEBUG_TIS) {
+ tpm_tis_dump_state(s, 0);
+ }
+
+ /*
+ * Synchronize with backend completion.
+ */
+ tpm_backend_finish_sync(s->be_driver);
+
+ return 0;
+}
+
+const VMStateDescription vmstate_locty = {
+ .name = "tpm-tis/locty",
+ .version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(state, TPMLocality),
+ VMSTATE_UINT32(inte, TPMLocality),
+ VMSTATE_UINT32(ints, TPMLocality),
+ VMSTATE_UINT8(access, TPMLocality),
+ VMSTATE_UINT32(sts, TPMLocality),
+ VMSTATE_UINT32(iface_id, TPMLocality),
+ VMSTATE_END_OF_LIST(),
+ }
+};
+
diff --git a/hw/tpm/tpm_tis_isa.c b/hw/tpm/tpm_tis_isa.c
new file mode 100644
index 000000000..10d8a14f1
--- /dev/null
+++ b/hw/tpm/tpm_tis_isa.c
@@ -0,0 +1,173 @@
+/*
+ * tpm_tis_isa.c - QEMU's TPM TIS ISA Device
+ *
+ * Copyright (C) 2006,2010-2013 IBM Corporation
+ *
+ * Authors:
+ * Stefan Berger <stefanb@us.ibm.com>
+ * David Safford <safford@us.ibm.com>
+ *
+ * Xen 4 support: Andrease Niederl <andreas.niederl@iaik.tugraz.at>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ * Implementation of the TIS interface according to specs found at
+ * http://www.trustedcomputinggroup.org. This implementation currently
+ * supports version 1.3, 21 March 2013
+ * In the developers menu choose the PC Client section then find the TIS
+ * specification.
+ *
+ * TPM TIS for TPM 2 implementation following TCG PC Client Platform
+ * TPM Profile (PTP) Specification, Familiy 2.0, Revision 00.43
+ */
+
+#include "qemu/osdep.h"
+#include "hw/isa/isa.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "hw/acpi/tpm.h"
+#include "tpm_prop.h"
+#include "tpm_tis.h"
+#include "qom/object.h"
+
+struct TPMStateISA {
+ /*< private >*/
+ ISADevice parent_obj;
+
+ /*< public >*/
+ TPMState state; /* not a QOM object */
+};
+
+OBJECT_DECLARE_SIMPLE_TYPE(TPMStateISA, TPM_TIS_ISA)
+
+static int tpm_tis_pre_save_isa(void *opaque)
+{
+ TPMStateISA *isadev = opaque;
+
+ return tpm_tis_pre_save(&isadev->state);
+}
+
+static const VMStateDescription vmstate_tpm_tis_isa = {
+ .name = "tpm-tis",
+ .version_id = 0,
+ .pre_save = tpm_tis_pre_save_isa,
+ .fields = (VMStateField[]) {
+ VMSTATE_BUFFER(state.buffer, TPMStateISA),
+ VMSTATE_UINT16(state.rw_offset, TPMStateISA),
+ VMSTATE_UINT8(state.active_locty, TPMStateISA),
+ VMSTATE_UINT8(state.aborting_locty, TPMStateISA),
+ VMSTATE_UINT8(state.next_locty, TPMStateISA),
+
+ VMSTATE_STRUCT_ARRAY(state.loc, TPMStateISA, TPM_TIS_NUM_LOCALITIES, 0,
+ vmstate_locty, TPMLocality),
+
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void tpm_tis_isa_request_completed(TPMIf *ti, int ret)
+{
+ TPMStateISA *isadev = TPM_TIS_ISA(ti);
+ TPMState *s = &isadev->state;
+
+ tpm_tis_request_completed(s, ret);
+}
+
+static enum TPMVersion tpm_tis_isa_get_tpm_version(TPMIf *ti)
+{
+ TPMStateISA *isadev = TPM_TIS_ISA(ti);
+ TPMState *s = &isadev->state;
+
+ return tpm_tis_get_tpm_version(s);
+}
+
+static void tpm_tis_isa_reset(DeviceState *dev)
+{
+ TPMStateISA *isadev = TPM_TIS_ISA(dev);
+ TPMState *s = &isadev->state;
+
+ return tpm_tis_reset(s);
+}
+
+static Property tpm_tis_isa_properties[] = {
+ DEFINE_PROP_UINT32("irq", TPMStateISA, state.irq_num, TPM_TIS_IRQ),
+ DEFINE_PROP_TPMBE("tpmdev", TPMStateISA, state.be_driver),
+ DEFINE_PROP_BOOL("ppi", TPMStateISA, state.ppi_enabled, true),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void tpm_tis_isa_initfn(Object *obj)
+{
+ TPMStateISA *isadev = TPM_TIS_ISA(obj);
+ TPMState *s = &isadev->state;
+
+ memory_region_init_io(&s->mmio, obj, &tpm_tis_memory_ops,
+ s, "tpm-tis-mmio",
+ TPM_TIS_NUM_LOCALITIES << TPM_TIS_LOCALITY_SHIFT);
+}
+
+static void tpm_tis_isa_realizefn(DeviceState *dev, Error **errp)
+{
+ TPMStateISA *isadev = TPM_TIS_ISA(dev);
+ TPMState *s = &isadev->state;
+
+ if (!tpm_find()) {
+ error_setg(errp, "at most one TPM device is permitted");
+ return;
+ }
+
+ if (!s->be_driver) {
+ error_setg(errp, "'tpmdev' property is required");
+ return;
+ }
+ if (s->irq_num > 15) {
+ error_setg(errp, "IRQ %d is outside valid range of 0 to 15",
+ s->irq_num);
+ return;
+ }
+
+ isa_init_irq(ISA_DEVICE(dev), &s->irq, s->irq_num);
+
+ memory_region_add_subregion(isa_address_space(ISA_DEVICE(dev)),
+ TPM_TIS_ADDR_BASE, &s->mmio);
+
+ if (s->ppi_enabled) {
+ tpm_ppi_init(&s->ppi, isa_address_space(ISA_DEVICE(dev)),
+ TPM_PPI_ADDR_BASE, OBJECT(dev));
+ }
+}
+
+static void tpm_tis_isa_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ TPMIfClass *tc = TPM_IF_CLASS(klass);
+
+ device_class_set_props(dc, tpm_tis_isa_properties);
+ dc->vmsd = &vmstate_tpm_tis_isa;
+ tc->model = TPM_MODEL_TPM_TIS;
+ dc->realize = tpm_tis_isa_realizefn;
+ dc->reset = tpm_tis_isa_reset;
+ tc->request_completed = tpm_tis_isa_request_completed;
+ tc->get_version = tpm_tis_isa_get_tpm_version;
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+}
+
+static const TypeInfo tpm_tis_isa_info = {
+ .name = TYPE_TPM_TIS_ISA,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(TPMStateISA),
+ .instance_init = tpm_tis_isa_initfn,
+ .class_init = tpm_tis_isa_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_TPM_IF },
+ { }
+ }
+};
+
+static void tpm_tis_isa_register(void)
+{
+ type_register_static(&tpm_tis_isa_info);
+}
+
+type_init(tpm_tis_isa_register)
diff --git a/hw/tpm/tpm_tis_sysbus.c b/hw/tpm/tpm_tis_sysbus.c
new file mode 100644
index 000000000..45e63efd6
--- /dev/null
+++ b/hw/tpm/tpm_tis_sysbus.c
@@ -0,0 +1,162 @@
+/*
+ * tpm_tis_sysbus.c - QEMU's TPM TIS SYSBUS Device
+ *
+ * Copyright (C) 2006,2010-2013 IBM Corporation
+ *
+ * Authors:
+ * Stefan Berger <stefanb@us.ibm.com>
+ * David Safford <safford@us.ibm.com>
+ *
+ * Xen 4 support: Andrease Niederl <andreas.niederl@iaik.tugraz.at>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ * Implementation of the TIS interface according to specs found at
+ * http://www.trustedcomputinggroup.org. This implementation currently
+ * supports version 1.3, 21 March 2013
+ * In the developers menu choose the PC Client section then find the TIS
+ * specification.
+ *
+ * TPM TIS for TPM 2 implementation following TCG PC Client Platform
+ * TPM Profile (PTP) Specification, Familiy 2.0, Revision 00.43
+ */
+
+#include "qemu/osdep.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "hw/acpi/tpm.h"
+#include "tpm_prop.h"
+#include "hw/sysbus.h"
+#include "tpm_tis.h"
+#include "qom/object.h"
+
+struct TPMStateSysBus {
+ /*< private >*/
+ SysBusDevice parent_obj;
+
+ /*< public >*/
+ TPMState state; /* not a QOM object */
+};
+
+OBJECT_DECLARE_SIMPLE_TYPE(TPMStateSysBus, TPM_TIS_SYSBUS)
+
+static int tpm_tis_pre_save_sysbus(void *opaque)
+{
+ TPMStateSysBus *sbdev = opaque;
+
+ return tpm_tis_pre_save(&sbdev->state);
+}
+
+static const VMStateDescription vmstate_tpm_tis_sysbus = {
+ .name = "tpm-tis",
+ .version_id = 0,
+ .pre_save = tpm_tis_pre_save_sysbus,
+ .fields = (VMStateField[]) {
+ VMSTATE_BUFFER(state.buffer, TPMStateSysBus),
+ VMSTATE_UINT16(state.rw_offset, TPMStateSysBus),
+ VMSTATE_UINT8(state.active_locty, TPMStateSysBus),
+ VMSTATE_UINT8(state.aborting_locty, TPMStateSysBus),
+ VMSTATE_UINT8(state.next_locty, TPMStateSysBus),
+
+ VMSTATE_STRUCT_ARRAY(state.loc, TPMStateSysBus, TPM_TIS_NUM_LOCALITIES,
+ 0, vmstate_locty, TPMLocality),
+
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void tpm_tis_sysbus_request_completed(TPMIf *ti, int ret)
+{
+ TPMStateSysBus *sbdev = TPM_TIS_SYSBUS(ti);
+ TPMState *s = &sbdev->state;
+
+ tpm_tis_request_completed(s, ret);
+}
+
+static enum TPMVersion tpm_tis_sysbus_get_tpm_version(TPMIf *ti)
+{
+ TPMStateSysBus *sbdev = TPM_TIS_SYSBUS(ti);
+ TPMState *s = &sbdev->state;
+
+ return tpm_tis_get_tpm_version(s);
+}
+
+static void tpm_tis_sysbus_reset(DeviceState *dev)
+{
+ TPMStateSysBus *sbdev = TPM_TIS_SYSBUS(dev);
+ TPMState *s = &sbdev->state;
+
+ return tpm_tis_reset(s);
+}
+
+static Property tpm_tis_sysbus_properties[] = {
+ DEFINE_PROP_UINT32("irq", TPMStateSysBus, state.irq_num, TPM_TIS_IRQ),
+ DEFINE_PROP_TPMBE("tpmdev", TPMStateSysBus, state.be_driver),
+ DEFINE_PROP_BOOL("ppi", TPMStateSysBus, state.ppi_enabled, false),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void tpm_tis_sysbus_initfn(Object *obj)
+{
+ TPMStateSysBus *sbdev = TPM_TIS_SYSBUS(obj);
+ TPMState *s = &sbdev->state;
+
+ memory_region_init_io(&s->mmio, obj, &tpm_tis_memory_ops,
+ s, "tpm-tis-mmio",
+ TPM_TIS_NUM_LOCALITIES << TPM_TIS_LOCALITY_SHIFT);
+
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
+}
+
+static void tpm_tis_sysbus_realizefn(DeviceState *dev, Error **errp)
+{
+ TPMStateSysBus *sbdev = TPM_TIS_SYSBUS(dev);
+ TPMState *s = &sbdev->state;
+
+ if (!tpm_find()) {
+ error_setg(errp, "at most one TPM device is permitted");
+ return;
+ }
+
+ if (!s->be_driver) {
+ error_setg(errp, "'tpmdev' property is required");
+ return;
+ }
+}
+
+static void tpm_tis_sysbus_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ TPMIfClass *tc = TPM_IF_CLASS(klass);
+
+ device_class_set_props(dc, tpm_tis_sysbus_properties);
+ dc->vmsd = &vmstate_tpm_tis_sysbus;
+ tc->model = TPM_MODEL_TPM_TIS;
+ dc->realize = tpm_tis_sysbus_realizefn;
+ dc->user_creatable = true;
+ dc->reset = tpm_tis_sysbus_reset;
+ tc->request_completed = tpm_tis_sysbus_request_completed;
+ tc->get_version = tpm_tis_sysbus_get_tpm_version;
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+}
+
+static const TypeInfo tpm_tis_sysbus_info = {
+ .name = TYPE_TPM_TIS_SYSBUS,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(TPMStateSysBus),
+ .instance_init = tpm_tis_sysbus_initfn,
+ .class_init = tpm_tis_sysbus_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_TPM_IF },
+ { }
+ }
+};
+
+static void tpm_tis_sysbus_register(void)
+{
+ type_register_static(&tpm_tis_sysbus_info);
+}
+
+type_init(tpm_tis_sysbus_register)
diff --git a/hw/tpm/trace-events b/hw/tpm/trace-events
new file mode 100644
index 000000000..f17110458
--- /dev/null
+++ b/hw/tpm/trace-events
@@ -0,0 +1,38 @@
+# See docs/devel/tracing.rst for syntax documentation.
+
+# tpm_crb.c
+tpm_crb_mmio_read(uint64_t addr, unsigned size, uint32_t val) "CRB read 0x%016" PRIx64 " len:%u val: 0x%" PRIx32
+tpm_crb_mmio_write(uint64_t addr, unsigned size, uint32_t val) "CRB write 0x%016" PRIx64 " len:%u val: 0x%" PRIx32
+
+# tpm_tis_common.c
+tpm_tis_raise_irq(uint32_t irqmask) "Raising IRQ for flag 0x%08x"
+tpm_tis_new_active_locality(uint8_t locty) "Active locality is now %d"
+tpm_tis_abort(uint8_t locty) "New active locality is %d"
+tpm_tis_data_read(uint32_t value, uint32_t off) "byte 0x%02x [%d]"
+tpm_tis_mmio_read(unsigned size, uint32_t addr, uint32_t val) " read.%u(0x%08x) = 0x%08x"
+tpm_tis_mmio_write(unsigned size, uint32_t addr, uint32_t val) "write.%u(0x%08x) = 0x%08x"
+tpm_tis_mmio_write_locty4(void) "Access to locality 4 only allowed from hardware"
+tpm_tis_mmio_write_release_locty(uint8_t locty) "Releasing locality %d"
+tpm_tis_mmio_write_locty_req_use(uint8_t locty) "Locality %d requests use"
+tpm_tis_mmio_write_next_locty(uint8_t locty) "Next active locality is %d"
+tpm_tis_mmio_write_locty_seized(uint8_t locty, uint8_t active) "Locality %d seized from locality %d"
+tpm_tis_mmio_write_init_abort(void) "Initiating abort"
+tpm_tis_mmio_write_lowering_irq(void) "Lowering IRQ"
+tpm_tis_mmio_write_data2send(uint32_t value, unsigned size) "Data to send to TPM: 0x%08x (size=%d)"
+tpm_tis_pre_save(uint8_t locty, uint32_t rw_offset) "locty: %d, rw_offset = %u"
+
+# tpm_ppi.c
+tpm_ppi_memset(uint8_t *ptr, size_t size) "memset: %p %zu"
+
+# tpm_spapr.c
+tpm_spapr_do_crq(uint8_t raw1, uint8_t raw2) "1st 2 bytes in CRQ: 0x%02x 0x%02x"
+tpm_spapr_do_crq_crq_result(void) "SPAPR_VTPM_INIT_CRQ_RESULT"
+tpm_spapr_do_crq_crq_complete_result(void) "SPAPR_VTPM_INIT_CRQ_COMP_RESULT"
+tpm_spapr_do_crq_tpm_command(void) "got TPM command payload"
+tpm_spapr_do_crq_tpm_get_rtce_buffer_size(size_t buffersize) "response: buffer size is %zu"
+tpm_spapr_do_crq_get_version(uint32_t version) "response: version %u"
+tpm_spapr_do_crq_prepare_to_suspend(void) "response: preparing to suspend"
+tpm_spapr_do_crq_unknown_msg_type(uint8_t type) "Unknown message type 0x%02x"
+tpm_spapr_do_crq_unknown_crq(uint8_t raw1, uint8_t raw2) "unknown CRQ 0x%02x 0x%02x ..."
+tpm_spapr_post_load(void) "Delivering TPM response after resume"
+tpm_spapr_caught_response(uint32_t v) "Caught response to deliver after resume: %u bytes"
diff --git a/hw/tpm/trace.h b/hw/tpm/trace.h
new file mode 100644
index 000000000..9827c128a
--- /dev/null
+++ b/hw/tpm/trace.h
@@ -0,0 +1 @@
+#include "trace/trace-hw_tpm.h"