Signed-off-by: Ninad Palsule <ninad@linux.ibm.com>
---
V2:
Incorporated Stephen's review comments.
- Handled checksum related register in I2C layer
- Defined I2C interface capabilities and return those instead of
capabilities from TPM TIS. Add required capabilities from TIS.
- Do not cache FIFO data in the I2C layer.
- Make sure that Device address change register is not passed to I2C
layer as capability indicate that it is not supported.
- Added boundary checks.
- Make sure that bits 26-31 are zeroed for the TPM_STS register on read
- Updated Kconfig files for new define.
---
V3:
- Moved processing of register TPM_I2C_LOC_SEL in the I2C. So I2C layer
remembers the locality and pass it to TIS on each read/write.
- The write data is no more cached in the I2C layer so the buffer size
is reduced to 16 bytes.
- Checksum registers are now managed by the I2C layer. Added new
function in TIS layer to return the checksum and used that to process
the request.
- Now 2-4 byte register value will be passed to TIS layer in a single
write call instead of 1 byte at a time. Added functions to convert
between little endian stream of bytes to single 32 bit unsigned
integer. Similarly 32 bit integer to stream of bytes.
- Added restriction on device change register.
- Replace few if-else statement with switch statement for clarity.
- Log warning when unknown register is received.
- Moved all register definations to acpi/tmp.h
---
V4:
Incorporated review comments from Cedric and Stefan.
- Reduced data[] size from 16 byte to 5 bytes.
- Added register name in the mapping table which can be used for
tracing.
- Removed the endian conversion functions instead used simple logic
provided by Stefan.
- Rename I2C registers to reduce the length.
- Added traces for send, recv and event functions. You can turn on trace
on command line by using "-trace "tpm_tis_i2c*" option.
---
hw/arm/Kconfig | 1 +
hw/tpm/Kconfig | 7 +
hw/tpm/meson.build | 1 +
hw/tpm/tpm_tis_i2c.c | 492 +++++++++++++++++++++++++++++++++++++++++++
hw/tpm/trace-events | 6 +
include/sysemu/tpm.h | 3 +
6 files changed, 510 insertions(+)
create mode 100644 hw/tpm/tpm_tis_i2c.c
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index b5aed4aff5..05d6ef1a31 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -6,6 +6,7 @@ config ARM_VIRT
imply VFIO_PLATFORM
imply VFIO_XGMAC
imply TPM_TIS_SYSBUS
+ imply TPM_TIS_I2C
imply NVDIMM
select ARM_GIC
select ACPI
diff --git a/hw/tpm/Kconfig b/hw/tpm/Kconfig
index 29e82f3c92..a46663288c 100644
--- a/hw/tpm/Kconfig
+++ b/hw/tpm/Kconfig
@@ -1,3 +1,10 @@
+config TPM_TIS_I2C
+ bool
+ depends on TPM
+ select TPM_BACKEND
+ select I2C
+ select TPM_TIS
+
config TPM_TIS_ISA
bool
depends on TPM && ISA_BUS
diff --git a/hw/tpm/meson.build b/hw/tpm/meson.build
index 7abc2d794a..76fe3cb098 100644
--- a/hw/tpm/meson.build
+++ b/hw/tpm/meson.build
@@ -1,6 +1,7 @@
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_TIS_I2C', if_true:
files('tpm_tis_i2c.c'))
softmmu_ss.add(when: 'CONFIG_TPM_CRB', if_true: files('tpm_crb.c'))
softmmu_ss.add(when: 'CONFIG_TPM_TIS', if_true: files('tpm_ppi.c'))
softmmu_ss.add(when: 'CONFIG_TPM_CRB', if_true: files('tpm_ppi.c'))
diff --git a/hw/tpm/tpm_tis_i2c.c b/hw/tpm/tpm_tis_i2c.c
new file mode 100644
index 0000000000..ee47859f6e
--- /dev/null
+++ b/hw/tpm/tpm_tis_i2c.c
@@ -0,0 +1,492 @@
+/*
+ * tpm_tis_i2c.c - QEMU's TPM TIS I2C Device
+ *
+ * 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
+ *
+ * TPM I2C implementation follows TCG TPM I2c Interface specification,
+ * Family 2.0, Level 00, Revision 1.00
+ */
+
+#include "qemu/osdep.h"
+#include "hw/i2c/i2c.h"
+#include "hw/qdev-properties.h"
+#include "hw/acpi/tpm.h"
+#include "migration/vmstate.h"
+#include "tpm_prop.h"
+#include "qom/object.h"
+#include "block/aio.h"
+#include "qemu/main-loop.h"
+#include "qemu/log.h"
+#include "trace.h"
+#include "tpm_tis.h"
+
+/* TPM_STS mask for read bits 31:26 must be zero */
+#define TPM_I2C_STS_READ_MASK 0x03ffffff
+
+/* Operations */
+#define OP_SEND 1
+#define OP_RECV 2
+
+typedef struct TPMStateI2C {
+ /*< private >*/
+ I2CSlave parent_obj;
+
+ int offset; /* offset in to data[] */
in to -> into
+ uint16_t tis_reg; /* TIS register */
+ char name[16]; /* Register name */
+} i2cRegMap;
+
+/*
+ * The register values in the common code is different than the latest
+ * register numbers as per the spec hence add the conversion map
+ */
+static const i2cRegMap tpm_tis_reg_map[] = {
+ /*
+ * These registers are sent to TIS layer. The register with UNKNOWN
+ * mapping are not sent to TIS layer and handled in I2c layer.
+ * NOTE: Adding frequently used registers at the start
+ */
+ { TPM_I2C_REG_DATA_FIFO, TPM_TIS_REG_DATA_FIFO,
"FIFO", },
+ { TPM_I2C_REG_STS, TPM_TIS_REG_STS, "STS", },
+ { TPM_I2C_REG_DATA_CSUM_GET, TPM_I2C_REG_UNKNOWN,
"CSUM_GET", },
+ { TPM_I2C_REG_LOC_SEL, TPM_I2C_REG_UNKNOWN, "LOC_SEL", },
+
+ { TPM_I2C_REG_ACCESS, TPM_TIS_REG_ACCESS, "ACCESS", },
+ { TPM_I2C_REG_INT_ENABLE, TPM_TIS_REG_INT_ENABLE,
"INT_ENABLE",},
+ { TPM_I2C_REG_INT_CAPABILITY, TPM_TIS_REG_INT_VECTOR,
"INT_VECTOR",},
+ { TPM_I2C_REG_INTF_CAPABILITY, TPM_TIS_REG_INTF_CAPABILITY,
"INTF_CAP", },
+ { TPM_I2C_REG_DID_VID, TPM_TIS_REG_DID_VID, "DID_VID", },
+ { TPM_I2C_REG_RID, TPM_TIS_REG_RID, "RID", },
+ { TPM_I2C_REG_I2C_DEV_ADDRESS, TPM_I2C_REG_UNKNOWN,
"DEV_ADDRESS", },
+ { TPM_I2C_REG_DATA_CSUM_ENABLE, TPM_I2C_REG_UNKNOWN,
"CSUM_ENABLE", },
+};
+
+/*
+ * Generate interface capability based on what is returned by TIS
and what is
+ * expected by I2C. Save the capability in the data array
overwriting the TIS
+ * capability.
+ */
+static uint32_t tpm_i2c_interface_capability(TPMStateI2C *i2cst,
+ uint32_t tis_cap)
+{
+ uint32_t i2c_cap = 0;
+
+ i2cst->tis_intf_cap = tis_cap;
You seem to be using tis_intf_cap only in this function and you could
remove it from i2cst. There's no reason to have, just use tis_cap.
+ ret = i2cst->data[i2cst->offset++];
+ }
+
+ } else if ((i2cst->operation == OP_SEND) && (i2cst->offset < 2)) {
+ /* First receive call after send */
+
+ i2cst->operation = OP_RECV;
+
+ switch (i2c_reg) {
+ case TPM_I2C_REG_LOC_SEL:
+ /* Location selection register is managed by i2c */
+ i2cst->data[1] = i2cst->locality;
+ break;
+ case TPM_I2C_REG_DATA_FIFO:
+ /* FIFO data is directly read from TPM TIS */
+ data_read = tpm_tis_read_data(s, addr, 1);
+ i2cst->data[1] = (data_read & 0xff);
+ break;
+ case TPM_I2C_REG_DATA_CSUM_ENABLE:
+ i2cst->data[1] = i2cst->csum_enable;
+ break;
+ case TPM_I2C_REG_DATA_CSUM_GET:
+ /*
+ * Checksum registers are not supported by common code
hence
+ * call a common code to get the checksum.
+ */
+ data_read = tpm_tis_get_checksum(s);
+
+ /* Save the byte stream in data field */
+ i2cst->data[1] = (data_read & 0xff);
+ i2cst->data[2] = ((data_read >> 8) & 0xff);
+ break;
+ default:
+ data_read = tpm_tis_read_data(s, addr, 4);
+
+ if (i2c_reg == TPM_I2C_REG_INTF_CAPABILITY) {
+ /* Prepare the capabilities as per I2C interface */
+ data_read = tpm_i2c_interface_capability(i2cst,
+ data_read);
+ } else if (i2c_reg == TPM_I2C_REG_STS) {
+ /*
+ * As per specs, STS bit 31:26 are reserved and must
+ * be set to 0
+ */
+ data_read &= TPM_I2C_STS_READ_MASK;
+ }
+ /* Save byte stream in data[] */
+ i2cst->data[1] = data_read;
+ i2cst->data[2] = data_read >> 8;
+ i2cst->data[3] = data_read >> 16;
+ i2cst->data[4] = data_read >> 24;
+ break;
+ }
+
+
+ /* Return first byte with this call */
+ i2cst->offset = 1; /* keep the register value intact for
debug */
+ ret = i2cst->data[i2cst->offset++];
+ } else {
+ i2cst->operation = OP_RECV;
+ }
+
+ trace_tpm_tis_i2c_recv(ret);
+
+ return ret;
+}
+
+/*
+ * Send function only remembers data in the buffer and then calls
+ * TPM TIS common code during FINISH event.
+ */
+static int tpm_tis_i2c_send(I2CSlave *i2c, uint8_t data)
+{
+ TPMStateI2C *i2cst = TPM_TIS_I2C(i2c);
+ uint16_t tis_reg;
+
+ /* Reject non-supported registers. */
+ if (i2cst->offset == 0) {
+
+ if (trace_event_get_state(TRACE_TPM_TIS_I2C_SEND_REG)) {
+ trace_tpm_tis_i2c_send_reg(tpm_tis_i2c_get_reg_name(data));
+ }
+
+ /* We do not support device address change */
+ if (data == TPM_I2C_REG_I2C_DEV_ADDRESS) {
+ qemu_log_mask(LOG_UNIMP, "%s: Device address change "
+ "is not supported.\n", __func__);
+ return 1;
+ }
+ } else {
+ trace_tpm_tis_i2c_send(data);
+ }
+
+ if (sizeof(i2cst->data)) {
+ i2cst->operation = OP_SEND;
+
+ /* Remember data locally for non-FIFO registers */
+ if ((i2cst->offset == 0) ||
+ (i2cst->data[0] != TPM_I2C_REG_DATA_FIFO)) {
+ i2cst->data[i2cst->offset++] = data;
+ } else {
+ tis_reg = tpm_tis_i2c_to_tis_reg(i2cst);
+ tpm_tis_write_data(&i2cst->state, tis_reg, data, 1);
+ }
+
+ return 0;
+
+ } else {
+ /* Return non-zero to indicate NAK */
+ return 1;
+ }
+}
+
+static Property tpm_tis_i2c_properties[] = {
+ DEFINE_PROP_UINT32("irq", TPMStateI2C, state.irq_num, TPM_TIS_IRQ),
+ DEFINE_PROP_TPMBE("tpmdev", TPMStateI2C, state.be_driver),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void tpm_tis_i2c_realizefn(DeviceState *dev, Error **errp)
+{
+ TPMStateI2C *i2cst = TPM_TIS_I2C(dev);
+ TPMState *s = &i2cst->state;
+
+ if (!tpm_find()) {
+ error_setg(errp, "at most one TPM device is permitted");
+ return;
+ }
+
+ /*
+ * Get the backend pointer. It is not initialized propery during
+ * device_class_set_props
+ */
+ s->be_driver = qemu_find_tpm_be("tpm0");
+
+ 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;
+ }
+}
+
+static void tpm_tis_i2c_reset(DeviceState *dev)
+{
+ TPMStateI2C *i2cst = TPM_TIS_I2C(dev);
+ TPMState *s = &i2cst->state;
+
+ tpm_tis_i2c_clear_data(i2cst);
+
+ i2cst->csum_enable = 0;
+ i2cst->locality = TPM_TIS_NO_LOCALITY;
+
+ return tpm_tis_reset(s);
+}
+
+static void tpm_tis_i2c_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
+ TPMIfClass *tc = TPM_IF_CLASS(klass);
+
+ dc->realize = tpm_tis_i2c_realizefn;
+ dc->reset = tpm_tis_i2c_reset;
+ device_class_set_props(dc, tpm_tis_i2c_properties);
+
+ k->event = tpm_tis_i2c_event;
+ k->recv = tpm_tis_i2c_recv;
+ k->send = tpm_tis_i2c_send;
+
+ tc->model = TPM_MODEL_TPM_TIS;
+ tc->request_completed = tpm_tis_i2c_request_completed;
+ tc->get_version = tpm_tis_i2c_get_tpm_version;
+}
+
+static const TypeInfo tpm_tis_i2c_info = {
+ .name = TYPE_TPM_TIS_I2C,
+ .parent = TYPE_I2C_SLAVE,
+ .instance_size = sizeof(TPMStateI2C),
+ .class_init = tpm_tis_i2c_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_TPM_IF },
+ { }
+ }
+};
+
+static void tpm_tis_i2c_register_types(void)
+{
+ type_register_static(&tpm_tis_i2c_info);
+}
You seem to have removed the VMStateDescription after my comments
that it's not migratable.
The following should do the trick -- you can take it:
@@ -91,6 +91,38 @@ static const i2cRegMap tpm_tis_reg_map[] = {
{ TPM_I2C_REG_DATA_CSUM_ENABLE, TPM_I2C_REG_UNKNOWN,
"CSUM_ENABLE", },
};
+static int tpm_tis_pre_save_i2c(void *opaque)
+{
+ TPMStateI2C *i2cst = opaque;
+
+ return tpm_tis_pre_save(&i2cst->state);
+}
+
+static const VMStateDescription vmstate_tpm_tis_i2c = {
+ .name = "tpm-tis-i2c",
+ .version_id = 0,
+ .pre_save = tpm_tis_pre_save_i2c,
+ .fields = (VMStateField[]) {
+ VMSTATE_BUFFER(state.buffer, TPMStateI2C),
+ VMSTATE_UINT16(state.rw_offset, TPMStateI2C),
+ VMSTATE_UINT8(state.active_locty, TPMStateI2C),
+ VMSTATE_UINT8(state.aborting_locty, TPMStateI2C),
+ VMSTATE_UINT8(state.next_locty, TPMStateI2C),
+
+ VMSTATE_STRUCT_ARRAY(state.loc, TPMStateI2C,
TPM_TIS_NUM_LOCALITIES, 0,
+ vmstate_locty, TPMLocality),
+
+ /* i2c specifics */
+ VMSTATE_UINT8(offset, TPMStateI2C),
+ VMSTATE_UINT8(operation, TPMStateI2C),
+ VMSTATE_BUFFER(data, TPMStateI2C),
+ VMSTATE_UINT8(locality, TPMStateI2C),
+ VMSTATE_UINT8(csum_enable, TPMStateI2C),
+
+ VMSTATE_END_OF_LIST()
+ }
+};
+
/*
* Generate interface capability based on what is returned by TIS and
what is
* expected by I2C. Save the capability in the data array overwriting
the TIS
@@ -460,6 +492,7 @@ static void tpm_tis_i2c_class_init(ObjectClass
*klass, void *data)
I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
TPMIfClass *tc = TPM_IF_CLASS(klass);
+ dc->vmsd = &vmstate_tpm_tis_i2c;
dc->realize = tpm_tis_i2c_realizefn;
dc->reset = tpm_tis_i2c_reset;
device_class_set_props(dc, tpm_tis_i2c_properties);