diff options
author | 2015-08-04 12:17:53 -0700 | |
---|---|---|
committer | 2015-08-04 15:44:42 -0700 | |
commit | 9ca8dbcc65cfc63d6f5ef3312a33184e1d726e00 (patch) | |
tree | 1c9cafbcd35f783a87880a10f85d1a060db1a563 /kernel/drivers/char/tpm | |
parent | 98260f3884f4a202f9ca5eabed40b1354c489b29 (diff) |
Add the rt linux 4.1.3-rt3 as base
Import the rt linux 4.1.3-rt3 as OPNFV kvm base.
It's from git://git.kernel.org/pub/scm/linux/kernel/git/rt/linux-rt-devel.git linux-4.1.y-rt and
the base is:
commit 0917f823c59692d751951bf5ea699a2d1e2f26a2
Author: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Date: Sat Jul 25 12:13:34 2015 +0200
Prepare v4.1.3-rt3
Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
We lose all the git history this way and it's not good. We
should apply another opnfv project repo in future.
Change-Id: I87543d81c9df70d99c5001fbdf646b202c19f423
Signed-off-by: Yunhong Jiang <yunhong.jiang@intel.com>
Diffstat (limited to 'kernel/drivers/char/tpm')
31 files changed, 11058 insertions, 0 deletions
diff --git a/kernel/drivers/char/tpm/Kconfig b/kernel/drivers/char/tpm/Kconfig new file mode 100644 index 000000000..3b84a8b1b --- /dev/null +++ b/kernel/drivers/char/tpm/Kconfig @@ -0,0 +1,126 @@ +# +# TPM device configuration +# + +menuconfig TCG_TPM + tristate "TPM Hardware Support" + depends on HAS_IOMEM + select SECURITYFS + ---help--- + If you have a TPM security chip in your system, which + implements the Trusted Computing Group's specification, + say Yes and it will be accessible from within Linux. For + more information see <http://www.trustedcomputinggroup.org>. + An implementation of the Trusted Software Stack (TSS), the + userspace enablement piece of the specification, can be + obtained at: <http://sourceforge.net/projects/trousers>. To + compile this driver as a module, choose M here; the module + will be called tpm. If unsure, say N. + Notes: + 1) For more TPM drivers enable CONFIG_PNP, CONFIG_ACPI + and CONFIG_PNPACPI. + 2) Without ACPI enabled, the BIOS event log won't be accessible, + which is required to validate the PCR 0-7 values. + +if TCG_TPM + +config TCG_TIS + tristate "TPM Interface Specification 1.2 Interface / TPM 2.0 FIFO Interface" + depends on X86 + ---help--- + If you have a TPM security chip that is compliant with the + TCG TIS 1.2 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. + +config TCG_TIS_I2C_ATMEL + tristate "TPM Interface Specification 1.2 Interface (I2C - Atmel)" + depends on I2C + ---help--- + If you have an Atmel I2C TPM security chip 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_atmel. + +config TCG_TIS_I2C_INFINEON + tristate "TPM Interface Specification 1.2 Interface (I2C - Infineon)" + depends on I2C + ---help--- + If you have a TPM security chip that is compliant with the + TCG TIS 1.2 TPM specification and Infineon's I2C Protocol Stack + Specification 0.20 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_infineon. + +config TCG_TIS_I2C_NUVOTON + tristate "TPM Interface Specification 1.2 Interface (I2C - Nuvoton)" + depends on I2C + ---help--- + If you have a TPM security chip with an I2C interface from + Nuvoton Technology Corp. 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_nuvoton. + +config TCG_NSC + tristate "National Semiconductor TPM Interface" + depends on X86 + ---help--- + If you have a TPM security chip from National Semiconductor + 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_nsc. + +config TCG_ATMEL + tristate "Atmel TPM Interface" + depends on PPC64 || HAS_IOPORT_MAP + ---help--- + If you have a TPM security chip from Atmel 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_atmel. + +config TCG_INFINEON + tristate "Infineon Technologies TPM Interface" + depends on PNP + ---help--- + If you have a TPM security chip from Infineon Technologies + (either SLD 9630 TT 1.1 or SLB 9635 TT 1.2) 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_infineon. + Further information on this driver and the supported hardware + can be found at http://www.trust.rub.de/projects/linux-device-driver-infineon-tpm/ + +config TCG_IBMVTPM + tristate "IBM VTPM Interface" + depends on PPC_PSERIES + ---help--- + If you have IBM virtual TPM (VTPM) support 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_ibmvtpm. + +config TCG_XEN + tristate "XEN TPM Interface" + depends on TCG_TPM && XEN + select XEN_XENBUS_FRONTEND + ---help--- + If you want to make TPM support available to a Xen user domain, + say Yes and it will be accessible from within Linux. See + the manpages for xl, xl.conf, and docs/misc/vtpm.txt in + the Xen source repository for more details. + To compile this driver as a module, choose M here; the module + will be called xen-tpmfront. + +config TCG_CRB + tristate "TPM 2.0 CRB Interface" + depends on X86 && ACPI + ---help--- + If you have a TPM security chip that is compliant with the + TCG CRB 2.0 TPM specification 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_crb. + +source "drivers/char/tpm/st33zp24/Kconfig" +endif # TCG_TPM diff --git a/kernel/drivers/char/tpm/Makefile b/kernel/drivers/char/tpm/Makefile new file mode 100644 index 000000000..56e8f1f3d --- /dev/null +++ b/kernel/drivers/char/tpm/Makefile @@ -0,0 +1,25 @@ +# +# Makefile for the kernel tpm device drivers. +# +obj-$(CONFIG_TCG_TPM) += tpm.o +tpm-y := tpm-interface.o tpm-dev.o tpm-sysfs.o tpm-chip.o tpm2-cmd.o +tpm-$(CONFIG_ACPI) += tpm_ppi.o + +ifdef CONFIG_ACPI + tpm-y += tpm_eventlog.o tpm_acpi.o +else +ifdef CONFIG_TCG_IBMVTPM + tpm-y += tpm_eventlog.o tpm_of.o +endif +endif +obj-$(CONFIG_TCG_TIS) += tpm_tis.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_NSC) += tpm_nsc.o +obj-$(CONFIG_TCG_ATMEL) += tpm_atmel.o +obj-$(CONFIG_TCG_INFINEON) += tpm_infineon.o +obj-$(CONFIG_TCG_IBMVTPM) += tpm_ibmvtpm.o +obj-$(CONFIG_TCG_TIS_ST33ZP24) += st33zp24/ +obj-$(CONFIG_TCG_XEN) += xen-tpmfront.o +obj-$(CONFIG_TCG_CRB) += tpm_crb.o diff --git a/kernel/drivers/char/tpm/st33zp24/Kconfig b/kernel/drivers/char/tpm/st33zp24/Kconfig new file mode 100644 index 000000000..09cb72786 --- /dev/null +++ b/kernel/drivers/char/tpm/st33zp24/Kconfig @@ -0,0 +1,30 @@ +config TCG_TIS_ST33ZP24 + tristate "STMicroelectronics TPM Interface Specification 1.2 Interface" + depends on GPIOLIB + ---help--- + STMicroelectronics ST33ZP24 core driver. It implements the core + TPM1.2 logic and hooks into the TPM kernel APIs. Physical layers will + register against it. + + To compile this driver as a module, choose m here. The module will be called + tpm_st33zp24. + +config TCG_TIS_ST33ZP24_I2C + tristate "TPM 1.2 ST33ZP24 I2C support" + depends on TCG_TIS_ST33ZP24 + depends on I2C + ---help--- + This module adds support for the STMicroelectronics TPM security chip + ST33ZP24 with i2c interface. + To compile this driver as a module, choose M here; the module will be + called tpm_st33zp24_i2c. + +config TCG_TIS_ST33ZP24_SPI + tristate "TPM 1.2 ST33ZP24 SPI support" + depends on TCG_TIS_ST33ZP24 + depends on SPI + ---help--- + This module adds support for the STMicroelectronics TPM security chip + ST33ZP24 with spi interface. + To compile this driver as a module, choose M here; the module will be + called tpm_st33zp24_spi. diff --git a/kernel/drivers/char/tpm/st33zp24/Makefile b/kernel/drivers/char/tpm/st33zp24/Makefile new file mode 100644 index 000000000..74a722e5e --- /dev/null +++ b/kernel/drivers/char/tpm/st33zp24/Makefile @@ -0,0 +1,12 @@ +# +# Makefile for ST33ZP24 TPM 1.2 driver +# + +tpm_st33zp24-objs = st33zp24.o +obj-$(CONFIG_TCG_TIS_ST33ZP24) += tpm_st33zp24.o + +tpm_st33zp24_i2c-objs = i2c.o +obj-$(CONFIG_TCG_TIS_ST33ZP24_I2C) += tpm_st33zp24_i2c.o + +tpm_st33zp24_spi-objs = spi.o +obj-$(CONFIG_TCG_TIS_ST33ZP24_SPI) += tpm_st33zp24_spi.o diff --git a/kernel/drivers/char/tpm/st33zp24/i2c.c b/kernel/drivers/char/tpm/st33zp24/i2c.c new file mode 100644 index 000000000..ad1ee180e --- /dev/null +++ b/kernel/drivers/char/tpm/st33zp24/i2c.c @@ -0,0 +1,276 @@ +/* + * STMicroelectronics TPM I2C Linux driver for TPM ST33ZP24 + * Copyright (C) 2009 - 2015 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/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 <linux/platform_data/st33zp24.h> + +#include "st33zp24.h" + +#define TPM_DUMMY_BYTE 0xAA + +struct st33zp24_i2c_phy { + struct i2c_client *client; + u8 buf[TPM_BUFSIZE + 1]; + int io_lpcpd; +}; + +/* + * write8_reg + * Send byte to the TIS register according to the ST33ZP24 I2C protocol. + * @param: tpm_register, the tpm tis register where the data should be written + * @param: tpm_data, the tpm_data to write inside the tpm_register + * @param: tpm_size, The length of the data + * @return: Returns negative errno, or else the number of bytes written. + */ +static int write8_reg(void *phy_id, u8 tpm_register, u8 *tpm_data, int tpm_size) +{ + struct st33zp24_i2c_phy *phy = phy_id; + + phy->buf[0] = tpm_register; + memcpy(phy->buf + 1, tpm_data, tpm_size); + return i2c_master_send(phy->client, phy->buf, tpm_size + 1); +} /* write8_reg() */ + +/* + * read8_reg + * Recv byte from the TIS register according to the ST33ZP24 I2C protocol. + * @param: tpm_register, the tpm tis register where the data should be read + * @param: tpm_data, the TPM response + * @param: tpm_size, tpm TPM response size to read. + * @return: number of byte read successfully: should be one if success. + */ +static int read8_reg(void *phy_id, u8 tpm_register, u8 *tpm_data, int tpm_size) +{ + struct st33zp24_i2c_phy *phy = phy_id; + u8 status = 0; + u8 data; + + data = TPM_DUMMY_BYTE; + status = write8_reg(phy, tpm_register, &data, 1); + if (status == 2) + status = i2c_master_recv(phy->client, tpm_data, tpm_size); + return status; +} /* read8_reg() */ + +/* + * st33zp24_i2c_send + * Send byte to the TIS register according to the ST33ZP24 I2C protocol. + * @param: phy_id, the phy description + * @param: tpm_register, the tpm tis register where the data should be written + * @param: tpm_data, the tpm_data to write inside the tpm_register + * @param: tpm_size, the length of the data + * @return: number of byte written successfully: should be one if success. + */ +static int st33zp24_i2c_send(void *phy_id, u8 tpm_register, u8 *tpm_data, + int tpm_size) +{ + return write8_reg(phy_id, tpm_register | TPM_WRITE_DIRECTION, tpm_data, + tpm_size); +} + +/* + * st33zp24_i2c_recv + * Recv byte from the TIS register according to the ST33ZP24 I2C protocol. + * @param: phy_id, the phy description + * @param: tpm_register, the tpm tis register where the data should be read + * @param: tpm_data, the TPM response + * @param: tpm_size, tpm TPM response size to read. + * @return: number of byte read successfully: should be one if success. + */ +static int st33zp24_i2c_recv(void *phy_id, u8 tpm_register, u8 *tpm_data, + int tpm_size) +{ + return read8_reg(phy_id, tpm_register, tpm_data, tpm_size); +} + +static const struct st33zp24_phy_ops i2c_phy_ops = { + .send = st33zp24_i2c_send, + .recv = st33zp24_i2c_recv, +}; + +#ifdef CONFIG_OF +static int st33zp24_i2c_of_request_resources(struct st33zp24_i2c_phy *phy) +{ + struct device_node *pp; + struct i2c_client *client = phy->client; + int gpio; + int ret; + + pp = client->dev.of_node; + if (!pp) { + dev_err(&client->dev, "No platform data\n"); + return -ENODEV; + } + + /* Get GPIO from device tree */ + gpio = of_get_named_gpio(pp, "lpcpd-gpios", 0); + if (gpio < 0) { + dev_err(&client->dev, + "Failed to retrieve lpcpd-gpios from dts.\n"); + phy->io_lpcpd = -1; + /* + * lpcpd pin is not specified. This is not an issue as + * power management can be also managed by TPM specific + * commands. So leave with a success status code. + */ + return 0; + } + /* GPIO request and configuration */ + ret = devm_gpio_request_one(&client->dev, gpio, + GPIOF_OUT_INIT_HIGH, "TPM IO LPCPD"); + if (ret) { + dev_err(&client->dev, "Failed to request lpcpd pin\n"); + return -ENODEV; + } + phy->io_lpcpd = gpio; + + return 0; +} +#else +static int st33zp24_i2c_of_request_resources(struct st33zp24_i2c_phy *phy) +{ + return -ENODEV; +} +#endif + +static int st33zp24_i2c_request_resources(struct i2c_client *client, + struct st33zp24_i2c_phy *phy) +{ + struct st33zp24_platform_data *pdata; + int ret; + + pdata = client->dev.platform_data; + if (!pdata) { + dev_err(&client->dev, "No platform data\n"); + return -ENODEV; + } + + /* store for late use */ + phy->io_lpcpd = pdata->io_lpcpd; + + if (gpio_is_valid(pdata->io_lpcpd)) { + ret = devm_gpio_request_one(&client->dev, + pdata->io_lpcpd, GPIOF_OUT_INIT_HIGH, + "TPM IO_LPCPD"); + if (ret) { + dev_err(&client->dev, "Failed to request lpcpd pin\n"); + return ret; + } + } + + return 0; +} + +/* + * st33zp24_i2c_probe initialize the TPM device + * @param: client, the i2c_client drescription (TPM I2C description). + * @param: id, the i2c_device_id struct. + * @return: 0 in case of success. + * -1 in other case. + */ +static int st33zp24_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct st33zp24_platform_data *pdata; + struct st33zp24_i2c_phy *phy; + + if (!client) { + pr_info("%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 st33zp24_i2c_phy), + GFP_KERNEL); + if (!phy) + return -ENOMEM; + + phy->client = client; + pdata = client->dev.platform_data; + if (!pdata && client->dev.of_node) { + ret = st33zp24_i2c_of_request_resources(phy); + if (ret) + return ret; + } else if (pdata) { + ret = st33zp24_i2c_request_resources(client, phy); + if (ret) + return ret; + } + + return st33zp24_probe(phy, &i2c_phy_ops, &client->dev, client->irq, + phy->io_lpcpd); +} + +/* + * st33zp24_i2c_remove remove the TPM device + * @param: client, the i2c_client description (TPM I2C description). + * @return: 0 in case of success. + */ +static int st33zp24_i2c_remove(struct i2c_client *client) +{ + struct tpm_chip *chip = i2c_get_clientdata(client); + + return st33zp24_remove(chip); +} + +static const struct i2c_device_id st33zp24_i2c_id[] = { + {TPM_ST33_I2C, 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, st33zp24_i2c_id); + +#ifdef CONFIG_OF +static const struct of_device_id of_st33zp24_i2c_match[] = { + { .compatible = "st,st33zp24-i2c", }, + {} +}; +MODULE_DEVICE_TABLE(of, of_st33zp24_i2c_match); +#endif + +static SIMPLE_DEV_PM_OPS(st33zp24_i2c_ops, st33zp24_pm_suspend, + st33zp24_pm_resume); + +static struct i2c_driver st33zp24_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = TPM_ST33_I2C, + .pm = &st33zp24_i2c_ops, + .of_match_table = of_match_ptr(of_st33zp24_i2c_match), + }, + .probe = st33zp24_i2c_probe, + .remove = st33zp24_i2c_remove, + .id_table = st33zp24_i2c_id +}; + +module_i2c_driver(st33zp24_i2c_driver); + +MODULE_AUTHOR("TPM support (TPMsupport@list.st.com)"); +MODULE_DESCRIPTION("STM TPM 1.2 I2C ST33 Driver"); +MODULE_VERSION("1.3.0"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/char/tpm/st33zp24/spi.c b/kernel/drivers/char/tpm/st33zp24/spi.c new file mode 100644 index 000000000..f0184a1b0 --- /dev/null +++ b/kernel/drivers/char/tpm/st33zp24/spi.c @@ -0,0 +1,399 @@ +/* + * STMicroelectronics TPM SPI Linux driver for TPM ST33ZP24 + * Copyright (C) 2009 - 2015 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/module.h> +#include <linux/spi/spi.h> +#include <linux/gpio.h> +#include <linux/of_irq.h> +#include <linux/of_gpio.h> +#include <linux/tpm.h> +#include <linux/platform_data/st33zp24.h> + +#include "st33zp24.h" + +#define TPM_DATA_FIFO 0x24 +#define TPM_INTF_CAPABILITY 0x14 + +#define TPM_DUMMY_BYTE 0x00 + +#define MAX_SPI_LATENCY 15 +#define LOCALITY0 0 + +#define ST33ZP24_OK 0x5A +#define ST33ZP24_UNDEFINED_ERR 0x80 +#define ST33ZP24_BADLOCALITY 0x81 +#define ST33ZP24_TISREGISTER_UKNOWN 0x82 +#define ST33ZP24_LOCALITY_NOT_ACTIVATED 0x83 +#define ST33ZP24_HASH_END_BEFORE_HASH_START 0x84 +#define ST33ZP24_BAD_COMMAND_ORDER 0x85 +#define ST33ZP24_INCORECT_RECEIVED_LENGTH 0x86 +#define ST33ZP24_TPM_FIFO_OVERFLOW 0x89 +#define ST33ZP24_UNEXPECTED_READ_FIFO 0x8A +#define ST33ZP24_UNEXPECTED_WRITE_FIFO 0x8B +#define ST33ZP24_CMDRDY_SET_WHEN_PROCESSING_HASH_END 0x90 +#define ST33ZP24_DUMMY_BYTES 0x00 + +/* + * TPM command can be up to 2048 byte, A TPM response can be up to + * 1024 byte. + * Between command and response, there are latency byte (up to 15 + * usually on st33zp24 2 are enough). + * + * Overall when sending a command and expecting an answer we need if + * worst case: + * 2048 (for the TPM command) + 1024 (for the TPM answer). We need + * some latency byte before the answer is available (max 15). + * We have 2048 + 1024 + 15. + */ +#define ST33ZP24_SPI_BUFFER_SIZE (TPM_BUFSIZE + (TPM_BUFSIZE / 2) +\ + MAX_SPI_LATENCY) + + +struct st33zp24_spi_phy { + struct spi_device *spi_device; + struct spi_transfer spi_xfer; + u8 tx_buf[ST33ZP24_SPI_BUFFER_SIZE]; + u8 rx_buf[ST33ZP24_SPI_BUFFER_SIZE]; + + int io_lpcpd; + int latency; +}; + +static int st33zp24_status_to_errno(u8 code) +{ + switch (code) { + case ST33ZP24_OK: + return 0; + case ST33ZP24_UNDEFINED_ERR: + case ST33ZP24_BADLOCALITY: + case ST33ZP24_TISREGISTER_UKNOWN: + case ST33ZP24_LOCALITY_NOT_ACTIVATED: + case ST33ZP24_HASH_END_BEFORE_HASH_START: + case ST33ZP24_BAD_COMMAND_ORDER: + case ST33ZP24_UNEXPECTED_READ_FIFO: + case ST33ZP24_UNEXPECTED_WRITE_FIFO: + case ST33ZP24_CMDRDY_SET_WHEN_PROCESSING_HASH_END: + return -EPROTO; + case ST33ZP24_INCORECT_RECEIVED_LENGTH: + case ST33ZP24_TPM_FIFO_OVERFLOW: + return -EMSGSIZE; + case ST33ZP24_DUMMY_BYTES: + return -ENOSYS; + } + return code; +} + +/* + * st33zp24_spi_send + * Send byte to the TIS register according to the ST33ZP24 SPI protocol. + * @param: phy_id, the phy description + * @param: tpm_register, the tpm tis register where the data should be written + * @param: tpm_data, the tpm_data to write inside the tpm_register + * @param: tpm_size, The length of the data + * @return: should be zero if success else a negative error code. + */ +static int st33zp24_spi_send(void *phy_id, u8 tpm_register, u8 *tpm_data, + int tpm_size) +{ + u8 data = 0; + int total_length = 0, nbr_dummy_bytes = 0, ret = 0; + struct st33zp24_spi_phy *phy = phy_id; + struct spi_device *dev = phy->spi_device; + u8 *tx_buf = (u8 *)phy->spi_xfer.tx_buf; + u8 *rx_buf = phy->spi_xfer.rx_buf; + + /* Pre-Header */ + data = TPM_WRITE_DIRECTION | LOCALITY0; + memcpy(tx_buf + total_length, &data, sizeof(data)); + total_length++; + data = tpm_register; + memcpy(tx_buf + total_length, &data, sizeof(data)); + total_length++; + + if (tpm_size > 0 && tpm_register == TPM_DATA_FIFO) { + tx_buf[total_length++] = tpm_size >> 8; + tx_buf[total_length++] = tpm_size; + } + + memcpy(&tx_buf[total_length], tpm_data, tpm_size); + total_length += tpm_size; + + nbr_dummy_bytes = phy->latency; + memset(&tx_buf[total_length], TPM_DUMMY_BYTE, nbr_dummy_bytes); + + phy->spi_xfer.len = total_length + nbr_dummy_bytes; + + ret = spi_sync_transfer(dev, &phy->spi_xfer, 1); + if (ret == 0) + ret = rx_buf[total_length + nbr_dummy_bytes - 1]; + + return st33zp24_status_to_errno(ret); +} /* st33zp24_spi_send() */ + +/* + * read8_recv + * Recv byte from the TIS register according to the ST33ZP24 SPI protocol. + * @param: phy_id, the phy description + * @param: tpm_register, the tpm tis register where the data should be read + * @param: tpm_data, the TPM response + * @param: tpm_size, tpm TPM response size to read. + * @return: should be zero if success else a negative error code. + */ +static int read8_reg(void *phy_id, u8 tpm_register, u8 *tpm_data, int tpm_size) +{ + u8 data = 0; + int total_length = 0, nbr_dummy_bytes, ret; + struct st33zp24_spi_phy *phy = phy_id; + struct spi_device *dev = phy->spi_device; + u8 *tx_buf = (u8 *)phy->spi_xfer.tx_buf; + u8 *rx_buf = phy->spi_xfer.rx_buf; + + /* Pre-Header */ + data = LOCALITY0; + memcpy(tx_buf + total_length, &data, sizeof(data)); + total_length++; + data = tpm_register; + memcpy(tx_buf + total_length, &data, sizeof(data)); + total_length++; + + nbr_dummy_bytes = phy->latency; + memset(&tx_buf[total_length], TPM_DUMMY_BYTE, + nbr_dummy_bytes + tpm_size); + + phy->spi_xfer.len = total_length + nbr_dummy_bytes + tpm_size; + + /* header + status byte + size of the data + status byte */ + ret = spi_sync_transfer(dev, &phy->spi_xfer, 1); + if (tpm_size > 0 && ret == 0) { + ret = rx_buf[total_length + nbr_dummy_bytes - 1]; + + memcpy(tpm_data, rx_buf + total_length + nbr_dummy_bytes, + tpm_size); + } + + return ret; +} /* read8_reg() */ + +/* + * st33zp24_spi_recv + * Recv byte from the TIS register according to the ST33ZP24 SPI protocol. + * @param: phy_id, the phy description + * @param: tpm_register, the tpm tis register where the data should be read + * @param: tpm_data, the TPM response + * @param: tpm_size, tpm TPM response size to read. + * @return: number of byte read successfully: should be one if success. + */ +static int st33zp24_spi_recv(void *phy_id, u8 tpm_register, u8 *tpm_data, + int tpm_size) +{ + int ret; + + ret = read8_reg(phy_id, tpm_register, tpm_data, tpm_size); + if (!st33zp24_status_to_errno(ret)) + return tpm_size; + return ret; +} /* st33zp24_spi_recv() */ + +static int evaluate_latency(void *phy_id) +{ + struct st33zp24_spi_phy *phy = phy_id; + int latency = 1, status = 0; + u8 data = 0; + + while (!status && latency < MAX_SPI_LATENCY) { + phy->latency = latency; + status = read8_reg(phy_id, TPM_INTF_CAPABILITY, &data, 1); + latency++; + } + return latency - 1; +} /* evaluate_latency() */ + +static const struct st33zp24_phy_ops spi_phy_ops = { + .send = st33zp24_spi_send, + .recv = st33zp24_spi_recv, +}; + +#ifdef CONFIG_OF +static int tpm_stm_spi_of_request_resources(struct st33zp24_spi_phy *phy) +{ + struct device_node *pp; + struct spi_device *dev = phy->spi_device; + int gpio; + int ret; + + pp = dev->dev.of_node; + if (!pp) { + dev_err(&dev->dev, "No platform data\n"); + return -ENODEV; + } + + /* Get GPIO from device tree */ + gpio = of_get_named_gpio(pp, "lpcpd-gpios", 0); + if (gpio < 0) { + dev_err(&dev->dev, + "Failed to retrieve lpcpd-gpios from dts.\n"); + phy->io_lpcpd = -1; + /* + * lpcpd pin is not specified. This is not an issue as + * power management can be also managed by TPM specific + * commands. So leave with a success status code. + */ + return 0; + } + /* GPIO request and configuration */ + ret = devm_gpio_request_one(&dev->dev, gpio, + GPIOF_OUT_INIT_HIGH, "TPM IO LPCPD"); + if (ret) { + dev_err(&dev->dev, "Failed to request lpcpd pin\n"); + return -ENODEV; + } + phy->io_lpcpd = gpio; + + return 0; +} +#else +static int tpm_stm_spi_of_request_resources(struct st33zp24_spi_phy *phy) +{ + return -ENODEV; +} +#endif + +static int tpm_stm_spi_request_resources(struct spi_device *dev, + struct st33zp24_spi_phy *phy) +{ + struct st33zp24_platform_data *pdata; + int ret; + + pdata = dev->dev.platform_data; + if (!pdata) { + dev_err(&dev->dev, "No platform data\n"); + return -ENODEV; + } + + /* store for late use */ + phy->io_lpcpd = pdata->io_lpcpd; + + if (gpio_is_valid(pdata->io_lpcpd)) { + ret = devm_gpio_request_one(&dev->dev, + pdata->io_lpcpd, GPIOF_OUT_INIT_HIGH, + "TPM IO_LPCPD"); + if (ret) { + dev_err(&dev->dev, "%s : reset gpio_request failed\n", + __FILE__); + return ret; + } + } + + return 0; +} + +/* + * tpm_st33_spi_probe initialize the TPM device + * @param: dev, the spi_device drescription (TPM SPI description). + * @return: 0 in case of success. + * or a negative value describing the error. + */ +static int +tpm_st33_spi_probe(struct spi_device *dev) +{ + int ret; + struct st33zp24_platform_data *pdata; + struct st33zp24_spi_phy *phy; + + /* Check SPI platform functionnalities */ + if (!dev) { + pr_info("%s: dev is NULL. Device is not accessible.\n", + __func__); + return -ENODEV; + } + + phy = devm_kzalloc(&dev->dev, sizeof(struct st33zp24_spi_phy), + GFP_KERNEL); + if (!phy) + return -ENOMEM; + + phy->spi_device = dev; + pdata = dev->dev.platform_data; + if (!pdata && dev->dev.of_node) { + ret = tpm_stm_spi_of_request_resources(phy); + if (ret) + return ret; + } else if (pdata) { + ret = tpm_stm_spi_request_resources(dev, phy); + if (ret) + return ret; + } + + phy->spi_xfer.tx_buf = phy->tx_buf; + phy->spi_xfer.rx_buf = phy->rx_buf; + + phy->latency = evaluate_latency(phy); + if (phy->latency <= 0) + return -ENODEV; + + return st33zp24_probe(phy, &spi_phy_ops, &dev->dev, dev->irq, + phy->io_lpcpd); +} + +/* + * tpm_st33_spi_remove remove the TPM device + * @param: client, the spi_device drescription (TPM SPI description). + * @return: 0 in case of success. + */ +static int tpm_st33_spi_remove(struct spi_device *dev) +{ + struct tpm_chip *chip = spi_get_drvdata(dev); + + return st33zp24_remove(chip); +} + +static const struct spi_device_id st33zp24_spi_id[] = { + {TPM_ST33_SPI, 0}, + {} +}; +MODULE_DEVICE_TABLE(spi, st33zp24_spi_id); + +#ifdef CONFIG_OF +static const struct of_device_id of_st33zp24_spi_match[] = { + { .compatible = "st,st33zp24-spi", }, + {} +}; +MODULE_DEVICE_TABLE(of, of_st33zp24_spi_match); +#endif + +static SIMPLE_DEV_PM_OPS(st33zp24_spi_ops, st33zp24_pm_suspend, + st33zp24_pm_resume); + +static struct spi_driver tpm_st33_spi_driver = { + .driver = { + .owner = THIS_MODULE, + .name = TPM_ST33_SPI, + .pm = &st33zp24_spi_ops, + .of_match_table = of_match_ptr(of_st33zp24_spi_match), + }, + .probe = tpm_st33_spi_probe, + .remove = tpm_st33_spi_remove, + .id_table = st33zp24_spi_id, +}; + +module_spi_driver(tpm_st33_spi_driver); + +MODULE_AUTHOR("TPM support (TPMsupport@list.st.com)"); +MODULE_DESCRIPTION("STM TPM 1.2 SPI ST33 Driver"); +MODULE_VERSION("1.3.0"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/char/tpm/st33zp24/st33zp24.c b/kernel/drivers/char/tpm/st33zp24/st33zp24.c new file mode 100644 index 000000000..8d626784c --- /dev/null +++ b/kernel/drivers/char/tpm/st33zp24/st33zp24.c @@ -0,0 +1,698 @@ +/* + * STMicroelectronics TPM Linux driver for TPM ST33ZP24 + * Copyright (C) 2009 - 2015 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/module.h> +#include <linux/fs.h> +#include <linux/miscdevice.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/wait.h> +#include <linux/freezer.h> +#include <linux/string.h> +#include <linux/interrupt.h> +#include <linux/gpio.h> +#include <linux/sched.h> +#include <linux/uaccess.h> +#include <linux/io.h> +#include <linux/slab.h> + +#include "../tpm.h" +#include "st33zp24.h" + +#define TPM_ACCESS 0x0 +#define TPM_STS 0x18 +#define TPM_DATA_FIFO 0x24 +#define TPM_INTF_CAPABILITY 0x14 +#define TPM_INT_STATUS 0x10 +#define TPM_INT_ENABLE 0x08 + +#define LOCALITY0 0 + +enum st33zp24_access { + TPM_ACCESS_VALID = 0x80, + TPM_ACCESS_ACTIVE_LOCALITY = 0x20, + TPM_ACCESS_REQUEST_PENDING = 0x04, + TPM_ACCESS_REQUEST_USE = 0x02, +}; + +enum st33zp24_status { + TPM_STS_VALID = 0x80, + TPM_STS_COMMAND_READY = 0x40, + TPM_STS_GO = 0x20, + TPM_STS_DATA_AVAIL = 0x10, + TPM_STS_DATA_EXPECT = 0x08, +}; + +enum st33zp24_int_flags { + TPM_GLOBAL_INT_ENABLE = 0x80, + TPM_INTF_CMD_READY_INT = 0x080, + TPM_INTF_FIFO_AVALAIBLE_INT = 0x040, + TPM_INTF_WAKE_UP_READY_INT = 0x020, + TPM_INTF_LOCALITY_CHANGE_INT = 0x004, + TPM_INTF_STS_VALID_INT = 0x002, + TPM_INTF_DATA_AVAIL_INT = 0x001, +}; + +enum tis_defaults { + TIS_SHORT_TIMEOUT = 750, + TIS_LONG_TIMEOUT = 2000, +}; + +struct st33zp24_dev { + struct tpm_chip *chip; + void *phy_id; + const struct st33zp24_phy_ops *ops; + u32 intrs; + int io_lpcpd; +}; + +/* + * clear_interruption clear the pending interrupt. + * @param: tpm_dev, the tpm device device. + * @return: the interrupt status value. + */ +static u8 clear_interruption(struct st33zp24_dev *tpm_dev) +{ + u8 interrupt; + + tpm_dev->ops->recv(tpm_dev->phy_id, TPM_INT_STATUS, &interrupt, 1); + tpm_dev->ops->send(tpm_dev->phy_id, TPM_INT_STATUS, &interrupt, 1); + return interrupt; +} /* clear_interruption() */ + +/* + * st33zp24_cancel, cancel the current command execution or + * set STS to COMMAND READY. + * @param: chip, the tpm_chip description as specified in driver/char/tpm/tpm.h + */ +static void st33zp24_cancel(struct tpm_chip *chip) +{ + struct st33zp24_dev *tpm_dev; + u8 data; + + tpm_dev = (struct st33zp24_dev *)TPM_VPRIV(chip); + + data = TPM_STS_COMMAND_READY; + tpm_dev->ops->send(tpm_dev->phy_id, TPM_STS, &data, 1); +} /* st33zp24_cancel() */ + +/* + * st33zp24_status return the TPM_STS register + * @param: chip, the tpm chip description + * @return: the TPM_STS register value. + */ +static u8 st33zp24_status(struct tpm_chip *chip) +{ + struct st33zp24_dev *tpm_dev; + u8 data; + + tpm_dev = (struct st33zp24_dev *)TPM_VPRIV(chip); + + tpm_dev->ops->recv(tpm_dev->phy_id, TPM_STS, &data, 1); + return data; +} /* st33zp24_status() */ + +/* + * check_locality if the locality is active + * @param: chip, the tpm chip description + * @return: the active locality or -EACCESS. + */ +static int check_locality(struct tpm_chip *chip) +{ + struct st33zp24_dev *tpm_dev; + u8 data; + u8 status; + + tpm_dev = (struct st33zp24_dev *)TPM_VPRIV(chip); + + status = tpm_dev->ops->recv(tpm_dev->phy_id, TPM_ACCESS, &data, 1); + if (status && (data & + (TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID)) == + (TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID)) + return chip->vendor.locality; + + return -EACCES; +} /* check_locality() */ + +/* + * request_locality request the TPM locality + * @param: chip, the chip description + * @return: the active locality or negative value. + */ +static int request_locality(struct tpm_chip *chip) +{ + unsigned long stop; + long ret; + struct st33zp24_dev *tpm_dev; + u8 data; + + if (check_locality(chip) == chip->vendor.locality) + return chip->vendor.locality; + + tpm_dev = (struct st33zp24_dev *)TPM_VPRIV(chip); + + data = TPM_ACCESS_REQUEST_USE; + ret = tpm_dev->ops->send(tpm_dev->phy_id, TPM_ACCESS, &data, 1); + if (ret < 0) + return ret; + + stop = jiffies + chip->vendor.timeout_a; + + /* Request locality is usually effective after the request */ + do { + if (check_locality(chip) >= 0) + return chip->vendor.locality; + msleep(TPM_TIMEOUT); + } while (time_before(jiffies, stop)); + + /* could not get locality */ + return -EACCES; +} /* request_locality() */ + +/* + * release_locality release the active locality + * @param: chip, the tpm chip description. + */ +static void release_locality(struct tpm_chip *chip) +{ + struct st33zp24_dev *tpm_dev; + u8 data; + + tpm_dev = (struct st33zp24_dev *)TPM_VPRIV(chip); + data = TPM_ACCESS_ACTIVE_LOCALITY; + + tpm_dev->ops->send(tpm_dev->phy_id, TPM_ACCESS, &data, 1); +} + +/* + * get_burstcount return the burstcount value + * @param: chip, the chip description + * return: the burstcount or negative value. + */ +static int get_burstcount(struct tpm_chip *chip) +{ + unsigned long stop; + int burstcnt, status; + u8 tpm_reg, temp; + struct st33zp24_dev *tpm_dev; + + tpm_dev = (struct st33zp24_dev *)TPM_VPRIV(chip); + + stop = jiffies + chip->vendor.timeout_d; + do { + tpm_reg = TPM_STS + 1; + status = tpm_dev->ops->recv(tpm_dev->phy_id, tpm_reg, &temp, 1); + if (status < 0) + return -EBUSY; + + tpm_reg = TPM_STS + 2; + burstcnt = temp; + status = tpm_dev->ops->recv(tpm_dev->phy_id, tpm_reg, &temp, 1); + if (status < 0) + return -EBUSY; + + burstcnt |= temp << 8; + if (burstcnt) + return burstcnt; + msleep(TPM_TIMEOUT); + } while (time_before(jiffies, stop)); + return -EBUSY; +} /* get_burstcount() */ + + +/* + * wait_for_tpm_stat_cond + * @param: chip, chip description + * @param: mask, expected mask value + * @param: check_cancel, does the command expected to be canceled ? + * @param: canceled, did we received a cancel request ? + * @return: true if status == mask or if the command is canceled. + * false in other cases. + */ +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 & mask) == mask) + return true; + if (check_cancel && chip->ops->req_canceled(chip, status)) { + *canceled = true; + return true; + } + return false; +} + +/* + * wait_for_stat wait for a TPM_STS value + * @param: chip, the tpm chip description + * @param: mask, the value mask to wait + * @param: timeout, the timeout + * @param: queue, the wait queue. + * @param: check_cancel, does the command can be cancelled ? + * @return: the tpm status, 0 if success, -ETIME if timeout is reached. + */ +static int wait_for_stat(struct tpm_chip *chip, u8 mask, unsigned long timeout, + wait_queue_head_t *queue, bool check_cancel) +{ + unsigned long stop; + int ret = 0; + bool canceled = false; + bool condition; + u32 cur_intrs; + u8 status; + struct st33zp24_dev *tpm_dev; + + tpm_dev = (struct st33zp24_dev *)TPM_VPRIV(chip); + + /* check current status */ + status = st33zp24_status(chip); + if ((status & mask) == mask) + return 0; + + stop = jiffies + timeout; + + if (chip->vendor.irq) { + cur_intrs = tpm_dev->intrs; + clear_interruption(tpm_dev); + enable_irq(chip->vendor.irq); + + do { + if (ret == -ERESTARTSYS && freezing(current)) + clear_thread_flag(TIF_SIGPENDING); + + timeout = stop - jiffies; + if ((long) timeout <= 0) + return -1; + + ret = wait_event_interruptible_timeout(*queue, + cur_intrs != tpm_dev->intrs, + timeout); + clear_interruption(tpm_dev); + condition = wait_for_tpm_stat_cond(chip, mask, + check_cancel, &canceled); + if (ret >= 0 && condition) { + if (canceled) + return -ECANCELED; + return 0; + } + } while (ret == -ERESTARTSYS && freezing(current)); + + disable_irq_nosync(chip->vendor.irq); + + } else { + do { + msleep(TPM_TIMEOUT); + status = chip->ops->status(chip); + if ((status & mask) == mask) + return 0; + } while (time_before(jiffies, stop)); + } + + return -ETIME; +} /* wait_for_stat() */ + +/* + * recv_data receive data + * @param: chip, the tpm chip description + * @param: buf, the buffer where the data are received + * @param: count, the number of data to receive + * @return: the number of bytes read from TPM FIFO. + */ +static int recv_data(struct tpm_chip *chip, u8 *buf, size_t count) +{ + int size = 0, burstcnt, len, ret; + struct st33zp24_dev *tpm_dev; + + tpm_dev = (struct st33zp24_dev *)TPM_VPRIV(chip); + + while (size < count && + wait_for_stat(chip, + TPM_STS_DATA_AVAIL | TPM_STS_VALID, + chip->vendor.timeout_c, + &chip->vendor.read_queue, true) == 0) { + burstcnt = get_burstcount(chip); + if (burstcnt < 0) + return burstcnt; + len = min_t(int, burstcnt, count - size); + ret = tpm_dev->ops->recv(tpm_dev->phy_id, TPM_DATA_FIFO, + buf + size, len); + if (ret < 0) + return ret; + + size += len; + } + return size; +} + +/* + * tpm_ioserirq_handler the serirq irq handler + * @param: irq, the tpm chip description + * @param: dev_id, the description of the chip + * @return: the status of the handler. + */ +static irqreturn_t tpm_ioserirq_handler(int irq, void *dev_id) +{ + struct tpm_chip *chip = dev_id; + struct st33zp24_dev *tpm_dev; + + tpm_dev = (struct st33zp24_dev *)TPM_VPRIV(chip); + + tpm_dev->intrs++; + wake_up_interruptible(&chip->vendor.read_queue); + disable_irq_nosync(chip->vendor.irq); + + return IRQ_HANDLED; +} /* tpm_ioserirq_handler() */ + +/* + * st33zp24_send send TPM commands through the I2C bus. + * + * @param: chip, the tpm_chip description as specified in driver/char/tpm/tpm.h + * @param: buf, the buffer to send. + * @param: count, the number of bytes to send. + * @return: In case of success the number of bytes sent. + * In other case, a < 0 value describing the issue. + */ +static int st33zp24_send(struct tpm_chip *chip, unsigned char *buf, + size_t len) +{ + u32 status, i, size, ordinal; + int burstcnt = 0; + int ret; + u8 data; + struct st33zp24_dev *tpm_dev; + + if (!chip) + return -EBUSY; + if (len < TPM_HEADER_SIZE) + return -EBUSY; + + tpm_dev = (struct st33zp24_dev *)TPM_VPRIV(chip); + + ret = request_locality(chip); + if (ret < 0) + return ret; + + status = st33zp24_status(chip); + if ((status & TPM_STS_COMMAND_READY) == 0) { + st33zp24_cancel(chip); + if (wait_for_stat + (chip, TPM_STS_COMMAND_READY, chip->vendor.timeout_b, + &chip->vendor.read_queue, false) < 0) { + ret = -ETIME; + goto out_err; + } + } + + for (i = 0; i < len - 1;) { + burstcnt = get_burstcount(chip); + if (burstcnt < 0) + return burstcnt; + size = min_t(int, len - i - 1, burstcnt); + ret = tpm_dev->ops->send(tpm_dev->phy_id, TPM_DATA_FIFO, + buf + i, size); + if (ret < 0) + goto out_err; + + i += size; + } + + status = st33zp24_status(chip); + if ((status & TPM_STS_DATA_EXPECT) == 0) { + ret = -EIO; + goto out_err; + } + + ret = tpm_dev->ops->send(tpm_dev->phy_id, TPM_DATA_FIFO, + buf + len - 1, 1); + if (ret < 0) + goto out_err; + + status = st33zp24_status(chip); + if ((status & TPM_STS_DATA_EXPECT) != 0) { + ret = -EIO; + goto out_err; + } + + data = TPM_STS_GO; + ret = tpm_dev->ops->send(tpm_dev->phy_id, TPM_STS, &data, 1); + if (ret < 0) + goto out_err; + + if (chip->vendor.irq) { + ordinal = be32_to_cpu(*((__be32 *) (buf + 6))); + + ret = wait_for_stat(chip, TPM_STS_DATA_AVAIL | TPM_STS_VALID, + tpm_calc_ordinal_duration(chip, ordinal), + &chip->vendor.read_queue, false); + if (ret < 0) + goto out_err; + } + + return len; +out_err: + st33zp24_cancel(chip); + release_locality(chip); + return ret; +} + +/* + * st33zp24_recv received TPM response through TPM phy. + * @param: chip, the tpm_chip description as specified in driver/char/tpm/tpm.h. + * @param: buf, the buffer to store datas. + * @param: count, the number of bytes to send. + * @return: In case of success the number of bytes received. + * In other case, a < 0 value describing the issue. + */ +static int st33zp24_recv(struct tpm_chip *chip, unsigned char *buf, + size_t count) +{ + int size = 0; + int expected; + + if (!chip) + return -EBUSY; + + if (count < TPM_HEADER_SIZE) { + size = -EIO; + goto out; + } + + size = recv_data(chip, buf, TPM_HEADER_SIZE); + if (size < TPM_HEADER_SIZE) { + dev_err(&chip->dev, "Unable to read header\n"); + goto out; + } + + expected = be32_to_cpu(*(__be32 *)(buf + 2)); + if (expected > count) { + size = -EIO; + goto out; + } + + size += recv_data(chip, &buf[TPM_HEADER_SIZE], + expected - TPM_HEADER_SIZE); + if (size < expected) { + dev_err(&chip->dev, "Unable to read remainder of result\n"); + size = -ETIME; + } + +out: + st33zp24_cancel(chip); + release_locality(chip); + return size; +} + +/* + * st33zp24_req_canceled + * @param: chip, the tpm_chip description as specified in driver/char/tpm/tpm.h. + * @param: status, the TPM status. + * @return: Does TPM ready to compute a new command ? true. + */ +static bool st33zp24_req_canceled(struct tpm_chip *chip, u8 status) +{ + return (status == TPM_STS_COMMAND_READY); +} + +static const struct tpm_class_ops st33zp24_tpm = { + .send = st33zp24_send, + .recv = st33zp24_recv, + .cancel = st33zp24_cancel, + .status = st33zp24_status, + .req_complete_mask = TPM_STS_DATA_AVAIL | TPM_STS_VALID, + .req_complete_val = TPM_STS_DATA_AVAIL | TPM_STS_VALID, + .req_canceled = st33zp24_req_canceled, +}; + +/* + * st33zp24_probe initialize the TPM device + * @param: client, the i2c_client drescription (TPM I2C description). + * @param: id, the i2c_device_id struct. + * @return: 0 in case of success. + * -1 in other case. + */ +int st33zp24_probe(void *phy_id, const struct st33zp24_phy_ops *ops, + struct device *dev, int irq, int io_lpcpd) +{ + int ret; + u8 intmask = 0; + struct tpm_chip *chip; + struct st33zp24_dev *tpm_dev; + + chip = tpmm_chip_alloc(dev, &st33zp24_tpm); + if (IS_ERR(chip)) + return PTR_ERR(chip); + + tpm_dev = devm_kzalloc(dev, sizeof(struct st33zp24_dev), + GFP_KERNEL); + if (!tpm_dev) + return -ENOMEM; + + TPM_VPRIV(chip) = tpm_dev; + tpm_dev->phy_id = phy_id; + tpm_dev->ops = ops; + + chip->vendor.timeout_a = msecs_to_jiffies(TIS_SHORT_TIMEOUT); + chip->vendor.timeout_b = msecs_to_jiffies(TIS_LONG_TIMEOUT); + chip->vendor.timeout_c = msecs_to_jiffies(TIS_SHORT_TIMEOUT); + chip->vendor.timeout_d = msecs_to_jiffies(TIS_SHORT_TIMEOUT); + + chip->vendor.locality = LOCALITY0; + + if (irq) { + /* INTERRUPT Setup */ + init_waitqueue_head(&chip->vendor.read_queue); + tpm_dev->intrs = 0; + + if (request_locality(chip) != LOCALITY0) { + ret = -ENODEV; + goto _tpm_clean_answer; + } + + clear_interruption(tpm_dev); + ret = devm_request_irq(dev, irq, tpm_ioserirq_handler, + IRQF_TRIGGER_HIGH, "TPM SERIRQ management", + chip); + if (ret < 0) { + dev_err(&chip->dev, "TPM SERIRQ signals %d not available\n", + irq); + goto _tpm_clean_answer; + } + + intmask |= TPM_INTF_CMD_READY_INT + | TPM_INTF_STS_VALID_INT + | TPM_INTF_DATA_AVAIL_INT; + + ret = tpm_dev->ops->send(tpm_dev->phy_id, TPM_INT_ENABLE, + &intmask, 1); + if (ret < 0) + goto _tpm_clean_answer; + + intmask = TPM_GLOBAL_INT_ENABLE; + ret = tpm_dev->ops->send(tpm_dev->phy_id, (TPM_INT_ENABLE + 3), + &intmask, 1); + if (ret < 0) + goto _tpm_clean_answer; + + chip->vendor.irq = irq; + + disable_irq_nosync(chip->vendor.irq); + + tpm_gen_interrupt(chip); + } + + tpm_get_timeouts(chip); + tpm_do_selftest(chip); + + return tpm_chip_register(chip); +_tpm_clean_answer: + dev_info(&chip->dev, "TPM initialization fail\n"); + return ret; +} +EXPORT_SYMBOL(st33zp24_probe); + +/* + * st33zp24_remove remove the TPM device + * @param: tpm_data, the tpm phy. + * @return: 0 in case of success. + */ +int st33zp24_remove(struct tpm_chip *chip) +{ + tpm_chip_unregister(chip); + return 0; +} +EXPORT_SYMBOL(st33zp24_remove); + +#ifdef CONFIG_PM_SLEEP +/* + * st33zp24_pm_suspend suspend the TPM device + * @param: tpm_data, the tpm phy. + * @param: mesg, the power management message. + * @return: 0 in case of success. + */ +int st33zp24_pm_suspend(struct device *dev) +{ + struct tpm_chip *chip = dev_get_drvdata(dev); + struct st33zp24_dev *tpm_dev; + int ret = 0; + + tpm_dev = (struct st33zp24_dev *)TPM_VPRIV(chip); + + if (gpio_is_valid(tpm_dev->io_lpcpd)) + gpio_set_value(tpm_dev->io_lpcpd, 0); + else + ret = tpm_pm_suspend(dev); + + return ret; +} /* st33zp24_pm_suspend() */ +EXPORT_SYMBOL(st33zp24_pm_suspend); + +/* + * st33zp24_pm_resume resume the TPM device + * @param: tpm_data, the tpm phy. + * @return: 0 in case of success. + */ +int st33zp24_pm_resume(struct device *dev) +{ + struct tpm_chip *chip = dev_get_drvdata(dev); + struct st33zp24_dev *tpm_dev; + int ret = 0; + + tpm_dev = (struct st33zp24_dev *)TPM_VPRIV(chip); + + if (gpio_is_valid(tpm_dev->io_lpcpd)) { + gpio_set_value(tpm_dev->io_lpcpd, 1); + ret = wait_for_stat(chip, + TPM_STS_VALID, chip->vendor.timeout_b, + &chip->vendor.read_queue, false); + } else { + ret = tpm_pm_resume(dev); + if (!ret) + tpm_do_selftest(chip); + } + return ret; +} /* st33zp24_pm_resume() */ +EXPORT_SYMBOL(st33zp24_pm_resume); +#endif + +MODULE_AUTHOR("TPM support (TPMsupport@list.st.com)"); +MODULE_DESCRIPTION("ST33ZP24 TPM 1.2 driver"); +MODULE_VERSION("1.3.0"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/char/tpm/st33zp24/st33zp24.h b/kernel/drivers/char/tpm/st33zp24/st33zp24.h new file mode 100644 index 000000000..c207cebf6 --- /dev/null +++ b/kernel/drivers/char/tpm/st33zp24/st33zp24.h @@ -0,0 +1,37 @@ +/* + * STMicroelectronics TPM Linux driver for TPM ST33ZP24 + * Copyright (C) 2009 - 2015 STMicroelectronics + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * 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/>. + */ + +#ifndef __LOCAL_ST33ZP24_H__ +#define __LOCAL_ST33ZP24_H__ + +#define TPM_WRITE_DIRECTION 0x80 +#define TPM_BUFSIZE 2048 + +struct st33zp24_phy_ops { + int (*send)(void *phy_id, u8 tpm_register, u8 *tpm_data, int tpm_size); + int (*recv)(void *phy_id, u8 tpm_register, u8 *tpm_data, int tpm_size); +}; + +#ifdef CONFIG_PM_SLEEP +int st33zp24_pm_suspend(struct device *dev); +int st33zp24_pm_resume(struct device *dev); +#endif + +int st33zp24_probe(void *phy_id, const struct st33zp24_phy_ops *ops, + struct device *dev, int irq, int io_lpcpd); +int st33zp24_remove(struct tpm_chip *chip); +#endif /* __LOCAL_ST33ZP24_H__ */ diff --git a/kernel/drivers/char/tpm/tpm-chip.c b/kernel/drivers/char/tpm/tpm-chip.c new file mode 100644 index 000000000..283f00a7f --- /dev/null +++ b/kernel/drivers/char/tpm/tpm-chip.c @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2004 IBM Corporation + * Copyright (C) 2014 Intel Corporation + * + * Authors: + * Jarkko Sakkinen <jarkko.sakkinen@linux.intel.com> + * Leendert van Doorn <leendert@watson.ibm.com> + * Dave Safford <safford@watson.ibm.com> + * Reiner Sailer <sailer@watson.ibm.com> + * Kylene Hall <kjhall@us.ibm.com> + * + * Maintained by: <tpmdd-devel@lists.sourceforge.net> + * + * TPM chip management routines. + * + * 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, version 2 of the + * License. + * + */ + +#include <linux/poll.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/spinlock.h> +#include <linux/freezer.h> +#include <linux/major.h> +#include "tpm.h" +#include "tpm_eventlog.h" + +static DECLARE_BITMAP(dev_mask, TPM_NUM_DEVICES); +static LIST_HEAD(tpm_chip_list); +static DEFINE_SPINLOCK(driver_lock); + +struct class *tpm_class; +dev_t tpm_devt; + +/* + * tpm_chip_find_get - return tpm_chip for a given chip number + * @chip_num the device number for the chip + */ +struct tpm_chip *tpm_chip_find_get(int chip_num) +{ + struct tpm_chip *pos, *chip = NULL; + + rcu_read_lock(); + list_for_each_entry_rcu(pos, &tpm_chip_list, list) { + if (chip_num != TPM_ANY_NUM && chip_num != pos->dev_num) + continue; + + if (try_module_get(pos->pdev->driver->owner)) { + chip = pos; + break; + } + } + rcu_read_unlock(); + return chip; +} + +/** + * tpm_dev_release() - free chip memory and the device number + * @dev: the character device for the TPM chip + * + * This is used as the release function for the character device. + */ +static void tpm_dev_release(struct device *dev) +{ + struct tpm_chip *chip = container_of(dev, struct tpm_chip, dev); + + spin_lock(&driver_lock); + clear_bit(chip->dev_num, dev_mask); + spin_unlock(&driver_lock); + kfree(chip); +} + +/** + * tpmm_chip_alloc() - allocate a new struct tpm_chip instance + * @dev: device to which the chip is associated + * @ops: struct tpm_class_ops instance + * + * Allocates a new struct tpm_chip instance and assigns a free + * device number for it. Caller does not have to worry about + * freeing the allocated resources. When the devices is removed + * devres calls tpmm_chip_remove() to do the job. + */ +struct tpm_chip *tpmm_chip_alloc(struct device *dev, + const struct tpm_class_ops *ops) +{ + struct tpm_chip *chip; + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (chip == NULL) + return ERR_PTR(-ENOMEM); + + mutex_init(&chip->tpm_mutex); + INIT_LIST_HEAD(&chip->list); + + chip->ops = ops; + + spin_lock(&driver_lock); + chip->dev_num = find_first_zero_bit(dev_mask, TPM_NUM_DEVICES); + spin_unlock(&driver_lock); + + if (chip->dev_num >= TPM_NUM_DEVICES) { + dev_err(dev, "No available tpm device numbers\n"); + kfree(chip); + return ERR_PTR(-ENOMEM); + } + + set_bit(chip->dev_num, dev_mask); + + scnprintf(chip->devname, sizeof(chip->devname), "tpm%d", chip->dev_num); + + chip->pdev = dev; + + dev_set_drvdata(dev, chip); + + chip->dev.class = tpm_class; + chip->dev.release = tpm_dev_release; + chip->dev.parent = chip->pdev; + + if (chip->dev_num == 0) + chip->dev.devt = MKDEV(MISC_MAJOR, TPM_MINOR); + else + chip->dev.devt = MKDEV(MAJOR(tpm_devt), chip->dev_num); + + dev_set_name(&chip->dev, "%s", chip->devname); + + device_initialize(&chip->dev); + + chip->cdev.owner = chip->pdev->driver->owner; + cdev_init(&chip->cdev, &tpm_fops); + + return chip; +} +EXPORT_SYMBOL_GPL(tpmm_chip_alloc); + +static int tpm_dev_add_device(struct tpm_chip *chip) +{ + int rc; + + rc = cdev_add(&chip->cdev, chip->dev.devt, 1); + if (rc) { + dev_err(&chip->dev, + "unable to cdev_add() %s, major %d, minor %d, err=%d\n", + chip->devname, MAJOR(chip->dev.devt), + MINOR(chip->dev.devt), rc); + + device_unregister(&chip->dev); + return rc; + } + + rc = device_add(&chip->dev); + if (rc) { + dev_err(&chip->dev, + "unable to device_register() %s, major %d, minor %d, err=%d\n", + chip->devname, MAJOR(chip->dev.devt), + MINOR(chip->dev.devt), rc); + + return rc; + } + + return rc; +} + +static void tpm_dev_del_device(struct tpm_chip *chip) +{ + cdev_del(&chip->cdev); + device_unregister(&chip->dev); +} + +static int tpm1_chip_register(struct tpm_chip *chip) +{ + int rc; + + if (chip->flags & TPM_CHIP_FLAG_TPM2) + return 0; + + rc = tpm_sysfs_add_device(chip); + if (rc) + return rc; + + rc = tpm_add_ppi(chip); + if (rc) { + tpm_sysfs_del_device(chip); + return rc; + } + + chip->bios_dir = tpm_bios_log_setup(chip->devname); + + return 0; +} + +static void tpm1_chip_unregister(struct tpm_chip *chip) +{ + if (chip->flags & TPM_CHIP_FLAG_TPM2) + return; + + if (chip->bios_dir) + tpm_bios_log_teardown(chip->bios_dir); + + tpm_remove_ppi(chip); + + tpm_sysfs_del_device(chip); +} + +/* + * tpm_chip_register() - create a character device for the TPM chip + * @chip: TPM chip to use. + * + * Creates a character device for the TPM chip and adds sysfs attributes for + * the device. As the last step this function adds the chip to the list of TPM + * chips available for in-kernel use. + * + * This function should be only called after the chip initialization is + * complete. + */ +int tpm_chip_register(struct tpm_chip *chip) +{ + int rc; + + rc = tpm1_chip_register(chip); + if (rc) + return rc; + + rc = tpm_dev_add_device(chip); + if (rc) + goto out_err; + + /* Make the chip available. */ + spin_lock(&driver_lock); + list_add_rcu(&chip->list, &tpm_chip_list); + spin_unlock(&driver_lock); + + chip->flags |= TPM_CHIP_FLAG_REGISTERED; + + return 0; +out_err: + tpm1_chip_unregister(chip); + return rc; +} +EXPORT_SYMBOL_GPL(tpm_chip_register); + +/* + * tpm_chip_unregister() - release the TPM driver + * @chip: TPM chip to use. + * + * Takes the chip first away from the list of available TPM chips and then + * cleans up all the resources reserved by tpm_chip_register(). + * + * NOTE: This function should be only called before deinitializing chip + * resources. + */ +void tpm_chip_unregister(struct tpm_chip *chip) +{ + if (!(chip->flags & TPM_CHIP_FLAG_REGISTERED)) + return; + + spin_lock(&driver_lock); + list_del_rcu(&chip->list); + spin_unlock(&driver_lock); + synchronize_rcu(); + + tpm1_chip_unregister(chip); + tpm_dev_del_device(chip); +} +EXPORT_SYMBOL_GPL(tpm_chip_unregister); diff --git a/kernel/drivers/char/tpm/tpm-dev.c b/kernel/drivers/char/tpm/tpm-dev.c new file mode 100644 index 000000000..de0337ebd --- /dev/null +++ b/kernel/drivers/char/tpm/tpm-dev.c @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2004 IBM Corporation + * Authors: + * Leendert van Doorn <leendert@watson.ibm.com> + * Dave Safford <safford@watson.ibm.com> + * Reiner Sailer <sailer@watson.ibm.com> + * Kylene Hall <kjhall@us.ibm.com> + * + * Copyright (C) 2013 Obsidian Research Corp + * Jason Gunthorpe <jgunthorpe@obsidianresearch.com> + * + * Device file system interface to the TPM + * + * 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, version 2 of the + * License. + * + */ +#include <linux/slab.h> +#include <linux/uaccess.h> +#include "tpm.h" + +struct file_priv { + struct tpm_chip *chip; + + /* Data passed to and from the tpm via the read/write calls */ + atomic_t data_pending; + struct mutex buffer_mutex; + + struct timer_list user_read_timer; /* user needs to claim result */ + struct work_struct work; + + u8 data_buffer[TPM_BUFSIZE]; +}; + +static void user_reader_timeout(unsigned long ptr) +{ + struct file_priv *priv = (struct file_priv *)ptr; + + schedule_work(&priv->work); +} + +static void timeout_work(struct work_struct *work) +{ + struct file_priv *priv = container_of(work, struct file_priv, work); + + mutex_lock(&priv->buffer_mutex); + atomic_set(&priv->data_pending, 0); + memset(priv->data_buffer, 0, sizeof(priv->data_buffer)); + mutex_unlock(&priv->buffer_mutex); +} + +static int tpm_open(struct inode *inode, struct file *file) +{ + struct tpm_chip *chip = + container_of(inode->i_cdev, struct tpm_chip, cdev); + struct file_priv *priv; + + /* It's assured that the chip will be opened just once, + * by the check of is_open variable, which is protected + * by driver_lock. */ + if (test_and_set_bit(0, &chip->is_open)) { + dev_dbg(chip->pdev, "Another process owns this TPM\n"); + return -EBUSY; + } + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (priv == NULL) { + clear_bit(0, &chip->is_open); + return -ENOMEM; + } + + priv->chip = chip; + atomic_set(&priv->data_pending, 0); + mutex_init(&priv->buffer_mutex); + setup_timer(&priv->user_read_timer, user_reader_timeout, + (unsigned long)priv); + INIT_WORK(&priv->work, timeout_work); + + file->private_data = priv; + get_device(chip->pdev); + return 0; +} + +static ssize_t tpm_read(struct file *file, char __user *buf, + size_t size, loff_t *off) +{ + struct file_priv *priv = file->private_data; + ssize_t ret_size; + int rc; + + del_singleshot_timer_sync(&priv->user_read_timer); + flush_work(&priv->work); + ret_size = atomic_read(&priv->data_pending); + if (ret_size > 0) { /* relay data */ + ssize_t orig_ret_size = ret_size; + if (size < ret_size) + ret_size = size; + + mutex_lock(&priv->buffer_mutex); + rc = copy_to_user(buf, priv->data_buffer, ret_size); + memset(priv->data_buffer, 0, orig_ret_size); + if (rc) + ret_size = -EFAULT; + + mutex_unlock(&priv->buffer_mutex); + } + + atomic_set(&priv->data_pending, 0); + + return ret_size; +} + +static ssize_t tpm_write(struct file *file, const char __user *buf, + size_t size, loff_t *off) +{ + struct file_priv *priv = file->private_data; + size_t in_size = size; + ssize_t out_size; + + /* cannot perform a write until the read has cleared + either via tpm_read or a user_read_timer timeout. + This also prevents splitted buffered writes from blocking here. + */ + if (atomic_read(&priv->data_pending) != 0) + return -EBUSY; + + if (in_size > TPM_BUFSIZE) + return -E2BIG; + + mutex_lock(&priv->buffer_mutex); + + if (copy_from_user + (priv->data_buffer, (void __user *) buf, in_size)) { + mutex_unlock(&priv->buffer_mutex); + return -EFAULT; + } + + /* atomic tpm command send and result receive */ + out_size = tpm_transmit(priv->chip, priv->data_buffer, + sizeof(priv->data_buffer)); + if (out_size < 0) { + mutex_unlock(&priv->buffer_mutex); + return out_size; + } + + atomic_set(&priv->data_pending, out_size); + mutex_unlock(&priv->buffer_mutex); + + /* Set a timeout by which the reader must come claim the result */ + mod_timer(&priv->user_read_timer, jiffies + (60 * HZ)); + + return in_size; +} + +/* + * Called on file close + */ +static int tpm_release(struct inode *inode, struct file *file) +{ + struct file_priv *priv = file->private_data; + + del_singleshot_timer_sync(&priv->user_read_timer); + flush_work(&priv->work); + file->private_data = NULL; + atomic_set(&priv->data_pending, 0); + clear_bit(0, &priv->chip->is_open); + put_device(priv->chip->pdev); + kfree(priv); + return 0; +} + +const struct file_operations tpm_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .open = tpm_open, + .read = tpm_read, + .write = tpm_write, + .release = tpm_release, +}; + + diff --git a/kernel/drivers/char/tpm/tpm-interface.c b/kernel/drivers/char/tpm/tpm-interface.c new file mode 100644 index 000000000..e85d3416d --- /dev/null +++ b/kernel/drivers/char/tpm/tpm-interface.c @@ -0,0 +1,1056 @@ +/* + * Copyright (C) 2004 IBM Corporation + * Copyright (C) 2014 Intel Corporation + * + * Authors: + * Leendert van Doorn <leendert@watson.ibm.com> + * Dave Safford <safford@watson.ibm.com> + * Reiner Sailer <sailer@watson.ibm.com> + * Kylene Hall <kjhall@us.ibm.com> + * + * Maintained by: <tpmdd-devel@lists.sourceforge.net> + * + * Device driver for TCG/TCPA TPM (trusted platform module). + * Specifications at www.trustedcomputinggroup.org + * + * 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, version 2 of the + * License. + * + * Note, the TPM chip is not interrupt driven (only polling) + * and can have very long timeouts (minutes!). Hence the unusual + * calls to msleep. + * + */ + +#include <linux/poll.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/spinlock.h> +#include <linux/freezer.h> + +#include "tpm.h" +#include "tpm_eventlog.h" + +#define TPM_MAX_ORDINAL 243 +#define TSC_MAX_ORDINAL 12 +#define TPM_PROTECTED_COMMAND 0x00 +#define TPM_CONNECTION_COMMAND 0x40 + +/* + * Bug workaround - some TPM's don't flush the most + * recently changed pcr on suspend, so force the flush + * with an extend to the selected _unused_ non-volatile pcr. + */ +static int tpm_suspend_pcr; +module_param_named(suspend_pcr, tpm_suspend_pcr, uint, 0644); +MODULE_PARM_DESC(suspend_pcr, + "PCR to use for dummy writes to faciltate flush on suspend."); + +/* + * Array with one entry per ordinal defining the maximum amount + * of time the chip could take to return the result. The ordinal + * designation of short, medium or long is defined in a table in + * TCG Specification TPM Main Part 2 TPM Structures Section 17. The + * values of the SHORT, MEDIUM, and LONG durations are retrieved + * from the chip during initialization with a call to tpm_get_timeouts. + */ +static const u8 tpm_ordinal_duration[TPM_MAX_ORDINAL] = { + TPM_UNDEFINED, /* 0 */ + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, /* 5 */ + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_SHORT, /* 10 */ + TPM_SHORT, + TPM_MEDIUM, + TPM_LONG, + TPM_LONG, + TPM_MEDIUM, /* 15 */ + TPM_SHORT, + TPM_SHORT, + TPM_MEDIUM, + TPM_LONG, + TPM_SHORT, /* 20 */ + TPM_SHORT, + TPM_MEDIUM, + TPM_MEDIUM, + TPM_MEDIUM, + TPM_SHORT, /* 25 */ + TPM_SHORT, + TPM_MEDIUM, + TPM_SHORT, + TPM_SHORT, + TPM_MEDIUM, /* 30 */ + TPM_LONG, + TPM_MEDIUM, + TPM_SHORT, + TPM_SHORT, + TPM_SHORT, /* 35 */ + TPM_MEDIUM, + TPM_MEDIUM, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_MEDIUM, /* 40 */ + TPM_LONG, + TPM_MEDIUM, + TPM_SHORT, + TPM_SHORT, + TPM_SHORT, /* 45 */ + TPM_SHORT, + TPM_SHORT, + TPM_SHORT, + TPM_LONG, + TPM_MEDIUM, /* 50 */ + TPM_MEDIUM, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, /* 55 */ + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_MEDIUM, /* 60 */ + TPM_MEDIUM, + TPM_MEDIUM, + TPM_SHORT, + TPM_SHORT, + TPM_MEDIUM, /* 65 */ + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_SHORT, /* 70 */ + TPM_SHORT, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, /* 75 */ + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_LONG, /* 80 */ + TPM_UNDEFINED, + TPM_MEDIUM, + TPM_LONG, + TPM_SHORT, + TPM_UNDEFINED, /* 85 */ + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_SHORT, /* 90 */ + TPM_SHORT, + TPM_SHORT, + TPM_SHORT, + TPM_SHORT, + TPM_UNDEFINED, /* 95 */ + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_MEDIUM, /* 100 */ + TPM_SHORT, + TPM_SHORT, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, /* 105 */ + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_SHORT, /* 110 */ + TPM_SHORT, + TPM_SHORT, + TPM_SHORT, + TPM_SHORT, + TPM_SHORT, /* 115 */ + TPM_SHORT, + TPM_SHORT, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_LONG, /* 120 */ + TPM_LONG, + TPM_MEDIUM, + TPM_UNDEFINED, + TPM_SHORT, + TPM_SHORT, /* 125 */ + TPM_SHORT, + TPM_LONG, + TPM_SHORT, + TPM_SHORT, + TPM_SHORT, /* 130 */ + TPM_MEDIUM, + TPM_UNDEFINED, + TPM_SHORT, + TPM_MEDIUM, + TPM_UNDEFINED, /* 135 */ + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_SHORT, /* 140 */ + TPM_SHORT, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, /* 145 */ + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_SHORT, /* 150 */ + TPM_MEDIUM, + TPM_MEDIUM, + TPM_SHORT, + TPM_SHORT, + TPM_UNDEFINED, /* 155 */ + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_SHORT, /* 160 */ + TPM_SHORT, + TPM_SHORT, + TPM_SHORT, + TPM_UNDEFINED, + TPM_UNDEFINED, /* 165 */ + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_LONG, /* 170 */ + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, /* 175 */ + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_MEDIUM, /* 180 */ + TPM_SHORT, + TPM_MEDIUM, + TPM_MEDIUM, + TPM_MEDIUM, + TPM_MEDIUM, /* 185 */ + TPM_SHORT, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, /* 190 */ + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, /* 195 */ + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_SHORT, /* 200 */ + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_SHORT, + TPM_SHORT, /* 205 */ + TPM_SHORT, + TPM_SHORT, + TPM_SHORT, + TPM_SHORT, + TPM_MEDIUM, /* 210 */ + TPM_UNDEFINED, + TPM_MEDIUM, + TPM_MEDIUM, + TPM_MEDIUM, + TPM_UNDEFINED, /* 215 */ + TPM_MEDIUM, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_SHORT, + TPM_SHORT, /* 220 */ + TPM_SHORT, + TPM_SHORT, + TPM_SHORT, + TPM_SHORT, + TPM_UNDEFINED, /* 225 */ + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_SHORT, /* 230 */ + TPM_LONG, + TPM_MEDIUM, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, /* 235 */ + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_UNDEFINED, + TPM_SHORT, /* 240 */ + TPM_UNDEFINED, + TPM_MEDIUM, +}; + +/* + * Returns max number of jiffies to wait + */ +unsigned long tpm_calc_ordinal_duration(struct tpm_chip *chip, + u32 ordinal) +{ + int duration_idx = TPM_UNDEFINED; + int duration = 0; + u8 category = (ordinal >> 24) & 0xFF; + + if ((category == TPM_PROTECTED_COMMAND && ordinal < TPM_MAX_ORDINAL) || + (category == TPM_CONNECTION_COMMAND && ordinal < TSC_MAX_ORDINAL)) + duration_idx = tpm_ordinal_duration[ordinal]; + + if (duration_idx != TPM_UNDEFINED) + duration = chip->vendor.duration[duration_idx]; + if (duration <= 0) + return 2 * 60 * HZ; + else + return duration; +} +EXPORT_SYMBOL_GPL(tpm_calc_ordinal_duration); + +/* + * Internal kernel interface to transmit TPM commands + */ +ssize_t tpm_transmit(struct tpm_chip *chip, const char *buf, + size_t bufsiz) +{ + ssize_t rc; + u32 count, ordinal; + unsigned long stop; + + if (bufsiz > TPM_BUFSIZE) + bufsiz = TPM_BUFSIZE; + + count = be32_to_cpu(*((__be32 *) (buf + 2))); + ordinal = be32_to_cpu(*((__be32 *) (buf + 6))); + if (count == 0) + return -ENODATA; + if (count > bufsiz) { + dev_err(chip->pdev, + "invalid count value %x %zx\n", count, bufsiz); + return -E2BIG; + } + + mutex_lock(&chip->tpm_mutex); + + rc = chip->ops->send(chip, (u8 *) buf, count); + if (rc < 0) { + dev_err(chip->pdev, + "tpm_transmit: tpm_send: error %zd\n", rc); + goto out; + } + + if (chip->vendor.irq) + goto out_recv; + + if (chip->flags & TPM_CHIP_FLAG_TPM2) + stop = jiffies + tpm2_calc_ordinal_duration(chip, ordinal); + else + stop = jiffies + tpm_calc_ordinal_duration(chip, ordinal); + do { + u8 status = chip->ops->status(chip); + if ((status & chip->ops->req_complete_mask) == + chip->ops->req_complete_val) + goto out_recv; + + if (chip->ops->req_canceled(chip, status)) { + dev_err(chip->pdev, "Operation Canceled\n"); + rc = -ECANCELED; + goto out; + } + + msleep(TPM_TIMEOUT); /* CHECK */ + rmb(); + } while (time_before(jiffies, stop)); + + chip->ops->cancel(chip); + dev_err(chip->pdev, "Operation Timed out\n"); + rc = -ETIME; + goto out; + +out_recv: + rc = chip->ops->recv(chip, (u8 *) buf, bufsiz); + if (rc < 0) + dev_err(chip->pdev, + "tpm_transmit: tpm_recv: error %zd\n", rc); +out: + mutex_unlock(&chip->tpm_mutex); + return rc; +} + +#define TPM_DIGEST_SIZE 20 +#define TPM_RET_CODE_IDX 6 + +ssize_t tpm_transmit_cmd(struct tpm_chip *chip, void *cmd, + int len, const char *desc) +{ + struct tpm_output_header *header; + int err; + + len = tpm_transmit(chip, (u8 *) cmd, len); + if (len < 0) + return len; + else if (len < TPM_HEADER_SIZE) + return -EFAULT; + + header = cmd; + + err = be32_to_cpu(header->return_code); + if (err != 0 && desc) + dev_err(chip->pdev, "A TPM error (%d) occurred %s\n", err, + desc); + + return err; +} + +#define TPM_INTERNAL_RESULT_SIZE 200 +#define TPM_ORD_GET_CAP cpu_to_be32(101) +#define TPM_ORD_GET_RANDOM cpu_to_be32(70) + +static const struct tpm_input_header tpm_getcap_header = { + .tag = TPM_TAG_RQU_COMMAND, + .length = cpu_to_be32(22), + .ordinal = TPM_ORD_GET_CAP +}; + +ssize_t tpm_getcap(struct device *dev, __be32 subcap_id, cap_t *cap, + const char *desc) +{ + struct tpm_cmd_t tpm_cmd; + int rc; + struct tpm_chip *chip = dev_get_drvdata(dev); + + tpm_cmd.header.in = tpm_getcap_header; + if (subcap_id == CAP_VERSION_1_1 || subcap_id == CAP_VERSION_1_2) { + tpm_cmd.params.getcap_in.cap = subcap_id; + /*subcap field not necessary */ + tpm_cmd.params.getcap_in.subcap_size = cpu_to_be32(0); + tpm_cmd.header.in.length -= cpu_to_be32(sizeof(__be32)); + } else { + if (subcap_id == TPM_CAP_FLAG_PERM || + subcap_id == TPM_CAP_FLAG_VOL) + tpm_cmd.params.getcap_in.cap = TPM_CAP_FLAG; + else + tpm_cmd.params.getcap_in.cap = TPM_CAP_PROP; + tpm_cmd.params.getcap_in.subcap_size = cpu_to_be32(4); + tpm_cmd.params.getcap_in.subcap = subcap_id; + } + rc = tpm_transmit_cmd(chip, &tpm_cmd, TPM_INTERNAL_RESULT_SIZE, desc); + if (!rc) + *cap = tpm_cmd.params.getcap_out.cap; + return rc; +} + +void tpm_gen_interrupt(struct tpm_chip *chip) +{ + struct tpm_cmd_t tpm_cmd; + ssize_t rc; + + tpm_cmd.header.in = tpm_getcap_header; + tpm_cmd.params.getcap_in.cap = TPM_CAP_PROP; + tpm_cmd.params.getcap_in.subcap_size = cpu_to_be32(4); + tpm_cmd.params.getcap_in.subcap = TPM_CAP_PROP_TIS_TIMEOUT; + + rc = tpm_transmit_cmd(chip, &tpm_cmd, TPM_INTERNAL_RESULT_SIZE, + "attempting to determine the timeouts"); +} +EXPORT_SYMBOL_GPL(tpm_gen_interrupt); + +#define TPM_ORD_STARTUP cpu_to_be32(153) +#define TPM_ST_CLEAR cpu_to_be16(1) +#define TPM_ST_STATE cpu_to_be16(2) +#define TPM_ST_DEACTIVATED cpu_to_be16(3) +static const struct tpm_input_header tpm_startup_header = { + .tag = TPM_TAG_RQU_COMMAND, + .length = cpu_to_be32(12), + .ordinal = TPM_ORD_STARTUP +}; + +static int tpm_startup(struct tpm_chip *chip, __be16 startup_type) +{ + struct tpm_cmd_t start_cmd; + start_cmd.header.in = tpm_startup_header; + + start_cmd.params.startup_in.startup_type = startup_type; + return tpm_transmit_cmd(chip, &start_cmd, TPM_INTERNAL_RESULT_SIZE, + "attempting to start the TPM"); +} + +int tpm_get_timeouts(struct tpm_chip *chip) +{ + struct tpm_cmd_t tpm_cmd; + unsigned long new_timeout[4]; + unsigned long old_timeout[4]; + struct duration_t *duration_cap; + ssize_t rc; + + tpm_cmd.header.in = tpm_getcap_header; + tpm_cmd.params.getcap_in.cap = TPM_CAP_PROP; + tpm_cmd.params.getcap_in.subcap_size = cpu_to_be32(4); + tpm_cmd.params.getcap_in.subcap = TPM_CAP_PROP_TIS_TIMEOUT; + rc = tpm_transmit_cmd(chip, &tpm_cmd, TPM_INTERNAL_RESULT_SIZE, NULL); + + if (rc == TPM_ERR_INVALID_POSTINIT) { + /* The TPM is not started, we are the first to talk to it. + Execute a startup command. */ + dev_info(chip->pdev, "Issuing TPM_STARTUP"); + if (tpm_startup(chip, TPM_ST_CLEAR)) + return rc; + + tpm_cmd.header.in = tpm_getcap_header; + tpm_cmd.params.getcap_in.cap = TPM_CAP_PROP; + tpm_cmd.params.getcap_in.subcap_size = cpu_to_be32(4); + tpm_cmd.params.getcap_in.subcap = TPM_CAP_PROP_TIS_TIMEOUT; + rc = tpm_transmit_cmd(chip, &tpm_cmd, TPM_INTERNAL_RESULT_SIZE, + NULL); + } + if (rc) { + dev_err(chip->pdev, + "A TPM error (%zd) occurred attempting to determine the timeouts\n", + rc); + goto duration; + } + + if (be32_to_cpu(tpm_cmd.header.out.return_code) != 0 || + be32_to_cpu(tpm_cmd.header.out.length) + != sizeof(tpm_cmd.header.out) + sizeof(u32) + 4 * sizeof(u32)) + return -EINVAL; + + old_timeout[0] = be32_to_cpu(tpm_cmd.params.getcap_out.cap.timeout.a); + old_timeout[1] = be32_to_cpu(tpm_cmd.params.getcap_out.cap.timeout.b); + old_timeout[2] = be32_to_cpu(tpm_cmd.params.getcap_out.cap.timeout.c); + old_timeout[3] = be32_to_cpu(tpm_cmd.params.getcap_out.cap.timeout.d); + memcpy(new_timeout, old_timeout, sizeof(new_timeout)); + + /* + * Provide ability for vendor overrides of timeout values in case + * of misreporting. + */ + if (chip->ops->update_timeouts != NULL) + chip->vendor.timeout_adjusted = + chip->ops->update_timeouts(chip, new_timeout); + + if (!chip->vendor.timeout_adjusted) { + /* Don't overwrite default if value is 0 */ + if (new_timeout[0] != 0 && new_timeout[0] < 1000) { + int i; + + /* timeouts in msec rather usec */ + for (i = 0; i != ARRAY_SIZE(new_timeout); i++) + new_timeout[i] *= 1000; + chip->vendor.timeout_adjusted = true; + } + } + + /* Report adjusted timeouts */ + if (chip->vendor.timeout_adjusted) { + dev_info(chip->pdev, + HW_ERR "Adjusting reported timeouts: A %lu->%luus B %lu->%luus C %lu->%luus D %lu->%luus\n", + old_timeout[0], new_timeout[0], + old_timeout[1], new_timeout[1], + old_timeout[2], new_timeout[2], + old_timeout[3], new_timeout[3]); + } + + chip->vendor.timeout_a = usecs_to_jiffies(new_timeout[0]); + chip->vendor.timeout_b = usecs_to_jiffies(new_timeout[1]); + chip->vendor.timeout_c = usecs_to_jiffies(new_timeout[2]); + chip->vendor.timeout_d = usecs_to_jiffies(new_timeout[3]); + +duration: + tpm_cmd.header.in = tpm_getcap_header; + tpm_cmd.params.getcap_in.cap = TPM_CAP_PROP; + tpm_cmd.params.getcap_in.subcap_size = cpu_to_be32(4); + tpm_cmd.params.getcap_in.subcap = TPM_CAP_PROP_TIS_DURATION; + + rc = tpm_transmit_cmd(chip, &tpm_cmd, TPM_INTERNAL_RESULT_SIZE, + "attempting to determine the durations"); + if (rc) + return rc; + + if (be32_to_cpu(tpm_cmd.header.out.return_code) != 0 || + be32_to_cpu(tpm_cmd.header.out.length) + != sizeof(tpm_cmd.header.out) + sizeof(u32) + 3 * sizeof(u32)) + return -EINVAL; + + duration_cap = &tpm_cmd.params.getcap_out.cap.duration; + chip->vendor.duration[TPM_SHORT] = + usecs_to_jiffies(be32_to_cpu(duration_cap->tpm_short)); + chip->vendor.duration[TPM_MEDIUM] = + usecs_to_jiffies(be32_to_cpu(duration_cap->tpm_medium)); + chip->vendor.duration[TPM_LONG] = + usecs_to_jiffies(be32_to_cpu(duration_cap->tpm_long)); + + /* The Broadcom BCM0102 chipset in a Dell Latitude D820 gets the above + * value wrong and apparently reports msecs rather than usecs. So we + * fix up the resulting too-small TPM_SHORT value to make things work. + * We also scale the TPM_MEDIUM and -_LONG values by 1000. + */ + if (chip->vendor.duration[TPM_SHORT] < (HZ / 100)) { + chip->vendor.duration[TPM_SHORT] = HZ; + chip->vendor.duration[TPM_MEDIUM] *= 1000; + chip->vendor.duration[TPM_LONG] *= 1000; + chip->vendor.duration_adjusted = true; + dev_info(chip->pdev, "Adjusting TPM timeout parameters."); + } + return 0; +} +EXPORT_SYMBOL_GPL(tpm_get_timeouts); + +#define TPM_ORD_CONTINUE_SELFTEST 83 +#define CONTINUE_SELFTEST_RESULT_SIZE 10 + +static struct tpm_input_header continue_selftest_header = { + .tag = TPM_TAG_RQU_COMMAND, + .length = cpu_to_be32(10), + .ordinal = cpu_to_be32(TPM_ORD_CONTINUE_SELFTEST), +}; + +/** + * tpm_continue_selftest -- run TPM's selftest + * @chip: TPM chip to use + * + * Returns 0 on success, < 0 in case of fatal error or a value > 0 representing + * a TPM error code. + */ +static int tpm_continue_selftest(struct tpm_chip *chip) +{ + int rc; + struct tpm_cmd_t cmd; + + cmd.header.in = continue_selftest_header; + rc = tpm_transmit_cmd(chip, &cmd, CONTINUE_SELFTEST_RESULT_SIZE, + "continue selftest"); + return rc; +} + +#define TPM_ORDINAL_PCRREAD cpu_to_be32(21) +#define READ_PCR_RESULT_SIZE 30 +static struct tpm_input_header pcrread_header = { + .tag = TPM_TAG_RQU_COMMAND, + .length = cpu_to_be32(14), + .ordinal = TPM_ORDINAL_PCRREAD +}; + +int tpm_pcr_read_dev(struct tpm_chip *chip, int pcr_idx, u8 *res_buf) +{ + int rc; + struct tpm_cmd_t cmd; + + cmd.header.in = pcrread_header; + cmd.params.pcrread_in.pcr_idx = cpu_to_be32(pcr_idx); + rc = tpm_transmit_cmd(chip, &cmd, READ_PCR_RESULT_SIZE, + "attempting to read a pcr value"); + + if (rc == 0) + memcpy(res_buf, cmd.params.pcrread_out.pcr_result, + TPM_DIGEST_SIZE); + return rc; +} + +/** + * tpm_pcr_read - read a pcr value + * @chip_num: tpm idx # or ANY + * @pcr_idx: pcr idx to retrieve + * @res_buf: TPM_PCR value + * size of res_buf is 20 bytes (or NULL if you don't care) + * + * The TPM driver should be built-in, but for whatever reason it + * isn't, protect against the chip disappearing, by incrementing + * the module usage count. + */ +int tpm_pcr_read(u32 chip_num, int pcr_idx, u8 *res_buf) +{ + struct tpm_chip *chip; + int rc; + + chip = tpm_chip_find_get(chip_num); + if (chip == NULL) + return -ENODEV; + if (chip->flags & TPM_CHIP_FLAG_TPM2) + rc = tpm2_pcr_read(chip, pcr_idx, res_buf); + else + rc = tpm_pcr_read_dev(chip, pcr_idx, res_buf); + tpm_chip_put(chip); + return rc; +} +EXPORT_SYMBOL_GPL(tpm_pcr_read); + +/** + * tpm_pcr_extend - extend pcr value with hash + * @chip_num: tpm idx # or AN& + * @pcr_idx: pcr idx to extend + * @hash: hash value used to extend pcr value + * + * The TPM driver should be built-in, but for whatever reason it + * isn't, protect against the chip disappearing, by incrementing + * the module usage count. + */ +#define TPM_ORD_PCR_EXTEND cpu_to_be32(20) +#define EXTEND_PCR_RESULT_SIZE 34 +static struct tpm_input_header pcrextend_header = { + .tag = TPM_TAG_RQU_COMMAND, + .length = cpu_to_be32(34), + .ordinal = TPM_ORD_PCR_EXTEND +}; + +int tpm_pcr_extend(u32 chip_num, int pcr_idx, const u8 *hash) +{ + struct tpm_cmd_t cmd; + int rc; + struct tpm_chip *chip; + + chip = tpm_chip_find_get(chip_num); + if (chip == NULL) + return -ENODEV; + + if (chip->flags & TPM_CHIP_FLAG_TPM2) { + rc = tpm2_pcr_extend(chip, pcr_idx, hash); + tpm_chip_put(chip); + return rc; + } + + cmd.header.in = pcrextend_header; + cmd.params.pcrextend_in.pcr_idx = cpu_to_be32(pcr_idx); + memcpy(cmd.params.pcrextend_in.hash, hash, TPM_DIGEST_SIZE); + rc = tpm_transmit_cmd(chip, &cmd, EXTEND_PCR_RESULT_SIZE, + "attempting extend a PCR value"); + + tpm_chip_put(chip); + return rc; +} +EXPORT_SYMBOL_GPL(tpm_pcr_extend); + +/** + * tpm_do_selftest - have the TPM continue its selftest and wait until it + * can receive further commands + * @chip: TPM chip to use + * + * Returns 0 on success, < 0 in case of fatal error or a value > 0 representing + * a TPM error code. + */ +int tpm_do_selftest(struct tpm_chip *chip) +{ + int rc; + unsigned int loops; + unsigned int delay_msec = 100; + unsigned long duration; + struct tpm_cmd_t cmd; + + duration = tpm_calc_ordinal_duration(chip, TPM_ORD_CONTINUE_SELFTEST); + + loops = jiffies_to_msecs(duration) / delay_msec; + + rc = tpm_continue_selftest(chip); + /* This may fail if there was no TPM driver during a suspend/resume + * cycle; some may return 10 (BAD_ORDINAL), others 28 (FAILEDSELFTEST) + */ + if (rc) + return rc; + + do { + /* Attempt to read a PCR value */ + cmd.header.in = pcrread_header; + cmd.params.pcrread_in.pcr_idx = cpu_to_be32(0); + rc = tpm_transmit(chip, (u8 *) &cmd, READ_PCR_RESULT_SIZE); + /* Some buggy TPMs will not respond to tpm_tis_ready() for + * around 300ms while the self test is ongoing, keep trying + * until the self test duration expires. */ + if (rc == -ETIME) { + dev_info(chip->pdev, HW_ERR "TPM command timed out during continue self test"); + msleep(delay_msec); + continue; + } + + if (rc < TPM_HEADER_SIZE) + return -EFAULT; + + rc = be32_to_cpu(cmd.header.out.return_code); + if (rc == TPM_ERR_DISABLED || rc == TPM_ERR_DEACTIVATED) { + dev_info(chip->pdev, + "TPM is disabled/deactivated (0x%X)\n", rc); + /* TPM is disabled and/or deactivated; driver can + * proceed and TPM does handle commands for + * suspend/resume correctly + */ + return 0; + } + if (rc != TPM_WARN_DOING_SELFTEST) + return rc; + msleep(delay_msec); + } while (--loops > 0); + + return rc; +} +EXPORT_SYMBOL_GPL(tpm_do_selftest); + +int tpm_send(u32 chip_num, void *cmd, size_t buflen) +{ + struct tpm_chip *chip; + int rc; + + chip = tpm_chip_find_get(chip_num); + if (chip == NULL) + return -ENODEV; + + rc = tpm_transmit_cmd(chip, cmd, buflen, "attempting tpm_cmd"); + + tpm_chip_put(chip); + return rc; +} +EXPORT_SYMBOL_GPL(tpm_send); + +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 & mask) == mask) + return true; + if (check_cancel && chip->ops->req_canceled(chip, status)) { + *canceled = true; + return true; + } + return false; +} + +int wait_for_tpm_stat(struct tpm_chip *chip, u8 mask, unsigned long timeout, + wait_queue_head_t *queue, bool check_cancel) +{ + unsigned long stop; + long rc; + u8 status; + bool canceled = false; + + /* check current status */ + status = chip->ops->status(chip); + if ((status & mask) == mask) + return 0; + + stop = jiffies + timeout; + + if (chip->vendor.irq) { +again: + timeout = stop - jiffies; + if ((long)timeout <= 0) + return -ETIME; + rc = wait_event_interruptible_timeout(*queue, + 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 { + msleep(TPM_TIMEOUT); + status = chip->ops->status(chip); + if ((status & mask) == mask) + return 0; + } while (time_before(jiffies, stop)); + } + return -ETIME; +} +EXPORT_SYMBOL_GPL(wait_for_tpm_stat); + +#define TPM_ORD_SAVESTATE cpu_to_be32(152) +#define SAVESTATE_RESULT_SIZE 10 + +static struct tpm_input_header savestate_header = { + .tag = TPM_TAG_RQU_COMMAND, + .length = cpu_to_be32(10), + .ordinal = TPM_ORD_SAVESTATE +}; + +/* + * We are about to suspend. Save the TPM state + * so that it can be restored. + */ +int tpm_pm_suspend(struct device *dev) +{ + struct tpm_chip *chip = dev_get_drvdata(dev); + struct tpm_cmd_t cmd; + int rc, try; + + u8 dummy_hash[TPM_DIGEST_SIZE] = { 0 }; + + if (chip == NULL) + return -ENODEV; + + if (chip->flags & TPM_CHIP_FLAG_TPM2) { + tpm2_shutdown(chip, TPM2_SU_STATE); + return 0; + } + + /* for buggy tpm, flush pcrs with extend to selected dummy */ + if (tpm_suspend_pcr) { + cmd.header.in = pcrextend_header; + cmd.params.pcrextend_in.pcr_idx = cpu_to_be32(tpm_suspend_pcr); + memcpy(cmd.params.pcrextend_in.hash, dummy_hash, + TPM_DIGEST_SIZE); + rc = tpm_transmit_cmd(chip, &cmd, EXTEND_PCR_RESULT_SIZE, + "extending dummy pcr before suspend"); + } + + /* now do the actual savestate */ + for (try = 0; try < TPM_RETRY; try++) { + cmd.header.in = savestate_header; + rc = tpm_transmit_cmd(chip, &cmd, SAVESTATE_RESULT_SIZE, NULL); + + /* + * If the TPM indicates that it is too busy to respond to + * this command then retry before giving up. It can take + * several seconds for this TPM to be ready. + * + * This can happen if the TPM has already been sent the + * SaveState command before the driver has loaded. TCG 1.2 + * specification states that any communication after SaveState + * may cause the TPM to invalidate previously saved state. + */ + if (rc != TPM_WARN_RETRY) + break; + msleep(TPM_TIMEOUT_RETRY); + } + + if (rc) + dev_err(chip->pdev, + "Error (%d) sending savestate before suspend\n", rc); + else if (try > 0) + dev_warn(chip->pdev, "TPM savestate took %dms\n", + try * TPM_TIMEOUT_RETRY); + + return rc; +} +EXPORT_SYMBOL_GPL(tpm_pm_suspend); + +/* + * Resume from a power safe. The BIOS already restored + * the TPM state. + */ +int tpm_pm_resume(struct device *dev) +{ + struct tpm_chip *chip = dev_get_drvdata(dev); + + if (chip == NULL) + return -ENODEV; + + return 0; +} +EXPORT_SYMBOL_GPL(tpm_pm_resume); + +#define TPM_GETRANDOM_RESULT_SIZE 18 +static struct tpm_input_header tpm_getrandom_header = { + .tag = TPM_TAG_RQU_COMMAND, + .length = cpu_to_be32(14), + .ordinal = TPM_ORD_GET_RANDOM +}; + +/** + * tpm_get_random() - Get random bytes from the tpm's RNG + * @chip_num: A specific chip number for the request or TPM_ANY_NUM + * @out: destination buffer for the random bytes + * @max: the max number of bytes to write to @out + * + * Returns < 0 on error and the number of bytes read on success + */ +int tpm_get_random(u32 chip_num, u8 *out, size_t max) +{ + struct tpm_chip *chip; + struct tpm_cmd_t tpm_cmd; + u32 recd, num_bytes = min_t(u32, max, TPM_MAX_RNG_DATA); + int err, total = 0, retries = 5; + u8 *dest = out; + + if (!out || !num_bytes || max > TPM_MAX_RNG_DATA) + return -EINVAL; + + chip = tpm_chip_find_get(chip_num); + if (chip == NULL) + return -ENODEV; + + if (chip->flags & TPM_CHIP_FLAG_TPM2) { + err = tpm2_get_random(chip, out, max); + tpm_chip_put(chip); + return err; + } + + do { + tpm_cmd.header.in = tpm_getrandom_header; + tpm_cmd.params.getrandom_in.num_bytes = cpu_to_be32(num_bytes); + + err = tpm_transmit_cmd(chip, &tpm_cmd, + TPM_GETRANDOM_RESULT_SIZE + num_bytes, + "attempting get random"); + if (err) + break; + + recd = be32_to_cpu(tpm_cmd.params.getrandom_out.rng_data_len); + memcpy(dest, tpm_cmd.params.getrandom_out.rng_data, recd); + + dest += recd; + total += recd; + num_bytes -= recd; + } while (retries-- && total < max); + + tpm_chip_put(chip); + return total ? total : -EIO; +} +EXPORT_SYMBOL_GPL(tpm_get_random); + +static int __init tpm_init(void) +{ + int rc; + + tpm_class = class_create(THIS_MODULE, "tpm"); + if (IS_ERR(tpm_class)) { + pr_err("couldn't create tpm class\n"); + return PTR_ERR(tpm_class); + } + + rc = alloc_chrdev_region(&tpm_devt, 0, TPM_NUM_DEVICES, "tpm"); + if (rc < 0) { + pr_err("tpm: failed to allocate char dev region\n"); + class_destroy(tpm_class); + return rc; + } + + return 0; +} + +static void __exit tpm_exit(void) +{ + class_destroy(tpm_class); + unregister_chrdev_region(tpm_devt, TPM_NUM_DEVICES); +} + +subsys_initcall(tpm_init); +module_exit(tpm_exit); + +MODULE_AUTHOR("Leendert van Doorn (leendert@watson.ibm.com)"); +MODULE_DESCRIPTION("TPM Driver"); +MODULE_VERSION("2.0"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/char/tpm/tpm-sysfs.c b/kernel/drivers/char/tpm/tpm-sysfs.c new file mode 100644 index 000000000..ee66fd467 --- /dev/null +++ b/kernel/drivers/char/tpm/tpm-sysfs.c @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2004 IBM Corporation + * Authors: + * Leendert van Doorn <leendert@watson.ibm.com> + * Dave Safford <safford@watson.ibm.com> + * Reiner Sailer <sailer@watson.ibm.com> + * Kylene Hall <kjhall@us.ibm.com> + * + * Copyright (C) 2013 Obsidian Research Corp + * Jason Gunthorpe <jgunthorpe@obsidianresearch.com> + * + * sysfs filesystem inspection interface to the TPM + * + * 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, version 2 of the + * License. + * + */ +#include <linux/device.h> +#include "tpm.h" + +#define READ_PUBEK_RESULT_SIZE 314 +#define TPM_ORD_READPUBEK cpu_to_be32(124) +static struct tpm_input_header tpm_readpubek_header = { + .tag = TPM_TAG_RQU_COMMAND, + .length = cpu_to_be32(30), + .ordinal = TPM_ORD_READPUBEK +}; +static ssize_t pubek_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + u8 *data; + struct tpm_cmd_t tpm_cmd; + ssize_t err; + int i, rc; + char *str = buf; + + struct tpm_chip *chip = dev_get_drvdata(dev); + + tpm_cmd.header.in = tpm_readpubek_header; + err = tpm_transmit_cmd(chip, &tpm_cmd, READ_PUBEK_RESULT_SIZE, + "attempting to read the PUBEK"); + if (err) + goto out; + + /* + ignore header 10 bytes + algorithm 32 bits (1 == RSA ) + encscheme 16 bits + sigscheme 16 bits + parameters (RSA 12->bytes: keybit, #primes, expbit) + keylenbytes 32 bits + 256 byte modulus + ignore checksum 20 bytes + */ + data = tpm_cmd.params.readpubek_out_buffer; + str += + sprintf(str, + "Algorithm: %02X %02X %02X %02X\n" + "Encscheme: %02X %02X\n" + "Sigscheme: %02X %02X\n" + "Parameters: %02X %02X %02X %02X " + "%02X %02X %02X %02X " + "%02X %02X %02X %02X\n" + "Modulus length: %d\n" + "Modulus:\n", + data[0], data[1], data[2], data[3], + data[4], data[5], + data[6], data[7], + data[12], data[13], data[14], data[15], + data[16], data[17], data[18], data[19], + data[20], data[21], data[22], data[23], + be32_to_cpu(*((__be32 *) (data + 24)))); + + for (i = 0; i < 256; i++) { + str += sprintf(str, "%02X ", data[i + 28]); + if ((i + 1) % 16 == 0) + str += sprintf(str, "\n"); + } +out: + rc = str - buf; + return rc; +} +static DEVICE_ATTR_RO(pubek); + +static ssize_t pcrs_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + cap_t cap; + u8 digest[TPM_DIGEST_SIZE]; + ssize_t rc; + int i, j, num_pcrs; + char *str = buf; + struct tpm_chip *chip = dev_get_drvdata(dev); + + rc = tpm_getcap(dev, TPM_CAP_PROP_PCR, &cap, + "attempting to determine the number of PCRS"); + if (rc) + return 0; + + num_pcrs = be32_to_cpu(cap.num_pcrs); + for (i = 0; i < num_pcrs; i++) { + rc = tpm_pcr_read_dev(chip, i, digest); + if (rc) + break; + str += sprintf(str, "PCR-%02d: ", i); + for (j = 0; j < TPM_DIGEST_SIZE; j++) + str += sprintf(str, "%02X ", digest[j]); + str += sprintf(str, "\n"); + } + return str - buf; +} +static DEVICE_ATTR_RO(pcrs); + +static ssize_t enabled_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + cap_t cap; + ssize_t rc; + + rc = tpm_getcap(dev, TPM_CAP_FLAG_PERM, &cap, + "attempting to determine the permanent enabled state"); + if (rc) + return 0; + + rc = sprintf(buf, "%d\n", !cap.perm_flags.disable); + return rc; +} +static DEVICE_ATTR_RO(enabled); + +static ssize_t active_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + cap_t cap; + ssize_t rc; + + rc = tpm_getcap(dev, TPM_CAP_FLAG_PERM, &cap, + "attempting to determine the permanent active state"); + if (rc) + return 0; + + rc = sprintf(buf, "%d\n", !cap.perm_flags.deactivated); + return rc; +} +static DEVICE_ATTR_RO(active); + +static ssize_t owned_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + cap_t cap; + ssize_t rc; + + rc = tpm_getcap(dev, TPM_CAP_PROP_OWNER, &cap, + "attempting to determine the owner state"); + if (rc) + return 0; + + rc = sprintf(buf, "%d\n", cap.owned); + return rc; +} +static DEVICE_ATTR_RO(owned); + +static ssize_t temp_deactivated_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + cap_t cap; + ssize_t rc; + + rc = tpm_getcap(dev, TPM_CAP_FLAG_VOL, &cap, + "attempting to determine the temporary state"); + if (rc) + return 0; + + rc = sprintf(buf, "%d\n", cap.stclear_flags.deactivated); + return rc; +} +static DEVICE_ATTR_RO(temp_deactivated); + +static ssize_t caps_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + cap_t cap; + ssize_t rc; + char *str = buf; + + rc = tpm_getcap(dev, TPM_CAP_PROP_MANUFACTURER, &cap, + "attempting to determine the manufacturer"); + if (rc) + return 0; + str += sprintf(str, "Manufacturer: 0x%x\n", + be32_to_cpu(cap.manufacturer_id)); + + /* Try to get a TPM version 1.2 TPM_CAP_VERSION_INFO */ + rc = tpm_getcap(dev, CAP_VERSION_1_2, &cap, + "attempting to determine the 1.2 version"); + if (!rc) { + str += sprintf(str, + "TCG version: %d.%d\nFirmware version: %d.%d\n", + cap.tpm_version_1_2.Major, + cap.tpm_version_1_2.Minor, + cap.tpm_version_1_2.revMajor, + cap.tpm_version_1_2.revMinor); + } else { + /* Otherwise just use TPM_STRUCT_VER */ + rc = tpm_getcap(dev, CAP_VERSION_1_1, &cap, + "attempting to determine the 1.1 version"); + if (rc) + return 0; + str += sprintf(str, + "TCG version: %d.%d\nFirmware version: %d.%d\n", + cap.tpm_version.Major, + cap.tpm_version.Minor, + cap.tpm_version.revMajor, + cap.tpm_version.revMinor); + } + + return str - buf; +} +static DEVICE_ATTR_RO(caps); + +static ssize_t cancel_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct tpm_chip *chip = dev_get_drvdata(dev); + if (chip == NULL) + return 0; + + chip->ops->cancel(chip); + return count; +} +static DEVICE_ATTR_WO(cancel); + +static ssize_t durations_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct tpm_chip *chip = dev_get_drvdata(dev); + + if (chip->vendor.duration[TPM_LONG] == 0) + return 0; + + return sprintf(buf, "%d %d %d [%s]\n", + jiffies_to_usecs(chip->vendor.duration[TPM_SHORT]), + jiffies_to_usecs(chip->vendor.duration[TPM_MEDIUM]), + jiffies_to_usecs(chip->vendor.duration[TPM_LONG]), + chip->vendor.duration_adjusted + ? "adjusted" : "original"); +} +static DEVICE_ATTR_RO(durations); + +static ssize_t timeouts_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct tpm_chip *chip = dev_get_drvdata(dev); + + return sprintf(buf, "%d %d %d %d [%s]\n", + jiffies_to_usecs(chip->vendor.timeout_a), + jiffies_to_usecs(chip->vendor.timeout_b), + jiffies_to_usecs(chip->vendor.timeout_c), + jiffies_to_usecs(chip->vendor.timeout_d), + chip->vendor.timeout_adjusted + ? "adjusted" : "original"); +} +static DEVICE_ATTR_RO(timeouts); + +static struct attribute *tpm_dev_attrs[] = { + &dev_attr_pubek.attr, + &dev_attr_pcrs.attr, + &dev_attr_enabled.attr, + &dev_attr_active.attr, + &dev_attr_owned.attr, + &dev_attr_temp_deactivated.attr, + &dev_attr_caps.attr, + &dev_attr_cancel.attr, + &dev_attr_durations.attr, + &dev_attr_timeouts.attr, + NULL, +}; + +static const struct attribute_group tpm_dev_group = { + .attrs = tpm_dev_attrs, +}; + +int tpm_sysfs_add_device(struct tpm_chip *chip) +{ + int err; + err = sysfs_create_group(&chip->pdev->kobj, + &tpm_dev_group); + + if (err) + dev_err(chip->pdev, + "failed to create sysfs attributes, %d\n", err); + return err; +} + +void tpm_sysfs_del_device(struct tpm_chip *chip) +{ + sysfs_remove_group(&chip->pdev->kobj, &tpm_dev_group); +} diff --git a/kernel/drivers/char/tpm/tpm.h b/kernel/drivers/char/tpm/tpm.h new file mode 100644 index 000000000..f8319a086 --- /dev/null +++ b/kernel/drivers/char/tpm/tpm.h @@ -0,0 +1,439 @@ +/* + * Copyright (C) 2004 IBM Corporation + * + * Authors: + * Leendert van Doorn <leendert@watson.ibm.com> + * Dave Safford <safford@watson.ibm.com> + * Reiner Sailer <sailer@watson.ibm.com> + * Kylene Hall <kjhall@us.ibm.com> + * + * Maintained by: <tpmdd-devel@lists.sourceforge.net> + * + * Device driver for TCG/TCPA TPM (trusted platform module). + * Specifications at www.trustedcomputinggroup.org + * + * 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, version 2 of the + * License. + * + */ +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/mutex.h> +#include <linux/sched.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/tpm.h> +#include <linux/acpi.h> +#include <linux/cdev.h> + +enum tpm_const { + TPM_MINOR = 224, /* officially assigned */ + TPM_BUFSIZE = 4096, + TPM_NUM_DEVICES = 256, + TPM_RETRY = 50, /* 5 seconds */ +}; + +enum tpm_timeout { + TPM_TIMEOUT = 5, /* msecs */ + TPM_TIMEOUT_RETRY = 100 /* msecs */ +}; + +/* TPM addresses */ +enum tpm_addr { + TPM_SUPERIO_ADDR = 0x2E, + TPM_ADDR = 0x4E, +}; + +/* Indexes the duration array */ +enum tpm_duration { + TPM_SHORT = 0, + TPM_MEDIUM = 1, + TPM_LONG = 2, + TPM_UNDEFINED, +}; + +#define TPM_WARN_RETRY 0x800 +#define TPM_WARN_DOING_SELFTEST 0x802 +#define TPM_ERR_DEACTIVATED 0x6 +#define TPM_ERR_DISABLED 0x7 +#define TPM_ERR_INVALID_POSTINIT 38 + +#define TPM_HEADER_SIZE 10 + +enum tpm2_const { + TPM2_PLATFORM_PCR = 24, + TPM2_PCR_SELECT_MIN = ((TPM2_PLATFORM_PCR + 7) / 8), + TPM2_TIMEOUT_A = 750, + TPM2_TIMEOUT_B = 2000, + TPM2_TIMEOUT_C = 200, + TPM2_TIMEOUT_D = 30, + TPM2_DURATION_SHORT = 20, + TPM2_DURATION_MEDIUM = 750, + TPM2_DURATION_LONG = 2000, +}; + +enum tpm2_structures { + TPM2_ST_NO_SESSIONS = 0x8001, + TPM2_ST_SESSIONS = 0x8002, +}; + +enum tpm2_return_codes { + TPM2_RC_INITIALIZE = 0x0100, + TPM2_RC_TESTING = 0x090A, + TPM2_RC_DISABLED = 0x0120, +}; + +enum tpm2_algorithms { + TPM2_ALG_SHA1 = 0x0004, +}; + +enum tpm2_command_codes { + TPM2_CC_FIRST = 0x011F, + TPM2_CC_SELF_TEST = 0x0143, + TPM2_CC_STARTUP = 0x0144, + TPM2_CC_SHUTDOWN = 0x0145, + TPM2_CC_GET_CAPABILITY = 0x017A, + TPM2_CC_GET_RANDOM = 0x017B, + TPM2_CC_PCR_READ = 0x017E, + TPM2_CC_PCR_EXTEND = 0x0182, + TPM2_CC_LAST = 0x018F, +}; + +enum tpm2_permanent_handles { + TPM2_RS_PW = 0x40000009, +}; + +enum tpm2_capabilities { + TPM2_CAP_TPM_PROPERTIES = 6, +}; + +enum tpm2_startup_types { + TPM2_SU_CLEAR = 0x0000, + TPM2_SU_STATE = 0x0001, +}; + +struct tpm_chip; + +struct tpm_vendor_specific { + void __iomem *iobase; /* ioremapped address */ + unsigned long base; /* TPM base address */ + + int irq; + int probed_irq; + + int region_size; + int have_region; + + struct list_head list; + int locality; + unsigned long timeout_a, timeout_b, timeout_c, timeout_d; /* jiffies */ + bool timeout_adjusted; + unsigned long duration[3]; /* jiffies */ + bool duration_adjusted; + void *priv; + + wait_queue_head_t read_queue; + wait_queue_head_t int_queue; + + u16 manufacturer_id; +}; + +#define TPM_VPRIV(c) ((c)->vendor.priv) + +#define TPM_VID_INTEL 0x8086 +#define TPM_VID_WINBOND 0x1050 +#define TPM_VID_STM 0x104A + +#define TPM_PPI_VERSION_LEN 3 + +enum tpm_chip_flags { + TPM_CHIP_FLAG_REGISTERED = BIT(0), + TPM_CHIP_FLAG_PPI = BIT(1), + TPM_CHIP_FLAG_TPM2 = BIT(2), +}; + +struct tpm_chip { + struct device *pdev; /* Device stuff */ + struct device dev; + struct cdev cdev; + + const struct tpm_class_ops *ops; + unsigned int flags; + + int dev_num; /* /dev/tpm# */ + char devname[7]; + unsigned long is_open; /* only one allowed */ + int time_expired; + + struct mutex tpm_mutex; /* tpm is processing */ + + struct tpm_vendor_specific vendor; + + struct dentry **bios_dir; + +#ifdef CONFIG_ACPI + acpi_handle acpi_dev_handle; + char ppi_version[TPM_PPI_VERSION_LEN + 1]; +#endif /* CONFIG_ACPI */ + + struct list_head list; +}; + +#define to_tpm_chip(n) container_of(n, struct tpm_chip, vendor) + +static inline void tpm_chip_put(struct tpm_chip *chip) +{ + module_put(chip->pdev->driver->owner); +} + +static inline int tpm_read_index(int base, int index) +{ + outb(index, base); + return inb(base+1) & 0xFF; +} + +static inline void tpm_write_index(int base, int index, int value) +{ + outb(index, base); + outb(value & 0xFF, base+1); +} +struct tpm_input_header { + __be16 tag; + __be32 length; + __be32 ordinal; +} __packed; + +struct tpm_output_header { + __be16 tag; + __be32 length; + __be32 return_code; +} __packed; + +#define TPM_TAG_RQU_COMMAND cpu_to_be16(193) + +struct stclear_flags_t { + __be16 tag; + u8 deactivated; + u8 disableForceClear; + u8 physicalPresence; + u8 physicalPresenceLock; + u8 bGlobalLock; +} __packed; + +struct tpm_version_t { + u8 Major; + u8 Minor; + u8 revMajor; + u8 revMinor; +} __packed; + +struct tpm_version_1_2_t { + __be16 tag; + u8 Major; + u8 Minor; + u8 revMajor; + u8 revMinor; +} __packed; + +struct timeout_t { + __be32 a; + __be32 b; + __be32 c; + __be32 d; +} __packed; + +struct duration_t { + __be32 tpm_short; + __be32 tpm_medium; + __be32 tpm_long; +} __packed; + +struct permanent_flags_t { + __be16 tag; + u8 disable; + u8 ownership; + u8 deactivated; + u8 readPubek; + u8 disableOwnerClear; + u8 allowMaintenance; + u8 physicalPresenceLifetimeLock; + u8 physicalPresenceHWEnable; + u8 physicalPresenceCMDEnable; + u8 CEKPUsed; + u8 TPMpost; + u8 TPMpostLock; + u8 FIPS; + u8 operator; + u8 enableRevokeEK; + u8 nvLocked; + u8 readSRKPub; + u8 tpmEstablished; + u8 maintenanceDone; + u8 disableFullDALogicInfo; +} __packed; + +typedef union { + struct permanent_flags_t perm_flags; + struct stclear_flags_t stclear_flags; + bool owned; + __be32 num_pcrs; + struct tpm_version_t tpm_version; + struct tpm_version_1_2_t tpm_version_1_2; + __be32 manufacturer_id; + struct timeout_t timeout; + struct duration_t duration; +} cap_t; + +enum tpm_capabilities { + TPM_CAP_FLAG = cpu_to_be32(4), + TPM_CAP_PROP = cpu_to_be32(5), + CAP_VERSION_1_1 = cpu_to_be32(0x06), + CAP_VERSION_1_2 = cpu_to_be32(0x1A) +}; + +enum tpm_sub_capabilities { + TPM_CAP_PROP_PCR = cpu_to_be32(0x101), + TPM_CAP_PROP_MANUFACTURER = cpu_to_be32(0x103), + TPM_CAP_FLAG_PERM = cpu_to_be32(0x108), + TPM_CAP_FLAG_VOL = cpu_to_be32(0x109), + TPM_CAP_PROP_OWNER = cpu_to_be32(0x111), + TPM_CAP_PROP_TIS_TIMEOUT = cpu_to_be32(0x115), + TPM_CAP_PROP_TIS_DURATION = cpu_to_be32(0x120), + +}; + +struct tpm_getcap_params_in { + __be32 cap; + __be32 subcap_size; + __be32 subcap; +} __packed; + +struct tpm_getcap_params_out { + __be32 cap_size; + cap_t cap; +} __packed; + +struct tpm_readpubek_params_out { + u8 algorithm[4]; + u8 encscheme[2]; + u8 sigscheme[2]; + __be32 paramsize; + u8 parameters[12]; /*assuming RSA*/ + __be32 keysize; + u8 modulus[256]; + u8 checksum[20]; +} __packed; + +typedef union { + struct tpm_input_header in; + struct tpm_output_header out; +} tpm_cmd_header; + +struct tpm_pcrread_out { + u8 pcr_result[TPM_DIGEST_SIZE]; +} __packed; + +struct tpm_pcrread_in { + __be32 pcr_idx; +} __packed; + +struct tpm_pcrextend_in { + __be32 pcr_idx; + u8 hash[TPM_DIGEST_SIZE]; +} __packed; + +/* 128 bytes is an arbitrary cap. This could be as large as TPM_BUFSIZE - 18 + * bytes, but 128 is still a relatively large number of random bytes and + * anything much bigger causes users of struct tpm_cmd_t to start getting + * compiler warnings about stack frame size. */ +#define TPM_MAX_RNG_DATA 128 + +struct tpm_getrandom_out { + __be32 rng_data_len; + u8 rng_data[TPM_MAX_RNG_DATA]; +} __packed; + +struct tpm_getrandom_in { + __be32 num_bytes; +} __packed; + +struct tpm_startup_in { + __be16 startup_type; +} __packed; + +typedef union { + struct tpm_getcap_params_out getcap_out; + struct tpm_readpubek_params_out readpubek_out; + u8 readpubek_out_buffer[sizeof(struct tpm_readpubek_params_out)]; + struct tpm_getcap_params_in getcap_in; + struct tpm_pcrread_in pcrread_in; + struct tpm_pcrread_out pcrread_out; + struct tpm_pcrextend_in pcrextend_in; + struct tpm_getrandom_in getrandom_in; + struct tpm_getrandom_out getrandom_out; + struct tpm_startup_in startup_in; +} tpm_cmd_params; + +struct tpm_cmd_t { + tpm_cmd_header header; + tpm_cmd_params params; +} __packed; + +extern struct class *tpm_class; +extern dev_t tpm_devt; +extern const struct file_operations tpm_fops; + +ssize_t tpm_getcap(struct device *, __be32, cap_t *, const char *); +ssize_t tpm_transmit(struct tpm_chip *chip, const char *buf, + size_t bufsiz); +ssize_t tpm_transmit_cmd(struct tpm_chip *chip, void *cmd, int len, + const char *desc); +extern int tpm_get_timeouts(struct tpm_chip *); +extern void tpm_gen_interrupt(struct tpm_chip *); +extern int tpm_do_selftest(struct tpm_chip *); +extern unsigned long tpm_calc_ordinal_duration(struct tpm_chip *, u32); +extern int tpm_pm_suspend(struct device *); +extern int tpm_pm_resume(struct device *); +extern int wait_for_tpm_stat(struct tpm_chip *, u8, unsigned long, + wait_queue_head_t *, bool); + +struct tpm_chip *tpm_chip_find_get(int chip_num); +extern struct tpm_chip *tpmm_chip_alloc(struct device *dev, + const struct tpm_class_ops *ops); +extern int tpm_chip_register(struct tpm_chip *chip); +extern void tpm_chip_unregister(struct tpm_chip *chip); + +int tpm_sysfs_add_device(struct tpm_chip *chip); +void tpm_sysfs_del_device(struct tpm_chip *chip); + +int tpm_pcr_read_dev(struct tpm_chip *chip, int pcr_idx, u8 *res_buf); + +#ifdef CONFIG_ACPI +extern int tpm_add_ppi(struct tpm_chip *chip); +extern void tpm_remove_ppi(struct tpm_chip *chip); +#else +static inline int tpm_add_ppi(struct tpm_chip *chip) +{ + return 0; +} + +static inline void tpm_remove_ppi(struct tpm_chip *chip) +{ +} +#endif + +int tpm2_pcr_read(struct tpm_chip *chip, int pcr_idx, u8 *res_buf); +int tpm2_pcr_extend(struct tpm_chip *chip, int pcr_idx, const u8 *hash); +int tpm2_get_random(struct tpm_chip *chip, u8 *out, size_t max); +ssize_t tpm2_get_tpm_pt(struct tpm_chip *chip, u32 property_id, + u32 *value, const char *desc); + +extern int tpm2_startup(struct tpm_chip *chip, u16 startup_type); +extern void tpm2_shutdown(struct tpm_chip *chip, u16 shutdown_type); +extern unsigned long tpm2_calc_ordinal_duration(struct tpm_chip *, u32); +extern int tpm2_do_selftest(struct tpm_chip *chip); +extern int tpm2_gen_interrupt(struct tpm_chip *chip); +extern int tpm2_probe(struct tpm_chip *chip); diff --git a/kernel/drivers/char/tpm/tpm2-cmd.c b/kernel/drivers/char/tpm/tpm2-cmd.c new file mode 100644 index 000000000..011909a9b --- /dev/null +++ b/kernel/drivers/char/tpm/tpm2-cmd.c @@ -0,0 +1,646 @@ +/* + * Copyright (C) 2014 Intel Corporation + * + * Authors: + * Jarkko Sakkinen <jarkko.sakkinen@linux.intel.com> + * + * Maintained by: <tpmdd-devel@lists.sourceforge.net> + * + * This file contains TPM2 protocol implementations of the commands + * used by the kernel internally. + * + * 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; version 2 + * of the License. + */ + +#include "tpm.h" + +struct tpm2_startup_in { + __be16 startup_type; +} __packed; + +struct tpm2_self_test_in { + u8 full_test; +} __packed; + +struct tpm2_pcr_read_in { + __be32 pcr_selects_cnt; + __be16 hash_alg; + u8 pcr_select_size; + u8 pcr_select[TPM2_PCR_SELECT_MIN]; +} __packed; + +struct tpm2_pcr_read_out { + __be32 update_cnt; + __be32 pcr_selects_cnt; + __be16 hash_alg; + u8 pcr_select_size; + u8 pcr_select[TPM2_PCR_SELECT_MIN]; + __be32 digests_cnt; + __be16 digest_size; + u8 digest[TPM_DIGEST_SIZE]; +} __packed; + +struct tpm2_null_auth_area { + __be32 handle; + __be16 nonce_size; + u8 attributes; + __be16 auth_size; +} __packed; + +struct tpm2_pcr_extend_in { + __be32 pcr_idx; + __be32 auth_area_size; + struct tpm2_null_auth_area auth_area; + __be32 digest_cnt; + __be16 hash_alg; + u8 digest[TPM_DIGEST_SIZE]; +} __packed; + +struct tpm2_get_tpm_pt_in { + __be32 cap_id; + __be32 property_id; + __be32 property_cnt; +} __packed; + +struct tpm2_get_tpm_pt_out { + u8 more_data; + __be32 subcap_id; + __be32 property_cnt; + __be32 property_id; + __be32 value; +} __packed; + +struct tpm2_get_random_in { + __be16 size; +} __packed; + +struct tpm2_get_random_out { + __be16 size; + u8 buffer[TPM_MAX_RNG_DATA]; +} __packed; + +union tpm2_cmd_params { + struct tpm2_startup_in startup_in; + struct tpm2_self_test_in selftest_in; + struct tpm2_pcr_read_in pcrread_in; + struct tpm2_pcr_read_out pcrread_out; + struct tpm2_pcr_extend_in pcrextend_in; + struct tpm2_get_tpm_pt_in get_tpm_pt_in; + struct tpm2_get_tpm_pt_out get_tpm_pt_out; + struct tpm2_get_random_in getrandom_in; + struct tpm2_get_random_out getrandom_out; +}; + +struct tpm2_cmd { + tpm_cmd_header header; + union tpm2_cmd_params params; +} __packed; + +/* + * Array with one entry per ordinal defining the maximum amount + * of time the chip could take to return the result. The values + * of the SHORT, MEDIUM, and LONG durations are taken from the + * PC Client Profile (PTP) specification. + */ +static const u8 tpm2_ordinal_duration[TPM2_CC_LAST - TPM2_CC_FIRST + 1] = { + TPM_UNDEFINED, /* 11F */ + TPM_UNDEFINED, /* 120 */ + TPM_LONG, /* 121 */ + TPM_UNDEFINED, /* 122 */ + TPM_UNDEFINED, /* 123 */ + TPM_UNDEFINED, /* 124 */ + TPM_UNDEFINED, /* 125 */ + TPM_UNDEFINED, /* 126 */ + TPM_UNDEFINED, /* 127 */ + TPM_UNDEFINED, /* 128 */ + TPM_LONG, /* 129 */ + TPM_UNDEFINED, /* 12a */ + TPM_UNDEFINED, /* 12b */ + TPM_UNDEFINED, /* 12c */ + TPM_UNDEFINED, /* 12d */ + TPM_UNDEFINED, /* 12e */ + TPM_UNDEFINED, /* 12f */ + TPM_UNDEFINED, /* 130 */ + TPM_UNDEFINED, /* 131 */ + TPM_UNDEFINED, /* 132 */ + TPM_UNDEFINED, /* 133 */ + TPM_UNDEFINED, /* 134 */ + TPM_UNDEFINED, /* 135 */ + TPM_UNDEFINED, /* 136 */ + TPM_UNDEFINED, /* 137 */ + TPM_UNDEFINED, /* 138 */ + TPM_UNDEFINED, /* 139 */ + TPM_UNDEFINED, /* 13a */ + TPM_UNDEFINED, /* 13b */ + TPM_UNDEFINED, /* 13c */ + TPM_UNDEFINED, /* 13d */ + TPM_MEDIUM, /* 13e */ + TPM_UNDEFINED, /* 13f */ + TPM_UNDEFINED, /* 140 */ + TPM_UNDEFINED, /* 141 */ + TPM_UNDEFINED, /* 142 */ + TPM_LONG, /* 143 */ + TPM_MEDIUM, /* 144 */ + TPM_UNDEFINED, /* 145 */ + TPM_UNDEFINED, /* 146 */ + TPM_UNDEFINED, /* 147 */ + TPM_UNDEFINED, /* 148 */ + TPM_UNDEFINED, /* 149 */ + TPM_UNDEFINED, /* 14a */ + TPM_UNDEFINED, /* 14b */ + TPM_UNDEFINED, /* 14c */ + TPM_UNDEFINED, /* 14d */ + TPM_LONG, /* 14e */ + TPM_UNDEFINED, /* 14f */ + TPM_UNDEFINED, /* 150 */ + TPM_UNDEFINED, /* 151 */ + TPM_UNDEFINED, /* 152 */ + TPM_UNDEFINED, /* 153 */ + TPM_UNDEFINED, /* 154 */ + TPM_UNDEFINED, /* 155 */ + TPM_UNDEFINED, /* 156 */ + TPM_UNDEFINED, /* 157 */ + TPM_UNDEFINED, /* 158 */ + TPM_UNDEFINED, /* 159 */ + TPM_UNDEFINED, /* 15a */ + TPM_UNDEFINED, /* 15b */ + TPM_MEDIUM, /* 15c */ + TPM_UNDEFINED, /* 15d */ + TPM_UNDEFINED, /* 15e */ + TPM_UNDEFINED, /* 15f */ + TPM_UNDEFINED, /* 160 */ + TPM_UNDEFINED, /* 161 */ + TPM_UNDEFINED, /* 162 */ + TPM_UNDEFINED, /* 163 */ + TPM_UNDEFINED, /* 164 */ + TPM_UNDEFINED, /* 165 */ + TPM_UNDEFINED, /* 166 */ + TPM_UNDEFINED, /* 167 */ + TPM_UNDEFINED, /* 168 */ + TPM_UNDEFINED, /* 169 */ + TPM_UNDEFINED, /* 16a */ + TPM_UNDEFINED, /* 16b */ + TPM_UNDEFINED, /* 16c */ + TPM_UNDEFINED, /* 16d */ + TPM_UNDEFINED, /* 16e */ + TPM_UNDEFINED, /* 16f */ + TPM_UNDEFINED, /* 170 */ + TPM_UNDEFINED, /* 171 */ + TPM_UNDEFINED, /* 172 */ + TPM_UNDEFINED, /* 173 */ + TPM_UNDEFINED, /* 174 */ + TPM_UNDEFINED, /* 175 */ + TPM_UNDEFINED, /* 176 */ + TPM_LONG, /* 177 */ + TPM_UNDEFINED, /* 178 */ + TPM_UNDEFINED, /* 179 */ + TPM_MEDIUM, /* 17a */ + TPM_LONG, /* 17b */ + TPM_UNDEFINED, /* 17c */ + TPM_UNDEFINED, /* 17d */ + TPM_UNDEFINED, /* 17e */ + TPM_UNDEFINED, /* 17f */ + TPM_UNDEFINED, /* 180 */ + TPM_UNDEFINED, /* 181 */ + TPM_MEDIUM, /* 182 */ + TPM_UNDEFINED, /* 183 */ + TPM_UNDEFINED, /* 184 */ + TPM_MEDIUM, /* 185 */ + TPM_MEDIUM, /* 186 */ + TPM_UNDEFINED, /* 187 */ + TPM_UNDEFINED, /* 188 */ + TPM_UNDEFINED, /* 189 */ + TPM_UNDEFINED, /* 18a */ + TPM_UNDEFINED, /* 18b */ + TPM_UNDEFINED, /* 18c */ + TPM_UNDEFINED, /* 18d */ + TPM_UNDEFINED, /* 18e */ + TPM_UNDEFINED /* 18f */ +}; + +#define TPM2_PCR_READ_IN_SIZE \ + (sizeof(struct tpm_input_header) + \ + sizeof(struct tpm2_pcr_read_in)) + +static const struct tpm_input_header tpm2_pcrread_header = { + .tag = cpu_to_be16(TPM2_ST_NO_SESSIONS), + .length = cpu_to_be32(TPM2_PCR_READ_IN_SIZE), + .ordinal = cpu_to_be32(TPM2_CC_PCR_READ) +}; + +/** + * tpm2_pcr_read() - read a PCR value + * @chip: TPM chip to use. + * @pcr_idx: index of the PCR to read. + * @ref_buf: buffer to store the resulting hash, + * + * 0 is returned when the operation is successful. If a negative number is + * returned it remarks a POSIX error code. If a positive number is returned + * it remarks a TPM error. + */ +int tpm2_pcr_read(struct tpm_chip *chip, int pcr_idx, u8 *res_buf) +{ + int rc; + struct tpm2_cmd cmd; + u8 *buf; + + if (pcr_idx >= TPM2_PLATFORM_PCR) + return -EINVAL; + + cmd.header.in = tpm2_pcrread_header; + cmd.params.pcrread_in.pcr_selects_cnt = cpu_to_be32(1); + cmd.params.pcrread_in.hash_alg = cpu_to_be16(TPM2_ALG_SHA1); + cmd.params.pcrread_in.pcr_select_size = TPM2_PCR_SELECT_MIN; + + memset(cmd.params.pcrread_in.pcr_select, 0, + sizeof(cmd.params.pcrread_in.pcr_select)); + cmd.params.pcrread_in.pcr_select[pcr_idx >> 3] = 1 << (pcr_idx & 0x7); + + rc = tpm_transmit_cmd(chip, &cmd, sizeof(cmd), + "attempting to read a pcr value"); + if (rc == 0) { + buf = cmd.params.pcrread_out.digest; + memcpy(res_buf, buf, TPM_DIGEST_SIZE); + } + + return rc; +} + +#define TPM2_GET_PCREXTEND_IN_SIZE \ + (sizeof(struct tpm_input_header) + \ + sizeof(struct tpm2_pcr_extend_in)) + +static const struct tpm_input_header tpm2_pcrextend_header = { + .tag = cpu_to_be16(TPM2_ST_SESSIONS), + .length = cpu_to_be32(TPM2_GET_PCREXTEND_IN_SIZE), + .ordinal = cpu_to_be32(TPM2_CC_PCR_EXTEND) +}; + +/** + * tpm2_pcr_extend() - extend a PCR value + * @chip: TPM chip to use. + * @pcr_idx: index of the PCR. + * @hash: hash value to use for the extend operation. + * + * 0 is returned when the operation is successful. If a negative number is + * returned it remarks a POSIX error code. If a positive number is returned + * it remarks a TPM error. + */ +int tpm2_pcr_extend(struct tpm_chip *chip, int pcr_idx, const u8 *hash) +{ + struct tpm2_cmd cmd; + int rc; + + cmd.header.in = tpm2_pcrextend_header; + cmd.params.pcrextend_in.pcr_idx = cpu_to_be32(pcr_idx); + cmd.params.pcrextend_in.auth_area_size = + cpu_to_be32(sizeof(struct tpm2_null_auth_area)); + cmd.params.pcrextend_in.auth_area.handle = + cpu_to_be32(TPM2_RS_PW); + cmd.params.pcrextend_in.auth_area.nonce_size = 0; + cmd.params.pcrextend_in.auth_area.attributes = 0; + cmd.params.pcrextend_in.auth_area.auth_size = 0; + cmd.params.pcrextend_in.digest_cnt = cpu_to_be32(1); + cmd.params.pcrextend_in.hash_alg = cpu_to_be16(TPM2_ALG_SHA1); + memcpy(cmd.params.pcrextend_in.digest, hash, TPM_DIGEST_SIZE); + + rc = tpm_transmit_cmd(chip, &cmd, sizeof(cmd), + "attempting extend a PCR value"); + + return rc; +} + +#define TPM2_GETRANDOM_IN_SIZE \ + (sizeof(struct tpm_input_header) + \ + sizeof(struct tpm2_get_random_in)) + +static const struct tpm_input_header tpm2_getrandom_header = { + .tag = cpu_to_be16(TPM2_ST_NO_SESSIONS), + .length = cpu_to_be32(TPM2_GETRANDOM_IN_SIZE), + .ordinal = cpu_to_be32(TPM2_CC_GET_RANDOM) +}; + +/** + * tpm2_get_random() - get random bytes from the TPM RNG + * @chip: TPM chip to use + * @out: destination buffer for the random bytes + * @max: the max number of bytes to write to @out + * + * 0 is returned when the operation is successful. If a negative number is + * returned it remarks a POSIX error code. If a positive number is returned + * it remarks a TPM error. + */ +int tpm2_get_random(struct tpm_chip *chip, u8 *out, size_t max) +{ + struct tpm2_cmd cmd; + u32 recd; + u32 num_bytes; + int err; + int total = 0; + int retries = 5; + u8 *dest = out; + + num_bytes = min_t(u32, max, sizeof(cmd.params.getrandom_out.buffer)); + + if (!out || !num_bytes || + max > sizeof(cmd.params.getrandom_out.buffer)) + return -EINVAL; + + do { + cmd.header.in = tpm2_getrandom_header; + cmd.params.getrandom_in.size = cpu_to_be16(num_bytes); + + err = tpm_transmit_cmd(chip, &cmd, sizeof(cmd), + "attempting get random"); + if (err) + break; + + recd = min_t(u32, be16_to_cpu(cmd.params.getrandom_out.size), + num_bytes); + memcpy(dest, cmd.params.getrandom_out.buffer, recd); + + dest += recd; + total += recd; + num_bytes -= recd; + } while (retries-- && total < max); + + return total ? total : -EIO; +} + +#define TPM2_GET_TPM_PT_IN_SIZE \ + (sizeof(struct tpm_input_header) + \ + sizeof(struct tpm2_get_tpm_pt_in)) + +static const struct tpm_input_header tpm2_get_tpm_pt_header = { + .tag = cpu_to_be16(TPM2_ST_NO_SESSIONS), + .length = cpu_to_be32(TPM2_GET_TPM_PT_IN_SIZE), + .ordinal = cpu_to_be32(TPM2_CC_GET_CAPABILITY) +}; + +/** + * tpm2_get_tpm_pt() - get value of a TPM_CAP_TPM_PROPERTIES type property + * @chip: TPM chip to use. + * @property_id: property ID. + * @value: output variable. + * @desc: passed to tpm_transmit_cmd() + * + * 0 is returned when the operation is successful. If a negative number is + * returned it remarks a POSIX error code. If a positive number is returned + * it remarks a TPM error. + */ +ssize_t tpm2_get_tpm_pt(struct tpm_chip *chip, u32 property_id, u32 *value, + const char *desc) +{ + struct tpm2_cmd cmd; + int rc; + + cmd.header.in = tpm2_get_tpm_pt_header; + cmd.params.get_tpm_pt_in.cap_id = cpu_to_be32(TPM2_CAP_TPM_PROPERTIES); + cmd.params.get_tpm_pt_in.property_id = cpu_to_be32(property_id); + cmd.params.get_tpm_pt_in.property_cnt = cpu_to_be32(1); + + rc = tpm_transmit_cmd(chip, &cmd, sizeof(cmd), desc); + if (!rc) + *value = cmd.params.get_tpm_pt_out.value; + + return rc; +} + +#define TPM2_STARTUP_IN_SIZE \ + (sizeof(struct tpm_input_header) + \ + sizeof(struct tpm2_startup_in)) + +static const struct tpm_input_header tpm2_startup_header = { + .tag = cpu_to_be16(TPM2_ST_NO_SESSIONS), + .length = cpu_to_be32(TPM2_STARTUP_IN_SIZE), + .ordinal = cpu_to_be32(TPM2_CC_STARTUP) +}; + +/** + * tpm2_startup() - send startup command to the TPM chip + * @chip: TPM chip to use. + * @startup_type startup type. The value is either + * TPM_SU_CLEAR or TPM_SU_STATE. + * + * 0 is returned when the operation is successful. If a negative number is + * returned it remarks a POSIX error code. If a positive number is returned + * it remarks a TPM error. + */ +int tpm2_startup(struct tpm_chip *chip, u16 startup_type) +{ + struct tpm2_cmd cmd; + + cmd.header.in = tpm2_startup_header; + + cmd.params.startup_in.startup_type = cpu_to_be16(startup_type); + return tpm_transmit_cmd(chip, &cmd, sizeof(cmd), + "attempting to start the TPM"); +} +EXPORT_SYMBOL_GPL(tpm2_startup); + +#define TPM2_SHUTDOWN_IN_SIZE \ + (sizeof(struct tpm_input_header) + \ + sizeof(struct tpm2_startup_in)) + +static const struct tpm_input_header tpm2_shutdown_header = { + .tag = cpu_to_be16(TPM2_ST_NO_SESSIONS), + .length = cpu_to_be32(TPM2_SHUTDOWN_IN_SIZE), + .ordinal = cpu_to_be32(TPM2_CC_SHUTDOWN) +}; + +/** + * tpm2_shutdown() - send shutdown command to the TPM chip + * @chip: TPM chip to use. + * @shutdown_type shutdown type. The value is either + * TPM_SU_CLEAR or TPM_SU_STATE. + */ +void tpm2_shutdown(struct tpm_chip *chip, u16 shutdown_type) +{ + struct tpm2_cmd cmd; + int rc; + + cmd.header.in = tpm2_shutdown_header; + cmd.params.startup_in.startup_type = cpu_to_be16(shutdown_type); + + rc = tpm_transmit_cmd(chip, &cmd, sizeof(cmd), "stopping the TPM"); + + /* In places where shutdown command is sent there's no much we can do + * except print the error code on a system failure. + */ + if (rc < 0) + dev_warn(chip->pdev, "transmit returned %d while stopping the TPM", + rc); +} +EXPORT_SYMBOL_GPL(tpm2_shutdown); + +/* + * tpm2_calc_ordinal_duration() - maximum duration for a command + * @chip: TPM chip to use. + * @ordinal: command code number. + * + * 0 is returned when the operation is successful. If a negative number is + * returned it remarks a POSIX error code. If a positive number is returned + * it remarks a TPM error. + */ +unsigned long tpm2_calc_ordinal_duration(struct tpm_chip *chip, u32 ordinal) +{ + int index = TPM_UNDEFINED; + int duration = 0; + + if (ordinal >= TPM2_CC_FIRST && ordinal <= TPM2_CC_LAST) + index = tpm2_ordinal_duration[ordinal - TPM2_CC_FIRST]; + + if (index != TPM_UNDEFINED) + duration = chip->vendor.duration[index]; + + if (duration <= 0) + duration = 2 * 60 * HZ; + + return duration; +} +EXPORT_SYMBOL_GPL(tpm2_calc_ordinal_duration); + +#define TPM2_SELF_TEST_IN_SIZE \ + (sizeof(struct tpm_input_header) + \ + sizeof(struct tpm2_self_test_in)) + +static const struct tpm_input_header tpm2_selftest_header = { + .tag = cpu_to_be16(TPM2_ST_NO_SESSIONS), + .length = cpu_to_be32(TPM2_SELF_TEST_IN_SIZE), + .ordinal = cpu_to_be32(TPM2_CC_SELF_TEST) +}; + +/** + * tpm2_continue_selftest() - start a self test + * @chip: TPM chip to use + * @full: test all commands instead of testing only those that were not + * previously tested. + * + * 0 is returned when the operation is successful. If a negative number is + * returned it remarks a POSIX error code. If a positive number is returned + * it remarks a TPM error. + */ +static int tpm2_start_selftest(struct tpm_chip *chip, bool full) +{ + int rc; + struct tpm2_cmd cmd; + + cmd.header.in = tpm2_selftest_header; + cmd.params.selftest_in.full_test = full; + + rc = tpm_transmit_cmd(chip, &cmd, TPM2_SELF_TEST_IN_SIZE, + "continue selftest"); + + /* At least some prototype chips seem to give RC_TESTING error + * immediately. This is a workaround for that. + */ + if (rc == TPM2_RC_TESTING) { + dev_warn(chip->pdev, "Got RC_TESTING, ignoring\n"); + rc = 0; + } + + return rc; +} + +/** + * tpm2_do_selftest() - run a full self test + * @chip: TPM chip to use + * + * During the self test TPM2 commands return with the error code RC_TESTING. + * Waiting is done by issuing PCR read until it executes successfully. + * + * 0 is returned when the operation is successful. If a negative number is + * returned it remarks a POSIX error code. If a positive number is returned + * it remarks a TPM error. + */ +int tpm2_do_selftest(struct tpm_chip *chip) +{ + int rc; + unsigned int loops; + unsigned int delay_msec = 100; + unsigned long duration; + struct tpm2_cmd cmd; + int i; + + duration = tpm2_calc_ordinal_duration(chip, TPM2_CC_SELF_TEST); + + loops = jiffies_to_msecs(duration) / delay_msec; + + rc = tpm2_start_selftest(chip, true); + if (rc) + return rc; + + for (i = 0; i < loops; i++) { + /* Attempt to read a PCR value */ + cmd.header.in = tpm2_pcrread_header; + cmd.params.pcrread_in.pcr_selects_cnt = cpu_to_be32(1); + cmd.params.pcrread_in.hash_alg = cpu_to_be16(TPM2_ALG_SHA1); + cmd.params.pcrread_in.pcr_select_size = TPM2_PCR_SELECT_MIN; + cmd.params.pcrread_in.pcr_select[0] = 0x01; + cmd.params.pcrread_in.pcr_select[1] = 0x00; + cmd.params.pcrread_in.pcr_select[2] = 0x00; + + rc = tpm_transmit_cmd(chip, (u8 *) &cmd, sizeof(cmd), NULL); + if (rc < 0) + break; + + rc = be32_to_cpu(cmd.header.out.return_code); + if (rc != TPM2_RC_TESTING) + break; + + msleep(delay_msec); + } + + return rc; +} +EXPORT_SYMBOL_GPL(tpm2_do_selftest); + +/** + * tpm2_gen_interrupt() - generate an interrupt + * @chip: TPM chip to use + * + * 0 is returned when the operation is successful. If a negative number is + * returned it remarks a POSIX error code. If a positive number is returned + * it remarks a TPM error. + */ +int tpm2_gen_interrupt(struct tpm_chip *chip) +{ + u32 dummy; + + return tpm2_get_tpm_pt(chip, 0x100, &dummy, + "attempting to generate an interrupt"); +} +EXPORT_SYMBOL_GPL(tpm2_gen_interrupt); + +/** + * tpm2_probe() - probe TPM 2.0 + * @chip: TPM chip to use + * + * Send idempotent TPM 2.0 command and see whether TPM 2.0 chip replied based on + * the reply tag. + */ +int tpm2_probe(struct tpm_chip *chip) +{ + struct tpm2_cmd cmd; + int rc; + + cmd.header.in = tpm2_get_tpm_pt_header; + cmd.params.get_tpm_pt_in.cap_id = cpu_to_be32(TPM2_CAP_TPM_PROPERTIES); + cmd.params.get_tpm_pt_in.property_id = cpu_to_be32(0x100); + cmd.params.get_tpm_pt_in.property_cnt = cpu_to_be32(1); + + rc = tpm_transmit(chip, (const char *) &cmd, sizeof(cmd)); + if (rc < 0) + return rc; + else if (rc < TPM_HEADER_SIZE) + return -EFAULT; + + if (be16_to_cpu(cmd.header.out.tag) == TPM2_ST_NO_SESSIONS) + chip->flags |= TPM_CHIP_FLAG_TPM2; + + return 0; +} +EXPORT_SYMBOL_GPL(tpm2_probe); diff --git a/kernel/drivers/char/tpm/tpm_acpi.c b/kernel/drivers/char/tpm/tpm_acpi.c new file mode 100644 index 000000000..565a9478c --- /dev/null +++ b/kernel/drivers/char/tpm/tpm_acpi.c @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2005 IBM Corporation + * + * Authors: + * Seiji Munetoh <munetoh@jp.ibm.com> + * Stefan Berger <stefanb@us.ibm.com> + * Reiner Sailer <sailer@watson.ibm.com> + * Kylene Hall <kjhall@us.ibm.com> + * + * Maintained by: <tpmdd-devel@lists.sourceforge.net> + * + * Access to the eventlog extended by the TCG BIOS of PC platform + * + * 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. + * + */ + +#include <linux/seq_file.h> +#include <linux/fs.h> +#include <linux/security.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/acpi.h> + +#include "tpm.h" +#include "tpm_eventlog.h" + +struct acpi_tcpa { + struct acpi_table_header hdr; + u16 platform_class; + union { + struct client_hdr { + u32 log_max_len __packed; + u64 log_start_addr __packed; + } client; + struct server_hdr { + u16 reserved; + u64 log_max_len __packed; + u64 log_start_addr __packed; + } server; + }; +}; + +/* read binary bios log */ +int read_log(struct tpm_bios_log *log) +{ + struct acpi_tcpa *buff; + acpi_status status; + void __iomem *virt; + u64 len, start; + + if (log->bios_event_log != NULL) { + printk(KERN_ERR + "%s: ERROR - Eventlog already initialized\n", + __func__); + return -EFAULT; + } + + /* Find TCPA entry in RSDT (ACPI_LOGICAL_ADDRESSING) */ + status = acpi_get_table(ACPI_SIG_TCPA, 1, + (struct acpi_table_header **)&buff); + + if (ACPI_FAILURE(status)) { + printk(KERN_ERR "%s: ERROR - Could not get TCPA table\n", + __func__); + return -EIO; + } + + switch(buff->platform_class) { + case BIOS_SERVER: + len = buff->server.log_max_len; + start = buff->server.log_start_addr; + break; + case BIOS_CLIENT: + default: + len = buff->client.log_max_len; + start = buff->client.log_start_addr; + break; + } + if (!len) { + printk(KERN_ERR "%s: ERROR - TCPA log area empty\n", __func__); + return -EIO; + } + + /* malloc EventLog space */ + log->bios_event_log = kmalloc(len, GFP_KERNEL); + if (!log->bios_event_log) { + printk("%s: ERROR - Not enough Memory for BIOS measurements\n", + __func__); + return -ENOMEM; + } + + log->bios_event_log_end = log->bios_event_log + len; + + virt = acpi_os_map_iomem(start, len); + if (!virt) { + kfree(log->bios_event_log); + printk("%s: ERROR - Unable to map memory\n", __func__); + return -EIO; + } + + memcpy_fromio(log->bios_event_log, virt, len); + + acpi_os_unmap_iomem(virt, len); + return 0; +} diff --git a/kernel/drivers/char/tpm/tpm_atmel.c b/kernel/drivers/char/tpm/tpm_atmel.c new file mode 100644 index 000000000..dfadad091 --- /dev/null +++ b/kernel/drivers/char/tpm/tpm_atmel.c @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2004 IBM Corporation + * + * Authors: + * Leendert van Doorn <leendert@watson.ibm.com> + * Dave Safford <safford@watson.ibm.com> + * Reiner Sailer <sailer@watson.ibm.com> + * Kylene Hall <kjhall@us.ibm.com> + * + * Maintained by: <tpmdd-devel@lists.sourceforge.net> + * + * Device driver for TCG/TCPA TPM (trusted platform module). + * Specifications at www.trustedcomputinggroup.org + * + * 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, version 2 of the + * License. + * + */ + +#include "tpm.h" +#include "tpm_atmel.h" + +/* write status bits */ +enum tpm_atmel_write_status { + ATML_STATUS_ABORT = 0x01, + ATML_STATUS_LASTBYTE = 0x04 +}; +/* read status bits */ +enum tpm_atmel_read_status { + ATML_STATUS_BUSY = 0x01, + ATML_STATUS_DATA_AVAIL = 0x02, + ATML_STATUS_REWRITE = 0x04, + ATML_STATUS_READY = 0x08 +}; + +static int tpm_atml_recv(struct tpm_chip *chip, u8 *buf, size_t count) +{ + u8 status, *hdr = buf; + u32 size; + int i; + __be32 *native_size; + + /* start reading header */ + if (count < 6) + return -EIO; + + for (i = 0; i < 6; i++) { + status = ioread8(chip->vendor.iobase + 1); + if ((status & ATML_STATUS_DATA_AVAIL) == 0) { + dev_err(chip->pdev, "error reading header\n"); + return -EIO; + } + *buf++ = ioread8(chip->vendor.iobase); + } + + /* size of the data received */ + native_size = (__force __be32 *) (hdr + 2); + size = be32_to_cpu(*native_size); + + if (count < size) { + dev_err(chip->pdev, + "Recv size(%d) less than available space\n", size); + for (; i < size; i++) { /* clear the waiting data anyway */ + status = ioread8(chip->vendor.iobase + 1); + if ((status & ATML_STATUS_DATA_AVAIL) == 0) { + dev_err(chip->pdev, "error reading data\n"); + return -EIO; + } + } + return -EIO; + } + + /* read all the data available */ + for (; i < size; i++) { + status = ioread8(chip->vendor.iobase + 1); + if ((status & ATML_STATUS_DATA_AVAIL) == 0) { + dev_err(chip->pdev, "error reading data\n"); + return -EIO; + } + *buf++ = ioread8(chip->vendor.iobase); + } + + /* make sure data available is gone */ + status = ioread8(chip->vendor.iobase + 1); + + if (status & ATML_STATUS_DATA_AVAIL) { + dev_err(chip->pdev, "data available is stuck\n"); + return -EIO; + } + + return size; +} + +static int tpm_atml_send(struct tpm_chip *chip, u8 *buf, size_t count) +{ + int i; + + dev_dbg(chip->pdev, "tpm_atml_send:\n"); + for (i = 0; i < count; i++) { + dev_dbg(chip->pdev, "%d 0x%x(%d)\n", i, buf[i], buf[i]); + iowrite8(buf[i], chip->vendor.iobase); + } + + return count; +} + +static void tpm_atml_cancel(struct tpm_chip *chip) +{ + iowrite8(ATML_STATUS_ABORT, chip->vendor.iobase + 1); +} + +static u8 tpm_atml_status(struct tpm_chip *chip) +{ + return ioread8(chip->vendor.iobase + 1); +} + +static bool tpm_atml_req_canceled(struct tpm_chip *chip, u8 status) +{ + return (status == ATML_STATUS_READY); +} + +static const struct tpm_class_ops tpm_atmel = { + .recv = tpm_atml_recv, + .send = tpm_atml_send, + .cancel = tpm_atml_cancel, + .status = tpm_atml_status, + .req_complete_mask = ATML_STATUS_BUSY | ATML_STATUS_DATA_AVAIL, + .req_complete_val = ATML_STATUS_DATA_AVAIL, + .req_canceled = tpm_atml_req_canceled, +}; + +static struct platform_device *pdev; + +static void atml_plat_remove(void) +{ + struct tpm_chip *chip = dev_get_drvdata(&pdev->dev); + + if (chip) { + tpm_chip_unregister(chip); + if (chip->vendor.have_region) + atmel_release_region(chip->vendor.base, + chip->vendor.region_size); + atmel_put_base_addr(chip->vendor.iobase); + platform_device_unregister(pdev); + } +} + +static SIMPLE_DEV_PM_OPS(tpm_atml_pm, tpm_pm_suspend, tpm_pm_resume); + +static struct platform_driver atml_drv = { + .driver = { + .name = "tpm_atmel", + .pm = &tpm_atml_pm, + }, +}; + +static int __init init_atmel(void) +{ + int rc = 0; + void __iomem *iobase = NULL; + int have_region, region_size; + unsigned long base; + struct tpm_chip *chip; + + rc = platform_driver_register(&atml_drv); + if (rc) + return rc; + + if ((iobase = atmel_get_base_addr(&base, ®ion_size)) == NULL) { + rc = -ENODEV; + goto err_unreg_drv; + } + + have_region = + (atmel_request_region + (base, region_size, "tpm_atmel0") == NULL) ? 0 : 1; + + pdev = platform_device_register_simple("tpm_atmel", -1, NULL, 0); + if (IS_ERR(pdev)) { + rc = PTR_ERR(pdev); + goto err_rel_reg; + } + + chip = tpmm_chip_alloc(&pdev->dev, &tpm_atmel); + if (IS_ERR(chip)) { + rc = PTR_ERR(chip); + goto err_unreg_dev; + } + + chip->vendor.iobase = iobase; + chip->vendor.base = base; + chip->vendor.have_region = have_region; + chip->vendor.region_size = region_size; + + rc = tpm_chip_register(chip); + if (rc) + goto err_unreg_dev; + + return 0; + +err_unreg_dev: + platform_device_unregister(pdev); +err_rel_reg: + atmel_put_base_addr(iobase); + if (have_region) + atmel_release_region(base, + region_size); +err_unreg_drv: + platform_driver_unregister(&atml_drv); + return rc; +} + +static void __exit cleanup_atmel(void) +{ + platform_driver_unregister(&atml_drv); + atml_plat_remove(); +} + +module_init(init_atmel); +module_exit(cleanup_atmel); + +MODULE_AUTHOR("Leendert van Doorn (leendert@watson.ibm.com)"); +MODULE_DESCRIPTION("TPM Driver"); +MODULE_VERSION("2.0"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/char/tpm/tpm_atmel.h b/kernel/drivers/char/tpm/tpm_atmel.h new file mode 100644 index 000000000..6c831f946 --- /dev/null +++ b/kernel/drivers/char/tpm/tpm_atmel.h @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2005 IBM Corporation + * + * Authors: + * Kylene Hall <kjhall@us.ibm.com> + * + * Maintained by: <tpmdd-devel@lists.sourceforge.net> + * + * Device driver for TCG/TCPA TPM (trusted platform module). + * Specifications at www.trustedcomputinggroup.org + * + * 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, version 2 of the + * License. + * + * These difference are required on power because the device must be + * discovered through the device tree and iomap must be used to get + * around the need for holes in the io_page_mask. This does not happen + * automatically because the tpm is not a normal pci device and lives + * under the root node. + * + */ + +#ifdef CONFIG_PPC64 + +#include <asm/prom.h> + +#define atmel_getb(chip, offset) readb(chip->vendor->iobase + offset); +#define atmel_putb(val, chip, offset) writeb(val, chip->vendor->iobase + offset) +#define atmel_request_region request_mem_region +#define atmel_release_region release_mem_region + +static inline void atmel_put_base_addr(void __iomem *iobase) +{ + iounmap(iobase); +} + +static void __iomem * atmel_get_base_addr(unsigned long *base, int *region_size) +{ + struct device_node *dn; + unsigned long address, size; + const unsigned int *reg; + int reglen; + int naddrc; + int nsizec; + + dn = of_find_node_by_name(NULL, "tpm"); + + if (!dn) + return NULL; + + if (!of_device_is_compatible(dn, "AT97SC3201")) { + of_node_put(dn); + return NULL; + } + + reg = of_get_property(dn, "reg", ®len); + naddrc = of_n_addr_cells(dn); + nsizec = of_n_size_cells(dn); + + of_node_put(dn); + + + if (naddrc == 2) + address = ((unsigned long) reg[0] << 32) | reg[1]; + else + address = reg[0]; + + if (nsizec == 2) + size = + ((unsigned long) reg[naddrc] << 32) | reg[naddrc + 1]; + else + size = reg[naddrc]; + + *base = address; + *region_size = size; + return ioremap(*base, *region_size); +} +#else +#define atmel_getb(chip, offset) inb(chip->vendor->base + offset) +#define atmel_putb(val, chip, offset) outb(val, chip->vendor->base + offset) +#define atmel_request_region request_region +#define atmel_release_region release_region +/* Atmel definitions */ +enum tpm_atmel_addr { + TPM_ATMEL_BASE_ADDR_LO = 0x08, + TPM_ATMEL_BASE_ADDR_HI = 0x09 +}; + +/* Verify this is a 1.1 Atmel TPM */ +static int atmel_verify_tpm11(void) +{ + + /* verify that it is an Atmel part */ + if (tpm_read_index(TPM_ADDR, 4) != 'A' || + tpm_read_index(TPM_ADDR, 5) != 'T' || + tpm_read_index(TPM_ADDR, 6) != 'M' || + tpm_read_index(TPM_ADDR, 7) != 'L') + return 1; + + /* query chip for its version number */ + if (tpm_read_index(TPM_ADDR, 0x00) != 1 || + tpm_read_index(TPM_ADDR, 0x01) != 1) + return 1; + + /* This is an atmel supported part */ + return 0; +} + +static inline void atmel_put_base_addr(void __iomem *iobase) +{ +} + +/* Determine where to talk to device */ +static void __iomem * atmel_get_base_addr(unsigned long *base, int *region_size) +{ + int lo, hi; + + if (atmel_verify_tpm11() != 0) + return NULL; + + lo = tpm_read_index(TPM_ADDR, TPM_ATMEL_BASE_ADDR_LO); + hi = tpm_read_index(TPM_ADDR, TPM_ATMEL_BASE_ADDR_HI); + + *base = (hi << 8) | lo; + *region_size = 2; + + return ioport_map(*base, *region_size); +} +#endif diff --git a/kernel/drivers/char/tpm/tpm_crb.c b/kernel/drivers/char/tpm/tpm_crb.c new file mode 100644 index 000000000..b26ceee35 --- /dev/null +++ b/kernel/drivers/char/tpm/tpm_crb.c @@ -0,0 +1,344 @@ +/* + * Copyright (C) 2014 Intel Corporation + * + * Authors: + * Jarkko Sakkinen <jarkko.sakkinen@linux.intel.com> + * + * Maintained by: <tpmdd-devel@lists.sourceforge.net> + * + * This device driver implements the TPM interface as defined in + * the TCG CRB 2.0 TPM specification. + * + * 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; version 2 + * of the License. + */ + +#include <linux/acpi.h> +#include <linux/highmem.h> +#include <linux/rculist.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include "tpm.h" + +#define ACPI_SIG_TPM2 "TPM2" + +static const u8 CRB_ACPI_START_UUID[] = { + /* 0000 */ 0xAB, 0x6C, 0xBF, 0x6B, 0x63, 0x54, 0x14, 0x47, + /* 0008 */ 0xB7, 0xCD, 0xF0, 0x20, 0x3C, 0x03, 0x68, 0xD4 +}; + +enum crb_defaults { + CRB_ACPI_START_REVISION_ID = 1, + CRB_ACPI_START_INDEX = 1, +}; + +enum crb_start_method { + CRB_SM_ACPI_START = 2, + CRB_SM_CRB = 7, + CRB_SM_CRB_WITH_ACPI_START = 8, +}; + +struct acpi_tpm2 { + struct acpi_table_header hdr; + u16 platform_class; + u16 reserved; + u64 control_area_pa; + u32 start_method; +} __packed; + +enum crb_ca_request { + CRB_CA_REQ_GO_IDLE = BIT(0), + CRB_CA_REQ_CMD_READY = BIT(1), +}; + +enum crb_ca_status { + CRB_CA_STS_ERROR = BIT(0), + CRB_CA_STS_TPM_IDLE = BIT(1), +}; + +enum crb_start { + CRB_START_INVOKE = BIT(0), +}; + +enum crb_cancel { + CRB_CANCEL_INVOKE = BIT(0), +}; + +struct crb_control_area { + u32 req; + u32 sts; + u32 cancel; + u32 start; + u32 int_enable; + u32 int_sts; + u32 cmd_size; + u64 cmd_pa; + u32 rsp_size; + u64 rsp_pa; +} __packed; + +enum crb_status { + CRB_STS_COMPLETE = BIT(0), +}; + +enum crb_flags { + CRB_FL_ACPI_START = BIT(0), + CRB_FL_CRB_START = BIT(1), +}; + +struct crb_priv { + unsigned int flags; + struct crb_control_area __iomem *cca; + u8 __iomem *cmd; + u8 __iomem *rsp; +}; + +static SIMPLE_DEV_PM_OPS(crb_pm, tpm_pm_suspend, tpm_pm_resume); + +static u8 crb_status(struct tpm_chip *chip) +{ + struct crb_priv *priv = chip->vendor.priv; + u8 sts = 0; + + if ((le32_to_cpu(ioread32(&priv->cca->start)) & CRB_START_INVOKE) != + CRB_START_INVOKE) + sts |= CRB_STS_COMPLETE; + + return sts; +} + +static int crb_recv(struct tpm_chip *chip, u8 *buf, size_t count) +{ + struct crb_priv *priv = chip->vendor.priv; + unsigned int expected; + + /* sanity check */ + if (count < 6) + return -EIO; + + if (le32_to_cpu(ioread32(&priv->cca->sts)) & CRB_CA_STS_ERROR) + return -EIO; + + memcpy_fromio(buf, priv->rsp, 6); + expected = be32_to_cpup((__be32 *) &buf[2]); + + if (expected > count) + return -EIO; + + memcpy_fromio(&buf[6], &priv->rsp[6], expected - 6); + + return expected; +} + +static int crb_do_acpi_start(struct tpm_chip *chip) +{ + union acpi_object *obj; + int rc; + + obj = acpi_evaluate_dsm(chip->acpi_dev_handle, + CRB_ACPI_START_UUID, + CRB_ACPI_START_REVISION_ID, + CRB_ACPI_START_INDEX, + NULL); + if (!obj) + return -ENXIO; + rc = obj->integer.value == 0 ? 0 : -ENXIO; + ACPI_FREE(obj); + return rc; +} + +static int crb_send(struct tpm_chip *chip, u8 *buf, size_t len) +{ + struct crb_priv *priv = chip->vendor.priv; + int rc = 0; + + if (len > le32_to_cpu(ioread32(&priv->cca->cmd_size))) { + dev_err(&chip->dev, + "invalid command count value %x %zx\n", + (unsigned int) len, + (size_t) le32_to_cpu(ioread32(&priv->cca->cmd_size))); + return -E2BIG; + } + + memcpy_toio(priv->cmd, buf, len); + + /* Make sure that cmd is populated before issuing start. */ + wmb(); + + if (priv->flags & CRB_FL_CRB_START) + iowrite32(cpu_to_le32(CRB_START_INVOKE), &priv->cca->start); + + if (priv->flags & CRB_FL_ACPI_START) + rc = crb_do_acpi_start(chip); + + return rc; +} + +static void crb_cancel(struct tpm_chip *chip) +{ + struct crb_priv *priv = chip->vendor.priv; + + iowrite32(cpu_to_le32(CRB_CANCEL_INVOKE), &priv->cca->cancel); + + /* Make sure that cmd is populated before issuing cancel. */ + wmb(); + + if ((priv->flags & CRB_FL_ACPI_START) && crb_do_acpi_start(chip)) + dev_err(&chip->dev, "ACPI Start failed\n"); + + iowrite32(0, &priv->cca->cancel); +} + +static bool crb_req_canceled(struct tpm_chip *chip, u8 status) +{ + struct crb_priv *priv = chip->vendor.priv; + u32 cancel = le32_to_cpu(ioread32(&priv->cca->cancel)); + + return (cancel & CRB_CANCEL_INVOKE) == CRB_CANCEL_INVOKE; +} + +static const struct tpm_class_ops tpm_crb = { + .status = crb_status, + .recv = crb_recv, + .send = crb_send, + .cancel = crb_cancel, + .req_canceled = crb_req_canceled, + .req_complete_mask = CRB_STS_COMPLETE, + .req_complete_val = CRB_STS_COMPLETE, +}; + +static int crb_acpi_add(struct acpi_device *device) +{ + struct tpm_chip *chip; + struct acpi_tpm2 *buf; + struct crb_priv *priv; + struct device *dev = &device->dev; + acpi_status status; + u32 sm; + u64 pa; + int rc; + + chip = tpmm_chip_alloc(dev, &tpm_crb); + if (IS_ERR(chip)) + return PTR_ERR(chip); + + chip->flags = TPM_CHIP_FLAG_TPM2; + + status = acpi_get_table(ACPI_SIG_TPM2, 1, + (struct acpi_table_header **) &buf); + if (ACPI_FAILURE(status)) { + dev_err(dev, "failed to get TPM2 ACPI table\n"); + return -ENODEV; + } + + if (buf->hdr.length < sizeof(struct acpi_tpm2)) { + dev_err(dev, "TPM2 ACPI table has wrong size"); + return -EINVAL; + } + + priv = (struct crb_priv *) devm_kzalloc(dev, sizeof(struct crb_priv), + GFP_KERNEL); + if (!priv) { + dev_err(dev, "failed to devm_kzalloc for private data\n"); + return -ENOMEM; + } + + sm = le32_to_cpu(buf->start_method); + + /* The reason for the extra quirk is that the PTT in 4th Gen Core CPUs + * report only ACPI start but in practice seems to require both + * ACPI start and CRB start. + */ + if (sm == CRB_SM_CRB || sm == CRB_SM_CRB_WITH_ACPI_START || + !strcmp(acpi_device_hid(device), "MSFT0101")) + priv->flags |= CRB_FL_CRB_START; + + if (sm == CRB_SM_ACPI_START || sm == CRB_SM_CRB_WITH_ACPI_START) + priv->flags |= CRB_FL_ACPI_START; + + priv->cca = (struct crb_control_area __iomem *) + devm_ioremap_nocache(dev, buf->control_area_pa, 0x1000); + if (!priv->cca) { + dev_err(dev, "ioremap of the control area failed\n"); + return -ENOMEM; + } + + memcpy_fromio(&pa, &priv->cca->cmd_pa, 8); + pa = le64_to_cpu(pa); + priv->cmd = devm_ioremap_nocache(dev, le64_to_cpu(pa), + ioread32(&priv->cca->cmd_size)); + if (!priv->cmd) { + dev_err(dev, "ioremap of the command buffer failed\n"); + return -ENOMEM; + } + + memcpy_fromio(&pa, &priv->cca->rsp_pa, 8); + pa = le64_to_cpu(pa); + priv->rsp = devm_ioremap_nocache(dev, le64_to_cpu(pa), + ioread32(&priv->cca->rsp_size)); + if (!priv->rsp) { + dev_err(dev, "ioremap of the response buffer failed\n"); + return -ENOMEM; + } + + chip->vendor.priv = priv; + + /* Default timeouts and durations */ + chip->vendor.timeout_a = msecs_to_jiffies(TPM2_TIMEOUT_A); + chip->vendor.timeout_b = msecs_to_jiffies(TPM2_TIMEOUT_B); + chip->vendor.timeout_c = msecs_to_jiffies(TPM2_TIMEOUT_C); + chip->vendor.timeout_d = msecs_to_jiffies(TPM2_TIMEOUT_D); + chip->vendor.duration[TPM_SHORT] = + msecs_to_jiffies(TPM2_DURATION_SHORT); + chip->vendor.duration[TPM_MEDIUM] = + msecs_to_jiffies(TPM2_DURATION_MEDIUM); + chip->vendor.duration[TPM_LONG] = + msecs_to_jiffies(TPM2_DURATION_LONG); + + chip->acpi_dev_handle = device->handle; + + rc = tpm2_do_selftest(chip); + if (rc) + return rc; + + return tpm_chip_register(chip); +} + +static int crb_acpi_remove(struct acpi_device *device) +{ + struct device *dev = &device->dev; + struct tpm_chip *chip = dev_get_drvdata(dev); + + tpm_chip_unregister(chip); + + if (chip->flags & TPM_CHIP_FLAG_TPM2) + tpm2_shutdown(chip, TPM2_SU_CLEAR); + + return 0; +} + +static struct acpi_device_id crb_device_ids[] = { + {"MSFT0101", 0}, + {"", 0}, +}; +MODULE_DEVICE_TABLE(acpi, crb_device_ids); + +static struct acpi_driver crb_acpi_driver = { + .name = "tpm_crb", + .ids = crb_device_ids, + .ops = { + .add = crb_acpi_add, + .remove = crb_acpi_remove, + }, + .drv = { + .pm = &crb_pm, + }, +}; + +module_acpi_driver(crb_acpi_driver); +MODULE_AUTHOR("Jarkko Sakkinen <jarkko.sakkinen@linux.intel.com>"); +MODULE_DESCRIPTION("TPM2 Driver"); +MODULE_VERSION("0.1"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/char/tpm/tpm_eventlog.c b/kernel/drivers/char/tpm/tpm_eventlog.c new file mode 100644 index 000000000..3a56a1315 --- /dev/null +++ b/kernel/drivers/char/tpm/tpm_eventlog.c @@ -0,0 +1,414 @@ +/* + * Copyright (C) 2005, 2012 IBM Corporation + * + * Authors: + * Kent Yoder <key@linux.vnet.ibm.com> + * Seiji Munetoh <munetoh@jp.ibm.com> + * Stefan Berger <stefanb@us.ibm.com> + * Reiner Sailer <sailer@watson.ibm.com> + * Kylene Hall <kjhall@us.ibm.com> + * + * Maintained by: <tpmdd-devel@lists.sourceforge.net> + * + * Access to the eventlog created by a system's firmware / BIOS + * + * 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. + * + */ + +#include <linux/seq_file.h> +#include <linux/fs.h> +#include <linux/security.h> +#include <linux/module.h> +#include <linux/slab.h> + +#include "tpm.h" +#include "tpm_eventlog.h" + + +static const char* tcpa_event_type_strings[] = { + "PREBOOT", + "POST CODE", + "", + "NO ACTION", + "SEPARATOR", + "ACTION", + "EVENT TAG", + "S-CRTM Contents", + "S-CRTM Version", + "CPU Microcode", + "Platform Config Flags", + "Table of Devices", + "Compact Hash", + "IPL", + "IPL Partition Data", + "Non-Host Code", + "Non-Host Config", + "Non-Host Info" +}; + +static const char* tcpa_pc_event_id_strings[] = { + "", + "SMBIOS", + "BIS Certificate", + "POST BIOS ", + "ESCD ", + "CMOS", + "NVRAM", + "Option ROM", + "Option ROM config", + "", + "Option ROM microcode ", + "S-CRTM Version", + "S-CRTM Contents ", + "POST Contents ", + "Table of Devices", +}; + +/* returns pointer to start of pos. entry of tcg log */ +static void *tpm_bios_measurements_start(struct seq_file *m, loff_t *pos) +{ + loff_t i; + struct tpm_bios_log *log = m->private; + void *addr = log->bios_event_log; + void *limit = log->bios_event_log_end; + struct tcpa_event *event; + + /* read over *pos measurements */ + for (i = 0; i < *pos; i++) { + event = addr; + + if ((addr + sizeof(struct tcpa_event)) < limit) { + if (event->event_type == 0 && event->event_size == 0) + return NULL; + addr += sizeof(struct tcpa_event) + event->event_size; + } + } + + /* now check if current entry is valid */ + if ((addr + sizeof(struct tcpa_event)) >= limit) + return NULL; + + event = addr; + + if ((event->event_type == 0 && event->event_size == 0) || + ((addr + sizeof(struct tcpa_event) + event->event_size) >= limit)) + return NULL; + + return addr; +} + +static void *tpm_bios_measurements_next(struct seq_file *m, void *v, + loff_t *pos) +{ + struct tcpa_event *event = v; + struct tpm_bios_log *log = m->private; + void *limit = log->bios_event_log_end; + + v += sizeof(struct tcpa_event) + event->event_size; + + /* now check if current entry is valid */ + if ((v + sizeof(struct tcpa_event)) >= limit) + return NULL; + + event = v; + + if (event->event_type == 0 && event->event_size == 0) + return NULL; + + if ((event->event_type == 0 && event->event_size == 0) || + ((v + sizeof(struct tcpa_event) + event->event_size) >= limit)) + return NULL; + + (*pos)++; + return v; +} + +static void tpm_bios_measurements_stop(struct seq_file *m, void *v) +{ +} + +static int get_event_name(char *dest, struct tcpa_event *event, + unsigned char * event_entry) +{ + const char *name = ""; + /* 41 so there is room for 40 data and 1 nul */ + char data[41] = ""; + int i, n_len = 0, d_len = 0; + struct tcpa_pc_event *pc_event; + + switch(event->event_type) { + case PREBOOT: + case POST_CODE: + case UNUSED: + case NO_ACTION: + case SCRTM_CONTENTS: + case SCRTM_VERSION: + case CPU_MICROCODE: + case PLATFORM_CONFIG_FLAGS: + case TABLE_OF_DEVICES: + case COMPACT_HASH: + case IPL: + case IPL_PARTITION_DATA: + case NONHOST_CODE: + case NONHOST_CONFIG: + case NONHOST_INFO: + name = tcpa_event_type_strings[event->event_type]; + n_len = strlen(name); + break; + case SEPARATOR: + case ACTION: + if (MAX_TEXT_EVENT > event->event_size) { + name = event_entry; + n_len = event->event_size; + } + break; + case EVENT_TAG: + pc_event = (struct tcpa_pc_event *)event_entry; + + /* ToDo Row data -> Base64 */ + + switch (pc_event->event_id) { + case SMBIOS: + case BIS_CERT: + case CMOS: + case NVRAM: + case OPTION_ROM_EXEC: + case OPTION_ROM_CONFIG: + case S_CRTM_VERSION: + name = tcpa_pc_event_id_strings[pc_event->event_id]; + n_len = strlen(name); + break; + /* hash data */ + case POST_BIOS_ROM: + case ESCD: + case OPTION_ROM_MICROCODE: + case S_CRTM_CONTENTS: + case POST_CONTENTS: + name = tcpa_pc_event_id_strings[pc_event->event_id]; + n_len = strlen(name); + for (i = 0; i < 20; i++) + d_len += sprintf(&data[2*i], "%02x", + pc_event->event_data[i]); + break; + default: + break; + } + default: + break; + } + + return snprintf(dest, MAX_TEXT_EVENT, "[%.*s%.*s]", + n_len, name, d_len, data); + +} + +static int tpm_binary_bios_measurements_show(struct seq_file *m, void *v) +{ + struct tcpa_event *event = v; + char *data = v; + int i; + + for (i = 0; i < sizeof(struct tcpa_event) + event->event_size; i++) + seq_putc(m, data[i]); + + return 0; +} + +static int tpm_bios_measurements_release(struct inode *inode, + struct file *file) +{ + struct seq_file *seq = file->private_data; + struct tpm_bios_log *log = seq->private; + + if (log) { + kfree(log->bios_event_log); + kfree(log); + } + + return seq_release(inode, file); +} + +static int tpm_ascii_bios_measurements_show(struct seq_file *m, void *v) +{ + int len = 0; + char *eventname; + struct tcpa_event *event = v; + unsigned char *event_entry = + (unsigned char *) (v + sizeof(struct tcpa_event)); + + eventname = kmalloc(MAX_TEXT_EVENT, GFP_KERNEL); + if (!eventname) { + printk(KERN_ERR "%s: ERROR - No Memory for event name\n ", + __func__); + return -EFAULT; + } + + seq_printf(m, "%2d ", event->pcr_index); + + /* 2nd: SHA1 */ + seq_printf(m, "%20phN", event->pcr_value); + + /* 3rd: event type identifier */ + seq_printf(m, " %02x", event->event_type); + + len += get_event_name(eventname, event, event_entry); + + /* 4th: eventname <= max + \'0' delimiter */ + seq_printf(m, " %s\n", eventname); + + kfree(eventname); + return 0; +} + +static const struct seq_operations tpm_ascii_b_measurments_seqops = { + .start = tpm_bios_measurements_start, + .next = tpm_bios_measurements_next, + .stop = tpm_bios_measurements_stop, + .show = tpm_ascii_bios_measurements_show, +}; + +static const struct seq_operations tpm_binary_b_measurments_seqops = { + .start = tpm_bios_measurements_start, + .next = tpm_bios_measurements_next, + .stop = tpm_bios_measurements_stop, + .show = tpm_binary_bios_measurements_show, +}; + +static int tpm_ascii_bios_measurements_open(struct inode *inode, + struct file *file) +{ + int err; + struct tpm_bios_log *log; + struct seq_file *seq; + + log = kzalloc(sizeof(struct tpm_bios_log), GFP_KERNEL); + if (!log) + return -ENOMEM; + + if ((err = read_log(log))) + goto out_free; + + /* now register seq file */ + err = seq_open(file, &tpm_ascii_b_measurments_seqops); + if (!err) { + seq = file->private_data; + seq->private = log; + } else { + goto out_free; + } + +out: + return err; +out_free: + kfree(log->bios_event_log); + kfree(log); + goto out; +} + +static const struct file_operations tpm_ascii_bios_measurements_ops = { + .open = tpm_ascii_bios_measurements_open, + .read = seq_read, + .llseek = seq_lseek, + .release = tpm_bios_measurements_release, +}; + +static int tpm_binary_bios_measurements_open(struct inode *inode, + struct file *file) +{ + int err; + struct tpm_bios_log *log; + struct seq_file *seq; + + log = kzalloc(sizeof(struct tpm_bios_log), GFP_KERNEL); + if (!log) + return -ENOMEM; + + if ((err = read_log(log))) + goto out_free; + + /* now register seq file */ + err = seq_open(file, &tpm_binary_b_measurments_seqops); + if (!err) { + seq = file->private_data; + seq->private = log; + } else { + goto out_free; + } + +out: + return err; +out_free: + kfree(log->bios_event_log); + kfree(log); + goto out; +} + +static const struct file_operations tpm_binary_bios_measurements_ops = { + .open = tpm_binary_bios_measurements_open, + .read = seq_read, + .llseek = seq_lseek, + .release = tpm_bios_measurements_release, +}; + +static int is_bad(void *p) +{ + if (!p) + return 1; + if (IS_ERR(p) && (PTR_ERR(p) != -ENODEV)) + return 1; + return 0; +} + +struct dentry **tpm_bios_log_setup(char *name) +{ + struct dentry **ret = NULL, *tpm_dir, *bin_file, *ascii_file; + + tpm_dir = securityfs_create_dir(name, NULL); + if (is_bad(tpm_dir)) + goto out; + + bin_file = + securityfs_create_file("binary_bios_measurements", + S_IRUSR | S_IRGRP, tpm_dir, NULL, + &tpm_binary_bios_measurements_ops); + if (is_bad(bin_file)) + goto out_tpm; + + ascii_file = + securityfs_create_file("ascii_bios_measurements", + S_IRUSR | S_IRGRP, tpm_dir, NULL, + &tpm_ascii_bios_measurements_ops); + if (is_bad(ascii_file)) + goto out_bin; + + ret = kmalloc(3 * sizeof(struct dentry *), GFP_KERNEL); + if (!ret) + goto out_ascii; + + ret[0] = ascii_file; + ret[1] = bin_file; + ret[2] = tpm_dir; + + return ret; + +out_ascii: + securityfs_remove(ascii_file); +out_bin: + securityfs_remove(bin_file); +out_tpm: + securityfs_remove(tpm_dir); +out: + return NULL; +} + +void tpm_bios_log_teardown(struct dentry **lst) +{ + int i; + + for (i = 0; i < 3; i++) + securityfs_remove(lst[i]); +} diff --git a/kernel/drivers/char/tpm/tpm_eventlog.h b/kernel/drivers/char/tpm/tpm_eventlog.h new file mode 100644 index 000000000..e7da086d6 --- /dev/null +++ b/kernel/drivers/char/tpm/tpm_eventlog.h @@ -0,0 +1,86 @@ + +#ifndef __TPM_EVENTLOG_H__ +#define __TPM_EVENTLOG_H__ + +#define TCG_EVENT_NAME_LEN_MAX 255 +#define MAX_TEXT_EVENT 1000 /* Max event string length */ +#define ACPI_TCPA_SIG "TCPA" /* 0x41504354 /'TCPA' */ + +enum bios_platform_class { + BIOS_CLIENT = 0x00, + BIOS_SERVER = 0x01, +}; + +struct tpm_bios_log { + void *bios_event_log; + void *bios_event_log_end; +}; + +struct tcpa_event { + u32 pcr_index; + u32 event_type; + u8 pcr_value[20]; /* SHA1 */ + u32 event_size; + u8 event_data[0]; +}; + +enum tcpa_event_types { + PREBOOT = 0, + POST_CODE, + UNUSED, + NO_ACTION, + SEPARATOR, + ACTION, + EVENT_TAG, + SCRTM_CONTENTS, + SCRTM_VERSION, + CPU_MICROCODE, + PLATFORM_CONFIG_FLAGS, + TABLE_OF_DEVICES, + COMPACT_HASH, + IPL, + IPL_PARTITION_DATA, + NONHOST_CODE, + NONHOST_CONFIG, + NONHOST_INFO, +}; + +struct tcpa_pc_event { + u32 event_id; + u32 event_size; + u8 event_data[0]; +}; + +enum tcpa_pc_event_ids { + SMBIOS = 1, + BIS_CERT, + POST_BIOS_ROM, + ESCD, + CMOS, + NVRAM, + OPTION_ROM_EXEC, + OPTION_ROM_CONFIG, + OPTION_ROM_MICROCODE = 10, + S_CRTM_VERSION, + S_CRTM_CONTENTS, + POST_CONTENTS, + HOST_TABLE_OF_DEVICES, +}; + +int read_log(struct tpm_bios_log *log); + +#if defined(CONFIG_TCG_IBMVTPM) || defined(CONFIG_TCG_IBMVTPM_MODULE) || \ + defined(CONFIG_ACPI) +extern struct dentry **tpm_bios_log_setup(char *); +extern void tpm_bios_log_teardown(struct dentry **); +#else +static inline struct dentry **tpm_bios_log_setup(char *name) +{ + return NULL; +} +static inline void tpm_bios_log_teardown(struct dentry **dir) +{ +} +#endif + +#endif diff --git a/kernel/drivers/char/tpm/tpm_i2c_atmel.c b/kernel/drivers/char/tpm/tpm_i2c_atmel.c new file mode 100644 index 000000000..7a0ca78ad --- /dev/null +++ b/kernel/drivers/char/tpm/tpm_i2c_atmel.c @@ -0,0 +1,230 @@ +/* + * ATMEL I2C TPM AT97SC3204T + * + * Copyright (C) 2012 V Lab Technologies + * Teddy Reed <teddy@prosauce.org> + * Copyright (C) 2013, Obsidian Research Corp. + * Jason Gunthorpe <jgunthorpe@obsidianresearch.com> + * Device driver for ATMEL I2C TPMs. + * + * Teddy Reed determined the basic I2C command flow, unlike other I2C TPM + * devices the raw TCG formatted TPM command data is written via I2C and then + * raw TCG formatted TPM command data is returned via I2C. + * + * TGC status/locality/etc functions seen in the LPC implementation do not + * seem to be present. + * + * 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/i2c.h> +#include "tpm.h" + +#define I2C_DRIVER_NAME "tpm_i2c_atmel" + +#define TPM_I2C_SHORT_TIMEOUT 750 /* ms */ +#define TPM_I2C_LONG_TIMEOUT 2000 /* 2 sec */ + +#define ATMEL_STS_OK 1 + +struct priv_data { + size_t len; + /* This is the amount we read on the first try. 25 was chosen to fit a + * fair number of read responses in the buffer so a 2nd retry can be + * avoided in small message cases. */ + u8 buffer[sizeof(struct tpm_output_header) + 25]; +}; + +static int i2c_atmel_send(struct tpm_chip *chip, u8 *buf, size_t len) +{ + struct priv_data *priv = chip->vendor.priv; + struct i2c_client *client = to_i2c_client(chip->pdev); + s32 status; + + priv->len = 0; + + if (len <= 2) + return -EIO; + + status = i2c_master_send(client, buf, len); + + dev_dbg(chip->pdev, + "%s(buf=%*ph len=%0zx) -> sts=%d\n", __func__, + (int)min_t(size_t, 64, len), buf, len, status); + return status; +} + +static int i2c_atmel_recv(struct tpm_chip *chip, u8 *buf, size_t count) +{ + struct priv_data *priv = chip->vendor.priv; + struct i2c_client *client = to_i2c_client(chip->pdev); + struct tpm_output_header *hdr = + (struct tpm_output_header *)priv->buffer; + u32 expected_len; + int rc; + + if (priv->len == 0) + return -EIO; + + /* Get the message size from the message header, if we didn't get the + * whole message in read_status then we need to re-read the + * message. */ + expected_len = be32_to_cpu(hdr->length); + if (expected_len > count) + return -ENOMEM; + + if (priv->len >= expected_len) { + dev_dbg(chip->pdev, + "%s early(buf=%*ph count=%0zx) -> ret=%d\n", __func__, + (int)min_t(size_t, 64, expected_len), buf, count, + expected_len); + memcpy(buf, priv->buffer, expected_len); + return expected_len; + } + + rc = i2c_master_recv(client, buf, expected_len); + dev_dbg(chip->pdev, + "%s reread(buf=%*ph count=%0zx) -> ret=%d\n", __func__, + (int)min_t(size_t, 64, expected_len), buf, count, + expected_len); + return rc; +} + +static void i2c_atmel_cancel(struct tpm_chip *chip) +{ + dev_err(chip->pdev, "TPM operation cancellation was requested, but is not supported"); +} + +static u8 i2c_atmel_read_status(struct tpm_chip *chip) +{ + struct priv_data *priv = chip->vendor.priv; + struct i2c_client *client = to_i2c_client(chip->pdev); + int rc; + + /* The TPM fails the I2C read until it is ready, so we do the entire + * transfer here and buffer it locally. This way the common code can + * properly handle the timeouts. */ + priv->len = 0; + memset(priv->buffer, 0, sizeof(priv->buffer)); + + + /* Once the TPM has completed the command the command remains readable + * until another command is issued. */ + rc = i2c_master_recv(client, priv->buffer, sizeof(priv->buffer)); + dev_dbg(chip->pdev, + "%s: sts=%d", __func__, rc); + if (rc <= 0) + return 0; + + priv->len = rc; + + return ATMEL_STS_OK; +} + +static bool i2c_atmel_req_canceled(struct tpm_chip *chip, u8 status) +{ + return false; +} + +static const struct tpm_class_ops i2c_atmel = { + .status = i2c_atmel_read_status, + .recv = i2c_atmel_recv, + .send = i2c_atmel_send, + .cancel = i2c_atmel_cancel, + .req_complete_mask = ATMEL_STS_OK, + .req_complete_val = ATMEL_STS_OK, + .req_canceled = i2c_atmel_req_canceled, +}; + +static int i2c_atmel_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct tpm_chip *chip; + struct device *dev = &client->dev; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return -ENODEV; + + chip = tpmm_chip_alloc(dev, &i2c_atmel); + if (IS_ERR(chip)) + return PTR_ERR(chip); + + chip->vendor.priv = devm_kzalloc(dev, sizeof(struct priv_data), + GFP_KERNEL); + if (!chip->vendor.priv) + return -ENOMEM; + + /* Default timeouts */ + chip->vendor.timeout_a = msecs_to_jiffies(TPM_I2C_SHORT_TIMEOUT); + chip->vendor.timeout_b = msecs_to_jiffies(TPM_I2C_LONG_TIMEOUT); + chip->vendor.timeout_c = msecs_to_jiffies(TPM_I2C_SHORT_TIMEOUT); + chip->vendor.timeout_d = msecs_to_jiffies(TPM_I2C_SHORT_TIMEOUT); + chip->vendor.irq = 0; + + /* There is no known way to probe for this device, and all version + * information seems to be read via TPM commands. Thus we rely on the + * TPM startup process in the common code to detect the device. */ + if (tpm_get_timeouts(chip)) + return -ENODEV; + + if (tpm_do_selftest(chip)) + return -ENODEV; + + return tpm_chip_register(chip); +} + +static int i2c_atmel_remove(struct i2c_client *client) +{ + struct device *dev = &(client->dev); + struct tpm_chip *chip = dev_get_drvdata(dev); + tpm_chip_unregister(chip); + return 0; +} + +static const struct i2c_device_id i2c_atmel_id[] = { + {I2C_DRIVER_NAME, 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, i2c_atmel_id); + +#ifdef CONFIG_OF +static const struct of_device_id i2c_atmel_of_match[] = { + {.compatible = "atmel,at97sc3204t"}, + {}, +}; +MODULE_DEVICE_TABLE(of, i2c_atmel_of_match); +#endif + +static SIMPLE_DEV_PM_OPS(i2c_atmel_pm_ops, tpm_pm_suspend, tpm_pm_resume); + +static struct i2c_driver i2c_atmel_driver = { + .id_table = i2c_atmel_id, + .probe = i2c_atmel_probe, + .remove = i2c_atmel_remove, + .driver = { + .name = I2C_DRIVER_NAME, + .owner = THIS_MODULE, + .pm = &i2c_atmel_pm_ops, + .of_match_table = of_match_ptr(i2c_atmel_of_match), + }, +}; + +module_i2c_driver(i2c_atmel_driver); + +MODULE_AUTHOR("Jason Gunthorpe <jgunthorpe@obsidianresearch.com>"); +MODULE_DESCRIPTION("Atmel TPM I2C Driver"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/char/tpm/tpm_i2c_infineon.c b/kernel/drivers/char/tpm/tpm_i2c_infineon.c new file mode 100644 index 000000000..33c5f360a --- /dev/null +++ b/kernel/drivers/char/tpm/tpm_i2c_infineon.c @@ -0,0 +1,724 @@ +/* + * Copyright (C) 2012,2013 Infineon Technologies + * + * Authors: + * Peter Huewe <peter.huewe@infineon.com> + * + * Device driver for TCG/TCPA TPM (trusted platform module). + * Specifications at www.trustedcomputinggroup.org + * + * This device driver implements the TPM interface as defined in + * the TCG TPM Interface Spec version 1.2, revision 1.0 and the + * Infineon I2C Protocol Stack Specification v0.20. + * + * It is based on the original tpm_tis device driver from Leendert van + * Dorn and Kyleen Hall. + * + * 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, version 2 of the + * License. + * + * + */ +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/wait.h> +#include "tpm.h" + +/* max. buffer size supported by our TPM */ +#define TPM_BUFSIZE 1260 + +/* max. number of iterations after I2C NAK */ +#define MAX_COUNT 3 + +#define SLEEP_DURATION_LOW 55 +#define SLEEP_DURATION_HI 65 + +/* max. number of iterations after I2C NAK for 'long' commands + * we need this especially for sending TPM_READY, since the cleanup after the + * transtion to the ready state may take some time, but it is unpredictable + * how long it will take. + */ +#define MAX_COUNT_LONG 50 + +#define SLEEP_DURATION_LONG_LOW 200 +#define SLEEP_DURATION_LONG_HI 220 + +/* After sending TPM_READY to 'reset' the TPM we have to sleep even longer */ +#define SLEEP_DURATION_RESET_LOW 2400 +#define SLEEP_DURATION_RESET_HI 2600 + +/* we want to use usleep_range instead of msleep for the 5ms TPM_TIMEOUT */ +#define TPM_TIMEOUT_US_LOW (TPM_TIMEOUT * 1000) +#define TPM_TIMEOUT_US_HI (TPM_TIMEOUT_US_LOW + 2000) + +/* expected value for DIDVID register */ +#define TPM_TIS_I2C_DID_VID_9635 0xd1150b00L +#define TPM_TIS_I2C_DID_VID_9645 0x001a15d1L + +enum i2c_chip_type { + SLB9635, + SLB9645, + UNKNOWN, +}; + +/* Structure to store I2C TPM specific stuff */ +struct tpm_inf_dev { + struct i2c_client *client; + u8 buf[TPM_BUFSIZE + sizeof(u8)]; /* max. buffer size + addr */ + struct tpm_chip *chip; + enum i2c_chip_type chip_type; +}; + +static struct tpm_inf_dev tpm_dev; + +/* + * iic_tpm_read() - read from TPM register + * @addr: register address to read from + * @buffer: provided by caller + * @len: number of bytes to read + * + * Read len bytes from TPM register and put them into + * buffer (little-endian format, i.e. first byte is put into buffer[0]). + * + * NOTE: TPM is big-endian for multi-byte values. Multi-byte + * values have to be swapped. + * + * NOTE: We can't unfortunately use the combined read/write functions + * provided by the i2c core as the TPM currently does not support the + * repeated start condition and due to it's special requirements. + * The i2c_smbus* functions do not work for this chip. + * + * Return -EIO on error, 0 on success. + */ +static int iic_tpm_read(u8 addr, u8 *buffer, size_t len) +{ + + struct i2c_msg msg1 = { + .addr = tpm_dev.client->addr, + .len = 1, + .buf = &addr + }; + struct i2c_msg msg2 = { + .addr = tpm_dev.client->addr, + .flags = I2C_M_RD, + .len = len, + .buf = buffer + }; + struct i2c_msg msgs[] = {msg1, msg2}; + + int rc = 0; + int count; + + /* Lock the adapter for the duration of the whole sequence. */ + if (!tpm_dev.client->adapter->algo->master_xfer) + return -EOPNOTSUPP; + i2c_lock_adapter(tpm_dev.client->adapter); + + if (tpm_dev.chip_type == SLB9645) { + /* use a combined read for newer chips + * unfortunately the smbus functions are not suitable due to + * the 32 byte limit of the smbus. + * retries should usually not be needed, but are kept just to + * be on the safe side. + */ + for (count = 0; count < MAX_COUNT; count++) { + rc = __i2c_transfer(tpm_dev.client->adapter, msgs, 2); + if (rc > 0) + break; /* break here to skip sleep */ + usleep_range(SLEEP_DURATION_LOW, SLEEP_DURATION_HI); + } + } else { + /* slb9635 protocol should work in all cases */ + for (count = 0; count < MAX_COUNT; count++) { + rc = __i2c_transfer(tpm_dev.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; + + /* After the TPM has successfully received the register address + * it needs some time, thus we're sleeping here again, before + * retrieving the data + */ + for (count = 0; count < MAX_COUNT; count++) { + usleep_range(SLEEP_DURATION_LOW, SLEEP_DURATION_HI); + rc = __i2c_transfer(tpm_dev.client->adapter, &msg2, 1); + if (rc > 0) + break; + } + } + +out: + i2c_unlock_adapter(tpm_dev.client->adapter); + /* take care of 'guard time' */ + usleep_range(SLEEP_DURATION_LOW, SLEEP_DURATION_HI); + + /* __i2c_transfer returns the number of successfully transferred + * messages. + * So rc should be greater than 0 here otherwise we have an error. + */ + if (rc <= 0) + return -EIO; + + return 0; +} + +static int iic_tpm_write_generic(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 = tpm_dev.client->addr, + .len = len + 1, + .buf = tpm_dev.buf + }; + + if (len > TPM_BUFSIZE) + return -EINVAL; + + if (!tpm_dev.client->adapter->algo->master_xfer) + return -EOPNOTSUPP; + i2c_lock_adapter(tpm_dev.client->adapter); + + /* prepend the 'register address' to the buffer */ + tpm_dev.buf[0] = addr; + memcpy(&(tpm_dev.buf[1]), buffer, len); + + /* + * NOTE: We have to use these special mechanisms here and unfortunately + * cannot rely on the standard behavior of i2c_transfer. + * Even for newer chips the smbus functions are not + * suitable due to the 32 byte limit of the smbus. + */ + for (count = 0; count < max_count; count++) { + rc = __i2c_transfer(tpm_dev.client->adapter, &msg1, 1); + if (rc > 0) + break; + usleep_range(sleep_low, sleep_hi); + } + + i2c_unlock_adapter(tpm_dev.client->adapter); + /* take care of 'guard time' */ + usleep_range(SLEEP_DURATION_LOW, SLEEP_DURATION_HI); + + /* __i2c_transfer returns the number of successfully transferred + * messages. + * So rc should be greater than 0 here otherwise we have an error. + */ + if (rc <= 0) + return -EIO; + + return 0; +} + +/* + * iic_tpm_write() - write to TPM register + * @addr: register address to write to + * @buffer: containing data to be written + * @len: number of bytes to write + * + * Write len bytes from provided buffer to TPM register (little + * endian format, i.e. buffer[0] is written as first byte). + * + * NOTE: TPM is big-endian for multi-byte values. Multi-byte + * values have to be swapped. + * + * NOTE: use this function instead of the iic_tpm_write_generic function. + * + * Return -EIO on error, 0 on success + */ +static int iic_tpm_write(u8 addr, u8 *buffer, size_t len) +{ + return iic_tpm_write_generic(addr, buffer, len, SLEEP_DURATION_LOW, + SLEEP_DURATION_HI, MAX_COUNT); +} + +/* + * This function is needed especially for the cleanup situation after + * sending TPM_READY + * */ +static int iic_tpm_write_long(u8 addr, u8 *buffer, size_t len) +{ + return iic_tpm_write_generic(addr, buffer, len, SLEEP_DURATION_LONG_LOW, + SLEEP_DURATION_LONG_HI, MAX_COUNT_LONG); +} + +enum tis_access { + TPM_ACCESS_VALID = 0x80, + TPM_ACCESS_ACTIVE_LOCALITY = 0x20, + TPM_ACCESS_REQUEST_PENDING = 0x04, + TPM_ACCESS_REQUEST_USE = 0x02, +}; + +enum tis_status { + TPM_STS_VALID = 0x80, + TPM_STS_COMMAND_READY = 0x40, + TPM_STS_GO = 0x20, + TPM_STS_DATA_AVAIL = 0x10, + TPM_STS_DATA_EXPECT = 0x08, +}; + +enum tis_defaults { + TIS_SHORT_TIMEOUT = 750, /* ms */ + TIS_LONG_TIMEOUT = 2000, /* 2 sec */ +}; + +#define TPM_ACCESS(l) (0x0000 | ((l) << 4)) +#define TPM_STS(l) (0x0001 | ((l) << 4)) +#define TPM_DATA_FIFO(l) (0x0005 | ((l) << 4)) +#define TPM_DID_VID(l) (0x0006 | ((l) << 4)) + +static int check_locality(struct tpm_chip *chip, int loc) +{ + u8 buf; + int rc; + + rc = iic_tpm_read(TPM_ACCESS(loc), &buf, 1); + if (rc < 0) + return rc; + + if ((buf & (TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID)) == + (TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID)) { + chip->vendor.locality = loc; + return loc; + } + + return -EIO; +} + +/* implementation similar to tpm_tis */ +static void release_locality(struct tpm_chip *chip, int loc, int force) +{ + u8 buf; + if (iic_tpm_read(TPM_ACCESS(loc), &buf, 1) < 0) + return; + + if (force || (buf & (TPM_ACCESS_REQUEST_PENDING | TPM_ACCESS_VALID)) == + (TPM_ACCESS_REQUEST_PENDING | TPM_ACCESS_VALID)) { + buf = TPM_ACCESS_ACTIVE_LOCALITY; + iic_tpm_write(TPM_ACCESS(loc), &buf, 1); + } +} + +static int request_locality(struct tpm_chip *chip, int loc) +{ + unsigned long stop; + u8 buf = TPM_ACCESS_REQUEST_USE; + + if (check_locality(chip, loc) >= 0) + return loc; + + iic_tpm_write(TPM_ACCESS(loc), &buf, 1); + + /* wait for burstcount */ + stop = jiffies + chip->vendor.timeout_a; + do { + if (check_locality(chip, loc) >= 0) + return loc; + usleep_range(TPM_TIMEOUT_US_LOW, TPM_TIMEOUT_US_HI); + } while (time_before(jiffies, stop)); + + return -ETIME; +} + +static u8 tpm_tis_i2c_status(struct tpm_chip *chip) +{ + /* NOTE: since I2C read may fail, return 0 in this case --> time-out */ + u8 buf = 0xFF; + u8 i = 0; + + do { + if (iic_tpm_read(TPM_STS(chip->vendor.locality), &buf, 1) < 0) + return 0; + + i++; + /* if locallity is set STS should not be 0xFF */ + } while ((buf == 0xFF) && i < 10); + + return buf; +} + +static void tpm_tis_i2c_ready(struct tpm_chip *chip) +{ + /* this causes the current command to be aborted */ + u8 buf = TPM_STS_COMMAND_READY; + iic_tpm_write_long(TPM_STS(chip->vendor.locality), &buf, 1); +} + +static ssize_t get_burstcount(struct tpm_chip *chip) +{ + unsigned long stop; + ssize_t burstcnt; + u8 buf[3]; + + /* wait for burstcount */ + /* which timeout value, spec has 2 answers (c & d) */ + stop = jiffies + chip->vendor.timeout_d; + do { + /* Note: STS is little endian */ + if (iic_tpm_read(TPM_STS(chip->vendor.locality)+1, buf, 3) < 0) + burstcnt = 0; + else + burstcnt = (buf[2] << 16) + (buf[1] << 8) + buf[0]; + + if (burstcnt) + return burstcnt; + + usleep_range(TPM_TIMEOUT_US_LOW, TPM_TIMEOUT_US_HI); + } while (time_before(jiffies, stop)); + return -EBUSY; +} + +static int wait_for_stat(struct tpm_chip *chip, u8 mask, unsigned long timeout, + int *status) +{ + unsigned long stop; + + /* check current status */ + *status = tpm_tis_i2c_status(chip); + if ((*status != 0xFF) && (*status & mask) == mask) + return 0; + + stop = jiffies + timeout; + do { + /* since we just checked the status, give the TPM some time */ + usleep_range(TPM_TIMEOUT_US_LOW, TPM_TIMEOUT_US_HI); + *status = tpm_tis_i2c_status(chip); + if ((*status & mask) == mask) + return 0; + + } while (time_before(jiffies, stop)); + + return -ETIME; +} + +static int recv_data(struct tpm_chip *chip, u8 *buf, size_t count) +{ + size_t size = 0; + ssize_t burstcnt; + u8 retries = 0; + int rc; + + while (size < count) { + burstcnt = get_burstcount(chip); + + /* burstcnt < 0 = TPM is busy */ + if (burstcnt < 0) + return burstcnt; + + /* limit received data to max. left */ + if (burstcnt > (count - size)) + burstcnt = count - size; + + rc = iic_tpm_read(TPM_DATA_FIFO(chip->vendor.locality), + &(buf[size]), burstcnt); + if (rc == 0) + size += burstcnt; + else if (rc < 0) + retries++; + + /* avoid endless loop in case of broken HW */ + if (retries > MAX_COUNT_LONG) + return -EIO; + } + return size; +} + +static int tpm_tis_i2c_recv(struct tpm_chip *chip, u8 *buf, size_t count) +{ + int size = 0; + int expected, status; + + if (count < TPM_HEADER_SIZE) { + size = -EIO; + goto out; + } + + /* read first 10 bytes, including tag, paramsize, and result */ + size = recv_data(chip, buf, TPM_HEADER_SIZE); + if (size < TPM_HEADER_SIZE) { + dev_err(chip->pdev, "Unable to read header\n"); + goto out; + } + + expected = be32_to_cpu(*(__be32 *)(buf + 2)); + if ((size_t) expected > count) { + size = -EIO; + goto out; + } + + size += recv_data(chip, &buf[TPM_HEADER_SIZE], + expected - TPM_HEADER_SIZE); + if (size < expected) { + dev_err(chip->pdev, "Unable to read remainder of result\n"); + size = -ETIME; + goto out; + } + + wait_for_stat(chip, TPM_STS_VALID, chip->vendor.timeout_c, &status); + if (status & TPM_STS_DATA_AVAIL) { /* retry? */ + dev_err(chip->pdev, "Error left over data\n"); + size = -EIO; + goto out; + } + +out: + tpm_tis_i2c_ready(chip); + /* The TPM needs some time to clean up here, + * so we sleep rather than keeping the bus busy + */ + usleep_range(SLEEP_DURATION_RESET_LOW, SLEEP_DURATION_RESET_HI); + release_locality(chip, chip->vendor.locality, 0); + return size; +} + +static int tpm_tis_i2c_send(struct tpm_chip *chip, u8 *buf, size_t len) +{ + int rc, status; + ssize_t burstcnt; + size_t count = 0; + u8 retries = 0; + u8 sts = TPM_STS_GO; + + if (len > TPM_BUFSIZE) + return -E2BIG; /* command is too long for our tpm, sorry */ + + if (request_locality(chip, 0) < 0) + return -EBUSY; + + status = tpm_tis_i2c_status(chip); + if ((status & TPM_STS_COMMAND_READY) == 0) { + tpm_tis_i2c_ready(chip); + if (wait_for_stat + (chip, TPM_STS_COMMAND_READY, + chip->vendor.timeout_b, &status) < 0) { + rc = -ETIME; + goto out_err; + } + } + + while (count < len - 1) { + burstcnt = get_burstcount(chip); + + /* burstcnt < 0 = TPM is busy */ + if (burstcnt < 0) + return burstcnt; + + if (burstcnt > (len - 1 - count)) + burstcnt = len - 1 - count; + + rc = iic_tpm_write(TPM_DATA_FIFO(chip->vendor.locality), + &(buf[count]), burstcnt); + if (rc == 0) + count += burstcnt; + else if (rc < 0) + retries++; + + /* avoid endless loop in case of broken HW */ + if (retries > MAX_COUNT_LONG) { + rc = -EIO; + goto out_err; + } + + wait_for_stat(chip, TPM_STS_VALID, + chip->vendor.timeout_c, &status); + + if ((status & TPM_STS_DATA_EXPECT) == 0) { + rc = -EIO; + goto out_err; + } + } + + /* write last byte */ + iic_tpm_write(TPM_DATA_FIFO(chip->vendor.locality), &(buf[count]), 1); + wait_for_stat(chip, TPM_STS_VALID, chip->vendor.timeout_c, &status); + if ((status & TPM_STS_DATA_EXPECT) != 0) { + rc = -EIO; + goto out_err; + } + + /* go and do it */ + iic_tpm_write(TPM_STS(chip->vendor.locality), &sts, 1); + + return len; +out_err: + tpm_tis_i2c_ready(chip); + /* The TPM needs some time to clean up here, + * so we sleep rather than keeping the bus busy + */ + usleep_range(SLEEP_DURATION_RESET_LOW, SLEEP_DURATION_RESET_HI); + release_locality(chip, chip->vendor.locality, 0); + return rc; +} + +static bool tpm_tis_i2c_req_canceled(struct tpm_chip *chip, u8 status) +{ + return (status == TPM_STS_COMMAND_READY); +} + +static const struct tpm_class_ops tpm_tis_i2c = { + .status = tpm_tis_i2c_status, + .recv = tpm_tis_i2c_recv, + .send = tpm_tis_i2c_send, + .cancel = tpm_tis_i2c_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_i2c_req_canceled, +}; + +static int tpm_tis_i2c_init(struct device *dev) +{ + u32 vendor; + int rc = 0; + struct tpm_chip *chip; + + chip = tpmm_chip_alloc(dev, &tpm_tis_i2c); + if (IS_ERR(chip)) + return PTR_ERR(chip); + + /* Disable interrupts */ + chip->vendor.irq = 0; + + /* Default timeouts */ + chip->vendor.timeout_a = msecs_to_jiffies(TIS_SHORT_TIMEOUT); + chip->vendor.timeout_b = msecs_to_jiffies(TIS_LONG_TIMEOUT); + chip->vendor.timeout_c = msecs_to_jiffies(TIS_SHORT_TIMEOUT); + chip->vendor.timeout_d = msecs_to_jiffies(TIS_SHORT_TIMEOUT); + + if (request_locality(chip, 0) != 0) { + dev_err(dev, "could not request locality\n"); + rc = -ENODEV; + goto out_err; + } + + /* read four bytes from DID_VID register */ + if (iic_tpm_read(TPM_DID_VID(0), (u8 *)&vendor, 4) < 0) { + dev_err(dev, "could not read vendor id\n"); + rc = -EIO; + goto out_release; + } + + if (vendor == TPM_TIS_I2C_DID_VID_9645) { + tpm_dev.chip_type = SLB9645; + } else if (vendor == TPM_TIS_I2C_DID_VID_9635) { + tpm_dev.chip_type = SLB9635; + } else { + dev_err(dev, "vendor id did not match! ID was %08x\n", vendor); + rc = -ENODEV; + goto out_release; + } + + dev_info(dev, "1.2 TPM (device-id 0x%X)\n", vendor >> 16); + + INIT_LIST_HEAD(&chip->vendor.list); + tpm_dev.chip = chip; + + tpm_get_timeouts(chip); + tpm_do_selftest(chip); + + return tpm_chip_register(chip); +out_release: + release_locality(chip, chip->vendor.locality, 1); + tpm_dev.client = NULL; +out_err: + return rc; +} + +static const struct i2c_device_id tpm_tis_i2c_table[] = { + {"tpm_i2c_infineon", 0}, + {"slb9635tt", 0}, + {"slb9645tt", 1}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, tpm_tis_i2c_table); + +#ifdef CONFIG_OF +static const struct of_device_id tpm_tis_i2c_of_match[] = { + { + .name = "tpm_i2c_infineon", + .type = "tpm", + .compatible = "infineon,tpm_i2c_infineon", + .data = (void *)0 + }, + { + .name = "slb9635tt", + .type = "tpm", + .compatible = "infineon,slb9635tt", + .data = (void *)0 + }, + { + .name = "slb9645tt", + .type = "tpm", + .compatible = "infineon,slb9645tt", + .data = (void *)1 + }, + {}, +}; +MODULE_DEVICE_TABLE(of, tpm_tis_i2c_of_match); +#endif + +static SIMPLE_DEV_PM_OPS(tpm_tis_i2c_ops, tpm_pm_suspend, tpm_pm_resume); + +static int tpm_tis_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc; + struct device *dev = &(client->dev); + + if (tpm_dev.client != NULL) { + dev_err(dev, "This driver only supports one client at a time\n"); + return -EBUSY; /* We only support one client */ + } + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(dev, "no algorithms associated to the i2c bus\n"); + return -ENODEV; + } + + tpm_dev.client = client; + rc = tpm_tis_i2c_init(&client->dev); + if (rc != 0) { + tpm_dev.client = NULL; + rc = -ENODEV; + } + return rc; +} + +static int tpm_tis_i2c_remove(struct i2c_client *client) +{ + struct tpm_chip *chip = tpm_dev.chip; + + tpm_chip_unregister(chip); + release_locality(chip, chip->vendor.locality, 1); + tpm_dev.client = NULL; + + return 0; +} + +static struct i2c_driver tpm_tis_i2c_driver = { + .id_table = tpm_tis_i2c_table, + .probe = tpm_tis_i2c_probe, + .remove = tpm_tis_i2c_remove, + .driver = { + .name = "tpm_i2c_infineon", + .owner = THIS_MODULE, + .pm = &tpm_tis_i2c_ops, + .of_match_table = of_match_ptr(tpm_tis_i2c_of_match), + }, +}; + +module_i2c_driver(tpm_tis_i2c_driver); +MODULE_AUTHOR("Peter Huewe <peter.huewe@infineon.com>"); +MODULE_DESCRIPTION("TPM TIS I2C Infineon Driver"); +MODULE_VERSION("2.2.0"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/char/tpm/tpm_i2c_nuvoton.c b/kernel/drivers/char/tpm/tpm_i2c_nuvoton.c new file mode 100644 index 000000000..9d42b7d78 --- /dev/null +++ b/kernel/drivers/char/tpm/tpm_i2c_nuvoton.c @@ -0,0 +1,654 @@ +/****************************************************************************** + * Nuvoton TPM I2C Device Driver Interface for WPCT301/NPCT501, + * based on the TCG TPM Interface Spec version 1.2. + * Specifications at www.trustedcomputinggroup.org + * + * Copyright (C) 2011, Nuvoton Technology Corporation. + * Dan Morav <dan.morav@nuvoton.com> + * Copyright (C) 2013, Obsidian Research Corp. + * Jason Gunthorpe <jgunthorpe@obsidianresearch.com> + * + * 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/>. + * + * Nuvoton contact information: APC.Support@nuvoton.com + *****************************************************************************/ + +#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 "tpm.h" + +/* I2C interface offsets */ +#define TPM_STS 0x00 +#define TPM_BURST_COUNT 0x01 +#define TPM_DATA_FIFO_W 0x20 +#define TPM_DATA_FIFO_R 0x40 +#define TPM_VID_DID_RID 0x60 +/* TPM command header size */ +#define TPM_HEADER_SIZE 10 +#define TPM_RETRY 5 +/* + * I2C bus device maximum buffer size w/o counting I2C address or command + * i.e. max size required for I2C write is 34 = addr, command, 32 bytes data + */ +#define TPM_I2C_MAX_BUF_SIZE 32 +#define TPM_I2C_RETRY_COUNT 32 +#define TPM_I2C_BUS_DELAY 1 /* msec */ +#define TPM_I2C_RETRY_DELAY_SHORT 2 /* msec */ +#define TPM_I2C_RETRY_DELAY_LONG 10 /* msec */ + +#define I2C_DRIVER_NAME "tpm_i2c_nuvoton" + +struct priv_data { + unsigned int intrs; +}; + +static s32 i2c_nuvoton_read_buf(struct i2c_client *client, u8 offset, u8 size, + u8 *data) +{ + s32 status; + + status = i2c_smbus_read_i2c_block_data(client, offset, size, data); + dev_dbg(&client->dev, + "%s(offset=%u size=%u data=%*ph) -> sts=%d\n", __func__, + offset, size, (int)size, data, status); + return status; +} + +static s32 i2c_nuvoton_write_buf(struct i2c_client *client, u8 offset, u8 size, + u8 *data) +{ + s32 status; + + status = i2c_smbus_write_i2c_block_data(client, offset, size, data); + dev_dbg(&client->dev, + "%s(offset=%u size=%u data=%*ph) -> sts=%d\n", __func__, + offset, size, (int)size, data, status); + return status; +} + +#define TPM_STS_VALID 0x80 +#define TPM_STS_COMMAND_READY 0x40 +#define TPM_STS_GO 0x20 +#define TPM_STS_DATA_AVAIL 0x10 +#define TPM_STS_EXPECT 0x08 +#define TPM_STS_RESPONSE_RETRY 0x02 +#define TPM_STS_ERR_VAL 0x07 /* bit2...bit0 reads always 0 */ + +#define TPM_I2C_SHORT_TIMEOUT 750 /* ms */ +#define TPM_I2C_LONG_TIMEOUT 2000 /* 2 sec */ + +/* read TPM_STS register */ +static u8 i2c_nuvoton_read_status(struct tpm_chip *chip) +{ + struct i2c_client *client = to_i2c_client(chip->pdev); + s32 status; + u8 data; + + status = i2c_nuvoton_read_buf(client, TPM_STS, 1, &data); + if (status <= 0) { + dev_err(chip->pdev, "%s() error return %d\n", __func__, + status); + data = TPM_STS_ERR_VAL; + } + + return data; +} + +/* write byte to TPM_STS register */ +static s32 i2c_nuvoton_write_status(struct i2c_client *client, u8 data) +{ + s32 status; + int i; + + /* this causes the current command to be aborted */ + for (i = 0, status = -1; i < TPM_I2C_RETRY_COUNT && status < 0; i++) { + status = i2c_nuvoton_write_buf(client, TPM_STS, 1, &data); + msleep(TPM_I2C_BUS_DELAY); + } + return status; +} + +/* write commandReady to TPM_STS register */ +static void i2c_nuvoton_ready(struct tpm_chip *chip) +{ + struct i2c_client *client = to_i2c_client(chip->pdev); + s32 status; + + /* this causes the current command to be aborted */ + status = i2c_nuvoton_write_status(client, TPM_STS_COMMAND_READY); + if (status < 0) + dev_err(chip->pdev, + "%s() fail to write TPM_STS.commandReady\n", __func__); +} + +/* read burstCount field from TPM_STS register + * return -1 on fail to read */ +static int i2c_nuvoton_get_burstcount(struct i2c_client *client, + struct tpm_chip *chip) +{ + unsigned long stop = jiffies + chip->vendor.timeout_d; + s32 status; + int burst_count = -1; + u8 data; + + /* wait for burstcount to be non-zero */ + do { + /* in I2C burstCount is 1 byte */ + status = i2c_nuvoton_read_buf(client, TPM_BURST_COUNT, 1, + &data); + if (status > 0 && data > 0) { + burst_count = min_t(u8, TPM_I2C_MAX_BUF_SIZE, data); + break; + } + msleep(TPM_I2C_BUS_DELAY); + } while (time_before(jiffies, stop)); + + return burst_count; +} + +/* + * WPCT301/NPCT501 SINT# supports only dataAvail + * any call to this function which is not waiting for dataAvail will + * set queue to NULL to avoid waiting for interrupt + */ +static bool i2c_nuvoton_check_status(struct tpm_chip *chip, u8 mask, u8 value) +{ + u8 status = i2c_nuvoton_read_status(chip); + return (status != TPM_STS_ERR_VAL) && ((status & mask) == value); +} + +static int i2c_nuvoton_wait_for_stat(struct tpm_chip *chip, u8 mask, u8 value, + u32 timeout, wait_queue_head_t *queue) +{ + if (chip->vendor.irq && queue) { + s32 rc; + struct priv_data *priv = chip->vendor.priv; + unsigned int cur_intrs = priv->intrs; + + enable_irq(chip->vendor.irq); + rc = wait_event_interruptible_timeout(*queue, + cur_intrs != priv->intrs, + timeout); + if (rc > 0) + return 0; + /* At this point we know that the SINT pin is asserted, so we + * do not need to do i2c_nuvoton_check_status */ + } else { + unsigned long ten_msec, stop; + bool status_valid; + + /* check current status */ + status_valid = i2c_nuvoton_check_status(chip, mask, value); + if (status_valid) + return 0; + + /* use polling to wait for the event */ + ten_msec = jiffies + msecs_to_jiffies(TPM_I2C_RETRY_DELAY_LONG); + stop = jiffies + timeout; + do { + if (time_before(jiffies, ten_msec)) + msleep(TPM_I2C_RETRY_DELAY_SHORT); + else + msleep(TPM_I2C_RETRY_DELAY_LONG); + status_valid = i2c_nuvoton_check_status(chip, mask, + value); + if (status_valid) + return 0; + } while (time_before(jiffies, stop)); + } + dev_err(chip->pdev, "%s(%02x, %02x) -> timeout\n", __func__, mask, + value); + return -ETIMEDOUT; +} + +/* wait for dataAvail field to be set in the TPM_STS register */ +static int i2c_nuvoton_wait_for_data_avail(struct tpm_chip *chip, u32 timeout, + wait_queue_head_t *queue) +{ + return i2c_nuvoton_wait_for_stat(chip, + TPM_STS_DATA_AVAIL | TPM_STS_VALID, + TPM_STS_DATA_AVAIL | TPM_STS_VALID, + timeout, queue); +} + +/* Read @count bytes into @buf from TPM_RD_FIFO register */ +static int i2c_nuvoton_recv_data(struct i2c_client *client, + struct tpm_chip *chip, u8 *buf, size_t count) +{ + s32 rc; + int burst_count, bytes2read, size = 0; + + while (size < count && + i2c_nuvoton_wait_for_data_avail(chip, + chip->vendor.timeout_c, + &chip->vendor.read_queue) == 0) { + burst_count = i2c_nuvoton_get_burstcount(client, chip); + if (burst_count < 0) { + dev_err(chip->pdev, + "%s() fail to read burstCount=%d\n", __func__, + burst_count); + return -EIO; + } + bytes2read = min_t(size_t, burst_count, count - size); + rc = i2c_nuvoton_read_buf(client, TPM_DATA_FIFO_R, + bytes2read, &buf[size]); + if (rc < 0) { + dev_err(chip->pdev, + "%s() fail on i2c_nuvoton_read_buf()=%d\n", + __func__, rc); + return -EIO; + } + dev_dbg(chip->pdev, "%s(%d):", __func__, bytes2read); + size += bytes2read; + } + + return size; +} + +/* Read TPM command results */ +static int i2c_nuvoton_recv(struct tpm_chip *chip, u8 *buf, size_t count) +{ + struct device *dev = chip->pdev; + struct i2c_client *client = to_i2c_client(dev); + s32 rc; + int expected, status, burst_count, retries, size = 0; + + if (count < TPM_HEADER_SIZE) { + i2c_nuvoton_ready(chip); /* return to idle */ + dev_err(dev, "%s() count < header size\n", __func__); + return -EIO; + } + for (retries = 0; retries < TPM_RETRY; retries++) { + if (retries > 0) { + /* if this is not the first trial, set responseRetry */ + i2c_nuvoton_write_status(client, + TPM_STS_RESPONSE_RETRY); + } + /* + * read first available (> 10 bytes), including: + * tag, paramsize, and result + */ + status = i2c_nuvoton_wait_for_data_avail( + chip, chip->vendor.timeout_c, &chip->vendor.read_queue); + if (status != 0) { + dev_err(dev, "%s() timeout on dataAvail\n", __func__); + size = -ETIMEDOUT; + continue; + } + burst_count = i2c_nuvoton_get_burstcount(client, chip); + if (burst_count < 0) { + dev_err(dev, "%s() fail to get burstCount\n", __func__); + size = -EIO; + continue; + } + size = i2c_nuvoton_recv_data(client, chip, buf, + burst_count); + if (size < TPM_HEADER_SIZE) { + dev_err(dev, "%s() fail to read header\n", __func__); + size = -EIO; + continue; + } + /* + * convert number of expected bytes field from big endian 32 bit + * to machine native + */ + expected = be32_to_cpu(*(__be32 *) (buf + 2)); + if (expected > count) { + dev_err(dev, "%s() expected > count\n", __func__); + size = -EIO; + continue; + } + rc = i2c_nuvoton_recv_data(client, chip, &buf[size], + expected - size); + size += rc; + if (rc < 0 || size < expected) { + dev_err(dev, "%s() fail to read remainder of result\n", + __func__); + size = -EIO; + continue; + } + if (i2c_nuvoton_wait_for_stat( + chip, TPM_STS_VALID | TPM_STS_DATA_AVAIL, + TPM_STS_VALID, chip->vendor.timeout_c, + NULL)) { + dev_err(dev, "%s() error left over data\n", __func__); + size = -ETIMEDOUT; + continue; + } + break; + } + i2c_nuvoton_ready(chip); + dev_dbg(chip->pdev, "%s() -> %d\n", __func__, size); + return size; +} + +/* + * Send TPM command. + * + * 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_nuvoton_send(struct tpm_chip *chip, u8 *buf, size_t len) +{ + struct device *dev = chip->pdev; + struct i2c_client *client = to_i2c_client(dev); + u32 ordinal; + size_t count = 0; + int burst_count, bytes2write, retries, rc = -EIO; + + for (retries = 0; retries < TPM_RETRY; retries++) { + i2c_nuvoton_ready(chip); + if (i2c_nuvoton_wait_for_stat(chip, TPM_STS_COMMAND_READY, + TPM_STS_COMMAND_READY, + chip->vendor.timeout_b, NULL)) { + dev_err(dev, "%s() timeout on commandReady\n", + __func__); + rc = -EIO; + continue; + } + rc = 0; + while (count < len - 1) { + burst_count = i2c_nuvoton_get_burstcount(client, + chip); + if (burst_count < 0) { + dev_err(dev, "%s() fail get burstCount\n", + __func__); + rc = -EIO; + break; + } + bytes2write = min_t(size_t, burst_count, + len - 1 - count); + rc = i2c_nuvoton_write_buf(client, TPM_DATA_FIFO_W, + bytes2write, &buf[count]); + if (rc < 0) { + dev_err(dev, "%s() fail i2cWriteBuf\n", + __func__); + break; + } + dev_dbg(dev, "%s(%d):", __func__, bytes2write); + count += bytes2write; + rc = i2c_nuvoton_wait_for_stat(chip, + TPM_STS_VALID | + TPM_STS_EXPECT, + TPM_STS_VALID | + TPM_STS_EXPECT, + chip->vendor.timeout_c, + NULL); + if (rc < 0) { + dev_err(dev, "%s() timeout on Expect\n", + __func__); + rc = -ETIMEDOUT; + break; + } + } + if (rc < 0) + continue; + + /* write last byte */ + rc = i2c_nuvoton_write_buf(client, TPM_DATA_FIFO_W, 1, + &buf[count]); + if (rc < 0) { + dev_err(dev, "%s() fail to write last byte\n", + __func__); + rc = -EIO; + continue; + } + dev_dbg(dev, "%s(last): %02x", __func__, buf[count]); + rc = i2c_nuvoton_wait_for_stat(chip, + TPM_STS_VALID | TPM_STS_EXPECT, + TPM_STS_VALID, + chip->vendor.timeout_c, NULL); + if (rc) { + dev_err(dev, "%s() timeout on Expect to clear\n", + __func__); + rc = -ETIMEDOUT; + continue; + } + break; + } + if (rc < 0) { + /* retries == TPM_RETRY */ + i2c_nuvoton_ready(chip); + return rc; + } + /* execute the TPM command */ + rc = i2c_nuvoton_write_status(client, TPM_STS_GO); + if (rc < 0) { + dev_err(dev, "%s() fail to write Go\n", __func__); + i2c_nuvoton_ready(chip); + return rc; + } + ordinal = be32_to_cpu(*((__be32 *) (buf + 6))); + rc = i2c_nuvoton_wait_for_data_avail(chip, + tpm_calc_ordinal_duration(chip, + ordinal), + &chip->vendor.read_queue); + if (rc) { + dev_err(dev, "%s() timeout command duration\n", __func__); + i2c_nuvoton_ready(chip); + return rc; + } + + dev_dbg(dev, "%s() -> %zd\n", __func__, len); + return len; +} + +static bool i2c_nuvoton_req_canceled(struct tpm_chip *chip, u8 status) +{ + return (status == TPM_STS_COMMAND_READY); +} + +static const struct tpm_class_ops tpm_i2c = { + .status = i2c_nuvoton_read_status, + .recv = i2c_nuvoton_recv, + .send = i2c_nuvoton_send, + .cancel = i2c_nuvoton_ready, + .req_complete_mask = TPM_STS_DATA_AVAIL | TPM_STS_VALID, + .req_complete_val = TPM_STS_DATA_AVAIL | TPM_STS_VALID, + .req_canceled = i2c_nuvoton_req_canceled, +}; + +/* 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. */ +static irqreturn_t i2c_nuvoton_int_handler(int dummy, void *dev_id) +{ + struct tpm_chip *chip = dev_id; + struct priv_data *priv = chip->vendor.priv; + + priv->intrs++; + wake_up(&chip->vendor.read_queue); + disable_irq_nosync(chip->vendor.irq); + return IRQ_HANDLED; +} + +static int get_vid(struct i2c_client *client, u32 *res) +{ + static const u8 vid_did_rid_value[] = { 0x50, 0x10, 0xfe }; + u32 temp; + s32 rc; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + rc = i2c_nuvoton_read_buf(client, TPM_VID_DID_RID, 4, (u8 *)&temp); + if (rc < 0) + return rc; + + /* check WPCT301 values - ignore RID */ + if (memcmp(&temp, vid_did_rid_value, sizeof(vid_did_rid_value))) { + /* + * f/w rev 2.81 has an issue where the VID_DID_RID is not + * reporting the right value. so give it another chance at + * offset 0x20 (FIFO_W). + */ + rc = i2c_nuvoton_read_buf(client, TPM_DATA_FIFO_W, 4, + (u8 *) (&temp)); + if (rc < 0) + return rc; + + /* check WPCT301 values - ignore RID */ + if (memcmp(&temp, vid_did_rid_value, + sizeof(vid_did_rid_value))) + return -ENODEV; + } + + *res = temp; + return 0; +} + +static int i2c_nuvoton_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc; + struct tpm_chip *chip; + struct device *dev = &client->dev; + u32 vid = 0; + + rc = get_vid(client, &vid); + if (rc) + return rc; + + dev_info(dev, "VID: %04X DID: %02X RID: %02X\n", (u16) vid, + (u8) (vid >> 16), (u8) (vid >> 24)); + + chip = tpmm_chip_alloc(dev, &tpm_i2c); + if (IS_ERR(chip)) + return PTR_ERR(chip); + + chip->vendor.priv = devm_kzalloc(dev, sizeof(struct priv_data), + GFP_KERNEL); + if (!chip->vendor.priv) + return -ENOMEM; + + init_waitqueue_head(&chip->vendor.read_queue); + init_waitqueue_head(&chip->vendor.int_queue); + + /* Default timeouts */ + chip->vendor.timeout_a = msecs_to_jiffies(TPM_I2C_SHORT_TIMEOUT); + chip->vendor.timeout_b = msecs_to_jiffies(TPM_I2C_LONG_TIMEOUT); + chip->vendor.timeout_c = msecs_to_jiffies(TPM_I2C_SHORT_TIMEOUT); + chip->vendor.timeout_d = msecs_to_jiffies(TPM_I2C_SHORT_TIMEOUT); + + /* + * I2C intfcaps (interrupt capabilitieis) in the chip are hard coded to: + * TPM_INTF_INT_LEVEL_LOW | TPM_INTF_DATA_AVAIL_INT + * The IRQ should be set in the i2c_board_info (which is done + * automatically in of_i2c_register_devices, for device tree users */ + chip->vendor.irq = client->irq; + + if (chip->vendor.irq) { + dev_dbg(dev, "%s() chip-vendor.irq\n", __func__); + rc = devm_request_irq(dev, chip->vendor.irq, + i2c_nuvoton_int_handler, + IRQF_TRIGGER_LOW, + chip->devname, + chip); + if (rc) { + dev_err(dev, "%s() Unable to request irq: %d for use\n", + __func__, chip->vendor.irq); + chip->vendor.irq = 0; + } else { + /* Clear any pending interrupt */ + i2c_nuvoton_ready(chip); + /* - wait for TPM_STS==0xA0 (stsValid, commandReady) */ + rc = i2c_nuvoton_wait_for_stat(chip, + TPM_STS_COMMAND_READY, + TPM_STS_COMMAND_READY, + chip->vendor.timeout_b, + NULL); + if (rc == 0) { + /* + * TIS is in ready state + * write dummy byte to enter reception state + * TPM_DATA_FIFO_W <- rc (0) + */ + rc = i2c_nuvoton_write_buf(client, + TPM_DATA_FIFO_W, + 1, (u8 *) (&rc)); + if (rc < 0) + return rc; + /* TPM_STS <- 0x40 (commandReady) */ + i2c_nuvoton_ready(chip); + } else { + /* + * timeout_b reached - command was + * aborted. TIS should now be in idle state - + * only TPM_STS_VALID should be set + */ + if (i2c_nuvoton_read_status(chip) != + TPM_STS_VALID) + return -EIO; + } + } + } + + if (tpm_get_timeouts(chip)) + return -ENODEV; + + if (tpm_do_selftest(chip)) + return -ENODEV; + + return tpm_chip_register(chip); +} + +static int i2c_nuvoton_remove(struct i2c_client *client) +{ + struct device *dev = &(client->dev); + struct tpm_chip *chip = dev_get_drvdata(dev); + tpm_chip_unregister(chip); + return 0; +} + +static const struct i2c_device_id i2c_nuvoton_id[] = { + {I2C_DRIVER_NAME, 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, i2c_nuvoton_id); + +#ifdef CONFIG_OF +static const struct of_device_id i2c_nuvoton_of_match[] = { + {.compatible = "nuvoton,npct501"}, + {.compatible = "winbond,wpct301"}, + {}, +}; +MODULE_DEVICE_TABLE(of, i2c_nuvoton_of_match); +#endif + +static SIMPLE_DEV_PM_OPS(i2c_nuvoton_pm_ops, tpm_pm_suspend, tpm_pm_resume); + +static struct i2c_driver i2c_nuvoton_driver = { + .id_table = i2c_nuvoton_id, + .probe = i2c_nuvoton_probe, + .remove = i2c_nuvoton_remove, + .driver = { + .name = I2C_DRIVER_NAME, + .owner = THIS_MODULE, + .pm = &i2c_nuvoton_pm_ops, + .of_match_table = of_match_ptr(i2c_nuvoton_of_match), + }, +}; + +module_i2c_driver(i2c_nuvoton_driver); + +MODULE_AUTHOR("Dan Morav (dan.morav@nuvoton.com)"); +MODULE_DESCRIPTION("Nuvoton TPM I2C Driver"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/char/tpm/tpm_ibmvtpm.c b/kernel/drivers/char/tpm/tpm_ibmvtpm.c new file mode 100644 index 000000000..42ffa5e7a --- /dev/null +++ b/kernel/drivers/char/tpm/tpm_ibmvtpm.c @@ -0,0 +1,699 @@ +/* + * Copyright (C) 2012 IBM Corporation + * + * Author: Ashley Lai <ashleydlai@gmail.com> + * + * Maintained by: <tpmdd-devel@lists.sourceforge.net> + * + * Device driver for TCG/TCPA TPM (trusted platform module). + * Specifications at www.trustedcomputinggroup.org + * + * 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, version 2 of the + * License. + * + */ + +#include <linux/dma-mapping.h> +#include <linux/dmapool.h> +#include <linux/slab.h> +#include <asm/vio.h> +#include <asm/irq.h> +#include <linux/types.h> +#include <linux/list.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/wait.h> +#include <asm/prom.h> + +#include "tpm.h" +#include "tpm_ibmvtpm.h" + +static const char tpm_ibmvtpm_driver_name[] = "tpm_ibmvtpm"; + +static struct vio_device_id tpm_ibmvtpm_device_table[] = { + { "IBM,vtpm", "IBM,vtpm"}, + { "", "" } +}; +MODULE_DEVICE_TABLE(vio, tpm_ibmvtpm_device_table); + +/** + * ibmvtpm_send_crq - Send a CRQ request + * @vdev: vio device struct + * @w1: first word + * @w2: second word + * + * Return value: + * 0 -Sucess + * Non-zero - Failure + */ +static int ibmvtpm_send_crq(struct vio_dev *vdev, u64 w1, u64 w2) +{ + return plpar_hcall_norets(H_SEND_CRQ, vdev->unit_address, w1, w2); +} + +/** + * ibmvtpm_get_data - Retrieve ibm vtpm data + * @dev: device struct + * + * Return value: + * vtpm device struct + */ +static struct ibmvtpm_dev *ibmvtpm_get_data(const struct device *dev) +{ + struct tpm_chip *chip = dev_get_drvdata(dev); + if (chip) + return (struct ibmvtpm_dev *)TPM_VPRIV(chip); + return NULL; +} + +/** + * tpm_ibmvtpm_recv - Receive data after send + * @chip: tpm chip struct + * @buf: buffer to read + * count: size of buffer + * + * Return value: + * Number of bytes read + */ +static int tpm_ibmvtpm_recv(struct tpm_chip *chip, u8 *buf, size_t count) +{ + struct ibmvtpm_dev *ibmvtpm; + u16 len; + int sig; + + ibmvtpm = (struct ibmvtpm_dev *)TPM_VPRIV(chip); + + if (!ibmvtpm->rtce_buf) { + dev_err(ibmvtpm->dev, "ibmvtpm device is not ready\n"); + return 0; + } + + sig = wait_event_interruptible(ibmvtpm->wq, ibmvtpm->res_len != 0); + if (sig) + return -EINTR; + + len = ibmvtpm->res_len; + + if (count < len) { + dev_err(ibmvtpm->dev, + "Invalid size in recv: count=%zd, crq_size=%d\n", + count, len); + return -EIO; + } + + spin_lock(&ibmvtpm->rtce_lock); + memcpy((void *)buf, (void *)ibmvtpm->rtce_buf, len); + memset(ibmvtpm->rtce_buf, 0, len); + ibmvtpm->res_len = 0; + spin_unlock(&ibmvtpm->rtce_lock); + return len; +} + +/** + * tpm_ibmvtpm_send - Send tpm request + * @chip: tpm chip struct + * @buf: buffer contains data to send + * count: size of buffer + * + * Return value: + * Number of bytes sent + */ +static int tpm_ibmvtpm_send(struct tpm_chip *chip, u8 *buf, size_t count) +{ + struct ibmvtpm_dev *ibmvtpm; + struct ibmvtpm_crq crq; + __be64 *word = (__be64 *)&crq; + int rc; + + ibmvtpm = (struct ibmvtpm_dev *)TPM_VPRIV(chip); + + if (!ibmvtpm->rtce_buf) { + dev_err(ibmvtpm->dev, "ibmvtpm device is not ready\n"); + return 0; + } + + if (count > ibmvtpm->rtce_size) { + dev_err(ibmvtpm->dev, + "Invalid size in send: count=%zd, rtce_size=%d\n", + count, ibmvtpm->rtce_size); + return -EIO; + } + + spin_lock(&ibmvtpm->rtce_lock); + memcpy((void *)ibmvtpm->rtce_buf, (void *)buf, count); + crq.valid = (u8)IBMVTPM_VALID_CMD; + crq.msg = (u8)VTPM_TPM_COMMAND; + crq.len = cpu_to_be16(count); + crq.data = cpu_to_be32(ibmvtpm->rtce_dma_handle); + + rc = ibmvtpm_send_crq(ibmvtpm->vdev, be64_to_cpu(word[0]), + be64_to_cpu(word[1])); + if (rc != H_SUCCESS) { + dev_err(ibmvtpm->dev, "tpm_ibmvtpm_send failed rc=%d\n", rc); + rc = 0; + } else + rc = count; + + spin_unlock(&ibmvtpm->rtce_lock); + return rc; +} + +static void tpm_ibmvtpm_cancel(struct tpm_chip *chip) +{ + return; +} + +static u8 tpm_ibmvtpm_status(struct tpm_chip *chip) +{ + return 0; +} + +/** + * ibmvtpm_crq_get_rtce_size - Send a CRQ request to get rtce size + * @ibmvtpm: vtpm device struct + * + * Return value: + * 0 - Success + * Non-zero - Failure + */ +static int ibmvtpm_crq_get_rtce_size(struct ibmvtpm_dev *ibmvtpm) +{ + struct ibmvtpm_crq crq; + u64 *buf = (u64 *) &crq; + int rc; + + crq.valid = (u8)IBMVTPM_VALID_CMD; + crq.msg = (u8)VTPM_GET_RTCE_BUFFER_SIZE; + + rc = ibmvtpm_send_crq(ibmvtpm->vdev, cpu_to_be64(buf[0]), + cpu_to_be64(buf[1])); + if (rc != H_SUCCESS) + dev_err(ibmvtpm->dev, + "ibmvtpm_crq_get_rtce_size failed rc=%d\n", rc); + + return rc; +} + +/** + * ibmvtpm_crq_get_version - Send a CRQ request to get vtpm version + * - Note that this is vtpm version and not tpm version + * @ibmvtpm: vtpm device struct + * + * Return value: + * 0 - Success + * Non-zero - Failure + */ +static int ibmvtpm_crq_get_version(struct ibmvtpm_dev *ibmvtpm) +{ + struct ibmvtpm_crq crq; + u64 *buf = (u64 *) &crq; + int rc; + + crq.valid = (u8)IBMVTPM_VALID_CMD; + crq.msg = (u8)VTPM_GET_VERSION; + + rc = ibmvtpm_send_crq(ibmvtpm->vdev, cpu_to_be64(buf[0]), + cpu_to_be64(buf[1])); + if (rc != H_SUCCESS) + dev_err(ibmvtpm->dev, + "ibmvtpm_crq_get_version failed rc=%d\n", rc); + + return rc; +} + +/** + * ibmvtpm_crq_send_init_complete - Send a CRQ initialize complete message + * @ibmvtpm: vtpm device struct + * + * Return value: + * 0 - Success + * Non-zero - Failure + */ +static int ibmvtpm_crq_send_init_complete(struct ibmvtpm_dev *ibmvtpm) +{ + int rc; + + rc = ibmvtpm_send_crq(ibmvtpm->vdev, INIT_CRQ_COMP_CMD, 0); + if (rc != H_SUCCESS) + dev_err(ibmvtpm->dev, + "ibmvtpm_crq_send_init_complete failed rc=%d\n", rc); + + return rc; +} + +/** + * ibmvtpm_crq_send_init - Send a CRQ initialize message + * @ibmvtpm: vtpm device struct + * + * Return value: + * 0 - Success + * Non-zero - Failure + */ +static int ibmvtpm_crq_send_init(struct ibmvtpm_dev *ibmvtpm) +{ + int rc; + + rc = ibmvtpm_send_crq(ibmvtpm->vdev, INIT_CRQ_CMD, 0); + if (rc != H_SUCCESS) + dev_err(ibmvtpm->dev, + "ibmvtpm_crq_send_init failed rc=%d\n", rc); + + return rc; +} + +/** + * tpm_ibmvtpm_remove - ibm vtpm remove entry point + * @vdev: vio device struct + * + * Return value: + * 0 + */ +static int tpm_ibmvtpm_remove(struct vio_dev *vdev) +{ + struct ibmvtpm_dev *ibmvtpm = ibmvtpm_get_data(&vdev->dev); + struct tpm_chip *chip = dev_get_drvdata(ibmvtpm->dev); + int rc = 0; + + tpm_chip_unregister(chip); + + free_irq(vdev->irq, ibmvtpm); + + do { + if (rc) + msleep(100); + rc = plpar_hcall_norets(H_FREE_CRQ, vdev->unit_address); + } while (rc == H_BUSY || H_IS_LONG_BUSY(rc)); + + dma_unmap_single(ibmvtpm->dev, ibmvtpm->crq_dma_handle, + CRQ_RES_BUF_SIZE, DMA_BIDIRECTIONAL); + free_page((unsigned long)ibmvtpm->crq_queue.crq_addr); + + if (ibmvtpm->rtce_buf) { + dma_unmap_single(ibmvtpm->dev, ibmvtpm->rtce_dma_handle, + ibmvtpm->rtce_size, DMA_BIDIRECTIONAL); + kfree(ibmvtpm->rtce_buf); + } + + kfree(ibmvtpm); + + return 0; +} + +/** + * tpm_ibmvtpm_get_desired_dma - Get DMA size needed by this driver + * @vdev: vio device struct + * + * Return value: + * Number of bytes the driver needs to DMA map + */ +static unsigned long tpm_ibmvtpm_get_desired_dma(struct vio_dev *vdev) +{ + struct ibmvtpm_dev *ibmvtpm = ibmvtpm_get_data(&vdev->dev); + + /* ibmvtpm initializes at probe time, so the data we are + * asking for may not be set yet. Estimate that 4K required + * for TCE-mapped buffer in addition to CRQ. + */ + if (!ibmvtpm) + return CRQ_RES_BUF_SIZE + PAGE_SIZE; + + return CRQ_RES_BUF_SIZE + ibmvtpm->rtce_size; +} + +/** + * tpm_ibmvtpm_suspend - Suspend + * @dev: device struct + * + * Return value: + * 0 + */ +static int tpm_ibmvtpm_suspend(struct device *dev) +{ + struct ibmvtpm_dev *ibmvtpm = ibmvtpm_get_data(dev); + struct ibmvtpm_crq crq; + u64 *buf = (u64 *) &crq; + int rc = 0; + + crq.valid = (u8)IBMVTPM_VALID_CMD; + crq.msg = (u8)VTPM_PREPARE_TO_SUSPEND; + + rc = ibmvtpm_send_crq(ibmvtpm->vdev, cpu_to_be64(buf[0]), + cpu_to_be64(buf[1])); + if (rc != H_SUCCESS) + dev_err(ibmvtpm->dev, + "tpm_ibmvtpm_suspend failed rc=%d\n", rc); + + return rc; +} + +/** + * ibmvtpm_reset_crq - Reset CRQ + * @ibmvtpm: ibm vtpm struct + * + * Return value: + * 0 - Success + * Non-zero - Failure + */ +static int ibmvtpm_reset_crq(struct ibmvtpm_dev *ibmvtpm) +{ + int rc = 0; + + do { + if (rc) + msleep(100); + rc = plpar_hcall_norets(H_FREE_CRQ, + ibmvtpm->vdev->unit_address); + } while (rc == H_BUSY || H_IS_LONG_BUSY(rc)); + + memset(ibmvtpm->crq_queue.crq_addr, 0, CRQ_RES_BUF_SIZE); + ibmvtpm->crq_queue.index = 0; + + return plpar_hcall_norets(H_REG_CRQ, ibmvtpm->vdev->unit_address, + ibmvtpm->crq_dma_handle, CRQ_RES_BUF_SIZE); +} + +/** + * tpm_ibmvtpm_resume - Resume from suspend + * @dev: device struct + * + * Return value: + * 0 + */ +static int tpm_ibmvtpm_resume(struct device *dev) +{ + struct ibmvtpm_dev *ibmvtpm = ibmvtpm_get_data(dev); + int rc = 0; + + do { + if (rc) + msleep(100); + rc = plpar_hcall_norets(H_ENABLE_CRQ, + ibmvtpm->vdev->unit_address); + } while (rc == H_IN_PROGRESS || rc == H_BUSY || H_IS_LONG_BUSY(rc)); + + if (rc) { + dev_err(dev, "Error enabling ibmvtpm rc=%d\n", rc); + return rc; + } + + rc = vio_enable_interrupts(ibmvtpm->vdev); + if (rc) { + dev_err(dev, "Error vio_enable_interrupts rc=%d\n", rc); + return rc; + } + + rc = ibmvtpm_crq_send_init(ibmvtpm); + if (rc) + dev_err(dev, "Error send_init rc=%d\n", rc); + + return rc; +} + +static bool tpm_ibmvtpm_req_canceled(struct tpm_chip *chip, u8 status) +{ + return (status == 0); +} + +static const struct tpm_class_ops tpm_ibmvtpm = { + .recv = tpm_ibmvtpm_recv, + .send = tpm_ibmvtpm_send, + .cancel = tpm_ibmvtpm_cancel, + .status = tpm_ibmvtpm_status, + .req_complete_mask = 0, + .req_complete_val = 0, + .req_canceled = tpm_ibmvtpm_req_canceled, +}; + +static const struct dev_pm_ops tpm_ibmvtpm_pm_ops = { + .suspend = tpm_ibmvtpm_suspend, + .resume = tpm_ibmvtpm_resume, +}; + +/** + * ibmvtpm_crq_get_next - Get next responded crq + * @ibmvtpm vtpm device struct + * + * Return value: + * vtpm crq pointer + */ +static struct ibmvtpm_crq *ibmvtpm_crq_get_next(struct ibmvtpm_dev *ibmvtpm) +{ + struct ibmvtpm_crq_queue *crq_q = &ibmvtpm->crq_queue; + struct ibmvtpm_crq *crq = &crq_q->crq_addr[crq_q->index]; + + if (crq->valid & VTPM_MSG_RES) { + if (++crq_q->index == crq_q->num_entry) + crq_q->index = 0; + smp_rmb(); + } else + crq = NULL; + return crq; +} + +/** + * ibmvtpm_crq_process - Process responded crq + * @crq crq to be processed + * @ibmvtpm vtpm device struct + * + * Return value: + * Nothing + */ +static void ibmvtpm_crq_process(struct ibmvtpm_crq *crq, + struct ibmvtpm_dev *ibmvtpm) +{ + int rc = 0; + + switch (crq->valid) { + case VALID_INIT_CRQ: + switch (crq->msg) { + case INIT_CRQ_RES: + dev_info(ibmvtpm->dev, "CRQ initialized\n"); + rc = ibmvtpm_crq_send_init_complete(ibmvtpm); + if (rc) + dev_err(ibmvtpm->dev, "Unable to send CRQ init complete rc=%d\n", rc); + return; + case INIT_CRQ_COMP_RES: + dev_info(ibmvtpm->dev, + "CRQ initialization completed\n"); + return; + default: + dev_err(ibmvtpm->dev, "Unknown crq message type: %d\n", crq->msg); + return; + } + case IBMVTPM_VALID_CMD: + switch (crq->msg) { + case VTPM_GET_RTCE_BUFFER_SIZE_RES: + if (be16_to_cpu(crq->len) <= 0) { + dev_err(ibmvtpm->dev, "Invalid rtce size\n"); + return; + } + ibmvtpm->rtce_size = be16_to_cpu(crq->len); + ibmvtpm->rtce_buf = kmalloc(ibmvtpm->rtce_size, + GFP_KERNEL); + if (!ibmvtpm->rtce_buf) { + dev_err(ibmvtpm->dev, "Failed to allocate memory for rtce buffer\n"); + return; + } + + ibmvtpm->rtce_dma_handle = dma_map_single(ibmvtpm->dev, + ibmvtpm->rtce_buf, ibmvtpm->rtce_size, + DMA_BIDIRECTIONAL); + + if (dma_mapping_error(ibmvtpm->dev, + ibmvtpm->rtce_dma_handle)) { + kfree(ibmvtpm->rtce_buf); + ibmvtpm->rtce_buf = NULL; + dev_err(ibmvtpm->dev, "Failed to dma map rtce buffer\n"); + } + + return; + case VTPM_GET_VERSION_RES: + ibmvtpm->vtpm_version = be32_to_cpu(crq->data); + return; + case VTPM_TPM_COMMAND_RES: + /* len of the data in rtce buffer */ + ibmvtpm->res_len = be16_to_cpu(crq->len); + wake_up_interruptible(&ibmvtpm->wq); + return; + default: + return; + } + } + return; +} + +/** + * ibmvtpm_interrupt - Interrupt handler + * @irq: irq number to handle + * @vtpm_instance: vtpm that received interrupt + * + * Returns: + * IRQ_HANDLED + **/ +static irqreturn_t ibmvtpm_interrupt(int irq, void *vtpm_instance) +{ + struct ibmvtpm_dev *ibmvtpm = (struct ibmvtpm_dev *) vtpm_instance; + struct ibmvtpm_crq *crq; + + /* while loop is needed for initial setup (get version and + * get rtce_size). There should be only one tpm request at any + * given time. + */ + while ((crq = ibmvtpm_crq_get_next(ibmvtpm)) != NULL) { + ibmvtpm_crq_process(crq, ibmvtpm); + crq->valid = 0; + smp_wmb(); + } + + return IRQ_HANDLED; +} + +/** + * tpm_ibmvtpm_probe - ibm vtpm initialize entry point + * @vio_dev: vio device struct + * @id: vio device id struct + * + * Return value: + * 0 - Success + * Non-zero - Failure + */ +static int tpm_ibmvtpm_probe(struct vio_dev *vio_dev, + const struct vio_device_id *id) +{ + struct ibmvtpm_dev *ibmvtpm; + struct device *dev = &vio_dev->dev; + struct ibmvtpm_crq_queue *crq_q; + struct tpm_chip *chip; + int rc = -ENOMEM, rc1; + + chip = tpmm_chip_alloc(dev, &tpm_ibmvtpm); + if (IS_ERR(chip)) + return PTR_ERR(chip); + + ibmvtpm = kzalloc(sizeof(struct ibmvtpm_dev), GFP_KERNEL); + if (!ibmvtpm) { + dev_err(dev, "kzalloc for ibmvtpm failed\n"); + goto cleanup; + } + + crq_q = &ibmvtpm->crq_queue; + crq_q->crq_addr = (struct ibmvtpm_crq *)get_zeroed_page(GFP_KERNEL); + if (!crq_q->crq_addr) { + dev_err(dev, "Unable to allocate memory for crq_addr\n"); + goto cleanup; + } + + crq_q->num_entry = CRQ_RES_BUF_SIZE / sizeof(*crq_q->crq_addr); + ibmvtpm->crq_dma_handle = dma_map_single(dev, crq_q->crq_addr, + CRQ_RES_BUF_SIZE, + DMA_BIDIRECTIONAL); + + if (dma_mapping_error(dev, ibmvtpm->crq_dma_handle)) { + dev_err(dev, "dma mapping failed\n"); + goto cleanup; + } + + rc = plpar_hcall_norets(H_REG_CRQ, vio_dev->unit_address, + ibmvtpm->crq_dma_handle, CRQ_RES_BUF_SIZE); + if (rc == H_RESOURCE) + rc = ibmvtpm_reset_crq(ibmvtpm); + + if (rc) { + dev_err(dev, "Unable to register CRQ rc=%d\n", rc); + goto reg_crq_cleanup; + } + + rc = request_irq(vio_dev->irq, ibmvtpm_interrupt, 0, + tpm_ibmvtpm_driver_name, ibmvtpm); + if (rc) { + dev_err(dev, "Error %d register irq 0x%x\n", rc, vio_dev->irq); + goto init_irq_cleanup; + } + + rc = vio_enable_interrupts(vio_dev); + if (rc) { + dev_err(dev, "Error %d enabling interrupts\n", rc); + goto init_irq_cleanup; + } + + init_waitqueue_head(&ibmvtpm->wq); + + crq_q->index = 0; + + ibmvtpm->dev = dev; + ibmvtpm->vdev = vio_dev; + TPM_VPRIV(chip) = (void *)ibmvtpm; + + spin_lock_init(&ibmvtpm->rtce_lock); + + rc = ibmvtpm_crq_send_init(ibmvtpm); + if (rc) + goto init_irq_cleanup; + + rc = ibmvtpm_crq_get_version(ibmvtpm); + if (rc) + goto init_irq_cleanup; + + rc = ibmvtpm_crq_get_rtce_size(ibmvtpm); + if (rc) + goto init_irq_cleanup; + + return tpm_chip_register(chip); +init_irq_cleanup: + do { + rc1 = plpar_hcall_norets(H_FREE_CRQ, vio_dev->unit_address); + } while (rc1 == H_BUSY || H_IS_LONG_BUSY(rc1)); +reg_crq_cleanup: + dma_unmap_single(dev, ibmvtpm->crq_dma_handle, CRQ_RES_BUF_SIZE, + DMA_BIDIRECTIONAL); +cleanup: + if (ibmvtpm) { + if (crq_q->crq_addr) + free_page((unsigned long)crq_q->crq_addr); + kfree(ibmvtpm); + } + + return rc; +} + +static struct vio_driver ibmvtpm_driver = { + .id_table = tpm_ibmvtpm_device_table, + .probe = tpm_ibmvtpm_probe, + .remove = tpm_ibmvtpm_remove, + .get_desired_dma = tpm_ibmvtpm_get_desired_dma, + .name = tpm_ibmvtpm_driver_name, + .pm = &tpm_ibmvtpm_pm_ops, +}; + +/** + * ibmvtpm_module_init - Initialize ibm vtpm module + * + * Return value: + * 0 -Success + * Non-zero - Failure + */ +static int __init ibmvtpm_module_init(void) +{ + return vio_register_driver(&ibmvtpm_driver); +} + +/** + * ibmvtpm_module_exit - Teardown ibm vtpm module + * + * Return value: + * Nothing + */ +static void __exit ibmvtpm_module_exit(void) +{ + vio_unregister_driver(&ibmvtpm_driver); +} + +module_init(ibmvtpm_module_init); +module_exit(ibmvtpm_module_exit); + +MODULE_AUTHOR("adlai@us.ibm.com"); +MODULE_DESCRIPTION("IBM vTPM Driver"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/char/tpm/tpm_ibmvtpm.h b/kernel/drivers/char/tpm/tpm_ibmvtpm.h new file mode 100644 index 000000000..6af928905 --- /dev/null +++ b/kernel/drivers/char/tpm/tpm_ibmvtpm.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2012 IBM Corporation + * + * Author: Ashley Lai <ashleydlai@gmail.com> + * + * Maintained by: <tpmdd-devel@lists.sourceforge.net> + * + * Device driver for TCG/TCPA TPM (trusted platform module). + * Specifications at www.trustedcomputinggroup.org + * + * 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, version 2 of the + * License. + * + */ + +#ifndef __TPM_IBMVTPM_H__ +#define __TPM_IBMVTPM_H__ + +/* vTPM Message Format 1 */ +struct ibmvtpm_crq { + u8 valid; + u8 msg; + __be16 len; + __be32 data; + __be64 reserved; +} __attribute__((packed, aligned(8))); + +struct ibmvtpm_crq_queue { + struct ibmvtpm_crq *crq_addr; + u32 index; + u32 num_entry; +}; + +struct ibmvtpm_dev { + struct device *dev; + struct vio_dev *vdev; + struct ibmvtpm_crq_queue crq_queue; + dma_addr_t crq_dma_handle; + u32 rtce_size; + void __iomem *rtce_buf; + dma_addr_t rtce_dma_handle; + spinlock_t rtce_lock; + wait_queue_head_t wq; + u16 res_len; + u32 vtpm_version; +}; + +#define CRQ_RES_BUF_SIZE PAGE_SIZE + +/* Initialize CRQ */ +#define INIT_CRQ_CMD 0xC001000000000000LL /* Init cmd */ +#define INIT_CRQ_COMP_CMD 0xC002000000000000LL /* Init complete cmd */ +#define INIT_CRQ_RES 0x01 /* Init respond */ +#define INIT_CRQ_COMP_RES 0x02 /* Init complete respond */ +#define VALID_INIT_CRQ 0xC0 /* Valid command for init crq */ + +/* vTPM CRQ response is the message type | 0x80 */ +#define VTPM_MSG_RES 0x80 +#define IBMVTPM_VALID_CMD 0x80 + +/* vTPM CRQ message types */ +#define VTPM_GET_VERSION 0x01 +#define VTPM_GET_VERSION_RES (0x01 | VTPM_MSG_RES) + +#define VTPM_TPM_COMMAND 0x02 +#define VTPM_TPM_COMMAND_RES (0x02 | VTPM_MSG_RES) + +#define VTPM_GET_RTCE_BUFFER_SIZE 0x03 +#define VTPM_GET_RTCE_BUFFER_SIZE_RES (0x03 | VTPM_MSG_RES) + +#define VTPM_PREPARE_TO_SUSPEND 0x04 +#define VTPM_PREPARE_TO_SUSPEND_RES (0x04 | VTPM_MSG_RES) + +#endif diff --git a/kernel/drivers/char/tpm/tpm_infineon.c b/kernel/drivers/char/tpm/tpm_infineon.c new file mode 100644 index 000000000..6c488e635 --- /dev/null +++ b/kernel/drivers/char/tpm/tpm_infineon.c @@ -0,0 +1,629 @@ +/* + * Description: + * Device Driver for the Infineon Technologies + * SLD 9630 TT 1.1 and SLB 9635 TT 1.2 Trusted Platform Module + * Specifications at www.trustedcomputinggroup.org + * + * Copyright (C) 2005, Marcel Selhorst <tpmdd@selhorst.net> + * Sirrix AG - security technologies <tpmdd@sirrix.com> and + * Applied Data Security Group, Ruhr-University Bochum, Germany + * Project-Homepage: http://www.trust.rub.de/projects/linux-device-driver-infineon-tpm/ + * + * 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, version 2 of the + * License. + */ + +#include <linux/init.h> +#include <linux/pnp.h> +#include "tpm.h" + +/* Infineon specific definitions */ +/* maximum number of WTX-packages */ +#define TPM_MAX_WTX_PACKAGES 50 +/* msleep-Time for WTX-packages */ +#define TPM_WTX_MSLEEP_TIME 20 +/* msleep-Time --> Interval to check status register */ +#define TPM_MSLEEP_TIME 3 +/* gives number of max. msleep()-calls before throwing timeout */ +#define TPM_MAX_TRIES 5000 +#define TPM_INFINEON_DEV_VEN_VALUE 0x15D1 + +#define TPM_INF_IO_PORT 0x0 +#define TPM_INF_IO_MEM 0x1 + +#define TPM_INF_ADDR 0x0 +#define TPM_INF_DATA 0x1 + +struct tpm_inf_dev { + int iotype; + + void __iomem *mem_base; /* MMIO ioremap'd addr */ + unsigned long map_base; /* phys MMIO base */ + unsigned long map_size; /* MMIO region size */ + unsigned int index_off; /* index register offset */ + + unsigned int data_regs; /* Data registers */ + unsigned int data_size; + + unsigned int config_port; /* IO Port config index reg */ + unsigned int config_size; +}; + +static struct tpm_inf_dev tpm_dev; + +static inline void tpm_data_out(unsigned char data, unsigned char offset) +{ + if (tpm_dev.iotype == TPM_INF_IO_PORT) + outb(data, tpm_dev.data_regs + offset); + else + writeb(data, tpm_dev.mem_base + tpm_dev.data_regs + offset); +} + +static inline unsigned char tpm_data_in(unsigned char offset) +{ + if (tpm_dev.iotype == TPM_INF_IO_PORT) + return inb(tpm_dev.data_regs + offset); + else + return readb(tpm_dev.mem_base + tpm_dev.data_regs + offset); +} + +static inline void tpm_config_out(unsigned char data, unsigned char offset) +{ + if (tpm_dev.iotype == TPM_INF_IO_PORT) + outb(data, tpm_dev.config_port + offset); + else + writeb(data, tpm_dev.mem_base + tpm_dev.index_off + offset); +} + +static inline unsigned char tpm_config_in(unsigned char offset) +{ + if (tpm_dev.iotype == TPM_INF_IO_PORT) + return inb(tpm_dev.config_port + offset); + else + return readb(tpm_dev.mem_base + tpm_dev.index_off + offset); +} + +/* TPM header definitions */ +enum infineon_tpm_header { + TPM_VL_VER = 0x01, + TPM_VL_CHANNEL_CONTROL = 0x07, + TPM_VL_CHANNEL_PERSONALISATION = 0x0A, + TPM_VL_CHANNEL_TPM = 0x0B, + TPM_VL_CONTROL = 0x00, + TPM_INF_NAK = 0x15, + TPM_CTRL_WTX = 0x10, + TPM_CTRL_WTX_ABORT = 0x18, + TPM_CTRL_WTX_ABORT_ACK = 0x18, + TPM_CTRL_ERROR = 0x20, + TPM_CTRL_CHAININGACK = 0x40, + TPM_CTRL_CHAINING = 0x80, + TPM_CTRL_DATA = 0x04, + TPM_CTRL_DATA_CHA = 0x84, + TPM_CTRL_DATA_CHA_ACK = 0xC4 +}; + +enum infineon_tpm_register { + WRFIFO = 0x00, + RDFIFO = 0x01, + STAT = 0x02, + CMD = 0x03 +}; + +enum infineon_tpm_command_bits { + CMD_DIS = 0x00, + CMD_LP = 0x01, + CMD_RES = 0x02, + CMD_IRQC = 0x06 +}; + +enum infineon_tpm_status_bits { + STAT_XFE = 0x00, + STAT_LPA = 0x01, + STAT_FOK = 0x02, + STAT_TOK = 0x03, + STAT_IRQA = 0x06, + STAT_RDA = 0x07 +}; + +/* some outgoing values */ +enum infineon_tpm_values { + CHIP_ID1 = 0x20, + CHIP_ID2 = 0x21, + TPM_DAR = 0x30, + RESET_LP_IRQC_DISABLE = 0x41, + ENABLE_REGISTER_PAIR = 0x55, + IOLIMH = 0x60, + IOLIML = 0x61, + DISABLE_REGISTER_PAIR = 0xAA, + IDVENL = 0xF1, + IDVENH = 0xF2, + IDPDL = 0xF3, + IDPDH = 0xF4 +}; + +static int number_of_wtx; + +static int empty_fifo(struct tpm_chip *chip, int clear_wrfifo) +{ + int status; + int check = 0; + int i; + + if (clear_wrfifo) { + for (i = 0; i < 4096; i++) { + status = tpm_data_in(WRFIFO); + if (status == 0xff) { + if (check == 5) + break; + else + check++; + } + } + } + /* Note: The values which are currently in the FIFO of the TPM + are thrown away since there is no usage for them. Usually, + this has nothing to say, since the TPM will give its answer + immediately or will be aborted anyway, so the data here is + usually garbage and useless. + We have to clean this, because the next communication with + the TPM would be rubbish, if there is still some old data + in the Read FIFO. + */ + i = 0; + do { + status = tpm_data_in(RDFIFO); + status = tpm_data_in(STAT); + i++; + if (i == TPM_MAX_TRIES) + return -EIO; + } while ((status & (1 << STAT_RDA)) != 0); + return 0; +} + +static int wait(struct tpm_chip *chip, int wait_for_bit) +{ + int status; + int i; + for (i = 0; i < TPM_MAX_TRIES; i++) { + status = tpm_data_in(STAT); + /* check the status-register if wait_for_bit is set */ + if (status & 1 << wait_for_bit) + break; + msleep(TPM_MSLEEP_TIME); + } + if (i == TPM_MAX_TRIES) { /* timeout occurs */ + if (wait_for_bit == STAT_XFE) + dev_err(chip->pdev, "Timeout in wait(STAT_XFE)\n"); + if (wait_for_bit == STAT_RDA) + dev_err(chip->pdev, "Timeout in wait(STAT_RDA)\n"); + return -EIO; + } + return 0; +}; + +static void wait_and_send(struct tpm_chip *chip, u8 sendbyte) +{ + wait(chip, STAT_XFE); + tpm_data_out(sendbyte, WRFIFO); +} + + /* Note: WTX means Waiting-Time-Extension. Whenever the TPM needs more + calculation time, it sends a WTX-package, which has to be acknowledged + or aborted. This usually occurs if you are hammering the TPM with key + creation. Set the maximum number of WTX-packages in the definitions + above, if the number is reached, the waiting-time will be denied + and the TPM command has to be resend. + */ + +static void tpm_wtx(struct tpm_chip *chip) +{ + number_of_wtx++; + dev_info(chip->pdev, "Granting WTX (%02d / %02d)\n", + number_of_wtx, TPM_MAX_WTX_PACKAGES); + wait_and_send(chip, TPM_VL_VER); + wait_and_send(chip, TPM_CTRL_WTX); + wait_and_send(chip, 0x00); + wait_and_send(chip, 0x00); + msleep(TPM_WTX_MSLEEP_TIME); +} + +static void tpm_wtx_abort(struct tpm_chip *chip) +{ + dev_info(chip->pdev, "Aborting WTX\n"); + wait_and_send(chip, TPM_VL_VER); + wait_and_send(chip, TPM_CTRL_WTX_ABORT); + wait_and_send(chip, 0x00); + wait_and_send(chip, 0x00); + number_of_wtx = 0; + msleep(TPM_WTX_MSLEEP_TIME); +} + +static int tpm_inf_recv(struct tpm_chip *chip, u8 * buf, size_t count) +{ + int i; + int ret; + u32 size = 0; + number_of_wtx = 0; + +recv_begin: + /* start receiving header */ + for (i = 0; i < 4; i++) { + ret = wait(chip, STAT_RDA); + if (ret) + return -EIO; + buf[i] = tpm_data_in(RDFIFO); + } + + if (buf[0] != TPM_VL_VER) { + dev_err(chip->pdev, + "Wrong transport protocol implementation!\n"); + return -EIO; + } + + if (buf[1] == TPM_CTRL_DATA) { + /* size of the data received */ + size = ((buf[2] << 8) | buf[3]); + + for (i = 0; i < size; i++) { + wait(chip, STAT_RDA); + buf[i] = tpm_data_in(RDFIFO); + } + + if ((size == 0x6D00) && (buf[1] == 0x80)) { + dev_err(chip->pdev, "Error handling on vendor layer!\n"); + return -EIO; + } + + for (i = 0; i < size; i++) + buf[i] = buf[i + 6]; + + size = size - 6; + return size; + } + + if (buf[1] == TPM_CTRL_WTX) { + dev_info(chip->pdev, "WTX-package received\n"); + if (number_of_wtx < TPM_MAX_WTX_PACKAGES) { + tpm_wtx(chip); + goto recv_begin; + } else { + tpm_wtx_abort(chip); + goto recv_begin; + } + } + + if (buf[1] == TPM_CTRL_WTX_ABORT_ACK) { + dev_info(chip->pdev, "WTX-abort acknowledged\n"); + return size; + } + + if (buf[1] == TPM_CTRL_ERROR) { + dev_err(chip->pdev, "ERROR-package received:\n"); + if (buf[4] == TPM_INF_NAK) + dev_err(chip->pdev, + "-> Negative acknowledgement" + " - retransmit command!\n"); + return -EIO; + } + return -EIO; +} + +static int tpm_inf_send(struct tpm_chip *chip, u8 * buf, size_t count) +{ + int i; + int ret; + u8 count_high, count_low, count_4, count_3, count_2, count_1; + + /* Disabling Reset, LP and IRQC */ + tpm_data_out(RESET_LP_IRQC_DISABLE, CMD); + + ret = empty_fifo(chip, 1); + if (ret) { + dev_err(chip->pdev, "Timeout while clearing FIFO\n"); + return -EIO; + } + + ret = wait(chip, STAT_XFE); + if (ret) + return -EIO; + + count_4 = (count & 0xff000000) >> 24; + count_3 = (count & 0x00ff0000) >> 16; + count_2 = (count & 0x0000ff00) >> 8; + count_1 = (count & 0x000000ff); + count_high = ((count + 6) & 0xffffff00) >> 8; + count_low = ((count + 6) & 0x000000ff); + + /* Sending Header */ + wait_and_send(chip, TPM_VL_VER); + wait_and_send(chip, TPM_CTRL_DATA); + wait_and_send(chip, count_high); + wait_and_send(chip, count_low); + + /* Sending Data Header */ + wait_and_send(chip, TPM_VL_VER); + wait_and_send(chip, TPM_VL_CHANNEL_TPM); + wait_and_send(chip, count_4); + wait_and_send(chip, count_3); + wait_and_send(chip, count_2); + wait_and_send(chip, count_1); + + /* Sending Data */ + for (i = 0; i < count; i++) { + wait_and_send(chip, buf[i]); + } + return count; +} + +static void tpm_inf_cancel(struct tpm_chip *chip) +{ + /* + Since we are using the legacy mode to communicate + with the TPM, we have no cancel functions, but have + a workaround for interrupting the TPM through WTX. + */ +} + +static u8 tpm_inf_status(struct tpm_chip *chip) +{ + return tpm_data_in(STAT); +} + +static const struct tpm_class_ops tpm_inf = { + .recv = tpm_inf_recv, + .send = tpm_inf_send, + .cancel = tpm_inf_cancel, + .status = tpm_inf_status, + .req_complete_mask = 0, + .req_complete_val = 0, +}; + +static const struct pnp_device_id tpm_inf_pnp_tbl[] = { + /* Infineon TPMs */ + {"IFX0101", 0}, + {"IFX0102", 0}, + {"", 0} +}; + +MODULE_DEVICE_TABLE(pnp, tpm_inf_pnp_tbl); + +static int tpm_inf_pnp_probe(struct pnp_dev *dev, + const struct pnp_device_id *dev_id) +{ + int rc = 0; + u8 iol, ioh; + int vendorid[2]; + int version[2]; + int productid[2]; + char chipname[20]; + struct tpm_chip *chip; + + /* read IO-ports through PnP */ + if (pnp_port_valid(dev, 0) && pnp_port_valid(dev, 1) && + !(pnp_port_flags(dev, 0) & IORESOURCE_DISABLED)) { + + tpm_dev.iotype = TPM_INF_IO_PORT; + + tpm_dev.config_port = pnp_port_start(dev, 0); + tpm_dev.config_size = pnp_port_len(dev, 0); + tpm_dev.data_regs = pnp_port_start(dev, 1); + tpm_dev.data_size = pnp_port_len(dev, 1); + if ((tpm_dev.data_size < 4) || (tpm_dev.config_size < 2)) { + rc = -EINVAL; + goto err_last; + } + dev_info(&dev->dev, "Found %s with ID %s\n", + dev->name, dev_id->id); + if (!((tpm_dev.data_regs >> 8) & 0xff)) { + rc = -EINVAL; + goto err_last; + } + /* publish my base address and request region */ + if (request_region(tpm_dev.data_regs, tpm_dev.data_size, + "tpm_infineon0") == NULL) { + rc = -EINVAL; + goto err_last; + } + if (request_region(tpm_dev.config_port, tpm_dev.config_size, + "tpm_infineon0") == NULL) { + release_region(tpm_dev.data_regs, tpm_dev.data_size); + rc = -EINVAL; + goto err_last; + } + } else if (pnp_mem_valid(dev, 0) && + !(pnp_mem_flags(dev, 0) & IORESOURCE_DISABLED)) { + + tpm_dev.iotype = TPM_INF_IO_MEM; + + tpm_dev.map_base = pnp_mem_start(dev, 0); + tpm_dev.map_size = pnp_mem_len(dev, 0); + + dev_info(&dev->dev, "Found %s with ID %s\n", + dev->name, dev_id->id); + + /* publish my base address and request region */ + if (request_mem_region(tpm_dev.map_base, tpm_dev.map_size, + "tpm_infineon0") == NULL) { + rc = -EINVAL; + goto err_last; + } + + tpm_dev.mem_base = ioremap(tpm_dev.map_base, tpm_dev.map_size); + if (tpm_dev.mem_base == NULL) { + release_mem_region(tpm_dev.map_base, tpm_dev.map_size); + rc = -EINVAL; + goto err_last; + } + + /* + * The only known MMIO based Infineon TPM system provides + * a single large mem region with the device config + * registers at the default TPM_ADDR. The data registers + * seem like they could be placed anywhere within the MMIO + * region, but lets just put them at zero offset. + */ + tpm_dev.index_off = TPM_ADDR; + tpm_dev.data_regs = 0x0; + } else { + rc = -EINVAL; + goto err_last; + } + + /* query chip for its vendor, its version number a.s.o. */ + tpm_config_out(ENABLE_REGISTER_PAIR, TPM_INF_ADDR); + tpm_config_out(IDVENL, TPM_INF_ADDR); + vendorid[1] = tpm_config_in(TPM_INF_DATA); + tpm_config_out(IDVENH, TPM_INF_ADDR); + vendorid[0] = tpm_config_in(TPM_INF_DATA); + tpm_config_out(IDPDL, TPM_INF_ADDR); + productid[1] = tpm_config_in(TPM_INF_DATA); + tpm_config_out(IDPDH, TPM_INF_ADDR); + productid[0] = tpm_config_in(TPM_INF_DATA); + tpm_config_out(CHIP_ID1, TPM_INF_ADDR); + version[1] = tpm_config_in(TPM_INF_DATA); + tpm_config_out(CHIP_ID2, TPM_INF_ADDR); + version[0] = tpm_config_in(TPM_INF_DATA); + + switch ((productid[0] << 8) | productid[1]) { + case 6: + snprintf(chipname, sizeof(chipname), " (SLD 9630 TT 1.1)"); + break; + case 11: + snprintf(chipname, sizeof(chipname), " (SLB 9635 TT 1.2)"); + break; + default: + snprintf(chipname, sizeof(chipname), " (unknown chip)"); + break; + } + + if ((vendorid[0] << 8 | vendorid[1]) == (TPM_INFINEON_DEV_VEN_VALUE)) { + + /* configure TPM with IO-ports */ + tpm_config_out(IOLIMH, TPM_INF_ADDR); + tpm_config_out((tpm_dev.data_regs >> 8) & 0xff, TPM_INF_DATA); + tpm_config_out(IOLIML, TPM_INF_ADDR); + tpm_config_out((tpm_dev.data_regs & 0xff), TPM_INF_DATA); + + /* control if IO-ports are set correctly */ + tpm_config_out(IOLIMH, TPM_INF_ADDR); + ioh = tpm_config_in(TPM_INF_DATA); + tpm_config_out(IOLIML, TPM_INF_ADDR); + iol = tpm_config_in(TPM_INF_DATA); + + if ((ioh << 8 | iol) != tpm_dev.data_regs) { + dev_err(&dev->dev, + "Could not set IO-data registers to 0x%x\n", + tpm_dev.data_regs); + rc = -EIO; + goto err_release_region; + } + + /* activate register */ + tpm_config_out(TPM_DAR, TPM_INF_ADDR); + tpm_config_out(0x01, TPM_INF_DATA); + tpm_config_out(DISABLE_REGISTER_PAIR, TPM_INF_ADDR); + + /* disable RESET, LP and IRQC */ + tpm_data_out(RESET_LP_IRQC_DISABLE, CMD); + + /* Finally, we're done, print some infos */ + dev_info(&dev->dev, "TPM found: " + "config base 0x%lx, " + "data base 0x%lx, " + "chip version 0x%02x%02x, " + "vendor id 0x%x%x (Infineon), " + "product id 0x%02x%02x" + "%s\n", + tpm_dev.iotype == TPM_INF_IO_PORT ? + tpm_dev.config_port : + tpm_dev.map_base + tpm_dev.index_off, + tpm_dev.iotype == TPM_INF_IO_PORT ? + tpm_dev.data_regs : + tpm_dev.map_base + tpm_dev.data_regs, + version[0], version[1], + vendorid[0], vendorid[1], + productid[0], productid[1], chipname); + + chip = tpmm_chip_alloc(&dev->dev, &tpm_inf); + if (IS_ERR(chip)) { + rc = PTR_ERR(chip); + goto err_release_region; + } + + rc = tpm_chip_register(chip); + if (rc) + goto err_release_region; + + return 0; + } else { + rc = -ENODEV; + goto err_release_region; + } + +err_release_region: + if (tpm_dev.iotype == TPM_INF_IO_PORT) { + release_region(tpm_dev.data_regs, tpm_dev.data_size); + release_region(tpm_dev.config_port, tpm_dev.config_size); + } else { + iounmap(tpm_dev.mem_base); + release_mem_region(tpm_dev.map_base, tpm_dev.map_size); + } + +err_last: + return rc; +} + +static void tpm_inf_pnp_remove(struct pnp_dev *dev) +{ + struct tpm_chip *chip = pnp_get_drvdata(dev); + + tpm_chip_unregister(chip); + + if (tpm_dev.iotype == TPM_INF_IO_PORT) { + release_region(tpm_dev.data_regs, tpm_dev.data_size); + release_region(tpm_dev.config_port, + tpm_dev.config_size); + } else { + iounmap(tpm_dev.mem_base); + release_mem_region(tpm_dev.map_base, tpm_dev.map_size); + } +} + +#ifdef CONFIG_PM_SLEEP +static int tpm_inf_resume(struct device *dev) +{ + /* Re-configure TPM after suspending */ + tpm_config_out(ENABLE_REGISTER_PAIR, TPM_INF_ADDR); + tpm_config_out(IOLIMH, TPM_INF_ADDR); + tpm_config_out((tpm_dev.data_regs >> 8) & 0xff, TPM_INF_DATA); + tpm_config_out(IOLIML, TPM_INF_ADDR); + tpm_config_out((tpm_dev.data_regs & 0xff), TPM_INF_DATA); + /* activate register */ + tpm_config_out(TPM_DAR, TPM_INF_ADDR); + tpm_config_out(0x01, TPM_INF_DATA); + tpm_config_out(DISABLE_REGISTER_PAIR, TPM_INF_ADDR); + /* disable RESET, LP and IRQC */ + tpm_data_out(RESET_LP_IRQC_DISABLE, CMD); + return tpm_pm_resume(dev); +} +#endif +static SIMPLE_DEV_PM_OPS(tpm_inf_pm, tpm_pm_suspend, tpm_inf_resume); + +static struct pnp_driver tpm_inf_pnp_driver = { + .name = "tpm_inf_pnp", + .id_table = tpm_inf_pnp_tbl, + .probe = tpm_inf_pnp_probe, + .remove = tpm_inf_pnp_remove, + .driver = { + .pm = &tpm_inf_pm, + } +}; + +module_pnp_driver(tpm_inf_pnp_driver); + +MODULE_AUTHOR("Marcel Selhorst <tpmdd@sirrix.com>"); +MODULE_DESCRIPTION("Driver for Infineon TPM SLD 9630 TT 1.1 / SLB 9635 TT 1.2"); +MODULE_VERSION("1.9.2"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/char/tpm/tpm_nsc.c b/kernel/drivers/char/tpm/tpm_nsc.c new file mode 100644 index 000000000..289389ece --- /dev/null +++ b/kernel/drivers/char/tpm/tpm_nsc.c @@ -0,0 +1,383 @@ +/* + * Copyright (C) 2004 IBM Corporation + * + * Authors: + * Leendert van Doorn <leendert@watson.ibm.com> + * Dave Safford <safford@watson.ibm.com> + * Reiner Sailer <sailer@watson.ibm.com> + * Kylene Hall <kjhall@us.ibm.com> + * + * Maintained by: <tpmdd-devel@lists.sourceforge.net> + * + * Device driver for TCG/TCPA TPM (trusted platform module). + * Specifications at www.trustedcomputinggroup.org + * + * 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, version 2 of the + * License. + * + */ + +#include <linux/platform_device.h> +#include <linux/slab.h> +#include "tpm.h" + +/* National definitions */ +enum tpm_nsc_addr{ + TPM_NSC_IRQ = 0x07, + TPM_NSC_BASE0_HI = 0x60, + TPM_NSC_BASE0_LO = 0x61, + TPM_NSC_BASE1_HI = 0x62, + TPM_NSC_BASE1_LO = 0x63 +}; + +enum tpm_nsc_index { + NSC_LDN_INDEX = 0x07, + NSC_SID_INDEX = 0x20, + NSC_LDC_INDEX = 0x30, + NSC_DIO_INDEX = 0x60, + NSC_CIO_INDEX = 0x62, + NSC_IRQ_INDEX = 0x70, + NSC_ITS_INDEX = 0x71 +}; + +enum tpm_nsc_status_loc { + NSC_STATUS = 0x01, + NSC_COMMAND = 0x01, + NSC_DATA = 0x00 +}; + +/* status bits */ +enum tpm_nsc_status { + NSC_STATUS_OBF = 0x01, /* output buffer full */ + NSC_STATUS_IBF = 0x02, /* input buffer full */ + NSC_STATUS_F0 = 0x04, /* F0 */ + NSC_STATUS_A2 = 0x08, /* A2 */ + NSC_STATUS_RDY = 0x10, /* ready to receive command */ + NSC_STATUS_IBR = 0x20 /* ready to receive data */ +}; + +/* command bits */ +enum tpm_nsc_cmd_mode { + NSC_COMMAND_NORMAL = 0x01, /* normal mode */ + NSC_COMMAND_EOC = 0x03, + NSC_COMMAND_CANCEL = 0x22 +}; +/* + * Wait for a certain status to appear + */ +static int wait_for_stat(struct tpm_chip *chip, u8 mask, u8 val, u8 * data) +{ + unsigned long stop; + + /* status immediately available check */ + *data = inb(chip->vendor.base + NSC_STATUS); + if ((*data & mask) == val) + return 0; + + /* wait for status */ + stop = jiffies + 10 * HZ; + do { + msleep(TPM_TIMEOUT); + *data = inb(chip->vendor.base + 1); + if ((*data & mask) == val) + return 0; + } + while (time_before(jiffies, stop)); + + return -EBUSY; +} + +static int nsc_wait_for_ready(struct tpm_chip *chip) +{ + int status; + unsigned long stop; + + /* status immediately available check */ + status = inb(chip->vendor.base + NSC_STATUS); + if (status & NSC_STATUS_OBF) + status = inb(chip->vendor.base + NSC_DATA); + if (status & NSC_STATUS_RDY) + return 0; + + /* wait for status */ + stop = jiffies + 100; + do { + msleep(TPM_TIMEOUT); + status = inb(chip->vendor.base + NSC_STATUS); + if (status & NSC_STATUS_OBF) + status = inb(chip->vendor.base + NSC_DATA); + if (status & NSC_STATUS_RDY) + return 0; + } + while (time_before(jiffies, stop)); + + dev_info(chip->pdev, "wait for ready failed\n"); + return -EBUSY; +} + + +static int tpm_nsc_recv(struct tpm_chip *chip, u8 * buf, size_t count) +{ + u8 *buffer = buf; + u8 data, *p; + u32 size; + __be32 *native_size; + + if (count < 6) + return -EIO; + + if (wait_for_stat(chip, NSC_STATUS_F0, NSC_STATUS_F0, &data) < 0) { + dev_err(chip->pdev, "F0 timeout\n"); + return -EIO; + } + if ((data = + inb(chip->vendor.base + NSC_DATA)) != NSC_COMMAND_NORMAL) { + dev_err(chip->pdev, "not in normal mode (0x%x)\n", + data); + return -EIO; + } + + /* read the whole packet */ + for (p = buffer; p < &buffer[count]; p++) { + if (wait_for_stat + (chip, NSC_STATUS_OBF, NSC_STATUS_OBF, &data) < 0) { + dev_err(chip->pdev, + "OBF timeout (while reading data)\n"); + return -EIO; + } + if (data & NSC_STATUS_F0) + break; + *p = inb(chip->vendor.base + NSC_DATA); + } + + if ((data & NSC_STATUS_F0) == 0 && + (wait_for_stat(chip, NSC_STATUS_F0, NSC_STATUS_F0, &data) < 0)) { + dev_err(chip->pdev, "F0 not set\n"); + return -EIO; + } + if ((data = inb(chip->vendor.base + NSC_DATA)) != NSC_COMMAND_EOC) { + dev_err(chip->pdev, + "expected end of command(0x%x)\n", data); + return -EIO; + } + + native_size = (__force __be32 *) (buf + 2); + size = be32_to_cpu(*native_size); + + if (count < size) + return -EIO; + + return size; +} + +static int tpm_nsc_send(struct tpm_chip *chip, u8 * buf, size_t count) +{ + u8 data; + int i; + + /* + * If we hit the chip with back to back commands it locks up + * and never set IBF. Hitting it with this "hammer" seems to + * fix it. Not sure why this is needed, we followed the flow + * chart in the manual to the letter. + */ + outb(NSC_COMMAND_CANCEL, chip->vendor.base + NSC_COMMAND); + + if (nsc_wait_for_ready(chip) != 0) + return -EIO; + + if (wait_for_stat(chip, NSC_STATUS_IBF, 0, &data) < 0) { + dev_err(chip->pdev, "IBF timeout\n"); + return -EIO; + } + + outb(NSC_COMMAND_NORMAL, chip->vendor.base + NSC_COMMAND); + if (wait_for_stat(chip, NSC_STATUS_IBR, NSC_STATUS_IBR, &data) < 0) { + dev_err(chip->pdev, "IBR timeout\n"); + return -EIO; + } + + for (i = 0; i < count; i++) { + if (wait_for_stat(chip, NSC_STATUS_IBF, 0, &data) < 0) { + dev_err(chip->pdev, + "IBF timeout (while writing data)\n"); + return -EIO; + } + outb(buf[i], chip->vendor.base + NSC_DATA); + } + + if (wait_for_stat(chip, NSC_STATUS_IBF, 0, &data) < 0) { + dev_err(chip->pdev, "IBF timeout\n"); + return -EIO; + } + outb(NSC_COMMAND_EOC, chip->vendor.base + NSC_COMMAND); + + return count; +} + +static void tpm_nsc_cancel(struct tpm_chip *chip) +{ + outb(NSC_COMMAND_CANCEL, chip->vendor.base + NSC_COMMAND); +} + +static u8 tpm_nsc_status(struct tpm_chip *chip) +{ + return inb(chip->vendor.base + NSC_STATUS); +} + +static bool tpm_nsc_req_canceled(struct tpm_chip *chip, u8 status) +{ + return (status == NSC_STATUS_RDY); +} + +static const struct tpm_class_ops tpm_nsc = { + .recv = tpm_nsc_recv, + .send = tpm_nsc_send, + .cancel = tpm_nsc_cancel, + .status = tpm_nsc_status, + .req_complete_mask = NSC_STATUS_OBF, + .req_complete_val = NSC_STATUS_OBF, + .req_canceled = tpm_nsc_req_canceled, +}; + +static struct platform_device *pdev = NULL; + +static void tpm_nsc_remove(struct device *dev) +{ + struct tpm_chip *chip = dev_get_drvdata(dev); + + tpm_chip_unregister(chip); + release_region(chip->vendor.base, 2); +} + +static SIMPLE_DEV_PM_OPS(tpm_nsc_pm, tpm_pm_suspend, tpm_pm_resume); + +static struct platform_driver nsc_drv = { + .driver = { + .name = "tpm_nsc", + .pm = &tpm_nsc_pm, + }, +}; + +static int __init init_nsc(void) +{ + int rc = 0; + int lo, hi, err; + int nscAddrBase = TPM_ADDR; + struct tpm_chip *chip; + unsigned long base; + + /* verify that it is a National part (SID) */ + if (tpm_read_index(TPM_ADDR, NSC_SID_INDEX) != 0xEF) { + nscAddrBase = (tpm_read_index(TPM_SUPERIO_ADDR, 0x2C)<<8)| + (tpm_read_index(TPM_SUPERIO_ADDR, 0x2B)&0xFE); + if (tpm_read_index(nscAddrBase, NSC_SID_INDEX) != 0xF6) + return -ENODEV; + } + + err = platform_driver_register(&nsc_drv); + if (err) + return err; + + hi = tpm_read_index(nscAddrBase, TPM_NSC_BASE0_HI); + lo = tpm_read_index(nscAddrBase, TPM_NSC_BASE0_LO); + base = (hi<<8) | lo; + + /* enable the DPM module */ + tpm_write_index(nscAddrBase, NSC_LDC_INDEX, 0x01); + + pdev = platform_device_alloc("tpm_nscl0", -1); + if (!pdev) { + rc = -ENOMEM; + goto err_unreg_drv; + } + + pdev->num_resources = 0; + pdev->dev.driver = &nsc_drv.driver; + pdev->dev.release = tpm_nsc_remove; + + if ((rc = platform_device_add(pdev)) < 0) + goto err_put_dev; + + if (request_region(base, 2, "tpm_nsc0") == NULL ) { + rc = -EBUSY; + goto err_del_dev; + } + + chip = tpmm_chip_alloc(&pdev->dev, &tpm_nsc); + if (IS_ERR(chip)) { + rc = -ENODEV; + goto err_rel_reg; + } + + rc = tpm_chip_register(chip); + if (rc) + goto err_rel_reg; + + dev_dbg(&pdev->dev, "NSC TPM detected\n"); + dev_dbg(&pdev->dev, + "NSC LDN 0x%x, SID 0x%x, SRID 0x%x\n", + tpm_read_index(nscAddrBase,0x07), tpm_read_index(nscAddrBase,0x20), + tpm_read_index(nscAddrBase,0x27)); + dev_dbg(&pdev->dev, + "NSC SIOCF1 0x%x SIOCF5 0x%x SIOCF6 0x%x SIOCF8 0x%x\n", + tpm_read_index(nscAddrBase,0x21), tpm_read_index(nscAddrBase,0x25), + tpm_read_index(nscAddrBase,0x26), tpm_read_index(nscAddrBase,0x28)); + dev_dbg(&pdev->dev, "NSC IO Base0 0x%x\n", + (tpm_read_index(nscAddrBase,0x60) << 8) | tpm_read_index(nscAddrBase,0x61)); + dev_dbg(&pdev->dev, "NSC IO Base1 0x%x\n", + (tpm_read_index(nscAddrBase,0x62) << 8) | tpm_read_index(nscAddrBase,0x63)); + dev_dbg(&pdev->dev, "NSC Interrupt number and wakeup 0x%x\n", + tpm_read_index(nscAddrBase,0x70)); + dev_dbg(&pdev->dev, "NSC IRQ type select 0x%x\n", + tpm_read_index(nscAddrBase,0x71)); + dev_dbg(&pdev->dev, + "NSC DMA channel select0 0x%x, select1 0x%x\n", + tpm_read_index(nscAddrBase,0x74), tpm_read_index(nscAddrBase,0x75)); + dev_dbg(&pdev->dev, + "NSC Config " + "0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x\n", + tpm_read_index(nscAddrBase,0xF0), tpm_read_index(nscAddrBase,0xF1), + tpm_read_index(nscAddrBase,0xF2), tpm_read_index(nscAddrBase,0xF3), + tpm_read_index(nscAddrBase,0xF4), tpm_read_index(nscAddrBase,0xF5), + tpm_read_index(nscAddrBase,0xF6), tpm_read_index(nscAddrBase,0xF7), + tpm_read_index(nscAddrBase,0xF8), tpm_read_index(nscAddrBase,0xF9)); + + dev_info(&pdev->dev, + "NSC TPM revision %d\n", + tpm_read_index(nscAddrBase, 0x27) & 0x1F); + + chip->vendor.base = base; + + return 0; + +err_rel_reg: + release_region(base, 2); +err_del_dev: + platform_device_del(pdev); +err_put_dev: + platform_device_put(pdev); +err_unreg_drv: + platform_driver_unregister(&nsc_drv); + return rc; +} + +static void __exit cleanup_nsc(void) +{ + if (pdev) { + tpm_nsc_remove(&pdev->dev); + platform_device_unregister(pdev); + } + + platform_driver_unregister(&nsc_drv); +} + +module_init(init_nsc); +module_exit(cleanup_nsc); + +MODULE_AUTHOR("Leendert van Doorn (leendert@watson.ibm.com)"); +MODULE_DESCRIPTION("TPM Driver"); +MODULE_VERSION("2.0"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/char/tpm/tpm_of.c b/kernel/drivers/char/tpm/tpm_of.c new file mode 100644 index 000000000..c002d1bd9 --- /dev/null +++ b/kernel/drivers/char/tpm/tpm_of.c @@ -0,0 +1,73 @@ +/* + * Copyright 2012 IBM Corporation + * + * Author: Ashley Lai <ashleydlai@gmail.com> + * + * Maintained by: <tpmdd-devel@lists.sourceforge.net> + * + * Read the event log created by the firmware on PPC64 + * + * 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. + * + */ + +#include <linux/slab.h> +#include <linux/of.h> + +#include "tpm.h" +#include "tpm_eventlog.h" + +int read_log(struct tpm_bios_log *log) +{ + struct device_node *np; + const u32 *sizep; + const __be64 *basep; + + if (log->bios_event_log != NULL) { + pr_err("%s: ERROR - Eventlog already initialized\n", __func__); + return -EFAULT; + } + + np = of_find_node_by_name(NULL, "ibm,vtpm"); + if (!np) { + pr_err("%s: ERROR - IBMVTPM not supported\n", __func__); + return -ENODEV; + } + + sizep = of_get_property(np, "linux,sml-size", NULL); + if (sizep == NULL) { + pr_err("%s: ERROR - SML size not found\n", __func__); + goto cleanup_eio; + } + if (*sizep == 0) { + pr_err("%s: ERROR - event log area empty\n", __func__); + goto cleanup_eio; + } + + basep = of_get_property(np, "linux,sml-base", NULL); + if (basep == NULL) { + pr_err(KERN_ERR "%s: ERROR - SML not found\n", __func__); + goto cleanup_eio; + } + + of_node_put(np); + log->bios_event_log = kmalloc(*sizep, GFP_KERNEL); + if (!log->bios_event_log) { + pr_err("%s: ERROR - Not enough memory for BIOS measurements\n", + __func__); + return -ENOMEM; + } + + log->bios_event_log_end = log->bios_event_log + *sizep; + + memcpy(log->bios_event_log, __va(be64_to_cpup(basep)), *sizep); + + return 0; + +cleanup_eio: + of_node_put(np); + return -EIO; +} diff --git a/kernel/drivers/char/tpm/tpm_ppi.c b/kernel/drivers/char/tpm/tpm_ppi.c new file mode 100644 index 000000000..6ca9b5d78 --- /dev/null +++ b/kernel/drivers/char/tpm/tpm_ppi.c @@ -0,0 +1,371 @@ +/* + * Copyright (C) 2012-2014 Intel Corporation + * + * Authors: + * Xiaoyan Zhang <xiaoyan.zhang@intel.com> + * Jiang Liu <jiang.liu@linux.intel.com> + * Jarkko Sakkinen <jarkko.sakkinen@linux.intel.com> + * + * Maintained by: <tpmdd-devel@lists.sourceforge.net> + * + * This file contains implementation of the sysfs interface for PPI. + * + * 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; version 2 + * of the License. + */ + + +#include <linux/acpi.h> +#include "tpm.h" + +#define TPM_PPI_REVISION_ID 1 +#define TPM_PPI_FN_VERSION 1 +#define TPM_PPI_FN_SUBREQ 2 +#define TPM_PPI_FN_GETREQ 3 +#define TPM_PPI_FN_GETACT 4 +#define TPM_PPI_FN_GETRSP 5 +#define TPM_PPI_FN_SUBREQ2 7 +#define TPM_PPI_FN_GETOPR 8 +#define PPI_TPM_REQ_MAX 22 +#define PPI_VS_REQ_START 128 +#define PPI_VS_REQ_END 255 + +static const u8 tpm_ppi_uuid[] = { + 0xA6, 0xFA, 0xDD, 0x3D, + 0x1B, 0x36, + 0xB4, 0x4E, + 0xA4, 0x24, + 0x8D, 0x10, 0x08, 0x9D, 0x16, 0x53 +}; + +static inline union acpi_object * +tpm_eval_dsm(acpi_handle ppi_handle, int func, acpi_object_type type, + union acpi_object *argv4) +{ + BUG_ON(!ppi_handle); + return acpi_evaluate_dsm_typed(ppi_handle, tpm_ppi_uuid, + TPM_PPI_REVISION_ID, + func, argv4, type); +} + +static ssize_t tpm_show_ppi_version(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tpm_chip *chip = dev_get_drvdata(dev); + + return scnprintf(buf, PAGE_SIZE, "%s\n", chip->ppi_version); +} + +static ssize_t tpm_show_ppi_request(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t size = -EINVAL; + union acpi_object *obj; + struct tpm_chip *chip = dev_get_drvdata(dev); + + obj = tpm_eval_dsm(chip->acpi_dev_handle, TPM_PPI_FN_GETREQ, + ACPI_TYPE_PACKAGE, NULL); + if (!obj) + return -ENXIO; + + /* + * output.pointer should be of package type, including two integers. + * The first is function return code, 0 means success and 1 means + * error. The second is pending TPM operation requested by the OS, 0 + * means none and >0 means operation value. + */ + if (obj->package.count == 2 && + obj->package.elements[0].type == ACPI_TYPE_INTEGER && + obj->package.elements[1].type == ACPI_TYPE_INTEGER) { + if (obj->package.elements[0].integer.value) + size = -EFAULT; + else + size = scnprintf(buf, PAGE_SIZE, "%llu\n", + obj->package.elements[1].integer.value); + } + + ACPI_FREE(obj); + + return size; +} + +static ssize_t tpm_store_ppi_request(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + u32 req; + u64 ret; + int func = TPM_PPI_FN_SUBREQ; + union acpi_object *obj, tmp; + union acpi_object argv4 = ACPI_INIT_DSM_ARGV4(1, &tmp); + struct tpm_chip *chip = dev_get_drvdata(dev); + + /* + * the function to submit TPM operation request to pre-os environment + * is updated with function index from SUBREQ to SUBREQ2 since PPI + * version 1.1 + */ + if (acpi_check_dsm(chip->acpi_dev_handle, tpm_ppi_uuid, + TPM_PPI_REVISION_ID, 1 << TPM_PPI_FN_SUBREQ2)) + func = TPM_PPI_FN_SUBREQ2; + + /* + * PPI spec defines params[3].type as ACPI_TYPE_PACKAGE. Some BIOS + * accept buffer/string/integer type, but some BIOS accept buffer/ + * string/package type. For PPI version 1.0 and 1.1, use buffer type + * for compatibility, and use package type since 1.2 according to spec. + */ + if (strcmp(chip->ppi_version, "1.2") < 0) { + if (sscanf(buf, "%d", &req) != 1) + return -EINVAL; + argv4.type = ACPI_TYPE_BUFFER; + argv4.buffer.length = sizeof(req); + argv4.buffer.pointer = (u8 *)&req; + } else { + tmp.type = ACPI_TYPE_INTEGER; + if (sscanf(buf, "%llu", &tmp.integer.value) != 1) + return -EINVAL; + } + + obj = tpm_eval_dsm(chip->acpi_dev_handle, func, ACPI_TYPE_INTEGER, + &argv4); + if (!obj) { + return -ENXIO; + } else { + ret = obj->integer.value; + ACPI_FREE(obj); + } + + if (ret == 0) + return (acpi_status)count; + + return (ret == 1) ? -EPERM : -EFAULT; +} + +static ssize_t tpm_show_ppi_transition_action(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + u32 ret; + acpi_status status; + union acpi_object *obj = NULL; + union acpi_object tmp = { + .buffer.type = ACPI_TYPE_BUFFER, + .buffer.length = 0, + .buffer.pointer = NULL + }; + struct tpm_chip *chip = dev_get_drvdata(dev); + + static char *info[] = { + "None", + "Shutdown", + "Reboot", + "OS Vendor-specific", + "Error", + }; + + /* + * PPI spec defines params[3].type as empty package, but some platforms + * (e.g. Capella with PPI 1.0) need integer/string/buffer type, so for + * compatibility, define params[3].type as buffer, if PPI version < 1.2 + */ + if (strcmp(chip->ppi_version, "1.2") < 0) + obj = &tmp; + obj = tpm_eval_dsm(chip->acpi_dev_handle, TPM_PPI_FN_GETACT, + ACPI_TYPE_INTEGER, obj); + if (!obj) { + return -ENXIO; + } else { + ret = obj->integer.value; + ACPI_FREE(obj); + } + + if (ret < ARRAY_SIZE(info) - 1) + status = scnprintf(buf, PAGE_SIZE, "%d: %s\n", ret, info[ret]); + else + status = scnprintf(buf, PAGE_SIZE, "%d: %s\n", ret, + info[ARRAY_SIZE(info)-1]); + return status; +} + +static ssize_t tpm_show_ppi_response(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + acpi_status status = -EINVAL; + union acpi_object *obj, *ret_obj; + u64 req, res; + struct tpm_chip *chip = dev_get_drvdata(dev); + + obj = tpm_eval_dsm(chip->acpi_dev_handle, TPM_PPI_FN_GETRSP, + ACPI_TYPE_PACKAGE, NULL); + if (!obj) + return -ENXIO; + + /* + * parameter output.pointer should be of package type, including + * 3 integers. The first means function return code, the second means + * most recent TPM operation request, and the last means response to + * the most recent TPM operation request. Only if the first is 0, and + * the second integer is not 0, the response makes sense. + */ + ret_obj = obj->package.elements; + if (obj->package.count < 3 || + ret_obj[0].type != ACPI_TYPE_INTEGER || + ret_obj[1].type != ACPI_TYPE_INTEGER || + ret_obj[2].type != ACPI_TYPE_INTEGER) + goto cleanup; + + if (ret_obj[0].integer.value) { + status = -EFAULT; + goto cleanup; + } + + req = ret_obj[1].integer.value; + res = ret_obj[2].integer.value; + if (req) { + if (res == 0) + status = scnprintf(buf, PAGE_SIZE, "%llu %s\n", req, + "0: Success"); + else if (res == 0xFFFFFFF0) + status = scnprintf(buf, PAGE_SIZE, "%llu %s\n", req, + "0xFFFFFFF0: User Abort"); + else if (res == 0xFFFFFFF1) + status = scnprintf(buf, PAGE_SIZE, "%llu %s\n", req, + "0xFFFFFFF1: BIOS Failure"); + else if (res >= 1 && res <= 0x00000FFF) + status = scnprintf(buf, PAGE_SIZE, "%llu %llu: %s\n", + req, res, "Corresponding TPM error"); + else + status = scnprintf(buf, PAGE_SIZE, "%llu %llu: %s\n", + req, res, "Error"); + } else { + status = scnprintf(buf, PAGE_SIZE, "%llu: %s\n", + req, "No Recent Request"); + } + +cleanup: + ACPI_FREE(obj); + return status; +} + +static ssize_t show_ppi_operations(acpi_handle dev_handle, char *buf, u32 start, + u32 end) +{ + int i; + u32 ret; + char *str = buf; + union acpi_object *obj, tmp; + union acpi_object argv = ACPI_INIT_DSM_ARGV4(1, &tmp); + + static char *info[] = { + "Not implemented", + "BIOS only", + "Blocked for OS by BIOS", + "User required", + "User not required", + }; + + if (!acpi_check_dsm(dev_handle, tpm_ppi_uuid, TPM_PPI_REVISION_ID, + 1 << TPM_PPI_FN_GETOPR)) + return -EPERM; + + tmp.integer.type = ACPI_TYPE_INTEGER; + for (i = start; i <= end; i++) { + tmp.integer.value = i; + obj = tpm_eval_dsm(dev_handle, TPM_PPI_FN_GETOPR, + ACPI_TYPE_INTEGER, &argv); + if (!obj) { + return -ENOMEM; + } else { + ret = obj->integer.value; + ACPI_FREE(obj); + } + + if (ret > 0 && ret < ARRAY_SIZE(info)) + str += scnprintf(str, PAGE_SIZE, "%d %d: %s\n", + i, ret, info[ret]); + } + + return str - buf; +} + +static ssize_t tpm_show_ppi_tcg_operations(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct tpm_chip *chip = dev_get_drvdata(dev); + + return show_ppi_operations(chip->acpi_dev_handle, buf, 0, + PPI_TPM_REQ_MAX); +} + +static ssize_t tpm_show_ppi_vs_operations(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct tpm_chip *chip = dev_get_drvdata(dev); + + return show_ppi_operations(chip->acpi_dev_handle, buf, PPI_VS_REQ_START, + PPI_VS_REQ_END); +} + +static DEVICE_ATTR(version, S_IRUGO, tpm_show_ppi_version, NULL); +static DEVICE_ATTR(request, S_IRUGO | S_IWUSR | S_IWGRP, + tpm_show_ppi_request, tpm_store_ppi_request); +static DEVICE_ATTR(transition_action, S_IRUGO, + tpm_show_ppi_transition_action, NULL); +static DEVICE_ATTR(response, S_IRUGO, tpm_show_ppi_response, NULL); +static DEVICE_ATTR(tcg_operations, S_IRUGO, tpm_show_ppi_tcg_operations, NULL); +static DEVICE_ATTR(vs_operations, S_IRUGO, tpm_show_ppi_vs_operations, NULL); + +static struct attribute *ppi_attrs[] = { + &dev_attr_version.attr, + &dev_attr_request.attr, + &dev_attr_transition_action.attr, + &dev_attr_response.attr, + &dev_attr_tcg_operations.attr, + &dev_attr_vs_operations.attr, NULL, +}; +static struct attribute_group ppi_attr_grp = { + .name = "ppi", + .attrs = ppi_attrs +}; + +int tpm_add_ppi(struct tpm_chip *chip) +{ + union acpi_object *obj; + int rc; + + if (!chip->acpi_dev_handle) + return 0; + + if (!acpi_check_dsm(chip->acpi_dev_handle, tpm_ppi_uuid, + TPM_PPI_REVISION_ID, 1 << TPM_PPI_FN_VERSION)) + return 0; + + /* Cache PPI version string. */ + obj = acpi_evaluate_dsm_typed(chip->acpi_dev_handle, tpm_ppi_uuid, + TPM_PPI_REVISION_ID, TPM_PPI_FN_VERSION, + NULL, ACPI_TYPE_STRING); + if (obj) { + strlcpy(chip->ppi_version, obj->string.pointer, + sizeof(chip->ppi_version)); + ACPI_FREE(obj); + } + + rc = sysfs_create_group(&chip->pdev->kobj, &ppi_attr_grp); + + if (!rc) + chip->flags |= TPM_CHIP_FLAG_PPI; + + return rc; +} + +void tpm_remove_ppi(struct tpm_chip *chip) +{ + if (chip->flags & TPM_CHIP_FLAG_PPI) + sysfs_remove_group(&chip->pdev->kobj, &ppi_attr_grp); +} diff --git a/kernel/drivers/char/tpm/tpm_tis.c b/kernel/drivers/char/tpm/tpm_tis.c new file mode 100644 index 000000000..f2dffa770 --- /dev/null +++ b/kernel/drivers/char/tpm/tpm_tis.c @@ -0,0 +1,1013 @@ +/* + * Copyright (C) 2005, 2006 IBM Corporation + * Copyright (C) 2014 Intel Corporation + * + * Authors: + * Leendert van Doorn <leendert@watson.ibm.com> + * Kylene Hall <kjhall@us.ibm.com> + * + * Maintained by: <tpmdd-devel@lists.sourceforge.net> + * + * Device driver for TCG/TCPA TPM (trusted platform module). + * Specifications at www.trustedcomputinggroup.org + * + * This device driver implements the TPM interface as defined in + * the TCG TPM Interface Spec version 1.2, revision 1.0. + * + * 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, version 2 of the + * License. + */ +#include <linux/init.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/pnp.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/wait.h> +#include <linux/acpi.h> +#include <linux/freezer.h> +#include "tpm.h" + +enum tis_access { + TPM_ACCESS_VALID = 0x80, + TPM_ACCESS_ACTIVE_LOCALITY = 0x20, + TPM_ACCESS_REQUEST_PENDING = 0x04, + TPM_ACCESS_REQUEST_USE = 0x02, +}; + +enum tis_status { + TPM_STS_VALID = 0x80, + TPM_STS_COMMAND_READY = 0x40, + TPM_STS_GO = 0x20, + TPM_STS_DATA_AVAIL = 0x10, + TPM_STS_DATA_EXPECT = 0x08, +}; + +enum tis_int_flags { + TPM_GLOBAL_INT_ENABLE = 0x80000000, + TPM_INTF_BURST_COUNT_STATIC = 0x100, + TPM_INTF_CMD_READY_INT = 0x080, + TPM_INTF_INT_EDGE_FALLING = 0x040, + TPM_INTF_INT_EDGE_RISING = 0x020, + TPM_INTF_INT_LEVEL_LOW = 0x010, + TPM_INTF_INT_LEVEL_HIGH = 0x008, + TPM_INTF_LOCALITY_CHANGE_INT = 0x004, + TPM_INTF_STS_VALID_INT = 0x002, + TPM_INTF_DATA_AVAIL_INT = 0x001, +}; + +enum tis_defaults { + TIS_MEM_BASE = 0xFED40000, + TIS_MEM_LEN = 0x5000, + TIS_SHORT_TIMEOUT = 750, /* ms */ + TIS_LONG_TIMEOUT = 2000, /* 2 sec */ +}; + + +/* Some timeout values are needed before it is known whether the chip is + * TPM 1.0 or TPM 2.0. + */ +#define TIS_TIMEOUT_A_MAX max(TIS_SHORT_TIMEOUT, TPM2_TIMEOUT_A) +#define TIS_TIMEOUT_B_MAX max(TIS_LONG_TIMEOUT, TPM2_TIMEOUT_B) +#define TIS_TIMEOUT_C_MAX max(TIS_SHORT_TIMEOUT, TPM2_TIMEOUT_C) +#define TIS_TIMEOUT_D_MAX max(TIS_SHORT_TIMEOUT, TPM2_TIMEOUT_D) + +#define TPM_ACCESS(l) (0x0000 | ((l) << 12)) +#define TPM_INT_ENABLE(l) (0x0008 | ((l) << 12)) +#define TPM_INT_VECTOR(l) (0x000C | ((l) << 12)) +#define TPM_INT_STATUS(l) (0x0010 | ((l) << 12)) +#define TPM_INTF_CAPS(l) (0x0014 | ((l) << 12)) +#define TPM_STS(l) (0x0018 | ((l) << 12)) +#define TPM_STS3(l) (0x001b | ((l) << 12)) +#define TPM_DATA_FIFO(l) (0x0024 | ((l) << 12)) + +#define TPM_DID_VID(l) (0x0F00 | ((l) << 12)) +#define TPM_RID(l) (0x0F04 | ((l) << 12)) + +struct priv_data { + bool irq_tested; +}; + +#if defined(CONFIG_PNP) && defined(CONFIG_ACPI) +static int is_itpm(struct pnp_dev *dev) +{ + struct acpi_device *acpi = pnp_acpi_device(dev); + struct acpi_hardware_id *id; + + if (!acpi) + return 0; + + list_for_each_entry(id, &acpi->pnp.ids, list) { + if (!strcmp("INTC0102", id->id)) + return 1; + } + + return 0; +} +#else +static inline int is_itpm(struct pnp_dev *dev) +{ + return 0; +} +#endif + +/* 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) +{ + unsigned long stop = jiffies + chip->vendor.timeout_a; + do { + if (ioread8(chip->vendor.iobase + TPM_ACCESS(l)) & + TPM_ACCESS_VALID) + return 0; + msleep(TPM_TIMEOUT); + } while (time_before(jiffies, stop)); + return -1; +} + +static int check_locality(struct tpm_chip *chip, int l) +{ + if ((ioread8(chip->vendor.iobase + TPM_ACCESS(l)) & + (TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID)) == + (TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID)) + return chip->vendor.locality = l; + + return -1; +} + +static void release_locality(struct tpm_chip *chip, int l, int force) +{ + if (force || (ioread8(chip->vendor.iobase + TPM_ACCESS(l)) & + (TPM_ACCESS_REQUEST_PENDING | TPM_ACCESS_VALID)) == + (TPM_ACCESS_REQUEST_PENDING | TPM_ACCESS_VALID)) + iowrite8(TPM_ACCESS_ACTIVE_LOCALITY, + chip->vendor.iobase + TPM_ACCESS(l)); +} + +static int request_locality(struct tpm_chip *chip, int l) +{ + unsigned long stop, timeout; + long rc; + + if (check_locality(chip, l) >= 0) + return l; + + iowrite8(TPM_ACCESS_REQUEST_USE, + chip->vendor.iobase + TPM_ACCESS(l)); + + stop = jiffies + chip->vendor.timeout_a; + + if (chip->vendor.irq) { +again: + timeout = stop - jiffies; + if ((long)timeout <= 0) + return -1; + rc = wait_event_interruptible_timeout(chip->vendor.int_queue, + (check_locality + (chip, l) >= 0), + timeout); + if (rc > 0) + return l; + if (rc == -ERESTARTSYS && freezing(current)) { + clear_thread_flag(TIF_SIGPENDING); + goto again; + } + } else { + /* wait for burstcount */ + do { + if (check_locality(chip, l) >= 0) + return l; + msleep(TPM_TIMEOUT); + } + while (time_before(jiffies, stop)); + } + return -1; +} + +static u8 tpm_tis_status(struct tpm_chip *chip) +{ + return ioread8(chip->vendor.iobase + + TPM_STS(chip->vendor.locality)); +} + +static void tpm_tis_ready(struct tpm_chip *chip) +{ + /* this causes the current command to be aborted */ + iowrite8(TPM_STS_COMMAND_READY, + chip->vendor.iobase + TPM_STS(chip->vendor.locality)); +} + +static int get_burstcount(struct tpm_chip *chip) +{ + unsigned long stop; + int burstcnt; + + /* wait for burstcount */ + /* which timeout value, spec has 2 answers (c & d) */ + stop = jiffies + chip->vendor.timeout_d; + do { + burstcnt = ioread8(chip->vendor.iobase + + TPM_STS(chip->vendor.locality) + 1); + burstcnt += ioread8(chip->vendor.iobase + + TPM_STS(chip->vendor.locality) + + 2) << 8; + if (burstcnt) + return burstcnt; + msleep(TPM_TIMEOUT); + } while (time_before(jiffies, stop)); + return -EBUSY; +} + +static int recv_data(struct tpm_chip *chip, u8 *buf, size_t count) +{ + int size = 0, burstcnt; + while (size < count && + wait_for_tpm_stat(chip, + TPM_STS_DATA_AVAIL | TPM_STS_VALID, + chip->vendor.timeout_c, + &chip->vendor.read_queue, true) + == 0) { + burstcnt = get_burstcount(chip); + for (; burstcnt > 0 && size < count; burstcnt--) + buf[size++] = ioread8(chip->vendor.iobase + + TPM_DATA_FIFO(chip->vendor. + locality)); + } + return size; +} + +static int tpm_tis_recv(struct tpm_chip *chip, u8 *buf, size_t count) +{ + int size = 0; + int expected, status; + + if (count < TPM_HEADER_SIZE) { + size = -EIO; + goto out; + } + + /* read first 10 bytes, including tag, paramsize, and result */ + if ((size = + recv_data(chip, buf, TPM_HEADER_SIZE)) < TPM_HEADER_SIZE) { + dev_err(chip->pdev, "Unable to read header\n"); + goto out; + } + + expected = be32_to_cpu(*(__be32 *) (buf + 2)); + if (expected > count) { + size = -EIO; + goto out; + } + + if ((size += + recv_data(chip, &buf[TPM_HEADER_SIZE], + expected - TPM_HEADER_SIZE)) < expected) { + dev_err(chip->pdev, "Unable to read remainder of result\n"); + size = -ETIME; + goto out; + } + + wait_for_tpm_stat(chip, TPM_STS_VALID, chip->vendor.timeout_c, + &chip->vendor.int_queue, false); + status = tpm_tis_status(chip); + if (status & TPM_STS_DATA_AVAIL) { /* retry? */ + dev_err(chip->pdev, "Error left over data\n"); + size = -EIO; + goto out; + } + +out: + tpm_tis_ready(chip); + release_locality(chip, chip->vendor.locality, 0); + return size; +} + +static bool itpm; +module_param(itpm, bool, 0444); +MODULE_PARM_DESC(itpm, "Force iTPM workarounds (found on some Lenovo laptops)"); + +/* + * 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 tpm_tis_send_data(struct tpm_chip *chip, u8 *buf, size_t len) +{ + int rc, status, burstcnt; + size_t count = 0; + + if (request_locality(chip, 0) < 0) + return -EBUSY; + + status = tpm_tis_status(chip); + if ((status & TPM_STS_COMMAND_READY) == 0) { + tpm_tis_ready(chip); + if (wait_for_tpm_stat + (chip, TPM_STS_COMMAND_READY, chip->vendor.timeout_b, + &chip->vendor.int_queue, false) < 0) { + rc = -ETIME; + goto out_err; + } + } + + while (count < len - 1) { + burstcnt = get_burstcount(chip); + for (; burstcnt > 0 && count < len - 1; burstcnt--) { + iowrite8(buf[count], chip->vendor.iobase + + TPM_DATA_FIFO(chip->vendor.locality)); + count++; + } + + wait_for_tpm_stat(chip, TPM_STS_VALID, chip->vendor.timeout_c, + &chip->vendor.int_queue, false); + status = tpm_tis_status(chip); + if (!itpm && (status & TPM_STS_DATA_EXPECT) == 0) { + rc = -EIO; + goto out_err; + } + } + + /* write last byte */ + iowrite8(buf[count], + chip->vendor.iobase + TPM_DATA_FIFO(chip->vendor.locality)); + wait_for_tpm_stat(chip, TPM_STS_VALID, chip->vendor.timeout_c, + &chip->vendor.int_queue, false); + status = tpm_tis_status(chip); + if ((status & TPM_STS_DATA_EXPECT) != 0) { + rc = -EIO; + goto out_err; + } + + return 0; + +out_err: + tpm_tis_ready(chip); + release_locality(chip, chip->vendor.locality, 0); + return rc; +} + +static void disable_interrupts(struct tpm_chip *chip) +{ + u32 intmask; + + intmask = + ioread32(chip->vendor.iobase + + TPM_INT_ENABLE(chip->vendor.locality)); + intmask &= ~TPM_GLOBAL_INT_ENABLE; + iowrite32(intmask, + chip->vendor.iobase + + TPM_INT_ENABLE(chip->vendor.locality)); + free_irq(chip->vendor.irq, chip); + chip->vendor.irq = 0; +} + +/* + * 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 tpm_tis_send_main(struct tpm_chip *chip, u8 *buf, size_t len) +{ + int rc; + u32 ordinal; + unsigned long dur; + + rc = tpm_tis_send_data(chip, buf, len); + if (rc < 0) + return rc; + + /* go and do it */ + iowrite8(TPM_STS_GO, + chip->vendor.iobase + TPM_STS(chip->vendor.locality)); + + if (chip->vendor.irq) { + ordinal = be32_to_cpu(*((__be32 *) (buf + 6))); + + if (chip->flags & TPM_CHIP_FLAG_TPM2) + dur = tpm2_calc_ordinal_duration(chip, ordinal); + else + dur = tpm_calc_ordinal_duration(chip, ordinal); + + if (wait_for_tpm_stat + (chip, TPM_STS_DATA_AVAIL | TPM_STS_VALID, dur, + &chip->vendor.read_queue, false) < 0) { + rc = -ETIME; + goto out_err; + } + } + return len; +out_err: + tpm_tis_ready(chip); + release_locality(chip, chip->vendor.locality, 0); + return rc; +} + +static int tpm_tis_send(struct tpm_chip *chip, u8 *buf, size_t len) +{ + int rc, irq; + struct priv_data *priv = chip->vendor.priv; + + if (!chip->vendor.irq || priv->irq_tested) + return tpm_tis_send_main(chip, buf, len); + + /* Verify receipt of the expected IRQ */ + irq = chip->vendor.irq; + chip->vendor.irq = 0; + rc = tpm_tis_send_main(chip, buf, len); + chip->vendor.irq = irq; + if (!priv->irq_tested) + msleep(1); + if (!priv->irq_tested) { + disable_interrupts(chip); + dev_err(chip->pdev, + FW_BUG "TPM interrupt not working, polling instead\n"); + } + priv->irq_tested = true; + return rc; +} + +struct tis_vendor_timeout_override { + u32 did_vid; + unsigned long timeout_us[4]; +}; + +static const struct tis_vendor_timeout_override vendor_timeout_overrides[] = { + /* Atmel 3204 */ + { 0x32041114, { (TIS_SHORT_TIMEOUT*1000), (TIS_LONG_TIMEOUT*1000), + (TIS_SHORT_TIMEOUT*1000), (TIS_SHORT_TIMEOUT*1000) } }, +}; + +static bool tpm_tis_update_timeouts(struct tpm_chip *chip, + unsigned long *timeout_cap) +{ + int i; + u32 did_vid; + + did_vid = ioread32(chip->vendor.iobase + TPM_DID_VID(0)); + + for (i = 0; i != ARRAY_SIZE(vendor_timeout_overrides); i++) { + if (vendor_timeout_overrides[i].did_vid != did_vid) + continue; + memcpy(timeout_cap, vendor_timeout_overrides[i].timeout_us, + sizeof(vendor_timeout_overrides[i].timeout_us)); + return true; + } + + return false; +} + +/* + * Early probing for iTPM with STS_DATA_EXPECT flaw. + * Try sending command without itpm flag set and if that + * fails, repeat with itpm flag set. + */ +static int probe_itpm(struct tpm_chip *chip) +{ + int rc = 0; + u8 cmd_getticks[] = { + 0x00, 0xc1, 0x00, 0x00, 0x00, 0x0a, + 0x00, 0x00, 0x00, 0xf1 + }; + size_t len = sizeof(cmd_getticks); + bool rem_itpm = itpm; + u16 vendor = ioread16(chip->vendor.iobase + TPM_DID_VID(0)); + + /* probe only iTPMS */ + if (vendor != TPM_VID_INTEL) + return 0; + + itpm = false; + + rc = tpm_tis_send_data(chip, cmd_getticks, len); + if (rc == 0) + goto out; + + tpm_tis_ready(chip); + release_locality(chip, chip->vendor.locality, 0); + + itpm = true; + + rc = tpm_tis_send_data(chip, cmd_getticks, len); + if (rc == 0) { + dev_info(chip->pdev, "Detected an iTPM.\n"); + rc = 1; + } else + rc = -EFAULT; + +out: + itpm = rem_itpm; + tpm_tis_ready(chip); + release_locality(chip, chip->vendor.locality, 0); + + return rc; +} + +static bool tpm_tis_req_canceled(struct tpm_chip *chip, u8 status) +{ + switch (chip->vendor.manufacturer_id) { + case TPM_VID_WINBOND: + return ((status == TPM_STS_VALID) || + (status == (TPM_STS_VALID | TPM_STS_COMMAND_READY))); + case TPM_VID_STM: + return (status == (TPM_STS_VALID | TPM_STS_COMMAND_READY)); + default: + return (status == TPM_STS_COMMAND_READY); + } +} + +static const struct tpm_class_ops tpm_tis = { + .status = tpm_tis_status, + .recv = tpm_tis_recv, + .send = tpm_tis_send, + .cancel = tpm_tis_ready, + .update_timeouts = tpm_tis_update_timeouts, + .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, +}; + +static irqreturn_t tis_int_probe(int irq, void *dev_id) +{ + struct tpm_chip *chip = dev_id; + u32 interrupt; + + interrupt = ioread32(chip->vendor.iobase + + TPM_INT_STATUS(chip->vendor.locality)); + + if (interrupt == 0) + return IRQ_NONE; + + chip->vendor.probed_irq = irq; + + /* Clear interrupts handled with TPM_EOI */ + iowrite32(interrupt, + chip->vendor.iobase + + TPM_INT_STATUS(chip->vendor.locality)); + return IRQ_HANDLED; +} + +static irqreturn_t tis_int_handler(int dummy, void *dev_id) +{ + struct tpm_chip *chip = dev_id; + u32 interrupt; + int i; + + interrupt = ioread32(chip->vendor.iobase + + TPM_INT_STATUS(chip->vendor.locality)); + + if (interrupt == 0) + return IRQ_NONE; + + ((struct priv_data *)chip->vendor.priv)->irq_tested = true; + if (interrupt & TPM_INTF_DATA_AVAIL_INT) + wake_up_interruptible(&chip->vendor.read_queue); + if (interrupt & TPM_INTF_LOCALITY_CHANGE_INT) + for (i = 0; i < 5; i++) + if (check_locality(chip, i) >= 0) + break; + if (interrupt & + (TPM_INTF_LOCALITY_CHANGE_INT | TPM_INTF_STS_VALID_INT | + TPM_INTF_CMD_READY_INT)) + wake_up_interruptible(&chip->vendor.int_queue); + + /* Clear interrupts handled with TPM_EOI */ + iowrite32(interrupt, + chip->vendor.iobase + + TPM_INT_STATUS(chip->vendor.locality)); + ioread32(chip->vendor.iobase + TPM_INT_STATUS(chip->vendor.locality)); + return IRQ_HANDLED; +} + +static bool interrupts = true; +module_param(interrupts, bool, 0444); +MODULE_PARM_DESC(interrupts, "Enable interrupts"); + +static void tpm_tis_remove(struct tpm_chip *chip) +{ + if (chip->flags & TPM_CHIP_FLAG_TPM2) + tpm2_shutdown(chip, TPM2_SU_CLEAR); + + iowrite32(~TPM_GLOBAL_INT_ENABLE & + ioread32(chip->vendor.iobase + + TPM_INT_ENABLE(chip->vendor. + locality)), + chip->vendor.iobase + + TPM_INT_ENABLE(chip->vendor.locality)); + release_locality(chip, chip->vendor.locality, 1); +} + +static int tpm_tis_init(struct device *dev, acpi_handle acpi_dev_handle, + resource_size_t start, resource_size_t len, + unsigned int irq) +{ + u32 vendor, intfcaps, intmask; + int rc, i, irq_s, irq_e, probe; + struct tpm_chip *chip; + struct priv_data *priv; + + priv = devm_kzalloc(dev, sizeof(struct priv_data), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + + chip = tpmm_chip_alloc(dev, &tpm_tis); + if (IS_ERR(chip)) + return PTR_ERR(chip); + + chip->vendor.priv = priv; +#ifdef CONFIG_ACPI + chip->acpi_dev_handle = acpi_dev_handle; +#endif + + chip->vendor.iobase = devm_ioremap(dev, start, len); + if (!chip->vendor.iobase) + return -EIO; + + /* Maximum timeouts */ + chip->vendor.timeout_a = TIS_TIMEOUT_A_MAX; + chip->vendor.timeout_b = TIS_TIMEOUT_B_MAX; + chip->vendor.timeout_c = TIS_TIMEOUT_C_MAX; + chip->vendor.timeout_d = TIS_TIMEOUT_D_MAX; + + if (wait_startup(chip, 0) != 0) { + rc = -ENODEV; + goto out_err; + } + + if (request_locality(chip, 0) != 0) { + rc = -ENODEV; + goto out_err; + } + + rc = tpm2_probe(chip); + if (rc) + goto out_err; + + vendor = ioread32(chip->vendor.iobase + TPM_DID_VID(0)); + chip->vendor.manufacturer_id = vendor; + + 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, ioread8(chip->vendor.iobase + TPM_RID(0))); + + if (!itpm) { + probe = probe_itpm(chip); + if (probe < 0) { + rc = -ENODEV; + goto out_err; + } + itpm = !!probe; + } + + if (itpm) + dev_info(dev, "Intel iTPM workaround enabled\n"); + + + /* Figure out the capabilities */ + intfcaps = + ioread32(chip->vendor.iobase + + TPM_INTF_CAPS(chip->vendor.locality)); + dev_dbg(dev, "TPM interface capabilities (0x%x):\n", + intfcaps); + if (intfcaps & TPM_INTF_BURST_COUNT_STATIC) + dev_dbg(dev, "\tBurst Count Static\n"); + if (intfcaps & TPM_INTF_CMD_READY_INT) + dev_dbg(dev, "\tCommand Ready Int Support\n"); + if (intfcaps & TPM_INTF_INT_EDGE_FALLING) + dev_dbg(dev, "\tInterrupt Edge Falling\n"); + if (intfcaps & TPM_INTF_INT_EDGE_RISING) + dev_dbg(dev, "\tInterrupt Edge Rising\n"); + if (intfcaps & TPM_INTF_INT_LEVEL_LOW) + dev_dbg(dev, "\tInterrupt Level Low\n"); + if (intfcaps & TPM_INTF_INT_LEVEL_HIGH) + dev_dbg(dev, "\tInterrupt Level High\n"); + if (intfcaps & TPM_INTF_LOCALITY_CHANGE_INT) + dev_dbg(dev, "\tLocality Change Int Support\n"); + if (intfcaps & TPM_INTF_STS_VALID_INT) + dev_dbg(dev, "\tSts Valid Int Support\n"); + if (intfcaps & TPM_INTF_DATA_AVAIL_INT) + dev_dbg(dev, "\tData Avail Int Support\n"); + + /* INTERRUPT Setup */ + init_waitqueue_head(&chip->vendor.read_queue); + init_waitqueue_head(&chip->vendor.int_queue); + + intmask = + ioread32(chip->vendor.iobase + + TPM_INT_ENABLE(chip->vendor.locality)); + + intmask |= TPM_INTF_CMD_READY_INT + | TPM_INTF_LOCALITY_CHANGE_INT | TPM_INTF_DATA_AVAIL_INT + | TPM_INTF_STS_VALID_INT; + + iowrite32(intmask, + chip->vendor.iobase + + TPM_INT_ENABLE(chip->vendor.locality)); + if (interrupts) + chip->vendor.irq = irq; + if (interrupts && !chip->vendor.irq) { + irq_s = + ioread8(chip->vendor.iobase + + TPM_INT_VECTOR(chip->vendor.locality)); + if (irq_s) { + irq_e = irq_s; + } else { + irq_s = 3; + irq_e = 15; + } + + for (i = irq_s; i <= irq_e && chip->vendor.irq == 0; i++) { + iowrite8(i, chip->vendor.iobase + + TPM_INT_VECTOR(chip->vendor.locality)); + if (devm_request_irq + (dev, i, tis_int_probe, IRQF_SHARED, + chip->devname, chip) != 0) { + dev_info(chip->pdev, + "Unable to request irq: %d for probe\n", + i); + continue; + } + + /* Clear all existing */ + iowrite32(ioread32 + (chip->vendor.iobase + + TPM_INT_STATUS(chip->vendor.locality)), + chip->vendor.iobase + + TPM_INT_STATUS(chip->vendor.locality)); + + /* Turn on */ + iowrite32(intmask | TPM_GLOBAL_INT_ENABLE, + chip->vendor.iobase + + TPM_INT_ENABLE(chip->vendor.locality)); + + chip->vendor.probed_irq = 0; + + /* Generate Interrupts */ + if (chip->flags & TPM_CHIP_FLAG_TPM2) + tpm2_gen_interrupt(chip); + else + tpm_gen_interrupt(chip); + + chip->vendor.irq = chip->vendor.probed_irq; + + /* free_irq will call into tis_int_probe; + clear all irqs we haven't seen while doing + tpm_gen_interrupt */ + iowrite32(ioread32 + (chip->vendor.iobase + + TPM_INT_STATUS(chip->vendor.locality)), + chip->vendor.iobase + + TPM_INT_STATUS(chip->vendor.locality)); + + /* Turn off */ + iowrite32(intmask, + chip->vendor.iobase + + TPM_INT_ENABLE(chip->vendor.locality)); + } + } + if (chip->vendor.irq) { + iowrite8(chip->vendor.irq, + chip->vendor.iobase + + TPM_INT_VECTOR(chip->vendor.locality)); + if (devm_request_irq + (dev, chip->vendor.irq, tis_int_handler, IRQF_SHARED, + chip->devname, chip) != 0) { + dev_info(chip->pdev, + "Unable to request irq: %d for use\n", + chip->vendor.irq); + chip->vendor.irq = 0; + } else { + /* Clear all existing */ + iowrite32(ioread32 + (chip->vendor.iobase + + TPM_INT_STATUS(chip->vendor.locality)), + chip->vendor.iobase + + TPM_INT_STATUS(chip->vendor.locality)); + + /* Turn on */ + iowrite32(intmask | TPM_GLOBAL_INT_ENABLE, + chip->vendor.iobase + + TPM_INT_ENABLE(chip->vendor.locality)); + } + } + + if (chip->flags & TPM_CHIP_FLAG_TPM2) { + chip->vendor.timeout_a = msecs_to_jiffies(TPM2_TIMEOUT_A); + chip->vendor.timeout_b = msecs_to_jiffies(TPM2_TIMEOUT_B); + chip->vendor.timeout_c = msecs_to_jiffies(TPM2_TIMEOUT_C); + chip->vendor.timeout_d = msecs_to_jiffies(TPM2_TIMEOUT_D); + chip->vendor.duration[TPM_SHORT] = + msecs_to_jiffies(TPM2_DURATION_SHORT); + chip->vendor.duration[TPM_MEDIUM] = + msecs_to_jiffies(TPM2_DURATION_MEDIUM); + chip->vendor.duration[TPM_LONG] = + msecs_to_jiffies(TPM2_DURATION_LONG); + + rc = tpm2_do_selftest(chip); + if (rc == TPM2_RC_INITIALIZE) { + dev_warn(dev, "Firmware has not started TPM\n"); + rc = tpm2_startup(chip, TPM2_SU_CLEAR); + if (!rc) + rc = tpm2_do_selftest(chip); + } + + if (rc) { + dev_err(dev, "TPM self test failed\n"); + if (rc > 0) + rc = -ENODEV; + goto out_err; + } + } else { + if (tpm_get_timeouts(chip)) { + dev_err(dev, "Could not get TPM timeouts and durations\n"); + rc = -ENODEV; + goto out_err; + } + + if (tpm_do_selftest(chip)) { + dev_err(dev, "TPM self test failed\n"); + rc = -ENODEV; + goto out_err; + } + } + + return tpm_chip_register(chip); +out_err: + tpm_tis_remove(chip); + return rc; +} + +#ifdef CONFIG_PM_SLEEP +static void tpm_tis_reenable_interrupts(struct tpm_chip *chip) +{ + u32 intmask; + + /* reenable interrupts that device may have lost or + BIOS/firmware may have disabled */ + iowrite8(chip->vendor.irq, chip->vendor.iobase + + TPM_INT_VECTOR(chip->vendor.locality)); + + intmask = + ioread32(chip->vendor.iobase + + TPM_INT_ENABLE(chip->vendor.locality)); + + intmask |= TPM_INTF_CMD_READY_INT + | TPM_INTF_LOCALITY_CHANGE_INT | TPM_INTF_DATA_AVAIL_INT + | TPM_INTF_STS_VALID_INT | TPM_GLOBAL_INT_ENABLE; + + iowrite32(intmask, + chip->vendor.iobase + TPM_INT_ENABLE(chip->vendor.locality)); +} + +static int tpm_tis_resume(struct device *dev) +{ + struct tpm_chip *chip = dev_get_drvdata(dev); + int ret; + + if (chip->vendor.irq) + tpm_tis_reenable_interrupts(chip); + + ret = tpm_pm_resume(dev); + if (ret) + return ret; + + /* TPM 1.2 requires self-test on resume. This function actually returns + * an error code but for unknown reason it isn't handled. + */ + if (!(chip->flags & TPM_CHIP_FLAG_TPM2)) + tpm_do_selftest(chip); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(tpm_tis_pm, tpm_pm_suspend, tpm_tis_resume); + +#ifdef CONFIG_PNP +static int tpm_tis_pnp_init(struct pnp_dev *pnp_dev, + const struct pnp_device_id *pnp_id) +{ + resource_size_t start, len; + unsigned int irq = 0; + acpi_handle acpi_dev_handle = NULL; + + start = pnp_mem_start(pnp_dev, 0); + len = pnp_mem_len(pnp_dev, 0); + + if (pnp_irq_valid(pnp_dev, 0)) + irq = pnp_irq(pnp_dev, 0); + else + interrupts = false; + + if (is_itpm(pnp_dev)) + itpm = true; + +#ifdef CONFIG_ACPI + if (pnp_acpi_device(pnp_dev)) + acpi_dev_handle = pnp_acpi_device(pnp_dev)->handle; +#endif + + return tpm_tis_init(&pnp_dev->dev, acpi_dev_handle, start, len, irq); +} + +static struct pnp_device_id tpm_pnp_tbl[] = { + {"PNP0C31", 0}, /* TPM */ + {"ATM1200", 0}, /* Atmel */ + {"IFX0102", 0}, /* Infineon */ + {"BCM0101", 0}, /* Broadcom */ + {"BCM0102", 0}, /* Broadcom */ + {"NSC1200", 0}, /* National */ + {"ICO0102", 0}, /* Intel */ + /* Add new here */ + {"", 0}, /* User Specified */ + {"", 0} /* Terminator */ +}; +MODULE_DEVICE_TABLE(pnp, tpm_pnp_tbl); + +static void tpm_tis_pnp_remove(struct pnp_dev *dev) +{ + struct tpm_chip *chip = pnp_get_drvdata(dev); + tpm_chip_unregister(chip); + tpm_tis_remove(chip); +} + +static struct pnp_driver tis_pnp_driver = { + .name = "tpm_tis", + .id_table = tpm_pnp_tbl, + .probe = tpm_tis_pnp_init, + .remove = tpm_tis_pnp_remove, + .driver = { + .pm = &tpm_tis_pm, + }, +}; + +#define TIS_HID_USR_IDX sizeof(tpm_pnp_tbl)/sizeof(struct pnp_device_id) -2 +module_param_string(hid, tpm_pnp_tbl[TIS_HID_USR_IDX].id, + sizeof(tpm_pnp_tbl[TIS_HID_USR_IDX].id), 0444); +MODULE_PARM_DESC(hid, "Set additional specific HID for this driver to probe"); +#endif + +static struct platform_driver tis_drv = { + .driver = { + .name = "tpm_tis", + .pm = &tpm_tis_pm, + }, +}; + +static struct platform_device *pdev; + +static bool force; +module_param(force, bool, 0444); +MODULE_PARM_DESC(force, "Force device probe rather than using ACPI entry"); +static int __init init_tis(void) +{ + int rc; +#ifdef CONFIG_PNP + if (!force) + return pnp_register_driver(&tis_pnp_driver); +#endif + + rc = platform_driver_register(&tis_drv); + if (rc < 0) + return rc; + pdev = platform_device_register_simple("tpm_tis", -1, NULL, 0); + if (IS_ERR(pdev)) { + rc = PTR_ERR(pdev); + goto err_dev; + } + rc = tpm_tis_init(&pdev->dev, NULL, TIS_MEM_BASE, TIS_MEM_LEN, 0); + if (rc) + goto err_init; + return 0; +err_init: + platform_device_unregister(pdev); +err_dev: + platform_driver_unregister(&tis_drv); + return rc; +} + +static void __exit cleanup_tis(void) +{ + struct tpm_chip *chip; +#ifdef CONFIG_PNP + if (!force) { + pnp_unregister_driver(&tis_pnp_driver); + return; + } +#endif + chip = dev_get_drvdata(&pdev->dev); + tpm_chip_unregister(chip); + tpm_tis_remove(chip); + platform_device_unregister(pdev); + platform_driver_unregister(&tis_drv); +} + +module_init(init_tis); +module_exit(cleanup_tis); +MODULE_AUTHOR("Leendert van Doorn (leendert@watson.ibm.com)"); +MODULE_DESCRIPTION("TPM Driver"); +MODULE_VERSION("2.0"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/char/tpm/xen-tpmfront.c b/kernel/drivers/char/tpm/xen-tpmfront.c new file mode 100644 index 000000000..3111f2778 --- /dev/null +++ b/kernel/drivers/char/tpm/xen-tpmfront.c @@ -0,0 +1,401 @@ +/* + * Implementation of the Xen vTPM device frontend + * + * Author: Daniel De Graaf <dgdegra@tycho.nsa.gov> + * + * 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/errno.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <xen/xen.h> +#include <xen/events.h> +#include <xen/interface/io/tpmif.h> +#include <xen/grant_table.h> +#include <xen/xenbus.h> +#include <xen/page.h> +#include "tpm.h" +#include <xen/platform_pci.h> + +struct tpm_private { + struct tpm_chip *chip; + struct xenbus_device *dev; + + struct vtpm_shared_page *shr; + + unsigned int evtchn; + int ring_ref; + domid_t backend_id; +}; + +enum status_bits { + VTPM_STATUS_RUNNING = 0x1, + VTPM_STATUS_IDLE = 0x2, + VTPM_STATUS_RESULT = 0x4, + VTPM_STATUS_CANCELED = 0x8, +}; + +static u8 vtpm_status(struct tpm_chip *chip) +{ + struct tpm_private *priv = TPM_VPRIV(chip); + switch (priv->shr->state) { + case VTPM_STATE_IDLE: + return VTPM_STATUS_IDLE | VTPM_STATUS_CANCELED; + case VTPM_STATE_FINISH: + return VTPM_STATUS_IDLE | VTPM_STATUS_RESULT; + case VTPM_STATE_SUBMIT: + case VTPM_STATE_CANCEL: /* cancel requested, not yet canceled */ + return VTPM_STATUS_RUNNING; + default: + return 0; + } +} + +static bool vtpm_req_canceled(struct tpm_chip *chip, u8 status) +{ + return status & VTPM_STATUS_CANCELED; +} + +static void vtpm_cancel(struct tpm_chip *chip) +{ + struct tpm_private *priv = TPM_VPRIV(chip); + priv->shr->state = VTPM_STATE_CANCEL; + wmb(); + notify_remote_via_evtchn(priv->evtchn); +} + +static unsigned int shr_data_offset(struct vtpm_shared_page *shr) +{ + return sizeof(*shr) + sizeof(u32) * shr->nr_extra_pages; +} + +static int vtpm_send(struct tpm_chip *chip, u8 *buf, size_t count) +{ + struct tpm_private *priv = TPM_VPRIV(chip); + struct vtpm_shared_page *shr = priv->shr; + unsigned int offset = shr_data_offset(shr); + + u32 ordinal; + unsigned long duration; + + if (offset > PAGE_SIZE) + return -EINVAL; + + if (offset + count > PAGE_SIZE) + return -EINVAL; + + /* Wait for completion of any existing command or cancellation */ + if (wait_for_tpm_stat(chip, VTPM_STATUS_IDLE, chip->vendor.timeout_c, + &chip->vendor.read_queue, true) < 0) { + vtpm_cancel(chip); + return -ETIME; + } + + memcpy(offset + (u8 *)shr, buf, count); + shr->length = count; + barrier(); + shr->state = VTPM_STATE_SUBMIT; + wmb(); + notify_remote_via_evtchn(priv->evtchn); + + ordinal = be32_to_cpu(((struct tpm_input_header*)buf)->ordinal); + duration = tpm_calc_ordinal_duration(chip, ordinal); + + if (wait_for_tpm_stat(chip, VTPM_STATUS_IDLE, duration, + &chip->vendor.read_queue, true) < 0) { + /* got a signal or timeout, try to cancel */ + vtpm_cancel(chip); + return -ETIME; + } + + return count; +} + +static int vtpm_recv(struct tpm_chip *chip, u8 *buf, size_t count) +{ + struct tpm_private *priv = TPM_VPRIV(chip); + struct vtpm_shared_page *shr = priv->shr; + unsigned int offset = shr_data_offset(shr); + size_t length = shr->length; + + if (shr->state == VTPM_STATE_IDLE) + return -ECANCELED; + + /* In theory the wait at the end of _send makes this one unnecessary */ + if (wait_for_tpm_stat(chip, VTPM_STATUS_RESULT, chip->vendor.timeout_c, + &chip->vendor.read_queue, true) < 0) { + vtpm_cancel(chip); + return -ETIME; + } + + if (offset > PAGE_SIZE) + return -EIO; + + if (offset + length > PAGE_SIZE) + length = PAGE_SIZE - offset; + + if (length > count) + length = count; + + memcpy(buf, offset + (u8 *)shr, length); + + return length; +} + +static const struct tpm_class_ops tpm_vtpm = { + .status = vtpm_status, + .recv = vtpm_recv, + .send = vtpm_send, + .cancel = vtpm_cancel, + .req_complete_mask = VTPM_STATUS_IDLE | VTPM_STATUS_RESULT, + .req_complete_val = VTPM_STATUS_IDLE | VTPM_STATUS_RESULT, + .req_canceled = vtpm_req_canceled, +}; + +static irqreturn_t tpmif_interrupt(int dummy, void *dev_id) +{ + struct tpm_private *priv = dev_id; + + switch (priv->shr->state) { + case VTPM_STATE_IDLE: + case VTPM_STATE_FINISH: + wake_up_interruptible(&priv->chip->vendor.read_queue); + break; + case VTPM_STATE_SUBMIT: + case VTPM_STATE_CANCEL: + default: + break; + } + return IRQ_HANDLED; +} + +static int setup_chip(struct device *dev, struct tpm_private *priv) +{ + struct tpm_chip *chip; + + chip = tpmm_chip_alloc(dev, &tpm_vtpm); + if (IS_ERR(chip)) + return PTR_ERR(chip); + + init_waitqueue_head(&chip->vendor.read_queue); + + priv->chip = chip; + TPM_VPRIV(chip) = priv; + + return 0; +} + +/* caller must clean up in case of errors */ +static int setup_ring(struct xenbus_device *dev, struct tpm_private *priv) +{ + struct xenbus_transaction xbt; + const char *message = NULL; + int rv; + grant_ref_t gref; + + priv->shr = (void *)__get_free_page(GFP_KERNEL|__GFP_ZERO); + if (!priv->shr) { + xenbus_dev_fatal(dev, -ENOMEM, "allocating shared ring"); + return -ENOMEM; + } + + rv = xenbus_grant_ring(dev, &priv->shr, 1, &gref); + if (rv < 0) + return rv; + + priv->ring_ref = gref; + + rv = xenbus_alloc_evtchn(dev, &priv->evtchn); + if (rv) + return rv; + + rv = bind_evtchn_to_irqhandler(priv->evtchn, tpmif_interrupt, 0, + "tpmif", priv); + if (rv <= 0) { + xenbus_dev_fatal(dev, rv, "allocating TPM irq"); + return rv; + } + priv->chip->vendor.irq = rv; + + again: + rv = xenbus_transaction_start(&xbt); + if (rv) { + xenbus_dev_fatal(dev, rv, "starting transaction"); + return rv; + } + + rv = xenbus_printf(xbt, dev->nodename, + "ring-ref", "%u", priv->ring_ref); + if (rv) { + message = "writing ring-ref"; + goto abort_transaction; + } + + rv = xenbus_printf(xbt, dev->nodename, "event-channel", "%u", + priv->evtchn); + if (rv) { + message = "writing event-channel"; + goto abort_transaction; + } + + rv = xenbus_printf(xbt, dev->nodename, "feature-protocol-v2", "1"); + if (rv) { + message = "writing feature-protocol-v2"; + goto abort_transaction; + } + + rv = xenbus_transaction_end(xbt, 0); + if (rv == -EAGAIN) + goto again; + if (rv) { + xenbus_dev_fatal(dev, rv, "completing transaction"); + return rv; + } + + xenbus_switch_state(dev, XenbusStateInitialised); + + return 0; + + abort_transaction: + xenbus_transaction_end(xbt, 1); + if (message) + xenbus_dev_error(dev, rv, "%s", message); + + return rv; +} + +static void ring_free(struct tpm_private *priv) +{ + if (!priv) + return; + + if (priv->ring_ref) + gnttab_end_foreign_access(priv->ring_ref, 0, + (unsigned long)priv->shr); + else + free_page((unsigned long)priv->shr); + + if (priv->chip && priv->chip->vendor.irq) + unbind_from_irqhandler(priv->chip->vendor.irq, priv); + + kfree(priv); +} + +static int tpmfront_probe(struct xenbus_device *dev, + const struct xenbus_device_id *id) +{ + struct tpm_private *priv; + struct tpm_chip *chip; + int rv; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) { + xenbus_dev_fatal(dev, -ENOMEM, "allocating priv structure"); + return -ENOMEM; + } + + rv = setup_chip(&dev->dev, priv); + if (rv) { + kfree(priv); + return rv; + } + + rv = setup_ring(dev, priv); + if (rv) { + chip = dev_get_drvdata(&dev->dev); + tpm_chip_unregister(chip); + ring_free(priv); + return rv; + } + + tpm_get_timeouts(priv->chip); + + return tpm_chip_register(priv->chip); +} + +static int tpmfront_remove(struct xenbus_device *dev) +{ + struct tpm_chip *chip = dev_get_drvdata(&dev->dev); + struct tpm_private *priv = TPM_VPRIV(chip); + tpm_chip_unregister(chip); + ring_free(priv); + TPM_VPRIV(chip) = NULL; + return 0; +} + +static int tpmfront_resume(struct xenbus_device *dev) +{ + /* A suspend/resume/migrate will interrupt a vTPM anyway */ + tpmfront_remove(dev); + return tpmfront_probe(dev, NULL); +} + +static void backend_changed(struct xenbus_device *dev, + enum xenbus_state backend_state) +{ + int val; + + switch (backend_state) { + case XenbusStateInitialised: + case XenbusStateConnected: + if (dev->state == XenbusStateConnected) + break; + + if (xenbus_scanf(XBT_NIL, dev->otherend, + "feature-protocol-v2", "%d", &val) < 0) + val = 0; + if (!val) { + xenbus_dev_fatal(dev, -EINVAL, + "vTPM protocol 2 required"); + return; + } + xenbus_switch_state(dev, XenbusStateConnected); + break; + + case XenbusStateClosing: + case XenbusStateClosed: + device_unregister(&dev->dev); + xenbus_frontend_closed(dev); + break; + default: + break; + } +} + +static const struct xenbus_device_id tpmfront_ids[] = { + { "vtpm" }, + { "" } +}; +MODULE_ALIAS("xen:vtpm"); + +static struct xenbus_driver tpmfront_driver = { + .ids = tpmfront_ids, + .probe = tpmfront_probe, + .remove = tpmfront_remove, + .resume = tpmfront_resume, + .otherend_changed = backend_changed, +}; + +static int __init xen_tpmfront_init(void) +{ + if (!xen_domain()) + return -ENODEV; + + if (!xen_has_pv_devices()) + return -ENODEV; + + return xenbus_register_frontend(&tpmfront_driver); +} +module_init(xen_tpmfront_init); + +static void __exit xen_tpmfront_exit(void) +{ + xenbus_unregister_driver(&tpmfront_driver); +} +module_exit(xen_tpmfront_exit); + +MODULE_AUTHOR("Daniel De Graaf <dgdegra@tycho.nsa.gov>"); +MODULE_DESCRIPTION("Xen vTPM Driver"); +MODULE_LICENSE("GPL"); |