1f08c3bdfSopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 2f08c3bdfSopenharmony_ci/* Copyright (c) 2021 SUSE LLC <rpalethorpe@suse.com> */ 3f08c3bdfSopenharmony_ci/*\ 4f08c3bdfSopenharmony_ci * [Description] 5f08c3bdfSopenharmony_ci * 6f08c3bdfSopenharmony_ci * Check that something (e.g. irqbalance daemon) is performing IRQ 7f08c3bdfSopenharmony_ci * load balancing. 8f08c3bdfSopenharmony_ci * 9f08c3bdfSopenharmony_ci * On many systems userland needs to set /proc/irq/$IRQ/smp_affinity 10f08c3bdfSopenharmony_ci * to prevent many IRQs being delivered to the same CPU. 11f08c3bdfSopenharmony_ci * 12f08c3bdfSopenharmony_ci * Note some drivers and IRQ controllers will distribute IRQs 13f08c3bdfSopenharmony_ci * evenly. Some systems will have housekeeping CPUs configured. Some 14f08c3bdfSopenharmony_ci * IRQs can not be masked etc. So this test is not appropriate for all 15f08c3bdfSopenharmony_ci * scenarios. 16f08c3bdfSopenharmony_ci * 17f08c3bdfSopenharmony_ci * Furthermore, exactly how IRQs should be distributed is a 18f08c3bdfSopenharmony_ci * performance and/or security issue. This is only a generic smoke 19f08c3bdfSopenharmony_ci * test. It will hopefully detect misconfigured systems and total 20f08c3bdfSopenharmony_ci * balancing failures which are often silent errors. 21f08c3bdfSopenharmony_ci * 22f08c3bdfSopenharmony_ci * Heuristic: Evidence of Change 23f08c3bdfSopenharmony_ci * 24f08c3bdfSopenharmony_ci * 1. Find IRQs with a non-zero count 25f08c3bdfSopenharmony_ci * 2. Check if they are now disallowed 26f08c3bdfSopenharmony_ci * 27f08c3bdfSopenharmony_ci * There are two sources of information we need to parse: 28f08c3bdfSopenharmony_ci * 29f08c3bdfSopenharmony_ci * 1. /proc/interrupts 30f08c3bdfSopenharmony_ci * 2. /proc/irq/$IRQ/smp_affinity 31f08c3bdfSopenharmony_ci * 32f08c3bdfSopenharmony_ci * We get the active IRQs and CPUs from /proc/interrupts. It also 33f08c3bdfSopenharmony_ci * contains the per-CPU IRQ counts and info we do not care about. 34f08c3bdfSopenharmony_ci * 35f08c3bdfSopenharmony_ci * We get the IRQ masks from each active IRQ's smp_affinity file. This 36f08c3bdfSopenharmony_ci * is a bitmask written out in hexadecimal format. It shows which CPUs 37f08c3bdfSopenharmony_ci * an IRQ may be received by. 38f08c3bdfSopenharmony_ci */ 39f08c3bdfSopenharmony_ci 40f08c3bdfSopenharmony_ci#include <stdlib.h> 41f08c3bdfSopenharmony_ci 42f08c3bdfSopenharmony_ci#include "tst_test.h" 43f08c3bdfSopenharmony_ci#include "tst_safe_stdio.h" 44f08c3bdfSopenharmony_ci#include "tst_safe_file_at.h" 45f08c3bdfSopenharmony_ci 46f08c3bdfSopenharmony_cienum affinity { 47f08c3bdfSopenharmony_ci ALLOW = '+', 48f08c3bdfSopenharmony_ci DENY = '-', 49f08c3bdfSopenharmony_ci}; 50f08c3bdfSopenharmony_ci 51f08c3bdfSopenharmony_cistatic unsigned int *irq_stats; 52f08c3bdfSopenharmony_cistatic enum affinity *irq_affinity; 53f08c3bdfSopenharmony_ci 54f08c3bdfSopenharmony_cistatic unsigned int nr_cpus; 55f08c3bdfSopenharmony_cistatic unsigned int nr_irqs; 56f08c3bdfSopenharmony_cistatic unsigned int *irq_ids; 57f08c3bdfSopenharmony_ci 58f08c3bdfSopenharmony_cistatic char *read_proc_file(const char *const path, size_t *const len_out) 59f08c3bdfSopenharmony_ci{ 60f08c3bdfSopenharmony_ci const size_t pg_len = SAFE_SYSCONF(_SC_PAGESIZE); 61f08c3bdfSopenharmony_ci int fd = SAFE_OPEN(path, O_RDONLY); 62f08c3bdfSopenharmony_ci size_t ret = 0, used_len = 0; 63f08c3bdfSopenharmony_ci static size_t total_len; 64f08c3bdfSopenharmony_ci static char *buf; 65f08c3bdfSopenharmony_ci 66f08c3bdfSopenharmony_ci do { 67f08c3bdfSopenharmony_ci if (used_len + 1 >= total_len) { 68f08c3bdfSopenharmony_ci total_len += pg_len; 69f08c3bdfSopenharmony_ci buf = SAFE_REALLOC(buf, total_len); 70f08c3bdfSopenharmony_ci } 71f08c3bdfSopenharmony_ci 72f08c3bdfSopenharmony_ci ret = SAFE_READ(0, fd, 73f08c3bdfSopenharmony_ci buf + used_len, 74f08c3bdfSopenharmony_ci total_len - used_len - 1); 75f08c3bdfSopenharmony_ci used_len += ret; 76f08c3bdfSopenharmony_ci } while (ret); 77f08c3bdfSopenharmony_ci 78f08c3bdfSopenharmony_ci if (!used_len) 79f08c3bdfSopenharmony_ci tst_brk(TBROK, "Empty %s?", path); 80f08c3bdfSopenharmony_ci 81f08c3bdfSopenharmony_ci buf[used_len] = '\0'; 82f08c3bdfSopenharmony_ci 83f08c3bdfSopenharmony_ci SAFE_CLOSE(fd); 84f08c3bdfSopenharmony_ci 85f08c3bdfSopenharmony_ci if (len_out) 86f08c3bdfSopenharmony_ci *len_out = used_len; 87f08c3bdfSopenharmony_ci return buf; 88f08c3bdfSopenharmony_ci} 89f08c3bdfSopenharmony_ci 90f08c3bdfSopenharmony_cistatic void collect_irq_info(void) 91f08c3bdfSopenharmony_ci{ 92f08c3bdfSopenharmony_ci char *buf, *c, *first_row; 93f08c3bdfSopenharmony_ci char path[PATH_MAX]; 94f08c3bdfSopenharmony_ci size_t row, col, len; 95f08c3bdfSopenharmony_ci long acc; 96f08c3bdfSopenharmony_ci unsigned int cpu_total, bit; 97f08c3bdfSopenharmony_ci 98f08c3bdfSopenharmony_ci nr_cpus = 0; 99f08c3bdfSopenharmony_ci nr_irqs = 0; 100f08c3bdfSopenharmony_ci 101f08c3bdfSopenharmony_ci buf = read_proc_file("/proc/interrupts", NULL); 102f08c3bdfSopenharmony_ci 103f08c3bdfSopenharmony_ci /* Count CPUs, header columns are like /CPU[0-9]+/ */ 104f08c3bdfSopenharmony_ci for (c = buf; *c != '\0' && *c != '\n'; c++) { 105f08c3bdfSopenharmony_ci if (!strncmp(c, "CPU", 3)) 106f08c3bdfSopenharmony_ci nr_cpus++; 107f08c3bdfSopenharmony_ci } 108f08c3bdfSopenharmony_ci 109f08c3bdfSopenharmony_ci c++; 110f08c3bdfSopenharmony_ci first_row = c; 111f08c3bdfSopenharmony_ci /* Count IRQs, real IRQs start with /[0-9]+:/ */ 112f08c3bdfSopenharmony_ci while (*c != '\0') { 113f08c3bdfSopenharmony_ci switch (*c) { 114f08c3bdfSopenharmony_ci case ' ': 115f08c3bdfSopenharmony_ci case '\t': 116f08c3bdfSopenharmony_ci case '\n': 117f08c3bdfSopenharmony_ci case '0' ... '9': 118f08c3bdfSopenharmony_ci c++; 119f08c3bdfSopenharmony_ci break; 120f08c3bdfSopenharmony_ci case ':': 121f08c3bdfSopenharmony_ci nr_irqs++; 122f08c3bdfSopenharmony_ci /* fall-through */ 123f08c3bdfSopenharmony_ci default: 124f08c3bdfSopenharmony_ci while (*c != '\n' && *c != '\0') 125f08c3bdfSopenharmony_ci c++; 126f08c3bdfSopenharmony_ci } 127f08c3bdfSopenharmony_ci } 128f08c3bdfSopenharmony_ci 129f08c3bdfSopenharmony_ci tst_res(TINFO, "Found %u CPUS, %u IRQs", nr_cpus, nr_irqs); 130f08c3bdfSopenharmony_ci 131f08c3bdfSopenharmony_ci irq_ids = SAFE_REALLOC(irq_ids, nr_irqs * sizeof(*irq_ids)); 132f08c3bdfSopenharmony_ci irq_stats = SAFE_REALLOC(irq_stats, 133f08c3bdfSopenharmony_ci nr_cpus * (nr_irqs + 1) * sizeof(*irq_stats)); 134f08c3bdfSopenharmony_ci irq_affinity = SAFE_REALLOC(irq_affinity, 135f08c3bdfSopenharmony_ci nr_cpus * nr_irqs * sizeof(*irq_affinity)); 136f08c3bdfSopenharmony_ci 137f08c3bdfSopenharmony_ci c = first_row; 138f08c3bdfSopenharmony_ci acc = -1; 139f08c3bdfSopenharmony_ci row = col = 0; 140f08c3bdfSopenharmony_ci /* Parse columns containing IRQ counts and IRQ IDs into acc. Ignore 141f08c3bdfSopenharmony_ci * everything else. 142f08c3bdfSopenharmony_ci */ 143f08c3bdfSopenharmony_ci while (*c != '\0') { 144f08c3bdfSopenharmony_ci switch (*c) { 145f08c3bdfSopenharmony_ci case ' ': 146f08c3bdfSopenharmony_ci case '\t': 147f08c3bdfSopenharmony_ci if (acc >= 0) { 148f08c3bdfSopenharmony_ci irq_stats[row * nr_cpus + col] = acc; 149f08c3bdfSopenharmony_ci acc = -1; 150f08c3bdfSopenharmony_ci col++; 151f08c3bdfSopenharmony_ci } 152f08c3bdfSopenharmony_ci break; 153f08c3bdfSopenharmony_ci case '\n': 154f08c3bdfSopenharmony_ci if (acc != -1) 155f08c3bdfSopenharmony_ci tst_brk(TBROK, "Unexpected EOL"); 156f08c3bdfSopenharmony_ci col = 0; 157f08c3bdfSopenharmony_ci row++; 158f08c3bdfSopenharmony_ci break; 159f08c3bdfSopenharmony_ci case '0' ... '9': 160f08c3bdfSopenharmony_ci if (acc == -1) 161f08c3bdfSopenharmony_ci acc = 0; 162f08c3bdfSopenharmony_ci 163f08c3bdfSopenharmony_ci acc *= 10; 164f08c3bdfSopenharmony_ci acc += *c - '0'; 165f08c3bdfSopenharmony_ci break; 166f08c3bdfSopenharmony_ci case ':': 167f08c3bdfSopenharmony_ci if (acc == -1 || col != 0) 168f08c3bdfSopenharmony_ci tst_brk(TBROK, "Unexpected ':'"); 169f08c3bdfSopenharmony_ci irq_ids[row] = acc; 170f08c3bdfSopenharmony_ci acc = -1; 171f08c3bdfSopenharmony_ci break; 172f08c3bdfSopenharmony_ci default: 173f08c3bdfSopenharmony_ci acc = -1; 174f08c3bdfSopenharmony_ci while (*c != '\n' && *c != '\0') 175f08c3bdfSopenharmony_ci c++; 176f08c3bdfSopenharmony_ci continue; 177f08c3bdfSopenharmony_ci } 178f08c3bdfSopenharmony_ci 179f08c3bdfSopenharmony_ci c++; 180f08c3bdfSopenharmony_ci } 181f08c3bdfSopenharmony_ci 182f08c3bdfSopenharmony_ci for (col = 0; col < nr_cpus; col++) { 183f08c3bdfSopenharmony_ci cpu_total = 0; 184f08c3bdfSopenharmony_ci 185f08c3bdfSopenharmony_ci for (row = 0; row < nr_irqs; row++) 186f08c3bdfSopenharmony_ci cpu_total += irq_stats[row * nr_cpus + col]; 187f08c3bdfSopenharmony_ci 188f08c3bdfSopenharmony_ci irq_stats[row * nr_cpus + col] = cpu_total; 189f08c3bdfSopenharmony_ci } 190f08c3bdfSopenharmony_ci 191f08c3bdfSopenharmony_ci /* Read the CPU affinity masks for each IRQ. The first CPU is in the 192f08c3bdfSopenharmony_ci * right most (least significant) bit. See bitmap_string() in the kernel 193f08c3bdfSopenharmony_ci * (%*pb) 194f08c3bdfSopenharmony_ci */ 195f08c3bdfSopenharmony_ci for (row = 0; row < nr_irqs; row++) { 196f08c3bdfSopenharmony_ci sprintf(path, "/proc/irq/%u/smp_affinity", irq_ids[row]); 197f08c3bdfSopenharmony_ci buf = read_proc_file(path, &len); 198f08c3bdfSopenharmony_ci c = buf + len; 199f08c3bdfSopenharmony_ci col = 0; 200f08c3bdfSopenharmony_ci 201f08c3bdfSopenharmony_ci while (--c >= buf) { 202f08c3bdfSopenharmony_ci if (col > nr_cpus) { 203f08c3bdfSopenharmony_ci tst_res(TINFO, "%u/smp_affnity: %s", 204f08c3bdfSopenharmony_ci irq_ids[row], buf); 205f08c3bdfSopenharmony_ci tst_brk(TBROK, "More mask char bits than cpus"); 206f08c3bdfSopenharmony_ci } 207f08c3bdfSopenharmony_ci 208f08c3bdfSopenharmony_ci switch (*c) { 209f08c3bdfSopenharmony_ci case '\n': 210f08c3bdfSopenharmony_ci case ' ': 211f08c3bdfSopenharmony_ci case ',': 212f08c3bdfSopenharmony_ci continue; 213f08c3bdfSopenharmony_ci case '0' ... '9': 214f08c3bdfSopenharmony_ci acc = *c - '0'; 215f08c3bdfSopenharmony_ci break; 216f08c3bdfSopenharmony_ci case 'a' ... 'f': 217f08c3bdfSopenharmony_ci acc = 10 + *c - 'a'; 218f08c3bdfSopenharmony_ci break; 219f08c3bdfSopenharmony_ci default: 220f08c3bdfSopenharmony_ci tst_res(TINFO, "%u/smp_affnity: %s", 221f08c3bdfSopenharmony_ci irq_ids[row], buf); 222f08c3bdfSopenharmony_ci tst_brk(TBROK, "Wasn't expecting 0x%02x", *c); 223f08c3bdfSopenharmony_ci } 224f08c3bdfSopenharmony_ci 225f08c3bdfSopenharmony_ci for (bit = 0; bit < 4 && col < nr_cpus; bit++) { 226f08c3bdfSopenharmony_ci irq_affinity[row * nr_cpus + col++] = 227f08c3bdfSopenharmony_ci (acc & (1 << bit)) ? ALLOW : DENY; 228f08c3bdfSopenharmony_ci } 229f08c3bdfSopenharmony_ci } 230f08c3bdfSopenharmony_ci 231f08c3bdfSopenharmony_ci if (col < nr_cpus) { 232f08c3bdfSopenharmony_ci tst_res(TINFO, "%u/smp_affnity: %s", irq_ids[row], buf); 233f08c3bdfSopenharmony_ci tst_brk(TBROK, "Only found %zu cpus", col); 234f08c3bdfSopenharmony_ci } 235f08c3bdfSopenharmony_ci } 236f08c3bdfSopenharmony_ci} 237f08c3bdfSopenharmony_ci 238f08c3bdfSopenharmony_cistatic void print_irq_info(void) 239f08c3bdfSopenharmony_ci{ 240f08c3bdfSopenharmony_ci size_t row, col; 241f08c3bdfSopenharmony_ci unsigned int count; 242f08c3bdfSopenharmony_ci enum affinity aff; 243f08c3bdfSopenharmony_ci 244f08c3bdfSopenharmony_ci tst_printf(" IRQ "); 245f08c3bdfSopenharmony_ci for (col = 0; col < nr_cpus; col++) 246f08c3bdfSopenharmony_ci tst_printf("CPU%-8zu", col); 247f08c3bdfSopenharmony_ci 248f08c3bdfSopenharmony_ci tst_printf("\n"); 249f08c3bdfSopenharmony_ci 250f08c3bdfSopenharmony_ci for (row = 0; row < nr_irqs; row++) { 251f08c3bdfSopenharmony_ci tst_printf("%5u:", irq_ids[row]); 252f08c3bdfSopenharmony_ci 253f08c3bdfSopenharmony_ci for (col = 0; col < nr_cpus; col++) { 254f08c3bdfSopenharmony_ci count = irq_stats[row * nr_cpus + col]; 255f08c3bdfSopenharmony_ci aff = irq_affinity[row * nr_cpus + col]; 256f08c3bdfSopenharmony_ci 257f08c3bdfSopenharmony_ci tst_printf("%10u%c", count, aff); 258f08c3bdfSopenharmony_ci } 259f08c3bdfSopenharmony_ci 260f08c3bdfSopenharmony_ci tst_printf("\n"); 261f08c3bdfSopenharmony_ci } 262f08c3bdfSopenharmony_ci 263f08c3bdfSopenharmony_ci tst_printf("Total:"); 264f08c3bdfSopenharmony_ci 265f08c3bdfSopenharmony_ci for (col = 0; col < nr_cpus; col++) 266f08c3bdfSopenharmony_ci tst_printf("%10u ", irq_stats[row * nr_cpus + col]); 267f08c3bdfSopenharmony_ci 268f08c3bdfSopenharmony_ci tst_printf("\n"); 269f08c3bdfSopenharmony_ci} 270f08c3bdfSopenharmony_ci 271f08c3bdfSopenharmony_cistatic void evidence_of_change(void) 272f08c3bdfSopenharmony_ci{ 273f08c3bdfSopenharmony_ci size_t row, col, changed = 0; 274f08c3bdfSopenharmony_ci 275f08c3bdfSopenharmony_ci for (row = 0; row < nr_irqs; row++) { 276f08c3bdfSopenharmony_ci for (col = 0; col < nr_cpus; col++) { 277f08c3bdfSopenharmony_ci if (!irq_stats[row * nr_cpus + col]) 278f08c3bdfSopenharmony_ci continue; 279f08c3bdfSopenharmony_ci 280f08c3bdfSopenharmony_ci if (irq_affinity[row * nr_cpus + col] == ALLOW) 281f08c3bdfSopenharmony_ci continue; 282f08c3bdfSopenharmony_ci 283f08c3bdfSopenharmony_ci changed++; 284f08c3bdfSopenharmony_ci } 285f08c3bdfSopenharmony_ci } 286f08c3bdfSopenharmony_ci 287f08c3bdfSopenharmony_ci tst_res(changed ? TPASS : TFAIL, 288f08c3bdfSopenharmony_ci "Heuristic: Detected %zu irq-cpu pairs have been dissallowed", 289f08c3bdfSopenharmony_ci changed); 290f08c3bdfSopenharmony_ci} 291f08c3bdfSopenharmony_ci 292f08c3bdfSopenharmony_cistatic void setup(void) 293f08c3bdfSopenharmony_ci{ 294f08c3bdfSopenharmony_ci collect_irq_info(); 295f08c3bdfSopenharmony_ci print_irq_info(); 296f08c3bdfSopenharmony_ci 297f08c3bdfSopenharmony_ci if (nr_cpus < 1) 298f08c3bdfSopenharmony_ci tst_brk(TBROK, "No CPUs found in /proc/interrupts?"); 299f08c3bdfSopenharmony_ci 300f08c3bdfSopenharmony_ci if (nr_irqs < 1) 301f08c3bdfSopenharmony_ci tst_brk(TBROK, "No IRQs found in /proc/interrupts?"); 302f08c3bdfSopenharmony_ci} 303f08c3bdfSopenharmony_ci 304f08c3bdfSopenharmony_cistatic void run(void) 305f08c3bdfSopenharmony_ci{ 306f08c3bdfSopenharmony_ci collect_irq_info(); 307f08c3bdfSopenharmony_ci 308f08c3bdfSopenharmony_ci evidence_of_change(); 309f08c3bdfSopenharmony_ci} 310f08c3bdfSopenharmony_ci 311f08c3bdfSopenharmony_cistatic struct tst_test test = { 312f08c3bdfSopenharmony_ci .test_all = run, 313f08c3bdfSopenharmony_ci .setup = setup, 314f08c3bdfSopenharmony_ci .min_cpus = 2, 315f08c3bdfSopenharmony_ci}; 316