| // SPDX-License-Identifier: GPL-2.0 | |
| #include <sys/param.h> | |
| #include <sys/utsname.h> | |
| #include <inttypes.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <api/fs/fs.h> | |
| #include <linux/zalloc.h> | |
| #include <perf/cpumap.h> | |
| #include "cputopo.h" | |
| #include "cpumap.h" | |
| #include "debug.h" | |
| #include "env.h" | |
| #include "pmu.h" | |
| #include "pmus.h" | |
| #define PACKAGE_CPUS_FMT \ | |
| "%s/devices/system/cpu/cpu%d/topology/package_cpus_list" | |
| #define PACKAGE_CPUS_FMT_OLD \ | |
| "%s/devices/system/cpu/cpu%d/topology/core_siblings_list" | |
| #define DIE_CPUS_FMT \ | |
| "%s/devices/system/cpu/cpu%d/topology/die_cpus_list" | |
| #define CORE_CPUS_FMT \ | |
| "%s/devices/system/cpu/cpu%d/topology/core_cpus_list" | |
| #define CORE_CPUS_FMT_OLD \ | |
| "%s/devices/system/cpu/cpu%d/topology/thread_siblings_list" | |
| #define NODE_ONLINE_FMT \ | |
| "%s/devices/system/node/online" | |
| #define NODE_MEMINFO_FMT \ | |
| "%s/devices/system/node/node%d/meminfo" | |
| #define NODE_CPULIST_FMT \ | |
| "%s/devices/system/node/node%d/cpulist" | |
| static int build_cpu_topology(struct cpu_topology *tp, int cpu) | |
| { | |
| FILE *fp; | |
| char filename[MAXPATHLEN]; | |
| char *buf = NULL, *p; | |
| size_t len = 0; | |
| ssize_t sret; | |
| u32 i = 0; | |
| int ret = -1; | |
| scnprintf(filename, MAXPATHLEN, PACKAGE_CPUS_FMT, | |
| sysfs__mountpoint(), cpu); | |
| if (access(filename, F_OK) == -1) { | |
| scnprintf(filename, MAXPATHLEN, PACKAGE_CPUS_FMT_OLD, | |
| sysfs__mountpoint(), cpu); | |
| } | |
| fp = fopen(filename, "r"); | |
| if (!fp) | |
| goto try_dies; | |
| sret = getline(&buf, &len, fp); | |
| fclose(fp); | |
| if (sret <= 0) | |
| goto try_dies; | |
| p = strchr(buf, '\n'); | |
| if (p) | |
| *p = '\0'; | |
| for (i = 0; i < tp->package_cpus_lists; i++) { | |
| if (!strcmp(buf, tp->package_cpus_list[i])) | |
| break; | |
| } | |
| if (i == tp->package_cpus_lists) { | |
| tp->package_cpus_list[i] = buf; | |
| tp->package_cpus_lists++; | |
| buf = NULL; | |
| len = 0; | |
| } | |
| ret = 0; | |
| try_dies: | |
| if (!tp->die_cpus_list) | |
| goto try_threads; | |
| scnprintf(filename, MAXPATHLEN, DIE_CPUS_FMT, | |
| sysfs__mountpoint(), cpu); | |
| fp = fopen(filename, "r"); | |
| if (!fp) | |
| goto try_threads; | |
| sret = getline(&buf, &len, fp); | |
| fclose(fp); | |
| if (sret <= 0) | |
| goto try_threads; | |
| p = strchr(buf, '\n'); | |
| if (p) | |
| *p = '\0'; | |
| for (i = 0; i < tp->die_cpus_lists; i++) { | |
| if (!strcmp(buf, tp->die_cpus_list[i])) | |
| break; | |
| } | |
| if (i == tp->die_cpus_lists) { | |
| tp->die_cpus_list[i] = buf; | |
| tp->die_cpus_lists++; | |
| buf = NULL; | |
| len = 0; | |
| } | |
| ret = 0; | |
| try_threads: | |
| scnprintf(filename, MAXPATHLEN, CORE_CPUS_FMT, | |
| sysfs__mountpoint(), cpu); | |
| if (access(filename, F_OK) == -1) { | |
| scnprintf(filename, MAXPATHLEN, CORE_CPUS_FMT_OLD, | |
| sysfs__mountpoint(), cpu); | |
| } | |
| fp = fopen(filename, "r"); | |
| if (!fp) | |
| goto done; | |
| if (getline(&buf, &len, fp) <= 0) | |
| goto done; | |
| p = strchr(buf, '\n'); | |
| if (p) | |
| *p = '\0'; | |
| for (i = 0; i < tp->core_cpus_lists; i++) { | |
| if (!strcmp(buf, tp->core_cpus_list[i])) | |
| break; | |
| } | |
| if (i == tp->core_cpus_lists) { | |
| tp->core_cpus_list[i] = buf; | |
| tp->core_cpus_lists++; | |
| buf = NULL; | |
| } | |
| ret = 0; | |
| done: | |
| if (fp) | |
| fclose(fp); | |
| free(buf); | |
| return ret; | |
| } | |
| void cpu_topology__delete(struct cpu_topology *tp) | |
| { | |
| u32 i; | |
| if (!tp) | |
| return; | |
| for (i = 0 ; i < tp->package_cpus_lists; i++) | |
| zfree(&tp->package_cpus_list[i]); | |
| for (i = 0 ; i < tp->die_cpus_lists; i++) | |
| zfree(&tp->die_cpus_list[i]); | |
| for (i = 0 ; i < tp->core_cpus_lists; i++) | |
| zfree(&tp->core_cpus_list[i]); | |
| free(tp); | |
| } | |
| bool cpu_topology__smt_on(const struct cpu_topology *topology) | |
| { | |
| for (u32 i = 0; i < topology->core_cpus_lists; i++) { | |
| const char *cpu_list = topology->core_cpus_list[i]; | |
| /* | |
| * If there is a need to separate siblings in a core then SMT is | |
| * enabled. | |
| */ | |
| if (strchr(cpu_list, ',') || strchr(cpu_list, '-')) | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool cpu_topology__core_wide(const struct cpu_topology *topology, | |
| const char *user_requested_cpu_list) | |
| { | |
| struct perf_cpu_map *user_requested_cpus; | |
| /* | |
| * If user_requested_cpu_list is empty then all CPUs are recorded and so | |
| * core_wide is true. | |
| */ | |
| if (!user_requested_cpu_list) | |
| return true; | |
| user_requested_cpus = perf_cpu_map__new(user_requested_cpu_list); | |
| /* Check that every user requested CPU is the complete set of SMT threads on a core. */ | |
| for (u32 i = 0; i < topology->core_cpus_lists; i++) { | |
| const char *core_cpu_list = topology->core_cpus_list[i]; | |
| struct perf_cpu_map *core_cpus = perf_cpu_map__new(core_cpu_list); | |
| struct perf_cpu cpu; | |
| unsigned int idx; | |
| bool has_first, first = true; | |
| perf_cpu_map__for_each_cpu(cpu, idx, core_cpus) { | |
| if (first) { | |
| has_first = perf_cpu_map__has(user_requested_cpus, cpu); | |
| first = false; | |
| } else { | |
| /* | |
| * If the first core CPU is user requested then | |
| * all subsequent CPUs in the core must be user | |
| * requested too. If the first CPU isn't user | |
| * requested then none of the others must be | |
| * too. | |
| */ | |
| if (perf_cpu_map__has(user_requested_cpus, cpu) != has_first) { | |
| perf_cpu_map__put(core_cpus); | |
| perf_cpu_map__put(user_requested_cpus); | |
| return false; | |
| } | |
| } | |
| } | |
| perf_cpu_map__put(core_cpus); | |
| } | |
| perf_cpu_map__put(user_requested_cpus); | |
| return true; | |
| } | |
| static bool has_die_topology(void) | |
| { | |
| char filename[MAXPATHLEN]; | |
| struct utsname uts; | |
| if (uname(&uts) < 0) | |
| return false; | |
| if (strncmp(uts.machine, "x86_64", 6) && | |
| strncmp(uts.machine, "s390x", 5)) | |
| return false; | |
| scnprintf(filename, MAXPATHLEN, DIE_CPUS_FMT, | |
| sysfs__mountpoint(), 0); | |
| if (access(filename, F_OK) == -1) | |
| return false; | |
| return true; | |
| } | |
| const struct cpu_topology *online_topology(void) | |
| { | |
| static const struct cpu_topology *topology; | |
| if (!topology) { | |
| topology = cpu_topology__new(); | |
| if (!topology) { | |
| pr_err("Error creating CPU topology"); | |
| abort(); | |
| } | |
| } | |
| return topology; | |
| } | |
| struct cpu_topology *cpu_topology__new(void) | |
| { | |
| struct cpu_topology *tp = NULL; | |
| void *addr; | |
| u32 nr, i, nr_addr; | |
| size_t sz; | |
| long ncpus; | |
| int ret = -1; | |
| struct perf_cpu_map *map; | |
| bool has_die = has_die_topology(); | |
| ncpus = cpu__max_present_cpu().cpu; | |
| /* build online CPU map */ | |
| map = perf_cpu_map__new_online_cpus(); | |
| if (map == NULL) { | |
| pr_debug("failed to get system cpumap\n"); | |
| return NULL; | |
| } | |
| nr = (u32)(ncpus & UINT_MAX); | |
| sz = nr * sizeof(char *); | |
| if (has_die) | |
| nr_addr = 3; | |
| else | |
| nr_addr = 2; | |
| addr = calloc(1, sizeof(*tp) + nr_addr * sz); | |
| if (!addr) | |
| goto out_free; | |
| tp = addr; | |
| addr += sizeof(*tp); | |
| tp->package_cpus_list = addr; | |
| addr += sz; | |
| if (has_die) { | |
| tp->die_cpus_list = addr; | |
| addr += sz; | |
| } | |
| tp->core_cpus_list = addr; | |
| for (i = 0; i < nr; i++) { | |
| if (!perf_cpu_map__has(map, (struct perf_cpu){ .cpu = i })) | |
| continue; | |
| ret = build_cpu_topology(tp, i); | |
| if (ret < 0) | |
| break; | |
| } | |
| out_free: | |
| perf_cpu_map__put(map); | |
| if (ret) { | |
| cpu_topology__delete(tp); | |
| tp = NULL; | |
| } | |
| return tp; | |
| } | |
| static int load_numa_node(struct numa_topology_node *node, int nr) | |
| { | |
| char str[MAXPATHLEN]; | |
| char field[32]; | |
| char *buf = NULL, *p; | |
| size_t len = 0; | |
| int ret = -1; | |
| FILE *fp; | |
| u64 mem; | |
| node->node = (u32) nr; | |
| scnprintf(str, MAXPATHLEN, NODE_MEMINFO_FMT, | |
| sysfs__mountpoint(), nr); | |
| fp = fopen(str, "r"); | |
| if (!fp) | |
| return -1; | |
| while (getline(&buf, &len, fp) > 0) { | |
| /* skip over invalid lines */ | |
| if (!strchr(buf, ':')) | |
| continue; | |
| if (sscanf(buf, "%*s %*d %31s %"PRIu64, field, &mem) != 2) | |
| goto err; | |
| if (!strcmp(field, "MemTotal:")) | |
| node->mem_total = mem; | |
| if (!strcmp(field, "MemFree:")) | |
| node->mem_free = mem; | |
| if (node->mem_total && node->mem_free) | |
| break; | |
| } | |
| fclose(fp); | |
| fp = NULL; | |
| scnprintf(str, MAXPATHLEN, NODE_CPULIST_FMT, | |
| sysfs__mountpoint(), nr); | |
| fp = fopen(str, "r"); | |
| if (!fp) | |
| return -1; | |
| if (getline(&buf, &len, fp) <= 0) | |
| goto err; | |
| p = strchr(buf, '\n'); | |
| if (p) | |
| *p = '\0'; | |
| node->cpus = buf; | |
| fclose(fp); | |
| return 0; | |
| err: | |
| free(buf); | |
| if (fp) | |
| fclose(fp); | |
| return ret; | |
| } | |
| struct numa_topology *numa_topology__new(void) | |
| { | |
| struct perf_cpu_map *node_map = NULL; | |
| struct numa_topology *tp = NULL; | |
| char path[MAXPATHLEN]; | |
| char *buf = NULL; | |
| size_t len = 0; | |
| u32 nr, i; | |
| FILE *fp; | |
| char *c; | |
| scnprintf(path, MAXPATHLEN, NODE_ONLINE_FMT, | |
| sysfs__mountpoint()); | |
| fp = fopen(path, "r"); | |
| if (!fp) | |
| return NULL; | |
| if (getline(&buf, &len, fp) <= 0) | |
| goto out; | |
| c = strchr(buf, '\n'); | |
| if (c) | |
| *c = '\0'; | |
| node_map = perf_cpu_map__new(buf); | |
| if (!node_map) | |
| goto out; | |
| nr = (u32) perf_cpu_map__nr(node_map); | |
| tp = zalloc(sizeof(*tp) + sizeof(tp->nodes[0])*nr); | |
| if (!tp) | |
| goto out; | |
| tp->nr = nr; | |
| for (i = 0; i < nr; i++) { | |
| if (load_numa_node(&tp->nodes[i], perf_cpu_map__cpu(node_map, i).cpu)) { | |
| numa_topology__delete(tp); | |
| tp = NULL; | |
| break; | |
| } | |
| } | |
| out: | |
| free(buf); | |
| fclose(fp); | |
| perf_cpu_map__put(node_map); | |
| return tp; | |
| } | |
| void numa_topology__delete(struct numa_topology *tp) | |
| { | |
| u32 i; | |
| for (i = 0; i < tp->nr; i++) | |
| zfree(&tp->nodes[i].cpus); | |
| free(tp); | |
| } | |
| static int load_hybrid_node(struct hybrid_topology_node *node, | |
| struct perf_pmu *pmu) | |
| { | |
| char *buf = NULL, *p; | |
| FILE *fp; | |
| size_t len = 0; | |
| node->pmu_name = strdup(pmu->name); | |
| if (!node->pmu_name) | |
| return -1; | |
| fp = perf_pmu__open_file(pmu, "cpus"); | |
| if (!fp) | |
| goto err; | |
| if (getline(&buf, &len, fp) <= 0) { | |
| fclose(fp); | |
| goto err; | |
| } | |
| p = strchr(buf, '\n'); | |
| if (p) | |
| *p = '\0'; | |
| fclose(fp); | |
| node->cpus = buf; | |
| return 0; | |
| err: | |
| zfree(&node->pmu_name); | |
| free(buf); | |
| return -1; | |
| } | |
| struct hybrid_topology *hybrid_topology__new(void) | |
| { | |
| struct perf_pmu *pmu = NULL; | |
| struct hybrid_topology *tp = NULL; | |
| int nr = perf_pmus__num_core_pmus(), i = 0; | |
| if (nr <= 1) | |
| return NULL; | |
| tp = zalloc(sizeof(*tp) + sizeof(tp->nodes[0]) * nr); | |
| if (!tp) | |
| return NULL; | |
| tp->nr = nr; | |
| while ((pmu = perf_pmus__scan_core(pmu)) != NULL) { | |
| if (load_hybrid_node(&tp->nodes[i], pmu)) { | |
| hybrid_topology__delete(tp); | |
| return NULL; | |
| } | |
| i++; | |
| } | |
| return tp; | |
| } | |
| void hybrid_topology__delete(struct hybrid_topology *tp) | |
| { | |
| u32 i; | |
| for (i = 0; i < tp->nr; i++) { | |
| zfree(&tp->nodes[i].pmu_name); | |
| zfree(&tp->nodes[i].cpus); | |
| } | |
| free(tp); | |
| } |