diff options
Diffstat (limited to 'libstb')
-rw-r--r-- | libstb/drivers/Makefile.inc | 2 | ||||
-rw-r--r-- | libstb/drivers/tpm_i2c_nuvoton.c | 504 | ||||
-rw-r--r-- | libstb/drivers/tpm_i2c_nuvoton.h | 22 | ||||
-rw-r--r-- | libstb/status_codes.h | 4 | ||||
-rw-r--r-- | libstb/tpm_chip.c | 2 |
5 files changed, 533 insertions, 1 deletions
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)) /** |