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 + + <command>systemd-analyze cpu-features <optional><replaceable>NAME</replaceable>...</optional></command> + + 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 ✓ +… + + + <command>systemd-analyze smbios11</command> 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