Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit afb5abc2 authored by Jarkko Sakkinen's avatar Jarkko Sakkinen Committed by Peter Huewe
Browse files

tpm: two-phase chip management functions



tpm_register_hardware() and tpm_remove_hardware() are called often
before initializing the device. The problem is that the device might
not be fully initialized when it comes visible to the user space.

This patch resolves the issue by diving initialization into two
parts:

- tpmm_chip_alloc() creates struct tpm_chip.

- tpm_chip_register() sets up the character device and sysfs
  attributes.

The framework takes care of freeing struct tpm_chip by using the devres
API. The broken release callback has been wiped. ACPI drivers do not
ever get this callback.

Regards to Jason Gunthorpe for carefully reviewing this part of the
code.

Signed-off-by: default avatarJarkko Sakkinen <jarkko.sakkinen@linux.intel.com>
Reviewed-by: default avatarJasob Gunthorpe <jason.gunthorpe@obsidianresearch.com>
Reviewed-by: default avatarStefan Berger <stefanb@linux.vnet.ibm.com>
Tested-by: default avatarScot Doyle <lkml14@scotdoyle.com>
Tested-by: default avatarPeter Huewe <peterhuewe@gmx.de>
[phuewe: update to upstream changes]
Signed-off-by: default avatarPeter Huewe <peterhuewe@gmx.de>
parent 87155b73
Loading
Loading
Loading
Loading
+1 −1
Original line number Original line Diff line number Diff line
@@ -2,7 +2,7 @@
# Makefile for the kernel tpm device drivers.
# Makefile for the kernel tpm device drivers.
#
#
obj-$(CONFIG_TCG_TPM) += tpm.o
obj-$(CONFIG_TCG_TPM) += tpm.o
tpm-y := tpm-interface.o tpm-dev.o tpm-sysfs.o
tpm-y := tpm-interface.o tpm-dev.o tpm-sysfs.o tpm-chip.o
tpm-$(CONFIG_ACPI) += tpm_ppi.o
tpm-$(CONFIG_ACPI) += tpm_ppi.o


ifdef CONFIG_ACPI
ifdef CONFIG_ACPI
+199 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2004 IBM Corporation
 * Copyright (C) 2014 Intel Corporation
 *
 * Authors:
 * Jarkko Sakkinen <jarkko.sakkinen@linux.intel.com>
 * Leendert van Doorn <leendert@watson.ibm.com>
 * Dave Safford <safford@watson.ibm.com>
 * Reiner Sailer <sailer@watson.ibm.com>
 * Kylene Hall <kjhall@us.ibm.com>
 *
 * Maintained by: <tpmdd-devel@lists.sourceforge.net>
 *
 * TPM chip management routines.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, version 2 of the
 * License.
 *
 */

#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/spinlock.h>
#include <linux/freezer.h>
#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);

/*
 * tpm_chip_find_get - return tpm_chip for a given chip number
 * @chip_num the device number for the chip
 */
struct tpm_chip *tpm_chip_find_get(int chip_num)
{
	struct tpm_chip *pos, *chip = NULL;

	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;

		if (try_module_get(pos->dev->driver->owner)) {
			chip = pos;
			break;
		}
	}
	rcu_read_unlock();
	return chip;
}

/**
 * tpmm_chip_remove() - free chip memory and device number
 * @data: points to struct tpm_chip instance
 *
 * This is used internally by tpmm_chip_alloc() and called by devres
 * when the device is released. This function does the opposite of
 * tpmm_chip_alloc() freeing memory and the device number.
 */
static void tpmm_chip_remove(void *data)
{
	struct tpm_chip *chip = (struct tpm_chip *) data;

	spin_lock(&driver_lock);
	clear_bit(chip->dev_num, dev_mask);
	spin_unlock(&driver_lock);
	kfree(chip);
}

/**
 * tpmm_chip_alloc() - allocate a new struct tpm_chip instance
 * @dev: device to which the chip is associated
 * @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.
 */
struct tpm_chip *tpmm_chip_alloc(struct device *dev,
				 const struct tpm_class_ops *ops)
{
	struct tpm_chip *chip;

	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
	if (chip == NULL)
		return ERR_PTR(-ENOMEM);

	mutex_init(&chip->tpm_mutex);
	INIT_LIST_HEAD(&chip->list);

	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) {
		dev_err(dev, "No available tpm device numbers\n");
		kfree(chip);
		return ERR_PTR(-ENOMEM);
	}

	set_bit(chip->dev_num, dev_mask);

	scnprintf(chip->devname, sizeof(chip->devname), "tpm%d", chip->dev_num);

	chip->dev = dev;
	devm_add_action(dev, tpmm_chip_remove, chip);
	dev_set_drvdata(dev, chip);

	return chip;
}
EXPORT_SYMBOL_GPL(tpmm_chip_alloc);

/*
 * tpm_chip_register() - create a misc driver for the TPM chip
 * @chip: TPM chip to use.
 *
 * Creates a misc driver for the TPM chip and adds sysfs interfaces for
 * the device, PPI and TCPA. As the last step this function adds the
 * chip to the list of TPM chips available for use.
 *
 * NOTE: This function should be only called after the chip initialization
 * is complete.
 *
 * Called from tpm_<specific>.c probe function only for devices
 * the driver has determined it should claim.  Prior to calling
 * this function the specific probe function has called pci_enable_device
 * upon errant exit from this function specific probe function should call
 * pci_disable_device
 */
int tpm_chip_register(struct tpm_chip *chip)
{
	int rc;

	rc = tpm_dev_add_device(chip);
	if (rc)
		return rc;

	rc = tpm_sysfs_add_device(chip);
	if (rc)
		goto del_misc;

	rc = tpm_add_ppi(&chip->dev->kobj);
	if (rc)
		goto del_sysfs;

	chip->bios_dir = tpm_bios_log_setup(chip->devname);

	/* Make the chip available. */
	spin_lock(&driver_lock);
	list_add_rcu(&chip->list, &tpm_chip_list);
	spin_unlock(&driver_lock);

	chip->flags |= TPM_CHIP_FLAG_REGISTERED;

	return 0;
del_sysfs:
	tpm_sysfs_del_device(chip);
del_misc:
	tpm_dev_del_device(chip);
	return rc;
}
EXPORT_SYMBOL_GPL(tpm_chip_register);

/*
 * tpm_chip_unregister() - release the TPM driver
 * @chip: TPM chip to use.
 *
 * Takes the chip first away from the list of available TPM chips and then
 * cleans up all the resources reserved by tpm_chip_register().
 *
 * NOTE: This function should be only called before deinitializing chip
 * resources.
 */
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->bios_dir)
		tpm_bios_log_teardown(chip->bios_dir);
	tpm_remove_ppi(&chip->dev->kobj);
	tpm_sysfs_del_device(chip);

	tpm_dev_del_device(chip);
}
EXPORT_SYMBOL_GPL(tpm_chip_unregister);
+1 −147
Original line number Original line Diff line number Diff line
/*
/*
 * Copyright (C) 2004 IBM Corporation
 * Copyright (C) 2004 IBM Corporation
 * Copyright (C) 2014 Intel Corporation
 *
 *
 * Authors:
 * Authors:
 * Leendert van Doorn <leendert@watson.ibm.com>
 * Leendert van Doorn <leendert@watson.ibm.com>
@@ -47,10 +48,6 @@ module_param_named(suspend_pcr, tpm_suspend_pcr, uint, 0644);
MODULE_PARM_DESC(suspend_pcr,
MODULE_PARM_DESC(suspend_pcr,
		 "PCR to use for dummy writes to faciltate flush on suspend.");
		 "PCR to use for dummy writes to faciltate flush on suspend.");


static LIST_HEAD(tpm_chip_list);
static DEFINE_SPINLOCK(driver_lock);
static DECLARE_BITMAP(dev_mask, TPM_NUM_DEVICES);

/*
/*
 * Array with one entry per ordinal defining the maximum amount
 * Array with one entry per ordinal defining the maximum amount
 * of time the chip could take to return the result.  The ordinal
 * of time the chip could take to return the result.  The ordinal
@@ -639,27 +636,6 @@ static int tpm_continue_selftest(struct tpm_chip *chip)
	return rc;
	return rc;
}
}


/*
 * tpm_chip_find_get - return tpm_chip for given chip number
 */
static struct tpm_chip *tpm_chip_find_get(int chip_num)
{
	struct tpm_chip *pos, *chip = NULL;

	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;

		if (try_module_get(pos->dev->driver->owner)) {
			chip = pos;
			break;
		}
	}
	rcu_read_unlock();
	return chip;
}

#define TPM_ORDINAL_PCRREAD cpu_to_be32(21)
#define TPM_ORDINAL_PCRREAD cpu_to_be32(21)
#define READ_PCR_RESULT_SIZE 30
#define READ_PCR_RESULT_SIZE 30
static struct tpm_input_header pcrread_header = {
static struct tpm_input_header pcrread_header = {
@@ -887,30 +863,6 @@ int wait_for_tpm_stat(struct tpm_chip *chip, u8 mask, unsigned long timeout,
}
}
EXPORT_SYMBOL_GPL(wait_for_tpm_stat);
EXPORT_SYMBOL_GPL(wait_for_tpm_stat);


void tpm_remove_hardware(struct device *dev)
{
	struct tpm_chip *chip = dev_get_drvdata(dev);

	if (chip == NULL) {
		dev_err(dev, "No device data found\n");
		return;
	}

	spin_lock(&driver_lock);
	list_del_rcu(&chip->list);
	spin_unlock(&driver_lock);
	synchronize_rcu();

	tpm_dev_del_device(chip);
	tpm_sysfs_del_device(chip);
	tpm_remove_ppi(&dev->kobj);
	tpm_bios_log_teardown(chip->bios_dir);

	/* write it this way to be explicit (chip->dev == dev) */
	put_device(chip->dev);
}
EXPORT_SYMBOL_GPL(tpm_remove_hardware);

#define TPM_ORD_SAVESTATE cpu_to_be32(152)
#define TPM_ORD_SAVESTATE cpu_to_be32(152)
#define SAVESTATE_RESULT_SIZE 10
#define SAVESTATE_RESULT_SIZE 10


@@ -1044,104 +996,6 @@ int tpm_get_random(u32 chip_num, u8 *out, size_t max)
}
}
EXPORT_SYMBOL_GPL(tpm_get_random);
EXPORT_SYMBOL_GPL(tpm_get_random);


/* In case vendor provided release function, call it too.*/

void tpm_dev_vendor_release(struct tpm_chip *chip)
{
	if (!chip)
		return;

	clear_bit(chip->dev_num, dev_mask);
}
EXPORT_SYMBOL_GPL(tpm_dev_vendor_release);


/*
 * Once all references to platform device are down to 0,
 * release all allocated structures.
 */
static void tpm_dev_release(struct device *dev)
{
	struct tpm_chip *chip = dev_get_drvdata(dev);

	if (!chip)
		return;

	tpm_dev_vendor_release(chip);

	chip->release(dev);
	kfree(chip);
}

/*
 * Called from tpm_<specific>.c probe function only for devices
 * the driver has determined it should claim.  Prior to calling
 * this function the specific probe function has called pci_enable_device
 * upon errant exit from this function specific probe function should call
 * pci_disable_device
 */
struct tpm_chip *tpm_register_hardware(struct device *dev,
				       const struct tpm_class_ops *ops)
{
	struct tpm_chip *chip;

	/* Driver specific per-device data */
	chip = kzalloc(sizeof(*chip), GFP_KERNEL);

	if (chip == NULL)
		return NULL;

	mutex_init(&chip->tpm_mutex);
	INIT_LIST_HEAD(&chip->list);

	chip->ops = ops;
	chip->dev_num = find_first_zero_bit(dev_mask, TPM_NUM_DEVICES);

	if (chip->dev_num >= TPM_NUM_DEVICES) {
		dev_err(dev, "No available tpm device numbers\n");
		goto out_free;
	}

	set_bit(chip->dev_num, dev_mask);

	scnprintf(chip->devname, sizeof(chip->devname), "%s%d", "tpm",
		  chip->dev_num);

	chip->dev = get_device(dev);
	chip->release = dev->release;
	dev->release = tpm_dev_release;
	dev_set_drvdata(dev, chip);

	if (tpm_dev_add_device(chip))
		goto put_device;

	if (tpm_sysfs_add_device(chip))
		goto del_misc;

	if (tpm_add_ppi(&dev->kobj))
		goto del_sysfs;

	chip->bios_dir = tpm_bios_log_setup(chip->devname);

	/* Make chip available */
	spin_lock(&driver_lock);
	list_add_tail_rcu(&chip->list, &tpm_chip_list);
	spin_unlock(&driver_lock);

	return chip;

del_sysfs:
	tpm_sysfs_del_device(chip);
del_misc:
	tpm_dev_del_device(chip);
put_device:
	put_device(chip->dev);
out_free:
	kfree(chip);
	return NULL;
}
EXPORT_SYMBOL_GPL(tpm_register_hardware);

MODULE_AUTHOR("Leendert van Doorn (leendert@watson.ibm.com)");
MODULE_AUTHOR("Leendert van Doorn (leendert@watson.ibm.com)");
MODULE_DESCRIPTION("TPM Driver");
MODULE_DESCRIPTION("TPM Driver");
MODULE_VERSION("2.0");
MODULE_VERSION("2.0");
+11 −5
Original line number Original line Diff line number Diff line
@@ -94,9 +94,14 @@ struct tpm_vendor_specific {
#define TPM_VID_WINBOND  0x1050
#define TPM_VID_WINBOND  0x1050
#define TPM_VID_STM      0x104A
#define TPM_VID_STM      0x104A


enum tpm_chip_flags {
	TPM_CHIP_FLAG_REGISTERED	= BIT(0),
};

struct tpm_chip {
struct tpm_chip {
	struct device *dev;	/* Device stuff */
	struct device *dev;	/* Device stuff */
	const struct tpm_class_ops *ops;
	const struct tpm_class_ops *ops;
	unsigned int flags;


	int dev_num;		/* /dev/tpm# */
	int dev_num;		/* /dev/tpm# */
	char devname[7];
	char devname[7];
@@ -110,7 +115,6 @@ struct tpm_chip {
	struct dentry **bios_dir;
	struct dentry **bios_dir;


	struct list_head list;
	struct list_head list;
	void (*release) (struct device *);
};
};


#define to_tpm_chip(n) container_of(n, struct tpm_chip, vendor)
#define to_tpm_chip(n) container_of(n, struct tpm_chip, vendor)
@@ -322,15 +326,17 @@ extern int tpm_get_timeouts(struct tpm_chip *);
extern void tpm_gen_interrupt(struct tpm_chip *);
extern void tpm_gen_interrupt(struct tpm_chip *);
extern int tpm_do_selftest(struct tpm_chip *);
extern int tpm_do_selftest(struct tpm_chip *);
extern unsigned long tpm_calc_ordinal_duration(struct tpm_chip *, u32);
extern unsigned long tpm_calc_ordinal_duration(struct tpm_chip *, u32);
extern struct tpm_chip* tpm_register_hardware(struct device *,
					      const struct tpm_class_ops *ops);
extern void tpm_dev_vendor_release(struct tpm_chip *);
extern void tpm_remove_hardware(struct device *);
extern int tpm_pm_suspend(struct device *);
extern int tpm_pm_suspend(struct device *);
extern int tpm_pm_resume(struct device *);
extern int tpm_pm_resume(struct device *);
extern int wait_for_tpm_stat(struct tpm_chip *, u8, unsigned long,
extern int wait_for_tpm_stat(struct tpm_chip *, u8, unsigned long,
			     wait_queue_head_t *, bool);
			     wait_queue_head_t *, bool);


struct tpm_chip *tpm_chip_find_get(int chip_num);
extern struct tpm_chip *tpmm_chip_alloc(struct device *dev,
				       const struct tpm_class_ops *ops);
extern int tpm_chip_register(struct tpm_chip *chip);
extern void tpm_chip_unregister(struct tpm_chip *chip);

int tpm_dev_add_device(struct tpm_chip *chip);
int tpm_dev_add_device(struct tpm_chip *chip);
void tpm_dev_del_device(struct tpm_chip *chip);
void tpm_dev_del_device(struct tpm_chip *chip);
int tpm_sysfs_add_device(struct tpm_chip *chip);
int tpm_sysfs_add_device(struct tpm_chip *chip);
+8 −3
Original line number Original line Diff line number Diff line
@@ -138,11 +138,11 @@ static void atml_plat_remove(void)
	struct tpm_chip *chip = dev_get_drvdata(&pdev->dev);
	struct tpm_chip *chip = dev_get_drvdata(&pdev->dev);


	if (chip) {
	if (chip) {
		tpm_chip_unregister(chip);
		if (chip->vendor.have_region)
		if (chip->vendor.have_region)
			atmel_release_region(chip->vendor.base,
			atmel_release_region(chip->vendor.base,
					     chip->vendor.region_size);
					     chip->vendor.region_size);
		atmel_put_base_addr(chip->vendor.iobase);
		atmel_put_base_addr(chip->vendor.iobase);
		tpm_remove_hardware(chip->dev);
		platform_device_unregister(pdev);
		platform_device_unregister(pdev);
	}
	}
}
}
@@ -183,8 +183,9 @@ static int __init init_atmel(void)
		goto err_rel_reg;
		goto err_rel_reg;
	}
	}


	if (!(chip = tpm_register_hardware(&pdev->dev, &tpm_atmel))) {
	chip = tpmm_chip_alloc(&pdev->dev, &tpm_atmel);
		rc = -ENODEV;
	if (IS_ERR(chip)) {
		rc = PTR_ERR(chip);
		goto err_unreg_dev;
		goto err_unreg_dev;
	}
	}


@@ -193,6 +194,10 @@ static int __init init_atmel(void)
	chip->vendor.have_region = have_region;
	chip->vendor.have_region = have_region;
	chip->vendor.region_size = region_size;
	chip->vendor.region_size = region_size;


	rc = tpm_chip_register(chip);
	if (rc)
		goto err_unreg_dev;

	return 0;
	return 0;


err_unreg_dev:
err_unreg_dev:
Loading