diff --git a/docs/TRANSIENT-SETTINGS.md b/docs/TRANSIENT-SETTINGS.md
index 9fa856ec21..070ad5a285 100644
--- a/docs/TRANSIENT-SETTINGS.md
+++ b/docs/TRANSIENT-SETTINGS.md
@@ -68,6 +68,7 @@ Most generic unit settings are available for transient units.
✓ ConditionKernelCommandLine=
✓ ConditionKernelVersion=
✓ ConditionArchitecture=
+✓ ConditionFirmware=
✓ ConditionVirtualization=
✓ ConditionSecurity=
✓ ConditionCapability=
diff --git a/man/systemd.link.xml b/man/systemd.link.xml
index 5918a32189..6be115026b 100644
--- a/man/systemd.link.xml
+++ b/man/systemd.link.xml
@@ -222,6 +222,18 @@
+
+
+ Firmware=
+
+ Checks whether the system is running on a machine with the specified firmware. See
+ ConditionFirmware= in
+ systemd.unit5
+ for details. When prefixed with an exclamation mark (!), the result is negated.
+ If an empty string is assigned, then previously assigned value is cleared.
+
+
+
diff --git a/man/systemd.netdev.xml b/man/systemd.netdev.xml
index c1b1980bac..d93ee8bb30 100644
--- a/man/systemd.netdev.xml
+++ b/man/systemd.netdev.xml
@@ -258,6 +258,17 @@
+
+ Firmware=
+
+ Checks whether the system is running on a machine with the specified firmware. See
+ ConditionFirmware= in
+ systemd.unit5
+ for details. When prefixed with an exclamation mark (!), the result is negated.
+ If an empty string is assigned, then previously assigned value is cleared.
+
+
+
diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml
index 631658d88a..7f37f01ef9 100644
--- a/man/systemd.unit.xml
+++ b/man/systemd.unit.xml
@@ -1172,6 +1172,16 @@
+
+ ConditionFirmware=
+
+ Check whether the system's firmware is of a certain type. Possible values are:
+ uefi (for systems with EFI),
+ device-tree (for systems with a device tree) and
+ device-tree-compatible(xyz) (for systems with a device tree that is compatible to xyz).
+
+
+
ConditionVirtualization=
diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4
index c531380401..7c3c1c9095 100644
--- a/src/core/load-fragment-gperf.gperf.m4
+++ b/src/core/load-fragment-gperf.gperf.m4
@@ -297,6 +297,7 @@ Unit.ConditionFileIsExecutable, config_parse_unit_condition_path,
Unit.ConditionNeedsUpdate, config_parse_unit_condition_path, CONDITION_NEEDS_UPDATE, offsetof(Unit, conditions)
Unit.ConditionFirstBoot, config_parse_unit_condition_string, CONDITION_FIRST_BOOT, offsetof(Unit, conditions)
Unit.ConditionArchitecture, config_parse_unit_condition_string, CONDITION_ARCHITECTURE, offsetof(Unit, conditions)
+Unit.ConditionFirmware, config_parse_unit_condition_string, CONDITION_FIRMWARE, offsetof(Unit, conditions)
Unit.ConditionVirtualization, config_parse_unit_condition_string, CONDITION_VIRTUALIZATION, offsetof(Unit, conditions)
Unit.ConditionHost, config_parse_unit_condition_string, CONDITION_HOST, offsetof(Unit, conditions)
Unit.ConditionKernelCommandLine, config_parse_unit_condition_string, CONDITION_KERNEL_COMMAND_LINE, offsetof(Unit, conditions)
diff --git a/src/network/netdev/netdev-gperf.gperf b/src/network/netdev/netdev-gperf.gperf
index 8abe044890..6381bcfe0c 100644
--- a/src/network/netdev/netdev-gperf.gperf
+++ b/src/network/netdev/netdev-gperf.gperf
@@ -44,6 +44,7 @@ Match.Virtualization, config_parse_net_condition,
Match.KernelCommandLine, config_parse_net_condition, CONDITION_KERNEL_COMMAND_LINE, offsetof(NetDev, conditions)
Match.KernelVersion, config_parse_net_condition, CONDITION_KERNEL_VERSION, offsetof(NetDev, conditions)
Match.Architecture, config_parse_net_condition, CONDITION_ARCHITECTURE, offsetof(NetDev, conditions)
+Match.Firmware, config_parse_net_condition, CONDITION_FIRMWARE, offsetof(NetDev, conditions)
NetDev.Description, config_parse_string, 0, offsetof(NetDev, description)
NetDev.Name, config_parse_ifname, 0, offsetof(NetDev, ifname)
NetDev.Kind, config_parse_netdev_kind, 0, offsetof(NetDev, kind)
diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf
index 19d52a9296..91c21525fb 100644
--- a/src/network/networkd-network-gperf.gperf
+++ b/src/network/networkd-network-gperf.gperf
@@ -56,6 +56,7 @@ Match.Virtualization, config_parse_net_condition,
Match.KernelCommandLine, config_parse_net_condition, CONDITION_KERNEL_COMMAND_LINE, offsetof(Network, conditions)
Match.KernelVersion, config_parse_net_condition, CONDITION_KERNEL_VERSION, offsetof(Network, conditions)
Match.Architecture, config_parse_net_condition, CONDITION_ARCHITECTURE, offsetof(Network, conditions)
+Match.Firmware, config_parse_net_condition, CONDITION_FIRMWARE, offsetof(Network, conditions)
Link.MACAddress, config_parse_hwaddr, 0, offsetof(Network, mac)
Link.MTUBytes, config_parse_mtu, AF_UNSPEC, offsetof(Network, mtu)
Link.Group, config_parse_uint32, 0, offsetof(Network, group)
diff --git a/src/shared/condition.c b/src/shared/condition.c
index d84377c269..d4d9eacc4b 100644
--- a/src/shared/condition.c
+++ b/src/shared/condition.c
@@ -5,6 +5,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -450,6 +451,81 @@ static int condition_test_architecture(Condition *c, char **env) {
return a == b;
}
+#define DTCOMPAT_FILE "/sys/firmware/devicetree/base/compatible"
+static int condition_test_firmware_devicetree_compatible(const char *dtcarg) {
+ int r;
+ _cleanup_free_ char *dtcompat = NULL;
+ _cleanup_strv_free_ char **dtcompatlist = NULL;
+ size_t size;
+
+ r = read_full_virtual_file(DTCOMPAT_FILE, &dtcompat, &size);
+ if (r < 0) {
+ /* if the path doesn't exist it is incompatible */
+ if (r != -ENOENT)
+ log_debug_errno(r, "Failed to open() '%s', assuming machine is incompatible: %m", DTCOMPAT_FILE);
+ return false;
+ }
+
+ /* Not sure this can happen, but play safe. */
+ if (size == 0) {
+ log_debug("%s has zero length, assuming machine is incompatible", DTCOMPAT_FILE);
+ return false;
+ }
+
+ /*
+ * /sys/firmware/devicetree/base/compatible consists of one or more
+ * strings, each ending in '\0'. So the last character in dtcompat must
+ * be a '\0'.
+ */
+ if (dtcompat[size - 1] != '\0') {
+ log_debug("%s is in an unknown format, assuming machine is incompatible", DTCOMPAT_FILE);
+ return false;
+ }
+
+ dtcompatlist = strv_parse_nulstr(dtcompat, size);
+ if (!dtcompatlist)
+ return -ENOMEM;
+
+ return strv_contains(dtcompatlist, dtcarg);
+}
+
+static int condition_test_firmware(Condition *c, char **env) {
+ sd_char *dtc;
+
+ assert(c);
+ assert(c->parameter);
+ assert(c->type == CONDITION_FIRMWARE);
+
+ if (streq(c->parameter, "device-tree")) {
+ if (access("/sys/firmware/device-tree/", F_OK) < 0) {
+ if (errno != ENOENT)
+ log_debug_errno(errno, "Unexpected error when checking for /sys/firmware/device-tree/: %m");
+ return false;
+ } else
+ return true;
+ } else if ((dtc = startswith(c->parameter, "device-tree-compatible("))) {
+ _cleanup_free_ char *dtcarg = NULL;
+ char *end;
+
+ end = strchr(dtc, ')');
+ if (!end || *(end + 1) != '\0') {
+ log_debug("Malformed Firmware condition \"%s\"", c->parameter);
+ return false;
+ }
+
+ dtcarg = strndup(dtc, end - dtc);
+ if (!dtcarg)
+ return -ENOMEM;
+
+ return condition_test_firmware_devicetree_compatible(dtcarg);
+ } else if (streq(c->parameter, "uefi"))
+ return is_efi_boot();
+ else {
+ log_debug("Unsupported Firmware condition \"%s\"", c->parameter);
+ return false;
+ }
+}
+
static int condition_test_host(Condition *c, char **env) {
_cleanup_free_ char *h = NULL;
sd_id128_t x, y;
@@ -843,6 +919,7 @@ int condition_test(Condition *c, char **env) {
[CONDITION_HOST] = condition_test_host,
[CONDITION_AC_POWER] = condition_test_ac_power,
[CONDITION_ARCHITECTURE] = condition_test_architecture,
+ [CONDITION_FIRMWARE] = condition_test_firmware,
[CONDITION_NEEDS_UPDATE] = condition_test_needs_update,
[CONDITION_FIRST_BOOT] = condition_test_first_boot,
[CONDITION_USER] = condition_test_user,
@@ -949,6 +1026,7 @@ void condition_dump_list(Condition *first, FILE *f, const char *prefix, conditio
static const char* const condition_type_table[_CONDITION_TYPE_MAX] = {
[CONDITION_ARCHITECTURE] = "ConditionArchitecture",
+ [CONDITION_FIRMWARE] = "ConditionFirmware",
[CONDITION_VIRTUALIZATION] = "ConditionVirtualization",
[CONDITION_HOST] = "ConditionHost",
[CONDITION_KERNEL_COMMAND_LINE] = "ConditionKernelCommandLine",
@@ -981,6 +1059,7 @@ DEFINE_STRING_TABLE_LOOKUP(condition_type, ConditionType);
static const char* const assert_type_table[_CONDITION_TYPE_MAX] = {
[CONDITION_ARCHITECTURE] = "AssertArchitecture",
+ [CONDITION_FIRMWARE] = "AssertFirmware",
[CONDITION_VIRTUALIZATION] = "AssertVirtualization",
[CONDITION_HOST] = "AssertHost",
[CONDITION_KERNEL_COMMAND_LINE] = "AssertKernelCommandLine",
diff --git a/src/shared/condition.h b/src/shared/condition.h
index 75c430e9e0..6678689d7c 100644
--- a/src/shared/condition.h
+++ b/src/shared/condition.h
@@ -9,6 +9,7 @@
typedef enum ConditionType {
CONDITION_ARCHITECTURE,
+ CONDITION_FIRMWARE,
CONDITION_VIRTUALIZATION,
CONDITION_HOST,
CONDITION_KERNEL_COMMAND_LINE,
diff --git a/test/fuzz/fuzz-netdev-parser/directives.netdev b/test/fuzz/fuzz-netdev-parser/directives.netdev
index d4e5598409..8373ac7e89 100644
--- a/test/fuzz/fuzz-netdev-parser/directives.netdev
+++ b/test/fuzz/fuzz-netdev-parser/directives.netdev
@@ -22,6 +22,7 @@ Mode=
SourceMACAddress=
[Match]
Architecture=
+Firmware=
Host=
KernelVersion=
Virtualization=
diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network
index d6c1cc7f92..096ba9a8a9 100644
--- a/test/fuzz/fuzz-network-parser/directives.network
+++ b/test/fuzz/fuzz-network-parser/directives.network
@@ -18,6 +18,7 @@ KernelVersion=
Type=
Driver=
Architecture=
+Firmware=
Path=
WLANInterfaceType=
SSID=
diff --git a/test/fuzz/fuzz-unit-file/directives.service b/test/fuzz/fuzz-unit-file/directives.service
index 9fc83f04d2..73e1707c5c 100644
--- a/test/fuzz/fuzz-unit-file/directives.service
+++ b/test/fuzz/fuzz-unit-file/directives.service
@@ -36,6 +36,7 @@ CollectMode=
ConditionACPower=
ConditionArchitecture=
ConditionCPUs=
+ConditionFirmware=
ConditionCapability=
ConditionControlGroupController=
ConditionDirectoryNotEmpty=