Skip to contentSkip to conversation

tpm: Add Upgrade/Reduced mode support for TPM2 modules

PATCH In review Borys Movchan 4 participants 10 messages Sep 30, 2021 – Dec 21, 2021

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

PATCH v6 1/1 tpm: Add Upgrade/Reduced mode support for TPM2 modules

If something went wrong during the TPM firmware upgrade, like power failure or the firmware image file get corrupted, the TPM might end up in Upgrade or Failure mode upon the next start. The state is persistent between the TPM power cycle/restart.

According to TPM specification:
* If the TPM is in Upgrade mode, it will answer with TPM2_RC_UPGRADE to all commands except Field Upgrade related ones.
* If the TPM is in Failure mode, it will allow performing TPM initialization but will not provide any crypto operations. Will happily respond to Field Upgrade calls.

Change the behavior of the tpm2_auto_startup(), so it detects the active running mode of the TPM by adding the following checks. If tpm2_do_selftest() call returns TPM2_RC_UPGRADE, the TPM is in Upgrade mode.
If the TPM is in Failure mode, it will successfully respond to both tpm2_do_selftest() and tpm2_startup() calls. Although, will fail to answer to tpm2_get_cc_attrs_tbl(). Use this fact to conclude that TPM is in Failure mode.

If detected that the TPM is in the Upgrade or Failure mode, the function sets TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE flag.

The limited mode flag is used later during driver initialization/deinitialization to disable functionality which makes no sense or will fail in the current TPM state. Following functionality is affected:
* do not register TPM as a hwrng
* do not register sysfs entries which provide information impossible to obtain in limited mode
* do not register resource managed character device

---

Notes:
v2:
* Commit message updated.

v3:
* Commit message reworked.

v4:
* Description of how tpm2_auto_startup() detects the mode added to commit message.

v5:
* Introduce global flag: TPM_CHIP_FLAG_LIMITED_MODE.
* Add checks for the flag in places that will not work properly when TPM functionality is limited.
* Avoid registering sysfs and character device entries that have no useful function in limited mode.
* Do not register TPM as a hwrng.
* Do not try to obtain any crypto-related properties from TPM as it will fail in limited mode.

v6:
* Rename the TPM_CHIP_FLAG_LIMITED_MODE to TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE to reduce confusion. Update info messages.

 drivers/char/tpm/tpm-chip.c  | 23 ++++++++++++++++-------
 drivers/char/tpm/tpm-sysfs.c |  3 +++
 drivers/char/tpm/tpm2-cmd.c  | 14 +++++++++++++-
 include/linux/tpm.h          |  2 ++
 4 files changed, 34 insertions(+), 8 deletions(-)
drivers/char/tpm/tpm-chip.c +16 -7
index ddaeceb7e109..3378bfe06006 100644
@@ -444,7 +444,8 @@ static int tpm_add_char_device(struct tpm_chip *chip)
 		return rc;
 	}
 
-	if (chip->flags & TPM_CHIP_FLAG_TPM2) {
+	if (chip->flags & TPM_CHIP_FLAG_TPM2 &&
+	    !(chip->flags & TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE)) {
 		rc = cdev_device_add(&chip->cdevs, &chip->devs);
 		if (rc) {
 			dev_err(&chip->devs,
@@ -488,7 +489,8 @@ static void tpm_del_legacy_sysfs(struct tpm_chip *chip)
 {
 	struct attribute **i;
 
-	if (chip->flags & (TPM_CHIP_FLAG_TPM2 | TPM_CHIP_FLAG_VIRTUAL))
+	if (chip->flags & (TPM_CHIP_FLAG_TPM2 | TPM_CHIP_FLAG_VIRTUAL) ||
+	    chip->flags & TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE)
 		return;
 
 	sysfs_remove_link(&chip->dev.parent->kobj, "ppi");
@@ -506,7 +508,8 @@ static int tpm_add_legacy_sysfs(struct tpm_chip *chip)
 	struct attribute **i;
 	int rc;
 
-	if (chip->flags & (TPM_CHIP_FLAG_TPM2 | TPM_CHIP_FLAG_VIRTUAL))
+	if (chip->flags & (TPM_CHIP_FLAG_TPM2 | TPM_CHIP_FLAG_VIRTUAL) ||
+		chip->flags & TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE)
 		return 0;
 
 	rc = compat_only_sysfs_link_entry_to_kobj(
@@ -536,7 +539,8 @@ static int tpm_hwrng_read(struct hwrng *rng, void *data, size_t max, bool wait)
 
 static int tpm_add_hwrng(struct tpm_chip *chip)
 {
-	if (!IS_ENABLED(CONFIG_HW_RANDOM_TPM))
+	if (!IS_ENABLED(CONFIG_HW_RANDOM_TPM) ||
+	    chip->flags & TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE)
 		return 0;
 
 	snprintf(chip->hwrng_name, sizeof(chip->hwrng_name),
@@ -550,6 +554,9 @@ static int tpm_get_pcr_allocation(struct tpm_chip *chip)
 {
 	int rc;
 
+	if (chip->flags & TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE)
+		return 0;
+
 	rc = (chip->flags & TPM_CHIP_FLAG_TPM2) ?
 	     tpm2_get_pcr_allocation(chip) :
 	     tpm1_get_pcr_allocation(chip);
@@ -612,7 +619,7 @@ int tpm_chip_register(struct tpm_chip *chip)
 	return 0;
 
 out_hwrng:
-	if (IS_ENABLED(CONFIG_HW_RANDOM_TPM))
+	if (IS_ENABLED(CONFIG_HW_RANDOM_TPM) && !(chip->flags & TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE))
 		hwrng_unregister(&chip->hwrng);
 out_ppi:
 	tpm_bios_log_teardown(chip);
@@ -637,10 +644,12 @@ EXPORT_SYMBOL_GPL(tpm_chip_register);
 void tpm_chip_unregister(struct tpm_chip *chip)
 {
 	tpm_del_legacy_sysfs(chip);
-	if (IS_ENABLED(CONFIG_HW_RANDOM_TPM))
+	if (IS_ENABLED(CONFIG_HW_RANDOM_TPM) &&
+	    !(chip->flags & TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE))
 		hwrng_unregister(&chip->hwrng);
 	tpm_bios_log_teardown(chip);
-	if (chip->flags & TPM_CHIP_FLAG_TPM2)
+	if (chip->flags & TPM_CHIP_FLAG_TPM2 &&
+	    !(chip->flags & TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE))
 		cdev_device_del(&chip->cdevs, &chip->devs);
 	tpm_del_char_device(chip);
 }
drivers/char/tpm/tpm-sysfs.c +3 -0
index 63f03cfb8e6a..2842f3c667a5 100644
@@ -480,6 +480,9 @@ void tpm_sysfs_add_device(struct tpm_chip *chip)
 
 	WARN_ON(chip->groups_cnt != 0);
 
+	if (chip->flags & TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE)
+		return;
+
 	if (chip->flags & TPM_CHIP_FLAG_TPM2)
 		chip->groups[chip->groups_cnt++] = &tpm2_dev_group;
 	else
drivers/char/tpm/tpm2-cmd.c +13 -1
index a25815a6f625..76245c861e59 100644
@@ -729,7 +729,12 @@ int tpm2_auto_startup(struct tpm_chip *chip)
 		goto out;
 
 	rc = tpm2_do_selftest(chip);
-	if (rc && rc != TPM2_RC_INITIALIZE)
+	if (rc == TPM2_RC_UPGRADE) {
+		dev_info(&chip->dev, "TPM requires firmware upgrade\n");
+		chip->flags |= TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE;
+		rc = 0;
+		goto out;
+	} else if (rc && rc != TPM2_RC_INITIALIZE)
 		goto out;
 
 	if (rc == TPM2_RC_INITIALIZE) {
@@ -743,6 +748,13 @@ int tpm2_auto_startup(struct tpm_chip *chip)
 	}
 
 	rc = tpm2_get_cc_attrs_tbl(chip);
+	if (rc) {
+		dev_info(&chip->dev,
+			 "TPM requires firmware recovery/upgrade\n");
+		chip->flags |= TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE;
+		rc = 0;
+		goto out;
+	}
 
 out:
 	if (rc > 0)
include/linux/tpm.h +2 -0
index aa11fe323c56..498f487d786f 100644
@@ -207,6 +207,7 @@ enum tpm2_return_codes {
 	TPM2_RC_INITIALIZE	= 0x0100, /* RC_VER1 */
 	TPM2_RC_FAILURE		= 0x0101,
 	TPM2_RC_DISABLED	= 0x0120,
+	TPM2_RC_UPGRADE		= 0x012D,
 	TPM2_RC_COMMAND_CODE    = 0x0143,
 	TPM2_RC_TESTING		= 0x090A, /* RC_WARN */
 	TPM2_RC_REFERENCE_H0	= 0x0910,
@@ -277,6 +278,7 @@ enum tpm_chip_flags {
 	TPM_CHIP_FLAG_HAVE_TIMEOUTS	= BIT(4),
 	TPM_CHIP_FLAG_ALWAYS_POWERED	= BIT(5),
 	TPM_CHIP_FLAG_FIRMWARE_POWER_MANAGED	= BIT(6),
+	TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE	= BIT(7),
 };
 
 #define to_tpm_chip(d) container_of(d, struct tpm_chip, dev)
Signature · 1 line
2.20.1
maintainer
#1 lore raw
Quoted 8 lines · Borys Movchan

If something went wrong during the TPM firmware upgrade, like power failure or the firmware image file get corrupted, the TPM might end up in Upgrade or Failure mode upon the next start. The state is persistent between the TPM power cycle/restart.

According to TPM specification:
* If the TPM is in Upgrade mode, it will answer with TPM2_RC_UPGRADE to all commands except Field Upgrade related ones.

How many such command are there and what they are? This patch is missing the part that filters in only these commands.

Quoted 13 lines

* If the TPM is in Failure mode, it will allow performing TPM initialization but will not provide any crypto operations. Will happily respond to Field Upgrade calls.

Change the behavior of the tpm2_auto_startup(), so it detects the active running mode of the TPM by adding the following checks. If tpm2_do_selftest() call returns TPM2_RC_UPGRADE, the TPM is in Upgrade mode.
If the TPM is in Failure mode, it will successfully respond to both tpm2_do_selftest() and tpm2_startup() calls. Although, will fail to answer to tpm2_get_cc_attrs_tbl(). Use this fact to conclude that TPM is in Failure mode.

Quoted 2 lines

If detected that the TPM is in the Upgrade or Failure mode, the function sets TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE flag.

I think we should shorten this to TPM_CHIP_FLAG_FIRMWARE_MODE. It's clear enough and just easier for eyes, really.

Quoted 1 line

The limited mode flag is used later during driver

You probably have forgot to update commit message over here? Please try to align it with the code changes.

Quoted 4 lines

initialization/deinitialization to disable functionality which makes no sense or will fail in the current TPM state. Following functionality is affected:
* do not register TPM as a hwrng

Nit: "do" -> "Do"

Quoted 3 lines

* do not register sysfs entries which provide information impossible to obtain in limited mode
* do not register resource managed character device

Ditto.

Quoted 40 lines

---

Notes:
v2:
* Commit message updated.

v3:
* Commit message reworked.

v4:
* Description of how tpm2_auto_startup() detects the mode added to commit message.

v5:
* Introduce global flag: TPM_CHIP_FLAG_LIMITED_MODE.
* Add checks for the flag in places that will not work properly when TPM functionality is limited.
* Avoid registering sysfs and character device entries that have no useful function in limited mode.
* Do not register TPM as a hwrng.
* Do not try to obtain any crypto-related properties from TPM as it will fail in limited mode.

v6:
* Rename the TPM_CHIP_FLAG_LIMITED_MODE to TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE to reduce confusion. Update info messages.

 drivers/char/tpm/tpm-chip.c  | 23 ++++++++++++++++-------
 drivers/char/tpm/tpm-sysfs.c |  3 +++
 drivers/char/tpm/tpm2-cmd.c  | 14 +++++++++++++-
 include/linux/tpm.h          |  2 ++
 4 files changed, 34 insertions(+), 8 deletions(-)
drivers/char/tpm/tpm-chip.c +0 -0
index ddaeceb7e109..3378bfe06006 100644

drivers/char/tpm/tpm-chip.c static int tpm_add_char_device(struct tpm_chip *chip) · line 448

Quoted 6 lines
drivers/char/tpm/tpm-chip.c +2 -1
@@ -444,7 +444,8 @@ static int tpm_add_char_device(struct tpm_chip *chip)
 		return rc;
 	}
 
-	if (chip->flags & TPM_CHIP_FLAG_TPM2) {
+	if (chip->flags & TPM_CHIP_FLAG_TPM2 &&
+	    !(chip->flags & TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE)) {

Please do this instead in tpm.h, give that the check is everywhere:

/*
* Check if TPM device is in the firmware upgrade mode. */
static inline bool tpm_is_firmware_upgrade(void)

{
	return chip->flags & TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE;
}

Makes the code less stressing to read (and find the decision points later on with tools).

Quoted 100 lines
drivers/char/tpm/tpm-chip.c +14 -6
 		rc = cdev_device_add(&chip->cdevs, &chip->devs);
 		if (rc) {
 			dev_err(&chip->devs,
@@ -488,7 +489,8 @@ static void tpm_del_legacy_sysfs(struct tpm_chip *chip)
 {
 	struct attribute **i;
 
-	if (chip->flags & (TPM_CHIP_FLAG_TPM2 | TPM_CHIP_FLAG_VIRTUAL))
+	if (chip->flags & (TPM_CHIP_FLAG_TPM2 | TPM_CHIP_FLAG_VIRTUAL) ||
+	    chip->flags & TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE)
 		return;
 
 	sysfs_remove_link(&chip->dev.parent->kobj, "ppi");
@@ -506,7 +508,8 @@ static int tpm_add_legacy_sysfs(struct tpm_chip *chip)
 	struct attribute **i;
 	int rc;
 
-	if (chip->flags & (TPM_CHIP_FLAG_TPM2 | TPM_CHIP_FLAG_VIRTUAL))
+	if (chip->flags & (TPM_CHIP_FLAG_TPM2 | TPM_CHIP_FLAG_VIRTUAL) ||
+		chip->flags & TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE)
 		return 0;
 
 	rc = compat_only_sysfs_link_entry_to_kobj(
@@ -536,7 +539,8 @@ static int tpm_hwrng_read(struct hwrng *rng, void *data, size_t max, bool wait)
 
 static int tpm_add_hwrng(struct tpm_chip *chip)
 {
-	if (!IS_ENABLED(CONFIG_HW_RANDOM_TPM))
+	if (!IS_ENABLED(CONFIG_HW_RANDOM_TPM) ||
+	    chip->flags & TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE)
 		return 0;
 
 	snprintf(chip->hwrng_name, sizeof(chip->hwrng_name),
@@ -550,6 +554,9 @@ static int tpm_get_pcr_allocation(struct tpm_chip *chip)
 {
 	int rc;
 
+	if (chip->flags & TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE)
+		return 0;
+
 	rc = (chip->flags & TPM_CHIP_FLAG_TPM2) ?
 	     tpm2_get_pcr_allocation(chip) :
 	     tpm1_get_pcr_allocation(chip);
@@ -612,7 +619,7 @@ int tpm_chip_register(struct tpm_chip *chip)
 	return 0;
 
 out_hwrng:
-	if (IS_ENABLED(CONFIG_HW_RANDOM_TPM))
+	if (IS_ENABLED(CONFIG_HW_RANDOM_TPM) && !(chip->flags & TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE))
 		hwrng_unregister(&chip->hwrng);
 out_ppi:
 	tpm_bios_log_teardown(chip);
@@ -637,10 +644,12 @@ EXPORT_SYMBOL_GPL(tpm_chip_register);
 void tpm_chip_unregister(struct tpm_chip *chip)
 {
 	tpm_del_legacy_sysfs(chip);
-	if (IS_ENABLED(CONFIG_HW_RANDOM_TPM))
+	if (IS_ENABLED(CONFIG_HW_RANDOM_TPM) &&
+	    !(chip->flags & TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE))
 		hwrng_unregister(&chip->hwrng);
 	tpm_bios_log_teardown(chip);
-	if (chip->flags & TPM_CHIP_FLAG_TPM2)
+	if (chip->flags & TPM_CHIP_FLAG_TPM2 &&
+	    !(chip->flags & TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE))
 		cdev_device_del(&chip->cdevs, &chip->devs);
 	tpm_del_char_device(chip);
 }
drivers/char/tpm/tpm-sysfs.c +3 -0
index 63f03cfb8e6a..2842f3c667a5 100644
@@ -480,6 +480,9 @@ void tpm_sysfs_add_device(struct tpm_chip *chip)
 
 	WARN_ON(chip->groups_cnt != 0);
 
+	if (chip->flags & TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE)
+		return;
+
 	if (chip->flags & TPM_CHIP_FLAG_TPM2)
 		chip->groups[chip->groups_cnt++] = &tpm2_dev_group;
 	else
drivers/char/tpm/tpm2-cmd.c +6 -1
index a25815a6f625..76245c861e59 100644
@@ -729,7 +729,12 @@ int tpm2_auto_startup(struct tpm_chip *chip)
 		goto out;
 
 	rc = tpm2_do_selftest(chip);
-	if (rc && rc != TPM2_RC_INITIALIZE)
+	if (rc == TPM2_RC_UPGRADE) {
+		dev_info(&chip->dev, "TPM requires firmware upgrade\n");
+		chip->flags |= TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE;
+		rc = 0;
+		goto out;
+	} else if (rc && rc != TPM2_RC_INITIALIZE)
 		goto out;
 
 	if (rc == TPM2_RC_INITIALIZE) {

drivers/char/tpm/tpm2-cmd.c int tpm2_auto_startup(struct tpm_chip *chip) · line 753

Quoted 6 lines
drivers/char/tpm/tpm2-cmd.c +3 -0
@@ -743,6 +748,13 @@ int tpm2_auto_startup(struct tpm_chip *chip)
 	}
 
 	rc = tpm2_get_cc_attrs_tbl(chip);
+	if (rc) {
+		dev_info(&chip->dev,
+			 "TPM requires firmware recovery/upgrade\n");

Why these two dev_info() messages differ?

You should put a single info message that is unconditionally printed when in FW upgrade mode, and not sprikle bloat to fallback paths.

Quoted 20 lines
drivers/char/tpm/tpm2-cmd.c +4 -0
+		chip->flags |= TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE;
+		rc = 0;
+		goto out;
+	}
 
 out:
 	if (rc > 0)
include/linux/tpm.h +1 -0
index aa11fe323c56..498f487d786f 100644
@@ -207,6 +207,7 @@ enum tpm2_return_codes {
 	TPM2_RC_INITIALIZE	= 0x0100, /* RC_VER1 */
 	TPM2_RC_FAILURE		= 0x0101,
 	TPM2_RC_DISABLED	= 0x0120,
+	TPM2_RC_UPGRADE		= 0x012D,
 	TPM2_RC_COMMAND_CODE    = 0x0143,
 	TPM2_RC_TESTING		= 0x090A, /* RC_WARN */
 	TPM2_RC_REFERENCE_H0	= 0x0910,

include/linux/tpm.h enum tpm_chip_flags { · line 284

Quoted 7 lines
include/linux/tpm.h +1 -0
@@ -277,6 +278,7 @@ enum tpm_chip_flags {
 	TPM_CHIP_FLAG_HAVE_TIMEOUTS	= BIT(4),
 	TPM_CHIP_FLAG_ALWAYS_POWERED	= BIT(5),
 	TPM_CHIP_FLAG_FIRMWARE_POWER_MANAGED	= BIT(6),
+	TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE	= BIT(7),
 };
 
 #define to_tpm_chip(d) container_of(d, struct tpm_chip, dev)

/Jarkko

maintainer
#2 lore raw
Quoted 4 lines · Jarkko Sakkinen

static inline bool tpm_is_firmware_upgrade(void)

{
	return chip->flags & TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE;
}

Ugh, here's refined and fixed version:

static inline bool tpm_in_firmware_mode(struct tpm_chip *chip)

{
	return chip->flags & TPM_CHIP_FLAG_FIRMWARE_MODE;
}

/Jarkko

#3 lore raw

PATCH v6 1/1 tpm: Add Upgrade/Reduced mode support for TPM2 modules

In reply to Jarkko Sakkinen

Hello Jarkko!

I am Axel, a colleague to Borys, and will take over the integration of this patch since Borys is too busy with other work.

I am sorry for the long delay since the last update.

Quoted 11 lines
Quoted 8 lines

If something went wrong during the TPM firmware upgrade, like power failure or the firmware image file get corrupted, the TPM might end up in Upgrade or Failure mode upon the next start. The state is persistent between the TPM power cycle/restart.
=20
According to TPM specification:
* If the TPM is in Upgrade mode, it will answer with TPM2_RC_UPGRADE to all commands except Field Upgrade related ones.

How many such command are there and what they are? This patch is missing the part that filters in only these commands.

The documentation states:

"If the proper authorization is given, the TPM will retain the signed digest and enter the Field Upgrade mode (FUM). While in FUM, the TPM will accept TPM2_FieldUpgradeData() commands. It may accept other commands if it is able to complete them using the previously installed firmware. Otherwise, it will return TPM_RC_UPGRADE." www.trustedcomputinggroup.org/wp-content/uploads/ TPM-Rev-2.0-Part-3-Commands-01.38.pdf, page 307

and I do not not think that TPM2_FieldUpgradeData is a command which will/ should be given in a tpm kernel driver. So I think it is enough to handle all other commands as it is right now. Please tell me if you disagree.

I will update the commit message to clarify which commands there are.

Quoted 16 lines
Quoted 7 lines

* If the TPM is in Failure mode, it will allow performing TPM initialization but will not provide any crypto operations. Will happily respond to Field Upgrade calls.
=20
Change the behavior of the tpm2_auto_startup(), so it detects the active running mode of the TPM by adding the following checks. If tpm2_do_selftest() call returns TPM2_RC_UPGRADE, the TPM is in Upgrade

Quoted 8 lines

mode.
If the TPM is in Failure mode, it will successfully respond to both tpm2_do_selftest() and tpm2_startup() calls. Although, will fail to answer to tpm2_get_cc_attrs_tbl(). Use this fact to conclude that TPM is in Failure mode.
=20
If detected that the TPM is in the Upgrade or Failure mode, the function sets TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE flag.

Quoted 2 lines

I think we should shorten this to TPM_CHIP_FLAG_FIRMWARE_MODE. It's clear enough and just easier for eyes, really.

Done.

Quoted 4 lines
Quoted 1 line

The limited mode flag is used later during driver

You probably have forgot to update commit message over here? Please try to align it with the code changes.

Done.

Quoted 5 lines
Quoted 4 lines

initialization/deinitialization to disable functionality which makes no sense or will fail in the current TPM state. Following functionality is affected:
* do not register TPM as a hwrng

Quoted 7 lines

Nit: "do" -> "Do"

Quoted 3 lines

* do not register sysfs entries which provide information impossible to obtain in limited mode
* do not register resource managed character device

Ditto.'

Done.

Quoted 57 lines
Quoted 18 lines

=20

---
=20
Notes:
v2:
* Commit message updated.
=20
v3:
* Commit message reworked.
=20
v4:
* Description of how tpm2_auto_startup() detects the mode added to commit message.
=20
v5:
* Introduce global flag: TPM_CHIP_FLAG_LIMITED_MODE.
* Add checks for the flag in places that will not work properly when=

TPM

Quoted 2 lines

functionality is limited.
* Avoid registering sysfs and character device entries that have no =

useful

Quoted 3 lines

function in limited mode.
* Do not register TPM as a hwrng.
* Do not try to obtain any crypto-related properties from TPM as it =

will fail

Quoted 4 lines

in limited mode.
=20
v6:
* Rename the TPM_CHIP_FLAG_LIMITED_MODE to TPM_CHIP_FLAG_FIRMWARE_UP=

GRADE_MODE

Quoted 16 lines

to reduce confusion. Update info messages.
=20

 drivers/char/tpm/tpm-chip.c  | 23 ++++++++++++++++-------
 drivers/char/tpm/tpm-sysfs.c |  3 +++
 drivers/char/tpm/tpm2-cmd.c  | 14 +++++++++++++-
 include/linux/tpm.h          |  2 ++
 4 files changed, 34 insertions(+), 8 deletions(-)

=20

drivers/char/tpm/tpm-chip.c +0 -0
index ddaeceb7e109..3378bfe06006 100644
@@ -444,7 +444,8 @@ static int tpm_add_char_device(struct tpm_chip *chip)
 		return rc;
 	}

=20

drivers/char/tpm/tpm-chip.c static int tpm_add_char_device(struct tpm_chip *chip) · line 448

Quoted 3 lines
drivers/char/tpm/tpm-chip.c +2 -1
-	if (chip->flags & TPM_CHIP_FLAG_TPM2) {
+	if (chip->flags & TPM_CHIP_FLAG_TPM2 &&
+	    !(chip->flags & TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE)) {

Please do this instead in tpm.h, give that the check is everywhere:

/*
* Check if TPM device is in the firmware upgrade mode. */
static inline bool tpm_is_firmware_upgrade(void)

Quoted 8 lines
{
	return chip->flags & TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE;
}

Makes the code less stressing to read (and find the decision points later on with tools).

Done.

Quoted 114 lines

drivers/char/tpm/tpm-chip.c static void tpm_del_legacy_sysfs(struct tpm_chip *chi=

Quoted 4 lines
drivers/char/tpm/tpm-chip.c +0 -0
 		rc =3D cdev_device_add(&chip->cdevs, &chip->devs);
 		if (rc) {
 			dev_err(&chip->devs,
@@ -488,7 +489,8 @@ static void tpm_del_legacy_sysfs(struct tpm_chip *chi=

p)

drivers/char/tpm/tpm-chip.c static int tpm_add_legacy_sysfs(struct tpm_chip *chip=

Quoted 10 lines
drivers/char/tpm/tpm-chip.c +0 -0
 {
 	struct attribute **i;

=20

drivers/char/tpm/tpm-chip.c +2 -1
-	if (chip->flags & (TPM_CHIP_FLAG_TPM2 | TPM_CHIP_FLAG_VIRTUAL))
+	if (chip->flags & (TPM_CHIP_FLAG_TPM2 | TPM_CHIP_FLAG_VIRTUAL) ||
+	    chip->flags & TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE)
 		return;

=20

drivers/char/tpm/tpm-chip.c +0 -0
 	sysfs_remove_link(&chip->dev.parent->kobj, "ppi");
@@ -506,7 +508,8 @@ static int tpm_add_legacy_sysfs(struct tpm_chip *chip=

)

drivers/char/tpm/tpm-chip.c static int tpm_hwrng_read(struct hwrng *rng, void *da=

Quoted 10 lines
drivers/char/tpm/tpm-chip.c +0 -0
 	struct attribute **i;
 	int rc;

=20

drivers/char/tpm/tpm-chip.c +2 -1
-	if (chip->flags & (TPM_CHIP_FLAG_TPM2 | TPM_CHIP_FLAG_VIRTUAL))
+	if (chip->flags & (TPM_CHIP_FLAG_TPM2 | TPM_CHIP_FLAG_VIRTUAL) ||
+		chip->flags & TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE)
 		return 0;

=20

drivers/char/tpm/tpm-chip.c +0 -0
 	rc =3D compat_only_sysfs_link_entry_to_kobj(
@@ -536,7 +539,8 @@ static int tpm_hwrng_read(struct hwrng *rng, void *da=

ta, size_t max, bool wait)

drivers/char/tpm/tpm-chip.c static int tpm_get_pcr_allocation(struct tpm_chip *ch=

Quoted 10 lines

=20
static int tpm_add_hwrng(struct tpm_chip *chip)
{

drivers/char/tpm/tpm-chip.c +2 -1
-	if (!IS_ENABLED(CONFIG_HW_RANDOM_TPM))
+	if (!IS_ENABLED(CONFIG_HW_RANDOM_TPM) ||
+	    chip->flags & TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE)
 		return 0;

=20

drivers/char/tpm/tpm-chip.c +0 -0
 	snprintf(chip->hwrng_name, sizeof(chip->hwrng_name),
@@ -550,6 +554,9 @@ static int tpm_get_pcr_allocation(struct tpm_chip *ch=

ip)

Quoted 13 lines
drivers/char/tpm/tpm-chip.c +0 -0
 {
 	int rc;

=20

drivers/char/tpm/tpm-chip.c +3 -0
+	if (chip->flags & TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE)
+		return 0;
+
 	rc =3D (chip->flags & TPM_CHIP_FLAG_TPM2) ?
 	     tpm2_get_pcr_allocation(chip) :
 	     tpm1_get_pcr_allocation(chip);
@@ -612,7 +619,7 @@ int tpm_chip_register(struct tpm_chip *chip)
 	return 0;

=20
out_hwrng:

drivers/char/tpm/tpm-chip.c

Quoted 2 lines
drivers/char/tpm/tpm-chip.c +1 -1
-	if (IS_ENABLED(CONFIG_HW_RANDOM_TPM))
+	if (IS_ENABLED(CONFIG_HW_RANDOM_TPM) && !(chip->flags & TPM_CHIP_FLAG_F=

IRMWARE_UPGRADE_MODE))

Quoted 53 lines
drivers/char/tpm/tpm-chip.c +4 -2
 		hwrng_unregister(&chip->hwrng);
 out_ppi:
 	tpm_bios_log_teardown(chip);
@@ -637,10 +644,12 @@ EXPORT_SYMBOL_GPL(tpm_chip_register);
 void tpm_chip_unregister(struct tpm_chip *chip)
 {
 	tpm_del_legacy_sysfs(chip);
-	if (IS_ENABLED(CONFIG_HW_RANDOM_TPM))
+	if (IS_ENABLED(CONFIG_HW_RANDOM_TPM) &&
+	    !(chip->flags & TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE))
 		hwrng_unregister(&chip->hwrng);
 	tpm_bios_log_teardown(chip);
-	if (chip->flags & TPM_CHIP_FLAG_TPM2)
+	if (chip->flags & TPM_CHIP_FLAG_TPM2 &&
+	    !(chip->flags & TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE))
 		cdev_device_del(&chip->cdevs, &chip->devs);
 	tpm_del_char_device(chip);
 }
drivers/char/tpm/tpm-sysfs.c +0 -0
index 63f03cfb8e6a..2842f3c667a5 100644
@@ -480,6 +480,9 @@ void tpm_sysfs_add_device(struct tpm_chip *chip)

=20
WARN_ON(chip->groups_cnt !=3D 0);
=20

drivers/char/tpm/tpm-sysfs.c +3 -0
+	if (chip->flags & TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE)
+		return;
+
 	if (chip->flags & TPM_CHIP_FLAG_TPM2)
 		chip->groups[chip->groups_cnt++] =3D &tpm2_dev_group;
 	else
drivers/char/tpm/tpm2-cmd.c +0 -0
index a25815a6f625..76245c861e59 100644
@@ -729,7 +729,12 @@ int tpm2_auto_startup(struct tpm_chip *chip)
 		goto out;

=20

drivers/char/tpm/tpm2-cmd.c +6 -1
 	rc =3D tpm2_do_selftest(chip);
-	if (rc && rc !=3D TPM2_RC_INITIALIZE)
+	if (rc =3D=3D TPM2_RC_UPGRADE) {
+		dev_info(&chip->dev, "TPM requires firmware upgrade\n");
+		chip->flags |=3D TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE;
+		rc =3D 0;
+		goto out;
+	} else if (rc && rc !=3D TPM2_RC_INITIALIZE)
 		goto out;

=20

drivers/char/tpm/tpm2-cmd.c +0 -0
 	if (rc =3D=3D TPM2_RC_INITIALIZE) {
@@ -743,6 +748,13 @@ int tpm2_auto_startup(struct tpm_chip *chip)
 	}

=20

drivers/char/tpm/tpm2-cmd.c int tpm2_auto_startup(struct tpm_chip *chip) · line 753

Quoted 4 lines
drivers/char/tpm/tpm2-cmd.c +3 -0
 	rc =3D tpm2_get_cc_attrs_tbl(chip);
+	if (rc) {
+		dev_info(&chip->dev,
+			 "TPM requires firmware recovery/upgrade\n");

Why these two dev_info() messages differ?

Quoted 3 lines

You should put a single info message that is unconditionally printed when in FW upgrade mode, and not sprikle bloat to fallback paths.

I removed these dev_info() messages and added one in out which will be printed if (rc == TPM2_RC_UPGRADE).

Quoted 19 lines
Quoted 19 lines
drivers/char/tpm/tpm2-cmd.c +4 -0
+		chip->flags |=3D TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE;
+		rc =3D 0;
+		goto out;
+	}

=20
out:

drivers/char/tpm/tpm2-cmd.c +0 -0
 	if (rc > 0)
include/linux/tpm.h +1 -0
index aa11fe323c56..498f487d786f 100644
@@ -207,6 +207,7 @@ enum tpm2_return_codes {
 	TPM2_RC_INITIALIZE	=3D 0x0100, /* RC_VER1 */
 	TPM2_RC_FAILURE		=3D 0x0101,
 	TPM2_RC_DISABLED	=3D 0x0120,
+	TPM2_RC_UPGRADE		=3D 0x012D,
 	TPM2_RC_COMMAND_CODE    =3D 0x0143,
 	TPM2_RC_TESTING		=3D 0x090A, /* RC_WARN */
 	TPM2_RC_REFERENCE_H0	=3D 0x0910,
Quoted 8 lines
Quoted 8 lines
include/linux/tpm.h +1 -0
@@ -277,6 +278,7 @@ enum tpm_chip_flags {
 	TPM_CHIP_FLAG_HAVE_TIMEOUTS	=3D BIT(4),
 	TPM_CHIP_FLAG_ALWAYS_POWERED	=3D BIT(5),
 	TPM_CHIP_FLAG_FIRMWARE_POWER_MANAGED	=3D BIT(6),
+	TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE	=3D BIT(7),
 };

=20
#define to_tpm_chip(d) container_of(d, struct tpm_chip, dev)

Best regards, Axel

#4 lore raw

PATCH v7 1/1 tpm: Add Upgrade/Reduced mode support for TPM2 modules

In reply to Jarkko Sakkinen

From: Axel Jonsson <[email protected]>

If something went wrong during the TPM firmware upgrade, like power failure or the firmware image file get corrupted, the TPM might end up in Upgrade or Failure mode upon the next start. The state is persistent between the TPM power cycle/restart.

According to TPM specification:
* If the TPM is in Upgrade mode, it will answer with TPM2_RC_UPGRADE to all commands except TPM2_FieldUpgradeData(). It may also accept other commands if it is able to complete them using the previously installed firmware.
* If the TPM is in Failure mode, it will allow performing TPM initialization but will not provide any crypto operations. Will happily respond to Field Upgrade calls.

Change the behavior of the tpm2_auto_startup(), so it detects the active running mode of the TPM by adding the following checks. If tpm2_do_selftest() call returns TPM2_RC_UPGRADE, the TPM is in Upgrade mode.
If the TPM is in Failure mode, it will successfully respond to both tpm2_do_selftest() and tpm2_startup() calls. Although, will fail to answer to tpm2_get_cc_attrs_tbl(). Use this fact to conclude that TPM is in Failure mode.

If detected that the TPM is in the Upgrade or Failure mode, the function sets TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE flag.

The TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE flag is used later during driver initialization/deinitialization to disable functionality which makes no sense or will fail in the current TPM state. Following functionality is affected:
* Do not register TPM as a hwrng
* Do not register sysfs entries which provide information impossible to obtain in limited mode
* Do not register resource managed character device

---

Notes:
Notes:
v2:
* Commit message updated.

v3:
* Commit message reworked.

v4:
* Description of how tpm2_auto_startup() detects the mode added to commit message.

v5:
* Introduce global flag: TPM_CHIP_FLAG_LIMITED_MODE.
* Add checks for the flag in places that will not work properly when TPM functionality is limited.
* Avoid registering sysfs and character device entries that have no useful function in limited mode.
* Do not register TPM as a hwrng.
* Do not try to obtain any crypto-related properties from TPM as it will fail in limited mode.

v6:
* Rename the TPM_CHIP_FLAG_LIMITED_MODE to TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE to reduce confusion. Update info messages.

v7:
* Clarified in commit message which command can be run in firmware upgrade mode
* Corrected info and style in commit message
* Renamed TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE to TPM_CHIP_FLAG_FIRMWARE_MODE
* Replaced TPM_CHIP_FLAG_FIRMWARE_MODE flag check with function from include/linux/tpm.h

 drivers/char/tpm/tpm-chip.c  | 23 ++++++++++++++++-------
 drivers/char/tpm/tpm-sysfs.c |  3 +++
 drivers/char/tpm/tpm2-cmd.c  |  6 +++++-
 include/linux/tpm.h          | 10 ++++++++++
 4 files changed, 34 insertions(+), 8 deletions(-)
drivers/char/tpm/tpm-chip.c +16 -7
index ddaeceb7e109..285d054d187b 100644
@@ -444,7 +444,8 @@ static int tpm_add_char_device(struct tpm_chip *chip)
 		return rc;
 	}
 
-	if (chip->flags & TPM_CHIP_FLAG_TPM2) {
+	if (chip->flags & TPM_CHIP_FLAG_TPM2 &&
+	    !tpm_is_firmware_upgrade(chip)) {
 		rc = cdev_device_add(&chip->cdevs, &chip->devs);
 		if (rc) {
 			dev_err(&chip->devs,
@@ -488,7 +489,8 @@ static void tpm_del_legacy_sysfs(struct tpm_chip *chip)
 {
 	struct attribute **i;
 
-	if (chip->flags & (TPM_CHIP_FLAG_TPM2 | TPM_CHIP_FLAG_VIRTUAL))
+	if (chip->flags & (TPM_CHIP_FLAG_TPM2 | TPM_CHIP_FLAG_VIRTUAL) ||
+	    tpm_is_firmware_upgrade(chip))
 		return;
 
 	sysfs_remove_link(&chip->dev.parent->kobj, "ppi");
@@ -506,7 +508,8 @@ static int tpm_add_legacy_sysfs(struct tpm_chip *chip)
 	struct attribute **i;
 	int rc;
 
-	if (chip->flags & (TPM_CHIP_FLAG_TPM2 | TPM_CHIP_FLAG_VIRTUAL))
+	if (chip->flags & (TPM_CHIP_FLAG_TPM2 | TPM_CHIP_FLAG_VIRTUAL) ||
+		tpm_is_firmware_upgrade(chip))
 		return 0;
 
 	rc = compat_only_sysfs_link_entry_to_kobj(
@@ -536,7 +539,8 @@ static int tpm_hwrng_read(struct hwrng *rng, void *data, size_t max, bool wait)
 
 static int tpm_add_hwrng(struct tpm_chip *chip)
 {
-	if (!IS_ENABLED(CONFIG_HW_RANDOM_TPM))
+	if (!IS_ENABLED(CONFIG_HW_RANDOM_TPM) ||
+	    tpm_is_firmware_upgrade(chip))
 		return 0;
 
 	snprintf(chip->hwrng_name, sizeof(chip->hwrng_name),
@@ -550,6 +554,9 @@ static int tpm_get_pcr_allocation(struct tpm_chip *chip)
 {
 	int rc;
 
+	if (tpm_is_firmware_upgrade(chip))
+		return 0;
+
 	rc = (chip->flags & TPM_CHIP_FLAG_TPM2) ?
 	     tpm2_get_pcr_allocation(chip) :
 	     tpm1_get_pcr_allocation(chip);
@@ -612,7 +619,7 @@ int tpm_chip_register(struct tpm_chip *chip)
 	return 0;
 
 out_hwrng:
-	if (IS_ENABLED(CONFIG_HW_RANDOM_TPM))
+	if (IS_ENABLED(CONFIG_HW_RANDOM_TPM) && !tpm_is_firmware_upgrade(chip))
 		hwrng_unregister(&chip->hwrng);
 out_ppi:
 	tpm_bios_log_teardown(chip);
@@ -637,10 +644,12 @@ EXPORT_SYMBOL_GPL(tpm_chip_register);
 void tpm_chip_unregister(struct tpm_chip *chip)
 {
 	tpm_del_legacy_sysfs(chip);
-	if (IS_ENABLED(CONFIG_HW_RANDOM_TPM))
+	if (IS_ENABLED(CONFIG_HW_RANDOM_TPM) &&
+	    !tpm_is_firmware_upgrade(chip))
 		hwrng_unregister(&chip->hwrng);
 	tpm_bios_log_teardown(chip);
-	if (chip->flags & TPM_CHIP_FLAG_TPM2)
+	if (chip->flags & TPM_CHIP_FLAG_TPM2 &&
+	    !tpm_is_firmware_upgrade(chip))
 		cdev_device_del(&chip->cdevs, &chip->devs);
 	tpm_del_char_device(chip);
 }
drivers/char/tpm/tpm-sysfs.c +3 -0
index 63f03cfb8e6a..54c71473aa29 100644
@@ -480,6 +480,9 @@ void tpm_sysfs_add_device(struct tpm_chip *chip)
 
 	WARN_ON(chip->groups_cnt != 0);
 
+	if (tpm_is_firmware_upgrade(chip))
+		return;
+
 	if (chip->flags & TPM_CHIP_FLAG_TPM2)
 		chip->groups[chip->groups_cnt++] = &tpm2_dev_group;
 	else
drivers/char/tpm/tpm2-cmd.c +5 -1
index a25815a6f625..24928b0160f1 100644
@@ -743,8 +743,12 @@ int tpm2_auto_startup(struct tpm_chip *chip)
 	}
 
 	rc = tpm2_get_cc_attrs_tbl(chip);
-
 out:
+	if (rc == TPM2_RC_UPGRADE) {
+		dev_info(&chip->dev, "TPM in field upgrade mode, requires firmware upgrade\n");
+		chip->flags |= TPM_CHIP_FLAG_FIRMWARE_MODE;
+		rc = 0;
+	}
 	if (rc > 0)
 		rc = -ENODEV;
 	return rc;
include/linux/tpm.h +10 -0
index 12d827734686..d38efdae382c 100644
@@ -207,6 +207,7 @@ enum tpm2_return_codes {
 	TPM2_RC_INITIALIZE	= 0x0100, /* RC_VER1 */
 	TPM2_RC_FAILURE		= 0x0101,
 	TPM2_RC_DISABLED	= 0x0120,
+	TPM2_RC_UPGRADE		= 0x012D,
 	TPM2_RC_COMMAND_CODE    = 0x0143,
 	TPM2_RC_TESTING		= 0x090A, /* RC_WARN */
 	TPM2_RC_REFERENCE_H0	= 0x0910,
@@ -278,6 +279,7 @@ enum tpm_chip_flags {
 	TPM_CHIP_FLAG_HAVE_TIMEOUTS	= BIT(4),
 	TPM_CHIP_FLAG_ALWAYS_POWERED	= BIT(5),
 	TPM_CHIP_FLAG_FIRMWARE_POWER_MANAGED	= BIT(6),
+	TPM_CHIP_FLAG_FIRMWARE_MODE	= BIT(7),
 };
 
 #define to_tpm_chip(d) container_of(d, struct tpm_chip, dev)
@@ -399,6 +401,14 @@ static inline void tpm_buf_append_u32(struct tpm_buf *buf, const u32 value)
 	tpm_buf_append(buf, (u8 *) &value2, 4);
 }
 
+/*
+ * Check if TPM device is in the firmware upgrade mode.
+ */
+static inline bool tpm_is_firmware_upgrade(struct tpm_chip *chip)
+{
+	return chip->flags & TPM_CHIP_FLAG_FIRMWARE_MODE;
+}
+
 static inline u32 tpm2_rc_value(u32 rc)
 {
 	return (rc & BIT(7)) ? rc & 0xff : rc;
Signature · 1 line
2.20.1
maintainer
#5 lore raw
Quoted 85 lines · Axel Jonsson

From: Axel Jonsson <[email protected]>

If something went wrong during the TPM firmware upgrade, like power failure or the firmware image file get corrupted, the TPM might end up in Upgrade or Failure mode upon the next start. The state is persistent between the TPM power cycle/restart.

According to TPM specification:
* If the TPM is in Upgrade mode, it will answer with TPM2_RC_UPGRADE to all commands except TPM2_FieldUpgradeData(). It may also accept other commands if it is able to complete them using the previously installed firmware.
* If the TPM is in Failure mode, it will allow performing TPM initialization but will not provide any crypto operations. Will happily respond to Field Upgrade calls.

Change the behavior of the tpm2_auto_startup(), so it detects the active running mode of the TPM by adding the following checks. If tpm2_do_selftest() call returns TPM2_RC_UPGRADE, the TPM is in Upgrade mode.
If the TPM is in Failure mode, it will successfully respond to both tpm2_do_selftest() and tpm2_startup() calls. Although, will fail to answer to tpm2_get_cc_attrs_tbl(). Use this fact to conclude that TPM is in Failure mode.

If detected that the TPM is in the Upgrade or Failure mode, the function sets TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE flag.

The TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE flag is used later during driver initialization/deinitialization to disable functionality which makes no sense or will fail in the current TPM state. Following functionality is affected:
* Do not register TPM as a hwrng
* Do not register sysfs entries which provide information impossible to obtain in limited mode
* Do not register resource managed character device

---

Notes:
Notes:
v2:
* Commit message updated.

v3:
* Commit message reworked.

v4:
* Description of how tpm2_auto_startup() detects the mode added to commit message.

v5:
* Introduce global flag: TPM_CHIP_FLAG_LIMITED_MODE.
* Add checks for the flag in places that will not work properly when TPM functionality is limited.
* Avoid registering sysfs and character device entries that have no useful function in limited mode.
* Do not register TPM as a hwrng.
* Do not try to obtain any crypto-related properties from TPM as it will fail in limited mode.

v6:
* Rename the TPM_CHIP_FLAG_LIMITED_MODE to TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE to reduce confusion. Update info messages.

v7:
* Clarified in commit message which command can be run in firmware upgrade mode
* Corrected info and style in commit message
* Renamed TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE to TPM_CHIP_FLAG_FIRMWARE_MODE
* Replaced TPM_CHIP_FLAG_FIRMWARE_MODE flag check with function from include/linux/tpm.h

 drivers/char/tpm/tpm-chip.c  | 23 ++++++++++++++++-------
 drivers/char/tpm/tpm-sysfs.c |  3 +++
 drivers/char/tpm/tpm2-cmd.c  |  6 +++++-
 include/linux/tpm.h          | 10 ++++++++++
 4 files changed, 34 insertions(+), 8 deletions(-)
drivers/char/tpm/tpm-chip.c +0 -0
index ddaeceb7e109..285d054d187b 100644

drivers/char/tpm/tpm-chip.c static int tpm_add_char_device(struct tpm_chip *chip) · line 448

Quoted 6 lines
drivers/char/tpm/tpm-chip.c +2 -1
@@ -444,7 +444,8 @@ static int tpm_add_char_device(struct tpm_chip *chip)
 		return rc;
 	}
 
-	if (chip->flags & TPM_CHIP_FLAG_TPM2) {
+	if (chip->flags & TPM_CHIP_FLAG_TPM2 &&
+	    !tpm_is_firmware_upgrade(chip)) {

This can be in a single line (less than 100 characters)?

Quoted 24 lines
drivers/char/tpm/tpm-chip.c +4 -2
 		rc = cdev_device_add(&chip->cdevs, &chip->devs);
 		if (rc) {
 			dev_err(&chip->devs,
@@ -488,7 +489,8 @@ static void tpm_del_legacy_sysfs(struct tpm_chip *chip)
 {
 	struct attribute **i;
 
-	if (chip->flags & (TPM_CHIP_FLAG_TPM2 | TPM_CHIP_FLAG_VIRTUAL))
+	if (chip->flags & (TPM_CHIP_FLAG_TPM2 | TPM_CHIP_FLAG_VIRTUAL) ||
+	    tpm_is_firmware_upgrade(chip))
 		return;
 
 	sysfs_remove_link(&chip->dev.parent->kobj, "ppi");
@@ -506,7 +508,8 @@ static int tpm_add_legacy_sysfs(struct tpm_chip *chip)
 	struct attribute **i;
 	int rc;
 
-	if (chip->flags & (TPM_CHIP_FLAG_TPM2 | TPM_CHIP_FLAG_VIRTUAL))
+	if (chip->flags & (TPM_CHIP_FLAG_TPM2 | TPM_CHIP_FLAG_VIRTUAL) ||
+		tpm_is_firmware_upgrade(chip))
 		return 0;
 
 	rc = compat_only_sysfs_link_entry_to_kobj(

drivers/char/tpm/tpm-chip.c static int tpm_hwrng_read(struct hwrng *rng, void *data, size_t max, bool wait) · line 543

Quoted 6 lines
drivers/char/tpm/tpm-chip.c +2 -1
@@ -536,7 +539,8 @@ static int tpm_hwrng_read(struct hwrng *rng, void *data, size_t max, bool wait)
 
 static int tpm_add_hwrng(struct tpm_chip *chip)
 {
-	if (!IS_ENABLED(CONFIG_HW_RANDOM_TPM))
+	if (!IS_ENABLED(CONFIG_HW_RANDOM_TPM) ||
+	    tpm_is_firmware_upgrade(chip))

This can be in a single line (less than 100 characters)?

Quoted 23 lines
drivers/char/tpm/tpm-chip.c +4 -1
 		return 0;
 
 	snprintf(chip->hwrng_name, sizeof(chip->hwrng_name),
@@ -550,6 +554,9 @@ static int tpm_get_pcr_allocation(struct tpm_chip *chip)
 {
 	int rc;
 
+	if (tpm_is_firmware_upgrade(chip))
+		return 0;
+
 	rc = (chip->flags & TPM_CHIP_FLAG_TPM2) ?
 	     tpm2_get_pcr_allocation(chip) :
 	     tpm1_get_pcr_allocation(chip);
@@ -612,7 +619,7 @@ int tpm_chip_register(struct tpm_chip *chip)
 	return 0;
 
 out_hwrng:
-	if (IS_ENABLED(CONFIG_HW_RANDOM_TPM))
+	if (IS_ENABLED(CONFIG_HW_RANDOM_TPM) && !tpm_is_firmware_upgrade(chip))
 		hwrng_unregister(&chip->hwrng);
 out_ppi:
 	tpm_bios_log_teardown(chip);

drivers/char/tpm/tpm-chip.c EXPORT_SYMBOL_GPL(tpm_chip_register); · line 648

Quoted 6 lines
drivers/char/tpm/tpm-chip.c +2 -1
@@ -637,10 +644,12 @@ EXPORT_SYMBOL_GPL(tpm_chip_register);
 void tpm_chip_unregister(struct tpm_chip *chip)
 {
 	tpm_del_legacy_sysfs(chip);
-	if (IS_ENABLED(CONFIG_HW_RANDOM_TPM))
+	if (IS_ENABLED(CONFIG_HW_RANDOM_TPM) &&
+	    !tpm_is_firmware_upgrade(chip))

Ditto.

drivers/char/tpm/tpm-chip.c EXPORT_SYMBOL_GPL(tpm_chip_register); · line 652

Quoted 5 lines
drivers/char/tpm/tpm-chip.c +2 -1
 		hwrng_unregister(&chip->hwrng);
 	tpm_bios_log_teardown(chip);
-	if (chip->flags & TPM_CHIP_FLAG_TPM2)
+	if (chip->flags & TPM_CHIP_FLAG_TPM2 &&
+	    !tpm_is_firmware_upgrade(chip))

Ditto.

Quoted 22 lines
drivers/char/tpm/tpm-chip.c +0 -0
 		cdev_device_del(&chip->cdevs, &chip->devs);
 	tpm_del_char_device(chip);
 }
drivers/char/tpm/tpm-sysfs.c +3 -0
index 63f03cfb8e6a..54c71473aa29 100644
@@ -480,6 +480,9 @@ void tpm_sysfs_add_device(struct tpm_chip *chip)
 
 	WARN_ON(chip->groups_cnt != 0);
 
+	if (tpm_is_firmware_upgrade(chip))
+		return;
+
 	if (chip->flags & TPM_CHIP_FLAG_TPM2)
 		chip->groups[chip->groups_cnt++] = &tpm2_dev_group;
 	else
drivers/char/tpm/tpm2-cmd.c +0 -0
index a25815a6f625..24928b0160f1 100644

drivers/char/tpm/tpm2-cmd.c int tpm2_auto_startup(struct tpm_chip *chip) · line 746

Quoted 4 lines
drivers/char/tpm/tpm2-cmd.c +0 -1
@@ -743,8 +743,12 @@ int tpm2_auto_startup(struct tpm_chip *chip)
 	}
 
 	rc = tpm2_get_cc_attrs_tbl(chip);
-

Please, do not remove this empty line.

drivers/char/tpm/tpm2-cmd.c int tpm2_auto_startup(struct tpm_chip *chip) · line 751

Quoted 6 lines
drivers/char/tpm/tpm2-cmd.c +5 -0
 out:
+	if (rc == TPM2_RC_UPGRADE) {
+		dev_info(&chip->dev, "TPM in field upgrade mode, requires firmware upgrade\n");
+		chip->flags |= TPM_CHIP_FLAG_FIRMWARE_MODE;
+		rc = 0;
+	}

Empty line here.

Quoted 24 lines
drivers/char/tpm/tpm2-cmd.c +0 -0
 	if (rc > 0)
 		rc = -ENODEV;
 	return rc;
include/linux/tpm.h +2 -0
index 12d827734686..d38efdae382c 100644
@@ -207,6 +207,7 @@ enum tpm2_return_codes {
 	TPM2_RC_INITIALIZE	= 0x0100, /* RC_VER1 */
 	TPM2_RC_FAILURE		= 0x0101,
 	TPM2_RC_DISABLED	= 0x0120,
+	TPM2_RC_UPGRADE		= 0x012D,
 	TPM2_RC_COMMAND_CODE    = 0x0143,
 	TPM2_RC_TESTING		= 0x090A, /* RC_WARN */
 	TPM2_RC_REFERENCE_H0	= 0x0910,
@@ -278,6 +279,7 @@ enum tpm_chip_flags {
 	TPM_CHIP_FLAG_HAVE_TIMEOUTS	= BIT(4),
 	TPM_CHIP_FLAG_ALWAYS_POWERED	= BIT(5),
 	TPM_CHIP_FLAG_FIRMWARE_POWER_MANAGED	= BIT(6),
+	TPM_CHIP_FLAG_FIRMWARE_MODE	= BIT(7),
 };
 
 #define to_tpm_chip(d) container_of(d, struct tpm_chip, dev)

include/linux/tpm.h static inline void tpm_buf_append_u32(struct tpm_buf *buf, const u32 value) · line 410

Quoted 10 lines
include/linux/tpm.h +7 -0
@@ -399,6 +401,14 @@ static inline void tpm_buf_append_u32(struct tpm_buf *buf, const u32 value)
 	tpm_buf_append(buf, (u8 *) &value2, 4);
 }
 
+/*
+ * Check if TPM device is in the firmware upgrade mode.
+ */
+static inline bool tpm_is_firmware_upgrade(struct tpm_chip *chip)
+{
+	return chip->flags & TPM_CHIP_FLAG_FIRMWARE_MODE;
+}

For consistency sake, rename the flag as TPM_CHIP_FLAG_FIRMWARE_UPGRADE.

include/linux/tpm.h static inline void tpm_buf_append_u32(struct tpm_buf *buf, const u32 value) · line 414

Quoted 4 lines
include/linux/tpm.h +1 -0
+
 static inline u32 tpm2_rc_value(u32 rc)
 {
 	return (rc & BIT(7)) ? rc & 0xff : rc;

/Jarkko

#6 lore raw
Quoted 88 lines
Quoted 88 lines

From: Axel Jonsson <[email protected]>

If something went wrong during the TPM firmware upgrade, like power failure or the firmware image file get corrupted, the TPM might end up in Upgrade or Failure mode upon the next start. The state is persistent between the TPM power cycle/restart.

According to TPM specification:
* If the TPM is in Upgrade mode, it will answer with TPM2_RC_UPGRADE to all commands except TPM2_FieldUpgradeData(). It may also accept other commands if it is able to complete them using the previously installed firmware.
* If the TPM is in Failure mode, it will allow performing TPM initialization but will not provide any crypto operations. Will happily respond to Field Upgrade calls.

Change the behavior of the tpm2_auto_startup(), so it detects the active running mode of the TPM by adding the following checks. If tpm2_do_selftest() call returns TPM2_RC_UPGRADE, the TPM is in Upgrade mode.
If the TPM is in Failure mode, it will successfully respond to both tpm2_do_selftest() and tpm2_startup() calls. Although, will fail to answer to tpm2_get_cc_attrs_tbl(). Use this fact to conclude that TPM is in Failure mode.

If detected that the TPM is in the Upgrade or Failure mode, the function sets TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE flag.

The TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE flag is used later during driver initialization/deinitialization to disable functionality which makes no sense or will fail in the current TPM state. Following functionality is affected:
* Do not register TPM as a hwrng
* Do not register sysfs entries which provide information impossible to obtain in limited mode
* Do not register resource managed character device

---

Notes:
Notes:
v2:
* Commit message updated.

v3:
* Commit message reworked.

v4:
* Description of how tpm2_auto_startup() detects the mode added to commit message.

v5:
* Introduce global flag: TPM_CHIP_FLAG_LIMITED_MODE.
* Add checks for the flag in places that will not work properly when TPM functionality is limited.
* Avoid registering sysfs and character device entries that have no useful function in limited mode.
* Do not register TPM as a hwrng.
* Do not try to obtain any crypto-related properties from TPM as it will fail in limited mode.

v6:
* Rename the TPM_CHIP_FLAG_LIMITED_MODE to TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE to reduce confusion. Update info messages.

v7:
* Clarified in commit message which command can be run in firmware upgrade mode
* Corrected info and style in commit message
* Renamed TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE to TPM_CHIP_FLAG_FIRMWARE_MODE
* Replaced TPM_CHIP_FLAG_FIRMWARE_MODE flag check with function from include/linux/tpm.h

 drivers/char/tpm/tpm-chip.c  | 23 ++++++++++++++++-------
 drivers/char/tpm/tpm-sysfs.c |  3 +++
 drivers/char/tpm/tpm2-cmd.c  |  6 +++++-
 include/linux/tpm.h          | 10 ++++++++++
 4 files changed, 34 insertions(+), 8 deletions(-)
drivers/char/tpm/tpm-chip.c +0 -0
index ddaeceb7e109..285d054d187b 100644
@@ -444,7 +444,8 @@ static int tpm_add_char_device(struct tpm_chip *chip)
 		return rc;
 	}
 
Quoted 6 lines

drivers/char/tpm/tpm-chip.c static int tpm_add_char_device(struct tpm_chip *chip) · line 448

Quoted 3 lines
drivers/char/tpm/tpm-chip.c +2 -1
-	if (chip->flags & TPM_CHIP_FLAG_TPM2) {
+	if (chip->flags & TPM_CHIP_FLAG_TPM2 &&
+	    !tpm_is_firmware_upgrade(chip)) {

This can be in a single line (less than 100 characters)?

Done.

Quoted 25 lines
Quoted 25 lines
drivers/char/tpm/tpm-chip.c +4 -2
 		rc = cdev_device_add(&chip->cdevs, &chip->devs);
 		if (rc) {
 			dev_err(&chip->devs,
@@ -488,7 +489,8 @@ static void tpm_del_legacy_sysfs(struct tpm_chip *chip)
 {
 	struct attribute **i;
 
-	if (chip->flags & (TPM_CHIP_FLAG_TPM2 | TPM_CHIP_FLAG_VIRTUAL))
+	if (chip->flags & (TPM_CHIP_FLAG_TPM2 | TPM_CHIP_FLAG_VIRTUAL) ||
+	    tpm_is_firmware_upgrade(chip))
 		return;
 
 	sysfs_remove_link(&chip->dev.parent->kobj, "ppi");
@@ -506,7 +508,8 @@ static int tpm_add_legacy_sysfs(struct tpm_chip *chip)
 	struct attribute **i;
 	int rc;
 
-	if (chip->flags & (TPM_CHIP_FLAG_TPM2 | TPM_CHIP_FLAG_VIRTUAL))
+	if (chip->flags & (TPM_CHIP_FLAG_TPM2 | TPM_CHIP_FLAG_VIRTUAL) ||
+		tpm_is_firmware_upgrade(chip))
 		return 0;
 
 	rc = compat_only_sysfs_link_entry_to_kobj(
@@ -536,7 +539,8 @@ static int tpm_hwrng_read(struct hwrng *rng, void *data, size_t max, bool wait)
 
Quoted 8 lines

drivers/char/tpm/tpm-chip.c static int tpm_hwrng_read(struct hwrng *rng, void *data, size_t max, bool wait) · line 543

Quoted 5 lines
drivers/char/tpm/tpm-chip.c +2 -1
 static int tpm_add_hwrng(struct tpm_chip *chip)
 {
-	if (!IS_ENABLED(CONFIG_HW_RANDOM_TPM))
+	if (!IS_ENABLED(CONFIG_HW_RANDOM_TPM) ||
+	    tpm_is_firmware_upgrade(chip))

This can be in a single line (less than 100 characters)?

Done.

Quoted 16 lines
Quoted 16 lines
drivers/char/tpm/tpm-chip.c +3 -0
 		return 0;
 
 	snprintf(chip->hwrng_name, sizeof(chip->hwrng_name),
@@ -550,6 +554,9 @@ static int tpm_get_pcr_allocation(struct tpm_chip *chip)
 {
 	int rc;
 
+	if (tpm_is_firmware_upgrade(chip))
+		return 0;
+
 	rc = (chip->flags & TPM_CHIP_FLAG_TPM2) ?
 	     tpm2_get_pcr_allocation(chip) :
 	     tpm1_get_pcr_allocation(chip);
@@ -612,7 +619,7 @@ int tpm_chip_register(struct tpm_chip *chip)
 	return 0;
 
Quoted 16 lines

drivers/char/tpm/tpm-chip.c EXPORT_SYMBOL_GPL(tpm_chip_register); · line 648

Quoted 13 lines
drivers/char/tpm/tpm-chip.c +3 -2
 out_hwrng:
-	if (IS_ENABLED(CONFIG_HW_RANDOM_TPM))
+	if (IS_ENABLED(CONFIG_HW_RANDOM_TPM) && !tpm_is_firmware_upgrade(chip))
 		hwrng_unregister(&chip->hwrng);
 out_ppi:
 	tpm_bios_log_teardown(chip);
@@ -637,10 +644,12 @@ EXPORT_SYMBOL_GPL(tpm_chip_register);
 void tpm_chip_unregister(struct tpm_chip *chip)
 {
 	tpm_del_legacy_sysfs(chip);
-	if (IS_ENABLED(CONFIG_HW_RANDOM_TPM))
+	if (IS_ENABLED(CONFIG_HW_RANDOM_TPM) &&
+	    !tpm_is_firmware_upgrade(chip))

Ditto.

Done.

Quoted 8 lines

drivers/char/tpm/tpm-chip.c EXPORT_SYMBOL_GPL(tpm_chip_register); · line 652

Quoted 5 lines
drivers/char/tpm/tpm-chip.c +2 -1
 		hwrng_unregister(&chip->hwrng);
 	tpm_bios_log_teardown(chip);
-	if (chip->flags & TPM_CHIP_FLAG_TPM2)
+	if (chip->flags & TPM_CHIP_FLAG_TPM2 &&
+	    !tpm_is_firmware_upgrade(chip))

Ditto.

Done.

Quoted 24 lines
Quoted 24 lines
drivers/char/tpm/tpm-chip.c +0 -0
 		cdev_device_del(&chip->cdevs, &chip->devs);
 	tpm_del_char_device(chip);
 }
drivers/char/tpm/tpm-sysfs.c +3 -0
index 63f03cfb8e6a..54c71473aa29 100644
@@ -480,6 +480,9 @@ void tpm_sysfs_add_device(struct tpm_chip *chip)
 
 	WARN_ON(chip->groups_cnt != 0);
 
+	if (tpm_is_firmware_upgrade(chip))
+		return;
+
 	if (chip->flags & TPM_CHIP_FLAG_TPM2)
 		chip->groups[chip->groups_cnt++] = &tpm2_dev_group;
 	else
drivers/char/tpm/tpm2-cmd.c +0 -0
index a25815a6f625..24928b0160f1 100644
@@ -743,8 +743,12 @@ int tpm2_auto_startup(struct tpm_chip *chip)
 	}
 
Quoted 5 lines

drivers/char/tpm/tpm2-cmd.c int tpm2_auto_startup(struct tpm_chip *chip) · line 746

Quoted 2 lines
drivers/char/tpm/tpm2-cmd.c +0 -1
 	rc = tpm2_get_cc_attrs_tbl(chip);
-

Please, do not remove this empty line.

Done.

Quoted 9 lines

drivers/char/tpm/tpm2-cmd.c int tpm2_auto_startup(struct tpm_chip *chip) · line 751

Quoted 6 lines
drivers/char/tpm/tpm2-cmd.c +5 -0
 out:
+	if (rc == TPM2_RC_UPGRADE) {
+		dev_info(&chip->dev, "TPM in field upgrade mode, requires firmware upgrade\n");
+		chip->flags |= TPM_CHIP_FLAG_FIRMWARE_MODE;
+		rc = 0;
+	}

Empty line here.

Done.

Quoted 27 lines
Quoted 27 lines
drivers/char/tpm/tpm2-cmd.c +0 -0
 	if (rc > 0)
 		rc = -ENODEV;
 	return rc;
include/linux/tpm.h +2 -0
index 12d827734686..d38efdae382c 100644
@@ -207,6 +207,7 @@ enum tpm2_return_codes {
 	TPM2_RC_INITIALIZE	= 0x0100, /* RC_VER1 */
 	TPM2_RC_FAILURE		= 0x0101,
 	TPM2_RC_DISABLED	= 0x0120,
+	TPM2_RC_UPGRADE		= 0x012D,
 	TPM2_RC_COMMAND_CODE    = 0x0143,
 	TPM2_RC_TESTING		= 0x090A, /* RC_WARN */
 	TPM2_RC_REFERENCE_H0	= 0x0910,
@@ -278,6 +279,7 @@ enum tpm_chip_flags {
 	TPM_CHIP_FLAG_HAVE_TIMEOUTS	= BIT(4),
 	TPM_CHIP_FLAG_ALWAYS_POWERED	= BIT(5),
 	TPM_CHIP_FLAG_FIRMWARE_POWER_MANAGED	= BIT(6),
+	TPM_CHIP_FLAG_FIRMWARE_MODE	= BIT(7),
 };
 
 #define to_tpm_chip(d) container_of(d, struct tpm_chip, dev)
@@ -399,6 +401,14 @@ static inline void tpm_buf_append_u32(struct tpm_buf *buf, const u32 value)
 	tpm_buf_append(buf, (u8 *) &value2, 4);
 }
 
Quoted 10 lines

include/linux/tpm.h static inline void tpm_buf_append_u32(struct tpm_buf *buf, const u32 value) · line 410

Quoted 7 lines
include/linux/tpm.h +7 -0
+/*
+ * Check if TPM device is in the firmware upgrade mode.
+ */
+static inline bool tpm_is_firmware_upgrade(struct tpm_chip *chip)
+{
+	return chip->flags & TPM_CHIP_FLAG_FIRMWARE_MODE;
+}

For consistency sake, rename the flag as TPM_CHIP_FLAG_FIRMWARE_UPGRADE.

Done.

Quoted 4 lines

include/linux/tpm.h static inline void tpm_buf_append_u32(struct tpm_buf *buf, const u32 value) · line 414

Quoted 4 lines
include/linux/tpm.h +1 -0
+
 static inline u32 tpm2_rc_value(u32 rc)
 {
 	return (rc & BIT(7)) ? rc & 0xff : rc;

Best regards, Axel

#7 lore raw

PATCH v8 1/1 tpm: Add Upgrade/Reduced mode support for TPM2 modules

In reply to Jarkko Sakkinen

From: axelj <[email protected]>

If something went wrong during the TPM firmware upgrade, like power failure or the firmware image file get corrupted, the TPM might end up in Upgrade or Failure mode upon the next start. The state is persistent between the TPM power cycle/restart.

According to TPM specification:
* If the TPM is in Upgrade mode, it will answer with TPM2_RC_UPGRADE to all commands except TPM2_FieldUpgradeData(). It may also accept other commands if it is able to complete them using the previously installed firmware.
* If the TPM is in Failure mode, it will allow performing TPM initialization but will not provide any crypto operations. Will happily respond to Field Upgrade calls.

Change the behavior of the tpm2_auto_startup(), so it detects the active running mode of the TPM by adding the following checks. If tpm2_do_selftest() call returns TPM2_RC_UPGRADE, the TPM is in Upgrade mode.
If the TPM is in Failure mode, it will successfully respond to both tpm2_do_selftest() and tpm2_startup() calls. Although, will fail to answer to tpm2_get_cc_attrs_tbl(). Use this fact to conclude that TPM is in Failure mode.

If detected that the TPM is in the Upgrade or Failure mode, the function sets TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE flag.

The TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE flag is used later during driver initialization/deinitialization to disable functionality which makes no sense or will fail in the current TPM state. Following functionality is affected:
* Do not register TPM as a hwrng
* Do not register sysfs entries which provide information impossible to obtain in limited mode
* Do not register resource managed character device

---

Notes:
v2:
* Commit message updated.

v3:
* Commit message reworked.

v4:
* Description of how tpm2_auto_startup() detects the mode added to commit message.

v5:
* Introduce global flag: TPM_CHIP_FLAG_LIMITED_MODE.
* Add checks for the flag in places that will not work properly when TPM functionality is limited.
* Avoid registering sysfs and character device entries that have no useful function in limited mode.
* Do not register TPM as a hwrng.
* Do not try to obtain any crypto-related properties from TPM as it will fail in limited mode.

v6:
* Rename the TPM_CHIP_FLAG_LIMITED_MODE to TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE to reduce confusion. Update info messages.

v7:
* Clarified in commit message which command can be run in firmware upgrade mode
* Corrected info and style in commit message
* Renamed TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE to TPM_CHIP_FLAG_FIRMWARE_MODE
* Replaced TPM_CHIP_FLAG_FIRMWARE_MODE flag check with function from include/linux/tpm.h

v8:
* Fixed line formating.
* Renamed TPM_CHIP_FLAG_FIRMWARE_MODE to TPM_CHIP_FLAG_FIRMWARE_UPGRADE.

 drivers/char/tpm/tpm-chip.c  | 19 ++++++++++++-------
 drivers/char/tpm/tpm-sysfs.c |  3 +++
 drivers/char/tpm/tpm2-cmd.c  |  6 ++++++
 include/linux/tpm.h          | 10 ++++++++++
 4 files changed, 31 insertions(+), 7 deletions(-)
drivers/char/tpm/tpm-chip.c +12 -7
index ddaeceb7e109..b4ed3ae67a4b 100644
@@ -444,7 +444,7 @@ static int tpm_add_char_device(struct tpm_chip *chip)
 		return rc;
 	}
 
-	if (chip->flags & TPM_CHIP_FLAG_TPM2) {
+	if (chip->flags & TPM_CHIP_FLAG_TPM2 && !tpm_is_firmware_upgrade(chip)) {
 		rc = cdev_device_add(&chip->cdevs, &chip->devs);
 		if (rc) {
 			dev_err(&chip->devs,
@@ -488,7 +488,8 @@ static void tpm_del_legacy_sysfs(struct tpm_chip *chip)
 {
 	struct attribute **i;
 
-	if (chip->flags & (TPM_CHIP_FLAG_TPM2 | TPM_CHIP_FLAG_VIRTUAL))
+	if (chip->flags & (TPM_CHIP_FLAG_TPM2 | TPM_CHIP_FLAG_VIRTUAL) ||
+	    tpm_is_firmware_upgrade(chip))
 		return;
 
 	sysfs_remove_link(&chip->dev.parent->kobj, "ppi");
@@ -506,7 +507,8 @@ static int tpm_add_legacy_sysfs(struct tpm_chip *chip)
 	struct attribute **i;
 	int rc;
 
-	if (chip->flags & (TPM_CHIP_FLAG_TPM2 | TPM_CHIP_FLAG_VIRTUAL))
+	if (chip->flags & (TPM_CHIP_FLAG_TPM2 | TPM_CHIP_FLAG_VIRTUAL) ||
+		tpm_is_firmware_upgrade(chip))
 		return 0;
 
 	rc = compat_only_sysfs_link_entry_to_kobj(
@@ -536,7 +538,7 @@ static int tpm_hwrng_read(struct hwrng *rng, void *data, size_t max, bool wait)
 
 static int tpm_add_hwrng(struct tpm_chip *chip)
 {
-	if (!IS_ENABLED(CONFIG_HW_RANDOM_TPM))
+	if (!IS_ENABLED(CONFIG_HW_RANDOM_TPM) || tpm_is_firmware_upgrade(chip))
 		return 0;
 
 	snprintf(chip->hwrng_name, sizeof(chip->hwrng_name),
@@ -550,6 +552,9 @@ static int tpm_get_pcr_allocation(struct tpm_chip *chip)
 {
 	int rc;
 
+	if (tpm_is_firmware_upgrade(chip))
+		return 0;
+
 	rc = (chip->flags & TPM_CHIP_FLAG_TPM2) ?
 	     tpm2_get_pcr_allocation(chip) :
 	     tpm1_get_pcr_allocation(chip);
@@ -612,7 +617,7 @@ int tpm_chip_register(struct tpm_chip *chip)
 	return 0;
 
 out_hwrng:
-	if (IS_ENABLED(CONFIG_HW_RANDOM_TPM))
+	if (IS_ENABLED(CONFIG_HW_RANDOM_TPM) && !tpm_is_firmware_upgrade(chip))
 		hwrng_unregister(&chip->hwrng);
 out_ppi:
 	tpm_bios_log_teardown(chip);
@@ -637,10 +642,10 @@ EXPORT_SYMBOL_GPL(tpm_chip_register);
 void tpm_chip_unregister(struct tpm_chip *chip)
 {
 	tpm_del_legacy_sysfs(chip);
-	if (IS_ENABLED(CONFIG_HW_RANDOM_TPM))
+	if (IS_ENABLED(CONFIG_HW_RANDOM_TPM) && !tpm_is_firmware_upgrade(chip))
 		hwrng_unregister(&chip->hwrng);
 	tpm_bios_log_teardown(chip);
-	if (chip->flags & TPM_CHIP_FLAG_TPM2)
+	if (chip->flags & TPM_CHIP_FLAG_TPM2 && !tpm_is_firmware_upgrade(chip))
 		cdev_device_del(&chip->cdevs, &chip->devs);
 	tpm_del_char_device(chip);
 }
drivers/char/tpm/tpm-sysfs.c +3 -0
index 63f03cfb8e6a..54c71473aa29 100644
@@ -480,6 +480,9 @@ void tpm_sysfs_add_device(struct tpm_chip *chip)
 
 	WARN_ON(chip->groups_cnt != 0);
 
+	if (tpm_is_firmware_upgrade(chip))
+		return;
+
 	if (chip->flags & TPM_CHIP_FLAG_TPM2)
 		chip->groups[chip->groups_cnt++] = &tpm2_dev_group;
 	else
drivers/char/tpm/tpm2-cmd.c +6 -0
index a25815a6f625..4704fa553098 100644
@@ -745,6 +745,12 @@ int tpm2_auto_startup(struct tpm_chip *chip)
 	rc = tpm2_get_cc_attrs_tbl(chip);
 
 out:
+	if (rc == TPM2_RC_UPGRADE) {
+		dev_info(&chip->dev, "TPM in field upgrade mode, requires firmware upgrade\n");
+		chip->flags |= TPM_CHIP_FLAG_FIRMWARE_UPGRADE;
+		rc = 0;
+	}
+
 	if (rc > 0)
 		rc = -ENODEV;
 	return rc;
include/linux/tpm.h +10 -0
index 12d827734686..dfeb25a0362d 100644
@@ -207,6 +207,7 @@ enum tpm2_return_codes {
 	TPM2_RC_INITIALIZE	= 0x0100, /* RC_VER1 */
 	TPM2_RC_FAILURE		= 0x0101,
 	TPM2_RC_DISABLED	= 0x0120,
+	TPM2_RC_UPGRADE		= 0x012D,
 	TPM2_RC_COMMAND_CODE    = 0x0143,
 	TPM2_RC_TESTING		= 0x090A, /* RC_WARN */
 	TPM2_RC_REFERENCE_H0	= 0x0910,
@@ -278,6 +279,7 @@ enum tpm_chip_flags {
 	TPM_CHIP_FLAG_HAVE_TIMEOUTS	= BIT(4),
 	TPM_CHIP_FLAG_ALWAYS_POWERED	= BIT(5),
 	TPM_CHIP_FLAG_FIRMWARE_POWER_MANAGED	= BIT(6),
+	TPM_CHIP_FLAG_FIRMWARE_UPGRADE	= BIT(7),
 };
 
 #define to_tpm_chip(d) container_of(d, struct tpm_chip, dev)
@@ -399,6 +401,14 @@ static inline void tpm_buf_append_u32(struct tpm_buf *buf, const u32 value)
 	tpm_buf_append(buf, (u8 *) &value2, 4);
 }
 
+/*
+ * Check if TPM device is in the firmware upgrade mode.
+ */
+static inline bool tpm_is_firmware_upgrade(struct tpm_chip *chip)
+{
+	return chip->flags & TPM_CHIP_FLAG_FIRMWARE_UPGRADE;
+}
+
 static inline u32 tpm2_rc_value(u32 rc)
 {
 	return (rc & BIT(7)) ? rc & 0xff : rc;
Signature · 1 line
2.11.0
maintainer Reviewed-by on v8 1/1
#8 lore raw
Quoted 226 lines · Axel Jonsson

From: axelj <[email protected]>

If something went wrong during the TPM firmware upgrade, like power failure or the firmware image file get corrupted, the TPM might end up in Upgrade or Failure mode upon the next start. The state is persistent between the TPM power cycle/restart.

According to TPM specification:
* If the TPM is in Upgrade mode, it will answer with TPM2_RC_UPGRADE to all commands except TPM2_FieldUpgradeData(). It may also accept other commands if it is able to complete them using the previously installed firmware.
* If the TPM is in Failure mode, it will allow performing TPM initialization but will not provide any crypto operations. Will happily respond to Field Upgrade calls.

Change the behavior of the tpm2_auto_startup(), so it detects the active running mode of the TPM by adding the following checks. If tpm2_do_selftest() call returns TPM2_RC_UPGRADE, the TPM is in Upgrade mode.
If the TPM is in Failure mode, it will successfully respond to both tpm2_do_selftest() and tpm2_startup() calls. Although, will fail to answer to tpm2_get_cc_attrs_tbl(). Use this fact to conclude that TPM is in Failure mode.

If detected that the TPM is in the Upgrade or Failure mode, the function sets TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE flag.

The TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE flag is used later during driver initialization/deinitialization to disable functionality which makes no sense or will fail in the current TPM state. Following functionality is affected:
* Do not register TPM as a hwrng
* Do not register sysfs entries which provide information impossible to obtain in limited mode
* Do not register resource managed character device

---

Notes:
v2:
* Commit message updated.

v3:
* Commit message reworked.

v4:
* Description of how tpm2_auto_startup() detects the mode added to commit message.

v5:
* Introduce global flag: TPM_CHIP_FLAG_LIMITED_MODE.
* Add checks for the flag in places that will not work properly when TPM functionality is limited.
* Avoid registering sysfs and character device entries that have no useful function in limited mode.
* Do not register TPM as a hwrng.
* Do not try to obtain any crypto-related properties from TPM as it will fail in limited mode.

v6:
* Rename the TPM_CHIP_FLAG_LIMITED_MODE to TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE to reduce confusion. Update info messages.

v7:
* Clarified in commit message which command can be run in firmware upgrade mode
* Corrected info and style in commit message
* Renamed TPM_CHIP_FLAG_FIRMWARE_UPGRADE_MODE to TPM_CHIP_FLAG_FIRMWARE_MODE
* Replaced TPM_CHIP_FLAG_FIRMWARE_MODE flag check with function from include/linux/tpm.h

v8:
* Fixed line formating.
* Renamed TPM_CHIP_FLAG_FIRMWARE_MODE to TPM_CHIP_FLAG_FIRMWARE_UPGRADE.

 drivers/char/tpm/tpm-chip.c  | 19 ++++++++++++-------
 drivers/char/tpm/tpm-sysfs.c |  3 +++
 drivers/char/tpm/tpm2-cmd.c  |  6 ++++++
 include/linux/tpm.h          | 10 ++++++++++
 4 files changed, 31 insertions(+), 7 deletions(-)
drivers/char/tpm/tpm-chip.c +12 -7
index ddaeceb7e109..b4ed3ae67a4b 100644
@@ -444,7 +444,7 @@ static int tpm_add_char_device(struct tpm_chip *chip)
 		return rc;
 	}
 
-	if (chip->flags & TPM_CHIP_FLAG_TPM2) {
+	if (chip->flags & TPM_CHIP_FLAG_TPM2 && !tpm_is_firmware_upgrade(chip)) {
 		rc = cdev_device_add(&chip->cdevs, &chip->devs);
 		if (rc) {
 			dev_err(&chip->devs,
@@ -488,7 +488,8 @@ static void tpm_del_legacy_sysfs(struct tpm_chip *chip)
 {
 	struct attribute **i;
 
-	if (chip->flags & (TPM_CHIP_FLAG_TPM2 | TPM_CHIP_FLAG_VIRTUAL))
+	if (chip->flags & (TPM_CHIP_FLAG_TPM2 | TPM_CHIP_FLAG_VIRTUAL) ||
+	    tpm_is_firmware_upgrade(chip))
 		return;
 
 	sysfs_remove_link(&chip->dev.parent->kobj, "ppi");
@@ -506,7 +507,8 @@ static int tpm_add_legacy_sysfs(struct tpm_chip *chip)
 	struct attribute **i;
 	int rc;
 
-	if (chip->flags & (TPM_CHIP_FLAG_TPM2 | TPM_CHIP_FLAG_VIRTUAL))
+	if (chip->flags & (TPM_CHIP_FLAG_TPM2 | TPM_CHIP_FLAG_VIRTUAL) ||
+		tpm_is_firmware_upgrade(chip))
 		return 0;
 
 	rc = compat_only_sysfs_link_entry_to_kobj(
@@ -536,7 +538,7 @@ static int tpm_hwrng_read(struct hwrng *rng, void *data, size_t max, bool wait)
 
 static int tpm_add_hwrng(struct tpm_chip *chip)
 {
-	if (!IS_ENABLED(CONFIG_HW_RANDOM_TPM))
+	if (!IS_ENABLED(CONFIG_HW_RANDOM_TPM) || tpm_is_firmware_upgrade(chip))
 		return 0;
 
 	snprintf(chip->hwrng_name, sizeof(chip->hwrng_name),
@@ -550,6 +552,9 @@ static int tpm_get_pcr_allocation(struct tpm_chip *chip)
 {
 	int rc;
 
+	if (tpm_is_firmware_upgrade(chip))
+		return 0;
+
 	rc = (chip->flags & TPM_CHIP_FLAG_TPM2) ?
 	     tpm2_get_pcr_allocation(chip) :
 	     tpm1_get_pcr_allocation(chip);
@@ -612,7 +617,7 @@ int tpm_chip_register(struct tpm_chip *chip)
 	return 0;
 
 out_hwrng:
-	if (IS_ENABLED(CONFIG_HW_RANDOM_TPM))
+	if (IS_ENABLED(CONFIG_HW_RANDOM_TPM) && !tpm_is_firmware_upgrade(chip))
 		hwrng_unregister(&chip->hwrng);
 out_ppi:
 	tpm_bios_log_teardown(chip);
@@ -637,10 +642,10 @@ EXPORT_SYMBOL_GPL(tpm_chip_register);
 void tpm_chip_unregister(struct tpm_chip *chip)
 {
 	tpm_del_legacy_sysfs(chip);
-	if (IS_ENABLED(CONFIG_HW_RANDOM_TPM))
+	if (IS_ENABLED(CONFIG_HW_RANDOM_TPM) && !tpm_is_firmware_upgrade(chip))
 		hwrng_unregister(&chip->hwrng);
 	tpm_bios_log_teardown(chip);
-	if (chip->flags & TPM_CHIP_FLAG_TPM2)
+	if (chip->flags & TPM_CHIP_FLAG_TPM2 && !tpm_is_firmware_upgrade(chip))
 		cdev_device_del(&chip->cdevs, &chip->devs);
 	tpm_del_char_device(chip);
 }
drivers/char/tpm/tpm-sysfs.c +3 -0
index 63f03cfb8e6a..54c71473aa29 100644
@@ -480,6 +480,9 @@ void tpm_sysfs_add_device(struct tpm_chip *chip)
 
 	WARN_ON(chip->groups_cnt != 0);
 
+	if (tpm_is_firmware_upgrade(chip))
+		return;
+
 	if (chip->flags & TPM_CHIP_FLAG_TPM2)
 		chip->groups[chip->groups_cnt++] = &tpm2_dev_group;
 	else
drivers/char/tpm/tpm2-cmd.c +6 -0
index a25815a6f625..4704fa553098 100644
@@ -745,6 +745,12 @@ int tpm2_auto_startup(struct tpm_chip *chip)
 	rc = tpm2_get_cc_attrs_tbl(chip);
 
 out:
+	if (rc == TPM2_RC_UPGRADE) {
+		dev_info(&chip->dev, "TPM in field upgrade mode, requires firmware upgrade\n");
+		chip->flags |= TPM_CHIP_FLAG_FIRMWARE_UPGRADE;
+		rc = 0;
+	}
+
 	if (rc > 0)
 		rc = -ENODEV;
 	return rc;
include/linux/tpm.h +10 -0
index 12d827734686..dfeb25a0362d 100644
@@ -207,6 +207,7 @@ enum tpm2_return_codes {
 	TPM2_RC_INITIALIZE	= 0x0100, /* RC_VER1 */
 	TPM2_RC_FAILURE		= 0x0101,
 	TPM2_RC_DISABLED	= 0x0120,
+	TPM2_RC_UPGRADE		= 0x012D,
 	TPM2_RC_COMMAND_CODE    = 0x0143,
 	TPM2_RC_TESTING		= 0x090A, /* RC_WARN */
 	TPM2_RC_REFERENCE_H0	= 0x0910,
@@ -278,6 +279,7 @@ enum tpm_chip_flags {
 	TPM_CHIP_FLAG_HAVE_TIMEOUTS	= BIT(4),
 	TPM_CHIP_FLAG_ALWAYS_POWERED	= BIT(5),
 	TPM_CHIP_FLAG_FIRMWARE_POWER_MANAGED	= BIT(6),
+	TPM_CHIP_FLAG_FIRMWARE_UPGRADE	= BIT(7),
 };
 
 #define to_tpm_chip(d) container_of(d, struct tpm_chip, dev)
@@ -399,6 +401,14 @@ static inline void tpm_buf_append_u32(struct tpm_buf *buf, const u32 value)
 	tpm_buf_append(buf, (u8 *) &value2, 4);
 }
 
+/*
+ * Check if TPM device is in the firmware upgrade mode.
+ */
+static inline bool tpm_is_firmware_upgrade(struct tpm_chip *chip)
+{
+	return chip->flags & TPM_CHIP_FLAG_FIRMWARE_UPGRADE;
+}
+
 static inline u32 tpm2_rc_value(u32 rc)
 {
 	return (rc & BIT(7)) ? rc & 0xff : rc;
Signature · 1 line
2.11.0

Looks good to me, thank you.

/Jarkko

#9 lore raw
In reply to Borys Movchan

Coming back to my change, a quick reminder of the latest questions and some updated answers:

Quoted 4 lines
Quoted 2 lines

If detected that the TPM is in the Upgrade or Failure mode, the function sets TPM_CHIP_FLAG_LIMITED_MODE flag.

Does this apply for TPM 1.2? Are there differences?

As I mentioned earlier, I am not familiar with TPM 1.2. However, to generalize the answer, such a state, like a firmware upgrade does make sense on TPM 1.2. From what I know it is performed using different calls to TPM but the concept is there.

Quoted 2 lines

Maybe for consistency call it TPM_CHIP_FLAG_UPGRADE_MODE? It makes easier to "connect dots" later on (has probably something to do TPM_RC_UPGRADE).

Reconsidered and implemented appropriate change. See the patch.

Quoted 5 lines

Review

Quoted 2 lines
(quoted diff) +1 -1
-     if (chip->flags & TPM_CHIP_FLAG_TPM2) {
+     if (chip->flags & TPM_CHIP_FLAG_TPM2 && !(chip->flags & TPM_CHIP_FLAG_LIMITED_MODE)) {

You cannot rely on validity of TPM_CHIP_FLAG_TPM2, as tpm_tis driver uses a TPM command to probe the TPM version.

I never intended to relay on TPM_CHIP_FLAG_TPM2. In this case the point to avoid calling cdev_device_add if the chip is not TPM2 or is in firmware Upgrade/Recovery mode.

Quoted 4 lines
Quoted 2 lines
       rc = tpm2_get_cc_attrs_tbl(chip);

+ if (rc) {

Why all rc's apply?

The standard doesn't specify what RC should be returned if TPM is in Recovery mode. What the standard agrees on is that the underlying call will fail.

Quoted 4 lines
Quoted 1 line

+ dev_info(&chip->dev, "TPM is in failure mode, functionality limited\n");

Here is again a different name for the same thing (different than TPM_CHIP_FLAG_LIMITED_MODE).

The logic behind this check goes like that: Assume that if we reached this point TPM is not in Upgrade mode. Although, it can still be in Recovery mode. If the tpm2_get_cc_attrs_tbl() call fails, then my assumption is that TPM requires firmware recovery.

This mode is introduced to handle situations when TPM firmware Upgrade failed. In such a case TPM allows to flash only original firmware to bring the functionality back. There is a predefined list of calls to which TPM is going to answer in this mode. Responses to other calls are vendor-dependent.

Another assumption here is that if for some reason there is a communication error occurred at this point it will be caught here. Any better ideas on how to handle this are welcome.

--
Kind regards,
Borys