Hi Michael

The patches seem reasonable.

Simon wanted to convert the xilinx board as the first user which makes
sense. Also can you split this in 3 patches?
- Add the new Kconfig option + code
- Add the selftests
- Tweak the xilinx configs

Thanks
/Ilias

On Tue, 6 Oct 2026 at 12:05, Michael Haener <[email protected]> wrote:
>
> 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.
>
> 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.
>
> Enable the option on sandbox and add tests which send the event.
>
> Signed-off-by: Michael Haener <[email protected]>
> ---
> 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
> - Enable the option on sandbox and add tests
> - Explain in the commit message why an event is used instead of probe
>
>  configs/sandbox_defconfig   |  1 +
>  doc/usage/measured_boot.rst | 18 +++++++++++++++
>  lib/Kconfig                 | 19 ++++++++++++++++
>  lib/Makefile                |  1 +
>  lib/tpm_autostart.c         | 37 +++++++++++++++++++++++++++++++
>  test/dm/tpm.c               | 44 +++++++++++++++++++++++++++++++++++++
>  6 files changed, 120 insertions(+)
>  create mode 100644 lib/tpm_autostart.c
>
> diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig
> index a2708e6fb33..3efbdc6fdf1 100644
> --- a/configs/sandbox_defconfig
> +++ b/configs/sandbox_defconfig
> @@ -400,6 +400,7 @@ CONFIG_ECDSA=y
>  CONFIG_ECDSA_VERIFY=y
>  CONFIG_RSASSA_PSS=y
>  CONFIG_TPM=y
> +CONFIG_TPM_AUTOSTART=y
>  CONFIG_ERRNO_STR=y
>  CONFIG_GETOPT=y
>  CONFIG_TEST_FDTDEC=y
> 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/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..5b43b2aecbb
> --- /dev/null
> +++ b/lib/tpm_autostart.c
> @@ -0,0 +1,37 @@
> +// 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>
> +
> +static int tpm_evt_autostart(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)
> +                       log_warning("Cannot init TPM (err=%dE)\n", ret);
> +
> +               return 0;
> +       }
> +
> +       rc = tpm_auto_start(dev);
> +       if (rc)
> +               log_warning("TPM startup failed (rc=%#x)\n", rc);
> +
> +       return 0;
> +}
> +EVENT_SPY_SIMPLE(EVT_LAST_STAGE_INIT, tpm_evt_autostart);
> diff --git a/test/dm/tpm.c b/test/dm/tpm.c
> index 87c5c416daa..46e843787f1 100644
> --- a/test/dm/tpm.c
> +++ b/test/dm/tpm.c
> @@ -5,8 +5,11 @@
>   */
>
>  #include <dm.h>
> +#include <event.h>
>  #include <tpm_api.h>
> +#include <dm/device-internal.h>
>  #include <dm/test.h>
> +#include <dm/uclass-internal.h>
>  #include <test/test.h>
>  #include <test/ut.h>
>
> @@ -197,3 +200,44 @@ static int dm_test_tpm_autostart_reinit(struct 
> unit_test_state *uts)
>         return 0;
>  }
>  DM_TEST(dm_test_tpm_autostart_reinit, UTF_SCAN_FDT);
> +
> +/* Check that CONFIG_TPM_AUTOSTART starts the TPM from the init event */
> +static int dm_test_tpm_autostart_event(struct unit_test_state *uts)
> +{
> +       struct udevice *dev;
> +       char buf[50];
> +
> +       if (!IS_ENABLED(CONFIG_TPM_AUTOSTART))
> +               return 0;
> +
> +       ut_assertok(get_tpm_version(TPM_V2, &dev));
> +
> +       ut_assert(tpm_report_state(dev, buf, sizeof(buf)));
> +       ut_asserteq_str("init_done=0", buf);
> +
> +       ut_assertok(event_notify_null(EVT_LAST_STAGE_INIT));
> +
> +       ut_assert(tpm_report_state(dev, buf, sizeof(buf)));
> +       ut_asserteq_str("init_done=1", buf);
> +
> +       return 0;
> +}
> +DM_TEST(dm_test_tpm_autostart_event, UTF_SCAN_FDT);
> +
> +/* Check that a missing TPM does not make the init event fail */
> +static int dm_test_tpm_autostart_event_missing(struct unit_test_state *uts)
> +{
> +       struct udevice *dev;
> +
> +       if (!IS_ENABLED(CONFIG_TPM_AUTOSTART))
> +               return 0;
> +
> +       while (!uclass_find_first_device(UCLASS_TPM, &dev) && dev)
> +               ut_assertok(device_unbind(dev));
> +
> +       ut_asserteq(-ENODEV, uclass_first_device_err(UCLASS_TPM, &dev));
> +       ut_assertok(event_notify_null(EVT_LAST_STAGE_INIT));
> +
> +       return 0;
> +}
> +DM_TEST(dm_test_tpm_autostart_event_missing, UTF_SCAN_FDT);
>
> base-commit: 8d7bc8add17bbc69a58dfe0002eea039dd2dc1bc
> --
> 2.55.0
>

Reply via email to