* [PATCH v4 0/3] TPM TIS SPI Support
@ 2024-11-01 20:27 dan tan
2024-11-01 20:27 ` [PATCH v4 1/3] tpm/tpm_tis_spi: Support TPM for SPI (Serial Peripheral Interface) dan tan
` (2 more replies)
0 siblings, 3 replies; 9+ messages in thread
From: dan tan @ 2024-11-01 20:27 UTC (permalink / raw)
To: qemu-devel; +Cc: qemu-ppc, stefanb, pbonzini, farosas, lvivier, clg, dantan
*** BLURB HERE ***
Revision 4 summary:
- fold v3 commit 4/5 into 1/5
- fold v3 commit 5/5 into 3/5
Revision 3 summary:
device support:
- moved variable tis_addr from TPMStateSPI struct to local
- added the VM suspend/resume support:
- added vmstate_tpm_tis_spi declaration
- added tpm_tis_spi_pre_save() function
- fixed trace formatting string
qtest:
- removed the function prototypes declaration
- fixed code format to comply with convention
- changed function names and variable names to be the same
as the tpm-tis-i2c test.
- change hard coded numbers to #define's with meaningful
names that are identifiable with spec documentation
Version 2 summary,
addressed the following review comments:
- break up patch into 3 separate commits;
- add more details in the commit logs;
- I added links to the TCG TPM standard documents as this device
model communicates to the TPM device (hw / swtpm) via
tpm_tis_commom.c which is standard compliant;
- the TPM SPI model implementation in itself is not platform
specific. However, the SPI interface is via the PowerNV SPI
bus master, thus it is only supported on the PowerNV platform
- change all qemu_log() calls to trace events;
- move static global variables to the TPMStateSPI struct;
- fixed code formatting (verified by scripts/checkpatch.pl);
- per requests, make the code more readable by using self-
explanatory #defines and adding comments;
- added some documentation support (tpm.rst);
- beefed up the unit test exercising major supported locality
functionality
dan tan (3):
tpm/tpm_tis_spi: Support TPM for SPI (Serial Peripheral Interface)
tpm/tpm_tis_spi: activation for the PowerNV machines
tests/qtest/tpm: add unit test to tis-spi
docs/specs/tpm.rst | 15 +
include/sysemu/tpm.h | 3 +
hw/tpm/tpm_tis_spi.c | 360 +++++++++++++++
tests/qtest/tpm-tis-spi-pnv-test.c | 710 +++++++++++++++++++++++++++++
hw/ppc/Kconfig | 1 +
hw/tpm/Kconfig | 6 +
hw/tpm/meson.build | 1 +
hw/tpm/trace-events | 7 +
tests/qtest/meson.build | 2 +
9 files changed, 1105 insertions(+)
create mode 100644 hw/tpm/tpm_tis_spi.c
create mode 100644 tests/qtest/tpm-tis-spi-pnv-test.c
--
2.39.5
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v4 1/3] tpm/tpm_tis_spi: Support TPM for SPI (Serial Peripheral Interface)
2024-11-01 20:27 [PATCH v4 0/3] TPM TIS SPI Support dan tan
@ 2024-11-01 20:27 ` dan tan
2024-11-02 14:19 ` Stefan Berger
2024-11-01 20:27 ` [PATCH v4 2/3] tpm/tpm_tis_spi: activation for the PowerNV machines dan tan
2024-11-01 20:27 ` [PATCH v4 3/3] tests/qtest/tpm: add unit test to tis-spi dan tan
2 siblings, 1 reply; 9+ messages in thread
From: dan tan @ 2024-11-01 20:27 UTC (permalink / raw)
To: qemu-devel; +Cc: qemu-ppc, stefanb, pbonzini, farosas, lvivier, clg, dantan
Implement support for TPM via SPI interface. The SPI bus master
is provided by PowerNV SPI device which is an SSI peripheral.
It can uses the tpm_emulator driver backend with the external
swtpm.
Although the implementation is endian neutral, the SPI
bus master provider, pnv_spi.c is only supported on the
PowerNV platform, thus, is big endian specific.
tpm/tpm_tis_spi: Support TPM for SPI (rev 4)
- moved variable tis_addr from TPMStateSPI struct to local
- added the VM suspend/resume support:
- added vmstate_tpm_tis_spi declaration
- added tpm_tis_spi_pre_save() function
- fixed trace formatting string
Signed-off-by: dan tan <dantan@linux.vnet.ibm.com>
---
docs/specs/tpm.rst | 15 ++
include/sysemu/tpm.h | 3 +
hw/tpm/tpm_tis_spi.c | 360 +++++++++++++++++++++++++++++++++++++++++++
hw/tpm/Kconfig | 6 +
hw/tpm/meson.build | 1 +
hw/tpm/trace-events | 7 +
6 files changed, 392 insertions(+)
create mode 100644 hw/tpm/tpm_tis_spi.c
diff --git a/docs/specs/tpm.rst b/docs/specs/tpm.rst
index 1ad36ad709..63c3a43c7d 100644
--- a/docs/specs/tpm.rst
+++ b/docs/specs/tpm.rst
@@ -24,6 +24,7 @@ QEMU files related to TPM TIS interface:
- ``hw/tpm/tpm_tis_isa.c``
- ``hw/tpm/tpm_tis_sysbus.c``
- ``hw/tpm/tpm_tis_i2c.c``
+ - ``hw/tpm/tpm_tis_spi.c``
- ``hw/tpm/tpm_tis.h``
Both an ISA device and a sysbus device are available. The former is
@@ -33,6 +34,9 @@ Arm virt machine.
An I2C device support is also provided which can be instantiated in the Arm
based emulation machines. This device only supports the TPM 2 protocol.
+A Serial Peripheral Interface (SPI) device support has been added to the
+PowerNV emulation machines. This device only supports the TPM 2 protocol.
+
CRB interface
-------------
@@ -371,6 +375,17 @@ attached to I2C bus, use the following command line:
echo tpm_tis_i2c 0x2e > /sys/bus/i2c/devices/i2c-12/new_device
+In case a PowerNV machine is emulated and you want to use a TPM device
+attached to SPI bus, use the following command line (SPI bus master is
+provided by PowerNV SPI device):
+
+.. code-block:: console
+
+ qemu-system-ppc64 -m 2G -machine powernv10 -nographic \
+ -chardev socket,id=chrtpm,path=/tmp/mytpm1/swtpm-sock \
+ -tpmdev emulator,id=tpm0,chardev=chrtpm \
+ -device tpm-tis-spi,tpmdev=tpm0,bus=pnv-spi-bus.4
+
In case SeaBIOS is used as firmware, it should show the TPM menu item
after entering the menu with 'ESC'.
diff --git a/include/sysemu/tpm.h b/include/sysemu/tpm.h
index 1ee568b3b6..22b05110e2 100644
--- a/include/sysemu/tpm.h
+++ b/include/sysemu/tpm.h
@@ -49,6 +49,7 @@ struct TPMIfClass {
#define TYPE_TPM_CRB "tpm-crb"
#define TYPE_TPM_SPAPR "tpm-spapr"
#define TYPE_TPM_TIS_I2C "tpm-tis-i2c"
+#define TYPE_TPM_TIS_SPI "tpm-tis-spi"
#define TPM_IS_TIS_ISA(chr) \
object_dynamic_cast(OBJECT(chr), TYPE_TPM_TIS_ISA)
@@ -60,6 +61,8 @@ struct TPMIfClass {
object_dynamic_cast(OBJECT(chr), TYPE_TPM_SPAPR)
#define TPM_IS_TIS_I2C(chr) \
object_dynamic_cast(OBJECT(chr), TYPE_TPM_TIS_I2C)
+#define TPM_IS_TIS_SPI(chr) \
+ object_dynamic_cast(OBJECT(chr), TYPE_TPM_TIS_SPI)
/* returns NULL unless there is exactly one TPM device */
static inline TPMIf *tpm_find(void)
diff --git a/hw/tpm/tpm_tis_spi.c b/hw/tpm/tpm_tis_spi.c
new file mode 100644
index 0000000000..4aee1c6d6c
--- /dev/null
+++ b/hw/tpm/tpm_tis_spi.c
@@ -0,0 +1,360 @@
+/*
+ * QEMU PowerPC SPI TPM 2.0 model
+ *
+ * Copyright (c) 2024, IBM Corporation.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#define IBM_PONQ
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "hw/sysbus.h"
+#include "hw/acpi/tpm.h"
+#include "tpm_prop.h"
+#include "qemu/log.h"
+#include "trace.h"
+#include "tpm_tis.h"
+#include "hw/ssi/ssi.h"
+#include "migration/vmstate.h"
+
+typedef struct TPMStateSPI {
+ /*< private >*/
+ SSIPeripheral parent_object;
+
+ uint8_t byte_offset; /* byte offset in transfer */
+ uint8_t wait_state_cnt; /* wait state counter */
+ uint8_t xfer_size; /* data size of transfer */
+ uint32_t reg_addr; /* register address of transfer */
+
+ uint8_t spi_state; /* READ / WRITE / IDLE */
+#define SPI_STATE_IDLE 0
+#define SPI_STATE_WRITE 1
+#define SPI_STATE_READ 2
+
+ bool command;
+
+ /*< public >*/
+ TPMState tpm_state; /* not a QOM object */
+
+} TPMStateSPI;
+
+#define CMD_BYTE_WRITE (1 << 7)
+#define CMD_BYTE_XFER_SZ_MASK 0x1f
+#define TIS_SPI_HIGH_ADDR_BYTE 0xd4
+
+DECLARE_INSTANCE_CHECKER(TPMStateSPI, TPM_TIS_SPI, TYPE_TPM_TIS_SPI)
+
+static int tpm_tis_spi_pre_save(void *opaque)
+{
+ TPMStateSPI *spist = opaque;
+
+ return tpm_tis_pre_save(&spist->tpm_state);
+}
+
+static const VMStateDescription vmstate_tpm_tis_spi = {
+ .name = "tpm-tis-spi",
+ .version_id = 0,
+ .pre_save = tpm_tis_spi_pre_save,
+ .fields = (const VMStateField[]) {
+ VMSTATE_BUFFER(tpm_state.buffer, TPMStateSPI),
+ VMSTATE_UINT16(tpm_state.rw_offset, TPMStateSPI),
+ VMSTATE_UINT8(tpm_state.active_locty, TPMStateSPI),
+ VMSTATE_UINT8(tpm_state.aborting_locty, TPMStateSPI),
+ VMSTATE_UINT8(tpm_state.next_locty, TPMStateSPI),
+
+ VMSTATE_STRUCT_ARRAY(tpm_state.loc, TPMStateSPI,
+ TPM_TIS_NUM_LOCALITIES, 0,
+ vmstate_locty, TPMLocality),
+
+ /* spi specifics */
+ VMSTATE_UINT8(byte_offset, TPMStateSPI),
+ VMSTATE_UINT8(wait_state_cnt, TPMStateSPI),
+ VMSTATE_UINT8(xfer_size, TPMStateSPI),
+ VMSTATE_UINT32(reg_addr, TPMStateSPI),
+ VMSTATE_UINT8(spi_state, TPMStateSPI),
+ VMSTATE_BOOL(command, TPMStateSPI),
+
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static inline void tpm_tis_spi_clear_data(TPMStateSPI *spist)
+{
+ spist->spi_state = SPI_STATE_IDLE;
+ spist->byte_offset = 0;
+ spist->wait_state_cnt = 0;
+ spist->xfer_size = 0;
+ spist->reg_addr = 0;
+
+ return;
+}
+
+/* Callback from TPM to indicate that response is copied */
+static void tpm_tis_spi_request_completed(TPMIf *ti, int ret)
+{
+ TPMStateSPI *spist = TPM_TIS_SPI(ti);
+ TPMState *s = &spist->tpm_state;
+
+ /* Inform the common code. */
+ tpm_tis_request_completed(s, ret);
+}
+
+static enum TPMVersion tpm_tis_spi_get_tpm_version(TPMIf *ti)
+{
+ TPMStateSPI *spist = TPM_TIS_SPI(ti);
+ TPMState *s = &spist->tpm_state;
+
+ return tpm_tis_get_tpm_version(s);
+}
+
+/*
+ * TCG PC Client Platform TPM Profile Specification for TPM 2.0 ver 1.05 rev 14
+ *
+ * For system Software, the TPM has a 64-bit address of 0x0000_0000_FED4_xxxx.
+ * On SPI, the chipset passes the least significant 24 bits to the TPM.
+ * The upper bytes will be used by the chipset to select the TPM’s SPI CS#
+ * signal. Table 9 shows the locality based on the 16 least significant address
+ * bits and assume that either the LPC TPM sync or SPI TPM CS# is used.
+ *
+ */
+static void tpm_tis_spi_write(TPMStateSPI *spist, uint32_t addr, uint8_t val)
+{
+ TPMState *tpm_st = &spist->tpm_state;
+
+ trace_tpm_tis_spi_write(addr, val);
+ tpm_tis_write_data(tpm_st, addr, val, 1);
+}
+
+static uint8_t tpm_tis_spi_read(TPMStateSPI *spist, uint32_t addr)
+{
+ TPMState *tpm_st = &spist->tpm_state;
+ uint8_t data;
+
+ data = tpm_tis_read_data(tpm_st, addr, 1);
+ trace_tpm_tis_spi_read(addr, data);
+ return data;
+}
+
+static Property tpm_tis_spi_properties[] = {
+ DEFINE_PROP_TPMBE("tpmdev", TPMStateSPI, tpm_state.be_driver),
+ DEFINE_PROP_UINT32("irq", TPMStateSPI, tpm_state.irq_num, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void tpm_tis_spi_reset(DeviceState *dev)
+{
+ TPMStateSPI *spist = TPM_TIS_SPI(dev);
+ TPMState *s = &spist->tpm_state;
+
+ tpm_tis_spi_clear_data(spist);
+ return tpm_tis_reset(s);
+}
+
+static uint32_t tpm_transfer(SSIPeripheral *ss, uint32_t tx)
+{
+ TPMStateSPI *spist = TPM_TIS_SPI(ss);
+ uint32_t rx = 0;
+ uint8_t byte; /* reversed byte value */
+ uint8_t offset = 0; /* offset of byte in payload */
+ uint8_t index; /* index of data byte in transfer */
+ uint32_t tis_addr; /* tis address including locty */
+
+ /* new transfer or not */
+ if (spist->command) { /* new transfer start */
+ if (spist->spi_state != SPI_STATE_IDLE) {
+ qemu_log_mask(LOG_GUEST_ERROR, "unexpected new transfer\n");
+ }
+ spist->byte_offset = 0;
+ spist->wait_state_cnt = 0;
+ }
+ /*
+ * Explanation of wait_state:
+ * The original TPM model did not have wait state or "flow control" support
+ * built in. If you wanted to read a TPM register through SPI you sent
+ * the first byte with the read/write bit and size, then three address bytes
+ * and any additional bytes after that were don't care bytes for reads and
+ * the model would begin returning byte data to the SPI reader from the
+ * register address provided. In the real world this would mean that a
+ * TPM device had only the time between the 31st clock and the 32nd clock
+ * to fetch the register data that it had to provide to SPI MISO starting
+ * with the 32nd clock.
+ *
+ * In reality the TPM begins introducing a wait state at the 31st clock
+ * by holding MISO low. This is how it controls the "flow" of the
+ * operation. Once the data the TPM needs to return is ready it will
+ * select bit 31 + (8*N) to send back a 1 which indicates that it will
+ * now start returning data on MISO.
+ *
+ * The same wait states are applied to writes. In either the read or write
+ * case the wait state occurs between the command+address (4 bytes) and the
+ * data (1-n bytes) sections of the SPI frame. The code below introduces
+ * the support for a 32 bit wait state for P10. All reads and writes
+ * through the SPI interface MUST now be aware of the need to do flow
+ * control in order to use the TPM via SPI.
+ *
+ * In conjunction with these changes there were changes made to the SPIM
+ * engine that was introduced in P10 to support the 6x op code which is
+ * used to receive wait state 0s on the MISO line until it sees the b'1'
+ * come back before continuing to read real data from the SPI device(TPM).
+ */
+
+ trace_tpm_tis_spi_transfer_data("Payload byte_offset", spist->byte_offset);
+ /* process payload data */
+ while (offset < 4) {
+ spist->command = false;
+ byte = (tx >> (24 - 8 * offset)) & 0xFF;
+ trace_tpm_tis_spi_transfer_data("Extracted byte", byte);
+ trace_tpm_tis_spi_transfer_data("Payload offset", offset);
+ switch (spist->byte_offset) {
+ case 0: /* command byte */
+ if ((byte & CMD_BYTE_WRITE) == 0) { /* bit-7 */
+ spist->spi_state = SPI_STATE_WRITE;
+ trace_tpm_tis_spi_transfer_event("spi write");
+ } else {
+ spist->spi_state = SPI_STATE_READ;
+ trace_tpm_tis_spi_transfer_event("spi read");
+ }
+ spist->xfer_size = (byte & CMD_BYTE_XFER_SZ_MASK) + 1;
+ trace_tpm_tis_spi_transfer_data("xfer_size", spist->xfer_size);
+ break;
+ case 1: /* 1st address byte */
+ if (byte != TIS_SPI_HIGH_ADDR_BYTE) {
+ qemu_log_mask(LOG_GUEST_ERROR, "incorrect high address 0x%x\n",
+ byte);
+ }
+ spist->reg_addr = byte << 16;
+ trace_tpm_tis_spi_transfer_data("first addr byte", byte);
+ trace_tpm_tis_spi_transfer_addr("reg_addr", spist->reg_addr);
+ break;
+ case 2: /* 2nd address byte */
+ spist->reg_addr |= byte << 8;
+ trace_tpm_tis_spi_transfer_data("second addr byte", byte);
+ trace_tpm_tis_spi_transfer_addr("reg_addr", spist->reg_addr);
+ break;
+ case 3: /* 3rd address byte */
+ spist->reg_addr |= byte;
+ trace_tpm_tis_spi_transfer_data("third addr byte", byte);
+ trace_tpm_tis_spi_transfer_addr("reg_addr", spist->reg_addr);
+ break;
+ default: /* data bytes */
+ if (spist->wait_state_cnt < 4) {
+ spist->wait_state_cnt++;
+ if (spist->wait_state_cnt == 4) {
+ trace_tpm_tis_spi_transfer_data("wait complete, count",
+ spist->wait_state_cnt);
+ rx = rx | (0x01 << (24 - offset * 8));
+ return rx;
+ } else {
+ trace_tpm_tis_spi_transfer_data("in wait state, count",
+ spist->wait_state_cnt);
+ rx = 0;
+ }
+ } else {
+ index = spist->byte_offset - 4;
+ trace_tpm_tis_spi_transfer_data("index", index);
+ trace_tpm_tis_spi_transfer_data("data byte", byte);
+ trace_tpm_tis_spi_transfer_addr("reg_addr", spist->reg_addr);
+ if (index >= spist->xfer_size) {
+ /*
+ * SPI SSI framework limits both rx and tx
+ * to fixed 4-byte with each xfer
+ */
+ trace_tpm_tis_spi_transfer_event("index exceeds xfer_size");
+ return rx;
+ }
+ tis_addr = spist->reg_addr + (index % 4);
+ if (spist->spi_state == SPI_STATE_WRITE) {
+ tpm_tis_spi_write(spist, tis_addr, byte);
+ } else {
+ byte = tpm_tis_spi_read(spist, tis_addr);
+ rx = rx | (byte << (24 - offset * 8));
+ trace_tpm_tis_spi_transfer_data("byte added to response",
+ byte);
+ trace_tpm_tis_spi_transfer_data("offset", offset);
+ }
+ }
+ break;
+ }
+ if ((spist->wait_state_cnt == 0) || (spist->wait_state_cnt == 4)) {
+ offset++;
+ spist->byte_offset++;
+ } else {
+ break;
+ }
+ }
+ return rx;
+}
+
+static int tpm_cs(SSIPeripheral *ss, bool select)
+{
+ TPMStateSPI *spist = TPM_TIS_SPI(ss);
+
+ if (select) {
+ spist->command = false;
+ spist->spi_state = SPI_STATE_IDLE;
+ } else {
+ spist->command = true;
+ }
+ return 0;
+}
+
+static void tpm_realize(SSIPeripheral *dev, Error **errp)
+{
+ TPMStateSPI *spist = TPM_TIS_SPI(dev);
+ TPMState *s = &spist->tpm_state;
+
+ if (!tpm_find()) {
+ error_setg(errp, "at most one TPM device is permitted");
+ return;
+ }
+
+ s->be_driver = qemu_find_tpm_be("tpm0");
+
+ if (!s->be_driver) {
+ error_setg(errp, "unable to find tpm backend device");
+ return;
+ }
+}
+
+static void tpm_tis_spi_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ TPMIfClass *tc = TPM_IF_CLASS(klass);
+ SSIPeripheralClass *k = SSI_PERIPHERAL_CLASS(klass);
+
+ k->transfer = tpm_transfer;
+ k->realize = tpm_realize;
+ k->set_cs = tpm_cs;
+ k->cs_polarity = SSI_CS_LOW;
+
+ device_class_set_legacy_reset(dc, tpm_tis_spi_reset);
+ device_class_set_props(dc, tpm_tis_spi_properties);
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+
+ dc->desc = "SPI TPM";
+ dc->vmsd = &vmstate_tpm_tis_spi;
+
+ tc->model = TPM_MODEL_TPM_TIS;
+ tc->request_completed = tpm_tis_spi_request_completed;
+ tc->get_version = tpm_tis_spi_get_tpm_version;
+}
+
+static const TypeInfo tpm_tis_spi_info = {
+ .name = TYPE_TPM_TIS_SPI,
+ .parent = TYPE_SSI_PERIPHERAL,
+ .instance_size = sizeof(TPMStateSPI),
+ .class_init = tpm_tis_spi_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_TPM_IF },
+ { }
+ }
+};
+
+static void tpm_tis_spi_register_types(void)
+{
+ type_register_static(&tpm_tis_spi_info);
+}
+
+type_init(tpm_tis_spi_register_types)
diff --git a/hw/tpm/Kconfig b/hw/tpm/Kconfig
index a46663288c..5951c225cc 100644
--- a/hw/tpm/Kconfig
+++ b/hw/tpm/Kconfig
@@ -5,6 +5,12 @@ config TPM_TIS_I2C
select I2C
select TPM_TIS
+config TPM_TIS_SPI
+ bool
+ depends on TPM
+ select TPM_BACKEND
+ 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 6968e60b3f..e03cfb11b9 100644
--- a/hw/tpm/meson.build
+++ b/hw/tpm/meson.build
@@ -2,6 +2,7 @@ system_ss.add(when: 'CONFIG_TPM_TIS', if_true: files('tpm_tis_common.c'))
system_ss.add(when: 'CONFIG_TPM_TIS_ISA', if_true: files('tpm_tis_isa.c'))
system_ss.add(when: 'CONFIG_TPM_TIS_SYSBUS', if_true: files('tpm_tis_sysbus.c'))
system_ss.add(when: 'CONFIG_TPM_TIS_I2C', if_true: files('tpm_tis_i2c.c'))
+system_ss.add(when: 'CONFIG_TPM_TIS_SPI', if_true: files('tpm_tis_spi.c'))
system_ss.add(when: 'CONFIG_TPM_CRB', if_true: files('tpm_crb.c'))
system_ss.add(when: 'CONFIG_TPM_TIS', if_true: files('tpm_ppi.c'))
system_ss.add(when: 'CONFIG_TPM_CRB', if_true: files('tpm_ppi.c'))
diff --git a/hw/tpm/trace-events b/hw/tpm/trace-events
index fa882dfefe..0324ceb6d0 100644
--- a/hw/tpm/trace-events
+++ b/hw/tpm/trace-events
@@ -42,3 +42,10 @@ tpm_tis_i2c_recv(uint8_t data) "TPM I2C read: 0x%X"
tpm_tis_i2c_send(uint8_t data) "TPM I2C write: 0x%X"
tpm_tis_i2c_event(const char *event) "TPM I2C event: %s"
tpm_tis_i2c_send_reg(const char *name, int reg) "TPM I2C write register: %s(0x%X)"
+
+# tpm_tis_spi.c
+tpm_tis_spi_write(uint32_t addr, uint8_t val) "TPM SPI write - addr:0x%08X 0x%02x"
+tpm_tis_spi_read(uint32_t addr, uint8_t val) "TPM SPI read - addr:0x%08X 0x%02x"
+tpm_tis_spi_transfer_event(const char *event) "TPM SPI XFER event: %s"
+tpm_tis_spi_transfer_data(const char *name, uint8_t val) "TPM SPI XFER: %s = 0x%02x"
+tpm_tis_spi_transfer_addr(const char *name, uint32_t addr) "TPM SPI XFER: %s = 0x%08x"
--
2.39.5
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v4 2/3] tpm/tpm_tis_spi: activation for the PowerNV machines
2024-11-01 20:27 [PATCH v4 0/3] TPM TIS SPI Support dan tan
2024-11-01 20:27 ` [PATCH v4 1/3] tpm/tpm_tis_spi: Support TPM for SPI (Serial Peripheral Interface) dan tan
@ 2024-11-01 20:27 ` dan tan
2024-11-02 12:55 ` Stefan Berger
2024-11-01 20:27 ` [PATCH v4 3/3] tests/qtest/tpm: add unit test to tis-spi dan tan
2 siblings, 1 reply; 9+ messages in thread
From: dan tan @ 2024-11-01 20:27 UTC (permalink / raw)
To: qemu-devel; +Cc: qemu-ppc, stefanb, pbonzini, farosas, lvivier, clg, dantan
The addition to ppc/Kconfig is for building this into the
qemu-system-ppc64 binary. The enablement requires the
following command line argument:
-device tpm-tis-spi,tpmdev=tpm0,bus=pnv-spi-bus.4
Signed-off-by: dan tan <dantan@linux.vnet.ibm.com>
---
hw/ppc/Kconfig | 1 +
1 file changed, 1 insertion(+)
diff --git a/hw/ppc/Kconfig b/hw/ppc/Kconfig
index b44d91bebb..56dcf5902e 100644
--- a/hw/ppc/Kconfig
+++ b/hw/ppc/Kconfig
@@ -39,6 +39,7 @@ config POWERNV
select PCI_POWERNV
select PCA9552
select PCA9554
+ select TPM_TIS_SPI
select SERIAL_ISA
select SSI
select SSI_M25P80
--
2.39.5
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v4 3/3] tests/qtest/tpm: add unit test to tis-spi
2024-11-01 20:27 [PATCH v4 0/3] TPM TIS SPI Support dan tan
2024-11-01 20:27 ` [PATCH v4 1/3] tpm/tpm_tis_spi: Support TPM for SPI (Serial Peripheral Interface) dan tan
2024-11-01 20:27 ` [PATCH v4 2/3] tpm/tpm_tis_spi: activation for the PowerNV machines dan tan
@ 2024-11-01 20:27 ` dan tan
2024-11-02 14:01 ` Stefan Berger
2 siblings, 1 reply; 9+ messages in thread
From: dan tan @ 2024-11-01 20:27 UTC (permalink / raw)
To: qemu-devel; +Cc: qemu-ppc, stefanb, pbonzini, farosas, lvivier, clg, dantan
Add qtest cases to exercise main TPM locality functionality
The TPM device emulation is provided by swtpm, which is TCG
TPM 2.0, and TCG TPM TIS compliant. See
https://trustedcomputinggroup.org/wp-content/uploads/TCG_PC_Client_Platform_TPM_Profile_PTP_2.0_r1.03_v22.pdf
https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClientTPMInterfaceSpecification_TIS__1-3_27_03212013.pdf
The SPI registers are specific to the PowerNV platform
architecture
tests/qtest/tpm: add unit test to tis-spi (rev 4)
- removed the function prototypes declaration
- fixed code format to comply with convention
- changed function names and variable names to be the same
as the tpm-tis-i2c test.
- change hard coded numbers to #define's with meaningful
names that are identifiable with spec documentation
Signed-off-by: dan tan <dantan@linux.vnet.ibm.com>
---
tests/qtest/tpm-tis-spi-pnv-test.c | 710 +++++++++++++++++++++++++++++
tests/qtest/meson.build | 2 +
2 files changed, 712 insertions(+)
create mode 100644 tests/qtest/tpm-tis-spi-pnv-test.c
diff --git a/tests/qtest/tpm-tis-spi-pnv-test.c b/tests/qtest/tpm-tis-spi-pnv-test.c
new file mode 100644
index 0000000000..2d6e1186cf
--- /dev/null
+++ b/tests/qtest/tpm-tis-spi-pnv-test.c
@@ -0,0 +1,710 @@
+/*
+ * QTest testcase for a Nuvoton NPCT75x TPM SPI device
+ * running on the PowerNV machine.
+ *
+ * Copyright (c) 2024, IBM Corporation.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#include "qemu/osdep.h"
+#include <glib/gstdio.h>
+#include "libqtest-single.h"
+#include "hw/acpi/tpm.h"
+#include "hw/pci/pci_ids.h"
+#include "qtest_aspeed.h"
+#include "tpm-emu.h"
+#include "hw/ssi/pnv_spi_regs.h"
+#include "pnv-xscom.h"
+
+#define SPI_TPM_BASE 0xc0080
+#define SPI_SHIFT_COUNTER_N1 0x30000000
+#define SPI_SHIFT_COUNTER_N2 0x40000000
+#define SPI_RWX_OPCODE_SHIFT 56
+#define SPI_RWX_ADDR_SHIFT 32
+#define SPI_CMD_DATA_SHIFT 56
+
+#define CFG_COUNT_COMPARE_1 0x0000000200000000
+#define MM_REG_RDR_MATCH 0x00000000ff01ff00
+#define SEQ_OP_REG_BASIC 0x1134416200100000
+
+#define TPM_TIS_8BITS_MASK 0xff
+#define SPI_TPM_TIS_ADDR 0xd40000
+#define SPI_EXTEND 0x03
+#define TPM_WRITE_OP 0x0
+#define TPM_READ_OP 0x80
+
+#define SHORT_MAX_RETRIES 5
+#define LONG_MAX_RETRIES 10
+
+static const uint8_t TPM_CMD[12] =
+ "\x80\x01\x00\x00\x00\x0c\x00\x00\x01\x44\x00\x00";
+
+#define DPRINTF(fmt, ...) do { \
+ if (DEBUG_TIS_TEST) { \
+ printf(fmt, ## __VA_ARGS__); \
+ } \
+} while (0)
+
+#define DEBUG_TIS_TEST 0
+
+#define DPRINTF_ACCESS \
+ DPRINTF("%s: %d: locty=%d l=%d access=0x%02x pending_request_flag=0x%x\n", \
+ __func__, __LINE__, locty, l, access, pending_request_flag)
+
+#define DPRINTF_STS \
+ DPRINTF("%s: %d: sts = 0x%08x\n", __func__, __LINE__, sts)
+
+static uint64_t pnv_spi_tpm_read(const PnvChip *chip, uint32_t reg)
+{
+ uint32_t pcba = SPI_TPM_BASE + reg;
+
+ return qtest_readq(global_qtest, pnv_xscom_addr(chip, pcba));
+}
+
+static void pnv_spi_tpm_write(const PnvChip *chip,
+ uint32_t reg,
+ uint64_t val)
+{
+ uint32_t pcba = SPI_TPM_BASE + reg;
+
+ qtest_writeq(global_qtest, pnv_xscom_addr(chip, pcba), val);
+}
+
+static void spi_op_complete(const PnvChip *chip)
+{
+ uint64_t cfg_reg;
+
+ cfg_reg = pnv_spi_tpm_read(chip, SPI_CLK_CFG_REG);
+ g_assert_cmpuint(CFG_COUNT_COMPARE_1, ==, cfg_reg);
+ pnv_spi_tpm_write(chip, SPI_CLK_CFG_REG, 0);
+}
+
+static void spi_write_reg(const PnvChip *chip, uint64_t val)
+{
+ int i;
+ uint64_t spi_sts;
+
+ for (i = 0; i < LONG_MAX_RETRIES; i++) {
+ spi_sts = pnv_spi_tpm_read(chip, SPI_STS_REG);
+ if (GETFIELD(SPI_STS_TDR_FULL, spi_sts) == 1) {
+ sleep(0.5);
+ } else {
+ break;
+ }
+ }
+ /* cannot write if SPI_STS_TDR_FULL bit is still set */
+ g_assert_cmpuint(0, ==, GETFIELD(SPI_STS_TDR_FULL, spi_sts));
+ pnv_spi_tpm_write(chip, SPI_XMIT_DATA_REG, val);
+
+ for (i = 0; i < SHORT_MAX_RETRIES; i++) {
+ spi_sts = pnv_spi_tpm_read(chip, SPI_STS_REG);
+ if (GETFIELD(SPI_STS_SHIFTER_FSM, spi_sts) & FSM_DONE) {
+ break;
+ } else {
+ sleep(0.1);
+ }
+ }
+ /* it should be done given the amount of time */
+ g_assert_cmpuint(0, ==, GETFIELD(SPI_STS_SHIFTER_FSM, spi_sts) & FSM_DONE);
+ spi_op_complete(chip);
+}
+
+static uint64_t spi_read_reg(const PnvChip *chip)
+{
+ int i;
+ uint64_t spi_sts, val = 0;
+
+ for (i = 0; i < LONG_MAX_RETRIES; i++) {
+ spi_sts = pnv_spi_tpm_read(chip, SPI_STS_REG);
+ if (GETFIELD(SPI_STS_RDR_FULL, spi_sts) == 1) {
+ val = pnv_spi_tpm_read(chip, SPI_RCV_DATA_REG);
+ break;
+ }
+ sleep(0.5);
+ }
+ for (i = 0; i < SHORT_MAX_RETRIES; i++) {
+ spi_sts = pnv_spi_tpm_read(chip, SPI_STS_REG);
+ if (GETFIELD(SPI_STS_RDR_FULL, spi_sts) == 1) {
+ sleep(0.1);
+ } else {
+ break;
+ }
+ }
+ /* SPI_STS_RDR_FULL bit should be reset after read */
+ g_assert_cmpuint(0, ==, GETFIELD(SPI_STS_RDR_FULL, spi_sts));
+ spi_op_complete(chip);
+ return val;
+}
+
+static void spi_access_start(const PnvChip *chip,
+ bool n2,
+ uint8_t bytes,
+ uint8_t tpm_op,
+ uint32_t tpm_reg)
+{
+ uint64_t cfg_reg;
+ uint64_t reg_op;
+ uint64_t seq_op = SEQ_OP_REG_BASIC;
+
+ cfg_reg = pnv_spi_tpm_read(chip, SPI_CLK_CFG_REG);
+ if (cfg_reg != CFG_COUNT_COMPARE_1) {
+ pnv_spi_tpm_write(chip, SPI_CLK_CFG_REG, CFG_COUNT_COMPARE_1);
+ }
+ /* bytes - sequencer operation register bits 24:31 */
+ if (n2) {
+ seq_op |= SPI_SHIFT_COUNTER_N2 | (bytes << 0x18);
+ } else {
+ seq_op |= SPI_SHIFT_COUNTER_N1 | (bytes << 0x18);
+ }
+ pnv_spi_tpm_write(chip, SPI_SEQ_OP_REG, seq_op);
+ pnv_spi_tpm_write(chip, SPI_MM_REG, MM_REG_RDR_MATCH);
+ pnv_spi_tpm_write(chip, SPI_CTR_CFG_REG, (uint64_t)0);
+ reg_op = ((uint64_t)tpm_op << SPI_RWX_OPCODE_SHIFT) |
+ ((uint64_t)tpm_reg << SPI_RWX_ADDR_SHIFT);
+ pnv_spi_tpm_write(chip, SPI_XMIT_DATA_REG, reg_op);
+}
+
+static inline void tpm_reg_writeb(const PnvChip *c,
+ uint8_t locty,
+ uint8_t reg,
+ uint8_t val)
+{
+ uint32_t tpm_reg_locty = SPI_TPM_TIS_ADDR |
+ (locty << TPM_TIS_LOCALITY_SHIFT);
+
+ spi_access_start(c, false, 1, TPM_WRITE_OP, tpm_reg_locty | reg);
+ spi_write_reg(c, (uint64_t) val << SPI_CMD_DATA_SHIFT);
+}
+
+static inline uint8_t tpm_reg_readb(const PnvChip *c,
+ uint8_t locty,
+ uint8_t reg)
+{
+ uint32_t tpm_reg_locty = SPI_TPM_TIS_ADDR |
+ (locty << TPM_TIS_LOCALITY_SHIFT);
+
+ spi_access_start(c, true, 1, TPM_READ_OP, tpm_reg_locty | reg);
+ return spi_read_reg(c);
+}
+
+static inline void tpm_reg_writel(const PnvChip *c,
+ uint8_t locty,
+ uint16_t reg,
+ uint32_t val)
+{
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ tpm_reg_writeb(c, locty, reg + i, ((val >> (8 * i)) & 0xff));
+ }
+}
+
+static inline uint32_t tpm_reg_readl(const PnvChip *c,
+ uint8_t locty,
+ uint16_t reg)
+{
+ uint32_t val = 0;
+ int i;
+
+ /* special case for SPI xmit data reg set RWX bits */
+ if (reg == TPM_TIS_REG_DID_VID) {
+ spi_access_start(c, true, 4, TPM_READ_OP | SPI_EXTEND,
+ locty | TPM_TIS_REG_DID_VID);
+ val = bswap32(spi_read_reg(c));
+ } else {
+ for (i = 0; i < 4; i++) {
+ val |= tpm_reg_readb(c, locty, reg + i) << (8 * i);
+ }
+ }
+ return val;
+}
+
+static void tpm_set_verify_loc(const PnvChip *chip, uint8_t loc)
+{
+ uint8_t access;
+ uint32_t tpm_sts;
+
+ g_test_message("TPM locality %d tests:", loc);
+ access = tpm_reg_readb(chip, loc, TPM_TIS_REG_ACCESS);
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+
+ tpm_reg_writeb(chip, loc, TPM_TIS_REG_ACCESS, TPM_TIS_ACCESS_SEIZE);
+ tpm_reg_writeb(chip, loc, TPM_TIS_REG_ACCESS, TPM_TIS_ACCESS_REQUEST_USE);
+
+ access = tpm_reg_readb(chip, loc, TPM_TIS_REG_ACCESS);
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_ACTIVE_LOCALITY |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+ g_test_message("\tACCESS REG = 0x%x checked", access);
+
+ /* test tpm status register */
+ tpm_sts = tpm_reg_readl(chip, loc, TPM_TIS_REG_STS);
+ g_assert_cmpuint((tpm_sts & TPM_TIS_8BITS_MASK), ==, 0);
+ g_test_message("\tTPM STATUS: 0x%x, verified", tpm_sts);
+
+ /* release access */
+ tpm_reg_writeb(chip, loc, TPM_TIS_REG_ACCESS,
+ TPM_TIS_ACCESS_ACTIVE_LOCALITY);
+ access = tpm_reg_readb(chip, loc, TPM_TIS_REG_ACCESS);
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+ g_test_message("\tRELEASED ACCESS: 0x%x, checked", access);
+}
+
+static void test_spi_tpm_locality(const void *data)
+{
+ const PnvChip *chip = data;
+ uint8_t locality;
+
+ /* Locality 4 has special security restrictions, testing 0-3 */
+ for (locality = 0; locality < TPM_TIS_NUM_LOCALITIES - 1; locality++) {
+ tpm_set_verify_loc(chip, locality);
+ }
+}
+
+static void test_spi_tpm_basic(const void *data)
+{
+ const PnvChip *chip = data;
+ uint32_t didvid, tpm_sts, en_int;
+ uint8_t access;
+
+ g_test_message("TPM TIS SPI interface basic tests:");
+ /* vendor ID and device ID ... check against the known value*/
+ didvid = tpm_reg_readl(chip, 0, TPM_TIS_REG_DID_VID);
+ g_assert_cmpint(didvid, ==, (1 << 16) | PCI_VENDOR_ID_IBM);
+ g_test_message("\tDID_VID = 0x%x, verified", didvid);
+
+ /* access register, default see TCG TIS Spec (v1.3) table-14 */
+ access = tpm_reg_readb(chip, 0, TPM_TIS_REG_ACCESS);
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+ g_test_message("\tACCESS REG = 0x%x, checked", access);
+
+ /* interrupt enable register, default see TCG TIS Spec (v1.3) table-19 */
+ en_int = tpm_reg_readl(chip, 0, TPM_TIS_REG_INT_ENABLE);
+ g_assert_cmpuint(en_int, ==, TPM_TIS_INT_POLARITY_LOW_LEVEL);
+ g_test_message("\tINT ENABLE REG: 0x%x, verified", en_int);
+
+ /* status register, default see TCG TIS Spec (v1.3) table-15 */
+ tpm_sts = tpm_reg_readl(chip, 0, TPM_TIS_REG_STS);
+ /* for no active locality */
+ g_assert_cmpuint(tpm_sts, ==, 0xffffffff);
+ g_test_message("\tTPM STATUS: 0x%x, verified", tpm_sts);
+}
+
+/*
+ * Test case for seizing access by a higher number locality
+ */
+static void test_spi_tpm_access_seize_test(const void *data)
+{
+ const PnvChip *chip = data;
+ int locty, l;
+ uint8_t access;
+ uint8_t pending_request_flag;
+
+ g_test_message("TPM TIS SPI access seize tests:");
+ /* do not test locality 4 (hw only) */
+ for (locty = 0; locty < TPM_TIS_NUM_LOCALITIES - 1; locty++) {
+ pending_request_flag = 0;
+
+ access = tpm_reg_readb(chip, locty, TPM_TIS_REG_ACCESS);
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+
+ /* request use of locality */
+ tpm_reg_writeb(chip, locty, TPM_TIS_REG_ACCESS,
+ TPM_TIS_ACCESS_REQUEST_USE);
+
+ access = tpm_reg_readb(chip, locty, TPM_TIS_REG_ACCESS);
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_ACTIVE_LOCALITY |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+
+ /* lower localities cannot seize access */
+ for (l = 0; l < locty; l++) {
+ /* lower locality is not active */
+ access = tpm_reg_readb(chip, l, TPM_TIS_REG_ACCESS);
+ DPRINTF_ACCESS;
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ pending_request_flag |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+
+ /* try to request use from 'l' */
+ tpm_reg_writeb(chip, l, TPM_TIS_REG_ACCESS,
+ TPM_TIS_ACCESS_REQUEST_USE);
+
+ /*
+ * requesting use from 'l' was not possible;
+ * we must see REQUEST_USE and possibly PENDING_REQUEST
+ */
+ access = tpm_reg_readb(chip, l, TPM_TIS_REG_ACCESS);
+ DPRINTF_ACCESS;
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_REQUEST_USE |
+ pending_request_flag |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+
+ /*
+ * locality 'locty' must be unchanged;
+ * we must see PENDING_REQUEST
+ */
+ access = tpm_reg_readb(chip, locty, TPM_TIS_REG_ACCESS);
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_ACTIVE_LOCALITY |
+ TPM_TIS_ACCESS_PENDING_REQUEST |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+
+ /* try to seize from 'l' */
+ tpm_reg_writeb(chip, l, TPM_TIS_REG_ACCESS, TPM_TIS_ACCESS_SEIZE);
+ /* seize from 'l' was not possible */
+ access = tpm_reg_readb(chip, l, TPM_TIS_REG_ACCESS);
+ DPRINTF_ACCESS;
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_REQUEST_USE |
+ pending_request_flag |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+
+ /* locality 'locty' must be unchanged */
+ access = tpm_reg_readb(chip, locty, TPM_TIS_REG_ACCESS);
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_ACTIVE_LOCALITY |
+ TPM_TIS_ACCESS_PENDING_REQUEST |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+
+ /*
+ * on the next loop we will have a PENDING_REQUEST flag
+ * set for locality 'l'
+ */
+ pending_request_flag = TPM_TIS_ACCESS_PENDING_REQUEST;
+ }
+
+ /*
+ * higher localities can 'seize' access but not 'request use';
+ * note: this will activate first l+1, then l+2 etc.
+ */
+ for (l = locty + 1; l < TPM_TIS_NUM_LOCALITIES - 1; l++) {
+ /* try to 'request use' from 'l' */
+ tpm_reg_writeb(chip, l, TPM_TIS_REG_ACCESS,
+ TPM_TIS_ACCESS_REQUEST_USE);
+
+ /*
+ * requesting use from 'l' was not possible; we should see
+ * REQUEST_USE and may see PENDING_REQUEST
+ */
+ access = tpm_reg_readb(chip, l, TPM_TIS_REG_ACCESS);
+ DPRINTF_ACCESS;
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_REQUEST_USE |
+ pending_request_flag |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+
+ /*
+ * locality 'l-1' must be unchanged; we should always
+ * see PENDING_REQUEST from 'l' requesting access
+ */
+ access = tpm_reg_readb(chip, l - 1, TPM_TIS_REG_ACCESS);
+ DPRINTF_ACCESS;
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_ACTIVE_LOCALITY |
+ TPM_TIS_ACCESS_PENDING_REQUEST |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+
+ /* try to seize from 'l' */
+ tpm_reg_writeb(chip, l, TPM_TIS_REG_ACCESS, TPM_TIS_ACCESS_SEIZE);
+
+ /* seize from 'l' was possible */
+ access = tpm_reg_readb(chip, l, TPM_TIS_REG_ACCESS);
+ DPRINTF_ACCESS;
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_ACTIVE_LOCALITY |
+ pending_request_flag |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+
+ /* l - 1 should show that it has BEEN_SEIZED */
+ access = tpm_reg_readb(chip, l - 1, TPM_TIS_REG_ACCESS);
+ DPRINTF_ACCESS;
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_BEEN_SEIZED |
+ pending_request_flag |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+
+ /* clear the BEEN_SEIZED flag and make sure it's gone */
+ tpm_reg_writeb(chip, l - 1, TPM_TIS_REG_ACCESS,
+ TPM_TIS_ACCESS_BEEN_SEIZED);
+
+ access = tpm_reg_readb(chip, l - 1, TPM_TIS_REG_ACCESS);
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ pending_request_flag |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+ }
+
+ /*
+ * PENDING_REQUEST will not be set if locty = 0 since all localities
+ * were active; in case of locty = 1, locality 0 will be active
+ * but no PENDING_REQUEST anywhere
+ */
+ if (locty <= 1) {
+ pending_request_flag = 0;
+ }
+
+ /* release access from l - 1; this activates locty - 1 */
+ l--;
+
+ access = tpm_reg_readb(chip, l, TPM_TIS_REG_ACCESS);
+ DPRINTF_ACCESS;
+
+ DPRINTF("%s: %d: relinquishing control on l = %d\n",
+ __func__, __LINE__, l);
+ tpm_reg_writeb(chip, l, TPM_TIS_REG_ACCESS,
+ TPM_TIS_ACCESS_ACTIVE_LOCALITY);
+
+ access = tpm_reg_readb(chip, l, TPM_TIS_REG_ACCESS);
+ DPRINTF_ACCESS;
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ pending_request_flag |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+
+ for (l = locty - 1; l >= 0; l--) {
+ access = tpm_reg_readb(chip, l, TPM_TIS_REG_ACCESS);
+ DPRINTF_ACCESS;
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_ACTIVE_LOCALITY |
+ pending_request_flag |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+
+ /* release this locality */
+ tpm_reg_writeb(chip, l, TPM_TIS_REG_ACCESS,
+ TPM_TIS_ACCESS_ACTIVE_LOCALITY);
+
+ if (l == 1) {
+ pending_request_flag = 0;
+ }
+ }
+
+ /* no locality may be active now */
+ for (l = 0; l < TPM_TIS_NUM_LOCALITIES - 1; l++) {
+ access = tpm_reg_readb(chip, l, TPM_TIS_REG_ACCESS);
+ DPRINTF_ACCESS;
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+ }
+ g_test_message("\tTPM locality %d seize tests: passed", locty);
+ }
+}
+
+/*
+ * Test case for getting access when higher number locality relinquishes access
+ */
+static void test_spi_tpm_access_release_test(const void *data)
+{
+ const PnvChip *chip = data;
+ int locty, l;
+ uint8_t access;
+ uint8_t pending_request_flag;
+
+ g_test_message("TPM TIS SPI access release tests:");
+ /* do not test locality 4 (hw only) */
+ for (locty = TPM_TIS_NUM_LOCALITIES - 2; locty >= 0; locty--) {
+ pending_request_flag = 0;
+
+ access = tpm_reg_readb(chip, locty, TPM_TIS_REG_ACCESS);
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+
+ /* request use of locality */
+ tpm_reg_writeb(chip, locty, TPM_TIS_REG_ACCESS,
+ TPM_TIS_ACCESS_REQUEST_USE);
+ access = tpm_reg_readb(chip, locty, TPM_TIS_REG_ACCESS);
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_ACTIVE_LOCALITY |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+
+ /* request use of all other localities */
+ for (l = 0; l < TPM_TIS_NUM_LOCALITIES - 1; l++) {
+ if (l == locty) {
+ continue;
+ }
+ /*
+ * request use of locality 'l' -- we MUST see REQUEST USE and
+ * may see PENDING_REQUEST
+ */
+ tpm_reg_writeb(chip, l, TPM_TIS_REG_ACCESS,
+ TPM_TIS_ACCESS_REQUEST_USE);
+ access = tpm_reg_readb(chip, l, TPM_TIS_REG_ACCESS);
+ DPRINTF_ACCESS;
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_REQUEST_USE |
+ pending_request_flag |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+ pending_request_flag = TPM_TIS_ACCESS_PENDING_REQUEST;
+ }
+ /* release locality 'locty' */
+ tpm_reg_writeb(chip, locty, TPM_TIS_REG_ACCESS,
+ TPM_TIS_ACCESS_ACTIVE_LOCALITY);
+ /*
+ * highest locality should now be active; release it and make sure the
+ * next highest locality is active afterwards
+ */
+ for (l = TPM_TIS_NUM_LOCALITIES - 2; l >= 0; l--) {
+ if (l == locty) {
+ continue;
+ }
+ /* 'l' should be active now */
+ access = tpm_reg_readb(chip, l, TPM_TIS_REG_ACCESS);
+ DPRINTF_ACCESS;
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_ACTIVE_LOCALITY |
+ pending_request_flag |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+ /* 'l' relinquishes access */
+ tpm_reg_writeb(chip, l, TPM_TIS_REG_ACCESS,
+ TPM_TIS_ACCESS_ACTIVE_LOCALITY);
+ access = tpm_reg_readb(chip, l, TPM_TIS_REG_ACCESS);
+ DPRINTF_ACCESS;
+ if (l == 1 || (locty <= 1 && l == 2)) {
+ pending_request_flag = 0;
+ }
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ pending_request_flag |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+ }
+ g_test_message("\tTPM locality %d seize tests: passed", locty);
+ }
+}
+
+/*
+ * Test case for transmitting packets
+ */
+static void test_spi_tpm_transmit_test(const void *data)
+{
+ const PnvChip *chip = data;
+ uint16_t bcount;
+ uint8_t access;
+ uint32_t sts;
+ int i;
+
+ g_test_message("TPM TIS SPI transmit tests:");
+ /* request use of locality 0 */
+ tpm_reg_writeb(chip, 0, TPM_TIS_REG_ACCESS, TPM_TIS_ACCESS_REQUEST_USE);
+ access = tpm_reg_readb(chip, 0, TPM_TIS_REG_ACCESS);
+ g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
+ TPM_TIS_ACCESS_ACTIVE_LOCALITY |
+ TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
+
+ sts = tpm_reg_readl(chip, 0, TPM_TIS_REG_STS);
+ DPRINTF_STS;
+
+ g_assert_cmpint(sts & 0xff, ==, 0);
+
+ bcount = (sts >> 8) & 0xffff;
+ g_test_message("\t\tbcount: %x, sts: %x", bcount, sts);
+ g_assert_cmpint(bcount, >=, 128);
+
+ tpm_reg_writel(chip, 0, TPM_TIS_REG_STS, TPM_TIS_STS_COMMAND_READY);
+ sts = tpm_reg_readl(chip, 0, TPM_TIS_REG_STS);
+ DPRINTF_STS;
+ g_assert_cmpint(sts & 0xff, ==, TPM_TIS_STS_COMMAND_READY);
+
+ /* transmit command */
+ for (i = 0; i < sizeof(TPM_CMD); i++) {
+ tpm_reg_writeb(chip, 0, TPM_TIS_REG_DATA_FIFO, TPM_CMD[i]);
+ sts = tpm_reg_readl(chip, 0, TPM_TIS_REG_STS);
+ DPRINTF_STS;
+ if (i < sizeof(TPM_CMD) - 1) {
+ g_assert_cmpint(sts & 0xff, ==, TPM_TIS_STS_EXPECT |
+ TPM_TIS_STS_VALID);
+ } else {
+ g_assert_cmpint(sts & 0xff, ==, TPM_TIS_STS_VALID);
+ }
+ }
+ g_test_message("\ttransmit tests, check TPM_TIS_STS_EXPECT");
+
+ /* start processing */
+ tpm_reg_writel(chip, 0, TPM_TIS_REG_STS, TPM_TIS_STS_TPM_GO);
+
+ uint64_t end_time = g_get_monotonic_time() + 50 * G_TIME_SPAN_SECOND;
+ do {
+ sts = tpm_reg_readl(chip, 0, TPM_TIS_REG_STS);
+ if ((sts & TPM_TIS_STS_DATA_AVAILABLE) != 0) {
+ break;
+ }
+ } while (g_get_monotonic_time() < end_time);
+
+ sts = tpm_reg_readl(chip, 0, TPM_TIS_REG_STS);
+ DPRINTF_STS;
+ g_assert_cmpint(sts & 0xff, == , TPM_TIS_STS_VALID |
+ TPM_TIS_STS_DATA_AVAILABLE);
+ /* TCG TIS Spec (v1.3) table-15 */
+ g_test_message("\ttransmit tests, check tpmGo (w) & dataAvail (r)");
+ bcount = (sts >> 8) & 0xffff;
+
+ /* read response */
+ uint8_t tpm_msg[sizeof(struct tpm_hdr)];
+ g_assert_cmpint(sizeof(tpm_msg), ==, bcount);
+
+ for (i = 0; i < sizeof(tpm_msg); i++) {
+ tpm_msg[i] = tpm_reg_readb(chip, 0, TPM_TIS_REG_DATA_FIFO);
+ sts = tpm_reg_readl(chip, 0, TPM_TIS_REG_STS);
+ DPRINTF_STS;
+ if (sts & TPM_TIS_STS_DATA_AVAILABLE) {
+ g_assert_cmpint((sts >> 8) & 0xffff, ==, --bcount);
+ }
+ }
+ g_test_message("\treceive tests, passed");
+ /* relinquish use of locality 0 */
+ tpm_reg_writeb(chip, 0, TPM_TIS_REG_ACCESS, TPM_TIS_ACCESS_ACTIVE_LOCALITY);
+ access = tpm_reg_readb(chip, 0, TPM_TIS_REG_ACCESS);
+}
+
+int main(int argc, char **argv)
+{
+ int ret;
+ char *args;
+ GThread *thread;
+ TPMTestState test;
+ g_autofree char *tmp_path = g_dir_make_tmp("qemu-tpm-tis-spi-test.XXXXXX",
+ NULL);
+
+ module_call_init(MODULE_INIT_QOM);
+ g_test_init(&argc, &argv, NULL);
+
+ test.addr = g_new0(SocketAddress, 1);
+ test.addr->type = SOCKET_ADDRESS_TYPE_UNIX;
+ test.addr->u.q_unix.path = g_build_filename(tmp_path, "sock", NULL);
+ g_mutex_init(&test.data_mutex);
+ g_cond_init(&test.data_cond);
+ test.data_cond_signal = false;
+ test.tpm_version = TPM_VERSION_2_0;
+
+ thread = g_thread_new(NULL, tpm_emu_ctrl_thread, &test);
+ tpm_emu_test_wait_cond(&test);
+
+ args = g_strdup_printf("-m 2G -machine powernv10 -smp 2,cores=2,"
+ "threads=1 -accel tcg,thread=single -nographic "
+ "-chardev socket,id=chrtpm,path=%s "
+ "-tpmdev emulator,id=tpm0,chardev=chrtpm "
+ "-device tpm-tis-spi,tpmdev=tpm0,bus=pnv-spi-bus.4",
+ test.addr->u.q_unix.path);
+ qtest_start(args);
+ qtest_add_data_func("pnv-xscom/tpm-tis-spi/basic_test",
+ &pnv_chips[3], test_spi_tpm_basic);
+ qtest_add_data_func("pnv-xscom/tpm-tis-spi/locality_test",
+ &pnv_chips[3], test_spi_tpm_locality);
+ qtest_add_data_func("pnv-xscom/tpm-tis-spi/access_seize_test",
+ &pnv_chips[3], test_spi_tpm_access_seize_test);
+ qtest_add_data_func("pnv-xscom/tpm-tis-spi/access_release_test",
+ &pnv_chips[3], test_spi_tpm_access_release_test);
+ qtest_add_data_func("pnv-xscom/tpm-tis-spi/data_transmit_test",
+ &pnv_chips[3], test_spi_tpm_transmit_test);
+ ret = g_test_run();
+
+ qtest_end();
+ g_thread_join(thread);
+ g_unlink(test.addr->u.q_unix.path);
+ qapi_free_SocketAddress(test.addr);
+ g_rmdir(tmp_path);
+ g_free(args);
+ return ret;
+}
+
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index e8be8b3942..74aa9f57e0 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -177,6 +177,7 @@ qtests_ppc64 = \
(config_all_devices.has_key('CONFIG_PSERIES') ? ['device-plug-test'] : []) + \
(config_all_devices.has_key('CONFIG_POWERNV') ? ['pnv-xscom-test'] : []) + \
(config_all_devices.has_key('CONFIG_POWERNV') ? ['pnv-spi-seeprom-test'] : []) + \
+ (config_all_devices.has_key('CONFIG_POWERNV') ? ['tpm-tis-spi-pnv-test'] : []) + \
(config_all_devices.has_key('CONFIG_POWERNV') ? ['pnv-host-i2c-test'] : []) + \
(config_all_devices.has_key('CONFIG_PSERIES') ? ['numa-test'] : []) + \
(config_all_devices.has_key('CONFIG_PSERIES') ? ['rtas-test'] : []) + \
@@ -348,6 +349,7 @@ qtests = {
'tpm-tis-i2c-test': [io, tpmemu_files, 'qtest_aspeed.c'],
'tpm-tis-device-swtpm-test': [io, tpmemu_files, 'tpm-tis-util.c'],
'tpm-tis-device-test': [io, tpmemu_files, 'tpm-tis-util.c'],
+ 'tpm-tis-spi-pnv-test': [io, tpmemu_files],
'virtio-net-failover': files('migration-helpers.c'),
'vmgenid-test': files('boot-sector.c', 'acpi-utils.c'),
'netdev-socket': files('netdev-socket.c', '../unit/socket-helpers.c'),
--
2.39.5
^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH v4 2/3] tpm/tpm_tis_spi: activation for the PowerNV machines
2024-11-01 20:27 ` [PATCH v4 2/3] tpm/tpm_tis_spi: activation for the PowerNV machines dan tan
@ 2024-11-02 12:55 ` Stefan Berger
0 siblings, 0 replies; 9+ messages in thread
From: Stefan Berger @ 2024-11-02 12:55 UTC (permalink / raw)
To: dan tan, qemu-devel; +Cc: qemu-ppc, stefanb, pbonzini, farosas, lvivier, clg
On 11/1/24 4:27 PM, dan tan wrote:
> The addition to ppc/Kconfig is for building this into the
> qemu-system-ppc64 binary. The enablement requires the
> following command line argument:
> -device tpm-tis-spi,tpmdev=tpm0,bus=pnv-spi-bus.4
>
> Signed-off-by: dan tan <dantan@linux.vnet.ibm.com>
Reviewed-by: Stefan Berger <stefanb@linux.ibm.com>
> ---
> hw/ppc/Kconfig | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/hw/ppc/Kconfig b/hw/ppc/Kconfig
> index b44d91bebb..56dcf5902e 100644
> --- a/hw/ppc/Kconfig
> +++ b/hw/ppc/Kconfig
> @@ -39,6 +39,7 @@ config POWERNV
> select PCI_POWERNV
> select PCA9552
> select PCA9554
> + select TPM_TIS_SPI
> select SERIAL_ISA
> select SSI
> select SSI_M25P80
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v4 3/3] tests/qtest/tpm: add unit test to tis-spi
2024-11-01 20:27 ` [PATCH v4 3/3] tests/qtest/tpm: add unit test to tis-spi dan tan
@ 2024-11-02 14:01 ` Stefan Berger
2024-11-03 0:45 ` Stefan Berger
0 siblings, 1 reply; 9+ messages in thread
From: Stefan Berger @ 2024-11-02 14:01 UTC (permalink / raw)
To: dan tan, qemu-devel; +Cc: qemu-ppc, stefanb, pbonzini, farosas, lvivier, clg
On 11/1/24 4:27 PM, dan tan wrote:
> Add qtest cases to exercise main TPM locality functionality
It's not just locality. -> main TPM functionality.
> The TPM device emulation is provided by swtpm, which is TCG
> TPM 2.0, and TCG TPM TIS compliant. See
> https://trustedcomputinggroup.org/wp-content/uploads/TCG_PC_Client_Platform_TPM_Profile_PTP_2.0_r1.03_v22.pdf
> https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClientTPMInterfaceSpecification_TIS__1-3_27_03212013.pdf
> The SPI registers are specific to the PowerNV platform
> architecture
>
> tests/qtest/tpm: add unit test to tis-spi (rev 4)
This looks like the title of previous 5/5. Remove it.
>
> - removed the function prototypes declaration
> - fixed code format to comply with convention
> - changed function names and variable names to be the same
> as the tpm-tis-i2c test.
> - change hard coded numbers to #define's with meaningful
> names that are identifiable with spec documentation
>
> Signed-off-by: dan tan <dantan@linux.vnet.ibm.com>
Put the above here below three dashes:
---
v3:
- removed the function prototypes declaration
- fixed code format to comply with convention
- changed function names and variable names to be the same
as the tpm-tis-i2c test.
- change hard coded numbers to #define's with meaningful
names that are identifiable with spec documentation
> ---
> tests/qtest/tpm-tis-spi-pnv-test.c | 710 +++++++++++++++++++++++++++++
> tests/qtest/meson.build | 2 +
> 2 files changed, 712 insertions(+)
> create mode 100644 tests/qtest/tpm-tis-spi-pnv-test.c
>
> diff --git a/tests/qtest/tpm-tis-spi-pnv-test.c b/tests/qtest/tpm-tis-spi-pnv-test.c
> new file mode 100644
> index 0000000000..2d6e1186cf
> --- /dev/null
> +++ b/tests/qtest/tpm-tis-spi-pnv-test.c
> @@ -0,0 +1,710 @@
> +/*
> + * QTest testcase for a Nuvoton NPCT75x TPM SPI device
> + * running on the PowerNV machine.
> + *> + * Copyright (c) 2024, IBM Corporation.
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +#include "qemu/osdep.h"
> +#include <glib/gstdio.h>
> +#include "libqtest-single.h"
> +#include "hw/acpi/tpm.h"
> +#include "hw/pci/pci_ids.h"
> +#include "qtest_aspeed.h"
> +#include "tpm-emu.h"
> +#include "hw/ssi/pnv_spi_regs.h"
> +#include "pnv-xscom.h"
> +
> +#define SPI_TPM_BASE 0xc0080
> +#define SPI_SHIFT_COUNTER_N1 0x30000000
> +#define SPI_SHIFT_COUNTER_N2 0x40000000
> +#define SPI_RWX_OPCODE_SHIFT 56
> +#define SPI_RWX_ADDR_SHIFT 32
> +#define SPI_CMD_DATA_SHIFT 56
> +
> +#define CFG_COUNT_COMPARE_1 0x0000000200000000
> +#define MM_REG_RDR_MATCH 0x00000000ff01ff00
> +#define SEQ_OP_REG_BASIC 0x1134416200100000
> +
> +#define TPM_TIS_8BITS_MASK 0xff
> +#define SPI_TPM_TIS_ADDR 0xd40000
> +#define SPI_EXTEND 0x03
> +#define TPM_WRITE_OP 0x0
> +#define TPM_READ_OP 0x80
> +
> +#define SHORT_MAX_RETRIES 5
> +#define LONG_MAX_RETRIES 10
> +
> +static const uint8_t TPM_CMD[12] =
> + "\x80\x01\x00\x00\x00\x0c\x00\x00\x01\x44\x00\x00";
> +
> +#define DPRINTF(fmt, ...) do { \
> + if (DEBUG_TIS_TEST) { \
> + printf(fmt, ## __VA_ARGS__); \
> + } \
> +} while (0)
> +
> +#define DEBUG_TIS_TEST 0
> +
> +#define DPRINTF_ACCESS \
> + DPRINTF("%s: %d: locty=%d l=%d access=0x%02x pending_request_flag=0x%x\n", \
> + __func__, __LINE__, locty, l, access, pending_request_flag)
> +
> +#define DPRINTF_STS \
> + DPRINTF("%s: %d: sts = 0x%08x\n", __func__, __LINE__, sts)
> +
> +static uint64_t pnv_spi_tpm_read(const PnvChip *chip, uint32_t reg)
> +{
> + uint32_t pcba = SPI_TPM_BASE + reg;
> +
> + return qtest_readq(global_qtest, pnv_xscom_addr(chip, pcba));
> +}
> +
> +static void pnv_spi_tpm_write(const PnvChip *chip,
> + uint32_t reg,
> + uint64_t val)
> +{
> + uint32_t pcba = SPI_TPM_BASE + reg;
> +
> + qtest_writeq(global_qtest, pnv_xscom_addr(chip, pcba), val);
> +}
> +
> +static void spi_op_complete(const PnvChip *chip)
> +{
> + uint64_t cfg_reg;
> +
> + cfg_reg = pnv_spi_tpm_read(chip, SPI_CLK_CFG_REG);
> + g_assert_cmpuint(CFG_COUNT_COMPARE_1, ==, cfg_reg);
> + pnv_spi_tpm_write(chip, SPI_CLK_CFG_REG, 0);
> +}
> +
> +static void spi_write_reg(const PnvChip *chip, uint64_t val)
> +{
> + int i;
> + uint64_t spi_sts;
> +
> + for (i = 0; i < LONG_MAX_RETRIES; i++) {
> + spi_sts = pnv_spi_tpm_read(chip, SPI_STS_REG);
> + if (GETFIELD(SPI_STS_TDR_FULL, spi_sts) == 1) {
> + sleep(0.5);
> + } else {
> + break;
> + }
> + }
> + /* cannot write if SPI_STS_TDR_FULL bit is still set */
> + g_assert_cmpuint(0, ==, GETFIELD(SPI_STS_TDR_FULL, spi_sts));
> + pnv_spi_tpm_write(chip, SPI_XMIT_DATA_REG, val);
> +
> + for (i = 0; i < SHORT_MAX_RETRIES; i++) {
> + spi_sts = pnv_spi_tpm_read(chip, SPI_STS_REG);
> + if (GETFIELD(SPI_STS_SHIFTER_FSM, spi_sts) & FSM_DONE) {
> + break;
> + } else {
> + sleep(0.1);
> + }
> + }
> + /* it should be done given the amount of time */
> + g_assert_cmpuint(0, ==, GETFIELD(SPI_STS_SHIFTER_FSM, spi_sts) & FSM_DONE);
> + spi_op_complete(chip);
> +}
> +
> +static uint64_t spi_read_reg(const PnvChip *chip)
> +{
> + int i;
> + uint64_t spi_sts, val = 0;
> +
> + for (i = 0; i < LONG_MAX_RETRIES; i++) {
> + spi_sts = pnv_spi_tpm_read(chip, SPI_STS_REG);
> + if (GETFIELD(SPI_STS_RDR_FULL, spi_sts) == 1) {
> + val = pnv_spi_tpm_read(chip, SPI_RCV_DATA_REG);
> + break;
> + }
> + sleep(0.5);
> + }
> + for (i = 0; i < SHORT_MAX_RETRIES; i++) {
> + spi_sts = pnv_spi_tpm_read(chip, SPI_STS_REG);
> + if (GETFIELD(SPI_STS_RDR_FULL, spi_sts) == 1) {
> + sleep(0.1);
> + } else {
> + break;
> + }
> + }
> + /* SPI_STS_RDR_FULL bit should be reset after read */
> + g_assert_cmpuint(0, ==, GETFIELD(SPI_STS_RDR_FULL, spi_sts));
> + spi_op_complete(chip);
> + return val;
> +}
> +
> +static void spi_access_start(const PnvChip *chip,
> + bool n2,
> + uint8_t bytes,
> + uint8_t tpm_op,
> + uint32_t tpm_reg)
> +{
> + uint64_t cfg_reg;
> + uint64_t reg_op;
> + uint64_t seq_op = SEQ_OP_REG_BASIC;
> +
> + cfg_reg = pnv_spi_tpm_read(chip, SPI_CLK_CFG_REG);
> + if (cfg_reg != CFG_COUNT_COMPARE_1) {
> + pnv_spi_tpm_write(chip, SPI_CLK_CFG_REG, CFG_COUNT_COMPARE_1);
> + }
> + /* bytes - sequencer operation register bits 24:31 */
> + if (n2) {
> + seq_op |= SPI_SHIFT_COUNTER_N2 | (bytes << 0x18);
> + } else {
> + seq_op |= SPI_SHIFT_COUNTER_N1 | (bytes << 0x18);
> + }
> + pnv_spi_tpm_write(chip, SPI_SEQ_OP_REG, seq_op);
> + pnv_spi_tpm_write(chip, SPI_MM_REG, MM_REG_RDR_MATCH);
> + pnv_spi_tpm_write(chip, SPI_CTR_CFG_REG, (uint64_t)0);
> + reg_op = ((uint64_t)tpm_op << SPI_RWX_OPCODE_SHIFT) |
> + ((uint64_t)tpm_reg << SPI_RWX_ADDR_SHIFT);
> + pnv_spi_tpm_write(chip, SPI_XMIT_DATA_REG, reg_op);
> +}
> +
> +static inline void tpm_reg_writeb(const PnvChip *c,
> + uint8_t locty,
> + uint8_t reg,
> + uint8_t val)> +{
> + uint32_t tpm_reg_locty = SPI_TPM_TIS_ADDR |
> + (locty << TPM_TIS_LOCALITY_SHIFT);
> +
> + spi_access_start(c, false, 1, TPM_WRITE_OP, tpm_reg_locty | reg);
> + spi_write_reg(c, (uint64_t) val << SPI_CMD_DATA_SHIFT);
> +}
> +
> +static inline uint8_t tpm_reg_readb(const PnvChip *c,
> + uint8_t locty,
> + uint8_t reg)
uint16_t reg - due to DID_VID register at 0xf00
> +{
> + uint32_t tpm_reg_locty = SPI_TPM_TIS_ADDR |
> + (locty << TPM_TIS_LOCALITY_SHIFT);
> +
> + spi_access_start(c, true, 1, TPM_READ_OP, tpm_reg_locty | reg);
> + return spi_read_reg(c);
> +}
> +
> +static inline void tpm_reg_writel(const PnvChip *c,
> + uint8_t locty,
> + uint16_t reg,
> + uint32_t val)
> +{
> + int i;
> +
> + for (i = 0; i < 4; i++) {
> + tpm_reg_writeb(c, locty, reg + i, ((val >> (8 * i)) & 0xff));
> + }
> +}
> +
> +static inline uint32_t tpm_reg_readl(const PnvChip *c,
> + uint8_t locty,
> + uint16_t reg)
> +{
> + uint32_t val = 0;
> + int i;
> +
> + /* special case for SPI xmit data reg set RWX bits */
There should be no need for this special case. Just this should be enough:
for (i = 0; i < 4; i++) {
val |= tpm_reg_readb(c, locty, reg + i) << (8 * i);
}
or better this here as more efficient 32bit access for all registers:
spi_access_start(c, true, 4, TPM_READ_OP | SPI_EXTEND,
(locty << TPM_TIS_LOCALITY_SHIFT)| reg);
val = bswap32(spi_read_reg(c));
> + if (reg == TPM_TIS_REG_DID_VID) {
> + spi_access_start(c, true, 4, TPM_READ_OP | SPI_EXTEND,
> + locty | TPM_TIS_REG_DID_VID);
this was wrong: (locty << TPM_TIS_LOCALITY_SHIFT)
> + val = bswap32(spi_read_reg(c));
> + } else {
> + for (i = 0; i < 4; i++) {
> + val |= tpm_reg_readb(c, locty, reg + i) << (8 * i);
> + }
> + }
> + return val;
> +}
> +
> +static void tpm_set_verify_loc(const PnvChip *chip, uint8_t loc)> +{
> + uint8_t access;
> + uint32_t tpm_sts;
> +
> + g_test_message("TPM locality %d tests:", loc);
> + access = tpm_reg_readb(chip, loc, TPM_TIS_REG_ACCESS);
> + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
> + TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
> +
On I2C the capability flags were a little different than those of the
'normal' TIS. I suppose on SPI it's the 'normal' TIS flags that are
shown here.
capability = tpm_reg_readl(chip, loc, TPM_TIS_REG_INTF_CAPABILITY);
g_assert_cmpint(capability, ==, TPM_TIS_CAPABILITIES_SUPPORTED2_0);
> + tpm_reg_writeb(chip, loc, TPM_TIS_REG_ACCESS, TPM_TIS_ACCESS_SEIZE);
> + tpm_reg_writeb(chip, loc, TPM_TIS_REG_ACCESS, TPM_TIS_ACCESS_REQUEST_USE);
> +
> + access = tpm_reg_readb(chip, loc, TPM_TIS_REG_ACCESS);
> + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
> + TPM_TIS_ACCESS_ACTIVE_LOCALITY |
> + TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
> + g_test_message("\tACCESS REG = 0x%x checked", access);
> +
> + /* test tpm status register */
> + tpm_sts = tpm_reg_readl(chip, loc, TPM_TIS_REG_STS);
> + g_assert_cmpuint((tpm_sts & TPM_TIS_8BITS_MASK), ==, 0);
> + g_test_message("\tTPM STATUS: 0x%x, verified", tpm_sts);
> +
> + /* release access */
> + tpm_reg_writeb(chip, loc, TPM_TIS_REG_ACCESS,
> + TPM_TIS_ACCESS_ACTIVE_LOCALITY);
> + access = tpm_reg_readb(chip, loc, TPM_TIS_REG_ACCESS);
> + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
> + TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
> + g_test_message("\tRELEASED ACCESS: 0x%x, checked", access);
> +}
> +
> +static void test_spi_tpm_locality(const void *data)
> +{
> + const PnvChip *chip = data;
> + uint8_t locality;
> +
> + /* Locality 4 has special security restrictions, testing 0-3 */
> + for (locality = 0; locality < TPM_TIS_NUM_LOCALITIES - 1; locality++) {
> + tpm_set_verify_loc(chip, locality);
> + }
> +}
> +
> +static void test_spi_tpm_basic(const void *data)
> +{
> + const PnvChip *chip = data;
> + uint32_t didvid, tpm_sts, en_int;
> + uint8_t access;
> +
> + g_test_message("TPM TIS SPI interface basic tests:");
> + /* vendor ID and device ID ... check against the known value*/
> + didvid = tpm_reg_readl(chip, 0, TPM_TIS_REG_DID_VID);
> + g_assert_cmpint(didvid, ==, (1 << 16) | PCI_VENDOR_ID_IBM);
> + g_test_message("\tDID_VID = 0x%x, verified", didvid);
> +
> + /* access register, default see TCG TIS Spec (v1.3) table-14 */
> + access = tpm_reg_readb(chip, 0, TPM_TIS_REG_ACCESS);
> + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
> + TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
> + g_test_message("\tACCESS REG = 0x%x, checked", access);
> +
> + /* interrupt enable register, default see TCG TIS Spec (v1.3) table-19 */
> + en_int = tpm_reg_readl(chip, 0, TPM_TIS_REG_INT_ENABLE);
> + g_assert_cmpuint(en_int, ==, TPM_TIS_INT_POLARITY_LOW_LEVEL);
> + g_test_message("\tINT ENABLE REG: 0x%x, verified", en_int);
> +
> + /* status register, default see TCG TIS Spec (v1.3) table-15 */
> + tpm_sts = tpm_reg_readl(chip, 0, TPM_TIS_REG_STS);
> + /* for no active locality */
> + g_assert_cmpuint(tpm_sts, ==, 0xffffffff);
> + g_test_message("\tTPM STATUS: 0x%x, verified", tpm_sts);
> +}
> +
> +/*
> + * Test case for seizing access by a higher number locality
> + */
> +static void test_spi_tpm_access_seize_test(const void *data)
> +{
> + const PnvChip *chip = data;
> + int locty, l;
> + uint8_t access;
> + uint8_t pending_request_flag;
> +
> + g_test_message("TPM TIS SPI access seize tests:");
> + /* do not test locality 4 (hw only) */
> + for (locty = 0; locty < TPM_TIS_NUM_LOCALITIES - 1; locty++) {
> + pending_request_flag = 0;
> +
> + access = tpm_reg_readb(chip, locty, TPM_TIS_REG_ACCESS);
> + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
> + TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
> +
> + /* request use of locality */
> + tpm_reg_writeb(chip, locty, TPM_TIS_REG_ACCESS,
> + TPM_TIS_ACCESS_REQUEST_USE);
> +
> + access = tpm_reg_readb(chip, locty, TPM_TIS_REG_ACCESS);
> + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
> + TPM_TIS_ACCESS_ACTIVE_LOCALITY |
> + TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
> +
> + /* lower localities cannot seize access */
> + for (l = 0; l < locty; l++) {
> + /* lower locality is not active */
> + access = tpm_reg_readb(chip, l, TPM_TIS_REG_ACCESS);
> + DPRINTF_ACCESS;
> + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
> + pending_request_flag |
> + TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
> +
> + /* try to request use from 'l' */
> + tpm_reg_writeb(chip, l, TPM_TIS_REG_ACCESS,
> + TPM_TIS_ACCESS_REQUEST_USE);
> +
> + /*
> + * requesting use from 'l' was not possible;
> + * we must see REQUEST_USE and possibly PENDING_REQUEST
> + */
> + access = tpm_reg_readb(chip, l, TPM_TIS_REG_ACCESS);
> + DPRINTF_ACCESS;
> + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
> + TPM_TIS_ACCESS_REQUEST_USE |
> + pending_request_flag |
> + TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
> +
> + /*
> + * locality 'locty' must be unchanged;
> + * we must see PENDING_REQUEST
> + */
> + access = tpm_reg_readb(chip, locty, TPM_TIS_REG_ACCESS);
> + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
> + TPM_TIS_ACCESS_ACTIVE_LOCALITY |
> + TPM_TIS_ACCESS_PENDING_REQUEST |
> + TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
> +
> + /* try to seize from 'l' */
> + tpm_reg_writeb(chip, l, TPM_TIS_REG_ACCESS, TPM_TIS_ACCESS_SEIZE);
> + /* seize from 'l' was not possible */
> + access = tpm_reg_readb(chip, l, TPM_TIS_REG_ACCESS);
> + DPRINTF_ACCESS;
> + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
> + TPM_TIS_ACCESS_REQUEST_USE |
> + pending_request_flag |
> + TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
> +
> + /* locality 'locty' must be unchanged */
> + access = tpm_reg_readb(chip, locty, TPM_TIS_REG_ACCESS);
> + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
> + TPM_TIS_ACCESS_ACTIVE_LOCALITY |
> + TPM_TIS_ACCESS_PENDING_REQUEST |
> + TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
> +
> + /*
> + * on the next loop we will have a PENDING_REQUEST flag
> + * set for locality 'l'
> + */
> + pending_request_flag = TPM_TIS_ACCESS_PENDING_REQUEST;
> + }
> +
> + /*
> + * higher localities can 'seize' access but not 'request use';
> + * note: this will activate first l+1, then l+2 etc.
> + */
> + for (l = locty + 1; l < TPM_TIS_NUM_LOCALITIES - 1; l++) {
> + /* try to 'request use' from 'l' */
> + tpm_reg_writeb(chip, l, TPM_TIS_REG_ACCESS,
> + TPM_TIS_ACCESS_REQUEST_USE);
> +
> + /*
> + * requesting use from 'l' was not possible; we should see
> + * REQUEST_USE and may see PENDING_REQUEST
> + */
> + access = tpm_reg_readb(chip, l, TPM_TIS_REG_ACCESS);
> + DPRINTF_ACCESS;
> + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
> + TPM_TIS_ACCESS_REQUEST_USE |
> + pending_request_flag |
> + TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
> +
> + /*
> + * locality 'l-1' must be unchanged; we should always
> + * see PENDING_REQUEST from 'l' requesting access
> + */
> + access = tpm_reg_readb(chip, l - 1, TPM_TIS_REG_ACCESS);
> + DPRINTF_ACCESS;
> + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
> + TPM_TIS_ACCESS_ACTIVE_LOCALITY |
> + TPM_TIS_ACCESS_PENDING_REQUEST |
> + TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
> +
> + /* try to seize from 'l' */
> + tpm_reg_writeb(chip, l, TPM_TIS_REG_ACCESS, TPM_TIS_ACCESS_SEIZE);
> +
> + /* seize from 'l' was possible */
> + access = tpm_reg_readb(chip, l, TPM_TIS_REG_ACCESS);
> + DPRINTF_ACCESS;
> + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
> + TPM_TIS_ACCESS_ACTIVE_LOCALITY |
> + pending_request_flag |
> + TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
> +
> + /* l - 1 should show that it has BEEN_SEIZED */
> + access = tpm_reg_readb(chip, l - 1, TPM_TIS_REG_ACCESS);
> + DPRINTF_ACCESS;
> + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
> + TPM_TIS_ACCESS_BEEN_SEIZED |
> + pending_request_flag |
> + TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
> +
> + /* clear the BEEN_SEIZED flag and make sure it's gone */
> + tpm_reg_writeb(chip, l - 1, TPM_TIS_REG_ACCESS,
> + TPM_TIS_ACCESS_BEEN_SEIZED);
> +
> + access = tpm_reg_readb(chip, l - 1, TPM_TIS_REG_ACCESS);
> + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
> + pending_request_flag |
> + TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
> + }
> +
> + /*
> + * PENDING_REQUEST will not be set if locty = 0 since all localities
> + * were active; in case of locty = 1, locality 0 will be active
> + * but no PENDING_REQUEST anywhere
> + */
> + if (locty <= 1) {
> + pending_request_flag = 0;
> + }
> +
> + /* release access from l - 1; this activates locty - 1 */
> + l--;
> +
> + access = tpm_reg_readb(chip, l, TPM_TIS_REG_ACCESS);
> + DPRINTF_ACCESS;
> +
> + DPRINTF("%s: %d: relinquishing control on l = %d\n",
> + __func__, __LINE__, l);
> + tpm_reg_writeb(chip, l, TPM_TIS_REG_ACCESS,
> + TPM_TIS_ACCESS_ACTIVE_LOCALITY);
> +
> + access = tpm_reg_readb(chip, l, TPM_TIS_REG_ACCESS);
> + DPRINTF_ACCESS;
> + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
> + pending_request_flag |
> + TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
> +
> + for (l = locty - 1; l >= 0; l--) {
> + access = tpm_reg_readb(chip, l, TPM_TIS_REG_ACCESS);
> + DPRINTF_ACCESS;
> + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
> + TPM_TIS_ACCESS_ACTIVE_LOCALITY |
> + pending_request_flag |
> + TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
> +
> + /* release this locality */
> + tpm_reg_writeb(chip, l, TPM_TIS_REG_ACCESS,
> + TPM_TIS_ACCESS_ACTIVE_LOCALITY);
> +
> + if (l == 1) {
> + pending_request_flag = 0;
> + }
> + }
> +
> + /* no locality may be active now */
> + for (l = 0; l < TPM_TIS_NUM_LOCALITIES - 1; l++) {
> + access = tpm_reg_readb(chip, l, TPM_TIS_REG_ACCESS);
> + DPRINTF_ACCESS;
> + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
> + TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
> + }
> + g_test_message("\tTPM locality %d seize tests: passed", locty);
> + }
> +}
> +
> +/*
> + * Test case for getting access when higher number locality relinquishes access
> + */
> +static void test_spi_tpm_access_release_test(const void *data)
> +{
> + const PnvChip *chip = data;
> + int locty, l;
> + uint8_t access;
> + uint8_t pending_request_flag;
> +
> + g_test_message("TPM TIS SPI access release tests:");
> + /* do not test locality 4 (hw only) */
> + for (locty = TPM_TIS_NUM_LOCALITIES - 2; locty >= 0; locty--) {
> + pending_request_flag = 0;
> +
> + access = tpm_reg_readb(chip, locty, TPM_TIS_REG_ACCESS);
> + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
> + TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
> +
> + /* request use of locality */
> + tpm_reg_writeb(chip, locty, TPM_TIS_REG_ACCESS,
> + TPM_TIS_ACCESS_REQUEST_USE);
> + access = tpm_reg_readb(chip, locty, TPM_TIS_REG_ACCESS);
> + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
> + TPM_TIS_ACCESS_ACTIVE_LOCALITY |
> + TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
> +
> + /* request use of all other localities */
> + for (l = 0; l < TPM_TIS_NUM_LOCALITIES - 1; l++) {
> + if (l == locty) {
> + continue;
> + }
> + /*
> + * request use of locality 'l' -- we MUST see REQUEST USE and
> + * may see PENDING_REQUEST
> + */
> + tpm_reg_writeb(chip, l, TPM_TIS_REG_ACCESS,
> + TPM_TIS_ACCESS_REQUEST_USE);
> + access = tpm_reg_readb(chip, l, TPM_TIS_REG_ACCESS);
> + DPRINTF_ACCESS;
> + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
> + TPM_TIS_ACCESS_REQUEST_USE |
> + pending_request_flag |
> + TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
> + pending_request_flag = TPM_TIS_ACCESS_PENDING_REQUEST;
> + }
> + /* release locality 'locty' */
> + tpm_reg_writeb(chip, locty, TPM_TIS_REG_ACCESS,
> + TPM_TIS_ACCESS_ACTIVE_LOCALITY);
> + /*
> + * highest locality should now be active; release it and make sure the
> + * next highest locality is active afterwards
> + */
> + for (l = TPM_TIS_NUM_LOCALITIES - 2; l >= 0; l--) {
> + if (l == locty) {
> + continue;
> + }
> + /* 'l' should be active now */
> + access = tpm_reg_readb(chip, l, TPM_TIS_REG_ACCESS);
> + DPRINTF_ACCESS;
> + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
> + TPM_TIS_ACCESS_ACTIVE_LOCALITY |
> + pending_request_flag |
> + TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
> + /* 'l' relinquishes access */
> + tpm_reg_writeb(chip, l, TPM_TIS_REG_ACCESS,
> + TPM_TIS_ACCESS_ACTIVE_LOCALITY);
> + access = tpm_reg_readb(chip, l, TPM_TIS_REG_ACCESS);
> + DPRINTF_ACCESS;
> + if (l == 1 || (locty <= 1 && l == 2)) {
> + pending_request_flag = 0;
> + }
> + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
> + pending_request_flag |
> + TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
> + }
> + g_test_message("\tTPM locality %d seize tests: passed", locty);
> + }
> +}
> +
> +/*
> + * Test case for transmitting packets
> + */
> +static void test_spi_tpm_transmit_test(const void *data)
> +{
> + const PnvChip *chip = data;
For comparing received TPM responses we need access to TPMTestState in
this test case, this should be:
const PnvChip *chip = &pnv_chips[3]; <--- may want to consider
replacing this everywhere else as well.
const struct TPMTestState *s = data;
> + uint16_t bcount;
> + uint8_t access;
> + uint32_t sts;
> + int i;
> +
> + g_test_message("TPM TIS SPI transmit tests:");
> + /* request use of locality 0 */
> + tpm_reg_writeb(chip, 0, TPM_TIS_REG_ACCESS, TPM_TIS_ACCESS_REQUEST_USE);
> + access = tpm_reg_readb(chip, 0, TPM_TIS_REG_ACCESS);
> + g_assert_cmpint(access, ==, TPM_TIS_ACCESS_TPM_REG_VALID_STS |
> + TPM_TIS_ACCESS_ACTIVE_LOCALITY |
> + TPM_TIS_ACCESS_TPM_ESTABLISHMENT);
> +
> + sts = tpm_reg_readl(chip, 0, TPM_TIS_REG_STS);
> + DPRINTF_STS;
> +
> + g_assert_cmpint(sts & 0xff, ==, 0);
g_assert_cmpint(sts & TPM_TIS_STS_TPM_FAMILY_MASK, ==,
TPM_TIS_STS_TPM_FAMILY2_0);
> +
> + bcount = (sts >> 8) & 0xffff;
> + g_test_message("\t\tbcount: %x, sts: %x", bcount, sts);
> + g_assert_cmpint(bcount, >=, 128);
> +
> + tpm_reg_writel(chip, 0, TPM_TIS_REG_STS, TPM_TIS_STS_COMMAND_READY);
> + sts = tpm_reg_readl(chip, 0, TPM_TIS_REG_STS);
> + DPRINTF_STS;
> + g_assert_cmpint(sts & 0xff, ==, TPM_TIS_STS_COMMAND_READY);
> +
> + /* transmit command */
> + for (i = 0; i < sizeof(TPM_CMD); i++) {
> + tpm_reg_writeb(chip, 0, TPM_TIS_REG_DATA_FIFO, TPM_CMD[i]);
> + sts = tpm_reg_readl(chip, 0, TPM_TIS_REG_STS);
> + DPRINTF_STS;
> + if (i < sizeof(TPM_CMD) - 1) {
> + g_assert_cmpint(sts & 0xff, ==, TPM_TIS_STS_EXPECT |
> + TPM_TIS_STS_VALID);
> + } else {
> + g_assert_cmpint(sts & 0xff, ==, TPM_TIS_STS_VALID);
> + }
/* since STS is read byte-by-byte bcount will be constant 0xff */
g_assert_cmpint((sts >> 8) & 0xffff, ==, 0xff);
> + }
> + g_test_message("\ttransmit tests, check TPM_TIS_STS_EXPECT");
> +
> + /* start processing */
> + tpm_reg_writel(chip, 0, TPM_TIS_REG_STS, TPM_TIS_STS_TPM_GO);
> +
> + uint64_t end_time = g_get_monotonic_time() + 50 * G_TIME_SPAN_SECOND;
> + do {
> + sts = tpm_reg_readl(chip, 0, TPM_TIS_REG_STS);
> + if ((sts & TPM_TIS_STS_DATA_AVAILABLE) != 0) {
> + break;
> + }
> + } while (g_get_monotonic_time() < end_time);
> +
> + sts = tpm_reg_readl(chip, 0, TPM_TIS_REG_STS);
> + DPRINTF_STS;
> + g_assert_cmpint(sts & 0xff, == , TPM_TIS_STS_VALID |
> + TPM_TIS_STS_DATA_AVAILABLE);
> + /* TCG TIS Spec (v1.3) table-15 */
> + g_test_message("\ttransmit tests, check tpmGo (w) & dataAvail (r)");
> + bcount = (sts >> 8) & 0xffff;
> +
> + /* read response */
> + uint8_t tpm_msg[sizeof(struct tpm_hdr)];
> + g_assert_cmpint(sizeof(tpm_msg), ==, bcount);
> +
> + for (i = 0; i < sizeof(tpm_msg); i++) {
> + tpm_msg[i] = tpm_reg_readb(chip, 0, TPM_TIS_REG_DATA_FIFO);
> + sts = tpm_reg_readl(chip, 0, TPM_TIS_REG_STS);
> + DPRINTF_STS;
> + if (sts & TPM_TIS_STS_DATA_AVAILABLE) {
> + g_assert_cmpint((sts >> 8) & 0xffff, ==, --bcount);
> + }
> + }
With 's' accessible now:
g_assert_cmpmem(tpm_msg, sizeof(tpm_msg), s->tpm_msg,
sizeof(*s->tpm_msg));
> + g_test_message("\treceive tests, passed");
> + /* relinquish use of locality 0 */
> + tpm_reg_writeb(chip, 0, TPM_TIS_REG_ACCESS, TPM_TIS_ACCESS_ACTIVE_LOCALITY);
> + access = tpm_reg_readb(chip, 0, TPM_TIS_REG_ACCESS);
> +}
> +
> +int main(int argc, char **argv)
> +{
> + int ret;
> + char *args;
> + GThread *thread;
> + TPMTestState test;
> + g_autofree char *tmp_path = g_dir_make_tmp("qemu-tpm-tis-spi-test.XXXXXX",
> + NULL);
> +
> + module_call_init(MODULE_INIT_QOM);
> + g_test_init(&argc, &argv, NULL);
> +
> + test.addr = g_new0(SocketAddress, 1);
> + test.addr->type = SOCKET_ADDRESS_TYPE_UNIX;
> + test.addr->u.q_unix.path = g_build_filename(tmp_path, "sock", NULL);
> + g_mutex_init(&test.data_mutex);
> + g_cond_init(&test.data_cond);
> + test.data_cond_signal = false;
> + test.tpm_version = TPM_VERSION_2_0;
> +
> + thread = g_thread_new(NULL, tpm_emu_ctrl_thread, &test);
> + tpm_emu_test_wait_cond(&test);
> +
> + args = g_strdup_printf("-m 2G -machine powernv10 -smp 2,cores=2,"
> + "threads=1 -accel tcg,thread=single -nographic "
> + "-chardev socket,id=chrtpm,path=%s "
> + "-tpmdev emulator,id=tpm0,chardev=chrtpm "
> + "-device tpm-tis-spi,tpmdev=tpm0,bus=pnv-spi-bus.4",
> + test.addr->u.q_unix.path);
> + qtest_start(args);
> + qtest_add_data_func("pnv-xscom/tpm-tis-spi/basic_test",
> + &pnv_chips[3], test_spi_tpm_basic);
> + qtest_add_data_func("pnv-xscom/tpm-tis-spi/locality_test",
> + &pnv_chips[3], test_spi_tpm_locality);
> + qtest_add_data_func("pnv-xscom/tpm-tis-spi/access_seize_test",
> + &pnv_chips[3], test_spi_tpm_access_seize_test);
> + qtest_add_data_func("pnv-xscom/tpm-tis-spi/access_release_test",
> + &pnv_chips[3], test_spi_tpm_access_release_test);
> + qtest_add_data_func("pnv-xscom/tpm-tis-spi/data_transmit_test",
> + &pnv_chips[3], test_spi_tpm_transmit_test);
pass &test here instead of &pnv_chips[3].
> + ret = g_test_run();
> +
> + qtest_end();
> + g_thread_join(thread);
> + g_unlink(test.addr->u.q_unix.path);
> + qapi_free_SocketAddress(test.addr);
> + g_rmdir(tmp_path);
> + g_free(args);
> + return ret;
> +}
> +
> diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
> index e8be8b3942..74aa9f57e0 100644
> --- a/tests/qtest/meson.build
> +++ b/tests/qtest/meson.build
> @@ -177,6 +177,7 @@ qtests_ppc64 = \
> (config_all_devices.has_key('CONFIG_PSERIES') ? ['device-plug-test'] : []) + \
> (config_all_devices.has_key('CONFIG_POWERNV') ? ['pnv-xscom-test'] : []) + \
> (config_all_devices.has_key('CONFIG_POWERNV') ? ['pnv-spi-seeprom-test'] : []) + \
> + (config_all_devices.has_key('CONFIG_POWERNV') ? ['tpm-tis-spi-pnv-test'] : []) + \
> (config_all_devices.has_key('CONFIG_POWERNV') ? ['pnv-host-i2c-test'] : []) + \
> (config_all_devices.has_key('CONFIG_PSERIES') ? ['numa-test'] : []) + \
> (config_all_devices.has_key('CONFIG_PSERIES') ? ['rtas-test'] : []) + \
> @@ -348,6 +349,7 @@ qtests = {
> 'tpm-tis-i2c-test': [io, tpmemu_files, 'qtest_aspeed.c'],
> 'tpm-tis-device-swtpm-test': [io, tpmemu_files, 'tpm-tis-util.c'],
> 'tpm-tis-device-test': [io, tpmemu_files, 'tpm-tis-util.c'],
> + 'tpm-tis-spi-pnv-test': [io, tpmemu_files],
> 'virtio-net-failover': files('migration-helpers.c'),
> 'vmgenid-test': files('boot-sector.c', 'acpi-utils.c'),
> 'netdev-socket': files('netdev-socket.c', '../unit/socket-helpers.c'),
When running `ninja -C build test` I get this output that should have
its lines prefixed with '#' 'somehow':
42/424 qemu:qtest+qtest-ppc64 / qtest-ppc64/tpm-tis-spi-pnv-test
OK 0.31s 5 subtests passed
stdout: 34: UNKNOWN: [ 0.024173382,5] OPAL v7.1 starting...
stdout: 35: UNKNOWN: [ 0.024724739,7] initial console log level:
memory 7, driver 5
stdout: 36: UNKNOWN: [ 0.024778210,6] CPU: P10 generation processor
(max 4 threads/core)
stdout: 37: UNKNOWN: [ 0.024795001,7] CPU: Boot CPU PIR is 0x0000 PVR
is 0x00801200
stdout: 38: UNKNOWN: [ 0.024925108,7] OPAL table: 0x3018a960 ..
0x3018af00, branch table: 0x30120000
stdout: 39: UNKNOWN: [ 0.025103930,7] Assigning physical memory map
table for p10
stdout: 40: UNKNOWN: [ 0.025411575,7] FDT: Parsing fdt @0x1000000
stdout: 41: UNKNOWN: [ 0.028505988,5] CHIP: Detected QEMU simulator
stdout: 42: UNKNOWN: [ 0.028683295,6] CHIP: Initialised chip 0 from
xscom@603fc00000000
stdout: 43: UNKNOWN: [ 0.029247139,6] P10 DD2.00 detected
stdout: 44: UNKNOWN: [ 0.029267261,5] CHIP: Chip ID 0000 type: P10 DD2.00
stdout: 45: UNKNOWN: [ 0.029276249,7] XSCOM: Base address:
0x603fc00000000
stdout: 46: UNKNOWN: [ 0.029339220,7] XSTOP: ibm,sw-checkstop-fir
prop not found
stdout: 47: UNKNOWN: [ 0.029409300,6] MFSI 0:0: Initialized
stdout: 48: UNKNOWN: [ 0.029419734,6] MFSI 0:2: Initialized
stdout: 49: UNKNOWN: [ 0.029429173,6] MFSI 0:1: Initialized
stdout: 50: UNKNOWN: [ 0.030012352,6] LPC: LPC[000]: Initialized
stdout: 51: UNKNOWN: [ 0.030020186,7] LPC: access via MMIO
@0x6030000000000
stdout: 52: UNKNOWN: [ 0.030108378,7] LPC: Default bus on chip 0x0
stdout: 53: UNKNOWN: [ 0.030193313,7] CPU: New max PIR set to 0x7
stdout: 54: UNKNOWN: [ 0.030743076,7] MEM: parsing reserved memory
from reserved-names/-ranges properties
stdout: 55: UNKNOWN: [ 0.030828672,7] HOMER: Init chip 0
stdout: 56: UNKNOWN: [ 0.030862569,7] PBA BAR0 : 0x0000300ffd800000
stdout: 57: UNKNOWN: [ 0.030870164,7] PBA MASK0: 0x0000000000300000
stdout: 58: UNKNOWN: [ 0.030899260,7] HOMER Image at 0x300ffd800000
size 4MB
stdout: 59: UNKNOWN: [ 0.030943460,4] HOMER image is not reserved!
Reserving
stdout: 60: UNKNOWN: [ 0.031114747,7] PBA BAR2 : 0x0000300fff800000
stdout: 61: UNKNOWN: [ 0.031121980,7] PBA MASK2: 0x0000000000700000
stdout: 62: UNKNOWN: [ 0.031136886,7] OCC Common Area at
0x300fff800000 size 8MB
stdout: 63: UNKNOWN: [ 0.031171139,4] OCC common area is not
reserved! Reserving
stdout: 64: UNKNOWN: [ 0.031231035,7] CPU: decrementer bits 56
stdout: 65: UNKNOWN: [ 0.031321664,6] CPU: CPU from DT PIR=0x0000
Server#=0x0 State=4
stdout: 66: UNKNOWN: [ 0.031427205,6] CPU: 1 secondary threads
stdout: 67: UNKNOWN: [ 0.031441159,6] CPU: CPU from DT PIR=0x0004
Server#=0x4 State=4
stdout: 68: UNKNOWN: [ 0.031459259,6] CPU: 1 secondary threads
stdout: 69: UNKNOWN: [ 0.033492258,5] PLAT: Using SuperIO UART
stdout: 70: UNKNOWN: [ 0.033826231,7] UART: Using LPC IRQ 4
stdout: 71: UNKNOWN: [ 0.036351187,5] PLAT: Detected QEMU POWER10
platform
stdout: 72: UNKNOWN: [ 0.036415869,5] PLAT: Detected BMC platform
ast2500:openbmc
stdout: 73: UNKNOWN: [ 0.037499517,5] XIVE: [ IC 00 ] Initializing
XIVE block ID 0...
ERROR: Unknown TAP output lines for a supported TAP version.
This is probably a bug in the test; if they are not TAP syntax, prefix
them with a #
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v4 1/3] tpm/tpm_tis_spi: Support TPM for SPI (Serial Peripheral Interface)
2024-11-01 20:27 ` [PATCH v4 1/3] tpm/tpm_tis_spi: Support TPM for SPI (Serial Peripheral Interface) dan tan
@ 2024-11-02 14:19 ` Stefan Berger
2024-11-04 2:17 ` dan tan
0 siblings, 1 reply; 9+ messages in thread
From: Stefan Berger @ 2024-11-02 14:19 UTC (permalink / raw)
To: dan tan, qemu-devel; +Cc: qemu-ppc, stefanb, pbonzini, farosas, lvivier, clg
On 11/1/24 4:27 PM, dan tan wrote:
> Implement support for TPM via SPI interface. The SPI bus master
> is provided by PowerNV SPI device which is an SSI peripheral.
> It can uses the tpm_emulator driver backend with the external
> swtpm.
>
> Although the implementation is endian neutral, the SPI
> bus master provider, pnv_spi.c is only supported on the
> PowerNV platform, thus, is big endian specific.
>
Did you try running this with Linux? Did the Linux TPM driver work?
> tpm/tpm_tis_spi: Support TPM for SPI (rev 4)
Remove this. It seems to be from your patch merge.
>
> - moved variable tis_addr from TPMStateSPI struct to local
> - added the VM suspend/resume support:
> - added vmstate_tpm_tis_spi declaration
> - added tpm_tis_spi_pre_save() function
> - fixed trace formatting string
Move this below your Signed-off-by after the dashes '---'
>
> Signed-off-by: dan tan <dantan@linux.vnet.ibm.com>
---
v3:
- moved variable tis_addr from TPMStateSPI struct to local
- added the VM suspend/resume support:
- added vmstate_tpm_tis_spi declaration
- added tpm_tis_spi_pre_save() function
- fixed trace formatting string
> ---
> docs/specs/tpm.rst | 15 ++
> include/sysemu/tpm.h | 3 +
> hw/tpm/tpm_tis_spi.c | 360 +++++++++++++++++++++++++++++++++++++++++++
> hw/tpm/Kconfig | 6 +
> hw/tpm/meson.build | 1 +
> hw/tpm/trace-events | 7 +
> 6 files changed, 392 insertions(+)
> create mode 100644 hw/tpm/tpm_tis_spi.c
>
> diff --git a/docs/specs/tpm.rst b/docs/specs/tpm.rst
> index 1ad36ad709..63c3a43c7d 100644
> --- a/docs/specs/tpm.rst
> +++ b/docs/specs/tpm.rst
> @@ -24,6 +24,7 @@ QEMU files related to TPM TIS interface:
> - ``hw/tpm/tpm_tis_isa.c``
> - ``hw/tpm/tpm_tis_sysbus.c``
> - ``hw/tpm/tpm_tis_i2c.c``
> + - ``hw/tpm/tpm_tis_spi.c``
> - ``hw/tpm/tpm_tis.h``
>
> Both an ISA device and a sysbus device are available. The former is
> @@ -33,6 +34,9 @@ Arm virt machine.
> An I2C device support is also provided which can be instantiated in the Arm
> based emulation machines. This device only supports the TPM 2 protocol.
>
> +A Serial Peripheral Interface (SPI) device support has been added to the
> +PowerNV emulation machines. This device only supports the TPM 2 protocol.
> +
> CRB interface
> -------------
>
> @@ -371,6 +375,17 @@ attached to I2C bus, use the following command line:
>
> echo tpm_tis_i2c 0x2e > /sys/bus/i2c/devices/i2c-12/new_device
>
> +In case a PowerNV machine is emulated and you want to use a TPM device
> +attached to SPI bus, use the following command line (SPI bus master is
> +provided by PowerNV SPI device):
> +
> +.. code-block:: console
> +
> + qemu-system-ppc64 -m 2G -machine powernv10 -nographic \
> + -chardev socket,id=chrtpm,path=/tmp/mytpm1/swtpm-sock \
> + -tpmdev emulator,id=tpm0,chardev=chrtpm \
> + -device tpm-tis-spi,tpmdev=tpm0,bus=pnv-spi-bus.4
> +
If you move this a bit further up to after the 'pSeries machine' then we
have the ppc64 stuff in one place.
> In case SeaBIOS is used as firmware, it should show the TPM menu item
> after entering the menu with 'ESC'.
>
> diff --git a/include/sysemu/tpm.h b/include/sysemu/tpm.h
> index 1ee568b3b6..22b05110e2 100644
> --- a/include/sysemu/tpm.h
> +++ b/include/sysemu/tpm.h
> @@ -49,6 +49,7 @@ struct TPMIfClass {
> #define TYPE_TPM_CRB "tpm-crb"
> #define TYPE_TPM_SPAPR "tpm-spapr"
> #define TYPE_TPM_TIS_I2C "tpm-tis-i2c"
> +#define TYPE_TPM_TIS_SPI "tpm-tis-spi"
>
> #define TPM_IS_TIS_ISA(chr) \
> object_dynamic_cast(OBJECT(chr), TYPE_TPM_TIS_ISA)
> @@ -60,6 +61,8 @@ struct TPMIfClass {
> object_dynamic_cast(OBJECT(chr), TYPE_TPM_SPAPR)
> #define TPM_IS_TIS_I2C(chr) \
> object_dynamic_cast(OBJECT(chr), TYPE_TPM_TIS_I2C)
> +#define TPM_IS_TIS_SPI(chr) \
> + object_dynamic_cast(OBJECT(chr), TYPE_TPM_TIS_SPI)
>
> /* returns NULL unless there is exactly one TPM device */
> static inline TPMIf *tpm_find(void)
> diff --git a/hw/tpm/tpm_tis_spi.c b/hw/tpm/tpm_tis_spi.c
> new file mode 100644
> index 0000000000..4aee1c6d6c
> --- /dev/null
> +++ b/hw/tpm/tpm_tis_spi.c
> @@ -0,0 +1,360 @@
> +/*
> + * QEMU PowerPC SPI TPM 2.0 model
> + *
> + * Copyright (c) 2024, IBM Corporation.
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#define IBM_PONQ
> +
> +#include "qemu/osdep.h"
> +#include "qemu/log.h"
> +#include "hw/sysbus.h"
> +#include "hw/acpi/tpm.h"
> +#include "tpm_prop.h"
> +#include "qemu/log.h"
> +#include "trace.h"
> +#include "tpm_tis.h"
> +#include "hw/ssi/ssi.h"
> +#include "migration/vmstate.h"
> +
> +typedef struct TPMStateSPI {
> + /*< private >*/
> + SSIPeripheral parent_object;
> +
> + uint8_t byte_offset; /* byte offset in transfer */
> + uint8_t wait_state_cnt; /* wait state counter */
> + uint8_t xfer_size; /* data size of transfer */
> + uint32_t reg_addr; /* register address of transfer */
> +
> + uint8_t spi_state; /* READ / WRITE / IDLE */
> +#define SPI_STATE_IDLE 0
> +#define SPI_STATE_WRITE 1
> +#define SPI_STATE_READ 2
> +
> + bool command;
> +
> + /*< public >*/
> + TPMState tpm_state; /* not a QOM object */
> +
> +} TPMStateSPI;
> +
> +#define CMD_BYTE_WRITE (1 << 7)
> +#define CMD_BYTE_XFER_SZ_MASK 0x1f
> +#define TIS_SPI_HIGH_ADDR_BYTE 0xd4
> +
> +DECLARE_INSTANCE_CHECKER(TPMStateSPI, TPM_TIS_SPI, TYPE_TPM_TIS_SPI)
> +
> +static int tpm_tis_spi_pre_save(void *opaque)
> +{
> + TPMStateSPI *spist = opaque;
> +
> + return tpm_tis_pre_save(&spist->tpm_state);
> +}
> +
> +static const VMStateDescription vmstate_tpm_tis_spi = {
> + .name = "tpm-tis-spi",
> + .version_id = 0,
> + .pre_save = tpm_tis_spi_pre_save,
> + .fields = (const VMStateField[]) {
> + VMSTATE_BUFFER(tpm_state.buffer, TPMStateSPI),
> + VMSTATE_UINT16(tpm_state.rw_offset, TPMStateSPI),
> + VMSTATE_UINT8(tpm_state.active_locty, TPMStateSPI),
> + VMSTATE_UINT8(tpm_state.aborting_locty, TPMStateSPI),
> + VMSTATE_UINT8(tpm_state.next_locty, TPMStateSPI),
> +
> + VMSTATE_STRUCT_ARRAY(tpm_state.loc, TPMStateSPI,
> + TPM_TIS_NUM_LOCALITIES, 0,
> + vmstate_locty, TPMLocality),
> +
> + /* spi specifics */
> + VMSTATE_UINT8(byte_offset, TPMStateSPI),
> + VMSTATE_UINT8(wait_state_cnt, TPMStateSPI),
> + VMSTATE_UINT8(xfer_size, TPMStateSPI),
> + VMSTATE_UINT32(reg_addr, TPMStateSPI),
> + VMSTATE_UINT8(spi_state, TPMStateSPI),
> + VMSTATE_BOOL(command, TPMStateSPI),
> +
> + VMSTATE_END_OF_LIST()
> + }
> +};
> +
> +static inline void tpm_tis_spi_clear_data(TPMStateSPI *spist)
> +{
> + spist->spi_state = SPI_STATE_IDLE;
> + spist->byte_offset = 0;
> + spist->wait_state_cnt = 0;
> + spist->xfer_size = 0;
> + spist->reg_addr = 0;
> +
> + return;
> +}
> +
> +/* Callback from TPM to indicate that response is copied */
> +static void tpm_tis_spi_request_completed(TPMIf *ti, int ret)
> +{
> + TPMStateSPI *spist = TPM_TIS_SPI(ti);
> + TPMState *s = &spist->tpm_state;
> +
> + /* Inform the common code. */
> + tpm_tis_request_completed(s, ret);
> +}
> +
> +static enum TPMVersion tpm_tis_spi_get_tpm_version(TPMIf *ti)
> +{
> + TPMStateSPI *spist = TPM_TIS_SPI(ti);
> + TPMState *s = &spist->tpm_state;
> +
> + return tpm_tis_get_tpm_version(s);
> +}
> +
> +/*
> + * TCG PC Client Platform TPM Profile Specification for TPM 2.0 ver 1.05 rev 14
> + *
> + * For system Software, the TPM has a 64-bit address of 0x0000_0000_FED4_xxxx.
> + * On SPI, the chipset passes the least significant 24 bits to the TPM.
> + * The upper bytes will be used by the chipset to select the TPM’s SPI CS#
> + * signal. Table 9 shows the locality based on the 16 least significant address
> + * bits and assume that either the LPC TPM sync or SPI TPM CS# is used.
> + *
> + */
> +static void tpm_tis_spi_write(TPMStateSPI *spist, uint32_t addr, uint8_t val)
> +{
> + TPMState *tpm_st = &spist->tpm_state;
> +
> + trace_tpm_tis_spi_write(addr, val);
> + tpm_tis_write_data(tpm_st, addr, val, 1);
> +}
> +
> +static uint8_t tpm_tis_spi_read(TPMStateSPI *spist, uint32_t addr)
> +{
> + TPMState *tpm_st = &spist->tpm_state;
> + uint8_t data;
> +
> + data = tpm_tis_read_data(tpm_st, addr, 1);
> + trace_tpm_tis_spi_read(addr, data);
> + return data;
> +}
> +
> +static Property tpm_tis_spi_properties[] = {
> + DEFINE_PROP_TPMBE("tpmdev", TPMStateSPI, tpm_state.be_driver),
> + DEFINE_PROP_UINT32("irq", TPMStateSPI, tpm_state.irq_num, 0),
Please remove this line.
> + DEFINE_PROP_END_OF_LIST(),
> +};
> +
> +static void tpm_tis_spi_reset(DeviceState *dev)
> +{
> + TPMStateSPI *spist = TPM_TIS_SPI(dev);
> + TPMState *s = &spist->tpm_state;
> +
> + tpm_tis_spi_clear_data(spist);
> + return tpm_tis_reset(s);
> +}
> +
> +static uint32_t tpm_transfer(SSIPeripheral *ss, uint32_t tx)
> +{
> + TPMStateSPI *spist = TPM_TIS_SPI(ss);
> + uint32_t rx = 0;
> + uint8_t byte; /* reversed byte value */
> + uint8_t offset = 0; /* offset of byte in payload */
> + uint8_t index; /* index of data byte in transfer */
> + uint32_t tis_addr; /* tis address including locty */
> +
> + /* new transfer or not */
> + if (spist->command) { /* new transfer start */
> + if (spist->spi_state != SPI_STATE_IDLE) {
> + qemu_log_mask(LOG_GUEST_ERROR, "unexpected new transfer\n");
> + }
> + spist->byte_offset = 0;
> + spist->wait_state_cnt = 0;
> + }
> + /*
> + * Explanation of wait_state:
> + * The original TPM model did not have wait state or "flow control" support
> + * built in. If you wanted to read a TPM register through SPI you sent
> + * the first byte with the read/write bit and size, then three address bytes
> + * and any additional bytes after that were don't care bytes for reads and
> + * the model would begin returning byte data to the SPI reader from the
> + * register address provided. In the real world this would mean that a
> + * TPM device had only the time between the 31st clock and the 32nd clock
> + * to fetch the register data that it had to provide to SPI MISO starting
> + * with the 32nd clock.
> + *
> + * In reality the TPM begins introducing a wait state at the 31st clock
> + * by holding MISO low. This is how it controls the "flow" of the
> + * operation. Once the data the TPM needs to return is ready it will
> + * select bit 31 + (8*N) to send back a 1 which indicates that it will
> + * now start returning data on MISO.
> + *
> + * The same wait states are applied to writes. In either the read or write
> + * case the wait state occurs between the command+address (4 bytes) and the
> + * data (1-n bytes) sections of the SPI frame. The code below introduces
> + * the support for a 32 bit wait state for P10. All reads and writes
> + * through the SPI interface MUST now be aware of the need to do flow
> + * control in order to use the TPM via SPI.
> + *
> + * In conjunction with these changes there were changes made to the SPIM
> + * engine that was introduced in P10 to support the 6x op code which is
> + * used to receive wait state 0s on the MISO line until it sees the b'1'
> + * come back before continuing to read real data from the SPI device(TPM).
> + */
> +
> + trace_tpm_tis_spi_transfer_data("Payload byte_offset", spist->byte_offset);
> + /* process payload data */
> + while (offset < 4) {
> + spist->command = false;
> + byte = (tx >> (24 - 8 * offset)) & 0xFF;
> + trace_tpm_tis_spi_transfer_data("Extracted byte", byte);
> + trace_tpm_tis_spi_transfer_data("Payload offset", offset);
> + switch (spist->byte_offset) {
> + case 0: /* command byte */
> + if ((byte & CMD_BYTE_WRITE) == 0) { /* bit-7 */
> + spist->spi_state = SPI_STATE_WRITE;
> + trace_tpm_tis_spi_transfer_event("spi write");
> + } else {
> + spist->spi_state = SPI_STATE_READ;
> + trace_tpm_tis_spi_transfer_event("spi read");
> + }
> + spist->xfer_size = (byte & CMD_BYTE_XFER_SZ_MASK) + 1;
> + trace_tpm_tis_spi_transfer_data("xfer_size", spist->xfer_size);
> + break;
> + case 1: /* 1st address byte */
> + if (byte != TIS_SPI_HIGH_ADDR_BYTE) {
> + qemu_log_mask(LOG_GUEST_ERROR, "incorrect high address 0x%x\n",
> + byte);
> + }
> + spist->reg_addr = byte << 16;
> + trace_tpm_tis_spi_transfer_data("first addr byte", byte);
> + trace_tpm_tis_spi_transfer_addr("reg_addr", spist->reg_addr);
> + break;
> + case 2: /* 2nd address byte */
> + spist->reg_addr |= byte << 8;
> + trace_tpm_tis_spi_transfer_data("second addr byte", byte);
> + trace_tpm_tis_spi_transfer_addr("reg_addr", spist->reg_addr);
> + break;
> + case 3: /* 3rd address byte */
> + spist->reg_addr |= byte;
> + trace_tpm_tis_spi_transfer_data("third addr byte", byte);
> + trace_tpm_tis_spi_transfer_addr("reg_addr", spist->reg_addr);
> + break;
> + default: /* data bytes */
> + if (spist->wait_state_cnt < 4) {
> + spist->wait_state_cnt++;
> + if (spist->wait_state_cnt == 4) {
> + trace_tpm_tis_spi_transfer_data("wait complete, count",
> + spist->wait_state_cnt);
> + rx = rx | (0x01 << (24 - offset * 8));
> + return rx;
> + } else {
> + trace_tpm_tis_spi_transfer_data("in wait state, count",
> + spist->wait_state_cnt);
> + rx = 0;
> + }
> + } else {
> + index = spist->byte_offset - 4;
> + trace_tpm_tis_spi_transfer_data("index", index);
> + trace_tpm_tis_spi_transfer_data("data byte", byte);
> + trace_tpm_tis_spi_transfer_addr("reg_addr", spist->reg_addr);
> + if (index >= spist->xfer_size) {
> + /*
> + * SPI SSI framework limits both rx and tx
> + * to fixed 4-byte with each xfer
> + */
> + trace_tpm_tis_spi_transfer_event("index exceeds xfer_size");
> + return rx;
> + }
> + tis_addr = spist->reg_addr + (index % 4);
> + if (spist->spi_state == SPI_STATE_WRITE) {
> + tpm_tis_spi_write(spist, tis_addr, byte);
> + } else {
> + byte = tpm_tis_spi_read(spist, tis_addr);
> + rx = rx | (byte << (24 - offset * 8));
> + trace_tpm_tis_spi_transfer_data("byte added to response",
> + byte);
> + trace_tpm_tis_spi_transfer_data("offset", offset);
> + }
> + }
> + break;
> + }
> + if ((spist->wait_state_cnt == 0) || (spist->wait_state_cnt == 4)) {
> + offset++;
> + spist->byte_offset++;
> + } else {
> + break;
> + }
> + }
> + return rx;
> +}
> +
> +static int tpm_cs(SSIPeripheral *ss, bool select)
> +{
> + TPMStateSPI *spist = TPM_TIS_SPI(ss);
> +
> + if (select) {
> + spist->command = false;
> + spist->spi_state = SPI_STATE_IDLE;
> + } else {
> + spist->command = true;
> + }
> + return 0;
> +}
> +
> +static void tpm_realize(SSIPeripheral *dev, Error **errp)
> +{
> + TPMStateSPI *spist = TPM_TIS_SPI(dev);
> + TPMState *s = &spist->tpm_state;
> +
> + if (!tpm_find()) {
> + error_setg(errp, "at most one TPM device is permitted");
> + return;
> + }
> +
> + s->be_driver = qemu_find_tpm_be("tpm0");
> +
> + if (!s->be_driver) {
> + error_setg(errp, "unable to find tpm backend device");
> + return;
> + }
> +}
> +
> +static void tpm_tis_spi_class_init(ObjectClass *klass, void *data)
> +{
> + DeviceClass *dc = DEVICE_CLASS(klass);
> + TPMIfClass *tc = TPM_IF_CLASS(klass);
> + SSIPeripheralClass *k = SSI_PERIPHERAL_CLASS(klass);
> +
> + k->transfer = tpm_transfer;
> + k->realize = tpm_realize;
> + k->set_cs = tpm_cs;
> + k->cs_polarity = SSI_CS_LOW;
> +
> + device_class_set_legacy_reset(dc, tpm_tis_spi_reset);
> + device_class_set_props(dc, tpm_tis_spi_properties);
> + set_bit(DEVICE_CATEGORY_MISC, dc->categories);
> +
> + dc->desc = "SPI TPM";
> + dc->vmsd = &vmstate_tpm_tis_spi;
> +
> + tc->model = TPM_MODEL_TPM_TIS;
> + tc->request_completed = tpm_tis_spi_request_completed;
> + tc->get_version = tpm_tis_spi_get_tpm_version;
> +}
> +
> +static const TypeInfo tpm_tis_spi_info = {
> + .name = TYPE_TPM_TIS_SPI,
> + .parent = TYPE_SSI_PERIPHERAL,
> + .instance_size = sizeof(TPMStateSPI),
> + .class_init = tpm_tis_spi_class_init,
> + .interfaces = (InterfaceInfo[]) {
> + { TYPE_TPM_IF },
> + { }
> + }
> +};
> +
> +static void tpm_tis_spi_register_types(void)
> +{
> + type_register_static(&tpm_tis_spi_info);
> +}
> +
> +type_init(tpm_tis_spi_register_types)
> diff --git a/hw/tpm/Kconfig b/hw/tpm/Kconfig
> index a46663288c..5951c225cc 100644
> --- a/hw/tpm/Kconfig
> +++ b/hw/tpm/Kconfig
> @@ -5,6 +5,12 @@ config TPM_TIS_I2C
> select I2C
> select TPM_TIS
>
> +config TPM_TIS_SPI
> + bool
> + depends on TPM
> + select TPM_BACKEND
> + 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 6968e60b3f..e03cfb11b9 100644
> --- a/hw/tpm/meson.build
> +++ b/hw/tpm/meson.build
> @@ -2,6 +2,7 @@ system_ss.add(when: 'CONFIG_TPM_TIS', if_true: files('tpm_tis_common.c'))
> system_ss.add(when: 'CONFIG_TPM_TIS_ISA', if_true: files('tpm_tis_isa.c'))
> system_ss.add(when: 'CONFIG_TPM_TIS_SYSBUS', if_true: files('tpm_tis_sysbus.c'))
> system_ss.add(when: 'CONFIG_TPM_TIS_I2C', if_true: files('tpm_tis_i2c.c'))
> +system_ss.add(when: 'CONFIG_TPM_TIS_SPI', if_true: files('tpm_tis_spi.c'))
> system_ss.add(when: 'CONFIG_TPM_CRB', if_true: files('tpm_crb.c'))
> system_ss.add(when: 'CONFIG_TPM_TIS', if_true: files('tpm_ppi.c'))
> system_ss.add(when: 'CONFIG_TPM_CRB', if_true: files('tpm_ppi.c'))
> diff --git a/hw/tpm/trace-events b/hw/tpm/trace-events
> index fa882dfefe..0324ceb6d0 100644
> --- a/hw/tpm/trace-events
> +++ b/hw/tpm/trace-events
> @@ -42,3 +42,10 @@ tpm_tis_i2c_recv(uint8_t data) "TPM I2C read: 0x%X"
> tpm_tis_i2c_send(uint8_t data) "TPM I2C write: 0x%X"
> tpm_tis_i2c_event(const char *event) "TPM I2C event: %s"
> tpm_tis_i2c_send_reg(const char *name, int reg) "TPM I2C write register: %s(0x%X)"
> +
> +# tpm_tis_spi.c
> +tpm_tis_spi_write(uint32_t addr, uint8_t val) "TPM SPI write - addr:0x%08X 0x%02x"
> +tpm_tis_spi_read(uint32_t addr, uint8_t val) "TPM SPI read - addr:0x%08X 0x%02x"
> +tpm_tis_spi_transfer_event(const char *event) "TPM SPI XFER event: %s"
> +tpm_tis_spi_transfer_data(const char *name, uint8_t val) "TPM SPI XFER: %s = 0x%02x"
> +tpm_tis_spi_transfer_addr(const char *name, uint32_t addr) "TPM SPI XFER: %s = 0x%08x"
The rest looks good to me.
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v4 3/3] tests/qtest/tpm: add unit test to tis-spi
2024-11-02 14:01 ` Stefan Berger
@ 2024-11-03 0:45 ` Stefan Berger
0 siblings, 0 replies; 9+ messages in thread
From: Stefan Berger @ 2024-11-03 0:45 UTC (permalink / raw)
To: dan tan, qemu-devel; +Cc: qemu-ppc, stefanb, pbonzini, farosas, lvivier, clg
On 11/2/24 10:01 AM, Stefan Berger wrote:
>
>
> On 11/1/24 4:27 PM, dan tan wrote:
>> Add qtest cases to exercise main TPM locality functionality
>
>
>
> When running `ninja -C build test` I get this output that should have
> its lines prefixed with '#' 'somehow':
'somehow' = add '-serial null' to the QEMU command line parameters
>
> 42/424 qemu:qtest+qtest-ppc64 / qtest-ppc64/tpm-tis-spi-pnv-test
> OK 0.31s 5 subtests passed
>
> stdout: 34: UNKNOWN: [ 0.024173382,5] OPAL v7.1 starting...
> stdout: 35: UNKNOWN: [ 0.024724739,7] initial console log level:
> memory 7, driver 5
> stdout: 36: UNKNOWN: [ 0.024778210,6] CPU: P10 generation processor
> (max 4 threads/core)
> stdout: 37: UNKNOWN: [ 0.024795001,7] CPU: Boot CPU PIR is 0x0000 PVR
> is 0x00801200
> stdout: 38: UNKNOWN: [ 0.024925108,7] OPAL table: 0x3018a960 ..
> 0x3018af00, branch table: 0x30120000
> stdout: 39: UNKNOWN: [ 0.025103930,7] Assigning physical memory map
> table for p10
> stdout: 40: UNKNOWN: [ 0.025411575,7] FDT: Parsing fdt @0x1000000
> stdout: 41: UNKNOWN: [ 0.028505988,5] CHIP: Detected QEMU simulator
> stdout: 42: UNKNOWN: [ 0.028683295,6] CHIP: Initialised chip 0 from
> xscom@603fc00000000
> stdout: 43: UNKNOWN: [ 0.029247139,6] P10 DD2.00 detected
> stdout: 44: UNKNOWN: [ 0.029267261,5] CHIP: Chip ID 0000 type: P10
> DD2.00
> stdout: 45: UNKNOWN: [ 0.029276249,7] XSCOM: Base address:
> 0x603fc00000000
> stdout: 46: UNKNOWN: [ 0.029339220,7] XSTOP: ibm,sw-checkstop-fir
> prop not found
> stdout: 47: UNKNOWN: [ 0.029409300,6] MFSI 0:0: Initialized
> stdout: 48: UNKNOWN: [ 0.029419734,6] MFSI 0:2: Initialized
> stdout: 49: UNKNOWN: [ 0.029429173,6] MFSI 0:1: Initialized
> stdout: 50: UNKNOWN: [ 0.030012352,6] LPC: LPC[000]: Initialized
> stdout: 51: UNKNOWN: [ 0.030020186,7] LPC: access via MMIO
> @0x6030000000000
> stdout: 52: UNKNOWN: [ 0.030108378,7] LPC: Default bus on chip 0x0
> stdout: 53: UNKNOWN: [ 0.030193313,7] CPU: New max PIR set to 0x7
> stdout: 54: UNKNOWN: [ 0.030743076,7] MEM: parsing reserved memory
> from reserved-names/-ranges properties
> stdout: 55: UNKNOWN: [ 0.030828672,7] HOMER: Init chip 0
> stdout: 56: UNKNOWN: [ 0.030862569,7] PBA BAR0 : 0x0000300ffd800000
> stdout: 57: UNKNOWN: [ 0.030870164,7] PBA MASK0: 0x0000000000300000
> stdout: 58: UNKNOWN: [ 0.030899260,7] HOMER Image at 0x300ffd800000
> size 4MB
> stdout: 59: UNKNOWN: [ 0.030943460,4] HOMER image is not reserved!
> Reserving
> stdout: 60: UNKNOWN: [ 0.031114747,7] PBA BAR2 : 0x0000300fff800000
> stdout: 61: UNKNOWN: [ 0.031121980,7] PBA MASK2: 0x0000000000700000
> stdout: 62: UNKNOWN: [ 0.031136886,7] OCC Common Area at
> 0x300fff800000 size 8MB
> stdout: 63: UNKNOWN: [ 0.031171139,4] OCC common area is not
> reserved! Reserving
> stdout: 64: UNKNOWN: [ 0.031231035,7] CPU: decrementer bits 56
> stdout: 65: UNKNOWN: [ 0.031321664,6] CPU: CPU from DT PIR=0x0000
> Server#=0x0 State=4
> stdout: 66: UNKNOWN: [ 0.031427205,6] CPU: 1 secondary threads
> stdout: 67: UNKNOWN: [ 0.031441159,6] CPU: CPU from DT PIR=0x0004
> Server#=0x4 State=4
> stdout: 68: UNKNOWN: [ 0.031459259,6] CPU: 1 secondary threads
> stdout: 69: UNKNOWN: [ 0.033492258,5] PLAT: Using SuperIO UART
> stdout: 70: UNKNOWN: [ 0.033826231,7] UART: Using LPC IRQ 4
> stdout: 71: UNKNOWN: [ 0.036351187,5] PLAT: Detected QEMU POWER10
> platform
> stdout: 72: UNKNOWN: [ 0.036415869,5] PLAT: Detected BMC platform
> ast2500:openbmc
> stdout: 73: UNKNOWN: [ 0.037499517,5] XIVE: [ IC 00 ] Initializing
> XIVE block ID 0...
> ERROR: Unknown TAP output lines for a supported TAP version.
> This is probably a bug in the test; if they are not TAP syntax, prefix
> them with a #
>
>
>
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v4 1/3] tpm/tpm_tis_spi: Support TPM for SPI (Serial Peripheral Interface)
2024-11-02 14:19 ` Stefan Berger
@ 2024-11-04 2:17 ` dan tan
0 siblings, 0 replies; 9+ messages in thread
From: dan tan @ 2024-11-04 2:17 UTC (permalink / raw)
On 2024-11-02 09:19, Stefan Berger wrote:
> On 11/1/24 4:27 PM, dan tan wrote:
>> Implement support for TPM via SPI interface. The SPI bus master
>> is provided by PowerNV SPI device which is an SSI peripheral.
>> It can uses the tpm_emulator driver backend with the external
>> swtpm.
>>
>> Although the implementation is endian neutral, the SPI
>> bus master provider, pnv_spi.c is only supported on the
>> PowerNV platform, thus, is big endian specific.
>>
>
> Did you try running this with Linux? Did the Linux TPM driver work?
>
I am assuming that you mean, if I boot Linux on this PowerNV
virtual machine, will this TPM device interact with the Linux
TPM driver just like a hardware tpm device. The answer is I don't
know.
Our hypervisor has extensive interactions with the TPM device during
its secure boot IPL. I have the trace enabled on "tpm_*". After Linux
takes over, I don't see any access request in the QEMU trace, however,
the following appears on the Linux console:
[ 0.884411] Key type encrypted registered^M
[ 0.884570] Secure boot mode disabled^M
[ 0.884678] ima: No TPM chip found, activating TPM-bypass!^M
I am not sure if "No TPM chip found" is the result of the partition
firmare not presenting it to the operating system, or there is other
reason. There is no error or failure in the earlier hypervisor exchange
and there is no request directed at the TPM device prior to the chip
not found message. If this is important, I will spend sometime to
chase it down.
>> tpm/tpm_tis_spi: Support TPM for SPI (rev 4)
>
> Remove this. It seems to be from your patch merge.
>
done
>>
>> - moved variable tis_addr from TPMStateSPI struct to local
>> - added the VM suspend/resume support:
>> - added vmstate_tpm_tis_spi declaration
>> - added tpm_tis_spi_pre_save() function
>> - fixed trace formatting string
>
> Move this below your Signed-off-by after the dashes '---'
>
>>
>> Signed-off-by: dan tan <dantan@linux.vnet.ibm.com>
>
> ---
>
> v3:
> - moved variable tis_addr from TPMStateSPI struct to local
> - added the VM suspend/resume support:
> - added vmstate_tpm_tis_spi declaration
> - added tpm_tis_spi_pre_save() function
> - fixed trace formatting string
>
done
>> ---
>> docs/specs/tpm.rst | 15 ++
>> include/sysemu/tpm.h | 3 +
>> hw/tpm/tpm_tis_spi.c | 360
>> +++++++++++++++++++++++++++++++++++++++++++
>> hw/tpm/Kconfig | 6 +
>> hw/tpm/meson.build | 1 +
>> hw/tpm/trace-events | 7 +
>> 6 files changed, 392 insertions(+)
>> create mode 100644 hw/tpm/tpm_tis_spi.c
>>
>> diff --git a/docs/specs/tpm.rst b/docs/specs/tpm.rst
>> index 1ad36ad709..63c3a43c7d 100644
>> --- a/docs/specs/tpm.rst
>> +++ b/docs/specs/tpm.rst
>> @@ -24,6 +24,7 @@ QEMU files related to TPM TIS interface:
>> - ``hw/tpm/tpm_tis_isa.c``
>> - ``hw/tpm/tpm_tis_sysbus.c``
>> - ``hw/tpm/tpm_tis_i2c.c``
>> + - ``hw/tpm/tpm_tis_spi.c``
>> - ``hw/tpm/tpm_tis.h``
>> Both an ISA device and a sysbus device are available. The former
>> is
>> @@ -33,6 +34,9 @@ Arm virt machine.
>> An I2C device support is also provided which can be instantiated in
>> the Arm
>> based emulation machines. This device only supports the TPM 2
>> protocol.
>> +A Serial Peripheral Interface (SPI) device support has been added
>> to the
>> +PowerNV emulation machines. This device only supports the TPM 2
>> protocol.
>> +
>> CRB interface
>> -------------
>> @@ -371,6 +375,17 @@ attached to I2C bus, use the following command
>> line:
>> echo tpm_tis_i2c 0x2e > /sys/bus/i2c/devices/i2c-12/new_device
>> +In case a PowerNV machine is emulated and you want to use a TPM
>> device
>> +attached to SPI bus, use the following command line (SPI bus master
>> is
>> +provided by PowerNV SPI device):
>> +
>> +.. code-block:: console
>> +
>> + qemu-system-ppc64 -m 2G -machine powernv10 -nographic \
>> + -chardev socket,id=chrtpm,path=/tmp/mytpm1/swtpm-sock \
>> + -tpmdev emulator,id=tpm0,chardev=chrtpm \
>> + -device tpm-tis-spi,tpmdev=tpm0,bus=pnv-spi-bus.4
>> +
>
>
> If you move this a bit further up to after the 'pSeries machine' then
> we have the ppc64 stuff in one place.
>
done
>> In case SeaBIOS is used as firmware, it should show the TPM menu
>> item
>> after entering the menu with 'ESC'.
>> diff --git a/include/sysemu/tpm.h b/include/sysemu/tpm.h
>> index 1ee568b3b6..22b05110e2 100644
>> --- a/include/sysemu/tpm.h
>> +++ b/include/sysemu/tpm.h
>> @@ -49,6 +49,7 @@ struct TPMIfClass {
>> #define TYPE_TPM_CRB "tpm-crb"
>> #define TYPE_TPM_SPAPR "tpm-spapr"
>> #define TYPE_TPM_TIS_I2C "tpm-tis-i2c"
>> +#define TYPE_TPM_TIS_SPI "tpm-tis-spi"
>> #define TPM_IS_TIS_ISA(chr) \
>> object_dynamic_cast(OBJECT(chr), TYPE_TPM_TIS_ISA)
>> @@ -60,6 +61,8 @@ struct TPMIfClass {
>> object_dynamic_cast(OBJECT(chr), TYPE_TPM_SPAPR)
>> #define TPM_IS_TIS_I2C(chr) \
>> object_dynamic_cast(OBJECT(chr), TYPE_TPM_TIS_I2C)
>> +#define TPM_IS_TIS_SPI(chr) \
>> + object_dynamic_cast(OBJECT(chr), TYPE_TPM_TIS_SPI)
>> /* returns NULL unless there is exactly one TPM device */
>> static inline TPMIf *tpm_find(void)
>> diff --git a/hw/tpm/tpm_tis_spi.c b/hw/tpm/tpm_tis_spi.c
>> new file mode 100644
>> index 0000000000..4aee1c6d6c
>> --- /dev/null
>> +++ b/hw/tpm/tpm_tis_spi.c
>> @@ -0,0 +1,360 @@
>> +/*
>> + * QEMU PowerPC SPI TPM 2.0 model
>> + *
>> + * Copyright (c) 2024, IBM Corporation.
>> + *
>> + * SPDX-License-Identifier: GPL-2.0-or-later
>> + */
>> +
>> +#define IBM_PONQ
>> +
>> +#include "qemu/osdep.h"
>> +#include "qemu/log.h"
>> +#include "hw/sysbus.h"
>> +#include "hw/acpi/tpm.h"
>> +#include "tpm_prop.h"
>> +#include "qemu/log.h"
>> +#include "trace.h"
>> +#include "tpm_tis.h"
>> +#include "hw/ssi/ssi.h"
>> +#include "migration/vmstate.h"
>> +
>> +typedef struct TPMStateSPI {
>> + /*< private >*/
>> + SSIPeripheral parent_object;
>> +
>> + uint8_t byte_offset; /* byte offset in transfer */
>> + uint8_t wait_state_cnt; /* wait state counter */
>> + uint8_t xfer_size; /* data size of transfer */
>> + uint32_t reg_addr; /* register address of transfer */
>> +
>> + uint8_t spi_state; /* READ / WRITE / IDLE */
>> +#define SPI_STATE_IDLE 0
>> +#define SPI_STATE_WRITE 1
>> +#define SPI_STATE_READ 2
>> +
>> + bool command;
>> +
>> + /*< public >*/
>> + TPMState tpm_state; /* not a QOM object */
>> +
>> +} TPMStateSPI;
>> +
>> +#define CMD_BYTE_WRITE (1 << 7)
>> +#define CMD_BYTE_XFER_SZ_MASK 0x1f
>> +#define TIS_SPI_HIGH_ADDR_BYTE 0xd4
>> +
>> +DECLARE_INSTANCE_CHECKER(TPMStateSPI, TPM_TIS_SPI, TYPE_TPM_TIS_SPI)
>> +
>> +static int tpm_tis_spi_pre_save(void *opaque)
>> +{
>> + TPMStateSPI *spist = opaque;
>> +
>> + return tpm_tis_pre_save(&spist->tpm_state);
>> +}
>> +
>> +static const VMStateDescription vmstate_tpm_tis_spi = {
>> + .name = "tpm-tis-spi",
>> + .version_id = 0,
>> + .pre_save = tpm_tis_spi_pre_save,
>> + .fields = (const VMStateField[]) {
>> + VMSTATE_BUFFER(tpm_state.buffer, TPMStateSPI),
>> + VMSTATE_UINT16(tpm_state.rw_offset, TPMStateSPI),
>> + VMSTATE_UINT8(tpm_state.active_locty, TPMStateSPI),
>> + VMSTATE_UINT8(tpm_state.aborting_locty, TPMStateSPI),
>> + VMSTATE_UINT8(tpm_state.next_locty, TPMStateSPI),
>> +
>> + VMSTATE_STRUCT_ARRAY(tpm_state.loc, TPMStateSPI,
>> + TPM_TIS_NUM_LOCALITIES, 0,
>> + vmstate_locty, TPMLocality),
>> +
>> + /* spi specifics */
>> + VMSTATE_UINT8(byte_offset, TPMStateSPI),
>> + VMSTATE_UINT8(wait_state_cnt, TPMStateSPI),
>> + VMSTATE_UINT8(xfer_size, TPMStateSPI),
>> + VMSTATE_UINT32(reg_addr, TPMStateSPI),
>> + VMSTATE_UINT8(spi_state, TPMStateSPI),
>> + VMSTATE_BOOL(command, TPMStateSPI),
>> +
>> + VMSTATE_END_OF_LIST()
>> + }
>> +};
>> +
>> +static inline void tpm_tis_spi_clear_data(TPMStateSPI *spist)
>> +{
>> + spist->spi_state = SPI_STATE_IDLE;
>> + spist->byte_offset = 0;
>> + spist->wait_state_cnt = 0;
>> + spist->xfer_size = 0;
>> + spist->reg_addr = 0;
>> +
>> + return;
>> +}
>> +
>> +/* Callback from TPM to indicate that response is copied */
>> +static void tpm_tis_spi_request_completed(TPMIf *ti, int ret)
>> +{
>> + TPMStateSPI *spist = TPM_TIS_SPI(ti);
>> + TPMState *s = &spist->tpm_state;
>> +
>> + /* Inform the common code. */
>> + tpm_tis_request_completed(s, ret);
>> +}
>> +
>> +static enum TPMVersion tpm_tis_spi_get_tpm_version(TPMIf *ti)
>> +{
>> + TPMStateSPI *spist = TPM_TIS_SPI(ti);
>> + TPMState *s = &spist->tpm_state;
>> +
>> + return tpm_tis_get_tpm_version(s);
>> +}
>> +
>> +/*
>> + * TCG PC Client Platform TPM Profile Specification for TPM 2.0 ver
>> 1.05 rev 14
>> + *
>> + * For system Software, the TPM has a 64-bit address of
>> 0x0000_0000_FED4_xxxx.
>> + * On SPI, the chipset passes the least significant 24 bits to the
>> TPM.
>> + * The upper bytes will be used by the chipset to select the TPM’s
>> SPI CS#
>> + * signal. Table 9 shows the locality based on the 16 least
>> significant address
>> + * bits and assume that either the LPC TPM sync or SPI TPM CS# is
>> used.
>> + *
>> + */
>> +static void tpm_tis_spi_write(TPMStateSPI *spist, uint32_t addr,
>> uint8_t val)
>> +{
>> + TPMState *tpm_st = &spist->tpm_state;
>> +
>> + trace_tpm_tis_spi_write(addr, val);
>> + tpm_tis_write_data(tpm_st, addr, val, 1);
>> +}
>> +
>> +static uint8_t tpm_tis_spi_read(TPMStateSPI *spist, uint32_t addr)
>> +{
>> + TPMState *tpm_st = &spist->tpm_state;
>> + uint8_t data;
>> +
>> + data = tpm_tis_read_data(tpm_st, addr, 1);
>> + trace_tpm_tis_spi_read(addr, data);
>> + return data;
>> +}
>> +
>> +static Property tpm_tis_spi_properties[] = {
>> + DEFINE_PROP_TPMBE("tpmdev", TPMStateSPI, tpm_state.be_driver),
>> + DEFINE_PROP_UINT32("irq", TPMStateSPI, tpm_state.irq_num, 0),
>
> Please remove this line.
>
done
>> + DEFINE_PROP_END_OF_LIST(),
>> +};
>> +
>> +static void tpm_tis_spi_reset(DeviceState *dev)
>> +{
>> + TPMStateSPI *spist = TPM_TIS_SPI(dev);
>> + TPMState *s = &spist->tpm_state;
>> +
>> + tpm_tis_spi_clear_data(spist);
>> + return tpm_tis_reset(s);
>> +}
>> +
>> +static uint32_t tpm_transfer(SSIPeripheral *ss, uint32_t tx)
>> +{
>> + TPMStateSPI *spist = TPM_TIS_SPI(ss);
>> + uint32_t rx = 0;
>> + uint8_t byte; /* reversed byte value */
>> + uint8_t offset = 0; /* offset of byte in payload */
>> + uint8_t index; /* index of data byte in transfer */
>> + uint32_t tis_addr; /* tis address including locty */
>> +
>> + /* new transfer or not */
>> + if (spist->command) { /* new transfer start */
>> + if (spist->spi_state != SPI_STATE_IDLE) {
>> + qemu_log_mask(LOG_GUEST_ERROR, "unexpected new
>> transfer\n");
>> + }
>> + spist->byte_offset = 0;
>> + spist->wait_state_cnt = 0;
>> + }
>> + /*
>> + * Explanation of wait_state:
>> + * The original TPM model did not have wait state or "flow
>> control" support
>> + * built in. If you wanted to read a TPM register through SPI
>> you sent
>> + * the first byte with the read/write bit and size, then three
>> address bytes
>> + * and any additional bytes after that were don't care bytes for
>> reads and
>> + * the model would begin returning byte data to the SPI reader
>> from the
>> + * register address provided. In the real world this would mean
>> that a
>> + * TPM device had only the time between the 31st clock and the
>> 32nd clock
>> + * to fetch the register data that it had to provide to SPI MISO
>> starting
>> + * with the 32nd clock.
>> + *
>> + * In reality the TPM begins introducing a wait state at the 31st
>> clock
>> + * by holding MISO low. This is how it controls the "flow" of
>> the
>> + * operation. Once the data the TPM needs to return is ready it
>> will
>> + * select bit 31 + (8*N) to send back a 1 which indicates that it
>> will
>> + * now start returning data on MISO.
>> + *
>> + * The same wait states are applied to writes. In either the
>> read or write
>> + * case the wait state occurs between the command+address (4
>> bytes) and the
>> + * data (1-n bytes) sections of the SPI frame. The code below
>> introduces
>> + * the support for a 32 bit wait state for P10. All reads and
>> writes
>> + * through the SPI interface MUST now be aware of the need to do
>> flow
>> + * control in order to use the TPM via SPI.
>> + *
>> + * In conjunction with these changes there were changes made to
>> the SPIM
>> + * engine that was introduced in P10 to support the 6x op code
>> which is
>> + * used to receive wait state 0s on the MISO line until it sees
>> the b'1'
>> + * come back before continuing to read real data from the SPI
>> device(TPM).
>> + */
>> +
>> + trace_tpm_tis_spi_transfer_data("Payload byte_offset",
>> spist->byte_offset);
>> + /* process payload data */
>> + while (offset < 4) {
>> + spist->command = false;
>> + byte = (tx >> (24 - 8 * offset)) & 0xFF;
>> + trace_tpm_tis_spi_transfer_data("Extracted byte", byte);
>> + trace_tpm_tis_spi_transfer_data("Payload offset", offset);
>> + switch (spist->byte_offset) {
>> + case 0: /* command byte */
>> + if ((byte & CMD_BYTE_WRITE) == 0) { /* bit-7 */
>> + spist->spi_state = SPI_STATE_WRITE;
>> + trace_tpm_tis_spi_transfer_event("spi write");
>> + } else {
>> + spist->spi_state = SPI_STATE_READ;
>> + trace_tpm_tis_spi_transfer_event("spi read");
>> + }
>> + spist->xfer_size = (byte & CMD_BYTE_XFER_SZ_MASK) + 1;
>> + trace_tpm_tis_spi_transfer_data("xfer_size",
>> spist->xfer_size);
>> + break;
>> + case 1: /* 1st address byte */
>> + if (byte != TIS_SPI_HIGH_ADDR_BYTE) {
>> + qemu_log_mask(LOG_GUEST_ERROR, "incorrect high
>> address 0x%x\n",
>> + byte);
>> + }
>> + spist->reg_addr = byte << 16;
>> + trace_tpm_tis_spi_transfer_data("first addr byte", byte);
>> + trace_tpm_tis_spi_transfer_addr("reg_addr",
>> spist->reg_addr);
>> + break;
>> + case 2: /* 2nd address byte */
>> + spist->reg_addr |= byte << 8;
>> + trace_tpm_tis_spi_transfer_data("second addr byte",
>> byte);
>> + trace_tpm_tis_spi_transfer_addr("reg_addr",
>> spist->reg_addr);
>> + break;
>> + case 3: /* 3rd address byte */
>> + spist->reg_addr |= byte;
>> + trace_tpm_tis_spi_transfer_data("third addr byte", byte);
>> + trace_tpm_tis_spi_transfer_addr("reg_addr",
>> spist->reg_addr);
>> + break;
>> + default: /* data bytes */
>> + if (spist->wait_state_cnt < 4) {
>> + spist->wait_state_cnt++;
>> + if (spist->wait_state_cnt == 4) {
>> + trace_tpm_tis_spi_transfer_data("wait complete,
>> count",
>> +
>> spist->wait_state_cnt);
>> + rx = rx | (0x01 << (24 - offset * 8));
>> + return rx;
>> + } else {
>> + trace_tpm_tis_spi_transfer_data("in wait state,
>> count",
>> +
>> spist->wait_state_cnt);
>> + rx = 0;
>> + }
>> + } else {
>> + index = spist->byte_offset - 4;
>> + trace_tpm_tis_spi_transfer_data("index", index);
>> + trace_tpm_tis_spi_transfer_data("data byte", byte);
>> + trace_tpm_tis_spi_transfer_addr("reg_addr",
>> spist->reg_addr);
>> + if (index >= spist->xfer_size) {
>> + /*
>> + * SPI SSI framework limits both rx and tx
>> + * to fixed 4-byte with each xfer
>> + */
>> + trace_tpm_tis_spi_transfer_event("index exceeds
>> xfer_size");
>> + return rx;
>> + }
>> + tis_addr = spist->reg_addr + (index % 4);
>> + if (spist->spi_state == SPI_STATE_WRITE) {
>> + tpm_tis_spi_write(spist, tis_addr, byte);
>> + } else {
>> + byte = tpm_tis_spi_read(spist, tis_addr);
>> + rx = rx | (byte << (24 - offset * 8));
>> + trace_tpm_tis_spi_transfer_data("byte added to
>> response",
>> + byte);
>> + trace_tpm_tis_spi_transfer_data("offset",
>> offset);
>> + }
>> + }
>> + break;
>> + }
>> + if ((spist->wait_state_cnt == 0) || (spist->wait_state_cnt ==
>> 4)) {
>> + offset++;
>> + spist->byte_offset++;
>> + } else {
>> + break;
>> + }
>> + }
>> + return rx;
>> +}
>> +
>> +static int tpm_cs(SSIPeripheral *ss, bool select)
>> +{
>> + TPMStateSPI *spist = TPM_TIS_SPI(ss);
>> +
>> + if (select) {
>> + spist->command = false;
>> + spist->spi_state = SPI_STATE_IDLE;
>> + } else {
>> + spist->command = true;
>> + }
>> + return 0;
>> +}
>> +
>> +static void tpm_realize(SSIPeripheral *dev, Error **errp)
>> +{
>> + TPMStateSPI *spist = TPM_TIS_SPI(dev);
>> + TPMState *s = &spist->tpm_state;
>> +
>> + if (!tpm_find()) {
>> + error_setg(errp, "at most one TPM device is permitted");
>> + return;
>> + }
>> +
>> + s->be_driver = qemu_find_tpm_be("tpm0");
>> +
>> + if (!s->be_driver) {
>> + error_setg(errp, "unable to find tpm backend device");
>> + return;
>> + }
>> +}
>> +
>> +static void tpm_tis_spi_class_init(ObjectClass *klass, void *data)
>> +{
>> + DeviceClass *dc = DEVICE_CLASS(klass);
>> + TPMIfClass *tc = TPM_IF_CLASS(klass);
>> + SSIPeripheralClass *k = SSI_PERIPHERAL_CLASS(klass);
>> +
>> + k->transfer = tpm_transfer;
>> + k->realize = tpm_realize;
>> + k->set_cs = tpm_cs;
>> + k->cs_polarity = SSI_CS_LOW;
>> +
>> + device_class_set_legacy_reset(dc, tpm_tis_spi_reset);
>> + device_class_set_props(dc, tpm_tis_spi_properties);
>> + set_bit(DEVICE_CATEGORY_MISC, dc->categories);
>> +
>> + dc->desc = "SPI TPM";
>> + dc->vmsd = &vmstate_tpm_tis_spi;
>> +
>> + tc->model = TPM_MODEL_TPM_TIS;
>> + tc->request_completed = tpm_tis_spi_request_completed;
>> + tc->get_version = tpm_tis_spi_get_tpm_version;
>> +}
>> +
>> +static const TypeInfo tpm_tis_spi_info = {
>> + .name = TYPE_TPM_TIS_SPI,
>> + .parent = TYPE_SSI_PERIPHERAL,
>> + .instance_size = sizeof(TPMStateSPI),
>> + .class_init = tpm_tis_spi_class_init,
>> + .interfaces = (InterfaceInfo[]) {
>> + { TYPE_TPM_IF },
>> + { }
>> + }
>> +};
>> +
>> +static void tpm_tis_spi_register_types(void)
>> +{
>> + type_register_static(&tpm_tis_spi_info);
>> +}
>> +
>> +type_init(tpm_tis_spi_register_types)
>> diff --git a/hw/tpm/Kconfig b/hw/tpm/Kconfig
>> index a46663288c..5951c225cc 100644
>> --- a/hw/tpm/Kconfig
>> +++ b/hw/tpm/Kconfig
>> @@ -5,6 +5,12 @@ config TPM_TIS_I2C
>> select I2C
>> select TPM_TIS
>> +config TPM_TIS_SPI
>> + bool
>> + depends on TPM
>> + select TPM_BACKEND
>> + 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 6968e60b3f..e03cfb11b9 100644
>> --- a/hw/tpm/meson.build
>> +++ b/hw/tpm/meson.build
>> @@ -2,6 +2,7 @@ system_ss.add(when: 'CONFIG_TPM_TIS', if_true:
>> files('tpm_tis_common.c'))
>> system_ss.add(when: 'CONFIG_TPM_TIS_ISA', if_true:
>> files('tpm_tis_isa.c'))
>> system_ss.add(when: 'CONFIG_TPM_TIS_SYSBUS', if_true:
>> files('tpm_tis_sysbus.c'))
>> system_ss.add(when: 'CONFIG_TPM_TIS_I2C', if_true:
>> files('tpm_tis_i2c.c'))
>> +system_ss.add(when: 'CONFIG_TPM_TIS_SPI', if_true:
>> files('tpm_tis_spi.c'))
>> system_ss.add(when: 'CONFIG_TPM_CRB', if_true: files('tpm_crb.c'))
>> system_ss.add(when: 'CONFIG_TPM_TIS', if_true: files('tpm_ppi.c'))
>> system_ss.add(when: 'CONFIG_TPM_CRB', if_true: files('tpm_ppi.c'))
>> diff --git a/hw/tpm/trace-events b/hw/tpm/trace-events
>> index fa882dfefe..0324ceb6d0 100644
>> --- a/hw/tpm/trace-events
>> +++ b/hw/tpm/trace-events
>> @@ -42,3 +42,10 @@ tpm_tis_i2c_recv(uint8_t data) "TPM I2C read: 0x%X"
>> tpm_tis_i2c_send(uint8_t data) "TPM I2C write: 0x%X"
>> tpm_tis_i2c_event(const char *event) "TPM I2C event: %s"
>> tpm_tis_i2c_send_reg(const char *name, int reg) "TPM I2C write
>> register: %s(0x%X)"
>> +
>> +# tpm_tis_spi.c
>> +tpm_tis_spi_write(uint32_t addr, uint8_t val) "TPM SPI write -
>> addr:0x%08X 0x%02x"
>> +tpm_tis_spi_read(uint32_t addr, uint8_t val) "TPM SPI read -
>> addr:0x%08X 0x%02x"
>> +tpm_tis_spi_transfer_event(const char *event) "TPM SPI XFER event:
>> %s"
>> +tpm_tis_spi_transfer_data(const char *name, uint8_t val) "TPM SPI
>> XFER: %s = 0x%02x"
>> +tpm_tis_spi_transfer_addr(const char *name, uint32_t addr) "TPM SPI
>> XFER: %s = 0x%08x"
>
>
> The rest looks good to me.
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2024-11-04 2:18 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-11-01 20:27 [PATCH v4 0/3] TPM TIS SPI Support dan tan
2024-11-01 20:27 ` [PATCH v4 1/3] tpm/tpm_tis_spi: Support TPM for SPI (Serial Peripheral Interface) dan tan
2024-11-02 14:19 ` Stefan Berger
2024-11-04 2:17 ` dan tan
2024-11-01 20:27 ` [PATCH v4 2/3] tpm/tpm_tis_spi: activation for the PowerNV machines dan tan
2024-11-02 12:55 ` Stefan Berger
2024-11-01 20:27 ` [PATCH v4 3/3] tests/qtest/tpm: add unit test to tis-spi dan tan
2024-11-02 14:01 ` Stefan Berger
2024-11-03 0:45 ` Stefan Berger
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).