|
/* SPDX-License-Identifier: GPL-2.0 */ |
|
/* |
|
* Shared helpers for the x86 cacheinfo slab-out-of-bounds reproducer. |
|
* |
|
* Everything here is userspace-only: CPUID reads on a pinned CPU, sysfs |
|
* parsing, and a byte-for-byte re-implementation of the two pieces of kernel |
|
* logic that decide whether the out-of-bounds write happens: |
|
* |
|
* arch/x86/kernel/cpu/cacheinfo.c:__cache_cpumap_setup() (the sibling test) |
|
* arch/x86/kernel/cpu/cacheinfo.c:find_num_cache_leaves() (the array length) |
|
* |
|
* Author: Yunseong Kim <yunseong.kim@est.tech> |
|
*/ |
|
#ifndef CACHEINFO_OOB_H |
|
#define CACHEINFO_OOB_H |
|
|
|
#define _GNU_SOURCE |
|
#include <cpuid.h> |
|
#include <ctype.h> |
|
#include <errno.h> |
|
#include <fcntl.h> |
|
#include <sched.h> |
|
#include <stdarg.h> |
|
#include <stdbool.h> |
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <unistd.h> |
|
#include <sys/stat.h> |
|
#include <sys/types.h> |
|
|
|
#define MAX_CPUS 1024 |
|
#define MAX_LEAVES 16 |
|
#define SYSFS_CPU "/sys/devices/system/cpu" |
|
|
|
/* enum cache_type, mirrors include/linux/cacheinfo.h ordering of CPUID.4 types */ |
|
enum leaf_type { |
|
LEAF_NULL = 0, |
|
LEAF_DATA = 1, |
|
LEAF_INST = 2, |
|
LEAF_UNIFIED = 3, |
|
}; |
|
|
|
static const char *leaf_type_name(unsigned int t) |
|
{ |
|
switch (t) { |
|
case LEAF_DATA: return "Data"; |
|
case LEAF_INST: return "Instruction"; |
|
case LEAF_UNIFIED: return "Unified"; |
|
default: return "Null"; |
|
} |
|
} |
|
|
|
struct leaf_info { |
|
unsigned int level; |
|
unsigned int type; /* enum leaf_type */ |
|
unsigned int num_threads_sharing; |
|
unsigned int size; /* bytes, informational */ |
|
int index_msb; /* get_count_order(num_threads_sharing) */ |
|
unsigned int cache_id; /* apicid >> index_msb, as the kernel computes */ |
|
}; |
|
|
|
struct cpu_info { |
|
int cpu; |
|
bool present; |
|
bool online; |
|
bool probed; /* CPUID actually read on this CPU */ |
|
unsigned int apicid; /* x2APIC ID, what cpu_data(cpu).topo.apicid holds */ |
|
int cpuid_leaves; /* find_num_cache_leaves() equivalent */ |
|
int sysfs_leaves; /* number of cacheN/indexN dirs = kernel num_leaves */ |
|
struct leaf_info leaf[MAX_LEAVES]; |
|
/* sysfs view, only filled for online CPUs */ |
|
unsigned int sysfs_level[MAX_LEAVES]; |
|
unsigned int sysfs_type[MAX_LEAVES]; |
|
char sysfs_shared[MAX_LEAVES][128]; |
|
}; |
|
|
|
struct machine { |
|
struct cpu_info cpu[MAX_CPUS]; |
|
int nr_cpus; /* highest present cpu + 1 */ |
|
int nr_online; |
|
int nr_probed; |
|
unsigned int nr_cpus_config; /* CONFIG_NR_CPUS of the running kernel */ |
|
bool nr_cpus_config_known; |
|
size_t sizeof_cacheinfo; |
|
size_t offsetof_shared_cpu_map; |
|
char vendor[13]; |
|
char model_name[96]; |
|
bool intel_path; /* !AMD && !Hygon: the __cache_cpumap_setup() path */ |
|
}; |
|
|
|
/* ---------------------------------------------------------------- utilities */ |
|
|
|
static void die(const char *fmt, ...) |
|
{ |
|
va_list ap; |
|
|
|
va_start(ap, fmt); |
|
fprintf(stderr, "error: "); |
|
vfprintf(stderr, fmt, ap); |
|
va_end(ap); |
|
fprintf(stderr, "\n"); |
|
exit(3); |
|
} |
|
|
|
static int read_file(const char *path, char *buf, size_t len) |
|
{ |
|
int fd = open(path, O_RDONLY); |
|
ssize_t n; |
|
|
|
if (fd < 0) |
|
return -1; |
|
n = read(fd, buf, len - 1); |
|
close(fd); |
|
if (n < 0) |
|
return -1; |
|
buf[n] = '\0'; |
|
while (n > 0 && (buf[n - 1] == '\n' || buf[n - 1] == '\r')) |
|
buf[--n] = '\0'; |
|
return 0; |
|
} |
|
|
|
static inline int write_file(const char *path, const char *val) |
|
{ |
|
int fd = open(path, O_WRONLY); |
|
ssize_t n; |
|
|
|
if (fd < 0) |
|
return -1; |
|
n = write(fd, val, strlen(val)); |
|
if (close(fd) < 0 && n >= 0) |
|
return -1; |
|
return n < 0 ? -1 : 0; |
|
} |
|
|
|
/* "0-3,7" -> set bits in mask[] */ |
|
static void parse_cpu_list(const char *s, bool *mask, int max) |
|
{ |
|
while (*s) { |
|
int a = 0, b; |
|
|
|
while (*s && !isdigit((unsigned char)*s)) |
|
s++; |
|
if (!*s) |
|
return; |
|
a = (int)strtol(s, (char **)&s, 10); |
|
b = a; |
|
if (*s == '-') { |
|
s++; |
|
b = (int)strtol(s, (char **)&s, 10); |
|
} |
|
for (; a <= b && a < max; a++) |
|
if (a >= 0) |
|
mask[a] = true; |
|
} |
|
} |
|
|
|
/* include/linux/bitops.h:get_count_order() */ |
|
static int get_count_order(unsigned int count) |
|
{ |
|
int fls = 0; |
|
|
|
if (count == 0) |
|
return -1; |
|
count--; |
|
while (count) { /* fls(count) */ |
|
fls++; |
|
count >>= 1; |
|
} |
|
return fls; |
|
} |
|
|
|
/* |
|
* struct cacheinfo layout on x86-64, derived from include/linux/cacheinfo.h: |
|
* |
|
* 8 x unsigned int -> 0..32 |
|
* cpumask_t shared_cpu_map -> 32..32+mask |
|
* unsigned int attributes; void *fw_token; bool disable_sysfs; void *priv; |
|
* |
|
* which lands at sizeof == mask_bytes + 64 with shared_cpu_map at offset 32. |
|
* For CONFIG_NR_CPUS=8192 that is 1088/32, matching pahole on a built vmlinux. |
|
*/ |
|
static size_t cacheinfo_mask_bytes(unsigned int nr_cpus_config) |
|
{ |
|
return ((nr_cpus_config + 63) / 64) * 8; |
|
} |
|
|
|
static size_t cacheinfo_size(unsigned int nr_cpus_config) |
|
{ |
|
return cacheinfo_mask_bytes(nr_cpus_config) + 64; |
|
} |
|
|
|
/* kmalloc bucket that an allocation of @n bytes comes from (KMALLOC_NORMAL) */ |
|
static size_t kmalloc_bucket(size_t n) |
|
{ |
|
static const size_t small[] = { 8, 16, 32, 64, 96, 128, 192, 256 }; |
|
size_t b; |
|
unsigned int i; |
|
|
|
for (i = 0; i < sizeof(small) / sizeof(small[0]); i++) |
|
if (n <= small[i]) |
|
return small[i]; |
|
for (b = 512; b <= (size_t)1 << 30; b <<= 1) |
|
if (n <= b) |
|
return b; |
|
return n; |
|
} |
|
|
|
/* ------------------------------------------------------------------- CPUID */ |
|
|
|
static bool pin_to_cpu(int cpu) |
|
{ |
|
cpu_set_t set; |
|
|
|
CPU_ZERO(&set); |
|
CPU_SET(cpu, &set); |
|
if (sched_setaffinity(0, sizeof(set), &set) != 0) |
|
return false; |
|
sched_yield(); |
|
return sched_getcpu() == cpu; |
|
} |
|
|
|
static unsigned int read_apicid(void) |
|
{ |
|
unsigned int a, b, c, d, max_leaf; |
|
|
|
__cpuid(0, max_leaf, b, c, d); |
|
|
|
/* CPUID.1F:EDX and CPUID.0B:EDX both return the 32-bit x2APIC ID. */ |
|
if (max_leaf >= 0x1f) { |
|
__cpuid_count(0x1f, 0, a, b, c, d); |
|
if (b) |
|
return d; |
|
} |
|
if (max_leaf >= 0x0b) { |
|
__cpuid_count(0x0b, 0, a, b, c, d); |
|
if (b) |
|
return d; |
|
} |
|
/* Legacy initial APIC ID, CPUID.1:EBX[31:24]. */ |
|
__cpuid(1, a, b, c, d); |
|
return b >> 24; |
|
} |
|
|
|
/* |
|
* arch/x86/kernel/cpu/cacheinfo.c:find_num_cache_leaves() and the CPUID.4 |
|
* decoding in intel_fill_cpuid4_info(), for the CPU we are pinned to. |
|
*/ |
|
static void probe_cpuid_leaves(struct cpu_info *ci, bool amd_like) |
|
{ |
|
unsigned int op = amd_like ? 0x8000001d : 4; |
|
int i; |
|
|
|
ci->cpuid_leaves = 0; |
|
for (i = 0; i < MAX_LEAVES; i++) { |
|
unsigned int eax, ebx, ecx, edx; |
|
struct leaf_info *l = &ci->leaf[i]; |
|
|
|
__cpuid_count(op, (unsigned int)i, eax, ebx, ecx, edx); |
|
if ((eax & 0x1f) == LEAF_NULL) |
|
break; |
|
|
|
l->type = eax & 0x1f; |
|
l->level = (eax >> 5) & 0x7; |
|
l->num_threads_sharing = ((eax >> 14) & 0xfff) + 1; |
|
l->index_msb = get_count_order(l->num_threads_sharing); |
|
l->cache_id = ci->apicid >> l->index_msb; |
|
l->size = (((ecx & 0xffffffff) + 1) * |
|
((ebx & 0xfff) + 1) * |
|
(((ebx >> 12) & 0x3ff) + 1) * |
|
(((ebx >> 22) & 0x3ff) + 1)); |
|
ci->cpuid_leaves = i + 1; |
|
} |
|
} |
|
|
|
/* ------------------------------------------------------------------- sysfs */ |
|
|
|
static int count_sysfs_leaves(int cpu) |
|
{ |
|
int i; |
|
|
|
for (i = 0; i < MAX_LEAVES; i++) { |
|
char path[160]; |
|
struct stat st; |
|
|
|
snprintf(path, sizeof(path), SYSFS_CPU "/cpu%d/cache/index%d", cpu, i); |
|
if (stat(path, &st) != 0) |
|
break; |
|
} |
|
return i; |
|
} |
|
|
|
static void read_sysfs_leaves(struct cpu_info *ci) |
|
{ |
|
int i; |
|
|
|
ci->sysfs_leaves = count_sysfs_leaves(ci->cpu); |
|
for (i = 0; i < ci->sysfs_leaves && i < MAX_LEAVES; i++) { |
|
char path[192], buf[128]; |
|
|
|
snprintf(path, sizeof(path), SYSFS_CPU "/cpu%d/cache/index%d/level", |
|
ci->cpu, i); |
|
ci->sysfs_level[i] = read_file(path, buf, sizeof(buf)) ? 0 : |
|
(unsigned int)atoi(buf); |
|
|
|
snprintf(path, sizeof(path), SYSFS_CPU "/cpu%d/cache/index%d/type", |
|
ci->cpu, i); |
|
ci->sysfs_type[i] = LEAF_NULL; |
|
if (!read_file(path, buf, sizeof(buf))) { |
|
if (!strcmp(buf, "Data")) |
|
ci->sysfs_type[i] = LEAF_DATA; |
|
else if (!strcmp(buf, "Instruction")) |
|
ci->sysfs_type[i] = LEAF_INST; |
|
else if (!strcmp(buf, "Unified")) |
|
ci->sysfs_type[i] = LEAF_UNIFIED; |
|
} |
|
|
|
snprintf(path, sizeof(path), |
|
SYSFS_CPU "/cpu%d/cache/index%d/shared_cpu_list", ci->cpu, i); |
|
if (read_file(path, buf, sizeof(buf))) |
|
buf[0] = '\0'; |
|
snprintf(ci->sysfs_shared[i], sizeof(ci->sysfs_shared[i]), "%s", buf); |
|
} |
|
} |
|
|
|
static void read_vendor(struct machine *m) |
|
{ |
|
unsigned int a, b, c, d; |
|
FILE *f; |
|
char line[256]; |
|
|
|
__cpuid(0, a, b, c, d); |
|
memcpy(m->vendor + 0, &b, 4); |
|
memcpy(m->vendor + 4, &d, 4); |
|
memcpy(m->vendor + 8, &c, 4); |
|
m->vendor[12] = '\0'; |
|
m->intel_path = strcmp(m->vendor, "AuthenticAMD") && |
|
strcmp(m->vendor, "HygonGenuine"); |
|
|
|
m->model_name[0] = '\0'; |
|
f = fopen("/proc/cpuinfo", "r"); |
|
if (!f) |
|
return; |
|
while (fgets(line, sizeof(line), f)) { |
|
char *p = strstr(line, "model name"); |
|
|
|
if (!p) |
|
continue; |
|
p = strchr(line, ':'); |
|
if (!p) |
|
continue; |
|
p += 2; |
|
p[strcspn(p, "\n")] = '\0'; |
|
snprintf(m->model_name, sizeof(m->model_name), "%s", p); |
|
break; |
|
} |
|
fclose(f); |
|
} |
|
|
|
/* |
|
* CONFIG_NR_CPUS decides sizeof(struct cacheinfo) and therefore the exact |
|
* offsets a KASAN report will print. Take it from the running kernel's config |
|
* when it is available, otherwise let the caller override it. |
|
*/ |
|
static void read_nr_cpus_config(struct machine *m) |
|
{ |
|
char path[320], line[256]; |
|
FILE *f = fopen("/proc/config.gz", "r"); |
|
|
|
m->nr_cpus_config_known = false; |
|
if (f) { |
|
fclose(f); |
|
f = popen("zcat /proc/config.gz 2>/dev/null | grep -m1 '^CONFIG_NR_CPUS='", "r"); |
|
if (f) { |
|
if (fgets(line, sizeof(line), f)) { |
|
char *p = strchr(line, '='); |
|
|
|
if (p) { |
|
m->nr_cpus_config = (unsigned int)atoi(p + 1); |
|
m->nr_cpus_config_known = m->nr_cpus_config > 0; |
|
} |
|
} |
|
pclose(f); |
|
} |
|
} |
|
if (!m->nr_cpus_config_known) { |
|
char rel[128]; |
|
|
|
if (!read_file("/proc/sys/kernel/osrelease", rel, sizeof(rel))) { |
|
snprintf(path, sizeof(path), "/boot/config-%s", rel); |
|
f = fopen(path, "r"); |
|
if (f) { |
|
while (fgets(line, sizeof(line), f)) { |
|
if (!strncmp(line, "CONFIG_NR_CPUS=", 15)) { |
|
m->nr_cpus_config = (unsigned int)atoi(line + 15); |
|
m->nr_cpus_config_known = m->nr_cpus_config > 0; |
|
break; |
|
} |
|
} |
|
fclose(f); |
|
} |
|
} |
|
} |
|
if (!m->nr_cpus_config_known) |
|
m->nr_cpus_config = 8192; /* Debian's value, see README */ |
|
} |
|
|
|
/* ------------------------------------------------------- machine enumeration */ |
|
|
|
static void machine_scan(struct machine *m, unsigned int nr_cpus_override) |
|
{ |
|
bool present[MAX_CPUS] = { false }, online[MAX_CPUS] = { false }; |
|
char buf[4096]; |
|
cpu_set_t saved; |
|
int i; |
|
|
|
memset(m, 0, sizeof(*m)); |
|
read_vendor(m); |
|
read_nr_cpus_config(m); |
|
if (nr_cpus_override) { |
|
m->nr_cpus_config = nr_cpus_override; |
|
m->nr_cpus_config_known = true; |
|
} |
|
m->sizeof_cacheinfo = cacheinfo_size(m->nr_cpus_config); |
|
m->offsetof_shared_cpu_map = 32; |
|
|
|
if (read_file(SYSFS_CPU "/present", buf, sizeof(buf))) |
|
die("cannot read " SYSFS_CPU "/present: %s", strerror(errno)); |
|
parse_cpu_list(buf, present, MAX_CPUS); |
|
if (read_file(SYSFS_CPU "/online", buf, sizeof(buf))) |
|
die("cannot read " SYSFS_CPU "/online: %s", strerror(errno)); |
|
parse_cpu_list(buf, online, MAX_CPUS); |
|
|
|
if (sched_getaffinity(0, sizeof(saved), &saved) != 0) |
|
die("sched_getaffinity: %s", strerror(errno)); |
|
|
|
for (i = 0; i < MAX_CPUS; i++) { |
|
struct cpu_info *ci = &m->cpu[i]; |
|
|
|
ci->cpu = i; |
|
ci->present = present[i]; |
|
ci->online = online[i]; |
|
if (!ci->present) |
|
continue; |
|
m->nr_cpus = i + 1; |
|
if (!ci->online) |
|
continue; |
|
m->nr_online++; |
|
|
|
read_sysfs_leaves(ci); |
|
if (pin_to_cpu(i)) { |
|
ci->apicid = read_apicid(); |
|
probe_cpuid_leaves(ci, !m->intel_path); |
|
ci->probed = true; |
|
m->nr_probed++; |
|
} |
|
} |
|
sched_setaffinity(0, sizeof(saved), &saved); |
|
} |
|
|
|
/* |
|
* Refresh one CPU after a hotplug transition. |
|
*/ |
|
static inline void machine_rescan_cpu(struct machine *m, int cpu) |
|
{ |
|
struct cpu_info *ci = &m->cpu[cpu]; |
|
bool online[MAX_CPUS] = { false }; |
|
cpu_set_t saved; |
|
char buf[4096]; |
|
|
|
if (!read_file(SYSFS_CPU "/online", buf, sizeof(buf))) |
|
parse_cpu_list(buf, online, MAX_CPUS); |
|
ci->online = online[cpu]; |
|
if (!ci->online) |
|
return; |
|
|
|
read_sysfs_leaves(ci); |
|
if (sched_getaffinity(0, sizeof(saved), &saved) == 0) { |
|
if (pin_to_cpu(cpu)) { |
|
ci->apicid = read_apicid(); |
|
probe_cpuid_leaves(ci, !m->intel_path); |
|
ci->probed = true; |
|
} |
|
sched_setaffinity(0, sizeof(saved), &saved); |
|
} |
|
} |
|
|
|
/* Kernel num_leaves: sysfs is authoritative when online, CPUID otherwise. */ |
|
static int cpu_num_leaves(const struct cpu_info *ci) |
|
{ |
|
if (ci->online && ci->sysfs_leaves > 0) |
|
return ci->sysfs_leaves; |
|
return ci->cpuid_leaves; |
|
} |
|
|
|
/* ------------------------------------------------- the kernel's sibling test */ |
|
|
|
struct oob_hit { |
|
int attacker; /* CPU running populate_cache_leaves() */ |
|
int victim; /* sibling whose array is too short */ |
|
int index; /* leaf index being processed */ |
|
int victim_leaves; |
|
unsigned int num_threads_sharing; |
|
int index_msb; |
|
size_t alloc_size; /* victim's kmalloc size */ |
|
size_t write_off; /* offset of the 8-byte write in that object */ |
|
size_t bucket; /* kmalloc bucket the victim's array came from */ |
|
}; |
|
|
|
/* |
|
* Replicates, for a hypothetical "attacker comes online while victim is |
|
* already online" transition: |
|
* |
|
* for (idx = 0; idx < this_cpu_ci->num_leaves; idx++) [:620] |
|
* __cache_cpumap_setup(cpu, idx, &id4); [:631] |
|
* |
|
* index_msb = get_count_order(num_threads_sharing); |
|
* for_each_online_cpu(i) |
|
* if (cpu_data(i).topo.apicid >> index_msb == |
|
* c->topo.apicid >> index_msb) { [:570] |
|
* if (i == cpu || !sib_cpu_ci->info_list) |
|
* continue; |
|
* sibling_ci = sib_cpu_ci->info_list + index; [:578] |
|
* cpumask_set_cpu(cpu, &sibling_ci->shared_cpu_map); [:580] |
|
* |
|
* A hit means index >= num_leaves(victim), i.e. the write at :580 lands past |
|
* the end of the victim's array. |
|
*/ |
|
static int find_oob(const struct machine *m, int attacker, int victim, |
|
struct oob_hit *hit) |
|
{ |
|
const struct cpu_info *a = &m->cpu[attacker], *v = &m->cpu[victim]; |
|
int a_leaves = cpu_num_leaves(a), v_leaves = cpu_num_leaves(v); |
|
int idx, hits = 0; |
|
|
|
if (!m->intel_path || attacker == victim || !a->probed || !v->probed) |
|
return 0; |
|
|
|
for (idx = 0; idx < a_leaves && idx < MAX_LEAVES; idx++) { |
|
const struct leaf_info *l = &a->leaf[idx]; |
|
|
|
if (l->num_threads_sharing == 1) /* returns before the loop */ |
|
continue; |
|
if ((v->apicid >> l->index_msb) != (a->apicid >> l->index_msb)) |
|
continue; |
|
if (idx < v_leaves) |
|
continue; |
|
|
|
if (hit && !hits) { |
|
hit->attacker = attacker; |
|
hit->victim = victim; |
|
hit->index = idx; |
|
hit->victim_leaves = v_leaves; |
|
hit->num_threads_sharing = l->num_threads_sharing; |
|
hit->index_msb = l->index_msb; |
|
hit->alloc_size = (size_t)v_leaves * m->sizeof_cacheinfo; |
|
hit->write_off = (size_t)idx * m->sizeof_cacheinfo + |
|
m->offsetof_shared_cpu_map; |
|
hit->bucket = kmalloc_bucket(hit->alloc_size); |
|
} |
|
hits++; |
|
} |
|
return hits; |
|
} |
|
|
|
static void print_oob_prediction(const struct machine *m, const struct oob_hit *h) |
|
{ |
|
printf(" cpu%d (leaf index %d, %u leaves) -> cpu%d (%d leaves)\n", |
|
h->attacker, h->index, cpu_num_leaves(&m->cpu[h->attacker]), |
|
h->victim, h->victim_leaves); |
|
printf(" sibling test: num_threads_sharing=%u index_msb=%d, " |
|
"apicid %u>>%d == %u>>%d == %u\n", |
|
h->num_threads_sharing, h->index_msb, |
|
m->cpu[h->attacker].apicid, h->index_msb, |
|
m->cpu[h->victim].apicid, h->index_msb, |
|
m->cpu[h->victim].apicid >> h->index_msb); |
|
printf(" write: 8 bytes at info_list(cpu%d) + %d*%zu + %zu = +%zu,\n", |
|
h->victim, h->index, m->sizeof_cacheinfo, |
|
m->offsetof_shared_cpu_map, h->write_off); |
|
printf(" %zu bytes past the end of the %zu-byte allocation\n", |
|
h->write_off - h->alloc_size, h->alloc_size); |
|
printf(" KASAN should report: \"Write of size 8\" ... \"located %zu bytes to the " |
|
"right of allocated %zu-byte region\"\n", |
|
h->write_off - h->alloc_size, h->alloc_size); |
|
if (h->write_off >= h->bucket) |
|
printf(" without KASAN: the write leaves the %zu-byte kmalloc object " |
|
"entirely and corrupts the next one\n", h->bucket); |
|
else |
|
printf(" without KASAN: the write stays inside the %zu-byte kmalloc " |
|
"object's slack, so KASAN (or slub_debug=Z) is needed to see it\n", |
|
h->bucket); |
|
} |
|
|
|
/* |
|
* A KASAN-free footprint of the same bug: sysfs claims a CPU shares a cache |
|
* with a CPU that has no cache of that level and type. That cross-link can |
|
* only come from the out-of-bounds write. |
|
*/ |
|
static int find_sysfs_crosslinks(const struct machine *m, bool verbose) |
|
{ |
|
int cpu, idx, found = 0; |
|
|
|
for (cpu = 0; cpu < m->nr_cpus; cpu++) { |
|
const struct cpu_info *ci = &m->cpu[cpu]; |
|
|
|
if (!ci->online) |
|
continue; |
|
for (idx = 0; idx < ci->sysfs_leaves; idx++) { |
|
bool shared[MAX_CPUS] = { false }; |
|
int sib; |
|
|
|
parse_cpu_list(ci->sysfs_shared[idx], shared, MAX_CPUS); |
|
for (sib = 0; sib < m->nr_cpus; sib++) { |
|
const struct cpu_info *sc = &m->cpu[sib]; |
|
bool match = false; |
|
int j; |
|
|
|
if (sib == cpu || !shared[sib] || !sc->online) |
|
continue; |
|
for (j = 0; j < sc->sysfs_leaves; j++) |
|
if (sc->sysfs_level[j] == ci->sysfs_level[idx] && |
|
sc->sysfs_type[j] == ci->sysfs_type[idx]) |
|
match = true; |
|
if (match) |
|
continue; |
|
found++; |
|
if (verbose) |
|
printf(" cpu%d/cache/index%d (L%u %s) lists cpu%d as " |
|
"sharing it, but cpu%d has no L%u %s cache\n", |
|
cpu, idx, ci->sysfs_level[idx], |
|
leaf_type_name(ci->sysfs_type[idx]), sib, |
|
sib, ci->sysfs_level[idx], |
|
leaf_type_name(ci->sysfs_type[idx])); |
|
} |
|
} |
|
} |
|
return found; |
|
} |
|
|
|
static void print_machine(const struct machine *m) |
|
{ |
|
int cpu, i; |
|
|
|
printf("machine\n"); |
|
printf(" model : %s\n", m->model_name[0] ? m->model_name : "(unknown)"); |
|
printf(" vendor : %s (%s shared_cpu_map path)\n", m->vendor, |
|
m->intel_path ? "__cache_cpumap_setup" : "__cache_amd_cpumap_setup"); |
|
printf(" cpus present : %d, online: %d, probed: %d\n", |
|
m->nr_cpus, m->nr_online, m->nr_probed); |
|
printf(" CONFIG_NR_CPUS : %u%s\n", m->nr_cpus_config, |
|
m->nr_cpus_config_known ? "" : " (assumed, kernel config not readable)"); |
|
printf(" sizeof(struct cacheinfo) = %zu, offsetof(shared_cpu_map) = %zu\n\n", |
|
m->sizeof_cacheinfo, m->offsetof_shared_cpu_map); |
|
|
|
printf("per-cpu cache leaves (CPUID.4 as the kernel reads it)\n"); |
|
for (cpu = 0; cpu < m->nr_cpus; cpu++) { |
|
const struct cpu_info *ci = &m->cpu[cpu]; |
|
|
|
if (!ci->present) |
|
continue; |
|
if (!ci->online) { |
|
printf(" cpu%-3d offline, leaf count unknown until it is onlined\n", |
|
cpu); |
|
continue; |
|
} |
|
printf(" cpu%-3d apicid=%-3u num_leaves=%d (sysfs %d)%s\n", |
|
cpu, ci->apicid, ci->cpuid_leaves, ci->sysfs_leaves, |
|
ci->cpuid_leaves != ci->sysfs_leaves ? " <-- MISMATCH" : ""); |
|
for (i = 0; i < ci->cpuid_leaves; i++) { |
|
const struct leaf_info *l = &ci->leaf[i]; |
|
|
|
printf(" index%d L%u %-11s size=%-7u threads_sharing=%-3u " |
|
"index_msb=%d cache_id=%u shared_cpu_list=%s\n", |
|
i, l->level, leaf_type_name(l->type), l->size, |
|
l->num_threads_sharing, l->index_msb, l->cache_id, |
|
i < ci->sysfs_leaves ? ci->sysfs_shared[i] : "-"); |
|
} |
|
} |
|
printf("\n"); |
|
} |
|
|
|
#endif /* CACHEINFO_OOB_H */ |