Add CONFIG_TPM_AUTOSTART, which runs the TPM startup and self-test
sequence from an EVT_LAST_STAGE_INIT handler. This is the same thing the
'tpm autostart' command does, but it does not depend on a boot script or
on the environment.

Today a TPM is only started automatically when either CONFIG_MEASURED_BOOT
is used (and then only from bootm) or when booting via EFI with
CONFIG_EFI_TCG2_PROTOCOL. A plain booti/bootm therefore leaves the chip
uninitialised and the OS has to start it itself, which Linux reports as
"A TPM error (256) occurred attempting the self test". Boards have worked
around this by putting 'tpm autostart' into their environment, see
board/xilinx/zynqmp/zynqmp_kria.env.

The startup is done from an event rather than in the uclass probe:
nothing probes the TPM on a plain booti/bootm, and the event runs exactly
once in U-Boot proper without changing what probing a TPM means for the
existing callers. The startup itself is in tpm_autostart_init(), so that
it can also be called directly, e.g. from tests.

Boards that share one binary between variants with and without a TPM
fitted can enable this safely: when no TPM device is found the startup is
skipped silently. Other errors are reported as warnings but do not stop
the boot. Note that the full self-test adds to the boot time.

Signed-off-by: Michael Haener <[email protected]>
Reviewed-by: Simon Glass <[email protected]>
---
Changes in v4:
- Move the startup into tpm_autostart_init(), so that tests can call it
  without sending EVT_LAST_STAGE_INIT
- Add Reviewed-by from Simon

Changes in v3:
- Split the tests and the Kria conversion into separate patches

Changes in v2:
- Move the code to lib/tpm_autostart.c and the option to lib/Kconfig
- Stay silent only when no TPM device exists (-ENODEV), warn otherwise
- Use u32 and %#x for the tpm_auto_start() result
- Mention the boot-time cost of the self-test in the help text
- Document the option in doc/usage/measured_boot.rst
- Explain in the commit message why an event is used instead of probe

 doc/usage/measured_boot.rst | 18 ++++++++++++++
 include/tpm_api.h           | 10 ++++++++
 lib/Kconfig                 | 19 +++++++++++++++
 lib/Makefile                |  1 +
 lib/tpm_autostart.c         | 48 +++++++++++++++++++++++++++++++++++++
 5 files changed, 96 insertions(+)
 create mode 100644 lib/tpm_autostart.c

diff --git a/doc/usage/measured_boot.rst b/doc/usage/measured_boot.rst
index 488dd546f13..d74cfab2594 100644
--- a/doc/usage/measured_boot.rst
+++ b/doc/usage/measured_boot.rst
@@ -55,3 +55,21 @@ Requirements
 * The operating system must also be configured to use the memory regions
   specified in the U-Boot device-tree in order to make use of the event
   log.
+
+Starting the TPM without measured boot
+--------------------------------------
+
+Measured boot starts the TPM as a side effect, but only on the boot paths
+listed above. A plain booti/bootm without CONFIG_MEASURED_BOOT, or a boot
+which does not go through the EFI subsystem, leaves the chip uninitialised
+and the operating system has to start it itself. Some kernels report this as
+an error.
+
+CONFIG_TPM_AUTOSTART runs the same startup and self-test sequence as the
+'tpm autostart' command from an EVT_LAST_STAGE_INIT handler, independently of
+the boot path, the environment and any boot script. When no TPM device is
+found the startup is skipped, so a board which shares one binary between
+variants with and without a TPM fitted can enable it safely.
+
+Note that a full self-test can take a noticeable amount of time on a TPMv2,
+which is added to every boot even when the TPM is not used afterwards.
diff --git a/include/tpm_api.h b/include/tpm_api.h
index 022a8bbaeca..73c0ac6b3e7 100644
--- a/include/tpm_api.h
+++ b/include/tpm_api.h
@@ -339,4 +339,14 @@ static inline bool tpm_is_v2(struct udevice *dev)
  */
 u32 tpm_auto_start(struct udevice *dev);
 
+/**
+ * tpm_autostart_init() - start up the first TPM, if there is one
+ *
+ * This is run from EVT_LAST_STAGE_INIT when CONFIG_TPM_AUTOSTART is enabled.
+ * Errors are reported as warnings.
+ *
+ * Return: 0 if OK or if there is no TPM, -ve on error
+ */
+int tpm_autostart_init(void);
+
 #endif /* __TPM_API_H */
diff --git a/lib/Kconfig b/lib/Kconfig
index ff7655e1496..33ae8037479 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -565,6 +565,25 @@ config TPM
          If you want a fully functional TPM enable all hashing algorithms.
          If you enabled measured boot all hashing algorithms are selected.
 
+config TPM_AUTOSTART
+       bool "Start the TPM during board initialisation"
+       depends on TPM
+       select EVENT
+       help
+         Run the TPM startup and self-test sequence from an EVT_LAST_STAGE_INIT
+         handler, which is the same thing the 'tpm autostart' command does.
+         Without this the chip is still uninitialised when the OS takes over
+         and the OS has to start it itself, which some kernels report as an
+         error.
+
+         Boards that share one binary between variants with and without a TPM
+         fitted can enable this safely: when no TPM device is found the
+         startup is skipped silently.
+
+         Note that a full self-test can take a noticeable amount of time on
+         a TPMv2, so this adds to the boot time of every boot, even when the
+         TPM is not used afterwards.
+
 config SPL_TPM
        bool "Trusted Platform Module (TPM) Support in SPL"
        depends on SPL_DM
diff --git a/lib/Makefile b/lib/Makefile
index 93a389b10e4..b31861fe4b5 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -59,6 +59,7 @@ obj-$(CONFIG_$(PHASE_)OF_LIVE) += of_live.o
 obj-$(CONFIG_$(PHASE_)TPM) += tpm-common.o
 ifeq ($(CONFIG_$(PHASE_)TPM),y)
 obj-$(CONFIG_TPM) += tpm_api.o
+obj-$(CONFIG_TPM_AUTOSTART) += tpm_autostart.o
 obj-$(CONFIG_TPM_V1) += tpm-v1.o
 obj-$(CONFIG_TPM_V2) += tpm-v2.o
 obj-$(CONFIG_EFI_TCG2_PROTOCOL) += tpm_tcg2.o
diff --git a/lib/tpm_autostart.c b/lib/tpm_autostart.c
new file mode 100644
index 00000000000..ae69f797652
--- /dev/null
+++ b/lib/tpm_autostart.c
@@ -0,0 +1,48 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Start the TPM during init, so that the OS does not have to do it.
+ *
+ * Copyright (c) 2026 Siemens AG
+ */
+
+#define LOG_CATEGORY UCLASS_TPM
+
+#include <dm.h>
+#include <errno.h>
+#include <event.h>
+#include <log.h>
+#include <tpm_api.h>
+
+int tpm_autostart_init(void)
+{
+       struct udevice *dev;
+       int ret;
+       u32 rc;
+
+       ret = uclass_first_device_err(UCLASS_TPM, &dev);
+       if (ret) {
+               /* a board may share one binary between variants without a TPM 
*/
+               if (ret == -ENODEV)
+                       return 0;
+
+               log_warning("Cannot init TPM (err=%dE)\n", ret);
+               return ret;
+       }
+
+       rc = tpm_auto_start(dev);
+       if (rc) {
+               log_warning("TPM startup failed (rc=%#x)\n", rc);
+               return -EIO;
+       }
+
+       return 0;
+}
+
+static int tpm_evt_autostart(void)
+{
+       /* errors are reported above and must not stop the boot */
+       tpm_autostart_init();
+
+       return 0;
+}
+EVENT_SPY_SIMPLE(EVT_LAST_STAGE_INIT, tpm_evt_autostart);
-- 
2.55.0

Reply via email to