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 <[email protected]> --- 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 <[email protected]> +L: [email protected] +S: Supported +F: drivers/char/tpm/tpm_dtpm_tee.c + TPS546D24 DRIVER M: Duke Du <[email protected]> L: [email protected] 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
