]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
condition: add CPUFeature
authorGiedrius Statkevičius <giedriuswork@gmail.com>
Wed, 11 Nov 2020 20:45:58 +0000 (22:45 +0200)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Wed, 17 Feb 2021 06:31:29 +0000 (15:31 +0900)
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.

man/systemd.unit.xml
src/basic/virt.c
src/basic/virt.h
src/shared/condition.c
src/shared/condition.h
src/test/test-condition.c

index cc9eedd60b2506602d19f8fa761c263e5c9ceb64..da350844bd774b025fc60b6c82beca449eca4393 100644 (file)
           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>
index 6160b852dc6b9366783667cae684279e615a71e5..335f59d6fc60c8c9de3e973c03d84ac7142c2c6c 100644 (file)
@@ -786,6 +786,131 @@ int running_in_chroot(void) {
         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",
index 03aa1a72be3cc1a8187fd4292d90da8bf66aa3ba..378c7c4d232a5b84ec8f2e4ea9baa944e04d1f42 100644 (file)
@@ -61,3 +61,4 @@ int running_in_chroot(void);
 
 const char *virtualization_to_string(int v) _const_;
 int virtualization_from_string(const char *s) _pure_;
+bool has_cpu_with_flag(const char *flag);
index 485b3bab39e58607d7a49a0df964548eb27fd3e3..1e63956c05f1b1782135537722e885bc1a82acb7 100644 (file)
@@ -756,6 +756,14 @@ static int condition_test_path_is_read_write(Condition *c, char **env) {
         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;
 
@@ -834,6 +842,7 @@ int condition_test(Condition *c, char **env) {
                 [CONDITION_CPUS]                     = condition_test_cpus,
                 [CONDITION_MEMORY]                   = condition_test_memory,
                 [CONDITION_ENVIRONMENT]              = condition_test_environment,
+                [CONDITION_CPU_FEATURE]              = condition_test_cpufeature,
         };
 
         int r, b;
@@ -956,6 +965,7 @@ static const char* const condition_type_table[_CONDITION_TYPE_MAX] = {
         [CONDITION_CPUS] = "ConditionCPUs",
         [CONDITION_MEMORY] = "ConditionMemory",
         [CONDITION_ENVIRONMENT] = "ConditionEnvironment",
+        [CONDITION_CPU_FEATURE] = "ConditionCPUFeature",
 };
 
 DEFINE_STRING_TABLE_LOOKUP(condition_type, ConditionType);
@@ -987,6 +997,7 @@ static const char* const assert_type_table[_CONDITION_TYPE_MAX] = {
         [CONDITION_CPUS] = "AssertCPUs",
         [CONDITION_MEMORY] = "AssertMemory",
         [CONDITION_ENVIRONMENT] = "AssertEnvironment",
+        [CONDITION_CPU_FEATURE] = "AssertCPUFeature",
 };
 
 DEFINE_STRING_TABLE_LOOKUP(assert_type, ConditionType);
index 785349029075eebef85946e9a8eda57f6c093a41..75c430e9e07211aad4b5708b393519bcadf7adc2 100644 (file)
@@ -19,6 +19,7 @@ typedef enum ConditionType {
         CONDITION_MEMORY,
         CONDITION_CPUS,
         CONDITION_ENVIRONMENT,
+        CONDITION_CPU_FEATURE,
 
         CONDITION_NEEDS_UPDATE,
         CONDITION_FIRST_BOOT,
index 15099d8df8b521cba8c320594d6ce3d6cec4fe33..25483d015b3dc9b473808920076efef807051b3a 100644 (file)
@@ -439,6 +439,27 @@ static void test_condition_test_kernel_version(void) {
         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;
 
@@ -864,6 +885,9 @@ int main(int argc, char *argv[]) {
         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;
 }