diff options
Diffstat (limited to 'drivers/char/tpm/tpm_tis.c')
| -rw-r--r-- | drivers/char/tpm/tpm_tis.c | 399 |
1 files changed, 252 insertions, 147 deletions
diff --git a/drivers/char/tpm/tpm_tis.c b/drivers/char/tpm/tpm_tis.c index dd21df55689..a9ed2270c25 100644 --- a/drivers/char/tpm/tpm_tis.c +++ b/drivers/char/tpm/tpm_tis.c @@ -26,10 +26,9 @@ #include <linux/interrupt.h> #include <linux/wait.h> #include <linux/acpi.h> +#include <linux/freezer.h> #include "tpm.h" -#define TPM_HEADER_SIZE 10 - enum tis_access { TPM_ACCESS_VALID = 0x80, TPM_ACCESS_ACTIVE_LOCALITY = 0x20, @@ -77,14 +76,17 @@ enum tis_defaults { #define TPM_RID(l) (0x0F04 | ((l) << 12)) static LIST_HEAD(tis_chips); -static DEFINE_SPINLOCK(tis_lock); +static DEFINE_MUTEX(tis_lock); -#ifdef CONFIG_ACPI +#if defined(CONFIG_PNP) && defined(CONFIG_ACPI) static int is_itpm(struct pnp_dev *dev) { struct acpi_device *acpi = pnp_acpi_device(dev); struct acpi_hardware_id *id; + if (!acpi) + return 0; + list_for_each_entry(id, &acpi->pnp.ids, list) { if (!strcmp("INTC0102", id->id)) return 1; @@ -93,12 +95,28 @@ static int is_itpm(struct pnp_dev *dev) return 0; } #else -static int is_itpm(struct pnp_dev *dev) +static inline int is_itpm(struct pnp_dev *dev) { return 0; } #endif +/* 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 + * correct values in the other bits.' */ +static int wait_startup(struct tpm_chip *chip, int l) +{ + unsigned long stop = jiffies + chip->vendor.timeout_a; + do { + if (ioread8(chip->vendor.iobase + TPM_ACCESS(l)) & + TPM_ACCESS_VALID) + return 0; + msleep(TPM_TIMEOUT); + } while (time_before(jiffies, stop)); + return -1; +} + static int check_locality(struct tpm_chip *chip, int l) { if ((ioread8(chip->vendor.iobase + TPM_ACCESS(l)) & @@ -120,7 +138,7 @@ static void release_locality(struct tpm_chip *chip, int l, int force) static int request_locality(struct tpm_chip *chip, int l) { - unsigned long stop; + unsigned long stop, timeout; long rc; if (check_locality(chip, l) >= 0) @@ -129,17 +147,25 @@ static int request_locality(struct tpm_chip *chip, int l) iowrite8(TPM_ACCESS_REQUEST_USE, chip->vendor.iobase + TPM_ACCESS(l)); + stop = jiffies + chip->vendor.timeout_a; + if (chip->vendor.irq) { +again: + timeout = stop - jiffies; + if ((long)timeout <= 0) + return -1; rc = wait_event_interruptible_timeout(chip->vendor.int_queue, (check_locality (chip, l) >= 0), - chip->vendor.timeout_a); + timeout); if (rc > 0) return l; - + if (rc == -ERESTARTSYS && freezing(current)) { + clear_thread_flag(TIF_SIGPENDING); + goto again; + } } else { /* wait for burstcount */ - stop = jiffies + chip->vendor.timeout_a; do { if (check_locality(chip, l) >= 0) return l; @@ -184,45 +210,14 @@ static int get_burstcount(struct tpm_chip *chip) return -EBUSY; } -static int wait_for_stat(struct tpm_chip *chip, u8 mask, unsigned long timeout, - wait_queue_head_t *queue) -{ - unsigned long stop; - long rc; - u8 status; - - /* check current status */ - status = tpm_tis_status(chip); - if ((status & mask) == mask) - return 0; - - if (chip->vendor.irq) { - rc = wait_event_interruptible_timeout(*queue, - ((tpm_tis_status - (chip) & mask) == - mask), timeout); - if (rc > 0) - return 0; - } else { - stop = jiffies + timeout; - do { - msleep(TPM_TIMEOUT); - status = tpm_tis_status(chip); - if ((status & mask) == mask) - return 0; - } while (time_before(jiffies, stop)); - } - return -ETIME; -} - static int recv_data(struct tpm_chip *chip, u8 *buf, size_t count) { int size = 0, burstcnt; while (size < count && - wait_for_stat(chip, - TPM_STS_DATA_AVAIL | TPM_STS_VALID, - chip->vendor.timeout_c, - &chip->vendor.read_queue) + wait_for_tpm_stat(chip, + TPM_STS_DATA_AVAIL | TPM_STS_VALID, + chip->vendor.timeout_c, + &chip->vendor.read_queue, true) == 0) { burstcnt = get_burstcount(chip); for (; burstcnt > 0 && size < count; burstcnt--) @@ -264,8 +259,8 @@ static int tpm_tis_recv(struct tpm_chip *chip, u8 *buf, size_t count) goto out; } - wait_for_stat(chip, TPM_STS_VALID, chip->vendor.timeout_c, - &chip->vendor.int_queue); + wait_for_tpm_stat(chip, TPM_STS_VALID, chip->vendor.timeout_c, + &chip->vendor.int_queue, false); status = tpm_tis_status(chip); if (status & TPM_STS_DATA_AVAIL) { /* retry? */ dev_err(chip->dev, "Error left over data\n"); @@ -279,7 +274,7 @@ out: return size; } -static int itpm; +static bool itpm; module_param(itpm, bool, 0444); MODULE_PARM_DESC(itpm, "Force iTPM workarounds (found on some Lenovo laptops)"); @@ -288,11 +283,10 @@ MODULE_PARM_DESC(itpm, "Force iTPM workarounds (found on some Lenovo laptops)"); * tpm.c can skip polling for the data to be available as the interrupt is * waited for here */ -static int tpm_tis_send(struct tpm_chip *chip, u8 *buf, size_t len) +static int tpm_tis_send_data(struct tpm_chip *chip, u8 *buf, size_t len) { int rc, status, burstcnt; size_t count = 0; - u32 ordinal; if (request_locality(chip, 0) < 0) return -EBUSY; @@ -300,9 +294,9 @@ static int tpm_tis_send(struct tpm_chip *chip, u8 *buf, size_t len) status = tpm_tis_status(chip); if ((status & TPM_STS_COMMAND_READY) == 0) { tpm_tis_ready(chip); - if (wait_for_stat + if (wait_for_tpm_stat (chip, TPM_STS_COMMAND_READY, chip->vendor.timeout_b, - &chip->vendor.int_queue) < 0) { + &chip->vendor.int_queue, false) < 0) { rc = -ETIME; goto out_err; } @@ -316,8 +310,8 @@ static int tpm_tis_send(struct tpm_chip *chip, u8 *buf, size_t len) count++; } - wait_for_stat(chip, TPM_STS_VALID, chip->vendor.timeout_c, - &chip->vendor.int_queue); + wait_for_tpm_stat(chip, TPM_STS_VALID, chip->vendor.timeout_c, + &chip->vendor.int_queue, false); status = tpm_tis_status(chip); if (!itpm && (status & TPM_STS_DATA_EXPECT) == 0) { rc = -EIO; @@ -327,26 +321,47 @@ static int tpm_tis_send(struct tpm_chip *chip, u8 *buf, size_t len) /* write last byte */ iowrite8(buf[count], - chip->vendor.iobase + - TPM_DATA_FIFO(chip->vendor.locality)); - wait_for_stat(chip, TPM_STS_VALID, chip->vendor.timeout_c, - &chip->vendor.int_queue); + chip->vendor.iobase + TPM_DATA_FIFO(chip->vendor.locality)); + wait_for_tpm_stat(chip, TPM_STS_VALID, chip->vendor.timeout_c, + &chip->vendor.int_queue, false); status = tpm_tis_status(chip); if ((status & TPM_STS_DATA_EXPECT) != 0) { rc = -EIO; goto out_err; } + return 0; + +out_err: + tpm_tis_ready(chip); + release_locality(chip, chip->vendor.locality, 0); + return rc; +} + +/* + * If interrupts are used (signaled by an irq set in the vendor structure) + * tpm.c can skip polling for the data to be available as the interrupt is + * waited for here + */ +static int tpm_tis_send(struct tpm_chip *chip, u8 *buf, size_t len) +{ + int rc; + u32 ordinal; + + rc = tpm_tis_send_data(chip, buf, len); + if (rc < 0) + return rc; + /* go and do it */ iowrite8(TPM_STS_GO, chip->vendor.iobase + TPM_STS(chip->vendor.locality)); if (chip->vendor.irq) { ordinal = be32_to_cpu(*((__be32 *) (buf + 6))); - if (wait_for_stat + if (wait_for_tpm_stat (chip, TPM_STS_DATA_AVAIL | TPM_STS_VALID, tpm_calc_ordinal_duration(chip, ordinal), - &chip->vendor.read_queue) < 0) { + &chip->vendor.read_queue, false) < 0) { rc = -ETIME; goto out_err; } @@ -358,51 +373,73 @@ out_err: return rc; } -static const struct file_operations tis_ops = { - .owner = THIS_MODULE, - .llseek = no_llseek, - .open = tpm_open, - .read = tpm_read, - .write = tpm_write, - .release = tpm_release, -}; +/* + * Early probing for iTPM with STS_DATA_EXPECT flaw. + * Try sending command without itpm flag set and if that + * fails, repeat with itpm flag set. + */ +static int probe_itpm(struct tpm_chip *chip) +{ + int rc = 0; + u8 cmd_getticks[] = { + 0x00, 0xc1, 0x00, 0x00, 0x00, 0x0a, + 0x00, 0x00, 0x00, 0xf1 + }; + size_t len = sizeof(cmd_getticks); + bool rem_itpm = itpm; + u16 vendor = ioread16(chip->vendor.iobase + TPM_DID_VID(0)); + + /* probe only iTPMS */ + if (vendor != TPM_VID_INTEL) + return 0; -static DEVICE_ATTR(pubek, S_IRUGO, tpm_show_pubek, NULL); -static DEVICE_ATTR(pcrs, S_IRUGO, tpm_show_pcrs, NULL); -static DEVICE_ATTR(enabled, S_IRUGO, tpm_show_enabled, NULL); -static DEVICE_ATTR(active, S_IRUGO, tpm_show_active, NULL); -static DEVICE_ATTR(owned, S_IRUGO, tpm_show_owned, NULL); -static DEVICE_ATTR(temp_deactivated, S_IRUGO, tpm_show_temp_deactivated, - NULL); -static DEVICE_ATTR(caps, S_IRUGO, tpm_show_caps_1_2, NULL); -static DEVICE_ATTR(cancel, S_IWUSR | S_IWGRP, NULL, tpm_store_cancel); - -static struct attribute *tis_attrs[] = { - &dev_attr_pubek.attr, - &dev_attr_pcrs.attr, - &dev_attr_enabled.attr, - &dev_attr_active.attr, - &dev_attr_owned.attr, - &dev_attr_temp_deactivated.attr, - &dev_attr_caps.attr, - &dev_attr_cancel.attr, NULL, -}; + itpm = false; -static struct attribute_group tis_attr_grp = { - .attrs = tis_attrs -}; + rc = tpm_tis_send_data(chip, cmd_getticks, len); + if (rc == 0) + goto out; + + tpm_tis_ready(chip); + release_locality(chip, chip->vendor.locality, 0); + + itpm = true; + + rc = tpm_tis_send_data(chip, cmd_getticks, len); + if (rc == 0) { + dev_info(chip->dev, "Detected an iTPM.\n"); + rc = 1; + } else + rc = -EFAULT; + +out: + itpm = rem_itpm; + tpm_tis_ready(chip); + release_locality(chip, chip->vendor.locality, 0); + + return rc; +} -static struct tpm_vendor_specific tpm_tis = { +static bool tpm_tis_req_canceled(struct tpm_chip *chip, u8 status) +{ + switch (chip->vendor.manufacturer_id) { + case TPM_VID_WINBOND: + return ((status == TPM_STS_VALID) || + (status == (TPM_STS_VALID | TPM_STS_COMMAND_READY))); + case TPM_VID_STM: + return (status == (TPM_STS_VALID | TPM_STS_COMMAND_READY)); + default: + return (status == TPM_STS_COMMAND_READY); + } +} + +static const struct tpm_class_ops tpm_tis = { .status = tpm_tis_status, .recv = tpm_tis_recv, .send = tpm_tis_send, .cancel = tpm_tis_ready, .req_complete_mask = TPM_STS_DATA_AVAIL | TPM_STS_VALID, .req_complete_val = TPM_STS_DATA_AVAIL | TPM_STS_VALID, - .req_canceled = TPM_STS_COMMAND_READY, - .attr_group = &tis_attr_grp, - .miscdev = { - .fops = &tis_ops,}, + .req_canceled = tpm_tis_req_canceled, }; static irqreturn_t tis_int_probe(int irq, void *dev_id) @@ -416,7 +453,7 @@ static irqreturn_t tis_int_probe(int irq, void *dev_id) if (interrupt == 0) return IRQ_NONE; - chip->vendor.irq = irq; + chip->vendor.probed_irq = irq; /* Clear interrupts handled with TPM_EOI */ iowrite32(interrupt, @@ -456,7 +493,7 @@ static irqreturn_t tis_int_handler(int dummy, void *dev_id) return IRQ_HANDLED; } -static int interrupts = 1; +static bool interrupts = true; module_param(interrupts, bool, 0444); MODULE_PARM_DESC(interrupts, "Enable interrupts"); @@ -464,7 +501,7 @@ static int tpm_tis_init(struct device *dev, resource_size_t start, resource_size_t len, unsigned int irq) { u32 vendor, intfcaps, intmask; - int rc, i; + int rc, i, irq_s, irq_e, probe; struct tpm_chip *chip; if (!(chip = tpm_register_hardware(dev, &tpm_tis))) @@ -482,17 +519,32 @@ static int tpm_tis_init(struct device *dev, resource_size_t start, chip->vendor.timeout_c = msecs_to_jiffies(TIS_SHORT_TIMEOUT); chip->vendor.timeout_d = msecs_to_jiffies(TIS_SHORT_TIMEOUT); + if (wait_startup(chip, 0) != 0) { + rc = -ENODEV; + goto out_err; + } + if (request_locality(chip, 0) != 0) { rc = -ENODEV; goto out_err; } vendor = ioread32(chip->vendor.iobase + TPM_DID_VID(0)); + chip->vendor.manufacturer_id = vendor; dev_info(dev, "1.2 TPM (device-id 0x%X, rev-id %d)\n", vendor >> 16, ioread8(chip->vendor.iobase + TPM_RID(0))); + if (!itpm) { + probe = probe_itpm(chip); + if (probe < 0) { + rc = -ENODEV; + goto out_err; + } + itpm = !!probe; + } + if (itpm) dev_info(dev, "Intel iTPM workaround enabled\n"); @@ -522,6 +574,19 @@ static int tpm_tis_init(struct device *dev, resource_size_t start, if (intfcaps & TPM_INTF_DATA_AVAIL_INT) dev_dbg(dev, "\tData Avail Int Support\n"); + /* get the timeouts before testing for irqs */ + if (tpm_get_timeouts(chip)) { + dev_err(dev, "Could not get TPM timeouts and durations\n"); + rc = -ENODEV; + goto out_err; + } + + if (tpm_do_selftest(chip)) { + dev_err(dev, "TPM self test failed\n"); + rc = -ENODEV; + goto out_err; + } + /* INTERRUPT Setup */ init_waitqueue_head(&chip->vendor.read_queue); init_waitqueue_head(&chip->vendor.int_queue); @@ -540,13 +605,19 @@ static int tpm_tis_init(struct device *dev, resource_size_t start, if (interrupts) chip->vendor.irq = irq; if (interrupts && !chip->vendor.irq) { - chip->vendor.irq = + irq_s = ioread8(chip->vendor.iobase + TPM_INT_VECTOR(chip->vendor.locality)); + if (irq_s) { + irq_e = irq_s; + } else { + irq_s = 3; + irq_e = 15; + } - for (i = 3; i < 16 && chip->vendor.irq == 0; i++) { + for (i = irq_s; i <= irq_e && chip->vendor.irq == 0; i++) { iowrite8(i, chip->vendor.iobase + - TPM_INT_VECTOR(chip->vendor.locality)); + TPM_INT_VECTOR(chip->vendor.locality)); if (request_irq (i, tis_int_probe, IRQF_SHARED, chip->vendor.miscdev.name, chip) != 0) { @@ -568,9 +639,22 @@ static int tpm_tis_init(struct device *dev, resource_size_t start, chip->vendor.iobase + TPM_INT_ENABLE(chip->vendor.locality)); + chip->vendor.probed_irq = 0; + /* Generate Interrupts */ tpm_gen_interrupt(chip); + chip->vendor.irq = chip->vendor.probed_irq; + + /* free_irq will call into tis_int_probe; + clear all irqs we haven't seen while doing + tpm_gen_interrupt */ + iowrite32(ioread32 + (chip->vendor.iobase + + TPM_INT_STATUS(chip->vendor.locality)), + chip->vendor.iobase + + TPM_INT_STATUS(chip->vendor.locality)); + /* Turn off */ iowrite32(intmask, chip->vendor.iobase + @@ -605,12 +689,10 @@ static int tpm_tis_init(struct device *dev, resource_size_t start, } INIT_LIST_HEAD(&chip->vendor.list); - spin_lock(&tis_lock); + mutex_lock(&tis_lock); list_add(&chip->vendor.list, &tis_chips); - spin_unlock(&tis_lock); + mutex_unlock(&tis_lock); - tpm_get_timeouts(chip); - tpm_continue_selftest(chip); return 0; out_err: @@ -619,8 +701,49 @@ out_err: tpm_remove_hardware(chip->dev); return rc; } + +#ifdef CONFIG_PM_SLEEP +static void tpm_tis_reenable_interrupts(struct tpm_chip *chip) +{ + u32 intmask; + + /* reenable interrupts that device may have lost or + BIOS/firmware may have disabled */ + iowrite8(chip->vendor.irq, chip->vendor.iobase + + TPM_INT_VECTOR(chip->vendor.locality)); + + intmask = + ioread32(chip->vendor.iobase + + TPM_INT_ENABLE(chip->vendor.locality)); + + 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; + + iowrite32(intmask, + chip->vendor.iobase + TPM_INT_ENABLE(chip->vendor.locality)); +} + +static int tpm_tis_resume(struct device *dev) +{ + struct tpm_chip *chip = dev_get_drvdata(dev); + int ret; + + if (chip->vendor.irq) + tpm_tis_reenable_interrupts(chip); + + ret = tpm_pm_resume(dev); + if (!ret) + tpm_do_selftest(chip); + + return ret; +} +#endif + +static SIMPLE_DEV_PM_OPS(tpm_tis_pm, tpm_pm_suspend, tpm_tis_resume); + #ifdef CONFIG_PNP -static int __devinit tpm_tis_pnp_init(struct pnp_dev *pnp_dev, +static int tpm_tis_pnp_init(struct pnp_dev *pnp_dev, const struct pnp_device_id *pnp_id) { resource_size_t start, len; @@ -632,32 +755,15 @@ static int __devinit tpm_tis_pnp_init(struct pnp_dev *pnp_dev, if (pnp_irq_valid(pnp_dev, 0)) irq = pnp_irq(pnp_dev, 0); else - interrupts = 0; + interrupts = false; if (is_itpm(pnp_dev)) - itpm = 1; + itpm = true; return tpm_tis_init(&pnp_dev->dev, start, len, irq); } -static int tpm_tis_pnp_suspend(struct pnp_dev *dev, pm_message_t msg) -{ - return tpm_pm_suspend(&dev->dev, msg); -} - -static int tpm_tis_pnp_resume(struct pnp_dev *dev) -{ - struct tpm_chip *chip = pnp_get_drvdata(dev); - int ret; - - ret = tpm_pm_resume(&dev->dev); - if (!ret) - tpm_continue_selftest(chip); - - return ret; -} - -static struct pnp_device_id tpm_pnp_tbl[] __devinitdata = { +static struct pnp_device_id tpm_pnp_tbl[] = { {"PNP0C31", 0}, /* TPM */ {"ATM1200", 0}, /* Atmel */ {"IFX0102", 0}, /* Infineon */ @@ -671,7 +777,7 @@ static struct pnp_device_id tpm_pnp_tbl[] __devinitdata = { }; MODULE_DEVICE_TABLE(pnp, tpm_pnp_tbl); -static __devexit void tpm_tis_pnp_remove(struct pnp_dev *dev) +static void tpm_tis_pnp_remove(struct pnp_dev *dev) { struct tpm_chip *chip = pnp_get_drvdata(dev); @@ -685,9 +791,10 @@ static struct pnp_driver tis_pnp_driver = { .name = "tpm_tis", .id_table = tpm_pnp_tbl, .probe = tpm_tis_pnp_init, - .suspend = tpm_tis_pnp_suspend, - .resume = tpm_tis_pnp_resume, .remove = tpm_tis_pnp_remove, + .driver = { + .pm = &tpm_tis_pm, + }, }; #define TIS_HID_USR_IDX sizeof(tpm_pnp_tbl)/sizeof(struct pnp_device_id) -2 @@ -695,27 +802,18 @@ module_param_string(hid, tpm_pnp_tbl[TIS_HID_USR_IDX].id, sizeof(tpm_pnp_tbl[TIS_HID_USR_IDX].id), 0444); MODULE_PARM_DESC(hid, "Set additional specific HID for this driver to probe"); #endif -static int tpm_tis_suspend(struct platform_device *dev, pm_message_t msg) -{ - return tpm_pm_suspend(&dev->dev, msg); -} -static int tpm_tis_resume(struct platform_device *dev) -{ - return tpm_pm_resume(&dev->dev); -} static struct platform_driver tis_drv = { .driver = { .name = "tpm_tis", .owner = THIS_MODULE, + .pm = &tpm_tis_pm, }, - .suspend = tpm_tis_suspend, - .resume = tpm_tis_resume, }; static struct platform_device *pdev; -static int force; +static bool force; module_param(force, bool, 0444); MODULE_PARM_DESC(force, "Force device probe rather than using ACPI entry"); static int __init init_tis(void) @@ -729,12 +827,19 @@ static int __init init_tis(void) rc = platform_driver_register(&tis_drv); if (rc < 0) return rc; - if (IS_ERR(pdev=platform_device_register_simple("tpm_tis", -1, NULL, 0))) - return PTR_ERR(pdev); - if((rc=tpm_tis_init(&pdev->dev, TIS_MEM_BASE, TIS_MEM_LEN, 0)) != 0) { - platform_device_unregister(pdev); - platform_driver_unregister(&tis_drv); + pdev = platform_device_register_simple("tpm_tis", -1, NULL, 0); + if (IS_ERR(pdev)) { + rc = PTR_ERR(pdev); + goto err_dev; } + rc = tpm_tis_init(&pdev->dev, TIS_MEM_BASE, TIS_MEM_LEN, 0); + if (rc) + goto err_init; + return 0; +err_init: + platform_device_unregister(pdev); +err_dev: + platform_driver_unregister(&tis_drv); return rc; } @@ -742,7 +847,7 @@ static void __exit cleanup_tis(void) { struct tpm_vendor_specific *i, *j; struct tpm_chip *chip; - spin_lock(&tis_lock); + mutex_lock(&tis_lock); list_for_each_entry_safe(i, j, &tis_chips, list) { chip = to_tpm_chip(i); tpm_remove_hardware(chip->dev); @@ -758,7 +863,7 @@ static void __exit cleanup_tis(void) iounmap(i->iobase); list_del(&i->list); } - spin_unlock(&tis_lock); + mutex_unlock(&tis_lock); #ifdef CONFIG_PNP if (!force) { pnp_unregister_driver(&tis_pnp_driver); |
