diff options
Diffstat (limited to 'src/drivers/crb/tpm.c')
-rw-r--r-- | src/drivers/crb/tpm.c | 280 |
1 files changed, 280 insertions, 0 deletions
diff --git a/src/drivers/crb/tpm.c b/src/drivers/crb/tpm.c new file mode 100644 index 0000000000..0393417e74 --- /dev/null +++ b/src/drivers/crb/tpm.c @@ -0,0 +1,280 @@ +/*. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * This is a driver for a CRB Interface. + * + * The general flow looks like this: + * + * TPM starts in IDLE Mode + * + * IDLE --> READY --> Command Receiption + * ^ | + * | v + -- Cmd Complete <-- Command Execution + */ + +#include <timer.h> +#include <arch/early_variables.h> +#include <console/console.h> +#include <arch/mmio.h> +#include <delay.h> +#include <string.h> +#include <endian.h> +#include <soc/pci_devs.h> +#include <device/pci_ops.h> + +#include "tpm.h" + +static struct control_area { + uint32_t request; + uint32_t status; + uint32_t cancel; + uint32_t start; + uint64_t interrupt_control; + uint32_t command_size; + void *command_bfr; + uint32_t response_size; + void *response_bfr; +} control_area; + +static uint8_t cur_loc = 0; + +/* Read Control Area Structure back */ +static void crb_readControlArea(void) +{ + control_area.request = read32(CRB_REG(cur_loc, CRB_REG_REQUEST)); + control_area.status = read32(CRB_REG(cur_loc, CRB_REG_STATUS)); + control_area.cancel = read32(CRB_REG(cur_loc, CRB_REG_CANCEL)); + control_area.interrupt_control = read64(CRB_REG(cur_loc, CRB_REG_INT_CTRL)); + control_area.command_size = read32(CRB_REG(cur_loc, CRB_REG_CMD_SIZE)); + control_area.command_bfr = (void *)(uint32_t)read64(CRB_REG(cur_loc, CRB_REG_CMD_ADDR)); + control_area.response_size = read32(CRB_REG(cur_loc, CRB_REG_RESP_SIZE)); + control_area.response_bfr = + (void *)(uint32_t)read64(CRB_REG(cur_loc, CRB_REG_RESP_ADDR)); +} + +/* Wait for Reg to be expected Value */ +static int crb_wait_for_reg32(const void *addr, uint32_t timeoutMs, uint32_t mask, + uint32_t expectedValue) +{ + uint32_t regValue; + struct stopwatch sw; + + // Set up a timer which breaks the loop after timeout + stopwatch_init_msecs_expire(&sw, timeoutMs); + + while (1) { + // Now check if the TPM is in IDLE mode + regValue = read32(addr); + + if ((regValue & mask) == expectedValue) + return 0; + + if (stopwatch_expired(&sw)) { + printk(BIOS_ERR, + "CRB_WAIT: Error - Returning Zero with RegValue: %08x, Mask: %08x, Expected: %08x\n", + regValue, mask, expectedValue); + return -1; + } + } +} + +/* CRB PROBE + * + * Checks if the CRB Interface is ready + */ +static int crb_probe(void) +{ + uint64_t tpmStatus = read64(CRB_REG(cur_loc, CRB_REG_INTF_ID)); + printk(BIOS_SPEW, "Interface ID Reg. %llx\n", tpmStatus); + + if ((tpmStatus & CRB_INTF_REG_CAP_CRB) == 0) { + printk(BIOS_DEBUG, "TPM: CRB Interface is not supported.\n"); + return -1; + } + + if ((tpmStatus & (0xf)) != 1) { + printk(BIOS_DEBUG, + "TPM: CRB Interface is not active. System needs reboot in order to active TPM.\n"); + write32(CRB_REG(cur_loc, CRB_REG_INTF_ID), CRB_INTF_REG_INTF_SEL); + return -1; + } + + write32(CRB_REG(cur_loc, CRB_REG_INTF_ID), CRB_INTF_REG_INTF_SEL); + write32(CRB_REG(cur_loc, CRB_REG_INTF_ID), CRB_INTF_REG_INTF_LOCK); + + return 0; +} + +/* + * Get active Locality + * + * Get the active locality + */ +static uint8_t crb_activate_locality(void) +{ + + uint8_t locality = (read8(CRB_REG(0, CRB_REG_LOC_STATE)) >> 2) & 0x07; + printk(BIOS_SPEW, "Active locality: %i\n", locality); + + int rc = crb_wait_for_reg32(CRB_REG(locality, CRB_REG_LOC_STATE), 750, + LOC_STATE_LOC_ASSIGN, LOC_STATE_LOC_ASSIGN); + if (!rc && (locality == 0)) + return locality; + + if (rc) + write8(CRB_REG(locality, CRB_REG_LOC_CTRL), LOC_CTRL_REQ_ACCESS); + + + rc = crb_wait_for_reg32(CRB_REG(locality, CRB_REG_LOC_STATE), 750, LOC_STATE_LOC_ASSIGN, + LOC_STATE_LOC_ASSIGN); + if (rc) { + printk(BIOS_ERR, "TPM: Error - No Locality has been assigned TPM-wise.\n"); + return 0; + } + + rc = crb_wait_for_reg32(CRB_REG(locality, CRB_REG_LOC_STATE), 1500, + LOC_STATE_REG_VALID_STS, LOC_STATE_REG_VALID_STS); + if (rc) { + printk(BIOS_ERR, "TPM: Error - LOC_STATE Register %u contains errors.\n", + locality); + return 0; + } + + + return locality; +} + +/* Switch Device into a Ready State */ +static int crb_switch_to_ready(void) +{ + /* Transition into ready state */ + write8(CRB_REG(cur_loc, CRB_REG_REQUEST), 0x1); + int rc = crb_wait_for_reg32(CRB_REG(cur_loc, CRB_REG_REQUEST), 200, + CRB_REG_REQUEST_CMD_RDY, 0x0); + if (rc) { + printk(BIOS_ERR, + "TPM: Error - TPM did not transition into ready state in time.\n"); + return -1; + } + + /* Check TPM_CRB_CTRL_STS[0] to be "0" - no unrecoverable error */ + rc = crb_wait_for_reg32(CRB_REG(cur_loc, CRB_REG_STATUS), 500, CRB_REG_STATUS_ERROR, + 0x0); + if (rc) { + printk(BIOS_ERR, "TPM: Fatal Error - Could not recover.\n"); + return -1; + } + + return 0; +} + +/* + * tpm2_init + * + * Even though the TPM does not need an initialization we check + * if the TPM responds and is in IDLE mode, which should be the + * normal bring up mode. + * + */ +int tpm2_init(void) +{ + + + if (crb_probe()) { + printk(BIOS_ERR, "TPM: Probe failed.\n"); + return -1; + } + + /* Read back control area structure */ + crb_readControlArea(); + + /* Good to go. */ + printk(BIOS_SPEW, "TPM: CRB TPM initialized successfully\n"); + + return 0; +} + +/* + * tpm2_process_command + */ +size_t tpm2_process_command(const void *tpm2_command, size_t command_size, void *tpm2_response, + size_t max_response) +{ + int rc; + + if (command_size > control_area.command_size) { + printk(BIOS_ERR, "TPM: Command size is too big.\n"); + return -1; + } + + if (control_area.response_size < max_response) { + printk(BIOS_ERR, "TPM: Response size could be too big.\n"); + return -1; + } + + cur_loc = crb_activate_locality(); + + // Check if CMD bit is cleared. + rc = crb_wait_for_reg32(CRB_REG(0, CRB_REG_START), 250, CRB_REG_START_START, 0x0); + if (rc) { + printk(BIOS_ERR, "TPM: Error - Cmd Bit not cleared.\n"); + return -1; + } + + if (crb_switch_to_ready()) + return -1; + + // Write to Command Buffer + memcpy(control_area.command_bfr, tpm2_command, command_size); + + // Write Start Bit + write8(CRB_REG(cur_loc, CRB_REG_START), 0x1); + + // Poll for Response + rc = crb_wait_for_reg32(CRB_REG(cur_loc, CRB_REG_START), 3500, CRB_REG_START_START, 0); + if (rc) { + printk(BIOS_DEBUG, "TPM: Command Timed out.\n"); + return -1; + } + + // Check for errors + rc = crb_wait_for_reg32(CRB_REG(cur_loc, CRB_REG_STATUS), 200, CRB_REG_STATUS_ERROR, 0); + if (rc) { + printk(BIOS_DEBUG, "TPM: Command errored.\n"); + return -1; + } + + // Get Response Length + uint32_t length = be32_to_cpu(read32(control_area.response_bfr + 2)); + + /* Response has to have at least 6 bytes */ + if (length < 6) + return 1; + + // Copy Response + memcpy(tpm2_response, control_area.response_bfr, length); + + if (crb_switch_to_ready()) { + printk(BIOS_DEBUG, "TPM: Can not transition into ready state again.\n"); + return -1; + } + + return length; +} + +/* + * tp2_get_info + * + * Returns information about the TPM + * + */ +void tpm2_get_info(struct tpm2_info *tpm2_info) +{ + uint64_t interfaceReg = read64(CRB_REG(cur_loc, CRB_REG_INTF_ID)); + + tpm2_info->vendor_id = (interfaceReg >> 48) & 0xFFFF; + tpm2_info->device_id = (interfaceReg >> 32) & 0xFFFF; + tpm2_info->revision = (interfaceReg >> 24) & 0xFF; +} |