mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Jorge Ramirez-Ortiz <jorge.ramirez@oss.qualcomm.com>
To: jorge.ramirez@oss.qualcomm.com, peterhuewe@gmx.de,
	jarkko@kernel.org, jgg@ziepe.ca, sumit.garg@oss.qualcomm.com
Cc: linux-kernel@vger.kernel.org, linux-integrity@vger.kernel.org
Subject: [PATCH 1/1] tpm: add TEE driver for a discrete TPM behind OP-TEE
Date: Mon, 26 Oct 2026 17:32:48 +0100	[thread overview]
Message-ID: <20261026163259.1900439-2-jorge.ramirez@oss.qualcomm.com> (raw)
In-Reply-To: <20261026163259.1900439-1-jorge.ramirez@oss.qualcomm.com>

Add tpm_dtpm_tee for a discrete TPM that OP-TEE owns and the normal world
cannot reach directly (e.g. an SPI TPM on a bus driven by OP-TEE). The
driver binds to OP-TEE's passthrough pseudo TA by UUID and forwards TPM2
commands through it, presenting a standard TPM2 character device.

It is a separate driver rather than a reuse of tpm_ftpm_tee: the TEE
transport is the same, but that driver is tied to the Microsoft fTPM TA
and opens a public-login session, whereas this is a distinct TA for a real
chip, gated to REE-kernel access so that user space and other TAs cannot
reach the TPM. Sharing the fTPM identity would conflate two different
devices and collide on a platform that has both.

Signed-off-by: Jorge Ramirez-Ortiz <jorge.ramirez@oss.qualcomm.com>
---
 MAINTAINERS                     |   6 +
 drivers/char/tpm/Kconfig        |  18 ++
 drivers/char/tpm/Makefile       |   1 +
 drivers/char/tpm/tpm_dtpm_tee.c | 282 ++++++++++++++++++++++++++++++++
 4 files changed, 307 insertions(+)
 create mode 100644 drivers/char/tpm/tpm_dtpm_tee.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 782389bd5cd3..2d279f45fcea 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -27710,6 +27710,12 @@ F:	include/linux/tpm*.h
 F:	include/uapi/linux/vtpm_proxy.h
 F:	tools/testing/selftests/tpm2/
 
+TPM TEE DRIVER FOR DISCRETE TPM
+R:	Jorge Ramirez-Ortiz <jorge.ramirez@oss.qualcomm.com>
+L:	linux-integrity@vger.kernel.org
+S:	Supported
+F:	drivers/char/tpm/tpm_dtpm_tee.c
+
 TPS546D24 DRIVER
 M:	Duke Du <dukedu83@gmail.com>
 L:	linux-hwmon@vger.kernel.org
diff --git a/drivers/char/tpm/Kconfig b/drivers/char/tpm/Kconfig
index 5f672f2c01b0..8293e43e4bc0 100644
--- a/drivers/char/tpm/Kconfig
+++ b/drivers/char/tpm/Kconfig
@@ -243,6 +243,24 @@ config TCG_FTPM_TEE
 	help
 	  This driver proxies for firmware TPM running in TEE.
 
+config TCG_DTPM_TEE
+	tristate "TEE based discrete TPM Interface"
+	depends on TEE && OPTEE
+	help
+	  This driver proxies for a discrete TPM that is owned by OP-TEE and
+	  reachable only from the secure world (for example an SPI TPM behind
+	  OP-TEE's TPM TIS driver). OP-TEE exposes a passthrough pseudo TA and
+	  this driver pipes TPM commands through it, presenting a standard
+	  TPM character device to the normal world.
+
+	  The secure world is the sole owner of the TPM and of the bus it
+	  sits on. That bus/controller must be disabled in the non-secure
+	  device tree: disabling only the in-kernel TPM driver (e.g.
+	  tpm_tis_spi) is not enough, because the normal-world bus controller
+	  would still drive the engine in parallel with OP-TEE and corrupt
+	  its transfers. With the bus disabled, the TPM is reached only
+	  through this driver.
+
 config TCG_SVSM
 	tristate "SNP SVSM vTPM interface"
 	depends on AMD_MEM_ENCRYPT
diff --git a/drivers/char/tpm/Makefile b/drivers/char/tpm/Makefile
index 5b5cdc0d32e4..5c5e20001cac 100644
--- a/drivers/char/tpm/Makefile
+++ b/drivers/char/tpm/Makefile
@@ -45,5 +45,6 @@ obj-$(CONFIG_TCG_CRB) += tpm_crb.o
 obj-$(CONFIG_TCG_ARM_CRB_FFA) += tpm_crb_ffa.o
 obj-$(CONFIG_TCG_VTPM_PROXY) += tpm_vtpm_proxy.o
 obj-$(CONFIG_TCG_FTPM_TEE) += tpm_ftpm_tee.o
+obj-$(CONFIG_TCG_DTPM_TEE) += tpm_dtpm_tee.o
 obj-$(CONFIG_TCG_SVSM) += tpm_svsm.o
 obj-$(CONFIG_TCG_LOONGSON) += tpm_loongson.o
diff --git a/drivers/char/tpm/tpm_dtpm_tee.c b/drivers/char/tpm/tpm_dtpm_tee.c
new file mode 100644
index 000000000000..755429ff8a3f
--- /dev/null
+++ b/drivers/char/tpm/tpm_dtpm_tee.c
@@ -0,0 +1,282 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * TEE driver for a discrete TPM owned by OP-TEE and exposed to the normal
+ * world through a passthrough pseudo TA. The chip sits on a bus only the
+ * secure world can reach (e.g. SPI behind OP-TEE's TPM TIS driver); the driver
+ * binds the TA by UUID and relays raw TPM2 commands as a standard tpm_chip.
+ */
+
+#include <linux/module.h>
+#include <linux/tee_drv.h>
+#include <linux/tpm.h>
+#include <linux/uuid.h>
+
+#include "tpm.h"
+
+/* Must match the discrete TPM passthrough pseudo TA in OP-TEE */
+static const uuid_t dtpm_ta_uuid =
+	UUID_INIT(0x50568cd9, 0x370a, 0x46b8,
+		  0x9f, 0x2c, 0x6f, 0xd9, 0x06, 0x64, 0xd2, 0xee);
+
+#define DTPM_TEE_TA_SUBMIT_COMMAND	0
+
+#define DTPM_MAX_COMMAND_SIZE		4096
+#define DTPM_MAX_RESPONSE_SIZE		4096
+
+struct dtpm_tee_private {
+	struct tpm_chip *chip;
+	u32 session;
+	struct tee_context *ctx;
+	struct tee_shm *shm;
+};
+
+/* Validate the response header the TA wrote back, return its length or -EIO */
+static int dtpm_tee_resp_len(struct tpm_chip *chip, const u8 *resp,
+			     size_t bufsiz)
+{
+	size_t resp_len = be32_to_cpu(((struct tpm_header *)resp)->length);
+
+	if (resp_len < TPM_HEADER_SIZE || resp_len > DTPM_MAX_RESPONSE_SIZE ||
+	    resp_len > bufsiz) {
+		dev_err(&chip->dev, "bad response length %zu (buffer %zu)\n",
+			resp_len, bufsiz);
+		return -EIO;
+	}
+
+	return resp_len;
+}
+
+/**
+ * dtpm_tee_tpm_op_send() - relay a TPM command through TEE shared memory.
+ * @chip:	the tpm_chip description
+ * @buf:	command on entry, response on return
+ * @bufsiz:	size of @buf
+ * @cmd_len:	number of command bytes to send
+ *
+ * Return: number of response bytes on success, -errno on failure.
+ */
+static int dtpm_tee_tpm_op_send(struct tpm_chip *chip, u8 *buf, size_t bufsiz,
+				size_t cmd_len)
+{
+	struct dtpm_tee_private *pvt_data = dev_get_drvdata(chip->dev.parent);
+	struct tee_shm *shm = pvt_data->shm;
+	struct tee_ioctl_invoke_arg transceive_args;
+	struct tee_param command_params[4];
+	u8 *temp_buf;
+	int rc;
+
+	if (cmd_len > DTPM_MAX_COMMAND_SIZE || cmd_len > bufsiz) {
+		dev_err(&chip->dev, "command length %zu (buffer %zu)\n",
+			cmd_len, bufsiz);
+		return -EIO;
+	}
+
+	memset(&transceive_args, 0, sizeof(transceive_args));
+	memset(command_params, 0, sizeof(command_params));
+
+	transceive_args = (struct tee_ioctl_invoke_arg) {
+		.func = DTPM_TEE_TA_SUBMIT_COMMAND,
+		.session = pvt_data->session,
+		.num_params = 4,
+	};
+
+	/* memref[0]: command in, memref[1]: response in/out */
+	command_params[0] = (struct tee_param) {
+		.attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT,
+		.u.memref = {
+			.shm = shm,
+			.size = cmd_len,
+			.shm_offs = 0,
+		},
+	};
+
+	temp_buf = tee_shm_get_va(shm, 0);
+	if (IS_ERR(temp_buf)) {
+		dev_err(&chip->dev, "tee_shm_get_va failed for transmit\n");
+		return PTR_ERR(temp_buf);
+	}
+	memset(temp_buf, 0, DTPM_MAX_COMMAND_SIZE + DTPM_MAX_RESPONSE_SIZE);
+	memcpy(temp_buf, buf, cmd_len);
+
+	command_params[1] = (struct tee_param) {
+		.attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT,
+		.u.memref = {
+			.shm = shm,
+			.size = DTPM_MAX_RESPONSE_SIZE,
+			.shm_offs = DTPM_MAX_COMMAND_SIZE,
+		},
+	};
+
+	rc = tee_client_invoke_func(pvt_data->ctx, &transceive_args,
+				    command_params);
+	if (rc < 0 || transceive_args.ret != 0) {
+		dev_err(&chip->dev, "SUBMIT_COMMAND invoke error: 0x%x\n",
+			transceive_args.ret);
+		return (rc < 0) ? rc : -EIO;
+	}
+
+	temp_buf = tee_shm_get_va(shm, command_params[1].u.memref.shm_offs);
+	if (IS_ERR(temp_buf)) {
+		dev_err(&chip->dev, "tee_shm_get_va failed for receive\n");
+		return PTR_ERR(temp_buf);
+	}
+
+	rc = dtpm_tee_resp_len(chip, temp_buf, bufsiz);
+	if (rc < 0)
+		return rc;
+
+	memcpy(buf, temp_buf, rc);
+
+	return rc;
+}
+
+static const struct tpm_class_ops dtpm_tee_tpm_ops = {
+	.flags = TPM_OPS_AUTO_STARTUP,
+	.send = dtpm_tee_tpm_op_send,
+};
+
+static int dtpm_tee_match(struct tee_ioctl_version_data *ver, const void *data)
+{
+	/* Only GP-compliant OP-TEE is supported */
+	if (ver->impl_id == TEE_IMPL_ID_OPTEE && (ver->gen_caps & TEE_GEN_CAP_GP))
+		return 1;
+
+	return 0;
+}
+
+/*
+ * Open the TA session as the REE kernel: the TA gates the shared TPM on
+ * TEE_LOGIN_REE_KERNEL so user space and other TAs cannot submit raw commands.
+ */
+static int dtpm_tee_open_session(struct device *dev, struct tee_context *ctx,
+				 u32 *session)
+{
+	struct tee_ioctl_open_session_arg sess_arg;
+	int rc;
+
+	memset(&sess_arg, 0, sizeof(sess_arg));
+	export_uuid(sess_arg.uuid, &dtpm_ta_uuid);
+	sess_arg.clnt_login = TEE_IOCTL_LOGIN_REE_KERNEL;
+	sess_arg.num_params = 0;
+
+	rc = tee_client_open_session(ctx, &sess_arg, NULL);
+	if (rc < 0 || sess_arg.ret != 0) {
+		dev_err(dev, "tee_client_open_session failed, err=0x%x\n",
+			sess_arg.ret);
+		return -EINVAL;
+	}
+
+	*session = sess_arg.session;
+
+	return 0;
+}
+
+static int dtpm_tee_probe(struct tee_client_device *tcdev)
+{
+	struct device *dev = &tcdev->dev;
+	struct dtpm_tee_private *pvt_data;
+	struct tpm_chip *chip;
+	int rc;
+
+	pvt_data = devm_kzalloc(dev, sizeof(*pvt_data), GFP_KERNEL);
+	if (!pvt_data)
+		return -ENOMEM;
+
+	dev_set_drvdata(dev, pvt_data);
+
+	pvt_data->ctx = tee_client_open_context(NULL, dtpm_tee_match, NULL,
+						NULL);
+	if (IS_ERR(pvt_data->ctx)) {
+		if (PTR_ERR(pvt_data->ctx) == -ENOENT)
+			return -EPROBE_DEFER;
+		dev_err(dev, "tee_client_open_context failed\n");
+		return PTR_ERR(pvt_data->ctx);
+	}
+
+	rc = dtpm_tee_open_session(dev, pvt_data->ctx, &pvt_data->session);
+	if (rc)
+		goto out_tee_session;
+
+	pvt_data->shm = tee_shm_alloc_kernel_buf(pvt_data->ctx,
+						 DTPM_MAX_COMMAND_SIZE +
+						 DTPM_MAX_RESPONSE_SIZE);
+	if (IS_ERR(pvt_data->shm)) {
+		dev_err(dev, "tee_shm_alloc_kernel_buf failed\n");
+		rc = -ENOMEM;
+		goto out_shm_alloc;
+	}
+
+	chip = tpm_chip_alloc(dev, &dtpm_tee_tpm_ops);
+	if (IS_ERR(chip)) {
+		dev_err(dev, "tpm_chip_alloc failed\n");
+		rc = PTR_ERR(chip);
+		goto out_chip_alloc;
+	}
+
+	pvt_data->chip = chip;
+	pvt_data->chip->flags |= TPM_CHIP_FLAG_TPM2 | TPM_CHIP_FLAG_SYNC;
+
+	rc = tpm_chip_register(pvt_data->chip);
+	if (rc) {
+		dev_err(dev, "tpm_chip_register failed with rc=%d\n", rc);
+		goto out_chip;
+	}
+
+	return 0;
+
+out_chip:
+	put_device(&pvt_data->chip->dev);
+out_chip_alloc:
+	tee_shm_free(pvt_data->shm);
+out_shm_alloc:
+	tee_client_close_session(pvt_data->ctx, pvt_data->session);
+out_tee_session:
+	tee_client_close_context(pvt_data->ctx);
+
+	return rc;
+}
+
+static void dtpm_tee_remove(struct tee_client_device *tcdev)
+{
+	struct dtpm_tee_private *pvt_data = dev_get_drvdata(&tcdev->dev);
+
+	tpm_chip_unregister(pvt_data->chip);
+	put_device(&pvt_data->chip->dev);
+	tee_shm_free(pvt_data->shm);
+	tee_client_close_session(pvt_data->ctx, pvt_data->session);
+	tee_client_close_context(pvt_data->ctx);
+}
+
+static const struct tee_client_device_id dtpm_tee_id_table[] = {
+	{UUID_INIT(0x50568cd9, 0x370a, 0x46b8,
+		   0x9f, 0x2c, 0x6f, 0xd9, 0x06, 0x64, 0xd2, 0xee)},
+	{}
+};
+MODULE_DEVICE_TABLE(tee, dtpm_tee_id_table);
+
+static struct tee_client_driver dtpm_tee_driver = {
+	.probe		= dtpm_tee_probe,
+	.remove		= dtpm_tee_remove,
+	.id_table	= dtpm_tee_id_table,
+	.driver		= {
+		.name		= "optee-dtpm",
+	},
+};
+
+static int __init dtpm_tee_mod_init(void)
+{
+	return tee_client_driver_register(&dtpm_tee_driver);
+}
+
+static void __exit dtpm_tee_mod_exit(void)
+{
+	tee_client_driver_unregister(&dtpm_tee_driver);
+}
+
+module_init(dtpm_tee_mod_init);
+module_exit(dtpm_tee_mod_exit);
+
+MODULE_DESCRIPTION("TPM Driver for a discrete TPM behind OP-TEE");
+MODULE_LICENSE("GPL");
-- 
2.55.0


  reply	other threads:[~2026-10-26 16:33 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-26 16:32 [PATCH 0/1] tpm: TEE driver for a discrete TPM owned by OP-TEE Jorge Ramirez-Ortiz
2026-10-26 16:32 ` Jorge Ramirez-Ortiz [this message]
2026-10-26 20:07   ` [PATCH 1/1] tpm: add TEE driver for a discrete TPM behind OP-TEE Jarkko Sakkinen
2026-10-27  8:24     ` Jorge Ramirez
2026-10-27 13:10       ` Jarkko Sakkinen

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=20261026163259.1900439-2-jorge.ramirez@oss.qualcomm.com \
    --to=jorge.ramirez@oss.qualcomm.com \
    --cc=jarkko@kernel.org \
    --cc=jgg@ziepe.ca \
    --cc=linux-integrity@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=peterhuewe@gmx.de \
    --cc=sumit.garg@oss.qualcomm.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

all inboxes | Powered by JetHome®