diff mbox series

[v2,1/3] tpm/tpm_tis_spi: Support TPM for SPI (Serial Peripheral Interface)

Message ID 20241025201247.29574-2-dantan@linux.vnet.ibm.com (mailing list archive)
State New
Headers show
Series *** TPM TIS SPI pull request *** | expand

Commit Message

dan tan Oct. 25, 2024, 8:12 p.m. UTC
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.

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 | 328 +++++++++++++++++++++++++++++++++++++++++++
 hw/tpm/Kconfig       |   6 +
 hw/tpm/meson.build   |   1 +
 hw/tpm/trace-events  |   7 +
 6 files changed, 360 insertions(+)
 create mode 100644 hw/tpm/tpm_tis_spi.c

Comments

Stefan Berger Oct. 27, 2024, 1:47 a.m. UTC | #1
On 10/25/24 4:12 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.
> 
> 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 | 328 +++++++++++++++++++++++++++++++++++++++++++
>   hw/tpm/Kconfig       |   6 +
>   hw/tpm/meson.build   |   1 +
>   hw/tpm/trace-events  |   7 +
>   6 files changed, 360 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..5aacae8467
> --- /dev/null
> +++ b/hw/tpm/tpm_tis_spi.c
> @@ -0,0 +1,328 @@
> +/*
> + * 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"
> +
> +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 */
> +    uint32_t    tis_addr;        /* tis address including locty */

This should not be here but be a local variable in tpm_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 inline void tpm_tis_spi_clear_data(TPMStateSPI *spist)
> +{
> +    spist->spi_state = 0;

SPIST->Spi_state = SPI_STATE_IDLE;

> +    spist->byte_offset = 0;
> +    spist->wait_state_cnt = 0;
> +    spist->xfer_size = 0;
> +    spist->reg_addr = 0;
> +    spist->tis_addr = 0xffffffff;
remove

> +
> +    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 */
> +
> +    /* 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;
> +                }
> +                spist->tis_addr = spist->reg_addr + (index % 4);
> +                if (spist->spi_state == SPI_STATE_WRITE) {
> +                    tpm_tis_spi_write(spist, spist->tis_addr, byte);
> +                } else {
> +                    byte = tpm_tis_spi_read(spist, 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;
> +
> +    spist->command = true;
> +    spist->spi_state = SPI_STATE_IDLE;

These two are unnecessary since reset will be called after realize.

> +
> +    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 = "PowerNV SPI TPM";

I still think this is more generic than for only PowerNV. So I would 
just say "SPI TPM" here.

What's missing here and in this file is the VM suspend/resume support. 
Just copy this here from i2c and adjust:

static int tpm_tis_i2c_pre_save(void *opaque)
{
     TPMStateI2C *i2cst = opaque;

     return tpm_tis_pre_save(&i2cst->state);
}

static const VMStateDescription vmstate_tpm_tis_i2c = {
     .name = "tpm-tis-i2c",
     .version_id = 0,
     .pre_save  = tpm_tis_i2c_pre_save,
     .fields = (const VMStateField[]) {
         VMSTATE_BUFFER(state.buffer, TPMStateI2C),
         VMSTATE_UINT16(state.rw_offset, TPMStateI2C),
         VMSTATE_UINT8(state.active_locty, TPMStateI2C),
         VMSTATE_UINT8(state.aborting_locty, TPMStateI2C),
         VMSTATE_UINT8(state.next_locty, TPMStateI2C),

         VMSTATE_STRUCT_ARRAY(state.loc, TPMStateI2C, 
TPM_TIS_NUM_LOCALITIES, 0,
                              vmstate_locty, TPMLocality),

         /* spi specifics */
I think all of the variables from TPMStateSPI have to appear here:
         - byte_offset
         - wait_state_cnt
         - xfer_size
         - reg_addr
         - spi_state
         - command

         VMSTATE_END_OF_LIST()
     }
};



> +
> +    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..4b39be13e2 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%X 0x%x"

-> 0x%08X 0x%02x

> +tpm_tis_spi_read(uint32_t addr, uint8_t val) "TPM SPI read - addr:0x%X 0x%x"
-> 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%x"

-> 0x%02x

> +tpm_tis_spi_transfer_addr(const char *name, uint8_t val) "TPM SPI XFER: %s = 0x%08x"

val is only uint8_t but should also be 'uint32_t addr'
diff mbox series

Patch

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..5aacae8467
--- /dev/null
+++ b/hw/tpm/tpm_tis_spi.c
@@ -0,0 +1,328 @@ 
+/*
+ * 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"
+
+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 */
+    uint32_t    tis_addr;        /* tis address including locty */
+
+    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 inline void tpm_tis_spi_clear_data(TPMStateSPI *spist)
+{
+    spist->spi_state = 0;
+    spist->byte_offset = 0;
+    spist->wait_state_cnt = 0;
+    spist->xfer_size = 0;
+    spist->reg_addr = 0;
+    spist->tis_addr = 0xffffffff;
+
+    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 */
+
+    /* 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;
+                }
+                spist->tis_addr = spist->reg_addr + (index % 4);
+                if (spist->spi_state == SPI_STATE_WRITE) {
+                    tpm_tis_spi_write(spist, spist->tis_addr, byte);
+                } else {
+                    byte = tpm_tis_spi_read(spist, 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;
+
+    spist->command = true;
+    spist->spi_state = SPI_STATE_IDLE;
+
+    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 = "PowerNV SPI TPM";
+
+    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..4b39be13e2 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%X 0x%x"
+tpm_tis_spi_read(uint32_t addr, uint8_t val) "TPM SPI read - addr:0x%X 0x%x"
+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%x"
+tpm_tis_spi_transfer_addr(const char *name, uint8_t val) "TPM SPI XFER: %s = 0x%08x"