[Skiboot] [PATCH 13/40] Add i2c Nuvoton TPM 2.0 Driver
Stewart Smith
stewart at linux.vnet.ibm.com
Mon Oct 10 19:43:54 AEDT 2016
From: Claudio Carvalho <cclaudio at linux.vnet.ibm.com>
This adds the 1/5 step performed by the TPM I2C Nuvoton driver to
transmit a command to the TPM device. In this step the driver
checks if the TPM device is ready to receive a new command.
This adds the 2/5 step performed by the TPM I2C Nuvoton driver to
transmit a command to the TPM device. In this step the driver
writes a given command to master I2C FIFO.
This adds the 3/5 step performed by the TPM I2C Nuvoton driver to
transmit a command to the TPM device. In this step the driver
sets the TPMGO bit in the I2C master status register to indicate that
the command stored in the FIFO can be sent to the TPM device.
This adds the 4/5 step performed by the TPM I2C Nuvoton driver to
transmit a command to the TPM device. In this step the driver
reads from the I2C master FIFO the result that the TPM device returned
for the last command sent.
This adds the 5/5 step performed by the TPM I2C Nuvoton driver to
transmit a command to the TPM device. In this step the driver
sets the COMMAND_READY bit in the status register to indicate that the
TPM device is ready to receive a new command.
This adds the probe function to the TPM Nuvoton driver and also updates
the tpm_init() in tpm_chip.c to call the probe function.
Signed-off-by: Claudio Carvalho <cclaudio at linux.vnet.ibm.com>
[stewart at linux.vnet.ibm.com: squash commits into one]
Signed-off-by: Stewart Smith <stewart at linux.vnet.ibm.com>
---
libstb/drivers/Makefile.inc | 2 +-
libstb/drivers/tpm_i2c_nuvoton.c | 504 +++++++++++++++++++++++++++++++++++++++
libstb/drivers/tpm_i2c_nuvoton.h | 22 ++
libstb/status_codes.h | 4 +
libstb/tpm_chip.c | 2 +
5 files changed, 533 insertions(+), 1 deletion(-)
create mode 100644 libstb/drivers/tpm_i2c_nuvoton.c
create mode 100644 libstb/drivers/tpm_i2c_nuvoton.h
diff --git a/libstb/drivers/Makefile.inc b/libstb/drivers/Makefile.inc
index f0f3d70..2817378 100644
--- a/libstb/drivers/Makefile.inc
+++ b/libstb/drivers/Makefile.inc
@@ -4,7 +4,7 @@ DRIVERS_DIR = libstb/drivers
SUBDIRS += $(DRIVERS_DIR)
-DRIVERS_SRCS = romcode.c tpm_i2c_interface.c
+DRIVERS_SRCS = romcode.c tpm_i2c_interface.c tpm_i2c_nuvoton.c
DRIVERS_OBJS = $(DRIVERS_SRCS:%.c=%.o)
DRIVERS = $(DRIVERS_DIR)/built-in.o
diff --git a/libstb/drivers/tpm_i2c_nuvoton.c b/libstb/drivers/tpm_i2c_nuvoton.c
new file mode 100644
index 0000000..a4dfb23
--- /dev/null
+++ b/libstb/drivers/tpm_i2c_nuvoton.c
@@ -0,0 +1,504 @@
+/* Copyright 2013-2016 IBM Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/****************************************************************************
+ * THIS DRIVER WAS DEVELOPED BASED ON:
+ * https://github.com/open-power/hostboot/blob/master-p8/src/usr/i2c/tpmdd.C
+ ****************************************************************************/
+
+#include <timebase.h>
+#include <skiboot.h>
+#include <device.h>
+#include <i2c.h>
+#include "../status_codes.h"
+#include "../tpm_chip.h"
+#include "tpm_i2c_interface.h"
+#include "tpm_i2c_nuvoton.h"
+
+//#define DBG(fmt, ...) prlog(PR_DEBUG, fmt, ##__VA_ARGS__)
+#define DBG(fmt, ...)
+
+#define DRIVER_NAME "i2c_tpm_nuvoton"
+
+/*
+ * Timings between various states or transitions within the interface protocol
+ * as defined in the TCG PC Client Platform TPM Profile specification, Revision
+ * 00.43.
+ */
+#define TCG_PTP_TIMEOUT_A 750
+#define TCG_PTP_TIMEOUT_B 2000
+#define TCG_PTP_TIMEOUT_D 30
+
+/* I2C interface offsets */
+#define NUVOTON_TPM_STS 0x00
+#define NUVOTON_TPM_BURST_COUNT 0x01
+#define NUVOTON_TPM_DATA_FIFO_W 0x20
+#define NUVOTON_TPM_DATA_FIFO_R 0x40
+
+/* Bit masks for the TPM STATUS register */
+#define TCG_PTP_STS_VALID 0x80
+#define TCG_PTP_STS_COMMAND_READY 0x40
+#define TCG_PTP_STS_GO 0x20
+#define TCG_PTP_STS_DATA_AVAIL 0x10
+#define TCG_PTP_STS_EXPECT 0x08
+
+
+/* TPM Driver values */
+#define MAX_STSVALID_POLLS 5 /* Max poll of 50ms (5*10ms) */
+#define TPM_TIMEOUT_INTERVAL 10
+
+static struct tpm_dev *tpm_device = NULL;
+
+static int tpm_status_write_byte(uint8_t byte)
+{
+ uint8_t value = byte;
+ return tpm_i2c_request_send(tpm_device->bus_id, tpm_device->xscom_base,
+ SMBUS_WRITE, NUVOTON_TPM_STS, 1, &value,
+ sizeof(value));
+}
+
+static int tpm_read_sts_reg_valid(uint8_t* value)
+{
+ int polls, rc;
+
+ for(polls=0; polls<=MAX_STSVALID_POLLS; polls++) {
+ rc = tpm_i2c_request_send(tpm_device->bus_id,
+ tpm_device->xscom_base, SMBUS_READ,
+ NUVOTON_TPM_STS, 1, value, sizeof(uint8_t));
+ if (rc < 0)
+ return rc;
+ if (rc == 0 &&
+ ((*value & TCG_PTP_STS_VALID) == TCG_PTP_STS_VALID))
+ return 0;
+ /* Wait TPM STS register be settled */
+ time_wait_ms(5);
+ }
+ value = 0;
+ /**
+ * @fwts-label TPMValidBitTimeout
+ * @fwts-advice The valid bit of the tpm status register is taking
+ * longer to be settled. Either the wait time needs to be increased
+ * or the TPM device is not functional.
+ */
+ prlog(PR_ERR, "TPM: valid bit not settled. Timeout.\n");
+ return STB_TPM_TIMEOUT;
+}
+
+static bool tpm_is_command_ready(int* rc)
+{
+ uint8_t value = 0;
+ *rc = tpm_i2c_request_send(tpm_device->bus_id, tpm_device->xscom_base,
+ SMBUS_READ, NUVOTON_TPM_STS, 1, &value,
+ sizeof(value));
+ if (*rc == 0 &&
+ ((value & TCG_PTP_STS_COMMAND_READY) == TCG_PTP_STS_COMMAND_READY)){
+ DBG("---- TPM is command ready\n");
+ return true;
+ }
+ return false;
+}
+
+static int tpm_poll_for_command_ready(void)
+{
+ int rc, polls, delay;
+ /*
+ * The first write to command ready may just abort an
+ * outstanding command, so we poll twice
+ */
+ for (polls=0; polls<2; polls++) {
+ rc = tpm_status_write_byte(TCG_PTP_STS_COMMAND_READY);
+ if (rc < 0) {
+ return rc;
+ }
+ for (delay = 0; delay < TCG_PTP_TIMEOUT_B;
+ delay += TPM_TIMEOUT_INTERVAL) {
+ if (tpm_is_command_ready(&rc))
+ return rc;
+ time_wait_ms(TPM_TIMEOUT_INTERVAL);
+ }
+ DBG("--- Command ready polling, delay %d/%d\n",
+ delay, TCG_PTP_TIMEOUT_B);
+ }
+ /**
+ * @fwts-label TPMCommandReadyBitTimeout
+ * @fwts-advice The command ready bit of the tpm status register is
+ * taking longer to be settled. Either the wait time need to be
+ * increased or the TPM device is not functional.
+ */
+ prlog(PR_ERR, "TPM: command ready polling timeout\n");
+ return STB_TPM_TIMEOUT;
+}
+
+static bool tpm_is_expecting(int* rc)
+{
+ uint8_t value = 0;
+ *rc = tpm_read_sts_reg_valid(&value);
+ if (*rc == 0 &&
+ (( value & TCG_PTP_STS_EXPECT) == TCG_PTP_STS_EXPECT))
+ return true;
+ return false;
+}
+
+static bool tpm_is_data_avail(int* rc)
+{
+ uint8_t value = 0;
+
+ *rc = tpm_read_sts_reg_valid(&value);
+
+ if (*rc == 0 && (( value &
+ TCG_PTP_STS_DATA_AVAIL) == TCG_PTP_STS_DATA_AVAIL))
+ return true;
+
+ return false;
+}
+
+static int tpm_poll_for_data_avail(void)
+{
+ int delay, rc;
+
+ for (delay = 0; delay < TCG_PTP_TIMEOUT_A;
+ delay += TPM_TIMEOUT_INTERVAL) {
+ if (tpm_is_data_avail(&rc)) {
+ DBG("---- read FIFO. Data available. delay=%d/%d\n",
+ delay, TCG_PTP_TIMEOUT_A);
+ return rc;
+ }
+ time_wait_ms(TPM_TIMEOUT_INTERVAL);
+ }
+ /**
+ * @fwts-label TPMDataAvailBitTimeout
+ * @fwts-advice The data avail bit of the tpm status register is taking
+ * longer to be settled. Either the wait time need to be increased or
+ * the TPM device is not functional.
+ */
+ prlog(PR_ERR, "TPM: read FIFO. Polling timeout, delay=%d/%d\n",
+ delay, TCG_PTP_TIMEOUT_A);
+ return STB_TPM_TIMEOUT;
+}
+
+static int tpm_read_burst_count(uint8_t* burst_count)
+{
+ int rc = 0;
+ /* In i2C, burstCount is 1 byte */
+ rc = tpm_i2c_request_send(tpm_device->bus_id, tpm_device->xscom_base,
+ SMBUS_READ, NUVOTON_TPM_BURST_COUNT, 1,
+ burst_count, sizeof(uint8_t));
+ DBG("---- burst_count=%d rc=%d\n", *burst_count, rc);
+ if (rc < 0)
+ *burst_count = 0;
+ return rc;
+}
+
+static int tpm_write_fifo(uint8_t* buf, size_t buflen)
+{
+ uint8_t burst_count = 0;
+ int delay = 0;
+ int rc;
+ size_t curByte = 0;
+ uint8_t* bytePtr = buf;
+ uint8_t* curBytePtr = NULL;
+ /*
+ * We will transfer the command except for the last byte
+ * that will be transfered separately to allow for
+ * overflow checking
+ */
+ size_t length = buflen - 1;
+ size_t tx_len = 0;
+
+ do {
+ rc = tpm_read_burst_count(&burst_count);
+ if (rc < 0) {
+ return rc;
+ } else if (burst_count == 0) {
+ /* Need to delay to allow the TPM time */
+ time_wait_ms(TPM_TIMEOUT_INTERVAL);
+ delay += TPM_TIMEOUT_INTERVAL;
+ continue;
+ }
+ /*
+ * Send in some data
+ */
+ curBytePtr = &(bytePtr[curByte]);
+ tx_len = (curByte + burst_count > length ?
+ (length - curByte) : burst_count);
+ rc = tpm_i2c_request_send(tpm_device->bus_id,
+ tpm_device->xscom_base,
+ SMBUS_WRITE, NUVOTON_TPM_DATA_FIFO_W,
+ 1, curBytePtr, tx_len);
+ curByte += tx_len;
+ DBG("%s write FIFO sent %zd bytes."
+ " burstcount polling delay=%d/%d, rc=%d\n",
+ (rc) ? "!!!!" : "----", curByte, delay,
+ TCG_PTP_TIMEOUT_D, rc);
+ delay = 0;
+ if (rc < 0)
+ return rc;
+
+ if (!tpm_is_expecting(&rc)) {
+ /**
+ * @fwts-label TPMWriteFifoOverflow1
+ * @fwts-advice The write to the TPM FIFO overflowed,
+ * the TPM is not expecting more data. This indicates a bug
+ * in the TPM device driver.
+ */
+ prlog(PR_ERR, "TPM: write FIFO overflow1\n");
+ return STB_TPM_OVERFLOW;
+ }
+ /* Everything but the last byte sent? */
+ if (curByte >= length)
+ break;
+ } while (delay < TCG_PTP_TIMEOUT_D);
+
+ if (delay < TCG_PTP_TIMEOUT_D) {
+ /*
+ * Send the final byte
+ */
+ delay = 0;
+ do {
+ rc = tpm_read_burst_count(&burst_count);
+ if (rc < 0) {
+ return rc;
+ } else if (burst_count == 0) {
+ /* Need to delay to allow the TPM time */
+ time_wait_ms(TPM_TIMEOUT_INTERVAL);
+ delay += TPM_TIMEOUT_INTERVAL;
+ continue;
+ }
+ curBytePtr = &(bytePtr[curByte]);
+ rc = tpm_i2c_request_send(tpm_device->bus_id,
+ tpm_device->xscom_base,
+ SMBUS_WRITE,
+ NUVOTON_TPM_DATA_FIFO_W, 1,
+ curBytePtr, 1);
+ DBG("%s write FIFO sent last byte, delay=%d/%d,"
+ " rc=%d\n",
+ (rc) ? "!!!!" : "----", delay,
+ TCG_PTP_TIMEOUT_D, rc);
+ break;
+ } while (delay < TCG_PTP_TIMEOUT_D);
+ }
+
+ if (delay >= TCG_PTP_TIMEOUT_D) {
+ /**
+ * @fwts-label TPMWriteBurstcountBitTimeout
+ * @fwts-advice The burstcount bit of the tpm status register is
+ * taking longer to be settled. Either the wait time need to be
+ * increased or the TPM device is not functional.
+ */
+ prlog(PR_ERR, "TPM: write FIFO, burstcount polling timeout."
+ " delay=%d/%d\n", delay, TCG_PTP_TIMEOUT_D);
+ return STB_TPM_TIMEOUT;
+ }
+ if (rc == 0) {
+ if (tpm_is_expecting(&rc)) {
+ /**
+ * @fwts-label TPMWriteFifoOverflow2
+ * @fwts-advice The write to the TPM FIFO overflowed.
+ * It is expecting more data even though we think we
+ * are done. This indicates a bug in the TPM device
+ * driver.
+ */
+ prlog(PR_ERR, "TPM: write FIFO overflow2\n");
+ return STB_TPM_OVERFLOW;
+ }
+ }
+ return rc;
+}
+
+static int tpm_read_fifo(uint8_t* buf, size_t* buflen)
+{
+ int rc;
+ uint8_t burst_count;
+ int delay = 0;
+ size_t curByte = 0;
+ uint8_t* bytePtr = (uint8_t*)buf;
+ uint8_t* curBytePtr = NULL;
+
+ rc = tpm_poll_for_data_avail();
+
+ if (rc == 0) {
+ do {
+ rc = tpm_read_burst_count(&burst_count);
+ if (rc < 0) {
+ break;
+ } else if (burst_count == 0) {
+ /* Need to delay to allow the TPM time */
+ time_wait_ms(TPM_TIMEOUT_INTERVAL);
+ delay += TPM_TIMEOUT_INTERVAL;
+ continue;
+ }
+ /* Buffer overflow check */
+ if (curByte + burst_count > *buflen)
+ {
+ /**
+ * @fwts-label TPMReadFifoOverflow1
+ * @fwts-advice The read from TPM FIFO overflowed. It is
+ * expecting more data even though we think we are done.
+ * This indicates a bug in the TPM device driver.
+ */
+ prlog(PR_ERR, "TPM: read FIFO overflow1. delay %d/%d\n",
+ delay, TCG_PTP_TIMEOUT_D);
+ rc = STB_TPM_OVERFLOW;
+ }
+ /*
+ * Read some data
+ */
+ curBytePtr = &(bytePtr[curByte]);
+ rc = tpm_i2c_request_send(tpm_device->bus_id,
+ tpm_device->xscom_base,
+ SMBUS_READ,
+ NUVOTON_TPM_DATA_FIFO_R, 1,
+ curBytePtr, burst_count);
+ curByte += burst_count;
+ DBG("%s read FIFO. received %zd bytes. burstcount"
+ " polling delay=%d/%d, rc=%d\n",
+ (rc) ? "!!!!" : "----", curByte, delay,
+ TCG_PTP_TIMEOUT_D, rc);
+ delay = 0;
+ if (rc < 0)
+ break;
+ if (!tpm_is_data_avail(&rc))
+ break;
+ } while (delay < TCG_PTP_TIMEOUT_D);
+ }
+
+ if (rc == 0 && delay >= TCG_PTP_TIMEOUT_D) {
+ /**
+ * @fwts-label TPMReadBurstcountBitTimeout
+ * @fwts-advice The burstcount bit of the tpm status register is
+ * taking longer to be settled. Either the wait time needs to be
+ * increased or the TPM device is not functional.
+ */
+ prlog(PR_ERR, "TPM: read FIFO, burstcount polling timeout."
+ " delay=%d/%d\n",
+ delay, TCG_PTP_TIMEOUT_D);
+ return STB_TPM_TIMEOUT;
+ }
+ if (rc == 0)
+ *buflen = curByte;
+ else
+ *buflen = 0;
+ return rc;
+}
+
+static int tpm_transmit(struct tpm_dev *dev, uint8_t* buf, size_t cmdlen,
+ size_t* buflen)
+{
+ int rc = 0;
+ if (!dev) {
+ /**
+ * @fwts-label TPMDeviceNotInitialized
+ * @fwts-advice TPM device is not initialized. This indicates a
+ * bug in the tpm_transmit() caller
+ */
+ prlog(PR_ERR, "TPM: tpm device not initialized\n");
+ return STB_ARG_ERROR;
+ }
+ tpm_device = dev;
+ DBG("**** %s: dev %#x/%#x buf %016llx cmdlen %zu"
+ " buflen %zu ****\n",
+ __func__, dev->bus_id, dev->xscom_base, *(uint64_t*) buf,
+ cmdlen, *buflen);
+
+ DBG("step 1/5: check command ready\n");
+ if (!tpm_is_command_ready(&rc)) {
+ if (rc < 0)
+ goto out;
+ rc = tpm_poll_for_command_ready();
+ if (rc < 0)
+ goto out;
+ }
+
+ DBG("step 2/5: write FIFO\n");
+ rc = tpm_write_fifo(buf, cmdlen);
+ if (rc < 0)
+ goto out;
+
+ DBG("step 3/5: write tpmgo\n");
+ rc = tpm_status_write_byte(TCG_PTP_STS_GO);
+ if (rc < 0)
+ goto out;
+
+ DBG("step 4/5: read FIFO\n");
+ rc = tpm_read_fifo(buf, buflen);
+ if (rc < 0)
+ goto out;
+
+ DBG("step 5/5: write command ready\n");
+ rc = tpm_status_write_byte(TCG_PTP_STS_COMMAND_READY);
+
+out:
+ DBG("**** tpm_transmit %s, rc=%d ****\n",
+ (rc) ? "ERROR" : "SUCCESS", rc);
+ return rc;
+}
+
+static struct tpm_driver tpm_i2c_nuvoton_driver = {
+ .name = DRIVER_NAME,
+ .transmit = tpm_transmit,
+};
+
+void tpm_i2c_nuvoton_probe(void)
+{
+ struct tpm_dev *tpm_device = NULL;
+ struct dt_node *node = NULL;
+
+ dt_for_each_compatible(dt_root, node, "nuvoton,npct650") {
+ if (!dt_node_is_enabled(node))
+ continue;
+ tpm_device = (struct tpm_dev*) malloc(sizeof(struct tpm_dev));
+ assert(tpm_device);
+ /*
+ * Read TPM device address and bus id. Make sure the properties
+ * really exist if the default value is returned.
+ */
+ tpm_device->xscom_base = dt_prop_get_u32_def(node, "reg", 0);
+ if (!tpm_device->xscom_base &&
+ !dt_find_property(node, "reg")) {
+ /*
+ * @fwts-label NuvotonRegNotFound
+ * @fwts-advice reg property not found. This indicates
+ * a Hostboot bug if the property really doesn't exist
+ * in the tpm node.
+ */
+ prlog(PR_ERR, "NUVOTON: reg property not found, "
+ "tpm node %p\n", node);
+ goto disable;
+ }
+ tpm_device->bus_id = dt_prop_get_u32_def(node->parent,
+ "ibm,opal-id", 0);
+ if (!tpm_device->bus_id &&
+ !dt_find_property(node->parent, "ibm,opal-id")) {
+ /*
+ * @fwts-label NuvotonIbmOpalIdNotFound
+ * @fwts-advice ibm,opal-id property not found. This
+ * indicates a Hostboot bug if the property really
+ * doesn't exist in the tpm node.
+ */
+ prlog(PR_ERR, "NUVOTON: ibm,opal-id property not "
+ "found, tpm node parent %p\n", node->parent);
+ goto disable;
+ }
+ if (tpm_register_chip(node, tpm_device,
+ &tpm_i2c_nuvoton_driver))
+ free(tpm_device);
+ }
+ return;
+disable:
+ dt_add_property_string(node, "status", "disabled");
+ prlog(PR_NOTICE, "TPM: tpm node %p disabled\n", node);
+ free(tpm_device);
+}
diff --git a/libstb/drivers/tpm_i2c_nuvoton.h b/libstb/drivers/tpm_i2c_nuvoton.h
new file mode 100644
index 0000000..4d8a0b2
--- /dev/null
+++ b/libstb/drivers/tpm_i2c_nuvoton.h
@@ -0,0 +1,22 @@
+/* Copyright 2013-2016 IBM Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __TPM_I2C_NUVOTON_H
+#define __TPM_I2C_NUVOTON_H
+
+extern void tpm_i2c_nuvoton_probe(void);
+
+#endif /* __TPM_I2C_NUVOTON_H */
diff --git a/libstb/status_codes.h b/libstb/status_codes.h
index 385e764..1637e9f 100644
--- a/libstb/status_codes.h
+++ b/libstb/status_codes.h
@@ -25,4 +25,8 @@
/* secure boot */
#define STB_VERIFY_FAILED -100
+/* TPM */
+#define STB_TPM_OVERFLOW -300
+#define STB_TPM_TIMEOUT -301
+
#endif /* __STB_STATUS_CODES_H */
diff --git a/libstb/tpm_chip.c b/libstb/tpm_chip.c
index 0fc3f89..ee297c2 100644
--- a/libstb/tpm_chip.c
+++ b/libstb/tpm_chip.c
@@ -21,6 +21,7 @@
#include "status_codes.h"
#include "container.h"
#include "tpm_chip.h"
+#include "drivers/tpm_i2c_nuvoton.h"
static struct list_head tpm_list = LIST_HEAD_INIT(tpm_list);
@@ -78,6 +79,7 @@ void tpm_init(void)
list_head_init(&tpm_list);
/* tpm drivers supported */
+ tpm_i2c_nuvoton_probe();
if (list_empty(&tpm_list))
/**
--
2.7.4
More information about the Skiboot
mailing list