diff mbox

[tpmdd-devel,12/12] tpm/tpm_tis_i2c: Add support for i2c phy

Message ID 1458502483-16887-13-git-send-email-christophe-h.ricard@st.com
State New
Headers show

Commit Message

Christophe Ricard March 20, 2016, 7:34 p.m. UTC
A i2c protocol standardized by the TCG is now supported by most of TPM
vendors.

Signed-off-by: Christophe Ricard <christophe-h.ricard@st.com>
---
 .../bindings/security/tpm/tpm_tis_i2c.txt          |  30 ++
 drivers/char/tpm/Kconfig                           |  14 +-
 drivers/char/tpm/Makefile                          |   1 +
 drivers/char/tpm/tpm_tis_i2c.c                     | 499 +++++++++++++++++++++
 4 files changed, 543 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/devicetree/bindings/security/tpm/tpm_tis_i2c.txt
 create mode 100644 drivers/char/tpm/tpm_tis_i2c.c
diff mbox

Patch

diff --git a/Documentation/devicetree/bindings/security/tpm/tpm_tis_i2c.txt b/Documentation/devicetree/bindings/security/tpm/tpm_tis_i2c.txt
new file mode 100644
index 0000000..a09f922
--- /dev/null
+++ b/Documentation/devicetree/bindings/security/tpm/tpm_tis_i2c.txt
@@ -0,0 +1,30 @@ 
+Required properties:
+- compatible: Should be "st,st33htpm-i2c" or "tcg,tpm_tis-i2c"
+- clock-frequency: I²C work frequency.
+- reg: address on the bus
+
+Optional TPM_TIS Properties:
+- interrupt-parent: phandle for the interrupt gpio controller
+- interrupts: GPIO interrupt to which the chip is connected
+
+Optional SoC Specific Properties:
+- pinctrl-names: Contains only one value - "default".
+- pintctrl-0: Specifies the pin control groups used for this controller.
+
+Example (for ARM-based BeagleBoard xM with TPM_TIS on I2C2):
+
+&i2c2 {
+
+        status = "okay";
+
+        tpm_tis: tpm_tis@17 {
+
+                compatible = "st,tpm_tis-i2c";
+
+                reg = <0x17>;
+                clock-frequency = <400000>;
+
+                interrupt-parent = <&gpio5>;
+                interrupts = <7 IRQ_TYPE_LEVEL_HIGH>;
+        };
+};
diff --git a/drivers/char/tpm/Kconfig b/drivers/char/tpm/Kconfig
index 6fbe7468..56afc2f 100644
--- a/drivers/char/tpm/Kconfig
+++ b/drivers/char/tpm/Kconfig
@@ -72,7 +72,19 @@  config TCG_TIS_SPI
 	  TCG TIS 1.3 TPM specification (TPM1.2) or the TCG PTP FIFO
 	  specification (TPM2.0) 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_spi_tis.
+	  the module will be called tpm_tis_spi.
+
+config TCG_TIS_I2C
+	tristate "TPM Interface Specification 1.3 Interface / TPM 2.0 FIFO Interface - (I2C)"
+	depends on I2C
+	depends on CRC_CCITT
+	---help---
+	  If you have a TPM security chip which is connected to a regular,
+	  non-tcg I2C master (i.e. most embedded platforms) that is compliant with the
+	  TCG TIS 1.3 TPM specification (TPM1.2) or the TCG PTP FIFO
+	  specification (TPM2.0) 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_tis_i2c.
 
 config TCG_NSC
 	tristate "National Semiconductor TPM Interface"
diff --git a/drivers/char/tpm/Makefile b/drivers/char/tpm/Makefile
index 69508ef..eec3793 100644
--- a/drivers/char/tpm/Makefile
+++ b/drivers/char/tpm/Makefile
@@ -17,6 +17,7 @@  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_SPI) += tpm_tis_spi.o
+obj-$(CONFIG_TCG_TIS_I2C) += tpm_tis_i2c.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_tis_i2c.c b/drivers/char/tpm/tpm_tis_i2c.c
new file mode 100644
index 0000000..0591d13
--- /dev/null
+++ b/drivers/char/tpm/tpm_tis_i2c.c
@@ -0,0 +1,499 @@ 
+/*
+ * STMicroelectronics TPM I2C Linux driver for TPM ST33ZP24
+ * Copyright (C) 2016 STMicroelectronics
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#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/acpi.h>
+#include <linux/freezer.h>
+#include <linux/crc-ccitt.h>
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/gpio.h>
+#include <linux/of_irq.h>
+#include <linux/of_gpio.h>
+#include <linux/tpm.h>
+#include "tpm.h"
+#include "tpm_tis_core.h"
+
+#define TPM_LOC_SEL			0x04
+#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_I2C_DID_VID			0x48
+#define TPM_I2C_RID			0x4C
+
+#define TPM_I2C_DEFAULT_GUARD_TIME	0xFA
+
+enum tpm_tis_i2c_operation {
+	TPM_I2C_NONE,
+	TPM_I2C_RECV,
+	TPM_I2C_SEND,
+};
+
+#define TPM_I2C_DEVADRCHANGE(x)		((0x18000000 & x) >> 27)
+#define TPM_I2C_READ_READ(x)		((0x00100000 & x) >> 20)
+#define TPM_I2C_READ_WRITE(x)		((0x00080000 & x) >> 19)
+#define TPM_I2C_WRITE_READ(x)		((0x00040000 & x) >> 18)
+#define TPM_I2C_WRITE_WRITE(x)		((0x00020000 & x) >> 17)
+#define TPM_I2C_GUARD_TIME(x)		((0x0001FE00 & x) >> 9)
+
+struct tpm_tis_i2c_phy {
+	struct i2c_client *client;
+	u8 buf[TPM_BUFSIZE + 1];
+	u8 last_i2c_ops;
+
+	struct timer_list guard_timer;
+	struct mutex phy_lock;
+
+	bool data_csum;
+	bool devadrchange;
+	bool read_read;
+	bool read_write;
+	bool write_read;
+	bool write_write;
+	u8 guard_time;
+};
+
+static int tpm_tis_i2c_ptp_register_mapper(u32 addr, u8 *i2c_reg)
+{
+	*i2c_reg = (u8)(0x000000ff & addr);
+
+	switch (addr) {
+	case TPM_ACCESS(0):
+		*i2c_reg = TPM_LOC_SEL;
+		break;
+	case TPM_LOC_SEL:
+		*i2c_reg = TPM_ACCESS(0);
+		break;
+	case TPM_DID_VID(0):
+		*i2c_reg = TPM_I2C_DID_VID;
+		break;
+	case TPM_RID(0):
+		*i2c_reg = TPM_I2C_RID;
+		break;
+	case TPM_INT_VECTOR(0):
+		return -1;
+	}
+
+	return 0;
+}
+
+static void tpm_tis_i2c_guard_time_timeout(unsigned long data)
+{
+	struct tpm_tis_i2c_phy *phy = (struct tpm_tis_i2c_phy *)data;
+
+	pr_debug("\n");
+
+	/* GUARD_TIME expired */
+	phy->last_i2c_ops = TPM_I2C_NONE;
+}
+
+static void tpm_tis_i2c_sleep_guard_time(struct tpm_tis_i2c_phy *phy,
+					 u8 i2c_operation)
+{
+	del_timer_sync(&phy->guard_timer);
+	switch (i2c_operation) {
+	case TPM_I2C_RECV:
+		switch (phy->last_i2c_ops) {
+		case TPM_I2C_RECV:
+		if (phy->read_read)
+			udelay(phy->guard_time);
+		break;
+		case TPM_I2C_SEND:
+		if (phy->write_read)
+			udelay(phy->guard_time);
+		break;
+		}
+	break;
+	case TPM_I2C_SEND:
+		switch (phy->last_i2c_ops) {
+		case TPM_I2C_RECV:
+		if (phy->read_write)
+			udelay(phy->guard_time);
+		break;
+		case TPM_I2C_SEND:
+		if (phy->write_write)
+			udelay(phy->guard_time);
+		break;
+		}
+	break;
+	}
+	phy->last_i2c_ops = i2c_operation;
+}
+
+static int tpm_tis_i2c_read_bytes(struct tpm_chip *chip, u32 addr, size_t len,
+				  u8 size, u8 *result)
+{
+	struct tpm_tis_i2c_phy *phy = tpm_get_vendordata(chip);
+	int i, ret;
+	u8 i2c_reg;
+
+	mutex_lock(&phy->phy_lock);
+	ret = tpm_tis_i2c_ptp_register_mapper(addr, &i2c_reg);
+	if (ret < 0) {
+		/* If we don't have any register equivalence in i2c
+		 * ignore the sequence.
+		 */
+		ret = len;
+		goto exit;
+	}
+	ret = -1;
+
+	for (i = 0; i < TPM_RETRY && ret < 0; i++) {
+		tpm_tis_i2c_sleep_guard_time(phy, TPM_I2C_SEND);
+		ret = i2c_master_send(phy->client, &i2c_reg, 1);
+		mod_timer(&phy->guard_timer, phy->guard_time);
+	}
+
+	if (ret < 0)
+		goto exit;
+
+	ret = -1;
+	for (i = 0; i < TPM_RETRY && ret < 0; i++) {
+		tpm_tis_i2c_sleep_guard_time(phy, TPM_I2C_RECV);
+		ret = i2c_master_recv(phy->client, result, len * size);
+		mod_timer(&phy->guard_timer, phy->guard_time);
+	}
+
+exit:
+	mutex_unlock(&phy->phy_lock);
+	return ret;
+}
+
+static int tpm_tis_i2c_write_bytes(struct tpm_chip *chip, u32 addr, size_t len,
+				   u8 size, u8 *value)
+{
+	struct tpm_tis_i2c_phy *phy = tpm_get_vendordata(chip);
+	int i, ret;
+	u8 i2c_reg;
+
+	mutex_lock(&phy->phy_lock);
+	ret = tpm_tis_i2c_ptp_register_mapper(addr, &i2c_reg);
+	if (ret < 0) {
+		/* If we don't have any register equivalence in i2c
+		 * ignore the sequence.
+		 */
+		ret = len * size;
+		goto exit;
+	}
+
+	ret = -1;
+	phy->buf[0] = i2c_reg;
+	memcpy(phy->buf + 1, value, len * size);
+
+	for (i = 0; i < TPM_RETRY && (ret < 0 || ret < len + 1); i++) {
+		tpm_tis_i2c_sleep_guard_time(phy, TPM_I2C_SEND);
+		ret = i2c_master_send(phy->client, phy->buf, len * size + 1);
+		mod_timer(&phy->guard_timer, phy->guard_time);
+	}
+
+exit:
+	mutex_unlock(&phy->phy_lock);
+	return ret;
+}
+
+static bool tpm_tis_i2c_check_data(struct tpm_chip *chip, u8 *buf, size_t len)
+{
+	struct tpm_tis_i2c_phy *phy = tpm_get_vendordata(chip);
+	u16 crc, crc_tpm;
+
+	if (phy->data_csum) {
+		crc = crc_ccitt(0x0000, buf, len);
+
+		crc_tpm = tpm_read_word(chip, TPM_DATA_CSUM);
+		crc_tpm = be16_to_cpu(crc_tpm);
+
+		return crc == crc_tpm;
+	}
+	return true;
+}
+
+static const struct tpm_class_ops tpm_tis = {
+	.status = tpm_tis_status,
+	.recv = tpm_tis_recv,
+	.send = tpm_tis_send,
+	.cancel = tpm_tis_ready,
+	.req_complete_mask = TPM_STS_DATA_AVAIL | TPM_STS_VALID,
+	.req_complete_val = TPM_STS_DATA_AVAIL | TPM_STS_VALID,
+	.req_canceled = tpm_tis_req_canceled,
+	.read_bytes = tpm_tis_i2c_read_bytes,
+	.write_bytes = tpm_tis_i2c_write_bytes,
+	.check_data = tpm_tis_i2c_check_data,
+};
+
+static SIMPLE_DEV_PM_OPS(tpm_tis_i2c_pm, tpm_pm_suspend, tpm_tis_resume);
+
+static ssize_t i2c_addr_show(struct device *dev,
+			     struct device_attribute *attr,
+			     char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+
+	if (client)
+		return sprintf(buf, "0x%.2x\n", client->addr);
+
+	return 0;
+}
+
+static ssize_t i2c_addr_store(struct device *dev,
+			      struct device_attribute *attr,
+			      const char *buf, size_t count)
+{
+	struct tpm_chip *chip = dev_get_drvdata(dev);
+	struct tpm_tis_i2c_phy *phy;
+	long new_addr;
+	u16 cur_addr;
+	int ret = 0;
+
+	if (!chip)
+		goto exit;
+
+	phy = tpm_get_vendordata(chip);
+	if (!phy || !phy->client || !phy->devadrchange)
+		goto exit;
+
+	/* Base string automatically detected */
+	ret = kstrtol(buf, 0, &new_addr);
+	if (ret < 0)
+		goto exit;
+
+	ret = tpm_write_word(chip, TPM_I2C_DEVICE_ADDRESS, new_addr);
+	if (ret < 0)
+		goto exit;
+
+	cur_addr = tpm_read_word(chip, TPM_I2C_DEVICE_ADDRESS);
+	if (cur_addr == new_addr) {
+		phy->client->addr = new_addr & 0x00ff;
+		return count;
+	}
+
+	return -EINVAL;
+exit:
+	return ret;
+}
+static DEVICE_ATTR_RW(i2c_addr);
+
+static ssize_t csum_state_show(struct device *dev,
+			       struct device_attribute *attr,
+			       char *buf)
+{
+	struct tpm_chip *chip = dev_get_drvdata(dev);
+	struct tpm_tis_i2c_phy *phy;
+
+	if (!chip)
+		goto exit;
+
+	phy = tpm_get_vendordata(chip);
+	if (!phy || !phy->client)
+		goto exit;
+
+	return sprintf(buf, "%x\n", phy->data_csum);
+exit:
+	return 0;
+}
+
+static ssize_t csum_state_store(struct device *dev,
+				struct device_attribute *attr,
+				const char *buf, size_t count)
+{
+	struct tpm_chip *chip = dev_get_drvdata(dev);
+	struct tpm_tis_i2c_phy *phy;
+	long new_state;
+	u8 cur_state;
+	int ret = 0;
+
+	if (!chip)
+		goto exit;
+
+	phy = tpm_get_vendordata(chip);
+	if (!phy || !phy->client)
+		goto exit;
+
+	ret = kstrtol(buf, 2, &new_state);
+	if (ret < 0)
+		goto exit;
+
+	ret = tpm_write_byte(chip, TPM_DATA_CSUM_ENABLE, new_state);
+	if (ret < 0)
+		goto exit;
+
+	cur_state = tpm_read_byte(chip, TPM_DATA_CSUM_ENABLE);
+	if (new_state == cur_state) {
+		phy->data_csum = cur_state;
+		return count;
+	}
+
+	return -EINVAL;
+exit:
+	return ret;
+}
+static DEVICE_ATTR_RW(csum_state);
+
+static struct attribute *tpm_tis_i2c_attrs[] = {
+	&dev_attr_i2c_addr.attr,
+	&dev_attr_csum_state.attr,
+	NULL,
+};
+
+static struct attribute_group tpm_tis_i2c_attr_group = {
+	.attrs = tpm_tis_i2c_attrs,
+};
+
+static int tpm_tis_i2c_probe(struct i2c_client *client,
+			     const struct i2c_device_id *id)
+{
+	struct tpm_tis_i2c_phy *phy;
+	struct tpm_chip *chip;
+	unsigned int irq_polarity = IRQ_TYPE_NONE;
+	int ret, irq = -1;
+	u32 tmp;
+
+	if (!client) {
+		pr_err("%s: i2c client is NULL. Device not accessible.\n",
+				__func__);
+		return -ENODEV;
+	}
+
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+		dev_info(&client->dev, "client not i2c capable\n");
+		return -ENODEV;
+	}
+
+	phy = devm_kzalloc(&client->dev, sizeof(struct tpm_tis_i2c_phy),
+			   GFP_KERNEL);
+	if (!phy)
+		return -ENOMEM;
+
+	chip = tpmm_chip_alloc(&client->dev, &tpm_tis);
+	if (IS_ERR(chip))
+		return PTR_ERR(chip);
+
+	phy->client = client;
+	mutex_init(&phy->phy_lock);
+	tpm_set_vendordata(chip, phy);
+	i2c_set_clientdata(client, chip);
+
+	phy->guard_time = TPM_I2C_DEFAULT_GUARD_TIME;
+	phy->read_read = true;
+	phy->read_write = true;
+	phy->write_read = true;
+	phy->write_write = true;
+
+	/* initialize timer */
+	init_timer(&phy->guard_timer);
+	phy->guard_timer.data = (unsigned long)phy;
+	phy->guard_timer.function = tpm_tis_i2c_guard_time_timeout;
+
+	ret = tpm_write_byte(chip, TPM_LOC_SEL, 0);
+	if (ret < 0)
+		goto out_err;
+
+	phy->data_csum = tpm_read_byte(chip, TPM_DATA_CSUM_ENABLE);
+
+	if (client->irq > 0) {
+		irq = client->irq;
+		irq_polarity = irq_get_trigger_type(irq);
+		if (irq_polarity > 0)
+			irq_polarity |= IRQF_ONESHOT;
+		else
+			irq = -1;
+	}
+
+	ret = tpm_tis_init_core(&client->dev, chip, irq, irq_polarity);
+	if (ret < 0)
+		goto out_err;
+
+	tmp = tpm_read_dword(chip, TPM_I2C_INTERFACE_CAPABILITY);
+
+	tmp = be32_to_cpu((__force __be32)tmp);
+
+	phy->devadrchange = TPM_I2C_DEVADRCHANGE(tmp);
+	phy->read_read = TPM_I2C_READ_READ(tmp);
+	phy->read_write = TPM_I2C_READ_WRITE(tmp);
+	phy->write_read = TPM_I2C_WRITE_READ(tmp);
+	phy->write_write = TPM_I2C_WRITE_WRITE(tmp);
+	phy->guard_time = TPM_I2C_GUARD_TIME(tmp);
+
+	ret = sysfs_create_group(&client->dev.kobj, &tpm_tis_i2c_attr_group);
+	if (ret < 0) {
+		dev_err(&chip->dev,
+			"failed to create sysfs attributes, %d\n", ret);
+		goto deinit_err;
+	}
+
+	return ret;
+
+deinit_err:
+	tpm_chip_unregister(chip);
+out_err:
+	tpm_tis_remove(chip);
+	return ret;
+}
+
+static int tpm_tis_i2c_remove(struct i2c_client *client)
+{
+	struct tpm_chip *chip = i2c_get_clientdata(client);
+
+	tpm_chip_unregister(chip);
+	tpm_tis_remove(chip);
+	sysfs_remove_group(&client->dev.kobj, &tpm_tis_i2c_attr_group);
+	return 0;
+}
+
+static const struct i2c_device_id tpm_tis_i2c_id[] = {
+	{"tpm_tis_i2c", 0},
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, tpm_tis_i2c_id);
+
+static const struct of_device_id of_tis_i2c_match[] = {
+	{ .compatible = "st,st33htpm-i2c", },
+	{ .compatible = "tcg,tpm_tis-i2c", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, of_tis_i2c_match);
+
+static const struct acpi_device_id acpi_tis_i2c_match[] = {
+	{"SMO0768", 0},
+	{}
+};
+MODULE_DEVICE_TABLE(acpi, acpi_tis_i2c_match);
+
+static struct i2c_driver tpm_tis_i2c_driver = {
+	.driver = {
+		.owner = THIS_MODULE,
+		.name = "tpm_tis_i2c",
+		.pm = &tpm_tis_i2c_pm,
+		.of_match_table = of_match_ptr(of_tis_i2c_match),
+		.acpi_match_table = ACPI_PTR(acpi_tis_i2c_match),
+	},
+	.probe = tpm_tis_i2c_probe,
+	.remove = tpm_tis_i2c_remove,
+	.id_table = tpm_tis_i2c_id,
+};
+
+module_i2c_driver(tpm_tis_i2c_driver);
+
+MODULE_DESCRIPTION("TPM Driver for native I2C access");
+MODULE_LICENSE("GPL");