TPM commands need to complete within the command duration defined
in the TPM2 spec or to keep answering TPM2_RC_RETRY for at most
TPM2_DURATION_LONG (2s).
Devices that have different timeout requirements than the TPM2 spec
could exhaust the retry budget or exceed the command duration. These
failures disable the device during an auth session, failing all
subsequent TPM requests. Worse, when a TPM2_CC_FLUSH_CONTEXT command
fails, it leaks the TPM's transient memory:
tpm tpm0: in retry loop
tpm tpm0: tpm2_load_context: failed with a TPM error 0x0922
...
tpm tpm0: A TPM error (2338) occurred flushing context
Fix this by adding chip->busy_timeout_ms to support devices that know
the expected delay window. This value raises the TPM2_RC_RETRY retry
budget and per-command durations to at least busy_timeout_ms. Chips
that leave it at 0 keep the current timeouts. The driver sets it for
NitroTPM in the following patch.
Tested with CONFIG_TCG_TPM2_HMAC=y and the following patch with the
NitroTPM quirk applied, in QEMU with swtpm by stalling commands and
holding the TPM in TPM2_RC_RETRY.
Assisted-by: LLM
Signed-off-by: Surendran Kanagaraj <[email protected]>
---
drivers/char/tpm/tpm-interface.c | 11 ++++++++---
drivers/char/tpm/tpm.h | 2 +-
drivers/char/tpm/tpm2-cmd.c | 17 ++++++++++++-----
include/linux/tpm.h | 7 +++++++
4 files changed, 28 insertions(+), 9 deletions(-)
diff --git a/drivers/char/tpm/tpm-interface.c b/drivers/char/tpm/tpm-interface.c
index 0bab78c8767c..a423395b201a 100644
--- a/drivers/char/tpm/tpm-interface.c
+++ b/drivers/char/tpm/tpm-interface.c
@@ -53,7 +53,7 @@ MODULE_PARM_DESC(suspend_pcr,
unsigned long tpm_calc_ordinal_duration(struct tpm_chip *chip, u32 ordinal)
{
if (chip->flags & TPM_CHIP_FLAG_TPM2)
- return tpm2_calc_ordinal_duration(ordinal);
+ return tpm2_calc_ordinal_duration(chip, ordinal);
else
return tpm1_calc_ordinal_duration(chip, ordinal);
}
@@ -213,7 +213,8 @@ static ssize_t tpm_try_transmit(struct tpm_chip *chip, void
*buf, size_t bufsiz)
*
* A wrapper around tpm_try_transmit() that handles TPM2_RC_RETRY returns from
* the TPM and retransmits the command after a delay up to a maximum wait of
- * TPM2_DURATION_LONG.
+ * TPM2_DURATION_LONG, or chip->busy_timeout_ms when the driver declared a
+ * longer transient unavailability window.
*
* Note that TPM 1.x never returns TPM2_RC_RETRY so the retry logic is TPM 2.0
* only.
@@ -228,6 +229,7 @@ ssize_t tpm_transmit(struct tpm_chip *chip, u8 *buf, size_t
bufsiz)
/* space for header and handles */
u8 save[TPM_HEADER_SIZE + 3*sizeof(u32)];
unsigned int delay_msec = TPM2_DURATION_SHORT;
+ unsigned int max_delay_msec = TPM2_DURATION_LONG;
u32 rc = 0;
ssize_t ret;
const size_t save_size = min(sizeof(save), bufsiz);
@@ -241,6 +243,9 @@ ssize_t tpm_transmit(struct tpm_chip *chip, u8 *buf, size_t
bufsiz)
*/
memcpy(save, buf, save_size);
+ if (chip->busy_timeout_ms > max_delay_msec)
+ max_delay_msec = chip->busy_timeout_ms;
+
for (;;) {
ret = tpm_try_transmit(chip, buf, bufsiz);
if (ret < 0)
@@ -255,7 +260,7 @@ ssize_t tpm_transmit(struct tpm_chip *chip, u8 *buf, size_t
bufsiz)
if (rc == TPM2_RC_TESTING && cc == TPM2_CC_SELF_TEST)
break;
- if (delay_msec > TPM2_DURATION_LONG) {
+ if (delay_msec > max_delay_msec) {
if (rc == TPM2_RC_RETRY)
dev_err(&chip->dev, "in retry loop\n");
else
diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h
index fa554c5ad80b..457eba8d03dd 100644
--- a/drivers/char/tpm/tpm.h
+++ b/drivers/char/tpm/tpm.h
@@ -119,7 +119,7 @@ ssize_t tpm2_get_tpm_pt(struct tpm_chip *chip, u32
property_id,
ssize_t tpm2_get_pcr_allocation(struct tpm_chip *chip);
int tpm2_auto_startup(struct tpm_chip *chip);
void tpm2_shutdown(struct tpm_chip *chip, u16 shutdown_type);
-unsigned long tpm2_calc_ordinal_duration(u32 ordinal);
+unsigned long tpm2_calc_ordinal_duration(struct tpm_chip *chip, u32 ordinal);
int tpm2_probe(struct tpm_chip *chip);
int tpm2_get_cc_attrs_tbl(struct tpm_chip *chip);
int tpm2_find_cc(struct tpm_chip *chip, u32 cc);
diff --git a/drivers/char/tpm/tpm2-cmd.c b/drivers/char/tpm/tpm2-cmd.c
index ae22295df798..dc5ed57fefe1 100644
--- a/drivers/char/tpm/tpm2-cmd.c
+++ b/drivers/char/tpm/tpm2-cmd.c
@@ -78,20 +78,27 @@ static const struct {
/**
* tpm2_calc_ordinal_duration() - Calculate the maximum command duration
+ * @chip: TPM chip to use.
* @ordinal: TPM command ordinal.
*
* Returns the maximum amount of time the chip is expected by kernel to
- * take in jiffies.
+ * take in jiffies. The duration is never lower than chip->busy_timeout_ms.
*/
-unsigned long tpm2_calc_ordinal_duration(u32 ordinal)
+unsigned long tpm2_calc_ordinal_duration(struct tpm_chip *chip, u32 ordinal)
{
+ unsigned long duration = TPM2_DURATION_DEFAULT;
int i;
for (i = 0; i < ARRAY_SIZE(tpm2_ordinal_duration_map); i++)
- if (ordinal == tpm2_ordinal_duration_map[i].ordinal)
- return
msecs_to_jiffies(tpm2_ordinal_duration_map[i].duration);
+ if (ordinal == tpm2_ordinal_duration_map[i].ordinal) {
+ duration = tpm2_ordinal_duration_map[i].duration;
+ break;
+ }
+
+ if (duration < chip->busy_timeout_ms)
+ duration = chip->busy_timeout_ms;
- return msecs_to_jiffies(TPM2_DURATION_DEFAULT);
+ return msecs_to_jiffies(duration);
}
/**
diff --git a/include/linux/tpm.h b/include/linux/tpm.h
index 0db277af45c3..7089067412d3 100644
--- a/include/linux/tpm.h
+++ b/include/linux/tpm.h
@@ -140,6 +140,13 @@ struct tpm_chip {
unsigned long duration[TPM_NUM_DURATIONS]; /* jiffies */
bool duration_adjusted;
+ /*
+ * Longest unavailability expected from the chip in ms. Raises the
+ * TPM2_RC_RETRY retry budget and the per-command durations to at
+ * least this value; 0 keeps the defaults.
+ */
+ unsigned int busy_timeout_ms;
+
struct dentry *bios_dir;
const struct attribute_group *groups[3 + TPM_MAX_HASHES];
--
2.47.3