diff mbox

[tpmdd-devel,v3,4/6] tpm: TPM 2.0 sysfs attributes

Message ID 1413372916-12091-5-git-send-email-jarkko.sakkinen@linux.intel.com
State Superseded, archived
Headers show

Commit Message

Jarkko Sakkinen Oct. 15, 2014, 11:35 a.m. UTC
Implemented sysfs attributes for TPM2 devices. TPM2 sysfs attributes
are mounted in the actual device associated with the chip instead of
platform device like with TPM1 devices.

Documentation/ABI/stable/sysfs-class/tpm2 contains descriptions
of these attributes.

Signed-off-by: Jarkko Sakkinen <jarkko.sakkinen@linux.intel.com>
---
 Documentation/ABI/stable/sysfs-class-tpm2 |  72 +++++++
 drivers/char/tpm/Makefile                 |   2 +-
 drivers/char/tpm/tpm-chip.c               |  12 +-
 drivers/char/tpm/tpm.h                    |  24 +++
 drivers/char/tpm/tpm2-sysfs.c             | 314 ++++++++++++++++++++++++++++++
 5 files changed, 421 insertions(+), 3 deletions(-)
 create mode 100644 Documentation/ABI/stable/sysfs-class-tpm2
 create mode 100644 drivers/char/tpm/tpm2-sysfs.c

Comments

Greg KH Oct. 16, 2014, 9:31 a.m. UTC | #1
On Wed, Oct 15, 2014 at 01:35:14PM +0200, Jarkko Sakkinen wrote:
> +int tpm2_sysfs_add_device(struct tpm_chip *chip)
> +{
> +	struct pcr_bank *pcr_bank;
> +	struct kobject *pcrs_kobj;
> +	struct device *dev = chip->dev;
> +	int rc;
> +
> +	rc = sysfs_create_group(&chip->vendor.miscdev.this_device->kobj,
> +				&tpm_dev_group);
> +	if (rc) {
> +		dev_err(dev, "failed to create sysfs attributes, %d\n", rc);
> +		return rc;
> +	}

You just raced and created sysfs files _after_ userspace saw that your
device was enabled.

> +	pcrs_kobj = kobject_create_and_add("pcrs",
> +					   &chip->vendor.miscdev.this_device->kobj);

Ick, no no no no.  Never create a kobject under a 'struct device'.  You
just lost all libudev support for any attribute you created under here,
not good at all.  Use a "real" device if you really want a sub-device,
not a kobject.

thanks,

greg k-h

------------------------------------------------------------------------------
Comprehensive Server Monitoring with Site24x7.
Monitor 10 servers for $9/Month.
Get alerted through email, SMS, voice calls or mobile push notifications.
Take corrective actions from your mobile device.
http://p.sf.net/sfu/Zoho
Jarkko Sakkinen Oct. 16, 2014, 4:03 p.m. UTC | #2
On Thu, Oct 16, 2014 at 11:31:46AM +0200, Greg KH wrote:
> On Wed, Oct 15, 2014 at 01:35:14PM +0200, Jarkko Sakkinen wrote:
> > +int tpm2_sysfs_add_device(struct tpm_chip *chip)
> > +{
> > +	struct pcr_bank *pcr_bank;
> > +	struct kobject *pcrs_kobj;
> > +	struct device *dev = chip->dev;
> > +	int rc;
> > +
> > +	rc = sysfs_create_group(&chip->vendor.miscdev.this_device->kobj,
> > +				&tpm_dev_group);
> > +	if (rc) {
> > +		dev_err(dev, "failed to create sysfs attributes, %d\n", rc);
> > +		return rc;
> > +	}
> 
> You just raced and created sysfs files _after_ userspace saw that your
> device was enabled.

Here the options are limited because misc_register already spawns
KOBJ_ADD. Not much to do unless we would wipe the current use of 
misc driver completely.

> 
> > +	pcrs_kobj = kobject_create_and_add("pcrs",
> > +					   &chip->vendor.miscdev.this_device->kobj);
> 
> Ick, no no no no.  Never create a kobject under a 'struct device'.  You
> just lost all libudev support for any attribute you created under here,
> not good at all.  Use a "real" device if you really want a sub-device,
> not a kobject.

Ack, will rework this.

> thanks,
> 
> greg k-h

/Jarkko

------------------------------------------------------------------------------
Comprehensive Server Monitoring with Site24x7.
Monitor 10 servers for $9/Month.
Get alerted through email, SMS, voice calls or mobile push notifications.
Take corrective actions from your mobile device.
http://p.sf.net/sfu/Zoho
Tomas Winkler Oct. 19, 2014, 9:09 p.m. UTC | #3
>
> Here the options are limited because misc_register already spawns
> KOBJ_ADD. Not much to do unless we would wipe the current use of
> misc driver completely.

I would strongly recommend to drop the misc layer here, as tpm itself
can provide the needed abstraction for the devices.

------------------------------------------------------------------------------
Comprehensive Server Monitoring with Site24x7.
Monitor 10 servers for $9/Month.
Get alerted through email, SMS, voice calls or mobile push notifications.
Take corrective actions from your mobile device.
http://p.sf.net/sfu/Zoho
Andy Lutomirski Oct. 19, 2014, 10:07 p.m. UTC | #4
On Wed, Oct 15, 2014 at 4:35 AM, Jarkko Sakkinen
<jarkko.sakkinen@linux.intel.com> wrote:
> Implemented sysfs attributes for TPM2 devices. TPM2 sysfs attributes
> are mounted in the actual device associated with the chip instead of
> platform device like with TPM1 devices.
>
> Documentation/ABI/stable/sysfs-class/tpm2 contains descriptions
> of these attributes.
>
> Signed-off-by: Jarkko Sakkinen <jarkko.sakkinen@linux.intel.com>


> +What:          /sys/class/misc/tpmX/device/cancel
> +Date:          October 2014
> +KernelVersion: 3.19
> +Contact:       tpmdd-devel@lists.sf.net
> +Description:   The "cancel" property allows you to cancel the currently
> +               pending TPM command. Writing any value to cancel will call the
> +               TPM chip specific cancel operation.

This is weird.  From the POV of a sysfs user, what operation gets
canceled?  What if it's a kernel-internal operation?

Shouldn't this be an ioctl?

--Andy

------------------------------------------------------------------------------
Comprehensive Server Monitoring with Site24x7.
Monitor 10 servers for $9/Month.
Get alerted through email, SMS, voice calls or mobile push notifications.
Take corrective actions from your mobile device.
http://p.sf.net/sfu/Zoho
Jarkko Sakkinen Oct. 20, 2014, 10:35 a.m. UTC | #5
On Sun, Oct 19, 2014 at 03:07:49PM -0700, Andy Lutomirski wrote:
> On Wed, Oct 15, 2014 at 4:35 AM, Jarkko Sakkinen
> <jarkko.sakkinen@linux.intel.com> wrote:
> > Implemented sysfs attributes for TPM2 devices. TPM2 sysfs attributes
> > are mounted in the actual device associated with the chip instead of
> > platform device like with TPM1 devices.
> >
> > Documentation/ABI/stable/sysfs-class/tpm2 contains descriptions
> > of these attributes.
> >
> > Signed-off-by: Jarkko Sakkinen <jarkko.sakkinen@linux.intel.com>
> 
> 
> > +What:          /sys/class/misc/tpmX/device/cancel
> > +Date:          October 2014
> > +KernelVersion: 3.19
> > +Contact:       tpmdd-devel@lists.sf.net
> > +Description:   The "cancel" property allows you to cancel the currently
> > +               pending TPM command. Writing any value to cancel will call the
> > +               TPM chip specific cancel operation.
> 
> This is weird.  From the POV of a sysfs user, what operation gets
> canceled?  What if it's a kernel-internal operation?
> 
> Shouldn't this be an ioctl?

This was a really good insight, thank you. I just followed along the
lines what was defined for TPM1 but didn't think too much. This is
racy attribute and should not be added for TPM2.

I'll drop this. Adding ioctl() later would much better idea as it is
not racy but I think it should be postponed after this patch set so
that it stays digestable size.

> --Andy

/Jarkko

------------------------------------------------------------------------------
Comprehensive Server Monitoring with Site24x7.
Monitor 10 servers for $9/Month.
Get alerted through email, SMS, voice calls or mobile push notifications.
Take corrective actions from your mobile device.
http://p.sf.net/sfu/Zoho
Jarkko Sakkinen Oct. 20, 2014, 10:45 a.m. UTC | #6
On Mon, Oct 20, 2014 at 12:09:27AM +0300, Tomas Winkler wrote:
> >
> > Here the options are limited because misc_register already spawns
> > KOBJ_ADD. Not much to do unless we would wipe the current use of
> > misc driver completely.
> 
> I would strongly recommend to drop the misc layer here, as tpm itself
> can provide the needed abstraction for the devices.

This is exactly what I'm going to do for v4 of the patch set and take
the rtc subsystem as a guideline how to structure things in proper way.

It is ABI break but if you look at the TPM1 sysfs attributes most of
them haven't been ever machine readable anyway so I'll at least give it
shot and a take the feedback. I'm fully aware that doing such thing is 
comparable to killing your own mother :)

Past experiences of trying to do such thing [1] have not been succesful
but on the other hand this is very different scenario and different user
volumes.

It would make sense to rip off all the "human-only" TPM1 attributes in
the same round. I'm wondering what is the set of sysfs attributes is
that TrouSerS is dependent of? I would suggest to leave that subset
available and remove attributes such as 'pcrs' that breaks all the
conventions.

[1] http://lwn.net/Articles/172306/

/Jarkko

------------------------------------------------------------------------------
Comprehensive Server Monitoring with Site24x7.
Monitor 10 servers for $9/Month.
Get alerted through email, SMS, voice calls or mobile push notifications.
Take corrective actions from your mobile device.
http://p.sf.net/sfu/Zoho
diff mbox

Patch

diff --git a/Documentation/ABI/stable/sysfs-class-tpm2 b/Documentation/ABI/stable/sysfs-class-tpm2
new file mode 100644
index 0000000..b3d20a4
--- /dev/null
+++ b/Documentation/ABI/stable/sysfs-class-tpm2
@@ -0,0 +1,72 @@ 
+What:		/sys/class/misc/tpmX/device/
+Date:		October 2014
+KernelVersion:	3.19
+Contact:	tpmdd-devel@lists.sf.net
+Description:	The device/ directory under a specific TPM instance exposes
+		the properties of that TPM chip.
+
+What:		/sys/class/misc/tpmX/device/version
+Date:		October 2014
+KernelVersion:	3.19
+Contact:	tpmdd-devel@lists.sf.net
+Description:	The "version" property prints the protocol version number
+		in the major.minor format.
+
+What:		/sys/class/misc/tpmX/device/sh_enabled
+Date:		October 2014
+KernelVersion:	3.19
+Contact:	tpmdd-devel@lists.sf.net
+Description:	The "sh_enabled" property prints a '1' if the Storage Hierarchy
+		is enabled, i.e. if PM_PT_STARTUP_CLEAR.shEnable is set.
+
+What:		/sys/class/misc/tpmX/device/sh_owned
+Date:		October 2014
+KernelVersion:	3.19
+Contact:	tpmdd-devel@lists.sf.net
+Description:	The "sh_owned" property prints a '1' if the ownership of the
+		Storage Hierarchy has been taken, i.e. if
+		TPM_PT_PERMANENT.ownerAuthSet is set.
+
+What:		/sys/class/misc/tpmX/device/eh_enabled
+Date:		October 2014
+KernelVersion:	3.19
+Contact:	tpmdd-devel@lists.sf.net
+Description:	The "eh_enabled" property prints a '1' if the Endorsement
+		Hierarchy is enabled, i.e if PM_PT_STARTUP_CLEAR.ehEnable is
+		set.
+
+What:		/sys/class/misc/tpmX/device/eh_owned
+Date:		October 2014
+KernelVersion:	3.19
+Contact:	tpmdd-devel@lists.sf.net
+Description:	The "eh_owned" property prints a '1' if the ownership of the
+		Endrosoment Hierarchy has been taken, i.e if
+		TPM_PT_PERMANENT.endorsementAuthSet is set.
+
+What:		/sys/class/misc/tpmX/device/manufacturer
+Date:		October 2014
+KernelVersion:	3.19
+Contact:	tpmdd-devel@lists.sf.net
+Description:	The "manufacturer" property prints the vendor ID of the TPM
+		manufacturer.
+
+What:		/sys/class/misc/tpmX/device/firmware
+Date:		October 2014
+KernelVersion:	3.19
+Contact:	tpmdd-devel@lists.sf.net
+Description:	The property prints the vendor-specific value indicating the
+		version of the firmware.
+
+What:		/sys/class/misc/tpmX/device/pcr/sha1/X
+Date:		October 2014
+KernelVersion:	3.19
+Contact:	tpmdd-devel@lists.sf.net
+Description:	These files print PCR values for the SHA-1 bank.
+
+What:		/sys/class/misc/tpmX/device/cancel
+Date:		October 2014
+KernelVersion:	3.19
+Contact:	tpmdd-devel@lists.sf.net
+Description:	The "cancel" property allows you to cancel the currently
+		pending TPM command. Writing any value to cancel will call the
+		TPM chip specific cancel operation.
diff --git a/drivers/char/tpm/Makefile b/drivers/char/tpm/Makefile
index ae56af9..d3cf905 100644
--- a/drivers/char/tpm/Makefile
+++ b/drivers/char/tpm/Makefile
@@ -2,7 +2,7 @@ 
 # Makefile for the kernel tpm device drivers.
 #
 obj-$(CONFIG_TCG_TPM) += tpm.o
-tpm-y := tpm-interface.o tpm-dev.o tpm-sysfs.o tpm-chip.o tpm2-cmd.o
+tpm-y := tpm-interface.o tpm-dev.o tpm-sysfs.o tpm-chip.o tpm2-cmd.o tpm2-sysfs.o
 tpm-$(CONFIG_ACPI) += tpm_ppi.o
 
 ifdef CONFIG_ACPI
diff --git a/drivers/char/tpm/tpm-chip.c b/drivers/char/tpm/tpm-chip.c
index 3bb0d9f..5fac0a8 100644
--- a/drivers/char/tpm/tpm-chip.c
+++ b/drivers/char/tpm/tpm-chip.c
@@ -140,9 +140,13 @@  int tpm_chip_register(struct tpm_chip *chip)
 		return rc;
 
 	if (chip->flags & TPM_CHIP_FLAG_TPM2) {
-		rc = tpm_add_ppi(&chip->vendor.miscdev.this_device->kobj);
+		rc = tpm2_sysfs_add_device(chip);
 		if (rc)
 			goto del_misc;
+
+		rc = tpm_add_ppi(&chip->vendor.miscdev.this_device->kobj);
+		if (rc)
+			goto del_sysfs;
 	} else {
 		rc = tpm_sysfs_add_device(chip);
 		if (rc)
@@ -162,7 +166,10 @@  int tpm_chip_register(struct tpm_chip *chip)
 
 	return 0;
 del_sysfs:
-	tpm_sysfs_del_device(chip);
+	if (chip->flags & TPM_CHIP_FLAG_TPM2)
+		tpm2_sysfs_del_device(chip);
+	else
+		tpm_sysfs_del_device(chip);
 del_misc:
 	tpm_dev_del_device(chip);
 	return rc;
@@ -190,6 +197,7 @@  void tpm_chip_unregister(struct tpm_chip *chip)
 
 	if (chip->flags & TPM_CHIP_FLAG_TPM2) {
 		tpm_remove_ppi(&chip->vendor.miscdev.this_device->kobj);
+		tpm2_sysfs_del_device(chip);
 	} else {
 		tpm_bios_log_teardown(chip->bios_dir);
 		tpm_remove_ppi(&chip->dev->kobj);
diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h
index b1de902..9f1f146 100644
--- a/drivers/char/tpm/tpm.h
+++ b/drivers/char/tpm/tpm.h
@@ -107,6 +107,24 @@  enum tpm2_capabilities {
 	TPM2_CAP_TPM_PROPERTIES = 6,
 };
 
+enum tpm2_tpm_properties {
+	TPM2_PT_MANUFACTURER		= 0x00000105,
+	TPM2_PT_FIRMWARE_VERSION_1	= 0x00000111,
+	TPM2_PT_FIRMWARE_VERSION_2	= 0x00000111,
+	TPM2_PT_PERMANENT		= 0x00000200,
+	TPM2_PT_STARTUP_CLEAR		= 0x00000201,
+};
+
+enum tpm2_pt_startup_clear {
+	TPM2_PT_SC_SH_ENABLE	= BIT(1),
+	TPM2_PT_SC_EH_ENABLE	= BIT(2),
+};
+
+enum tpm2_pt_permanent {
+	TPM2_PT_PM_OWNER_AUTH_SET	= BIT(0),
+	TPM2_PT_PM_ENDORSEMENT_AUTH_SET	= BIT(1),
+};
+
 enum tpm2_startup_types {
 	TPM2_SU_CLEAR	= 0x0000,
 	TPM2_SU_STATE	= 0x0001,
@@ -165,6 +183,9 @@  struct tpm_chip {
 
 	struct dentry **bios_dir;
 
+	struct kobject *pcrs_kobj;
+	void *sha1_bank;
+
 	struct list_head list;
 };
 
@@ -416,3 +437,6 @@  extern ssize_t tpm2_get_tpm_pt(struct tpm_chip *chip, u32 property_id,
 			       u32* value, const char *desc);
 extern unsigned long tpm2_calc_ordinal_duration(struct tpm_chip *, u32);
 extern int tpm2_do_selftest(struct tpm_chip *chip);
+
+int tpm2_sysfs_add_device(struct tpm_chip *chip);
+void tpm2_sysfs_del_device(struct tpm_chip *chip);
diff --git a/drivers/char/tpm/tpm2-sysfs.c b/drivers/char/tpm/tpm2-sysfs.c
new file mode 100644
index 0000000..e502032
--- /dev/null
+++ b/drivers/char/tpm/tpm2-sysfs.c
@@ -0,0 +1,314 @@ 
+/*
+ * Copyright (C) 2014 Intel Corporation
+ * Copyright (C) 2004 IBM Corporation
+ * Copyright (C) 2013 Obsidian Research Corp
+ *
+ * Authors:
+ * Jarkko Sakkinen <jarkko.sakkinen@linux.intel.com>
+ *
+ * sysfs filesystem inspection interface to the TPM
+ *
+ * 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/device.h>
+#include <linux/slab.h>
+#include "tpm.h"
+
+static ssize_t sh_enabled_show(struct device *dev, struct device_attribute *attr,
+		     char *buf)
+{
+	struct tpm_chip *chip = dev_get_drvdata(dev);
+	u32 value;
+	ssize_t rc;
+
+	rc = tpm2_get_tpm_pt(chip, TPM2_PT_STARTUP_CLEAR, &value,
+			     "could not retrieve STARTUP_CLEAR property");
+	if (rc)
+		return 0;
+
+	rc = sprintf(buf, "%d\n", (value & TPM2_PT_SC_SH_ENABLE) > 0);
+	return rc;
+}
+static DEVICE_ATTR_RO(sh_enabled);
+
+static ssize_t sh_owned_show(struct device *dev, struct device_attribute *attr,
+			  char *buf)
+{
+	struct tpm_chip *chip = dev_get_drvdata(dev);
+	u32 value;
+	ssize_t rc;
+
+	rc = tpm2_get_tpm_pt(chip, TPM2_PT_PERMANENT, &value,
+			     "could not retrieve PERMANENT property");
+	if (rc)
+		return 0;
+
+	rc = sprintf(buf, "%d\n", (value & TPM2_PT_PM_OWNER_AUTH_SET) > 0);
+	return rc;
+}
+static DEVICE_ATTR_RO(sh_owned);
+
+static ssize_t eh_enabled_show(struct device *dev, struct device_attribute *attr,
+		     char *buf)
+{
+	struct tpm_chip *chip = dev_get_drvdata(dev);
+	u32 value;
+	ssize_t rc;
+
+	rc = tpm2_get_tpm_pt(chip, TPM2_PT_STARTUP_CLEAR, &value,
+			     "could not retrieve STARTUP_CLEAR property");
+	if (rc)
+		return 0;
+
+	rc = sprintf(buf, "%d\n", (value & TPM2_PT_SC_EH_ENABLE) > 0);
+	return rc;
+}
+static DEVICE_ATTR_RO(eh_enabled);
+
+static ssize_t eh_owned_show(struct device *dev, struct device_attribute *attr,
+			  char *buf)
+{
+	struct tpm_chip *chip = dev_get_drvdata(dev);
+	u32 value;
+	ssize_t rc;
+
+	rc = tpm2_get_tpm_pt(chip, TPM2_PT_PERMANENT, &value,
+			     "could not retrieve PERMANENT property");
+	if (rc)
+		return 0;
+
+	rc = sprintf(buf, "%d\n", (value & TPM2_PT_PM_ENDORSEMENT_AUTH_SET) > 0);
+	return rc;
+}
+static DEVICE_ATTR_RO(eh_owned);
+
+static ssize_t manufacturer_show(struct device *dev,
+				 struct device_attribute *attr,
+				 char *buf)
+{
+	struct tpm_chip *chip = dev_get_drvdata(dev);
+	u32 manufacturer;
+	ssize_t rc;
+	char *str = buf;
+
+	rc = tpm2_get_tpm_pt(chip, TPM2_PT_MANUFACTURER, (u32 *) &manufacturer,
+			     "could not retrieve MANUFACTURER property");
+	if (rc)
+		return 0;
+
+	str += sprintf(str, "0x%08x\n", be32_to_cpu(manufacturer));
+
+	return str - buf;
+}
+static DEVICE_ATTR_RO(manufacturer);
+
+static ssize_t firmware_show(struct device *dev, struct device_attribute *attr,
+			 char *buf)
+{
+	struct tpm_chip *chip = dev_get_drvdata(dev);
+	u32 firmware1;
+	u32 firmware2;
+	ssize_t rc;
+	char *str = buf;
+
+	rc = tpm2_get_tpm_pt(chip, TPM2_PT_FIRMWARE_VERSION_1, (u32 *) &firmware1,
+			     "could not retrieve FIRMWARE_VERSION_1 property");
+	if (rc)
+		return 0;
+
+	rc = tpm2_get_tpm_pt(chip, TPM2_PT_FIRMWARE_VERSION_2, (u32 *) &firmware2,
+			     "could not retrieve FIRMWARE_VERSION_2 property");
+	if (rc)
+		return 0;
+
+	str += sprintf(str, "0x%08x.0x%08x\n", firmware1, firmware2);
+
+	return str - buf;
+}
+static DEVICE_ATTR_RO(firmware);
+
+static ssize_t cancel_store(struct device *dev, struct device_attribute *attr,
+			    const char *buf, size_t count)
+{
+	struct tpm_chip *chip = dev_get_drvdata(dev);
+	if (chip == NULL)
+		return 0;
+
+	chip->ops->cancel(chip);
+	return count;
+}
+static DEVICE_ATTR_WO(cancel);
+
+static ssize_t version_show(struct device *dev, struct device_attribute *attr,
+			   char *buf)
+{
+	char *str = buf;
+
+	str += sprintf(str, "2.0\n");
+
+	return str - buf;
+}
+static DEVICE_ATTR_RO(version);
+
+static struct attribute *tpm_dev_attrs[] = {
+	&dev_attr_sh_enabled.attr,
+	&dev_attr_sh_owned.attr,
+	&dev_attr_eh_enabled.attr,
+	&dev_attr_eh_owned.attr,
+	&dev_attr_manufacturer.attr,
+	&dev_attr_firmware.attr,
+	&dev_attr_cancel.attr,
+	&dev_attr_version.attr,
+	NULL,
+};
+
+static const struct attribute_group tpm_dev_group = {
+	.attrs	= tpm_dev_attrs,
+};
+
+struct pcr_attr {
+	struct attribute attr;
+	unsigned int index;
+	char name[3];
+};
+
+struct pcr_bank {
+	struct kobject kobj;
+	struct kobj_type ktype;
+	struct device *dev;
+	struct pcr_attr pcr_attrs[TPM2_PLATFORM_PCR];
+	struct attribute *attrs[TPM2_PLATFORM_PCR + 1];
+};
+
+static ssize_t pcr_bank_attr_show(struct kobject *kobj,
+				  struct attribute *attr,
+				  char *buf)
+{
+	u8 digest[TPM_DIGEST_SIZE];
+	ssize_t rc;
+	int i;
+	char *str = buf;
+	struct tpm_chip *chip;
+	struct pcr_attr *pcr_attr;
+	struct pcr_bank *pcr_bank;
+
+	pcr_attr = container_of(attr, struct pcr_attr, attr);
+	pcr_bank = container_of(kobj, struct pcr_bank, kobj);
+	chip = dev_get_drvdata(pcr_bank->dev);
+
+	rc = tpm2_pcr_read(chip, pcr_attr->index, digest);
+	if (rc)
+		return rc;
+
+	for (i = 0; i < TPM_DIGEST_SIZE; i++)
+		str += sprintf(str, "%02X", digest[i]);
+
+	str += sprintf(str, "\n");
+
+	return str - buf;
+}
+
+static void pcr_bank_release(struct kobject *kobj)
+{
+	struct pcr_bank *pcr_bank;
+	pcr_bank = container_of(kobj, struct pcr_bank, kobj);
+	kfree(pcr_bank);
+}
+
+static const struct sysfs_ops pcr_bank_sysfs_ops = {
+	.show		= pcr_bank_attr_show,
+};
+
+static struct pcr_bank *pcr_bank_create(struct device *dev,
+					struct kobject *parent_kobj)
+{
+	struct pcr_bank *pcr_bank;
+	struct pcr_attr *pcr_attr;
+	struct attribute *attr;
+	int i;
+	int rc;
+
+	pcr_bank = kzalloc(sizeof(*pcr_bank), GFP_KERNEL);
+	if (!pcr_bank)
+		return NULL;
+
+	pcr_bank->dev			= dev;
+	pcr_bank->ktype.sysfs_ops	= &pcr_bank_sysfs_ops;
+	pcr_bank->ktype.default_attrs	= pcr_bank->attrs;
+	pcr_bank->ktype.release		= pcr_bank_release;
+
+	for (i = 0; i < TPM2_PLATFORM_PCR; i++) {
+		pcr_attr = &pcr_bank->pcr_attrs[i];
+		pcr_attr->index = i;
+		sprintf(pcr_attr->name, "%d", i);
+
+		attr = &pcr_attr->attr;
+		attr->name = pcr_attr->name;
+		attr->mode = S_IRUGO;
+
+		pcr_bank->attrs[i] = attr;
+	}
+
+	pcr_bank->attrs[i] = NULL;
+
+	rc = kobject_init_and_add(&pcr_bank->kobj, &pcr_bank->ktype,
+				  parent_kobj, "sha1");
+	if (rc) {
+		kfree(pcr_bank);
+		return NULL;
+	}
+
+	return pcr_bank;
+}
+
+int tpm2_sysfs_add_device(struct tpm_chip *chip)
+{
+	struct pcr_bank *pcr_bank;
+	struct kobject *pcrs_kobj;
+	struct device *dev = chip->dev;
+	int rc;
+
+	rc = sysfs_create_group(&chip->vendor.miscdev.this_device->kobj,
+				&tpm_dev_group);
+	if (rc) {
+		dev_err(dev, "failed to create sysfs attributes, %d\n", rc);
+		return rc;
+	}
+
+	pcrs_kobj = kobject_create_and_add("pcrs",
+					   &chip->vendor.miscdev.this_device->kobj);
+	if (!pcrs_kobj) {
+		sysfs_remove_group(&dev->kobj, &tpm_dev_group);
+		dev_err(dev, "failed to create sysfs attributes, %d\n", rc);
+		return -ENOMEM;
+	}
+
+	pcr_bank = pcr_bank_create(chip->vendor.miscdev.this_device,
+				   pcrs_kobj);
+	if (!pcr_bank) {
+		kobject_put(pcrs_kobj);
+		sysfs_remove_group(&dev->kobj, &tpm_dev_group);
+		dev_err(dev, "failed to create sysfs attributes, %d\n", rc);
+		return -ENOMEM;
+	}
+
+	kobject_uevent(pcrs_kobj, KOBJ_ADD);
+
+	chip = dev_get_drvdata(dev);
+	chip->pcrs_kobj = pcrs_kobj;
+	chip->sha1_bank = &pcr_bank->kobj;
+
+	return 0;
+}
+
+void tpm2_sysfs_del_device(struct tpm_chip *chip)
+{
+	kobject_put(chip->sha1_bank);
+	kobject_put(chip->pcrs_kobj);
+	sysfs_remove_group(&chip->vendor.miscdev.this_device->kobj,
+			   &tpm_dev_group);
+}