All of lore.kernel.org
 help / color / mirror / Atom feed
From: Oshri Alkoby <oshrialkoby85@gmail.com>
To: robh+dt@kernel.org, mark.rutland@arm.com, peterhuewe@gmx.de,
	jarkko.sakkinen@linux.intel.com, jgg@ziepe.ca, arnd@arndb.de,
	gregkh@linuxfoundation.org, oshrialkoby85@gmail.com,
	oshri.alkoby@nuvoton.com
Cc: devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-integrity@vger.kernel.org, gcwilson@us.ibm.com,
	kgoldman@us.ibm.com, nayna@linux.vnet.ibm.com,
	dan.morav@nuvoton.com, tomer.maimon@nuvoton.com
Subject: [PATCH v2 2/2] char: tpm: add new driver for tpm i2c ptp
Date: Fri, 28 Jun 2019 18:13:27 +0300	[thread overview]
Message-ID: <20190628151327.206818-3-oshrialkoby85@gmail.com> (raw)
In-Reply-To: <20190628151327.206818-1-oshrialkoby85@gmail.com>

Signed-off-by: Oshri Alkoby <oshrialkoby85@gmail.com>
---
 drivers/char/tpm/Kconfig       |   22 +
 drivers/char/tpm/Makefile      |    1 +
 drivers/char/tpm/tpm_i2c_ptp.c | 1099 ++++++++++++++++++++++++++++++++
 3 files changed, 1122 insertions(+)
 create mode 100644 drivers/char/tpm/tpm_i2c_ptp.c

diff --git a/drivers/char/tpm/Kconfig b/drivers/char/tpm/Kconfig
index 88a3c06fc153..ea4138145191 100644
--- a/drivers/char/tpm/Kconfig
+++ b/drivers/char/tpm/Kconfig
@@ -97,6 +97,28 @@ config TCG_TIS_I2C_NUVOTON
 	  To compile this driver as a module, choose M here; the module
 	  will be called tpm_i2c_nuvoton.
 
+config TCG_TIS_I2C_PTP
+	tristate "TPM Interface Specification 1.2/2.0 Interface (I2C - PTP)"
+	depends on I2C
+	select CRC_CCITT
+	---help---
+	  If you have a TPM security chip with an I2C interface that impelements
+	  the TPM I2C interface protocol defined by the PTP 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_i2c_ptp.
+
+config TCG_TIS_I2C_PTP_MAX_SIZE
+	prompt "Max I2C Buffer Size"
+	depends on TCG_TIS_I2C_PTP
+	int
+	default 32
+	help
+	  Set the maximal I2C buffer size. This will alter the default value. A
+	  different size can be set by using a module parameter (i2c_max_size)
+	  as well. If no parameter is provided when loading, this is the value
+	  that will be used.
+
 config TCG_NSC
 	tristate "National Semiconductor TPM Interface"
 	depends on X86
diff --git a/drivers/char/tpm/Makefile b/drivers/char/tpm/Makefile
index a01c4cab902a..d736f22205cb 100644
--- a/drivers/char/tpm/Makefile
+++ b/drivers/char/tpm/Makefile
@@ -25,6 +25,7 @@ obj-$(CONFIG_TCG_TIS_SPI) += tpm_tis_spi.o
 obj-$(CONFIG_TCG_TIS_I2C_ATMEL) += tpm_i2c_atmel.o
 obj-$(CONFIG_TCG_TIS_I2C_INFINEON) += tpm_i2c_infineon.o
 obj-$(CONFIG_TCG_TIS_I2C_NUVOTON) += tpm_i2c_nuvoton.o
+obj-$(CONFIG_TCG_TIS_I2C_PTP) += tpm_i2c_ptp.o
 obj-$(CONFIG_TCG_NSC) += tpm_nsc.o
 obj-$(CONFIG_TCG_ATMEL) += tpm_atmel.o
 obj-$(CONFIG_TCG_INFINEON) += tpm_infineon.o
diff --git a/drivers/char/tpm/tpm_i2c_ptp.c b/drivers/char/tpm/tpm_i2c_ptp.c
new file mode 100644
index 000000000000..cc1065b79c2d
--- /dev/null
+++ b/drivers/char/tpm/tpm_i2c_ptp.c
@@ -0,0 +1,1099 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2014-2019 Nuvoton Technology corporation
+ *
+ * TPM I2C PTP
+ *
+ * TPM I2C Device Driver Interface for devices that implement the TPM I2C
+ * Interface defined by TCG PC Client Platform TPM Profile (PTP) Specification
+ * Revision 01.03 v22 at www.trustedcomputinggroup.org
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/wait.h>
+#include <linux/i2c.h>
+#include <linux/freezer.h>
+#include <linux/of_device.h>
+#include <linux/gpio.h>
+#include <linux/of_gpio.h>
+#include <linux/crc-ccitt.h>
+#include "tpm.h"
+
+/* I2C interface offsets */
+#define TPM_LOC_SEL                     0x00
+#define TPM_ACCESS                      0x04
+#define TPM_INT_ENABLE                  0x08
+#define TPM_INT_STATUS                  0x10
+#define TPM_INT_CAPABILITY              0x14
+#define TPM_STS                         0x18
+#define TPM_STS_BURST_COUNT             0x19
+#define TPM_DATA_FIFO                   0x24
+#define TPM_I2C_INTERFACE_CAPABILITY    0x30
+#define TPM_I2C_DEVICE_ADDRESS          0x38
+#define TPM_DATA_CSUM_ENABLE            0x40
+#define TPM_DATA_CSUM                   0x44
+#define TPM_VID_DID                     0x48
+#define TPM_RID                         0x4C
+
+ /* max. command/response length */
+#define TPM_I2C_BUFSIZE                 2048
+
+/* I2C bus device maximum buffer size w/o counting I2C address or command */
+#define TPM_I2C_MAX_BUF_SIZE            32
+
+#define TPM_I2C_MAX_RETRY_CNT           5
+#define TPM_I2C_RETRY_DELAY_SHORT_US    (2 * 1000)
+#define TPM_I2C_RETRY_DELAY_LONG_US     (10 * 1000)
+#define TPM_I2C_DELAY_RANGE_US          300
+
+#define OF_IS_TPM2 ((void *)1)
+#define I2C_IS_TPM2 1
+
+struct tpm_tis_data {
+	u16 manufacturer_id;
+	int locality;
+	int irq;
+	bool irq_tested;
+	unsigned int flags;
+	wait_queue_head_t int_queue;
+	wait_queue_head_t read_queue;
+	const struct tpm_tis_phy_ops *phy_ops;
+	unsigned int intrs;
+	unsigned short rng_quality;
+};
+
+#define MAX_COUNT               3
+#define SLEEP_DURATION_LOW      55
+#define SLEEP_DURATION_HI       65
+
+static int iic_tpm_read(struct i2c_client *client, u8 addr, u8 *buffer,
+			size_t len)
+{
+	struct i2c_msg msg1 = {
+		.addr = client->addr,
+		.len = 1,
+		.buf = &addr
+	};
+	struct i2c_msg msg2 = {
+		.addr = client->addr,
+		.flags = I2C_M_RD,
+		.len = len,
+		.buf = buffer
+	};
+	int rc = 0;
+	int count;
+
+	if (!client->adapter->algo->master_xfer)
+		return -EOPNOTSUPP;
+	i2c_lock_bus(client->adapter, I2C_LOCK_ROOT_ADAPTER);
+	for (count = 0; count < MAX_COUNT; count++) {
+		rc = __i2c_transfer(client->adapter, &msg1, 1);
+		if (rc > 0)
+			break;	/* break here to skip sleep */
+		usleep_range(SLEEP_DURATION_LOW, SLEEP_DURATION_HI);
+	}
+	if (rc <= 0)
+		goto out;
+	for (count = 0; count < MAX_COUNT; count++) {
+		rc = __i2c_transfer(client->adapter, &msg2, 1);
+		if (rc > 0)
+			break;
+		usleep_range(SLEEP_DURATION_LOW, SLEEP_DURATION_HI);
+	}
+out:
+	i2c_unlock_bus(client->adapter, I2C_LOCK_ROOT_ADAPTER);
+	usleep_range(SLEEP_DURATION_LOW, SLEEP_DURATION_HI);
+	if (rc <= 0)
+		return -EIO;
+	return len;
+}
+
+static u8 tpm_dev_buf[TPM_I2C_BUFSIZE + sizeof(u8)]; /* max buff size + addr */
+
+#ifdef CONFIG_TCG_TIS_I2C_PTP_MAX_SIZE
+	static int i2c_max_size = CONFIG_TCG_TIS_I2C_PTP_MAX_SIZE;
+#else
+	static int i2c_max_size = TPM_I2C_MAX_BUF_SIZE;
+#endif
+module_param(i2c_max_size, int, 0660);
+
+static int iic_tpm_write_generic(struct i2c_client *client,
+				 u8 addr, u8 *buffer, size_t len,
+				 unsigned int sleep_low,
+				 unsigned int sleep_hi, u8 max_count)
+{
+	int rc = -EIO;
+	int count;
+	struct i2c_msg msg1 = {
+		.addr = client->addr,
+		.len = len + 1,
+		.buf = tpm_dev_buf
+	};
+
+	if (len > TPM_BUFSIZE)
+		return -EINVAL;
+	if (!client->adapter->algo->master_xfer)
+		return -EOPNOTSUPP;
+	i2c_lock_bus(client->adapter, I2C_LOCK_ROOT_ADAPTER);
+	tpm_dev_buf[0] = addr;
+	memcpy(&tpm_dev_buf[1], buffer, len);
+	for (count = 0; count < max_count; count++) {
+		rc = __i2c_transfer(client->adapter, &msg1, 1);
+		if (rc > 0)
+			break;
+		usleep_range(sleep_low, sleep_hi);
+	}
+	i2c_unlock_bus(client->adapter, I2C_LOCK_ROOT_ADAPTER);
+	usleep_range(SLEEP_DURATION_LOW, SLEEP_DURATION_HI);
+	if (rc <= 0)
+		return -EIO;
+	return 0;
+}
+
+static s32 i2c_ptp_read_buf(struct i2c_client *client, u8 offset, size_t size,
+			    u8 *data)
+{
+	s32 status;
+
+	status = iic_tpm_read(client, offset, data, size);
+	dev_dbg(&client->dev,
+		"%s(offset=%u size=%zu data=%*ph) -> sts=%d\n", __func__,
+		offset, size, (int)size, data, status);
+	return status;
+}
+
+static s32 i2c_ptp_write_buf(struct i2c_client *client, u8 offset, size_t size,
+			     u8 *data)
+{
+	s32 status;
+
+	status = iic_tpm_write_generic(client, offset, data, size,
+				       SLEEP_DURATION_LOW, SLEEP_DURATION_HI,
+				       MAX_COUNT);
+	dev_dbg(&client->dev,
+		"%s(offset=%u size=%zu data=%*ph) -> sts=%d\n", __func__,
+		offset, size, (int)size, data, status);
+
+	return status;
+}
+
+#define TPM_INT_DATA_AVAIL		(BIT(0))
+#define TPM_INT_STS_VALID		(BIT(1))
+#define TPM_INT_LOC_CHANGED		(BIT(2))
+#define TPM_INT_CMD_READY		(BIT(7))
+#define TPM_INT_GLOBAL			(BIT(31))
+
+#define TPM_INT_TO_ACTIVATE		(TPM_INT_DATA_AVAIL)
+
+static bool wait_for_tpm_stat_cond(struct tpm_chip *chip, u8 mask,
+				   bool check_cancel, bool *canceled)
+{
+	u8 status = chip->ops->status(chip);
+
+	*canceled = false;
+	if (status != 0xff && (status & mask) == mask)
+		return true;
+	if (check_cancel && chip->ops->req_canceled(chip, status)) {
+		*canceled = true;
+		return true;
+	}
+	return false;
+}
+
+int i2c_ptp_wait_for_tpm_stat_l(struct tpm_chip *chip, u8 mask,
+				unsigned long timeout, wait_queue_head_t *queue,
+				bool check_cancel)
+{
+	struct tpm_tis_data *priv = dev_get_drvdata(&chip->dev);
+	unsigned long stop, start, long_delay;
+	long rc;
+	u8 status;
+	bool canceled = false;
+	unsigned int cur_intrs;
+
+	start = jiffies;
+	stop = start + timeout;
+	long_delay = start + usecs_to_jiffies(TPM_I2C_RETRY_DELAY_LONG_US);
+
+	if (queue && chip->flags & TPM_CHIP_FLAG_IRQ) {
+again:
+		cur_intrs = priv->intrs;
+		timeout = stop - jiffies;
+		if ((long)timeout <= 0)
+			return -ETIME;
+
+		enable_irq(priv->irq);
+		rc = wait_event_interruptible_timeout(*queue,
+						      ((cur_intrs != priv->intrs) &&
+						      wait_for_tpm_stat_cond(chip, mask, check_cancel, &canceled)),
+						      timeout);
+		if (rc > 0) {
+			if (canceled)
+				return -ECANCELED;
+			return 0;
+		}
+		if (rc == -ERESTARTSYS && freezing(current)) {
+			clear_thread_flag(TIF_SIGPENDING);
+			goto again;
+		}
+	} else {
+		do {
+			status = chip->ops->status(chip);
+			if (status != 0xff && (status & mask) == mask)
+				return 0;
+
+			if (time_before(jiffies, long_delay))
+				usleep_range(TPM_I2C_RETRY_DELAY_SHORT_US,
+					     TPM_I2C_RETRY_DELAY_SHORT_US
+					     + TPM_I2C_DELAY_RANGE_US);
+			else
+				usleep_range(TPM_I2C_RETRY_DELAY_LONG_US,
+					     TPM_I2C_RETRY_DELAY_LONG_US
+					     + TPM_I2C_DELAY_RANGE_US);
+
+		} while (time_before(jiffies, stop));
+	}
+	return -ETIME;
+}
+
+/* wrapper of wait_for_tpm_stat, since we can't do the int processing during
+ * the int_handler in I2C, here we do it after the int_handler is done and
+ * wait_for_tpm_stat retruns
+ */
+static int i2c_ptp_wait_for_tpm_stat(struct tpm_chip *chip, u8 mask,
+				     unsigned long timeout,
+				     wait_queue_head_t *queue,
+				     bool check_cancel)
+{
+	struct i2c_client *client = to_i2c_client(chip->dev.parent);
+	u32 intsts;
+	int rc;
+
+	rc = i2c_ptp_wait_for_tpm_stat_l(chip, mask, timeout, queue,
+					 check_cancel);
+	if (rc < 0)
+		return rc;
+
+	if (chip->flags & TPM_CHIP_FLAG_IRQ) {
+		rc = i2c_ptp_read_buf(client, TPM_INT_STATUS, 4, (u8 *)&intsts);
+		if (rc < 0) {
+			dev_err(&chip->dev,
+				"%s() fail to read TPM_INT_STATUS\n", __func__);
+			return -EIO;
+		}
+
+		/* Clear interrupts handled */
+		rc = i2c_ptp_write_buf(client, TPM_INT_STATUS, 4,
+				       (u8 *)&intsts);
+		if (rc < 0) {
+			dev_err(&chip->dev,
+				"%s() fail to clear int status\n", __func__);
+			return -EIO;
+		}
+	}
+
+	return 0;
+}
+
+#define TPM_ACCESS_REQUEST_USE		(BIT(1))
+#define TPM_ACCESS_REQUEST_PENDING	(BIT(2))
+#define TPM_ACCESS_ACTIVE_LOCALITY	(BIT(5))
+#define TPM_ACCESS_VALID_STS		(BIT(7))
+
+/* Before we attempt to access the TPM we must see that the valid bit is set.
+ * The specification says that this bit is 0 at reset and remains 0 until the
+ * 'TPM has gone through its self test and initialization and has established
+ * correct values in the other bits.'
+ */
+static int wait_startup(struct tpm_chip *chip, int l)
+{
+	struct i2c_client *client = to_i2c_client(chip->dev.parent);
+	unsigned long stop = jiffies + chip->timeout_a;
+
+	do {
+		int rc;
+		u8 access;
+
+		rc = i2c_ptp_read_buf(client, TPM_ACCESS, 1, &access);
+		if (rc < 0)
+			return rc;
+
+		if (access != 0xff && (access & TPM_ACCESS_VALID_STS) != 0)
+			return 0;
+		tpm_msleep(TPM_TIMEOUT);
+	} while (time_before(jiffies, stop));
+	return -1;
+}
+
+static bool i2c_ptp_check_locality(struct tpm_chip *chip, int l)
+{
+	struct i2c_client *client = to_i2c_client(chip->dev.parent);
+	struct tpm_tis_data *priv = dev_get_drvdata(&chip->dev);
+	int rc;
+	u8 access;
+	u8 data;
+
+	data = (u8)l;
+	rc = i2c_ptp_write_buf(client, TPM_LOC_SEL, 1, &data);
+	if (rc < 0)
+		return false;
+
+	rc = i2c_ptp_read_buf(client, TPM_ACCESS, 1, &access);
+	if (rc < 0)
+		return false;
+
+	if ((access & (TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID_STS)) ==
+	    (TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID_STS)) {
+		priv->locality = l;
+		return true;
+	}
+
+	return false;
+}
+
+static bool i2c_ptp_locality_inactive(struct tpm_chip *chip, int l)
+{
+	struct i2c_client *client = to_i2c_client(chip->dev.parent);
+	int rc;
+	u8 access;
+	u8 data;
+
+	data = (u8)l;
+	rc = i2c_ptp_write_buf(client, TPM_LOC_SEL, 1, &data);
+	if (rc < 0)
+		return false;
+
+	rc = i2c_ptp_read_buf(client, TPM_ACCESS, 1, &access);
+	if (rc < 0)
+		return false;
+
+	if ((access & (TPM_ACCESS_VALID_STS | TPM_ACCESS_ACTIVE_LOCALITY))
+	    == TPM_ACCESS_VALID_STS)
+		return true;
+
+	return false;
+}
+
+static int i2c_ptp_release_locality(struct tpm_chip *chip, int l)
+{
+	struct i2c_client *client = to_i2c_client(chip->dev.parent);
+	unsigned long stop;
+	int rc;
+	u8 data;
+
+	if (i2c_ptp_locality_inactive(chip, l))
+		return 0;
+
+	data = TPM_ACCESS_ACTIVE_LOCALITY;
+	rc = i2c_ptp_write_buf(client, TPM_ACCESS, 1, &data);
+	if (rc < 0)
+		return rc;
+
+	stop = jiffies + chip->timeout_a;
+	do {
+		if (i2c_ptp_locality_inactive(chip, l))
+			return 0;
+
+		tpm_msleep(TPM_TIMEOUT);
+	} while (time_before(jiffies, stop));
+
+	return -1;
+}
+
+static int i2c_ptp_request_locality(struct tpm_chip *chip, int l)
+{
+	struct i2c_client *client = to_i2c_client(chip->dev.parent);
+	unsigned long stop;
+	int rc;
+	u8 data;
+
+	if (i2c_ptp_check_locality(chip, l))
+		return l;
+
+	data = TPM_ACCESS_REQUEST_USE;
+	rc = i2c_ptp_write_buf(client, TPM_ACCESS, 1, &data);
+	if (rc < 0)
+		return rc;
+
+	stop = jiffies + chip->timeout_a;
+
+	do {
+		if (i2c_ptp_check_locality(chip, l))
+			return l;
+
+		rc = i2c_ptp_write_buf(client, TPM_ACCESS, 1, &data);
+		if (rc < 0)
+			return rc;
+
+		tpm_msleep(TPM_TIMEOUT);
+	} while (time_before(jiffies, stop));
+
+	return -1;
+}
+
+#define TPM_STS_RESPONSE_RETRY (BIT(1))
+#define TPM_STS_SELFTEST_DONE  (BIT(2))
+#define TPM_STS_EXPECT         (BIT(3))
+#define TPM_STS_DATA_AVAIL     (BIT(4))
+#define TPM_STS_GO             (BIT(5))
+#define TPM_STS_COMMAND_READY  (BIT(6))
+#define TPM_STS_VALID          (BIT(7))
+#define TPM_STS_COMMAND_CANCEL (BIT(24))
+
+/* bit1...bit0 reads always 0 */
+#define TPM_STS_ERR_VAL        (BIT(0) | BIT(1))
+
+/* read TPM_STS register */
+static u8 i2c_ptp_status(struct tpm_chip *chip)
+{
+	struct i2c_client *client = to_i2c_client(chip->dev.parent);
+	int rc;
+	u8 status;
+
+	rc = i2c_ptp_read_buf(client, TPM_STS, 1, &status);
+	if (rc < 0)
+		return 0;
+
+	return status;
+}
+
+/* write commandReady to TPM_STS register */
+static void i2c_ptp_ready(struct tpm_chip *chip)
+{
+	struct i2c_client *client = to_i2c_client(chip->dev.parent);
+	u8 data;
+
+	data = TPM_STS_COMMAND_READY;
+	i2c_ptp_write_buf(client, TPM_STS, 1, &data);
+}
+
+/* read Burst Count */
+static int i2c_ptp_get_burstcount(struct tpm_chip *chip)
+{
+	struct i2c_client *client = to_i2c_client(chip->dev.parent);
+	unsigned long stop;
+	int burstcnt = 0, rc;
+
+	/* wait for burstcount */
+	stop = jiffies + chip->timeout_a;
+
+	do {
+		rc = i2c_ptp_read_buf(client, TPM_STS_BURST_COUNT, 2,
+				      (u8 *)&burstcnt);
+		if (rc < 0)
+			return rc;
+
+		if (burstcnt)
+			return burstcnt;
+
+		usleep_range(TPM_TIMEOUT_USECS_MIN, TPM_TIMEOUT_USECS_MAX);
+	} while (time_before(jiffies, stop));
+
+	return -EBUSY;
+}
+
+/* write commandCancel to TPM_STS register */
+static void i2c_ptp_cancel(struct tpm_chip *chip)
+{
+	struct i2c_client *client = to_i2c_client(chip->dev.parent);
+	u32 data;
+
+	data = TPM_STS_COMMAND_CANCEL;
+	i2c_ptp_write_buf(client, TPM_STS, 4, (u8 *)&data);
+}
+
+/* enable checksum */
+static int i2c_ptp_enable_csum(struct tpm_chip *chip)
+{
+	struct i2c_client *client = to_i2c_client(chip->dev.parent);
+	s32 rc;
+	u8 data = 1;
+
+	/* Enable CSUM */
+	rc = i2c_ptp_write_buf(client, TPM_DATA_CSUM_ENABLE, 1, &data);
+	if (rc < 0) {
+		dev_err(&chip->dev,
+			"%s() fail to read to TPM_DATA_CSUM_ENABLE: %d\n",
+			__func__, rc);
+		return rc;
+	}
+	return 0;
+}
+
+/* Caclculate crc16 of the buffer and compares it to TPM_DATA_CSUM */
+static int i2c_ptp_check_crc(struct tpm_chip *chip, u8 *buf, size_t len)
+{
+	struct i2c_client *client = to_i2c_client(chip->dev.parent);
+	s32 status;
+	u16 tpm_csum = 0;
+	u16 crc = 0;
+
+	crc = crc_ccitt(crc, buf, len);
+	crc = be16_to_cpu(crc);
+	status = i2c_ptp_read_buf(client, TPM_DATA_CSUM, 2, (u8 *)&tpm_csum);
+	if (status <= 0) {
+		dev_err(&chip->dev, "%s() fail to read to TPM_DATA_CSUM: %d\n",
+			__func__, status);
+		return -EIO;
+	}
+
+	if (tpm_csum != crc) {
+		dev_err(&chip->dev, "%s() bad data csum\n", __func__);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int i2c_ptp_recv_data(struct tpm_chip *chip, u8 *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(chip->dev.parent);
+	int size = 0, burstcnt, rc, bytes2read = count;
+	bool paramsize_flag = false;
+
+	while (size < bytes2read) {
+		burstcnt = i2c_ptp_get_burstcount(chip);
+		if (burstcnt <= 0)
+			return burstcnt;
+
+		burstcnt = min_t(int, (bytes2read - size), burstcnt);
+		rc = i2c_ptp_read_buf(client, TPM_DATA_FIFO, burstcnt,
+				      buf + size);
+		if (rc < 0)
+			return rc;
+
+		size += burstcnt;
+
+		if (size >= 6 && !paramsize_flag) {
+			bytes2read = be32_to_cpu(*(__be32 *)(buf + 2));
+			paramsize_flag = true;
+		}
+	}
+
+	return size;
+}
+
+static int i2c_ptp_recv(struct tpm_chip *chip, u8 *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(chip->dev.parent);
+	int size = 0;
+	int status, retries;
+
+	if (count < TPM_HEADER_SIZE) {
+		status = -EIO;
+		goto out;
+	}
+
+	for (retries = 0; retries < TPM_I2C_MAX_RETRY_CNT; retries++) {
+		size = 0;
+
+		if (retries > 0) {
+			/* if this is not the first trial, set responseRetry */
+			u8 data = TPM_STS_RESPONSE_RETRY;
+
+			i2c_ptp_write_buf(client, TPM_STS, 1, &data);
+		}
+
+		status = i2c_ptp_wait_for_tpm_stat(chip,
+						   (TPM_STS_DATA_AVAIL | TPM_STS_VALID),
+						   chip->timeout_b, NULL,
+						   false);
+		if (status < 0)
+			return status;
+
+		size = i2c_ptp_recv_data(chip, buf, TPM_HEADER_SIZE);
+		if (size < TPM_HEADER_SIZE)
+			continue;
+
+		status = i2c_ptp_wait_for_tpm_stat(chip, TPM_STS_VALID,
+						   chip->timeout_a, NULL,
+						   false);
+		if (status < 0)
+			continue;
+
+		/* check CRC */
+		status = i2c_ptp_check_crc(chip, buf, size);
+		if (status < 0)
+			continue;
+
+		status = size;
+		break;
+	}
+
+out:
+	i2c_ptp_ready(chip);
+	return status;
+}
+
+/*
+ * If interrupts are used (signaled by an irq set in the vendor structure)
+ * tpm.c can skip polling for the data to be available as the interrupt is
+ * waited for here
+ */
+static int i2c_ptp_send_data(struct tpm_chip *chip, u8 *buf, size_t len)
+{
+	struct i2c_client *client = to_i2c_client(chip->dev.parent);
+	int rc, status, burstcnt, retries;
+	size_t count = 0, bytes2send;
+	u32 ordinal = be32_to_cpu(*((__be32 *)(buf + 6)));
+	u32 intmask;
+
+	/* When NPCT7XX FU Mode command or startup (when TPM I2C may be has
+	 * been reset), wait for STS_VALID and re-initialize I2C regs
+	 */
+	if (ordinal == 0x20000201 || ordinal == 0x144) {
+		// wait for I2C to be ready
+		if (wait_startup(chip, 0) != 0)
+			return -ENODEV;
+
+		rc = i2c_ptp_enable_csum(chip);
+		if (rc < 0) {
+			dev_err(&chip->dev, "%s() fail to enable checksum\n",
+				__func__);
+			return rc;
+		}
+
+		// in interrupt mode re-eanble and clear the interrupts
+		if (chip->flags & TPM_CHIP_FLAG_IRQ) {
+			intmask = (TPM_INT_TO_ACTIVATE | TPM_INT_GLOBAL);
+			i2c_ptp_write_buf(client, TPM_INT_ENABLE, 4,
+					  (u8 *)&intmask);
+			i2c_ptp_write_buf(client, TPM_INT_STATUS, 4,
+					  (u8 *)&intmask);
+		}
+	}
+
+	for (retries = 0; retries < TPM_I2C_MAX_RETRY_CNT; retries++) {
+		count = 0;
+		rc = -1;
+
+		if (retries > 0) {
+			/* if this is not the first trial, abort the command */
+			i2c_ptp_ready(chip);
+		}
+
+		status = i2c_ptp_status(chip);
+		if ((status & TPM_STS_COMMAND_READY) == 0) {
+			i2c_ptp_ready(chip);
+			if (i2c_ptp_wait_for_tpm_stat
+			    (chip, TPM_STS_COMMAND_READY, chip->timeout_b,
+			     NULL, false) < 0) {
+				rc = -ETIME;
+				continue;
+			}
+		}
+
+		while (count < len) {
+			burstcnt = i2c_ptp_get_burstcount(chip);
+			if (burstcnt < 0) {
+				dev_err(&chip->dev, "Unable to read burstcount\n");
+				rc = burstcnt;
+				break;
+			}
+
+			bytes2send = min_t(int, i2c_max_size, burstcnt);
+			bytes2send = min_t(int, bytes2send, (len - count));
+			rc = i2c_ptp_write_buf(client, TPM_DATA_FIFO,
+					       bytes2send, buf + count);
+			if (rc < 0)
+				break;
+
+			count += bytes2send;
+		}
+
+		if (rc < 0)
+			continue;
+
+		if (i2c_ptp_wait_for_tpm_stat(chip, TPM_STS_VALID,
+					      chip->timeout_a, NULL,
+					      false) < 0) {
+			rc = -ETIME;
+			continue;
+		}
+
+		/* check CRC */
+		rc = i2c_ptp_check_crc(chip, buf, len);
+		if (rc < 0)
+			continue;
+
+		return 0;
+	}
+
+	i2c_ptp_ready(chip);
+	return rc;
+}
+
+static void disable_interrupts(struct tpm_chip *chip)
+{
+	struct i2c_client *client = to_i2c_client(chip->dev.parent);
+	struct tpm_tis_data *priv = dev_get_drvdata(&chip->dev);
+	u32 intmask;
+	int rc;
+
+	rc = i2c_ptp_read_buf(client, TPM_INT_ENABLE, 4, (u8 *)&intmask);
+	if (rc < 0)
+		intmask = 0;
+
+	intmask &= ~TPM_INT_GLOBAL;
+	rc = i2c_ptp_write_buf(client, TPM_INT_ENABLE, 4, (u8 *)&intmask);
+	if (rc < 0)
+		dev_err(&chip->dev, "%s() fail to write TPM_INT_ENABLE\n",
+			__func__);
+
+	devm_free_irq(chip->dev.parent, priv->irq, chip);
+	priv->irq = 0;
+	chip->flags &= ~TPM_CHIP_FLAG_IRQ;
+}
+
+/*
+ * If interrupts are used (signaled by an irq set in the vendor structure)
+ * tpm.c can skip polling for the data to be available as the interrupt is
+ * waited for here
+ */
+static int i2c_ptp_send_main(struct tpm_chip *chip, u8 *buf, size_t len)
+{
+	struct i2c_client *client = to_i2c_client(chip->dev.parent);
+	struct tpm_tis_data *priv = dev_get_drvdata(&chip->dev);
+	int rc;
+	u32 ordinal;
+	unsigned long dur;
+	u8 data;
+
+	rc = i2c_ptp_send_data(chip, buf, len);
+	if (rc < 0)
+		return rc;
+
+	/* go and do it */
+	data = TPM_STS_GO;
+	rc = i2c_ptp_write_buf(client, TPM_STS, 1, &data);
+	if (rc < 0)
+		goto out_err;
+
+	ordinal = be32_to_cpu(*((__be32 *)(buf + 6)));
+
+	dur = tpm_calc_ordinal_duration(chip, ordinal);
+	if (i2c_ptp_wait_for_tpm_stat
+	    (chip, TPM_STS_DATA_AVAIL | TPM_STS_VALID, dur,
+	     &priv->int_queue, false) < 0) {
+		rc = -ETIME;
+		goto out_err;
+	}
+
+	return 0;
+out_err:
+	i2c_ptp_ready(chip);
+	return rc;
+}
+
+static int i2c_ptp_send(struct tpm_chip *chip, u8 *buf, size_t len)
+{
+	int rc, irq;
+	struct tpm_tis_data *priv = dev_get_drvdata(&chip->dev);
+
+	if (!(chip->flags & TPM_CHIP_FLAG_IRQ) || priv->irq_tested)
+		return i2c_ptp_send_main(chip, buf, len);
+
+	/* Verify receipt of the expected IRQ */
+	irq = priv->irq;
+	chip->flags &= ~TPM_CHIP_FLAG_IRQ;
+	rc = i2c_ptp_send_main(chip, buf, len);
+	priv->irq = irq;
+	chip->flags |= TPM_CHIP_FLAG_IRQ;
+	if (!priv->irq_tested)
+		tpm_msleep(1);
+	if (!priv->irq_tested)
+		disable_interrupts(chip);
+	priv->irq_tested = true;
+	return rc;
+}
+
+static bool i2c_ptp_req_canceled(struct tpm_chip *chip, u8 status)
+{
+	return (status == TPM_STS_COMMAND_READY);
+}
+
+/* The only purpose for the handler is to signal to any waiting threads that
+ * the interrupt is currently being asserted. The driver does not do any
+ * processing triggered by interrupts, and the chip provides no way to mask at
+ * the source (plus that would be slow over I2C). Run the IRQ as a one-shot,
+ * this means it cannot be shared. The processing of the interrupt status
+ * is done in i2c_ptp_wait_for_tpm_stat
+ */
+static irqreturn_t i2c_ptp_int_handler(int dummy, void *dev_id)
+{
+	struct tpm_chip *chip = dev_id;
+	struct tpm_tis_data *priv = dev_get_drvdata(&chip->dev);
+
+	priv->irq_tested = true;
+	priv->intrs++;
+	wake_up_interruptible(&priv->int_queue);
+	disable_irq_nosync(priv->irq);
+	return IRQ_HANDLED;
+}
+
+static int i2c_ptp_gen_interrupt(struct tpm_chip *chip)
+{
+	const char *desc = "attempting to generate an interrupt";
+	u32 cap2;
+
+	return tpm2_get_tpm_pt(chip, 0x100, &cap2, desc);
+}
+
+/* Register the IRQ and issue a command that will cause an interrupt. If an
+ * irq is seen then leave the chip setup for IRQ operation, otherwise reverse
+ * everything and leave in polling mode. Returns 0 on success.
+ */
+static int i2c_ptp_probe_irq_single(struct tpm_chip *chip, u32 intmask,
+				    int flags, int irq)
+{
+	struct i2c_client *client = to_i2c_client(chip->dev.parent);
+	struct tpm_tis_data *priv = dev_get_drvdata(&chip->dev);
+	int rc;
+	u32 int_support;
+
+	if (devm_request_irq(chip->dev.parent, irq, i2c_ptp_int_handler, flags,
+			     dev_name(&chip->dev), chip) != 0) {
+		dev_info(&chip->dev, "Unable to request irq: %d for probe\n",
+			 irq);
+		return -1;
+	}
+	priv->irq = irq;
+
+	chip->flags |= TPM_CHIP_FLAG_IRQ;
+
+	/* check what are the interrupts that the TPM supports */
+	rc = i2c_ptp_read_buf(client, TPM_INT_CAPABILITY, 4, (u8 *)
+			   &int_support);
+	if (rc < 0)
+		return rc;
+
+	/* Clear all existing */
+	rc = i2c_ptp_write_buf(client, TPM_INT_STATUS, 4, (u8 *)&int_support);
+	if (rc < 0)
+		return rc;
+
+	/* Turn on */
+	int_support &= TPM_INT_TO_ACTIVATE;
+	int_support |= TPM_INT_GLOBAL;
+	rc = i2c_ptp_write_buf(client, TPM_INT_ENABLE, 4, (u8 *)&int_support);
+	if (rc < 0)
+		return rc;
+
+	priv->irq_tested = false;
+
+	/* Generate an interrupt by having the core call through to
+	 * i2c_ptp_send
+	 */
+	rc = i2c_ptp_gen_interrupt(chip);
+	if (rc < 0)
+		return rc;
+
+	return 0;
+}
+
+static int i2c_ptp_remove(struct i2c_client *client)
+{
+	u32 interrupt;
+	int rc;
+
+	rc = i2c_ptp_read_buf(client, TPM_INT_ENABLE, 4, (u8 *)&interrupt);
+	if (rc < 0)
+		interrupt = 0;
+
+	// Clear and disable interrupts
+	interrupt &= ~TPM_INT_GLOBAL;
+	rc = i2c_ptp_write_buf(client, TPM_INT_STATUS, 4, (u8 *)&interrupt);
+	rc = i2c_ptp_write_buf(client, TPM_INT_ENABLE, 4, (u8 *)&interrupt);
+
+	return rc;
+}
+EXPORT_SYMBOL_GPL(i2c_ptp_remove);
+
+static const struct tpm_class_ops tpm_i2c = {
+	.flags = TPM_OPS_AUTO_STARTUP,
+	.status = i2c_ptp_status,
+	.recv = i2c_ptp_recv,
+	.send = i2c_ptp_send,
+	.cancel = i2c_ptp_cancel,
+	.req_complete_mask = TPM_STS_DATA_AVAIL | TPM_STS_VALID,
+	.req_complete_val = TPM_STS_DATA_AVAIL | TPM_STS_VALID,
+	.req_canceled = i2c_ptp_req_canceled,
+	.request_locality = i2c_ptp_request_locality,
+	.relinquish_locality = i2c_ptp_release_locality,
+};
+
+int i2c_ptp_core_init(struct i2c_client *client, struct tpm_tis_data *priv,
+		      int irq, const struct tpm_tis_phy_ops *phy_ops,
+		      acpi_handle acpi_dev_handle)
+{
+	u32 vendor;
+	u32 intmask;
+	u8 rid;
+	int rc;
+	struct tpm_chip *chip;
+	struct device *dev = &client->dev;
+
+	chip = tpmm_chip_alloc(dev, &tpm_i2c);
+	if (IS_ERR(chip))
+		return PTR_ERR(chip);
+
+	chip->hwrng.quality = priv->rng_quality;
+
+	/* Maximum timeouts */
+	chip->timeout_a = msecs_to_jiffies(TPM2_TIMEOUT_A);
+	chip->timeout_b = msecs_to_jiffies(TPM2_TIMEOUT_B);
+	chip->timeout_c = msecs_to_jiffies(TPM2_TIMEOUT_C);
+	chip->timeout_d = msecs_to_jiffies(TPM2_TIMEOUT_D);
+	priv->phy_ops = phy_ops;
+	priv->intrs = 0;
+	dev_set_drvdata(&chip->dev, priv);
+
+	if (wait_startup(chip, 0) != 0) {
+		rc = -ENODEV;
+		goto out_err;
+	}
+
+	/* Take control of the TPM's interrupt hardware and shut it off */
+	rc = i2c_ptp_read_buf(client, TPM_INT_ENABLE, 4, (u8 *)&intmask);
+	if (rc < 0)
+		goto out_err;
+
+	intmask = TPM_INT_TO_ACTIVATE;	// global should be off now
+	i2c_ptp_write_buf(client, TPM_INT_ENABLE, 4, (u8 *)&intmask);
+
+	rc = i2c_ptp_enable_csum(chip);
+	if (rc < 0) {
+		dev_err(&chip->dev, "%s() fail to enable checksum\n", __func__);
+		rc = -ENODEV;
+		goto out_err;
+	}
+
+	rc = tpm_chip_start(chip);
+	if (rc)
+		goto out_err;
+
+	rc = tpm2_probe(chip);
+	if (rc)
+		goto out_err;
+
+	rc = i2c_ptp_read_buf(client, TPM_VID_DID, 4, (u8 *)&vendor);
+	if (rc < 0)
+		goto out_err;
+
+	priv->manufacturer_id = vendor;
+
+	rc = i2c_ptp_read_buf(client, TPM_RID, 1, &rid);
+	if (rc < 0)
+		goto out_err;
+
+	dev_info(dev, "%s TPM (device-id 0x%X, rev-id %d)\n",
+		 (chip->flags & TPM_CHIP_FLAG_TPM2) ? "2.0" : "1.2",
+		 vendor >> 16, rid);
+
+	/* INTERRUPT Setup */
+	init_waitqueue_head(&priv->int_queue);
+	if (irq != -1) {
+		/* Before doing irq testing issue a command to the TPM in polling mode
+		 * to make sure it works. May as well use that command to set the
+		 * proper timeouts for the driver.
+		 */
+		if (tpm_get_timeouts(chip)) {
+			dev_err(dev, "Could not get TPM timeouts and durations\n");
+			rc = -ENODEV;
+			goto out_err;
+		}
+
+		i2c_ptp_probe_irq_single(chip, intmask,	IRQF_TRIGGER_LOW, irq);
+		if (!(chip->flags & TPM_CHIP_FLAG_IRQ))
+			dev_err(&chip->dev, FW_BUG
+				"TPM interrupt not working, polling instead\n");
+	}
+
+	rc = tpm_chip_register(chip);
+	if (rc)
+		goto out_err;
+
+	return 0;
+
+out_err:
+	i2c_ptp_remove(client);
+
+	return rc;
+}
+
+static int i2c_ptp_probe(struct i2c_client *client,
+			 const struct i2c_device_id *id)
+{
+	int irq = -1, gpio;
+	struct tpm_tis_data *priv;
+
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
+		return -ENODEV;
+
+	priv = devm_kzalloc(&client->dev, sizeof(struct tpm_tis_data),
+			    GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	if (i2c_max_size < 1)
+		i2c_max_size = CONFIG_TCG_TIS_I2C_PTP_MAX_SIZE;
+
+	// Get interrupt from board device
+	if (client->irq) {
+		irq = client->irq;
+		goto out;
+	}
+
+	// If IRQ doesn't exists, get GPIO from device tree
+	gpio = of_get_named_gpio(client->dev.of_node, "tpm-pirq", 0);
+	if (gpio < 0) {
+		dev_err(&client->dev, "Failed to retrieve tpm-pirq\n");
+		goto out;
+	}
+
+	// GPIO request and configuration
+	if (devm_gpio_request_one(&client->dev, gpio, GPIOF_IN, "TPM PIRQ")) {
+		dev_err(&client->dev, "Failed to request tpm-pirq pin\n");
+		goto out;
+	}
+
+	irq = gpio_to_irq(gpio);
+
+out:
+	return i2c_ptp_core_init(client, priv, irq, NULL, 0);
+}
+
+static const struct of_device_id of_i2c_ptp_match[] = {
+	{.compatible = "tcg,tpm_i2c_ptp", .data = OF_IS_TPM2},
+	{},
+};
+
+static const struct i2c_device_id i2c_ptp_id[] = {
+	{"tpm_i2c_ptp"},
+	{"tpm2_i2c_ptp", .driver_data = I2C_IS_TPM2},
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, i2c_ptp_id);
+
+static SIMPLE_DEV_PM_OPS(i2c_ptp_pm_ops, tpm_pm_suspend, tpm_pm_resume);
+
+static struct i2c_driver i2c_ptp_driver = {
+	.id_table = i2c_ptp_id,
+	.probe = i2c_ptp_probe,
+	.remove = i2c_ptp_remove,
+	.driver = {
+		.name = "tpm_i2c_ptp",
+		.pm = &i2c_ptp_pm_ops,
+		.of_match_table = of_match_ptr(of_i2c_ptp_match),
+	},
+};
+module_i2c_driver(i2c_ptp_driver);
+
+MODULE_AUTHOR("Oshri Alkoby (oshri.alkoby@nuvoton.com)");
+MODULE_DESCRIPTION("TPM I2C Driver");
+MODULE_VERSION("2.1.3");
+MODULE_LICENSE("GPL");
-- 
2.18.0


  parent reply	other threads:[~2019-06-28 15:14 UTC|newest]

Thread overview: 29+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-06-28 15:13 [PATCH v2 0/2] char: tpm: add new driver for tpm i2c ptp Oshri Alkoby
2019-06-28 15:13 ` [PATCH v2 1/2] dt-bindings: tpm: add the TPM I2C PTP device tree binding documentation Oshri Alkoby
2019-06-28 15:13 ` Oshri Alkoby [this message]
2019-07-04  8:43 ` [PATCH v2 0/2] char: tpm: add new driver for tpm i2c ptp Jarkko Sakkinen
2019-07-04 11:29   ` Alexander Steffen
2019-07-04 11:29     ` Alexander Steffen
2019-07-04 17:48     ` Oshri Alkobi
2019-07-05 11:28       ` Jarkko Sakkinen
     [not found]         ` <6e7ff1b958d84f6e8e585fd3273ef295@NTILML02.nuvoton.com>
2019-07-15  8:08           ` Tomer Maimon
2019-07-15  9:45             ` Jarkko Sakkinen
2019-07-15  9:45               ` Jarkko Sakkinen
2019-07-18 12:51               ` Eyal.Cohen
2019-07-18 12:51                 ` Eyal.Cohen
2019-07-18 12:51                 ` Eyal.Cohen
2019-07-18 17:10                 ` Alexander Steffen
2019-07-18 17:10                   ` Alexander Steffen
2019-07-30  8:39                   ` Benoit HOUYERE
2019-07-30  8:39                     ` Benoit HOUYERE
2019-07-30 17:42                     ` Alexander Steffen
2019-07-30 17:42                       ` Alexander Steffen
2019-09-06 12:16                       ` Benoit HOUYERE
2019-09-06 12:16                         ` Benoit HOUYERE
2019-08-15 17:03                   ` Oshri Alkobi
2019-08-16 16:12                     ` Alexander Steffen
2019-08-16 16:12                       ` Alexander Steffen
2019-08-25 11:25                       ` Oshri Alkobi
2019-07-17  7:48             ` Alexander Steffen
2019-07-17  7:48               ` Alexander Steffen
2019-07-05 11:15     ` 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=20190628151327.206818-3-oshrialkoby85@gmail.com \
    --to=oshrialkoby85@gmail.com \
    --cc=arnd@arndb.de \
    --cc=dan.morav@nuvoton.com \
    --cc=devicetree@vger.kernel.org \
    --cc=gcwilson@us.ibm.com \
    --cc=gregkh@linuxfoundation.org \
    --cc=jarkko.sakkinen@linux.intel.com \
    --cc=jgg@ziepe.ca \
    --cc=kgoldman@us.ibm.com \
    --cc=linux-integrity@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=mark.rutland@arm.com \
    --cc=nayna@linux.vnet.ibm.com \
    --cc=oshri.alkoby@nuvoton.com \
    --cc=peterhuewe@gmx.de \
    --cc=robh+dt@kernel.org \
    --cc=tomer.maimon@nuvoton.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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.