diff --git a/man/rules/meson.build b/man/rules/meson.build
index e99b77eb50..c99f79eba8 100644
--- a/man/rules/meson.build
+++ b/man/rules/meson.build
@@ -1100,6 +1100,7 @@ manpages = [
'systemd-tmpfiles-setup-dev.service',
'systemd-tmpfiles-setup.service'],
''],
+ ['systemd-tpm2-generator', '8', [], ''],
['systemd-tpm2-setup.service',
'8',
['systemd-tpm2-setup', 'systemd-tpm2-setup-early.service'],
diff --git a/man/systemd-tpm2-generator.xml b/man/systemd-tpm2-generator.xml
new file mode 100644
index 0000000000..51950eece9
--- /dev/null
+++ b/man/systemd-tpm2-generator.xml
@@ -0,0 +1,60 @@
+
+
+
+%entities;
+]>
+
+
+
+
+ systemd-tpm2-generator
+ systemd
+
+
+
+ systemd-tpm2-generator
+ 8
+
+
+
+ systemd-tpm2-generator
+ Generator for inserting TPM2 synchronization point in the boot process
+
+
+
+ /usr/lib/systemd/system-generators/systemd-tpm2-generator
+
+
+
+ Description
+
+ systemd-tpm2-generator is a generator that adds a Wants=
+ dependency from sysinit.target to tpm2.target when it detects
+ that the firmware discovered a TPM2 device but the OS kernel so far did
+ not. tpm2.target is supposed to act as synchronization point for all services that
+ require TPM2 device access. See
+ systemd.special7 for
+ details.
+
+ The kernel command line option may be used to override
+ behaviour of the generator. It accepts a boolean value: if true then tpm2.target
+ will be added as synchronization point even if the firmware has not detected a TPM2 device. If false, the
+ target will not be inserted even if firmware reported a device but the OS kernel doesn't expose a device
+ for it yet. The latter might be useful in environments where a suitable TPM2 driver for the available
+ hardware is not available.
+
+ systemd-tpm2-generator implements
+ systemd.generator7.
+
+
+
+ See Also
+
+ systemd1
+ systemd.special7
+ kernel-command-line7
+
+
+
diff --git a/man/systemd.special.xml b/man/systemd.special.xml
index ff0f73f191..988b7175ba 100644
--- a/man/systemd.special.xml
+++ b/man/systemd.special.xml
@@ -92,6 +92,7 @@
time-set.target,
time-sync.target,
timers.target,
+ tpm2.target,
umount.target,
usb-gadget.target,
-.slice,
@@ -948,6 +949,24 @@
+
+ tpm2.target
+
+ This target is started automatically if a TPM2 device is discovered, either by the OS or by
+ the firmware. It acts as synchronization point for services that require TPM2 device access. The
+ target unit is enqueued by
+ systemd-tpm2-generator8
+ if it detects that the firmware has discovered a TPM2 device but the OS kernel has not activated
+ a driver for it yet. It is also pulled in whenever
+ systemd-udevd.service8
+ discovers a TPM2 device. The target unit is ordered after the /dev/tpmrm0
+ device node, so that it only becomes active once the TPM2 device is actually accessible. Early
+ boot programs that intend to access the TPM2 device should hence order themselves after this
+ target unit, but not pull it in.
+
+
+
+
diff --git a/rules.d/99-systemd.rules.in b/rules.d/99-systemd.rules.in
index 455a2368eb..33624f88a5 100644
--- a/rules.d/99-systemd.rules.in
+++ b/rules.d/99-systemd.rules.in
@@ -85,4 +85,7 @@ SUBSYSTEM=="misc", KERNEL=="rfkill", TAG+="systemd", ENV{SYSTEMD_WANTS}+="system
SUBSYSTEM=="module", KERNEL=="fuse", TAG+="systemd", ENV{SYSTEMD_WANTS}+="sys-fs-fuse-connections.mount"
SUBSYSTEM=="module", KERNEL=="configfs", TAG+="systemd", ENV{SYSTEMD_WANTS}+="sys-kernel-config.mount"
+# Pull in tpm2.target whenever /dev/tpmrm* shows up
+SUBSYSTEM=="tpmrm", KERNEL=="tpmrm[0-9]*", TAG+="systemd", ENV{SYSTEMD_WANTS}+="tpm2.target"
+
LABEL="systemd_end"
diff --git a/src/basic/special.h b/src/basic/special.h
index a625e75bed..27d2c26e48 100644
--- a/src/basic/special.h
+++ b/src/basic/special.h
@@ -47,6 +47,7 @@
#define SPECIAL_TIME_SYNC_TARGET "time-sync.target" /* LSB's $time */
#define SPECIAL_TIME_SET_TARGET "time-set.target"
#define SPECIAL_BASIC_TARGET "basic.target"
+#define SPECIAL_TPM2_TARGET "tpm2.target"
/* LSB compatibility */
#define SPECIAL_NETWORK_TARGET "network.target" /* LSB's $network */
diff --git a/src/tpm2-setup/meson.build b/src/tpm2-setup/meson.build
index c85721c98e..77fad97b7f 100644
--- a/src/tpm2-setup/meson.build
+++ b/src/tpm2-setup/meson.build
@@ -13,4 +13,10 @@ executables += [
libopenssl,
],
},
+
+ generator_template + {
+ 'name' : 'systemd-tpm2-generator',
+ 'sources' : files('tpm2-generator.c'),
+ },
+
]
diff --git a/src/tpm2-setup/tpm2-generator.c b/src/tpm2-setup/tpm2-generator.c
new file mode 100644
index 0000000000..1ba8a7fc93
--- /dev/null
+++ b/src/tpm2-setup/tpm2-generator.c
@@ -0,0 +1,80 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "generator.h"
+#include "proc-cmdline.h"
+#include "special.h"
+#include "tpm2-util.h"
+#include "parse-util.h"
+
+/* A small generator that enqueues tpm2.target as synchronization point if the TPM2 device hasn't shown up
+ * yet, but the firmware reports it to exist. This is supposed to deal with systems where the TPM2 driver
+ * support is built as kmod and must be loaded before it's ready to be used. The tpm2.target is only enqueued
+ * if firmware says there is a TPM2 device, our userspace support for TPM2 is fully available but the TPM2
+ * device hasn't shown up in /dev/ yet. */
+
+static const char *arg_dest = NULL;
+static int arg_tpm2_wait = -1; /* tri-state: negative → don't know */
+
+static int parse_proc_cmdline_item(const char *key, const char *value, void *data) {
+ int r;
+
+ assert(key);
+
+ if (proc_cmdline_key_streq(key, "systemd.tpm2-wait")) {
+ r = value ? parse_boolean(value) : 1;
+ if (r < 0)
+ log_warning_errno(r, "Failed to parse 'systemd.tpm2-wait' kernel command line argument, ignoring: %s", value);
+ else
+ arg_tpm2_wait = r;
+ }
+
+ return 0;
+}
+
+static int generate_tpm_target_symlink(void) {
+ int r;
+
+ if (arg_tpm2_wait == 0) {
+ log_debug("Not generating tpm2.target synchronization point, as this was explicitly turned off via kernel command line.");
+ return 0;
+ }
+
+ if (arg_tpm2_wait < 0) {
+ Tpm2Support support = tpm2_support();
+
+ if (FLAGS_SET(support, TPM2_SUPPORT_DRIVER)) {
+ log_debug("Not generating tpm2.target synchronization point, as TPM2 device is already present.");
+ return 0;
+ }
+
+ if (!FLAGS_SET(support, TPM2_SUPPORT_FIRMWARE)) {
+ log_debug("Not generating tpm2.target synchronization point, as firmware reports no TPM2 present.");
+ return 0;
+ }
+
+ if (!FLAGS_SET(support, TPM2_SUPPORT_SYSTEM|TPM2_SUPPORT_SUBSYSTEM|TPM2_SUPPORT_LIBRARIES)) {
+ log_debug("Not generating tpm2.target synchronization point, as userspace support for TPM2 is not complete.");
+ return 0;
+ }
+ }
+
+ r = generator_add_symlink(arg_dest, SPECIAL_SYSINIT_TARGET, "wants", SYSTEM_DATA_UNIT_DIR "/" SPECIAL_TPM2_TARGET);
+ if (r < 0)
+ return log_error_errno(r, "Failed to hook in tpm2.target: %m");
+
+ return 0;
+}
+
+static int run(const char *dest, const char *dest_early, const char *dest_late) {
+ int r;
+
+ assert_se(arg_dest = dest);
+
+ r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, PROC_CMDLINE_STRIP_RD_PREFIX);
+ if (r < 0)
+ log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m");
+
+ return generate_tpm_target_symlink();
+}
+
+DEFINE_MAIN_GENERATOR_FUNCTION(run);
diff --git a/units/meson.build b/units/meson.build
index 9d3604951d..40cc3d10d3 100644
--- a/units/meson.build
+++ b/units/meson.build
@@ -700,6 +700,7 @@ units = [
'file' : 'tmp.mount',
'symlinks' : ['local-fs.target.wants/'],
},
+ { 'file' : 'tpm2.target' },
{ 'file' : 'umount.target' },
{ 'file' : 'usb-gadget.target' },
{ 'file' : 'user-runtime-dir@.service.in' },
diff --git a/units/systemd-pcrextend.socket b/units/systemd-pcrextend.socket
index 6d7b8ff84c..7d156c1448 100644
--- a/units/systemd-pcrextend.socket
+++ b/units/systemd-pcrextend.socket
@@ -11,6 +11,7 @@
Description=TPM2 PCR Extension (Varlink)
Documentation=man:systemd-pcrextend(8)
DefaultDependencies=no
+After=tpm2.target
Before=sockets.target
ConditionSecurity=measured-uki
diff --git a/units/systemd-pcrextend@.service.in b/units/systemd-pcrextend@.service.in
index 2305b1cd4c..6020a21c40 100644
--- a/units/systemd-pcrextend@.service.in
+++ b/units/systemd-pcrextend@.service.in
@@ -11,6 +11,7 @@
Description=TPM2 PCR Extension (Varlink)
Documentation=man:systemd-pcrphase.service(8)
DefaultDependencies=no
+After=tpm2.target
Conflicts=shutdown.target initrd-switch-root.target
Before=shutdown.target initrd-switch-root.target
diff --git a/units/systemd-pcrfs-root.service.in b/units/systemd-pcrfs-root.service.in
index 11dc747194..582e1d83e5 100644
--- a/units/systemd-pcrfs-root.service.in
+++ b/units/systemd-pcrfs-root.service.in
@@ -12,7 +12,7 @@ Description=TPM2 PCR Root File System Measurement
Documentation=man:systemd-pcrfs-root.service(8)
DefaultDependencies=no
Conflicts=shutdown.target
-After=systemd-pcrmachine.service
+After=tpm2.target systemd-pcrmachine.service
Before=shutdown.target
ConditionPathExists=!/etc/initrd-release
ConditionSecurity=measured-uki
diff --git a/units/systemd-pcrfs@.service.in b/units/systemd-pcrfs@.service.in
index fbaec4b999..262a82fb04 100644
--- a/units/systemd-pcrfs@.service.in
+++ b/units/systemd-pcrfs@.service.in
@@ -13,7 +13,7 @@ Documentation=man:systemd-pcrfs@.service(8)
DefaultDependencies=no
BindsTo=%i.mount
Conflicts=shutdown.target
-After=%i.mount systemd-pcrfs-root.service
+After=%i.mount tpm2.target systemd-pcrfs-root.service
Before=shutdown.target
ConditionPathExists=!/etc/initrd-release
ConditionSecurity=measured-uki
diff --git a/units/systemd-pcrmachine.service.in b/units/systemd-pcrmachine.service.in
index fb7d3ce601..cbd1005104 100644
--- a/units/systemd-pcrmachine.service.in
+++ b/units/systemd-pcrmachine.service.in
@@ -12,6 +12,7 @@ Description=TPM2 PCR Machine ID Measurement
Documentation=man:systemd-pcrmachine.service(8)
DefaultDependencies=no
Conflicts=shutdown.target
+After=tpm2.target
Before=sysinit.target shutdown.target
ConditionPathExists=!/etc/initrd-release
ConditionSecurity=measured-uki
diff --git a/units/systemd-pcrphase-initrd.service.in b/units/systemd-pcrphase-initrd.service.in
index b337d602ba..3b18b4f29d 100644
--- a/units/systemd-pcrphase-initrd.service.in
+++ b/units/systemd-pcrphase-initrd.service.in
@@ -12,6 +12,7 @@ Description=TPM2 PCR Barrier (initrd)
Documentation=man:systemd-pcrphase-initrd.service(8)
DefaultDependencies=no
Conflicts=shutdown.target initrd-switch-root.target
+After=tpm2.target
Before=sysinit.target cryptsetup-pre.target cryptsetup.target shutdown.target initrd-switch-root.target systemd-sysext.service
ConditionPathExists=/etc/initrd-release
ConditionSecurity=measured-uki
diff --git a/units/systemd-pcrphase-sysinit.service.in b/units/systemd-pcrphase-sysinit.service.in
index 08f73973be..d938e02401 100644
--- a/units/systemd-pcrphase-sysinit.service.in
+++ b/units/systemd-pcrphase-sysinit.service.in
@@ -12,7 +12,7 @@ Description=TPM2 PCR Barrier (Initialization)
Documentation=man:systemd-pcrphase-sysinit.service(8)
DefaultDependencies=no
Conflicts=shutdown.target
-After=sysinit.target
+After=sysinit.target tpm2.target
Before=basic.target shutdown.target
ConditionPathExists=!/etc/initrd-release
ConditionSecurity=measured-uki
diff --git a/units/systemd-pcrphase.service.in b/units/systemd-pcrphase.service.in
index c94ad756d4..26b9e5c538 100644
--- a/units/systemd-pcrphase.service.in
+++ b/units/systemd-pcrphase.service.in
@@ -10,7 +10,7 @@
[Unit]
Description=TPM2 PCR Barrier (User)
Documentation=man:systemd-pcrphase.service(8)
-After=remote-fs.target remote-cryptsetup.target
+After=remote-fs.target remote-cryptsetup.target tpm2.target
Before=systemd-user-sessions.service
ConditionPathExists=!/etc/initrd-release
ConditionSecurity=measured-uki
diff --git a/units/systemd-tpm2-setup-early.service.in b/units/systemd-tpm2-setup-early.service.in
index c1597ea3f9..628f816678 100644
--- a/units/systemd-tpm2-setup-early.service.in
+++ b/units/systemd-tpm2-setup-early.service.in
@@ -15,6 +15,7 @@ Conflicts=shutdown.target
Before=sysinit.target shutdown.target
ConditionSecurity=measured-uki
ConditionPathExists=!/run/systemd/tpm2-srk-public-key.pem
+After=tpm2.target
[Service]
Type=oneshot
diff --git a/units/systemd-tpm2-setup.service.in b/units/systemd-tpm2-setup.service.in
index 6c99f3af0a..4830afa243 100644
--- a/units/systemd-tpm2-setup.service.in
+++ b/units/systemd-tpm2-setup.service.in
@@ -17,6 +17,7 @@ Before=sysinit.target shutdown.target
RequiresMountsFor=/var/lib/systemd/tpm2-srk-public-key.pem
ConditionSecurity=measured-uki
ConditionPathExists=!/etc/initrd-release
+After=tpm2.target
[Service]
Type=oneshot
diff --git a/units/tpm2.target b/units/tpm2.target
new file mode 100644
index 0000000000..ba51d5740e
--- /dev/null
+++ b/units/tpm2.target
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Unit]
+Description=Trusted Platform Module
+Documentation=man:systemd.special(7)
+
+# Make this a synchronization point on the first TPM device found
+After=dev-tpmrm0.device
+Wants=dev-tpmrm0.device