diff options
Diffstat (limited to 'drivers/char/tpm/tpm_tis_core.c')
-rw-r--r-- | drivers/char/tpm/tpm_tis_core.c | 193 |
1 files changed, 185 insertions, 8 deletions
diff --git a/drivers/char/tpm/tpm_tis_core.c b/drivers/char/tpm/tpm_tis_core.c index fdde971bc810..183a5f54d875 100644 --- a/drivers/char/tpm/tpm_tis_core.c +++ b/drivers/char/tpm/tpm_tis_core.c @@ -31,6 +31,74 @@ #include "tpm.h" #include "tpm_tis_core.h" +/* This is a polling delay to check for status and burstcount. + * As per ddwg input, expectation is that status check and burstcount + * check should return within few usecs. + */ +#define TPM_POLL_SLEEP 1 /* msec */ + +static void tpm_tis_clkrun_enable(struct tpm_chip *chip, bool value); + +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; +} + +static 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->flags & TPM_CHIP_FLAG_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 { + tpm_msleep(TPM_POLL_SLEEP); + status = chip->ops->status(chip); + if ((status & mask) == mask) + return 0; + } while (time_before(jiffies, stop)); + } + return -ETIME; +} + /* 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 @@ -164,7 +232,7 @@ static int get_burstcount(struct tpm_chip *chip) burstcnt = (value >> 8) & 0xFFFF; if (burstcnt) return burstcnt; - tpm_msleep(TPM_TIMEOUT); + tpm_msleep(TPM_POLL_SLEEP); } while (time_before(jiffies, stop)); return -EBUSY; } @@ -421,19 +489,28 @@ static bool tpm_tis_update_timeouts(struct tpm_chip *chip, int i, rc; u32 did_vid; + if (chip->ops->clk_enable != NULL) + chip->ops->clk_enable(chip, true); + rc = tpm_tis_read32(priv, TPM_DID_VID(0), &did_vid); if (rc < 0) - return rc; + goto out; 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; + rc = true; } - return false; + rc = false; + +out: + if (chip->ops->clk_enable != NULL) + chip->ops->clk_enable(chip, false); + + return rc; } /* @@ -653,14 +730,73 @@ void tpm_tis_remove(struct tpm_chip *chip) u32 interrupt; int rc; + tpm_tis_clkrun_enable(chip, true); + rc = tpm_tis_read32(priv, reg, &interrupt); if (rc < 0) interrupt = 0; tpm_tis_write32(priv, reg, ~TPM_GLOBAL_INT_ENABLE & interrupt); + + tpm_tis_clkrun_enable(chip, false); + + if (priv->ilb_base_addr) + iounmap(priv->ilb_base_addr); } EXPORT_SYMBOL_GPL(tpm_tis_remove); +/** + * tpm_tis_clkrun_enable() - Keep clkrun protocol disabled for entire duration + * of a single TPM command + * @chip: TPM chip to use + * @value: 1 - Disable CLKRUN protocol, so that clocks are free running + * 0 - Enable CLKRUN protocol + * Call this function directly in tpm_tis_remove() in error or driver removal + * path, since the chip->ops is set to NULL in tpm_chip_unregister(). + */ +static void tpm_tis_clkrun_enable(struct tpm_chip *chip, bool value) +{ + struct tpm_tis_data *data = dev_get_drvdata(&chip->dev); + u32 clkrun_val; + + if (!IS_ENABLED(CONFIG_X86) || !is_bsw() || + !data->ilb_base_addr) + return; + + if (value) { + data->clkrun_enabled++; + if (data->clkrun_enabled > 1) + return; + clkrun_val = ioread32(data->ilb_base_addr + LPC_CNTRL_OFFSET); + + /* Disable LPC CLKRUN# */ + clkrun_val &= ~LPC_CLKRUN_EN; + iowrite32(clkrun_val, data->ilb_base_addr + LPC_CNTRL_OFFSET); + + /* + * Write any random value on port 0x80 which is on LPC, to make + * sure LPC clock is running before sending any TPM command. + */ + outb(0xCC, 0x80); + } else { + data->clkrun_enabled--; + if (data->clkrun_enabled) + return; + + clkrun_val = ioread32(data->ilb_base_addr + LPC_CNTRL_OFFSET); + + /* Enable LPC CLKRUN# */ + clkrun_val |= LPC_CLKRUN_EN; + iowrite32(clkrun_val, data->ilb_base_addr + LPC_CNTRL_OFFSET); + + /* + * Write any random value on port 0x80 which is on LPC, to make + * sure LPC clock is running before sending any TPM command. + */ + outb(0xCC, 0x80); + } +} + static const struct tpm_class_ops tpm_tis = { .flags = TPM_OPS_AUTO_STARTUP, .status = tpm_tis_status, @@ -673,13 +809,17 @@ static const struct tpm_class_ops tpm_tis = { .req_canceled = tpm_tis_req_canceled, .request_locality = request_locality, .relinquish_locality = release_locality, + .clk_enable = tpm_tis_clkrun_enable, }; int tpm_tis_core_init(struct device *dev, struct tpm_tis_data *priv, int irq, const struct tpm_tis_phy_ops *phy_ops, acpi_handle acpi_dev_handle) { - u32 vendor, intfcaps, intmask; + u32 vendor; + u32 intfcaps; + u32 intmask; + u32 clkrun_val; u8 rid; int rc, probe; struct tpm_chip *chip; @@ -700,6 +840,23 @@ int tpm_tis_core_init(struct device *dev, struct tpm_tis_data *priv, int irq, priv->phy_ops = phy_ops; dev_set_drvdata(&chip->dev, priv); + if (is_bsw()) { + priv->ilb_base_addr = ioremap(INTEL_LEGACY_BLK_BASE_ADDR, + ILB_REMAP_SIZE); + if (!priv->ilb_base_addr) + return -ENOMEM; + + clkrun_val = ioread32(priv->ilb_base_addr + LPC_CNTRL_OFFSET); + /* Check if CLKRUN# is already not enabled in the LPC bus */ + if (!(clkrun_val & LPC_CLKRUN_EN)) { + iounmap(priv->ilb_base_addr); + priv->ilb_base_addr = NULL; + } + } + + if (chip->ops->clk_enable != NULL) + chip->ops->clk_enable(chip, true); + if (wait_startup(chip, 0) != 0) { rc = -ENODEV; goto out_err; @@ -790,9 +947,20 @@ int tpm_tis_core_init(struct device *dev, struct tpm_tis_data *priv, int irq, } } - return tpm_chip_register(chip); + rc = tpm_chip_register(chip); + if (rc) + goto out_err; + + if (chip->ops->clk_enable != NULL) + chip->ops->clk_enable(chip, false); + + return 0; out_err: + if ((chip->ops != NULL) && (chip->ops->clk_enable != NULL)) + chip->ops->clk_enable(chip, false); + tpm_tis_remove(chip); + return rc; } EXPORT_SYMBOL_GPL(tpm_tis_core_init); @@ -804,22 +972,31 @@ static void tpm_tis_reenable_interrupts(struct tpm_chip *chip) u32 intmask; int rc; + if (chip->ops->clk_enable != NULL) + chip->ops->clk_enable(chip, true); + /* reenable interrupts that device may have lost or * BIOS/firmware may have disabled */ rc = tpm_tis_write8(priv, TPM_INT_VECTOR(priv->locality), priv->irq); if (rc < 0) - return; + goto out; rc = tpm_tis_read32(priv, TPM_INT_ENABLE(priv->locality), &intmask); if (rc < 0) - return; + goto out; 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; tpm_tis_write32(priv, TPM_INT_ENABLE(priv->locality), intmask); + +out: + if (chip->ops->clk_enable != NULL) + chip->ops->clk_enable(chip, false); + + return; } int tpm_tis_resume(struct device *dev) |