From: Aidan Garske <aidan@wolfssl.com>
To: u-boot@lists.denx.de
Cc: Peter Robinson <pbrobinson@gmail.com>,
Ilias Apalodimas <ilias.apalodimas@linaro.org>,
Tom Rini <trini@konsulko.com>, David Garske <david@wolfssl.com>,
Aidan <aidan@wolfssl.com>,
Jagan Teki <jagan@amarulasolutions.com>,
Vignesh R <vigneshr@ti.com>,
Tudor Ambarus <tudor.ambarus@linaro.org>,
Simon Glass <sjg@chromium.org>
Subject: [PATCH v4 11/14] tpm: add sandbox TPM SPI emulator
Date: Tue, 12 May 2026 17:26:15 -0700 [thread overview]
Message-ID: <20260513002625.76915-11-aidan@wolfssl.com> (raw)
In-Reply-To: <cover.1778619453.git.aidan@wolfssl.com>
From: Aidan <aidan@wolfssl.com>
Add a TPM SPI emulator for sandbox testing that implements the TIS
(TPM Interface Specification) SPI protocol, allowing wolfTPM's SPI
HAL code to be tested without physical hardware.
drivers/tpm/tpm_spi_sandbox.c (new):
Emulates a TPM connected via SPI by implementing the TIS register
set and SPI protocol:
- SPI protocol state machine: parses 4-byte TIS SPI headers
(R/W bit, transfer length, register address) and handles data
phase with immediate ready signaling (no wait states)
- TIS register emulation: TPM_ACCESS (locality request/grant),
TPM_STS (command ready, data expect, data available, burst
count), TPM_INTF_CAPS, TPM_DID_VID (Infineon SLB9670 IDs),
TPM_RID, and TPM_DATA_FIFO (command/response buffering)
- TIS state machine: IDLE -> READY -> RECEPTION -> EXECUTION ->
COMPLETION, with command-ready abort support
- Generates simple TPM_RC_SUCCESS responses (a full implementation
would integrate the sandbox TPM2 state machine)
- Registers as UCLASS_SPI_EMUL with compatible "sandbox,tpm-spi-emul"
- Also registers a SPI slave driver (UCLASS_SPI_GENERIC) with
compatible "sandbox,tpm-spi" for the DTS device node
drivers/mtd/spi/sandbox.c:
Modify sandbox_spi_get_emul() to check for a "sandbox,emul"
phandle property on SPI slave devices before falling back to the
default SPI flash emulation binding. This allows non-flash SPI
devices (like the TPM emulator) to specify their own emulator
via device tree phandle.
Signed-off-by: Aidan Garske <aidan@wolfssl.com>
---
drivers/mtd/spi/sandbox.c | 30 ++-
drivers/tpm/tpm_spi_sandbox.c | 410 ++++++++++++++++++++++++++++++++++
2 files changed, 431 insertions(+), 9 deletions(-)
create mode 100644 drivers/tpm/tpm_spi_sandbox.c
diff --git a/drivers/mtd/spi/sandbox.c b/drivers/mtd/spi/sandbox.c
index e5ebc3479fb..41bd07817aa 100644
--- a/drivers/mtd/spi/sandbox.c
+++ b/drivers/mtd/spi/sandbox.c
@@ -571,16 +571,28 @@ int sandbox_spi_get_emul(struct sandbox_state *state,
info = &state->spi[busnum][cs];
if (!info->emul) {
- /* Use the same device tree node as the SPI flash device */
- debug("%s: busnum=%u, cs=%u: binding SPI flash emulation: ",
- __func__, busnum, cs);
- ret = sandbox_sf_bind_emul(state, busnum, cs, bus,
- dev_ofnode(slave), slave->name);
- if (ret) {
- debug("failed (err=%d)\n", ret);
- return ret;
+ struct udevice *emul;
+ ofnode node = dev_ofnode(slave);
+
+ /* First check for sandbox,emul phandle property */
+ ret = uclass_get_device_by_phandle(UCLASS_SPI_EMUL, slave,
+ "sandbox,emul", &emul);
+ if (!ret) {
+ debug("%s: busnum=%u, cs=%u: using phandle emulator\n",
+ __func__, busnum, cs);
+ info->emul = emul;
+ } else {
+ /* Fall back to SPI flash emulation binding */
+ debug("%s: busnum=%u, cs=%u: binding SPI flash emulation: ",
+ __func__, busnum, cs);
+ ret = sandbox_sf_bind_emul(state, busnum, cs, bus,
+ node, slave->name);
+ if (ret) {
+ debug("failed (err=%d)\n", ret);
+ return ret;
+ }
+ debug("OK\n");
}
- debug("OK\n");
}
*emulp = info->emul;
diff --git a/drivers/tpm/tpm_spi_sandbox.c b/drivers/tpm/tpm_spi_sandbox.c
new file mode 100644
index 00000000000..694c5d721f0
--- /dev/null
+++ b/drivers/tpm/tpm_spi_sandbox.c
@@ -0,0 +1,410 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Sandbox TPM SPI Emulator
+ *
+ * Copyright (c) 2025 wolfSSL Inc.
+ * Author: Aidan Garske <aidan@wolfssl.com>
+ *
+ * Emulates TPM TIS SPI protocol for testing wolfTPM SPI HAL
+ * without hardware. Wraps the existing sandbox TPM2 state machine.
+ */
+
+#include <dm.h>
+#include <log.h>
+#include <spi.h>
+#include <spi_flash.h>
+#include <asm/spi.h>
+#include <asm/state.h>
+#include <linux/bitops.h>
+
+/* TIS register addresses (locality 0) */
+#define TPM_ACCESS_REG 0x0000
+#define TPM_INT_ENABLE_REG 0x0008
+#define TPM_INTF_CAPS_REG 0x0014
+#define TPM_STS_REG 0x0018
+#define TPM_DATA_FIFO_REG 0x0024
+#define TPM_DID_VID_REG 0x0F00
+#define TPM_RID_REG 0x0F04
+
+/* TIS access register bits */
+#define TPM_ACCESS_VALID 0x80
+#define TPM_ACCESS_ACTIVE_LOCALITY 0x20
+#define TPM_ACCESS_REQUEST_PENDING 0x04
+#define TPM_ACCESS_REQUEST_USE 0x02
+
+/* TIS status register bits */
+#define TPM_STS_VALID 0x80
+#define TPM_STS_COMMAND_READY 0x40
+#define TPM_STS_GO 0x20
+#define TPM_STS_DATA_AVAIL 0x10
+#define TPM_STS_DATA_EXPECT 0x08
+
+/* Interface capabilities */
+#define TPM_INTF_CAPS_VALUE 0x30000697 /* Typical Infineon value */
+
+/* Device/Vendor ID - Infineon SLB9670 */
+#define TPM_DID_VID_VALUE 0x001D15D1
+
+/* Revision ID */
+#define TPM_RID_VALUE 0x36
+
+/* Maximum buffer sizes */
+#define TPM_CMD_BUF_SIZE 4096
+#define TPM_RSP_BUF_SIZE 4096
+#define MAX_SPI_FRAMESIZE 64
+
+/* TPM TIS SPI protocol states */
+enum tpm_spi_state {
+ TPM_SPI_IDLE,
+ TPM_SPI_HEADER, /* Receiving 4-byte header */
+ TPM_SPI_WAIT_STATE, /* Sending wait state bytes */
+ TPM_SPI_DATA, /* Transfer data */
+};
+
+/* TIS state machine */
+enum tpm_tis_state {
+ TIS_IDLE,
+ TIS_READY, /* Ready to receive command */
+ TIS_RECEPTION, /* Receiving command data */
+ TIS_EXECUTION, /* Executing command */
+ TIS_COMPLETION, /* Response available */
+};
+
+struct sandbox_tpm_spi {
+ /* SPI protocol state */
+ enum tpm_spi_state spi_state;
+ u8 header[4];
+ int header_pos;
+ bool is_read;
+ u32 addr;
+ int xfer_len;
+ int data_pos;
+
+ /* TIS state */
+ enum tpm_tis_state tis_state;
+ u8 access_reg;
+ u32 sts_reg;
+ u32 intf_caps;
+
+ /* Command/response buffers */
+ u8 cmd_buf[TPM_CMD_BUF_SIZE];
+ int cmd_len;
+ int cmd_pos;
+ u8 rsp_buf[TPM_RSP_BUF_SIZE];
+ int rsp_len;
+ int rsp_pos;
+
+ /* Burst count for status register */
+ u16 burst_count;
+};
+
+/*
+ * Parse TIS SPI header
+ * Format: [R/W|len-1][0xD4][addr_hi][addr_lo]
+ * Bit 7 of byte 0: 1=read, 0=write
+ * Bits 5:0 of byte 0: transfer length - 1
+ */
+static void parse_spi_header(struct sandbox_tpm_spi *priv)
+{
+ priv->is_read = (priv->header[0] & 0x80) != 0;
+ priv->xfer_len = (priv->header[0] & 0x3F) + 1;
+ priv->addr = (priv->header[2] << 8) | priv->header[3];
+ priv->data_pos = 0;
+}
+
+/*
+ * Read from TIS register
+ */
+static u8 tis_reg_read(struct sandbox_tpm_spi *priv, u32 addr)
+{
+ u32 reg = addr & 0x0FFF; /* Mask off locality bits */
+
+ switch (reg) {
+ case TPM_ACCESS_REG:
+ return priv->access_reg;
+
+ case TPM_STS_REG:
+ case TPM_STS_REG + 1:
+ case TPM_STS_REG + 2:
+ case TPM_STS_REG + 3: {
+ int byte_off = reg - TPM_STS_REG;
+ u32 sts = priv->sts_reg;
+
+ /* Update burst count in status */
+ sts |= ((u32)priv->burst_count << 8);
+ return (sts >> (byte_off * 8)) & 0xFF;
+ }
+
+ case TPM_INTF_CAPS_REG:
+ case TPM_INTF_CAPS_REG + 1:
+ case TPM_INTF_CAPS_REG + 2:
+ case TPM_INTF_CAPS_REG + 3: {
+ int byte_off = reg - TPM_INTF_CAPS_REG;
+
+ return (priv->intf_caps >> (byte_off * 8)) & 0xFF;
+ }
+
+ case TPM_DID_VID_REG:
+ case TPM_DID_VID_REG + 1:
+ case TPM_DID_VID_REG + 2:
+ case TPM_DID_VID_REG + 3: {
+ int byte_off = reg - TPM_DID_VID_REG;
+
+ return (TPM_DID_VID_VALUE >> (byte_off * 8)) & 0xFF;
+ }
+
+ case TPM_RID_REG:
+ return TPM_RID_VALUE;
+
+ default:
+ /*
+ * Handle FIFO reads - the FIFO can be accessed at any address
+ * from 0x0024 up to 0x0F00 for multi-byte transfers.
+ */
+ if (reg >= TPM_DATA_FIFO_REG && reg < TPM_DID_VID_REG) {
+ if (priv->tis_state == TIS_COMPLETION &&
+ priv->rsp_pos < priv->rsp_len) {
+ u8 data = priv->rsp_buf[priv->rsp_pos++];
+
+ /* Update status when all data read */
+ if (priv->rsp_pos >= priv->rsp_len) {
+ priv->sts_reg &= ~TPM_STS_DATA_AVAIL;
+ priv->sts_reg |= TPM_STS_COMMAND_READY;
+ priv->tis_state = TIS_READY;
+ }
+ return data;
+ }
+ return 0xFF;
+ }
+ return 0xFF;
+ }
+}
+
+/*
+ * Write to TIS register
+ */
+static void tis_reg_write(struct sandbox_tpm_spi *priv, u32 addr, u8 value)
+{
+ u32 reg = addr & 0x0FFF;
+
+ switch (reg) {
+ case TPM_ACCESS_REG:
+ if (value & TPM_ACCESS_REQUEST_USE) {
+ /* Request locality */
+ priv->access_reg |= TPM_ACCESS_ACTIVE_LOCALITY;
+ priv->access_reg |= TPM_ACCESS_VALID;
+ }
+ break;
+
+ case TPM_STS_REG:
+ if (value & TPM_STS_COMMAND_READY) {
+ /* Abort current command and go to ready state */
+ priv->tis_state = TIS_READY;
+ priv->cmd_len = 0;
+ priv->cmd_pos = 0;
+ priv->rsp_len = 0;
+ priv->rsp_pos = 0;
+ priv->sts_reg = TPM_STS_VALID | TPM_STS_COMMAND_READY;
+ priv->burst_count = MAX_SPI_FRAMESIZE;
+ }
+ if (value & TPM_STS_GO) {
+ /* Execute command */
+ if (priv->tis_state == TIS_RECEPTION &&
+ priv->cmd_len > 0) {
+ /*
+ * Generate a simple success response.
+ * A full implementation would call the
+ * sandbox TPM2 state machine here.
+ */
+ priv->rsp_buf[0] = 0x80; /* TPM_ST_NO_SESSIONS */
+ priv->rsp_buf[1] = 0x01;
+ priv->rsp_buf[2] = 0x00; /* Response size: 10 */
+ priv->rsp_buf[3] = 0x00;
+ priv->rsp_buf[4] = 0x00;
+ priv->rsp_buf[5] = 0x0A;
+ priv->rsp_buf[6] = 0x00; /* TPM_RC_SUCCESS */
+ priv->rsp_buf[7] = 0x00;
+ priv->rsp_buf[8] = 0x00;
+ priv->rsp_buf[9] = 0x00;
+ priv->rsp_len = 10;
+ priv->rsp_pos = 0;
+
+ priv->tis_state = TIS_COMPLETION;
+ priv->sts_reg = TPM_STS_VALID |
+ TPM_STS_DATA_AVAIL;
+ }
+ }
+ break;
+
+ default:
+ /*
+ * Handle FIFO writes - the FIFO is at 0x0024 but any address
+ * from 0x0024 up to 0x0F00 can be used for FIFO access when
+ * doing multi-byte transfers (address auto-increments).
+ */
+ if (reg >= TPM_DATA_FIFO_REG && reg < TPM_DID_VID_REG) {
+ if (priv->tis_state == TIS_READY) {
+ /* Start receiving command */
+ priv->tis_state = TIS_RECEPTION;
+ priv->cmd_len = 0;
+ priv->cmd_pos = 0;
+ priv->sts_reg = TPM_STS_VALID | TPM_STS_DATA_EXPECT;
+ }
+ if (priv->tis_state == TIS_RECEPTION) {
+ if (priv->cmd_len < TPM_CMD_BUF_SIZE) {
+ priv->cmd_buf[priv->cmd_len++] = value;
+
+ /* Check if we have complete command */
+ if (priv->cmd_len >= 6) {
+ u32 expected_len;
+
+ expected_len = (priv->cmd_buf[2] << 24) |
+ (priv->cmd_buf[3] << 16) |
+ (priv->cmd_buf[4] << 8) |
+ priv->cmd_buf[5];
+ if (priv->cmd_len >= expected_len) {
+ /* Command complete */
+ priv->sts_reg &=
+ ~TPM_STS_DATA_EXPECT;
+ }
+ }
+ }
+ }
+ }
+ break;
+ }
+}
+
+/*
+ * SPI emulation transfer callback
+ */
+static int sandbox_tpm_spi_xfer(struct udevice *dev, unsigned int bitlen,
+ const void *dout, void *din, unsigned long flags)
+{
+ struct sandbox_tpm_spi *priv = dev_get_priv(dev);
+ int bytes = bitlen / 8;
+ const u8 *tx = dout;
+ u8 *rx = din;
+ int i;
+
+ /* Handle CS assert - reset state machine */
+ if (flags & SPI_XFER_BEGIN) {
+ priv->spi_state = TPM_SPI_HEADER;
+ priv->header_pos = 0;
+ }
+
+ for (i = 0; i < bytes; i++) {
+ u8 tx_byte = tx ? tx[i] : 0;
+ u8 rx_byte = 0;
+
+ switch (priv->spi_state) {
+ case TPM_SPI_IDLE:
+ /* Should not happen during active transfer */
+ rx_byte = 0xFF;
+ break;
+
+ case TPM_SPI_HEADER:
+ /* Receive 4-byte header */
+ priv->header[priv->header_pos++] = tx_byte;
+ rx_byte = 0x00;
+
+ if (priv->header_pos >= 4) {
+ parse_spi_header(priv);
+ log_debug("TPM SPI: %s len=%d addr=0x%04x\n",
+ priv->is_read ? "read" : "write",
+ priv->xfer_len, priv->addr);
+ /* Return wait state in last header byte */
+ rx_byte = 0x01; /* Ready immediately */
+ priv->spi_state = TPM_SPI_DATA;
+ }
+ break;
+
+ case TPM_SPI_DATA:
+ if (priv->is_read) {
+ /* Read from TPM register */
+ rx_byte = tis_reg_read(priv,
+ priv->addr + priv->data_pos);
+ } else {
+ /* Write to TPM register */
+ tis_reg_write(priv, priv->addr + priv->data_pos,
+ tx_byte);
+ rx_byte = 0x00;
+ }
+ priv->data_pos++;
+ break;
+
+ default:
+ rx_byte = 0xFF;
+ break;
+ }
+
+ if (rx)
+ rx[i] = rx_byte;
+ }
+
+ /* Handle CS deassert - return to idle */
+ if (flags & SPI_XFER_END)
+ priv->spi_state = TPM_SPI_IDLE;
+
+ return 0;
+}
+
+static int sandbox_tpm_spi_probe(struct udevice *dev)
+{
+ struct sandbox_tpm_spi *priv = dev_get_priv(dev);
+
+ /* Initialize TIS state */
+ priv->spi_state = TPM_SPI_IDLE;
+ priv->tis_state = TIS_IDLE;
+ priv->access_reg = TPM_ACCESS_VALID;
+ priv->sts_reg = TPM_STS_VALID;
+ priv->intf_caps = TPM_INTF_CAPS_VALUE;
+ priv->burst_count = MAX_SPI_FRAMESIZE;
+ priv->cmd_len = 0;
+ priv->rsp_len = 0;
+
+ log_debug("TPM SPI sandbox emulator probed\n");
+
+ return 0;
+}
+
+static const struct dm_spi_emul_ops sandbox_tpm_spi_ops = {
+ .xfer = sandbox_tpm_spi_xfer,
+};
+
+static const struct udevice_id sandbox_tpm_spi_ids[] = {
+ { .compatible = "sandbox,tpm-spi-emul" },
+ { }
+};
+
+U_BOOT_DRIVER(sandbox_tpm_spi_emul) = {
+ .name = "sandbox_tpm_spi_emul",
+ .id = UCLASS_SPI_EMUL,
+ .of_match = sandbox_tpm_spi_ids,
+ .ops = &sandbox_tpm_spi_ops,
+ .probe = sandbox_tpm_spi_probe,
+ .priv_auto = sizeof(struct sandbox_tpm_spi),
+};
+
+/*
+ * SPI slave driver for TPM device
+ * This gets probed when a device with "sandbox,tpm-spi" is found in DTS.
+ * The actual SPI transfers are handled by the emulator above.
+ */
+static int sandbox_tpm_spi_slave_probe(struct udevice *dev)
+{
+ log_debug("TPM SPI slave device probed\n");
+ return 0;
+}
+
+static const struct udevice_id sandbox_tpm_spi_slave_ids[] = {
+ { .compatible = "sandbox,tpm-spi" },
+ { }
+};
+
+U_BOOT_DRIVER(sandbox_tpm_spi) = {
+ .name = "sandbox_tpm_spi",
+ .id = UCLASS_SPI_GENERIC,
+ .of_match = sandbox_tpm_spi_slave_ids,
+ .probe = sandbox_tpm_spi_slave_probe,
+};
--
2.49.0
next prev parent reply other threads:[~2026-05-13 0:28 UTC|newest]
Thread overview: 30+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-13 0:26 [PATCH v4 00/14] tpm: Add wolfTPM library support for TPM 2.0 Aidan Garske
2026-05-13 0:26 ` [PATCH v4 01/14] tpm: export tpm_show_device, tpm_set_device, and get_tpm Aidan Garske
2026-05-15 13:06 ` Simon Glass
2026-05-13 0:26 ` [PATCH v4 02/14] include/hash: add SHA384 hash wrapper declaration for wolfTPM Aidan Garske
2026-05-13 0:26 ` [PATCH v4 03/14] spi: add BCM2835/BCM2711 hardware SPI controller driver Aidan Garske
2026-05-15 13:07 ` Simon Glass
2026-05-15 15:13 ` Peter Robinson
2026-05-13 0:26 ` [PATCH v4 04/14] arm: dts: bcm2711-rpi-4-b: add Infineon SLB9670/9672 TPM in U-Boot dtsi Aidan Garske
2026-05-15 13:08 ` Simon Glass
2026-05-13 0:26 ` [PATCH v4 05/14] arm: dts: qemu-arm64: add TPM TIS MMIO node Aidan Garske
2026-05-15 13:09 ` Simon Glass
2026-05-13 0:26 ` [PATCH v4 06/14] sandbox: dts: add TPM SPI emulator node Aidan Garske
2026-05-15 13:11 ` Simon Glass
2026-05-13 0:26 ` [PATCH v4 07/14] tpm: add wolfTPM build rules and Kconfig Aidan Garske
2026-05-13 0:26 ` [PATCH v4 08/14] tpm: add wolfTPM headers and SHA384 glue code Aidan Garske
2026-05-13 0:26 ` [PATCH v4 09/14] tpm: add wolfTPM driver helpers and Kconfig options Aidan Garske
2026-05-13 0:26 ` [PATCH v4 10/14] cmd: refactor tpm2 command into frontend/backend architecture Aidan Garske
2026-05-15 14:11 ` Simon Glass
2026-05-15 14:15 ` Simon Glass
2026-05-13 0:26 ` Aidan Garske [this message]
2026-05-15 13:24 ` [PATCH v4 11/14] tpm: add sandbox TPM SPI emulator Simon Glass
2026-05-13 0:26 ` [PATCH v4 12/14] test: add wolfTPM C unit tests and Python integration tests Aidan Garske
2026-05-15 14:15 ` Simon Glass
2026-05-13 0:26 ` [PATCH v4 13/14] doc: add wolfTPM documentation Aidan Garske
2026-05-13 0:26 ` [PATCH v4 14/14] configs: add rpi_4_wolftpm_defconfig Aidan Garske
2026-05-15 11:31 ` Matthias Brugger
2026-05-13 6:35 ` [PATCH v4 00/14] tpm: Add wolfTPM library support for TPM 2.0 Ilias Apalodimas
2026-05-13 14:34 ` Tom Rini
2026-05-13 16:04 ` Aidan Garske
2026-05-13 16:36 ` Peter Robinson
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260513002625.76915-11-aidan@wolfssl.com \
--to=aidan@wolfssl.com \
--cc=david@wolfssl.com \
--cc=ilias.apalodimas@linaro.org \
--cc=jagan@amarulasolutions.com \
--cc=pbrobinson@gmail.com \
--cc=sjg@chromium.org \
--cc=trini@konsulko.com \
--cc=tudor.ambarus@linaro.org \
--cc=u-boot@lists.denx.de \
--cc=vigneshr@ti.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox