tpm: check for TPM_CHIP_FLAG_TPM2 before calling tpm2_shutdown()
[linux-2.6-microblaze.git] / drivers / char / tpm / tpm-chip.c
index 274dd01..2ea2f15 100644 (file)
 #include "tpm.h"
 #include "tpm_eventlog.h"
 
-static DECLARE_BITMAP(dev_mask, TPM_NUM_DEVICES);
-static LIST_HEAD(tpm_chip_list);
-static DEFINE_SPINLOCK(driver_lock);
+DEFINE_IDR(dev_nums_idr);
+static DEFINE_MUTEX(idr_lock);
 
 struct class *tpm_class;
 dev_t tpm_devt;
 
-/*
- * tpm_chip_find_get - return tpm_chip for a given chip number
- * @chip_num the device number for the chip
+/**
+ * tpm_try_get_ops() - Get a ref to the tpm_chip
+ * @chip: Chip to ref
+ *
+ * The caller must already have some kind of locking to ensure that chip is
+ * valid. This function will lock the chip so that the ops member can be
+ * accessed safely. The locking prevents tpm_chip_unregister from
+ * completing, so it should not be held for long periods.
+ *
+ * Returns -ERRNO if the chip could not be got.
  */
-struct tpm_chip *tpm_chip_find_get(int chip_num)
+int tpm_try_get_ops(struct tpm_chip *chip)
 {
-       struct tpm_chip *pos, *chip = NULL;
+       int rc = -EIO;
 
-       rcu_read_lock();
-       list_for_each_entry_rcu(pos, &tpm_chip_list, list) {
-               if (chip_num != TPM_ANY_NUM && chip_num != pos->dev_num)
-                       continue;
+       get_device(&chip->dev);
 
-               if (try_module_get(pos->pdev->driver->owner)) {
-                       chip = pos;
-                       break;
-               }
+       down_read(&chip->ops_sem);
+       if (!chip->ops)
+               goto out_lock;
+
+       return 0;
+out_lock:
+       up_read(&chip->ops_sem);
+       put_device(&chip->dev);
+       return rc;
+}
+EXPORT_SYMBOL_GPL(tpm_try_get_ops);
+
+/**
+ * tpm_put_ops() - Release a ref to the tpm_chip
+ * @chip: Chip to put
+ *
+ * This is the opposite pair to tpm_try_get_ops(). After this returns chip may
+ * be kfree'd.
+ */
+void tpm_put_ops(struct tpm_chip *chip)
+{
+       up_read(&chip->ops_sem);
+       put_device(&chip->dev);
+}
+EXPORT_SYMBOL_GPL(tpm_put_ops);
+
+/**
+ * tpm_chip_find_get() - return tpm_chip for a given chip number
+ * @chip_num: id to find
+ *
+ * The return'd chip has been tpm_try_get_ops'd and must be released via
+ * tpm_put_ops
+  */
+struct tpm_chip *tpm_chip_find_get(int chip_num)
+{
+       struct tpm_chip *chip, *res = NULL;
+       int chip_prev;
+
+       mutex_lock(&idr_lock);
+
+       if (chip_num == TPM_ANY_NUM) {
+               chip_num = 0;
+               do {
+                       chip_prev = chip_num;
+                       chip = idr_get_next(&dev_nums_idr, &chip_num);
+                       if (chip && !tpm_try_get_ops(chip)) {
+                               res = chip;
+                               break;
+                       }
+               } while (chip_prev != chip_num);
+       } else {
+               chip = idr_find_slowpath(&dev_nums_idr, chip_num);
+               if (chip && !tpm_try_get_ops(chip))
+                       res = chip;
        }
-       rcu_read_unlock();
-       return chip;
+
+       mutex_unlock(&idr_lock);
+
+       return res;
 }
 
 /**
@@ -68,24 +123,25 @@ static void tpm_dev_release(struct device *dev)
 {
        struct tpm_chip *chip = container_of(dev, struct tpm_chip, dev);
 
-       spin_lock(&driver_lock);
-       clear_bit(chip->dev_num, dev_mask);
-       spin_unlock(&driver_lock);
+       mutex_lock(&idr_lock);
+       idr_remove(&dev_nums_idr, chip->dev_num);
+       mutex_unlock(&idr_lock);
+
        kfree(chip);
 }
 
 /**
- * tpmm_chip_alloc() - allocate a new struct tpm_chip instance
- * @dev: device to which the chip is associated
+ * tpm_chip_alloc() - allocate a new struct tpm_chip instance
+ * @pdev: device to which the chip is associated
+ *        At this point pdev mst be initialized, but does not have to
+ *        be registered
  * @ops: struct tpm_class_ops instance
  *
  * Allocates a new struct tpm_chip instance and assigns a free
- * device number for it. Caller does not have to worry about
- * freeing the allocated resources. When the devices is removed
- * devres calls tpmm_chip_remove() to do the job.
+ * device number for it. Must be paired with put_device(&chip->dev).
  */
-struct tpm_chip *tpmm_chip_alloc(struct device *dev,
-                                const struct tpm_class_ops *ops)
+struct tpm_chip *tpm_chip_alloc(struct device *dev,
+                               const struct tpm_class_ops *ops)
 {
        struct tpm_chip *chip;
        int rc;
@@ -95,31 +151,25 @@ struct tpm_chip *tpmm_chip_alloc(struct device *dev,
                return ERR_PTR(-ENOMEM);
 
        mutex_init(&chip->tpm_mutex);
-       INIT_LIST_HEAD(&chip->list);
+       init_rwsem(&chip->ops_sem);
 
        chip->ops = ops;
 
-       spin_lock(&driver_lock);
-       chip->dev_num = find_first_zero_bit(dev_mask, TPM_NUM_DEVICES);
-       spin_unlock(&driver_lock);
-
-       if (chip->dev_num >= TPM_NUM_DEVICES) {
+       mutex_lock(&idr_lock);
+       rc = idr_alloc(&dev_nums_idr, NULL, 0, TPM_NUM_DEVICES, GFP_KERNEL);
+       mutex_unlock(&idr_lock);
+       if (rc < 0) {
                dev_err(dev, "No available tpm device numbers\n");
                kfree(chip);
-               return ERR_PTR(-ENOMEM);
+               return ERR_PTR(rc);
        }
+       chip->dev_num = rc;
 
-       set_bit(chip->dev_num, dev_mask);
-
-       scnprintf(chip->devname, sizeof(chip->devname), "tpm%d", chip->dev_num);
-
-       chip->pdev = dev;
-
-       dev_set_drvdata(dev, chip);
+       device_initialize(&chip->dev);
 
        chip->dev.class = tpm_class;
        chip->dev.release = tpm_dev_release;
-       chip->dev.parent = chip->pdev;
+       chip->dev.parent = dev;
 #ifdef CONFIG_ACPI
        chip->dev.groups = chip->groups;
 #endif
@@ -129,20 +179,47 @@ struct tpm_chip *tpmm_chip_alloc(struct device *dev,
        else
                chip->dev.devt = MKDEV(MAJOR(tpm_devt), chip->dev_num);
 
-       dev_set_name(&chip->dev, "%s", chip->devname);
-
-       device_initialize(&chip->dev);
+       rc = dev_set_name(&chip->dev, "tpm%d", chip->dev_num);
+       if (rc)
+               goto out;
 
        cdev_init(&chip->cdev, &tpm_fops);
-       chip->cdev.owner = chip->pdev->driver->owner;
+       chip->cdev.owner = THIS_MODULE;
        chip->cdev.kobj.parent = &chip->dev.kobj;
 
-       rc = devm_add_action(dev, (void (*)(void *)) put_device, &chip->dev);
+       return chip;
+
+out:
+       put_device(&chip->dev);
+       return ERR_PTR(rc);
+}
+EXPORT_SYMBOL_GPL(tpm_chip_alloc);
+
+/**
+ * tpmm_chip_alloc() - allocate a new struct tpm_chip instance
+ * @pdev: parent device to which the chip is associated
+ * @ops: struct tpm_class_ops instance
+ *
+ * Same as tpm_chip_alloc except devm is used to do the put_device
+ */
+struct tpm_chip *tpmm_chip_alloc(struct device *pdev,
+                                const struct tpm_class_ops *ops)
+{
+       struct tpm_chip *chip;
+       int rc;
+
+       chip = tpm_chip_alloc(pdev, ops);
+       if (IS_ERR(chip))
+               return chip;
+
+       rc = devm_add_action(pdev, (void (*)(void *)) put_device, &chip->dev);
        if (rc) {
                put_device(&chip->dev);
                return ERR_PTR(rc);
        }
 
+       dev_set_drvdata(pdev, chip);
+
        return chip;
 }
 EXPORT_SYMBOL_GPL(tpmm_chip_alloc);
@@ -155,7 +232,7 @@ static int tpm_add_char_device(struct tpm_chip *chip)
        if (rc) {
                dev_err(&chip->dev,
                        "unable to cdev_add() %s, major %d, minor %d, err=%d\n",
-                       chip->devname, MAJOR(chip->dev.devt),
+                       dev_name(&chip->dev), MAJOR(chip->dev.devt),
                        MINOR(chip->dev.devt), rc);
 
                return rc;
@@ -165,13 +242,18 @@ static int tpm_add_char_device(struct tpm_chip *chip)
        if (rc) {
                dev_err(&chip->dev,
                        "unable to device_register() %s, major %d, minor %d, err=%d\n",
-                       chip->devname, MAJOR(chip->dev.devt),
+                       dev_name(&chip->dev), MAJOR(chip->dev.devt),
                        MINOR(chip->dev.devt), rc);
 
                cdev_del(&chip->cdev);
                return rc;
        }
 
+       /* Make the chip available. */
+       mutex_lock(&idr_lock);
+       idr_replace(&dev_nums_idr, chip, chip->dev_num);
+       mutex_unlock(&idr_lock);
+
        return rc;
 }
 
@@ -179,6 +261,18 @@ static void tpm_del_char_device(struct tpm_chip *chip)
 {
        cdev_del(&chip->cdev);
        device_del(&chip->dev);
+
+       /* Make the chip unavailable. */
+       mutex_lock(&idr_lock);
+       idr_replace(&dev_nums_idr, NULL, chip->dev_num);
+       mutex_unlock(&idr_lock);
+
+       /* Make the driver uncallable. */
+       down_write(&chip->ops_sem);
+       if (chip->flags & TPM_CHIP_FLAG_TPM2)
+               tpm2_shutdown(chip, TPM2_SU_CLEAR);
+       chip->ops = NULL;
+       up_write(&chip->ops_sem);
 }
 
 static int tpm1_chip_register(struct tpm_chip *chip)
@@ -192,7 +286,7 @@ static int tpm1_chip_register(struct tpm_chip *chip)
        if (rc)
                return rc;
 
-       chip->bios_dir = tpm_bios_log_setup(chip->devname);
+       chip->bios_dir = tpm_bios_log_setup(dev_name(&chip->dev));
 
        return 0;
 }
@@ -233,17 +327,11 @@ int tpm_chip_register(struct tpm_chip *chip)
        if (rc)
                goto out_err;
 
-       /* Make the chip available. */
-       spin_lock(&driver_lock);
-       list_add_tail_rcu(&chip->list, &tpm_chip_list);
-       spin_unlock(&driver_lock);
-
        chip->flags |= TPM_CHIP_FLAG_REGISTERED;
 
        if (!(chip->flags & TPM_CHIP_FLAG_TPM2)) {
-               rc = __compat_only_sysfs_link_entry_to_kobj(&chip->pdev->kobj,
-                                                           &chip->dev.kobj,
-                                                           "ppi");
+               rc = __compat_only_sysfs_link_entry_to_kobj(
+                   &chip->dev.parent->kobj, &chip->dev.kobj, "ppi");
                if (rc && rc != -ENOENT) {
                        tpm_chip_unregister(chip);
                        return rc;
@@ -264,6 +352,9 @@ EXPORT_SYMBOL_GPL(tpm_chip_register);
  * Takes the chip first away from the list of available TPM chips and then
  * cleans up all the resources reserved by tpm_chip_register().
  *
+ * Once this function returns the driver call backs in 'op's will not be
+ * running and will no longer start.
+ *
  * NOTE: This function should be only called before deinitializing chip
  * resources.
  */
@@ -272,13 +363,8 @@ void tpm_chip_unregister(struct tpm_chip *chip)
        if (!(chip->flags & TPM_CHIP_FLAG_REGISTERED))
                return;
 
-       spin_lock(&driver_lock);
-       list_del_rcu(&chip->list);
-       spin_unlock(&driver_lock);
-       synchronize_rcu();
-
        if (!(chip->flags & TPM_CHIP_FLAG_TPM2))
-               sysfs_remove_link(&chip->pdev->kobj, "ppi");
+               sysfs_remove_link(&chip->dev.parent->kobj, "ppi");
 
        tpm1_chip_unregister(chip);
        tpm_del_char_device(chip);