* [PATCH 1/2] tpm: Add per-chip timeout for transient unavailability
2026-09-23 14:28 [PATCH 0/2] tpm: Handle transient NitroTPM unavailability Surendran Kanagaraj
@ 2026-09-23 14:28 ` Surendran Kanagaraj
2026-09-23 14:28 ` [PATCH 2/2] tpm_crb: Raise timeouts for Amazon NitroTPM Surendran Kanagaraj
1 sibling, 0 replies; 3+ messages in thread
From: Surendran Kanagaraj @ 2026-09-23 14:28 UTC (permalink / raw)
To: Jarkko Sakkinen, Peter Huewe, Jason Gunthorpe
Cc: linux-integrity, linux-kernel, nh-open-source, Alexander Graf,
Gunnar Kudrjavets, Josh Levinson
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 <surenkj@amazon.com>
---
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
^ permalink raw reply [flat|nested] 3+ messages in thread* [PATCH 2/2] tpm_crb: Raise timeouts for Amazon NitroTPM
2026-09-23 14:28 [PATCH 0/2] tpm: Handle transient NitroTPM unavailability Surendran Kanagaraj
2026-09-23 14:28 ` [PATCH 1/2] tpm: Add per-chip timeout for transient unavailability Surendran Kanagaraj
@ 2026-09-23 14:28 ` Surendran Kanagaraj
1 sibling, 0 replies; 3+ messages in thread
From: Surendran Kanagaraj @ 2026-09-23 14:28 UTC (permalink / raw)
To: Jarkko Sakkinen, Peter Huewe, Jason Gunthorpe
Cc: linux-integrity, linux-kernel, nh-open-source, Alexander Graf,
Gunnar Kudrjavets, Josh Levinson
TPM commands need to complete within the command duration defined in
the TPM2 spec or keep answering TPM2_RC_RETRY for at most
TPM2_DURATION_LONG (2s).
In rare scenarios NitroTPM can enter a mode where it either sends
TPM2_RC_RETRY for more than 2s or delays command completion. When
this happens during auth session start, the driver disables the chip
and every subsequent request fails:
tpm tpm0: in retry loop
tpm tpm0: tpm2_load_context: failed with a TPM error 0x0922
Add support for vendor quirks to the CRB driver. The table is keyed by
the vendor ID read from the interface ID register. For NitroTPM, set
busy_timeout_ms to 30s, which covers the longest unavailability time
along with some headroom.
Tested on an EC2 instance with NitroTPM by inducing multiple
unavailability windows with no TPM errors, and in QEMU with swtpm by
holding the TPM in TPM2_RC_RETRY and stalling command completion for
longer than 2s with the quirk applied.
Assisted-by: LLM
Signed-off-by: Surendran Kanagaraj <surenkj@amazon.com>
---
drivers/char/tpm/tpm_crb.c | 54 ++++++++++++++++++++++++++++++++++++++
1 file changed, 54 insertions(+)
diff --git a/drivers/char/tpm/tpm_crb.c b/drivers/char/tpm/tpm_crb.c
index ceb4100ba400..4107203beec9 100644
--- a/drivers/char/tpm/tpm_crb.c
+++ b/drivers/char/tpm/tpm_crb.c
@@ -13,6 +13,7 @@
#include <linux/acpi.h>
#include <linux/highmem.h>
+#include <linux/io-64-nonatomic-lo-hi.h>
#include <linux/rculist.h>
#include <linux/module.h>
#include <linux/platform_device.h>
@@ -26,6 +27,15 @@
#define ACPI_SIG_TPM2 "TPM2"
#define TPM_CRB_MAX_RESOURCES 3
+/* TPM_CRB_INTF_ID_x vendor ID field, bits 47:32 of the 64-bit register */
+#define CRB_INTF_ID_VID(intf_id) (((intf_id) >> 32) & 0xffff)
+
+/* Amazon NitroTPM */
+#define CRB_INTF_VID_AMZN 0x0ec2
+
+/* Longest unavailability seen from NitroTPM */
+#define CRB_AMZN_BUSY_TIMEOUT_MS 30000
+
static const guid_t crb_acpi_start_guid =
GUID_INIT(0x6BBF6CAB, 0x5463, 0x4714,
0xB7, 0xCD, 0xF0, 0x20, 0x3C, 0x03, 0x68, 0xD4);
@@ -783,6 +793,48 @@ static int crb_map_pluton(struct device *dev, struct crb_priv *priv,
return 0;
}
+/*
+ * Read the vendor ID from the interface ID register. Returns -ENODEV when
+ * the head registers are not mapped for this start method.
+ */
+static int crb_vendor_id(struct crb_priv *priv)
+{
+ u64 intf_id;
+
+ if (!priv->regs_h)
+ return -ENODEV;
+
+ intf_id = lo_hi_readq(&priv->regs_h->intf_id);
+
+ return CRB_INTF_ID_VID(intf_id);
+}
+
+static void crb_amzn_quirk(struct tpm_chip *chip)
+{
+ /* NitroTPM requires larger timeouts */
+ chip->busy_timeout_ms = CRB_AMZN_BUSY_TIMEOUT_MS;
+}
+
+static const struct crb_vendor_quirk {
+ u16 vendor_id;
+ void (*apply)(struct tpm_chip *chip);
+} crb_vendor_quirks[] = {
+ { CRB_INTF_VID_AMZN, crb_amzn_quirk },
+};
+
+static void crb_apply_vendor_quirks(struct crb_priv *priv, struct tpm_chip *chip)
+{
+ int vendor_id = crb_vendor_id(priv);
+ unsigned int i;
+
+ if (vendor_id < 0)
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(crb_vendor_quirks); i++)
+ if (crb_vendor_quirks[i].vendor_id == vendor_id)
+ crb_vendor_quirks[i].apply(chip);
+}
+
static int crb_acpi_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -887,6 +939,8 @@ static int crb_acpi_probe(struct platform_device *pdev)
chip->acpi_dev_handle = device->handle;
chip->flags = TPM_CHIP_FLAG_TPM2;
+ crb_apply_vendor_quirks(priv, chip);
+
rc = tpm_chip_bootstrap(chip);
if (rc)
goto out;
--
2.47.3
^ permalink raw reply [flat|nested] 3+ messages in thread