From: mayuanchen <mayuanchen(a)hygon.cn>
Hygon CPU implemented a firmware-based TPM2 device, which runs on its
internal secure processor named PSP. The device is fully compatible with
TCG TPM2.0 spec (part 1 ~ 4) in the commands level, but underlying uses
an unique private interface in the form of some hardware mailbox between
X86 cores and PSP, which is for sure different from the TIS or CRB
interfaces defined in the PTP spec.
As such, to support this device we need a specialized driver which handles
the basic send and receive operations required by the kernel TPM core
layer. ACPI device info passed from underlying BIOS indicates the device
presence by setting the _HID field (see TCG ACPI Sepcification, Family
1.2 and 2.0, Chapter 8 "ACPI Device") to "HYGT0101", which
distinguishes
it from the rest of devices. If the BIOS does not support this setting,
the driver will not be activated and thus has no impact to the system
at all.
Signed-off-by: mayuanchen <mayuanchen(a)hygon.cn>
Change-Id: I18fb935eedcbc5467ba1589fc7a85b5953d741d9
---
drivers/char/tpm/Kconfig | 11 +++
drivers/char/tpm/Makefile | 1 +
drivers/char/tpm/tpm_hygon.c | 187 +++++++++++++++++++++++++++++++++++
3 files changed, 199 insertions(+)
create mode 100644 drivers/char/tpm/tpm_hygon.c
diff --git a/drivers/char/tpm/Kconfig b/drivers/char/tpm/Kconfig
index 18c81cbe4704..490045af1cbf 100644
--- a/drivers/char/tpm/Kconfig
+++ b/drivers/char/tpm/Kconfig
@@ -164,6 +164,17 @@ config TCG_VTPM_PROXY
/dev/vtpmX and a server-side file descriptor on which the vTPM
can receive commands.
+config TCG_HYGON
+ tristate "Hygon TPM Interface"
+ depends on ACPI
+ depends on CRYPTO_DEV_CCP_DD
+ depends on CRYPTO_DEV_SP_PSP
+ default y
+ ---help---
+ If you want to make Hygon TPM support available, say Yes and
+ it will be accessible from within Linux. To compile this
+ driver as a module, choose M here; the module will be called
+ tpm_hygon.
source "drivers/char/tpm/st33zp24/Kconfig"
endif # TCG_TPM
diff --git a/drivers/char/tpm/Makefile b/drivers/char/tpm/Makefile
index 4e9c33ca1f8f..f01c601b37d0 100644
--- a/drivers/char/tpm/Makefile
+++ b/drivers/char/tpm/Makefile
@@ -23,3 +23,4 @@ obj-$(CONFIG_TCG_TIS_ST33ZP24) += st33zp24/
obj-$(CONFIG_TCG_XEN) += xen-tpmfront.o
obj-$(CONFIG_TCG_CRB) += tpm_crb.o
obj-$(CONFIG_TCG_VTPM_PROXY) += tpm_vtpm_proxy.o
+obj-$(CONFIG_TCG_HYGON) += tpm_hygon.o
diff --git a/drivers/char/tpm/tpm_hygon.c b/drivers/char/tpm/tpm_hygon.c
new file mode 100644
index 000000000000..7fad553b6699
--- /dev/null
+++ b/drivers/char/tpm/tpm_hygon.c
@@ -0,0 +1,187 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * The Hygon TPM2.0 device driver.
+ *
+ * Copyright (C) 2020 Hygon Info Technologies Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/uaccess.h>
+#include <linux/err.h>
+#include <linux/psp-sev.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/tpm.h>
+#include "tpm.h"
+
+#define TPM2PSP_CMD(id) (0x100 | (id))
+#define MAX_TPM_BUF_LEN 4096
+#define MAX_CMD_BUF_LEN (MAX_TPM_BUF_LEN + sizeof(u32) + sizeof(u32))
+
+struct tpm_hygon_priv {
+ u8 priv_buf[MAX_CMD_BUF_LEN];
+};
+
+/*
+ * tpm header struct name is different in different kernel versions.
+ * so redefine it for driver porting.
+ */
+struct tpm_header_t {
+ __be16 tag;
+ __be32 length;
+ union {
+ __be32 ordinal;
+ __be32 return_code;
+ };
+} __packed;
+
+static int tpm_c_recv(struct tpm_chip *chip, u8 *buf, size_t count)
+{
+ int ret = 0;
+ struct tpm_hygon_priv *priv = dev_get_drvdata(&chip->dev);
+ struct tpm_header_t *header = (void *)(priv->priv_buf + sizeof(u32) + sizeof(u32));
+ u32 len = be32_to_cpu(header->length);
+
+ if (len > count) {
+ ret = -E2BIG;
+ goto out;
+ }
+
+ if (len > 0)
+ memmove(buf, (u8 *)header, len);
+
+ ret = len;
+
+out:
+ return ret;
+}
+
+static int tpm_c_send(struct tpm_chip *chip, u8 *buf, size_t count)
+{
+ int ret, error;
+ struct tpm_hygon_priv *priv = dev_get_drvdata(&chip->dev);
+ u32 buf_size = cpu_to_be32(sizeof(priv->priv_buf));
+ u32 cmd_size = cpu_to_be32((u32)count);
+ u8 *p = priv->priv_buf;
+
+ *(u32 *)p = buf_size;
+ p += sizeof(buf_size);
+ *(u32 *)p = cmd_size;
+ p += sizeof(cmd_size);
+ memmove(p, buf, count);
+
+ ret = psp_do_cmd(TPM2PSP_CMD(0), priv->priv_buf, &error);
+ if (ret) {
+ pr_err("%s: sev do cmd error, %d\n", __func__, error);
+ ret = -EIO;
+ }
+
+ return ret;
+}
+
+static const struct tpm_class_ops tpm_c_ops = {
+ .flags = TPM_OPS_AUTO_STARTUP,
+ .recv = tpm_c_recv,
+ .send = tpm_c_send,
+};
+
+static int hygon_tpm2_acpi_add(struct acpi_device *device)
+{
+ int ret;
+ struct tpm_chip *chip;
+ struct tpm_hygon_priv *priv;
+ struct device *dev = &device->dev;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ chip = tpmm_chip_alloc(dev, &tpm_c_ops);
+ if (IS_ERR(chip)) {
+ pr_err("tpmm_chip_alloc fail\n");
+ ret = PTR_ERR(chip);
+ goto err;
+ }
+
+ dev_set_drvdata(&chip->dev, priv);
+
+ chip->flags |= TPM_CHIP_FLAG_TPM2;
+ chip->flags |= TPM_CHIP_FLAG_IRQ;
+
+ ret = tpm_chip_register(chip);
+ if (ret) {
+ pr_err("tpm_chip_register fail\n");
+ goto err;
+ }
+
+ pr_info("Hygon TPM2 detected\n");
+
+ return 0;
+
+err:
+ return ret;
+}
+
+static int hygon_tpm2_acpi_remove(struct acpi_device *device)
+{
+ struct device *dev = &device->dev;
+ struct tpm_chip *chip = dev_get_drvdata(dev);
+
+ tpm_chip_unregister(chip);
+
+ pr_info("Hygon TPM2 removed\n");
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(tpm_hygon_pm, tpm_pm_suspend, tpm_pm_resume);
+
+static const struct acpi_device_id hygon_tpm2_device_ids[] = {
+ {"HYGT0101", 0},
+ {"", 0},
+};
+
+MODULE_DEVICE_TABLE(acpi, hygon_tpm2_device_ids);
+
+static struct acpi_driver hygon_tpm2_acpi_driver = {
+ .name = "tpm_hygon",
+ .ids = hygon_tpm2_device_ids,
+ .ops = {
+ .add = hygon_tpm2_acpi_add,
+ .remove = hygon_tpm2_acpi_remove,
+ },
+ .drv = {
+ .pm = &tpm_hygon_pm,
+ },
+};
+
+static int __init hygon_tpm2_init(void)
+{
+ return acpi_bus_register_driver(&hygon_tpm2_acpi_driver);
+}
+
+static void __exit hygon_tpm2_exit(void)
+{
+ acpi_bus_unregister_driver(&hygon_tpm2_acpi_driver);
+}
+
+/*
+ * hygon_tpm2_init must be done after ccp module init, but before
+ * ima module init. That's why we use a device_initcall_sync which is
+ * called after all the device_initcall(includes ccp) but before the
+ * late_initcall(includes ima).
+ */
+device_initcall_sync(hygon_tpm2_init);
+module_exit(hygon_tpm2_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("mayuanchen (mayuanchen(a)hygon.cn)")uot;);
+MODULE_DESCRIPTION("TPM2 device driver for Hygon PSP");
--
2.17.1