diff mbox series

[v4,3/3] tests/qtest/tpm: add unit test to tis-spi

Message ID 20241101202727.9023-4-dantan@linux.vnet.ibm.com (mailing list archive)
State New
Headers show
Series TPM TIS SPI Support | expand

Commit Message

dan tan Nov. 1, 2024, 8:27 p.m. UTC
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

Comments

Stefan Berger Nov. 2, 2024, 2:01 p.m. UTC | #1
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 #
Stefan Berger Nov. 3, 2024, 12:45 a.m. UTC | #2
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 #
> 
> 
>
diff mbox series

Patch

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'),