*
* Implementation of the TIS interface according to specs found at
* http://www.trustedcomputinggroup.org. This implementation currently
- * supports version 1.21, revision 1.0.
+ * 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 "sysemu/tpm_backend.h"
#include "tpm_int.h"
-#include "block/block.h"
+#include "sysemu/block-backend.h"
#include "exec/address-spaces.h"
#include "hw/hw.h"
#include "hw/i386/pc.h"
#include "hw/pci/pci_ids.h"
#include "tpm_tis.h"
#include "qemu-common.h"
+#include "qemu/main-loop.h"
+#include "sysemu/tpm_backend.h"
-/*#define DEBUG_TIS */
+#define DEBUG_TIS 0
-#ifdef DEBUG_TIS
-#define DPRINTF(fmt, ...) \
- do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0)
-#else
-#define DPRINTF(fmt, ...) \
- do { } while (0)
-#endif
+#define DPRINTF(fmt, ...) do { \
+ if (DEBUG_TIS) { \
+ printf(fmt, ## __VA_ARGS__); \
+ } \
+} while (0);
/* whether the STS interrupt is supported */
#define RAISE_STS_IRQ
#define TPM_TIS_REG_INTF_CAPABILITY 0x14
#define TPM_TIS_REG_STS 0x18
#define TPM_TIS_REG_DATA_FIFO 0x24
+#define TPM_TIS_REG_INTERFACE_ID 0x30
+#define TPM_TIS_REG_DATA_XFIFO 0x80
+#define TPM_TIS_REG_DATA_XFIFO_END 0xbc
#define TPM_TIS_REG_DID_VID 0xf00
#define TPM_TIS_REG_RID 0xf04
/* vendor-specific registers */
#define TPM_TIS_REG_DEBUG 0xf90
+#define TPM_TIS_STS_TPM_FAMILY_MASK (0x3 << 26)/* TPM 2.0 */
+#define TPM_TIS_STS_TPM_FAMILY1_2 (0 << 26) /* TPM 2.0 */
+#define TPM_TIS_STS_TPM_FAMILY2_0 (1 << 26) /* TPM 2.0 */
+#define TPM_TIS_STS_RESET_ESTABLISHMENT_BIT (1 << 25) /* TPM 2.0 */
+#define TPM_TIS_STS_COMMAND_CANCEL (1 << 24) /* TPM 2.0 */
+
#define TPM_TIS_STS_VALID (1 << 7)
#define TPM_TIS_STS_COMMAND_READY (1 << 6)
#define TPM_TIS_STS_TPM_GO (1 << 5)
#define TPM_TIS_STS_DATA_AVAILABLE (1 << 4)
#define TPM_TIS_STS_EXPECT (1 << 3)
+#define TPM_TIS_STS_SELFTEST_DONE (1 << 2)
#define TPM_TIS_STS_RESPONSE_RETRY (1 << 1)
#define TPM_TIS_BURST_COUNT_SHIFT 8
#endif
+#define TPM_TIS_CAP_INTERFACE_VERSION1_3 (2 << 28)
+#define TPM_TIS_CAP_INTERFACE_VERSION1_3_FOR_TPM2_0 (3 << 28)
+#define TPM_TIS_CAP_DATA_TRANSFER_64B (3 << 9)
+#define TPM_TIS_CAP_DATA_TRANSFER_LEGACY (0 << 9)
+#define TPM_TIS_CAP_BURST_COUNT_DYNAMIC (0 << 8)
#define TPM_TIS_CAP_INTERRUPT_LOW_LEVEL (1 << 4) /* support is mandatory */
-#define TPM_TIS_CAPABILITIES_SUPPORTED (TPM_TIS_CAP_INTERRUPT_LOW_LEVEL | \
- TPM_TIS_INTERRUPTS_SUPPORTED)
+#define TPM_TIS_CAPABILITIES_SUPPORTED1_3 \
+ (TPM_TIS_CAP_INTERRUPT_LOW_LEVEL | \
+ TPM_TIS_CAP_BURST_COUNT_DYNAMIC | \
+ TPM_TIS_CAP_DATA_TRANSFER_64B | \
+ TPM_TIS_CAP_INTERFACE_VERSION1_3 | \
+ TPM_TIS_INTERRUPTS_SUPPORTED)
+
+#define TPM_TIS_CAPABILITIES_SUPPORTED2_0 \
+ (TPM_TIS_CAP_INTERRUPT_LOW_LEVEL | \
+ TPM_TIS_CAP_BURST_COUNT_DYNAMIC | \
+ TPM_TIS_CAP_DATA_TRANSFER_64B | \
+ TPM_TIS_CAP_INTERFACE_VERSION1_3_FOR_TPM2_0 | \
+ TPM_TIS_INTERRUPTS_SUPPORTED)
+
+#define TPM_TIS_IFACE_ID_INTERFACE_TIS1_3 (0xf) /* TPM 2.0 */
+#define TPM_TIS_IFACE_ID_INTERFACE_FIFO (0x0) /* TPM 2.0 */
+#define TPM_TIS_IFACE_ID_INTERFACE_VER_FIFO (0 << 4) /* TPM 2.0 */
+#define TPM_TIS_IFACE_ID_CAP_5_LOCALITIES (1 << 8) /* TPM 2.0 */
+#define TPM_TIS_IFACE_ID_CAP_TIS_SUPPORTED (1 << 13) /* TPM 2.0 */
+#define TPM_TIS_IFACE_ID_INT_SEL_LOCK (1 << 19) /* TPM 2.0 */
+
+#define TPM_TIS_IFACE_ID_SUPPORTED_FLAGS1_3 \
+ (TPM_TIS_IFACE_ID_INTERFACE_TIS1_3 | \
+ (~0u << 4)/* all of it is don't care */)
+
+/* if backend was a TPM 2.0: */
+#define TPM_TIS_IFACE_ID_SUPPORTED_FLAGS2_0 \
+ (TPM_TIS_IFACE_ID_INTERFACE_FIFO | \
+ TPM_TIS_IFACE_ID_INTERFACE_VER_FIFO | \
+ TPM_TIS_IFACE_ID_CAP_5_LOCALITIES | \
+ TPM_TIS_IFACE_ID_CAP_TIS_SUPPORTED)
#define TPM_TIS_TPM_DID 0x0001
#define TPM_TIS_TPM_VID PCI_VENDOR_ID_IBM
#endif
}
+/*
+ * 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.
*/
*/
if (tis->aborting_locty == tis->next_locty) {
tis->loc[tis->aborting_locty].state = TPM_TIS_STATE_READY;
- tis->loc[tis->aborting_locty].sts = TPM_TIS_STS_COMMAND_READY;
+ tpm_tis_sts_set(&tis->loc[tis->aborting_locty],
+ TPM_TIS_STS_COMMAND_READY);
tpm_tis_raise_irq(s, tis->aborting_locty, TPM_TIS_INT_COMMAND_READY);
}
TPMTISEmuState *tis = &s->s.tis;
uint8_t locty = s->locty_number;
- tis->loc[locty].sts = TPM_TIS_STS_VALID | TPM_TIS_STS_DATA_AVAILABLE;
+ tpm_tis_sts_set(&tis->loc[locty],
+ TPM_TIS_STS_VALID | TPM_TIS_STS_DATA_AVAILABLE);
tis->loc[locty].state = TPM_TIS_STATE_COMPLETION;
tis->loc[locty].r_offset = 0;
tis->loc[locty].w_offset = 0;
/*
* Callback from the TPM to indicate that the response was received.
*/
-static void tpm_tis_receive_cb(TPMState *s, uint8_t locty)
+static void tpm_tis_receive_cb(TPMState *s, uint8_t locty,
+ bool is_selftest_done)
{
TPMTISEmuState *tis = &s->s.tis;
+ uint8_t l;
assert(s->locty_number == locty);
+ if (is_selftest_done) {
+ for (l = 0; l < TPM_TIS_NUM_LOCALITIES; l++) {
+ tis->loc[locty].sts |= TPM_TIS_STS_SELFTEST_DONE;
+ }
+ }
+
qemu_bh_schedule(tis->bh);
}
ret = tis->loc[locty].r_buffer.buffer[tis->loc[locty].r_offset++];
if (tis->loc[locty].r_offset >= len) {
/* got last byte */
- tis->loc[locty].sts = TPM_TIS_STS_VALID;
+ tpm_tis_sts_set(&tis->loc[locty], TPM_TIS_STS_VALID);
#ifdef RAISE_STS_IRQ
tpm_tis_raise_irq(s, locty, TPM_TIS_INT_STS_VALID);
#endif
for (idx = 0; regs[idx] != 0xfff; idx++) {
DPRINTF("tpm_tis: 0x%04x : 0x%08x\n", regs[idx],
- (uint32_t)tpm_tis_mmio_read(opaque, base + regs[idx], 4));
+ (int)tpm_tis_mmio_read(opaque, base + regs[idx], 4));
}
DPRINTF("tpm_tis: read offset : %d\n"
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 val;
val = tis->loc[locty].ints;
break;
case TPM_TIS_REG_INTF_CAPABILITY:
- val = TPM_TIS_CAPABILITIES_SUPPORTED;
+ 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 (tis->active_locty == locty) {
}
break;
case TPM_TIS_REG_DATA_FIFO:
+ case TPM_TIS_REG_DATA_XFIFO ... TPM_TIS_REG_DATA_XFIFO_END:
if (tis->active_locty == locty) {
- switch (tis->loc[locty].state) {
- case TPM_TIS_STATE_COMPLETION:
- val = tpm_tis_data_read(s, locty);
- break;
- default:
- val = TPM_TIS_NO_DATA_BYTE;
- break;
+ if (size > 4 - (addr & 0x3)) {
+ /* prevent access beyond FIFO */
+ size = 4 - (addr & 0x3);
}
+ val = 0;
+ shift = 0;
+ while (size > 0) {
+ switch (tis->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 = tis->loc[locty].iface_id;
+ break;
case TPM_TIS_REG_DID_VID:
val = (TPM_TIS_TPM_DID << 16) | TPM_TIS_TPM_VID;
break;
val >>= shift;
}
- DPRINTF("tpm_tis: read.%u(%08x) = %08x\n", size, (int)addr, (uint32_t)val);
+ DPRINTF("tpm_tis: read.%u(%08x) = %08x\n", size, (int)addr, (int)val);
return val;
}
{
TPMState *s = opaque;
TPMTISEmuState *tis = &s->s.tis;
- uint16_t off = addr & 0xfff;
+ 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);
- DPRINTF("tpm_tis: write.%u(%08x) = %08x\n", size, (int)addr, (uint32_t)val);
+ DPRINTF("tpm_tis: write.%u(%08x) = %08x\n", size, (int)addr, (int)val);
if (locty == 4 && !hw_access) {
DPRINTF("tpm_tis: Access to locality 4 only allowed from hardware\n");
return;
}
+ val &= mask;
+
+ if (shift) {
+ val <<= shift;
+ mask <<= shift;
+ }
+
+ mask ^= 0xffffffff;
+
switch (off) {
case TPM_TIS_REG_ACCESS:
break;
}
- tis->loc[locty].inte = (val & (TPM_TIS_INT_ENABLED |
- TPM_TIS_INT_POLARITY_MASK |
- TPM_TIS_INTERRUPTS_SUPPORTED));
+ tis->loc[locty].inte &= mask;
+ tis->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;
}
+ 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 (tis->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);
break;
case TPM_TIS_STATE_IDLE:
- tis->loc[locty].sts = TPM_TIS_STS_COMMAND_READY;
+ tpm_tis_sts_set(&tis->loc[locty], TPM_TIS_STS_COMMAND_READY);
tis->loc[locty].state = TPM_TIS_STATE_READY;
tpm_tis_raise_irq(s, locty, TPM_TIS_INT_COMMAND_READY);
break;
/* shortcut to ready state with C/R set */
tis->loc[locty].state = TPM_TIS_STATE_READY;
if (!(tis->loc[locty].sts & TPM_TIS_STS_COMMAND_READY)) {
- tis->loc[locty].sts = TPM_TIS_STS_COMMAND_READY;
+ tpm_tis_sts_set(&tis->loc[locty],
+ TPM_TIS_STS_COMMAND_READY);
tpm_tis_raise_irq(s, locty, TPM_TIS_INT_COMMAND_READY);
}
tis->loc[locty].sts &= ~(TPM_TIS_STS_DATA_AVAILABLE);
switch (tis->loc[locty].state) {
case TPM_TIS_STATE_COMPLETION:
tis->loc[locty].r_offset = 0;
- tis->loc[locty].sts = TPM_TIS_STS_VALID |
- TPM_TIS_STS_DATA_AVAILABLE;
+ tpm_tis_sts_set(&tis->loc[locty],
+ TPM_TIS_STS_VALID|
+ TPM_TIS_STS_DATA_AVAILABLE);
break;
default:
/* ignore */
}
break;
case TPM_TIS_REG_DATA_FIFO:
+ case TPM_TIS_REG_DATA_XFIFO ... TPM_TIS_REG_DATA_XFIFO_END:
/* data fifo */
if (tis->active_locty != locty) {
break;
tis->loc[locty].state == TPM_TIS_STATE_COMPLETION) {
/* drop the byte */
} else {
- DPRINTF("tpm_tis: Byte to send to TPM: %02x\n", (uint8_t)val);
+ DPRINTF("tpm_tis: Data to send to TPM: %08x (size=%d)\n",
+ (int)val, size);
if (tis->loc[locty].state == TPM_TIS_STATE_READY) {
tis->loc[locty].state = TPM_TIS_STATE_RECEPTION;
- tis->loc[locty].sts = TPM_TIS_STS_EXPECT | TPM_TIS_STS_VALID;
+ tpm_tis_sts_set(&tis->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);
}
- if ((tis->loc[locty].sts & TPM_TIS_STS_EXPECT)) {
+ while ((tis->loc[locty].sts & TPM_TIS_STS_EXPECT) && size > 0) {
if (tis->loc[locty].w_offset < tis->loc[locty].w_buffer.size) {
tis->loc[locty].w_buffer.
buffer[tis->loc[locty].w_offset++] = (uint8_t)val;
+ val >>= 8;
+ size--;
} else {
- tis->loc[locty].sts = TPM_TIS_STS_VALID;
+ tpm_tis_sts_set(&tis->loc[locty], TPM_TIS_STS_VALID);
}
}
(tis->loc[locty].sts & TPM_TIS_STS_EXPECT)) {
/* we have a packet length - see if we have all of it */
#ifdef RAISE_STS_IRQ
- bool needIrq = !(tis->loc[locty].sts & TPM_TIS_STS_VALID);
+ bool need_irq = !(tis->loc[locty].sts & TPM_TIS_STS_VALID);
#endif
len = tpm_tis_get_size_from_buffer(&tis->loc[locty].w_buffer);
if (len > tis->loc[locty].w_offset) {
- tis->loc[locty].sts = TPM_TIS_STS_EXPECT |
- TPM_TIS_STS_VALID;
+ tpm_tis_sts_set(&tis->loc[locty],
+ TPM_TIS_STS_EXPECT | TPM_TIS_STS_VALID);
} else {
/* packet complete */
- tis->loc[locty].sts = TPM_TIS_STS_VALID;
+ tpm_tis_sts_set(&tis->loc[locty], TPM_TIS_STS_VALID);
}
#ifdef RAISE_STS_IRQ
- if (needIrq) {
+ if (need_irq) {
tpm_tis_raise_irq(s, locty, TPM_TIS_INT_STS_VALID);
}
#endif
}
}
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++) {
+ tis->loc[l].iface_id |= TPM_TIS_IFACE_ID_INT_SEL_LOCK;
+ }
+ }
+ break;
}
}
static void tpm_tis_mmio_write(void *opaque, hwaddr addr,
uint64_t val, unsigned size)
{
- return tpm_tis_mmio_write_intern(opaque, addr, val, size, false);
+ tpm_tis_mmio_write_intern(opaque, addr, val, size, false);
}
static const MemoryRegionOps tpm_tis_memory_ops = {
return tpm_backend_startup_tpm(s->be_driver);
}
+/*
+ * Get the TPMVersion of the backend device being used
+ */
+TPMVersion tpm_tis_get_tpm_version(Object *obj)
+{
+ TPMState *s = TPM(obj);
+
+ return tpm_backend_get_tpm_version(s->be_driver);
+}
+
/*
* This function is called when the machine starts, resets or due to
* S3 resume.
TPMTISEmuState *tis = &s->s.tis;
int c;
+ s->be_tpm_version = tpm_backend_get_tpm_version(s->be_driver);
+
tpm_backend_reset(s->be_driver);
tis->active_locty = TPM_TIS_NO_LOCALITY;
for (c = 0; c < TPM_TIS_NUM_LOCALITIES; c++) {
tis->loc[c].access = TPM_TIS_ACCESS_TPM_REG_VALID_STS;
- tis->loc[c].sts = 0;
+ switch (s->be_tpm_version) {
+ case TPM_VERSION_UNSPEC:
+ break;
+ case TPM_VERSION_1_2:
+ tis->loc[c].sts = TPM_TIS_STS_TPM_FAMILY1_2;
+ tis->loc[c].iface_id = TPM_TIS_IFACE_ID_SUPPORTED_FLAGS1_3;
+ break;
+ case TPM_VERSION_2_0:
+ tis->loc[c].sts = TPM_TIS_STS_TPM_FAMILY2_0;
+ tis->loc[c].iface_id = TPM_TIS_IFACE_ID_SUPPORTED_FLAGS2_0;
+ break;
+ }
tis->loc[c].inte = TPM_TIS_INT_POLARITY_LOW_LEVEL;
tis->loc[c].ints = 0;
tis->loc[c].state = TPM_TIS_STATE_IDLE;
tis->bh = qemu_bh_new(tpm_tis_receive_bh, s);
isa_init_irq(&s->busdev, &tis->irq, tis->irq_num);
+
+ memory_region_add_subregion(isa_address_space(ISA_DEVICE(dev)),
+ TPM_TIS_ADDR_BASE, &s->mmio);
}
static void tpm_tis_initfn(Object *obj)
{
- ISADevice *dev = ISA_DEVICE(obj);
TPMState *s = TPM(obj);
memory_region_init_io(&s->mmio, OBJECT(s), &tpm_tis_memory_ops,
s, "tpm-tis-mmio",
TPM_TIS_NUM_LOCALITIES << TPM_TIS_LOCALITY_SHIFT);
- memory_region_add_subregion(isa_address_space(dev), TPM_TIS_ADDR_BASE,
- &s->mmio);
-}
-
-static void tpm_tis_uninitfn(Object *obj)
-{
- TPMState *s = TPM(obj);
-
- memory_region_del_subregion(get_system_memory(), &s->mmio);
- memory_region_destroy(&s->mmio);
}
static void tpm_tis_class_init(ObjectClass *klass, void *data)
.parent = TYPE_ISA_DEVICE,
.instance_size = sizeof(TPMState),
.instance_init = tpm_tis_initfn,
- .instance_finalize = tpm_tis_uninitfn,
.class_init = tpm_tis_class_init,
};