/* SPDX-License-Identifier: BSD-3-Clause */ /* * This is a driver for a CRB Interface. * * The general flow looks like this: * * TPM starts in IDLE Mode * * IDLE --> READY --> Command Reception * ^ | * | v -- Cmd Complete <-- Command Execution */ #include <timer.h> #include <console/console.h> #include <device/mmio.h> #include <string.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 *)(uintptr_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 *)(uintptr_t)read64(CRB_REG(cur_loc, CRB_REG_RESP_ADDR)); /* * Intel PTT has to write the command/response address and size * register before each command submission otherwise the control area * is all zeroed. This has been observed on Alder Lake S CPU and may be * applicable to other new microarchitectures. Update the local control * area data to make tpm2_process_command not fail on buffer checks. * PTT command/response buffer is fixed to be at offset 0x80 and spans * up to the end of 4KB region for the current locality. */ if (CONFIG(HAVE_INTEL_PTT)) { control_area.command_size = 0x1000 - CRB_REG_DATA_BUFF; control_area.response_size = control_area.command_size; control_area.command_bfr = CRB_REG(cur_loc, CRB_REG_DATA_BUFF); control_area.response_bfr = CRB_REG(cur_loc, CRB_REG_DATA_BUFF); } } /* 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(); /* * PTT may have no assigned locality before startup. Request locality here to save * some precious milliseconds which would be wasted in crb_activate_locality polling * for LOC_STATE_LOC_ASSIGN bit for the first time. */ if (CONFIG(HAVE_INTEL_PTT)) { uint8_t locality = (read8(CRB_REG(0, CRB_REG_LOC_STATE)) >> 2) & 0x07; write8(CRB_REG(locality, CRB_REG_LOC_CTRL), LOC_CTRL_REQ_ACCESS); } /* Good to go. */ printk(BIOS_SPEW, "TPM: CRB TPM initialized successfully\n"); return 0; } static void set_ptt_cmd_resp_buffers(void) { write32(CRB_REG(cur_loc, CRB_REG_CMD_ADDR + 4), 0); write32(CRB_REG(cur_loc, CRB_REG_CMD_ADDR), (uintptr_t)CRB_REG(cur_loc, CRB_REG_DATA_BUFF)); write32(CRB_REG(cur_loc, CRB_REG_CMD_SIZE), control_area.command_size); write64(CRB_REG(cur_loc, CRB_REG_RESP_ADDR), (uintptr_t)CRB_REG(cur_loc, CRB_REG_DATA_BUFF)); write32(CRB_REG(cur_loc, CRB_REG_RESP_SIZE), control_area.response_size); } /* * 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); /* * Initialize CRB addresses and sizes for PTT. It seems to be possible * only after CRB is switched to ready and before writing start bit. * This is also what EDK2 TPM CRB drivers do. */ if (CONFIG(HAVE_INTEL_PTT)) set_ptt_cmd_resp_buffers(); // 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; }