diff --git a/man/systemd-analyze.xml b/man/systemd-analyze.xml
index a7f438343f..52d0545baf 100644
--- a/man/systemd-analyze.xml
+++ b/man/systemd-analyze.xml
@@ -224,6 +224,12 @@
architectures
NAME
+
+ systemd-analyze
+ OPTIONS
+ cpu-features
+ NAME
+
systemd-analyze
OPTIONS
@@ -1159,6 +1165,30 @@ x86-64 native
+
+ systemd-analyze cpu-features NAME...
+
+ Lists all known CPU features for the system's CPU architecture, indicating which of them are
+ supported. The listed feature names are those understood by ConditionCPUFeature=;
+ see systemd.unit5
+ for details. If one or more feature names are specified, only those are listed.
+
+ The command is currently supported only on arm64, x86, and x86_64 systems.
+
+
+ Table output on an x86_64 system
+ $ systemd-analyze cpu-features
+NAME SUPPORT
+abm ✓
+adx ✓
+aes ✓
+apic ✓
+avx ✓
+avx2 ✓
+…
+
+
+
systemd-analyze smbios11
diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml
index 54d2496189..7106c0467b 100644
--- a/man/systemd.unit.xml
+++ b/man/systemd.unit.xml
@@ -2058,6 +2058,11 @@
among others. For the full list, see the documentation on
ARM64 ELF hwcaps.
+ See
+ systemd-analyze1's
+ cpu-features command for the complete list of known CPU features for the system.
+
+
To avoid ambiguities in mixed-architecture fleets, the value can be prefixed
with the architecture name using the form ARCH.FEATURE. If the
architecture prefix does not match the running system, the condition evaluates
diff --git a/shell-completion/bash/systemd-analyze b/shell-completion/bash/systemd-analyze
index 04b4e56e12..4ba660bb82 100644
--- a/shell-completion/bash/systemd-analyze
+++ b/shell-completion/bash/systemd-analyze
@@ -56,6 +56,10 @@ __get_architectures() {
systemd-analyze --no-legend --no-pager architectures 2>/dev/null | { while read -r a b; do echo " $a"; done; }
}
+__get_cpu_features() {
+ systemd-analyze --no-legend --no-pager cpu-features 2>/dev/null | { while read -r a b; do echo " $a"; done; }
+}
+
__get_filesystem_sets() {
local line
systemd-analyze filesystems --no-pager 2>/dev/null | while IFS= read -r line; do
@@ -91,6 +95,7 @@ _systemd_analyze() {
[DLOPEN_METADATA]='dlopen-metadata'
[PLOT]='plot'
[ARCHITECTURES]='architectures'
+ [CPU_FEATURES]='cpu-features'
[FDSTORE]='fdstore'
[CAPABILITY]='capability'
[TRANSIENT_SETTINGS]='transient-settings'
@@ -250,6 +255,13 @@ _systemd_analyze() {
comps=$( __get_architectures )
fi
+ elif __contains_word "$verb" ${VERBS[CPU_FEATURES]}; then
+ if [[ $cur = -* ]]; then
+ comps='--help --version --no-pager --json=off --json=pretty --json=short --no-legend'
+ else
+ comps=$( __get_cpu_features )
+ fi
+
elif __contains_word "$verb" ${VERBS[FDSTORE]}; then
if [[ $cur = -* ]]; then
comps='--help --version --system --user --global -H --host -M --machine --no-pager --json=off --json=pretty --json=short --root --image'
diff --git a/shell-completion/zsh/_systemd-analyze b/shell-completion/zsh/_systemd-analyze
index e87af915d7..a802febdc4 100644
--- a/shell-completion/zsh/_systemd-analyze
+++ b/shell-completion/zsh/_systemd-analyze
@@ -52,6 +52,13 @@
_describe -t architectures 'architectures' _architectures || compadd "$@"
}
+(( $+functions[_systemd-analyze_cpu-features] )) ||
+ _systemd-analyze_cpu-features() {
+ local -a _features
+ _features=( $(systemd-analyze --quiet --no-pager --no-legend cpu-features 2>/dev/null | { while read -r a b; do echo " $a"; done; }) )
+ _describe -t features 'cpu features' _features || compadd "$@"
+ }
+
(( $+functions[_systemd-analyze_plot] )) ||
_systemd-analyze_plot() {
local -a _options
@@ -95,6 +102,7 @@
'has-tpm2:Report whether TPM2 support is available'
'transient-settings:List transient settings for unit types'
'architectures:List known architectures'
+ 'cpu-features:List known CPU features'
'smbios11:List strings passed via SMBIOS Type #11'
'chid:List local CHIDs'
'compare-versions:Compare two version strings'
diff --git a/src/analyze/analyze-cpu-features.c b/src/analyze/analyze-cpu-features.c
new file mode 100644
index 0000000000..3fe3af9719
--- /dev/null
+++ b/src/analyze/analyze-cpu-features.c
@@ -0,0 +1,77 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "analyze.h"
+#include "analyze-cpu-features.h"
+#include "ansi-color.h"
+#include "architecture.h"
+#include "format-table.h"
+#include "log.h"
+#include "string-util.h"
+#include "strv.h"
+#include "virt.h"
+
+static int add_feature(Table *table, const char *feature, bool known) {
+ int r;
+
+ assert(table);
+ assert(feature);
+
+ if (known) {
+ bool has = has_cpu_with_flag(feature);
+
+ r = table_add_many(table,
+ TABLE_STRING, feature,
+ TABLE_BOOLEAN_CHECKMARK, has,
+ TABLE_SET_COLOR, ansi_highlight_green_red(has));
+ } else
+ r = table_add_many(table,
+ TABLE_STRING, feature,
+ TABLE_TRISTATE, -1,
+ TABLE_SET_COLOR, ansi_grey());
+ if (r < 0)
+ return table_log_add_error(r);
+
+ return 0;
+}
+
+int verb_cpu_features(int argc, char *argv[], uintptr_t _data, void *userdata) {
+ int r;
+
+ _cleanup_strv_free_ char **features = NULL;
+ r = cpu_known_features(&features);
+ if (r < 0)
+ return log_oom();
+
+ if (strv_isempty(features))
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "No known CPU features for the %s architecture.",
+ architecture_to_string(native_architecture()));
+
+ _cleanup_(table_unrefp) Table *table = table_new("name", "support");
+ if (!table)
+ return log_oom();
+
+ (void) table_set_sort(table, (size_t) 0);
+ table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
+
+ char **args = strv_skip(argv, 1);
+ if (args)
+ STRV_FOREACH(feat, args) {
+ _cleanup_free_ char *lower = strdup(*feat);
+ if (!lower)
+ return log_oom();
+
+ ascii_strlower(lower);
+ r = add_feature(table, lower, strv_contains(features, lower));
+ if (r < 0)
+ return r;
+ }
+ else
+ STRV_FOREACH(feat, features) {
+ r = add_feature(table, *feat, /* known= */ true);
+ if (r < 0)
+ return r;
+ }
+
+ return table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend);
+}
diff --git a/src/analyze/analyze-cpu-features.h b/src/analyze/analyze-cpu-features.h
new file mode 100644
index 0000000000..66c46ce501
--- /dev/null
+++ b/src/analyze/analyze-cpu-features.h
@@ -0,0 +1,6 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "forward.h"
+
+int verb_cpu_features(int argc, char *argv[], uintptr_t _data, void *userdata);
diff --git a/src/analyze/analyze.c b/src/analyze/analyze.c
index a2db0061ec..ffc0c2391c 100644
--- a/src/analyze/analyze.c
+++ b/src/analyze/analyze.c
@@ -20,6 +20,7 @@
#include "analyze-chid.h"
#include "analyze-compare-versions.h"
#include "analyze-condition.h"
+#include "analyze-cpu-features.h"
#include "analyze-critical-chain.h"
#include "analyze-dlopen-metadata.h"
#include "analyze-dot.h"
@@ -243,6 +244,8 @@ VERB_SCOPE(, verb_filesystems, "filesystems", "[NAME...]\0", VERB_ANY, VERB_ANY,
"List known filesystems");
VERB_SCOPE(, verb_architectures, "architectures", "[NAME...]\0", VERB_ANY, VERB_ANY, 0,
"List known architectures");
+VERB_SCOPE(, verb_cpu_features, "cpu-features", "[NAME...]\0", VERB_ANY, VERB_ANY, 0,
+ "List known CPU features");
VERB_SCOPE(, verb_smbios11, "smbios11", NULL, VERB_ANY, 1, 0,
"List strings passed via SMBIOS Type #11");
VERB_SCOPE(, verb_chid, "chid", NULL, VERB_ANY, VERB_ANY, 0,
@@ -578,9 +581,12 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) {
"Option --offline= requires one or more units to perform a security review.");
if (arg_json_format_flags != SD_JSON_FORMAT_OFF &&
- !STRPTR_IN_SET(verb, "security", "inspect-elf", "dlopen-metadata", "plot", "fdstore", "pcrs", "nvpcrs", "architectures", "capability", "exit-status", "chid", "blame", "identify-tpm2"))
+ !STRPTR_IN_SET(verb,
+ "security", "inspect-elf", "dlopen-metadata", "plot", "fdstore", "pcrs", "nvpcrs",
+ "architectures", "cpu-features", "capability", "exit-status", "chid", "blame",
+ "identify-tpm2"))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "Option --json= is only supported for security, inspect-elf, dlopen-metadata, plot, fdstore, pcrs, nvpcrs, architectures, capability, exit-status, chid, blame, identify-tpm2 right now.");
+ "Option --json= is only supported for security, inspect-elf, dlopen-metadata, plot, fdstore, pcrs, nvpcrs, architectures, cpu-features, capability, exit-status, chid, blame, identify-tpm2 right now.");
if (arg_threshold != 100 && !streq_ptr(verb, "security"))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
diff --git a/src/analyze/meson.build b/src/analyze/meson.build
index 1f40ba8559..d5874e86a1 100644
--- a/src/analyze/meson.build
+++ b/src/analyze/meson.build
@@ -9,6 +9,7 @@ systemd_analyze_sources = files(
'analyze-chid.c',
'analyze-compare-versions.c',
'analyze-condition.c',
+ 'analyze-cpu-features.c',
'analyze-critical-chain.c',
'analyze-dlopen-metadata.c',
'analyze-dot.c',
diff --git a/src/basic/virt.c b/src/basic/virt.c
index 6ccb015e52..2510638ceb 100644
--- a/src/basic/virt.c
+++ b/src/basic/virt.c
@@ -1137,6 +1137,51 @@ bool has_cpu_with_flag(const char *flag) {
#endif
}
+int cpu_known_features(char ***ret) {
+ assert(ret);
+
+#if defined(__i386__) || defined(__x86_64__)
+ static const struct {
+ const struct cpuid_table_entry *table;
+ size_t n_entries;
+ } tables[] = {
+ { leaf1_edx, ELEMENTSOF(leaf1_edx), },
+ { leaf1_ecx, ELEMENTSOF(leaf1_ecx), },
+ { leaf7_ebx, ELEMENTSOF(leaf7_ebx), },
+ { leaf81_ecx, ELEMENTSOF(leaf81_ecx), },
+ { leaf81_edx, ELEMENTSOF(leaf81_edx), },
+ { leaf87_edx, ELEMENTSOF(leaf87_edx), },
+ };
+#elif defined(__aarch64__)
+ static const struct {
+ const struct hwcap_table_entry *table;
+ size_t n_entries;
+ } tables[] = {
+ { hwcap, ELEMENTSOF(hwcap), },
+ { hwcap2, ELEMENTSOF(hwcap2), },
+ { hwcap3, ELEMENTSOF(hwcap3), },
+ };
+#endif
+
+#if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__)
+ _cleanup_strv_free_ char **features = NULL;
+ size_t n = 0;
+ int r;
+
+ FOREACH_ELEMENT(table, tables)
+ FOREACH_ARRAY(i, table->table, table->n_entries) {
+ r = strv_extend_with_size(&features, &n, i->name);
+ if (r < 0)
+ return r;
+ }
+
+ *ret = TAKE_PTR(features);
+#else
+ *ret = NULL;
+#endif
+ return 0;
+}
+
static const char *const virtualization_table[_VIRTUALIZATION_MAX] = {
[VIRTUALIZATION_NONE] = "none",
[VIRTUALIZATION_KVM] = "kvm",
diff --git a/src/basic/virt.h b/src/basic/virt.h
index fcb3cdc5ae..e867486bbd 100644
--- a/src/basic/virt.h
+++ b/src/basic/virt.h
@@ -64,3 +64,4 @@ int running_in_chroot(void);
DECLARE_STRING_TABLE_LOOKUP(virtualization, Virtualization);
bool has_cpu_with_flag(const char *flag);
+int cpu_known_features(char ***ret);
diff --git a/test/units/TEST-65-ANALYZE.sh b/test/units/TEST-65-ANALYZE.sh
index 05a7b7f0c1..d38534fe12 100755
--- a/test/units/TEST-65-ANALYZE.sh
+++ b/test/units/TEST-65-ANALYZE.sh
@@ -1125,6 +1125,16 @@ systemd-analyze architectures x86-64
systemd-analyze architectures native
systemd-analyze architectures uname
+if systemd-analyze --no-legend architectures native | grep -E '^(x86|x86-64|arm64|arm64-be)[[:space:]]' >/dev/null; then
+ rc=0
+else
+ rc=1
+fi
+assert_rc "$rc" systemd-analyze cpu-features
+assert_rc "$rc" systemd-analyze cpu-features --json=pretty
+assert_rc "$rc" systemd-analyze cpu-features foo hoge fpu fp
+assert_rc "$rc" systemd-analyze cpu-features --json=pretty foo hoge fpu fp avx
+
systemd-analyze smbios11
systemd-analyze smbios11 -q