[PATCH v1 2/2] char: tpm: add new driver for tpm i2c ptp

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Signed-off-by: Oshri Alkoby <oshrialkoby85@xxxxxxxxx>
---
 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@xxxxxxxxxxx)");
+MODULE_DESCRIPTION("TPM I2C Driver");
+MODULE_VERSION("2.1.3");
+MODULE_LICENSE("GPL");
-- 
2.18.0




[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]


  Powered by Linux