| // SPDX-License-Identifier: GPL-2.0+ | 
|   | 
| /* | 
|  * Copyright 2018-2019 IBM Corporation. | 
|  */ | 
|   | 
| #define __SANE_USERSPACE_TYPES__ | 
|   | 
| #include <sys/types.h> | 
| #include <stdint.h> | 
| #include <malloc.h> | 
| #include <unistd.h> | 
| #include <stdlib.h> | 
| #include <string.h> | 
| #include <stdio.h> | 
| #include <sys/prctl.h> | 
| #include "utils.h" | 
|   | 
| #include "../pmu/event.h" | 
|   | 
|   | 
| extern void pattern_cache_loop(void); | 
| extern void indirect_branch_loop(void); | 
|   | 
| static int do_count_loop(struct event *events, bool is_p9, s64 *miss_percent) | 
| { | 
|     u64 pred, mpred; | 
|   | 
|     prctl(PR_TASK_PERF_EVENTS_ENABLE); | 
|   | 
|     if (is_p9) | 
|         pattern_cache_loop(); | 
|     else | 
|         indirect_branch_loop(); | 
|   | 
|     prctl(PR_TASK_PERF_EVENTS_DISABLE); | 
|   | 
|     event_read(&events[0]); | 
|     event_read(&events[1]); | 
|   | 
|     // We could scale all the events by running/enabled but we're lazy | 
|     // As long as the PMU is uncontended they should all run | 
|     FAIL_IF(events[0].result.running != events[0].result.enabled); | 
|     FAIL_IF(events[1].result.running != events[1].result.enabled); | 
|   | 
|     pred =  events[0].result.value; | 
|     mpred = events[1].result.value; | 
|   | 
|     if (is_p9) { | 
|         event_read(&events[2]); | 
|         event_read(&events[3]); | 
|         FAIL_IF(events[2].result.running != events[2].result.enabled); | 
|         FAIL_IF(events[3].result.running != events[3].result.enabled); | 
|   | 
|         pred  += events[2].result.value; | 
|         mpred += events[3].result.value; | 
|     } | 
|   | 
|     *miss_percent = 100 * mpred / pred; | 
|   | 
|     return 0; | 
| } | 
|   | 
| static void setup_event(struct event *e, u64 config, char *name) | 
| { | 
|     event_init_named(e, config, name); | 
|   | 
|     e->attr.disabled = 1; | 
|     e->attr.exclude_kernel = 1; | 
|     e->attr.exclude_hv = 1; | 
|     e->attr.exclude_idle = 1; | 
| } | 
|   | 
| enum spectre_v2_state { | 
|     VULNERABLE = 0, | 
|     UNKNOWN = 1,        // Works with FAIL_IF() | 
|     NOT_AFFECTED, | 
|     BRANCH_SERIALISATION, | 
|     COUNT_CACHE_DISABLED, | 
|     COUNT_CACHE_FLUSH_SW, | 
|     COUNT_CACHE_FLUSH_HW, | 
|     BTB_FLUSH, | 
| }; | 
|   | 
| static enum spectre_v2_state get_sysfs_state(void) | 
| { | 
|     enum spectre_v2_state state = UNKNOWN; | 
|     char buf[256]; | 
|     int len; | 
|   | 
|     memset(buf, 0, sizeof(buf)); | 
|     FAIL_IF(read_sysfs_file("devices/system/cpu/vulnerabilities/spectre_v2", buf, sizeof(buf))); | 
|   | 
|     // Make sure it's NULL terminated | 
|     buf[sizeof(buf) - 1] = '\0'; | 
|   | 
|     // Trim the trailing newline | 
|     len = strlen(buf); | 
|     FAIL_IF(len < 1); | 
|     buf[len - 1] = '\0'; | 
|   | 
|     printf("sysfs reports: '%s'\n", buf); | 
|   | 
|     // Order matters | 
|     if (strstr(buf, "Vulnerable")) | 
|         state = VULNERABLE; | 
|     else if (strstr(buf, "Not affected")) | 
|         state = NOT_AFFECTED; | 
|     else if (strstr(buf, "Indirect branch serialisation (kernel only)")) | 
|         state = BRANCH_SERIALISATION; | 
|     else if (strstr(buf, "Indirect branch cache disabled")) | 
|         state = COUNT_CACHE_DISABLED; | 
|     else if (strstr(buf, "Software count cache flush (hardware accelerated)")) | 
|         state = COUNT_CACHE_FLUSH_HW; | 
|     else if (strstr(buf, "Software count cache flush")) | 
|         state = COUNT_CACHE_FLUSH_SW; | 
|     else if (strstr(buf, "Branch predictor state flush")) | 
|         state = BTB_FLUSH; | 
|   | 
|     return state; | 
| } | 
|   | 
| #define PM_BR_PRED_CCACHE    0x040a4    // P8 + P9 | 
| #define PM_BR_MPRED_CCACHE    0x040ac    // P8 + P9 | 
| #define PM_BR_PRED_PCACHE    0x048a0    // P9 only | 
| #define PM_BR_MPRED_PCACHE    0x048b0    // P9 only | 
|   | 
| #define SPRN_PVR 287 | 
|   | 
| int spectre_v2_test(void) | 
| { | 
|     enum spectre_v2_state state; | 
|     struct event events[4]; | 
|     s64 miss_percent; | 
|     bool is_p9; | 
|   | 
|     // The PMU events we use only work on Power8 or later | 
|     SKIP_IF(!have_hwcap2(PPC_FEATURE2_ARCH_2_07)); | 
|   | 
|     state = get_sysfs_state(); | 
|     if (state == UNKNOWN) { | 
|         printf("Error: couldn't determine spectre_v2 mitigation state?\n"); | 
|         return -1; | 
|     } | 
|   | 
|     memset(events, 0, sizeof(events)); | 
|   | 
|     setup_event(&events[0], PM_BR_PRED_CCACHE,  "PM_BR_PRED_CCACHE"); | 
|     setup_event(&events[1], PM_BR_MPRED_CCACHE, "PM_BR_MPRED_CCACHE"); | 
|     FAIL_IF(event_open(&events[0])); | 
|     FAIL_IF(event_open_with_group(&events[1], events[0].fd) == -1); | 
|   | 
|     is_p9 = ((mfspr(SPRN_PVR) >>  16) & 0xFFFF) == 0x4e; | 
|   | 
|     if (is_p9) { | 
|         // Count pattern cache too | 
|         setup_event(&events[2], PM_BR_PRED_PCACHE,  "PM_BR_PRED_PCACHE"); | 
|         setup_event(&events[3], PM_BR_MPRED_PCACHE, "PM_BR_MPRED_PCACHE"); | 
|   | 
|         FAIL_IF(event_open_with_group(&events[2], events[0].fd) == -1); | 
|         FAIL_IF(event_open_with_group(&events[3], events[0].fd) == -1); | 
|     } | 
|   | 
|     FAIL_IF(do_count_loop(events, is_p9, &miss_percent)); | 
|   | 
|     event_report_justified(&events[0], 18, 10); | 
|     event_report_justified(&events[1], 18, 10); | 
|     event_close(&events[0]); | 
|     event_close(&events[1]); | 
|   | 
|     if (is_p9) { | 
|         event_report_justified(&events[2], 18, 10); | 
|         event_report_justified(&events[3], 18, 10); | 
|         event_close(&events[2]); | 
|         event_close(&events[3]); | 
|     } | 
|   | 
|     printf("Miss percent %lld %%\n", miss_percent); | 
|   | 
|     switch (state) { | 
|     case VULNERABLE: | 
|     case NOT_AFFECTED: | 
|     case COUNT_CACHE_FLUSH_SW: | 
|     case COUNT_CACHE_FLUSH_HW: | 
|         // These should all not affect userspace branch prediction | 
|         if (miss_percent > 15) { | 
|             printf("Branch misses > 15%% unexpected in this configuration!\n"); | 
|             printf("Possible mis-match between reported & actual mitigation\n"); | 
|             /* | 
|              * Such a mismatch may be caused by a guest system | 
|              * reporting as vulnerable when the host is mitigated. | 
|              * Return skip code to avoid detecting this as an error. | 
|              * We are not vulnerable and reporting otherwise, so | 
|              * missing such a mismatch is safe. | 
|              */ | 
|             if (miss_percent > 95) | 
|                 return 4; | 
|   | 
|             return 1; | 
|         } | 
|         break; | 
|     case BRANCH_SERIALISATION: | 
|         // This seems to affect userspace branch prediction a bit? | 
|         if (miss_percent > 25) { | 
|             printf("Branch misses > 25%% unexpected in this configuration!\n"); | 
|             printf("Possible mis-match between reported & actual mitigation\n"); | 
|             return 1; | 
|         } | 
|         break; | 
|     case COUNT_CACHE_DISABLED: | 
|         if (miss_percent < 95) { | 
|             printf("Branch misses < 20%% unexpected in this configuration!\n"); | 
|             printf("Possible mis-match between reported & actual mitigation\n"); | 
|             return 1; | 
|         } | 
|         break; | 
|     case UNKNOWN: | 
|     case BTB_FLUSH: | 
|         printf("Not sure!\n"); | 
|         return 1; | 
|     } | 
|   | 
|     printf("OK - Measured branch prediction rates match reported spectre v2 mitigation.\n"); | 
|   | 
|     return 0; | 
| } | 
|   | 
| int main(int argc, char *argv[]) | 
| { | 
|     return test_harness(spectre_v2_test, "spectre_v2"); | 
| } |