Taking a stab at implementing #14479.
Add {Condition,Assert}CPUFeature to `systemd-analyze` & friends. Implement it
by executing the CPUID instruction. Add tables for common x86/i386
features.
Tested via unit tests + checked that commands such as:
```bash
systemd-analyze condition 'AssertCPUFeature = rdrand'
```
Succeed as expected and that commands such as
```bash
systemd-analyze condition 'AssertCPUFeature = foobar'
```
Fail as expected. Finally, I have amended the `systemd.unit` manual page
with the new condition and the list of all currently supported flags.
to the container and not the physically available ones.</para></listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>ConditionCPUFeature=</varname></term>
+
+ <listitem><para>Verify that a given CPU feature is available via the <literal>CPUID</literal>
+ instruction. This condition only does something on i386 and x86-64 processors. On other
+ processors it is assumed that the CPU does not support the given feature. It checks the leaves
+ <literal>1</literal>, <literal>7</literal>, <literal>0x80000001</literal>, and
+ <literal>0x80000007</literal>. Valid values are:
+ <literal>fpu</literal>,
+ <literal>vme</literal>,
+ <literal>de</literal>,
+ <literal>pse</literal>,
+ <literal>tsc</literal>,
+ <literal>msr</literal>,
+ <literal>pae</literal>,
+ <literal>mce</literal>,
+ <literal>cx8</literal>,
+ <literal>apic</literal>,
+ <literal>sep</literal>,
+ <literal>mtrr</literal>,
+ <literal>pge</literal>,
+ <literal>mca</literal>,
+ <literal>cmov</literal>,
+ <literal>pat</literal>,
+ <literal>pse36</literal>,
+ <literal>clflush</literal>,
+ <literal>mmx</literal>,
+ <literal>fxsr</literal>,
+ <literal>sse</literal>,
+ <literal>sse2</literal>,
+ <literal>ht</literal>,
+ <literal>pni</literal>,
+ <literal>pclmul</literal>,
+ <literal>monitor</literal>,
+ <literal>ssse3</literal>,
+ <literal>fma3</literal>,
+ <literal>cx16</literal>,
+ <literal>sse4_1</literal>,
+ <literal>sse4_2</literal>,
+ <literal>movbe</literal>,
+ <literal>popcnt</literal>,
+ <literal>aes</literal>,
+ <literal>xsave</literal>,
+ <literal>osxsave</literal>,
+ <literal>avx</literal>,
+ <literal>f16c</literal>,
+ <literal>rdrand</literal>,
+ <literal>bmi1</literal>,
+ <literal>avx2</literal>,
+ <literal>bmi2</literal>,
+ <literal>rdseed</literal>,
+ <literal>adx</literal>,
+ <literal>sha_ni</literal>,
+ <literal>syscall</literal>,
+ <literal>rdtscp</literal>,
+ <literal>lm</literal>,
+ <literal>lahf_lm</literal>,
+ <literal>abm</literal>,
+ <literal>constant_tsc</literal>.</para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><varname>AssertArchitecture=</varname></term>
<term><varname>AssertVirtualization=</varname></term>
return r == 0;
}
+#if defined(__i386__) || defined(__x86_64__)
+struct cpuid_table_entry {
+ uint32_t flag_bit;
+ const char *name;
+};
+
+static const struct cpuid_table_entry leaf1_edx[] = {
+ { 0, "fpu" },
+ { 1, "vme" },
+ { 2, "de" },
+ { 3, "pse" },
+ { 4, "tsc" },
+ { 5, "msr" },
+ { 6, "pae" },
+ { 7, "mce" },
+ { 8, "cx8" },
+ { 9, "apic" },
+ { 11, "sep" },
+ { 12, "mtrr" },
+ { 13, "pge" },
+ { 14, "mca" },
+ { 15, "cmov" },
+ { 16, "pat" },
+ { 17, "pse36" },
+ { 19, "clflush" },
+ { 23, "mmx" },
+ { 24, "fxsr" },
+ { 25, "sse" },
+ { 26, "sse2" },
+ { 28, "ht" },
+};
+
+static const struct cpuid_table_entry leaf1_ecx[] = {
+ { 0, "pni" },
+ { 1, "pclmul" },
+ { 3, "monitor" },
+ { 9, "ssse3" },
+ { 12, "fma3" },
+ { 13, "cx16" },
+ { 19, "sse4_1" },
+ { 20, "sse4_2" },
+ { 22, "movbe" },
+ { 23, "popcnt" },
+ { 25, "aes" },
+ { 26, "xsave" },
+ { 27, "osxsave" },
+ { 28, "avx" },
+ { 29, "f16c" },
+ { 30, "rdrand" },
+};
+
+static const struct cpuid_table_entry leaf7_ebx[] = {
+ { 3, "bmi1" },
+ { 5, "avx2" },
+ { 8, "bmi2" },
+ { 18, "rdseed" },
+ { 19, "adx" },
+ { 29, "sha_ni" },
+};
+
+static const struct cpuid_table_entry leaf81_edx[] = {
+ { 11, "syscall" },
+ { 27, "rdtscp" },
+ { 29, "lm" },
+};
+
+static const struct cpuid_table_entry leaf81_ecx[] = {
+ { 0, "lahf_lm" },
+ { 5, "abm" },
+};
+
+static const struct cpuid_table_entry leaf87_edx[] = {
+ { 8, "constant_tsc" },
+};
+
+static bool given_flag_in_set(const char *flag, const struct cpuid_table_entry *set, size_t set_size, uint32_t val) {
+ for (size_t i = 0; i < set_size; i++) {
+ if ((UINT32_C(1) << set[i].flag_bit) & val &&
+ streq(flag, set[i].name))
+ return true;
+ }
+ return false;
+}
+
+static bool real_has_cpu_with_flag(const char *flag) {
+ uint32_t eax, ebx, ecx, edx;
+
+ if (__get_cpuid(1, &eax, &ebx, &ecx, &edx)) {
+ if (given_flag_in_set(flag, leaf1_ecx, ELEMENTSOF(leaf1_ecx), ecx))
+ return true;
+
+ if (given_flag_in_set(flag, leaf1_edx, ELEMENTSOF(leaf1_edx), edx))
+ return true;
+ }
+
+ if (__get_cpuid(7, &eax, &ebx, &ecx, &edx)) {
+ if (given_flag_in_set(flag, leaf7_ebx, ELEMENTSOF(leaf7_ebx), ebx))
+ return true;
+ }
+
+ if (__get_cpuid(0x80000001U, &eax, &ebx, &ecx, &edx)) {
+ if (given_flag_in_set(flag, leaf81_ecx, ELEMENTSOF(leaf81_ecx), ecx))
+ return true;
+
+ if (given_flag_in_set(flag, leaf81_edx, ELEMENTSOF(leaf81_edx), edx))
+ return true;
+ }
+
+ if (__get_cpuid(0x80000007U, &eax, &ebx, &ecx, &edx))
+ if (given_flag_in_set(flag, leaf87_edx, ELEMENTSOF(leaf87_edx), edx))
+ return true;
+
+ return false;
+}
+#endif
+
+bool has_cpu_with_flag(const char *flag) {
+ /* CPUID is an x86 specific interface. Assume on all others that no CPUs have those flags. */
+#if defined(__i386__) || defined(__x86_64__)
+ return real_has_cpu_with_flag(flag);
+#else
+ return false;
+#endif
+}
+
static const char *const virtualization_table[_VIRTUALIZATION_MAX] = {
[VIRTUALIZATION_NONE] = "none",
[VIRTUALIZATION_KVM] = "kvm",
const char *virtualization_to_string(int v) _const_;
int virtualization_from_string(const char *s) _pure_;
+bool has_cpu_with_flag(const char *flag);
return path_is_read_only_fs(c->parameter) <= 0;
}
+static int condition_test_cpufeature(Condition *c, char **env) {
+ assert(c);
+ assert(c->parameter);
+ assert(c->type == CONDITION_CPU_FEATURE);
+
+ return has_cpu_with_flag(ascii_strlower(c->parameter));
+}
+
static int condition_test_path_is_encrypted(Condition *c, char **env) {
int r;
[CONDITION_CPUS] = condition_test_cpus,
[CONDITION_MEMORY] = condition_test_memory,
[CONDITION_ENVIRONMENT] = condition_test_environment,
+ [CONDITION_CPU_FEATURE] = condition_test_cpufeature,
};
int r, b;
[CONDITION_CPUS] = "ConditionCPUs",
[CONDITION_MEMORY] = "ConditionMemory",
[CONDITION_ENVIRONMENT] = "ConditionEnvironment",
+ [CONDITION_CPU_FEATURE] = "ConditionCPUFeature",
};
DEFINE_STRING_TABLE_LOOKUP(condition_type, ConditionType);
[CONDITION_CPUS] = "AssertCPUs",
[CONDITION_MEMORY] = "AssertMemory",
[CONDITION_ENVIRONMENT] = "AssertEnvironment",
+ [CONDITION_CPU_FEATURE] = "AssertCPUFeature",
};
DEFINE_STRING_TABLE_LOOKUP(assert_type, ConditionType);
CONDITION_MEMORY,
CONDITION_CPUS,
CONDITION_ENVIRONMENT,
+ CONDITION_CPU_FEATURE,
CONDITION_NEEDS_UPDATE,
CONDITION_FIRST_BOOT,
condition_free(condition);
}
+#if defined(__i386__) || defined(__x86_64__)
+static void test_condition_test_cpufeature(void) {
+ Condition *condition;
+
+ condition = condition_new(CONDITION_CPU_FEATURE, "fpu", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) > 0);
+ condition_free(condition);
+
+ condition = condition_new(CONDITION_CPU_FEATURE, "somecpufeaturethatreallydoesntmakesense", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) == 0);
+ condition_free(condition);
+
+ condition = condition_new(CONDITION_CPU_FEATURE, "a", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) == 0);
+ condition_free(condition);
+}
+#endif
+
static void test_condition_test_security(void) {
Condition *condition;
test_condition_test_cpus();
test_condition_test_memory();
test_condition_test_environment();
+#if defined(__i386__) || defined(__x86_64__)
+ test_condition_test_cpufeature();
+#endif
return 0;
}