1// SPDX-License-Identifier: GPL-2.0
2#include <sys/param.h>
3#include <sys/utsname.h>
4#include <inttypes.h>
5#include <stdlib.h>
6#include <string.h>
7#include <api/fs/fs.h>
8#include <linux/zalloc.h>
9#include <perf/cpumap.h>
10
11#include "cputopo.h"
12#include "cpumap.h"
13#include "debug.h"
14#include "env.h"
15#include "pmu.h"
16#include "pmus.h"
17
18#define PACKAGE_CPUS_FMT \
19	"%s/devices/system/cpu/cpu%d/topology/package_cpus_list"
20#define PACKAGE_CPUS_FMT_OLD \
21	"%s/devices/system/cpu/cpu%d/topology/core_siblings_list"
22#define DIE_CPUS_FMT \
23	"%s/devices/system/cpu/cpu%d/topology/die_cpus_list"
24#define CORE_CPUS_FMT \
25	"%s/devices/system/cpu/cpu%d/topology/core_cpus_list"
26#define CORE_CPUS_FMT_OLD \
27	"%s/devices/system/cpu/cpu%d/topology/thread_siblings_list"
28#define NODE_ONLINE_FMT \
29	"%s/devices/system/node/online"
30#define NODE_MEMINFO_FMT \
31	"%s/devices/system/node/node%d/meminfo"
32#define NODE_CPULIST_FMT \
33	"%s/devices/system/node/node%d/cpulist"
34
35static int build_cpu_topology(struct cpu_topology *tp, int cpu)
36{
37	FILE *fp;
38	char filename[MAXPATHLEN];
39	char *buf = NULL, *p;
40	size_t len = 0;
41	ssize_t sret;
42	u32 i = 0;
43	int ret = -1;
44
45	scnprintf(filename, MAXPATHLEN, PACKAGE_CPUS_FMT,
46		  sysfs__mountpoint(), cpu);
47	if (access(filename, F_OK) == -1) {
48		scnprintf(filename, MAXPATHLEN, PACKAGE_CPUS_FMT_OLD,
49			sysfs__mountpoint(), cpu);
50	}
51	fp = fopen(filename, "r");
52	if (!fp)
53		goto try_dies;
54
55	sret = getline(&buf, &len, fp);
56	fclose(fp);
57	if (sret <= 0)
58		goto try_dies;
59
60	p = strchr(buf, '\n');
61	if (p)
62		*p = '\0';
63
64	for (i = 0; i < tp->package_cpus_lists; i++) {
65		if (!strcmp(buf, tp->package_cpus_list[i]))
66			break;
67	}
68	if (i == tp->package_cpus_lists) {
69		tp->package_cpus_list[i] = buf;
70		tp->package_cpus_lists++;
71		buf = NULL;
72		len = 0;
73	}
74	ret = 0;
75
76try_dies:
77	if (!tp->die_cpus_list)
78		goto try_threads;
79
80	scnprintf(filename, MAXPATHLEN, DIE_CPUS_FMT,
81		  sysfs__mountpoint(), cpu);
82	fp = fopen(filename, "r");
83	if (!fp)
84		goto try_threads;
85
86	sret = getline(&buf, &len, fp);
87	fclose(fp);
88	if (sret <= 0)
89		goto try_threads;
90
91	p = strchr(buf, '\n');
92	if (p)
93		*p = '\0';
94
95	for (i = 0; i < tp->die_cpus_lists; i++) {
96		if (!strcmp(buf, tp->die_cpus_list[i]))
97			break;
98	}
99	if (i == tp->die_cpus_lists) {
100		tp->die_cpus_list[i] = buf;
101		tp->die_cpus_lists++;
102		buf = NULL;
103		len = 0;
104	}
105	ret = 0;
106
107try_threads:
108	scnprintf(filename, MAXPATHLEN, CORE_CPUS_FMT,
109		  sysfs__mountpoint(), cpu);
110	if (access(filename, F_OK) == -1) {
111		scnprintf(filename, MAXPATHLEN, CORE_CPUS_FMT_OLD,
112			  sysfs__mountpoint(), cpu);
113	}
114	fp = fopen(filename, "r");
115	if (!fp)
116		goto done;
117
118	if (getline(&buf, &len, fp) <= 0)
119		goto done;
120
121	p = strchr(buf, '\n');
122	if (p)
123		*p = '\0';
124
125	for (i = 0; i < tp->core_cpus_lists; i++) {
126		if (!strcmp(buf, tp->core_cpus_list[i]))
127			break;
128	}
129	if (i == tp->core_cpus_lists) {
130		tp->core_cpus_list[i] = buf;
131		tp->core_cpus_lists++;
132		buf = NULL;
133	}
134	ret = 0;
135done:
136	if (fp)
137		fclose(fp);
138	free(buf);
139	return ret;
140}
141
142void cpu_topology__delete(struct cpu_topology *tp)
143{
144	u32 i;
145
146	if (!tp)
147		return;
148
149	for (i = 0 ; i < tp->package_cpus_lists; i++)
150		zfree(&tp->package_cpus_list[i]);
151
152	for (i = 0 ; i < tp->die_cpus_lists; i++)
153		zfree(&tp->die_cpus_list[i]);
154
155	for (i = 0 ; i < tp->core_cpus_lists; i++)
156		zfree(&tp->core_cpus_list[i]);
157
158	free(tp);
159}
160
161bool cpu_topology__smt_on(const struct cpu_topology *topology)
162{
163	for (u32 i = 0; i < topology->core_cpus_lists; i++) {
164		const char *cpu_list = topology->core_cpus_list[i];
165
166		/*
167		 * If there is a need to separate siblings in a core then SMT is
168		 * enabled.
169		 */
170		if (strchr(cpu_list, ',') || strchr(cpu_list, '-'))
171			return true;
172	}
173	return false;
174}
175
176bool cpu_topology__core_wide(const struct cpu_topology *topology,
177			     const char *user_requested_cpu_list)
178{
179	struct perf_cpu_map *user_requested_cpus;
180
181	/*
182	 * If user_requested_cpu_list is empty then all CPUs are recorded and so
183	 * core_wide is true.
184	 */
185	if (!user_requested_cpu_list)
186		return true;
187
188	user_requested_cpus = perf_cpu_map__new(user_requested_cpu_list);
189	/* Check that every user requested CPU is the complete set of SMT threads on a core. */
190	for (u32 i = 0; i < topology->core_cpus_lists; i++) {
191		const char *core_cpu_list = topology->core_cpus_list[i];
192		struct perf_cpu_map *core_cpus = perf_cpu_map__new(core_cpu_list);
193		struct perf_cpu cpu;
194		int idx;
195		bool has_first, first = true;
196
197		perf_cpu_map__for_each_cpu(cpu, idx, core_cpus) {
198			if (first) {
199				has_first = perf_cpu_map__has(user_requested_cpus, cpu);
200				first = false;
201			} else {
202				/*
203				 * If the first core CPU is user requested then
204				 * all subsequent CPUs in the core must be user
205				 * requested too. If the first CPU isn't user
206				 * requested then none of the others must be
207				 * too.
208				 */
209				if (perf_cpu_map__has(user_requested_cpus, cpu) != has_first) {
210					perf_cpu_map__put(core_cpus);
211					perf_cpu_map__put(user_requested_cpus);
212					return false;
213				}
214			}
215		}
216		perf_cpu_map__put(core_cpus);
217	}
218	perf_cpu_map__put(user_requested_cpus);
219	return true;
220}
221
222static bool has_die_topology(void)
223{
224	char filename[MAXPATHLEN];
225	struct utsname uts;
226
227	if (uname(&uts) < 0)
228		return false;
229
230	if (strncmp(uts.machine, "x86_64", 6) &&
231	    strncmp(uts.machine, "s390x", 5))
232		return false;
233
234	scnprintf(filename, MAXPATHLEN, DIE_CPUS_FMT,
235		  sysfs__mountpoint(), 0);
236	if (access(filename, F_OK) == -1)
237		return false;
238
239	return true;
240}
241
242const struct cpu_topology *online_topology(void)
243{
244	static const struct cpu_topology *topology;
245
246	if (!topology) {
247		topology = cpu_topology__new();
248		if (!topology) {
249			pr_err("Error creating CPU topology");
250			abort();
251		}
252	}
253	return topology;
254}
255
256struct cpu_topology *cpu_topology__new(void)
257{
258	struct cpu_topology *tp = NULL;
259	void *addr;
260	u32 nr, i, nr_addr;
261	size_t sz;
262	long ncpus;
263	int ret = -1;
264	struct perf_cpu_map *map;
265	bool has_die = has_die_topology();
266
267	ncpus = cpu__max_present_cpu().cpu;
268
269	/* build online CPU map */
270	map = perf_cpu_map__new_online_cpus();
271	if (map == NULL) {
272		pr_debug("failed to get system cpumap\n");
273		return NULL;
274	}
275
276	nr = (u32)(ncpus & UINT_MAX);
277
278	sz = nr * sizeof(char *);
279	if (has_die)
280		nr_addr = 3;
281	else
282		nr_addr = 2;
283	addr = calloc(1, sizeof(*tp) + nr_addr * sz);
284	if (!addr)
285		goto out_free;
286
287	tp = addr;
288	addr += sizeof(*tp);
289	tp->package_cpus_list = addr;
290	addr += sz;
291	if (has_die) {
292		tp->die_cpus_list = addr;
293		addr += sz;
294	}
295	tp->core_cpus_list = addr;
296
297	for (i = 0; i < nr; i++) {
298		if (!perf_cpu_map__has(map, (struct perf_cpu){ .cpu = i }))
299			continue;
300
301		ret = build_cpu_topology(tp, i);
302		if (ret < 0)
303			break;
304	}
305
306out_free:
307	perf_cpu_map__put(map);
308	if (ret) {
309		cpu_topology__delete(tp);
310		tp = NULL;
311	}
312	return tp;
313}
314
315static int load_numa_node(struct numa_topology_node *node, int nr)
316{
317	char str[MAXPATHLEN];
318	char field[32];
319	char *buf = NULL, *p;
320	size_t len = 0;
321	int ret = -1;
322	FILE *fp;
323	u64 mem;
324
325	node->node = (u32) nr;
326
327	scnprintf(str, MAXPATHLEN, NODE_MEMINFO_FMT,
328		  sysfs__mountpoint(), nr);
329	fp = fopen(str, "r");
330	if (!fp)
331		return -1;
332
333	while (getline(&buf, &len, fp) > 0) {
334		/* skip over invalid lines */
335		if (!strchr(buf, ':'))
336			continue;
337		if (sscanf(buf, "%*s %*d %31s %"PRIu64, field, &mem) != 2)
338			goto err;
339		if (!strcmp(field, "MemTotal:"))
340			node->mem_total = mem;
341		if (!strcmp(field, "MemFree:"))
342			node->mem_free = mem;
343		if (node->mem_total && node->mem_free)
344			break;
345	}
346
347	fclose(fp);
348	fp = NULL;
349
350	scnprintf(str, MAXPATHLEN, NODE_CPULIST_FMT,
351		  sysfs__mountpoint(), nr);
352
353	fp = fopen(str, "r");
354	if (!fp)
355		return -1;
356
357	if (getline(&buf, &len, fp) <= 0)
358		goto err;
359
360	p = strchr(buf, '\n');
361	if (p)
362		*p = '\0';
363
364	node->cpus = buf;
365	fclose(fp);
366	return 0;
367
368err:
369	free(buf);
370	if (fp)
371		fclose(fp);
372	return ret;
373}
374
375struct numa_topology *numa_topology__new(void)
376{
377	struct perf_cpu_map *node_map = NULL;
378	struct numa_topology *tp = NULL;
379	char path[MAXPATHLEN];
380	char *buf = NULL;
381	size_t len = 0;
382	u32 nr, i;
383	FILE *fp;
384	char *c;
385
386	scnprintf(path, MAXPATHLEN, NODE_ONLINE_FMT,
387		  sysfs__mountpoint());
388
389	fp = fopen(path, "r");
390	if (!fp)
391		return NULL;
392
393	if (getline(&buf, &len, fp) <= 0)
394		goto out;
395
396	c = strchr(buf, '\n');
397	if (c)
398		*c = '\0';
399
400	node_map = perf_cpu_map__new(buf);
401	if (!node_map)
402		goto out;
403
404	nr = (u32) perf_cpu_map__nr(node_map);
405
406	tp = zalloc(sizeof(*tp) + sizeof(tp->nodes[0])*nr);
407	if (!tp)
408		goto out;
409
410	tp->nr = nr;
411
412	for (i = 0; i < nr; i++) {
413		if (load_numa_node(&tp->nodes[i], perf_cpu_map__cpu(node_map, i).cpu)) {
414			numa_topology__delete(tp);
415			tp = NULL;
416			break;
417		}
418	}
419
420out:
421	free(buf);
422	fclose(fp);
423	perf_cpu_map__put(node_map);
424	return tp;
425}
426
427void numa_topology__delete(struct numa_topology *tp)
428{
429	u32 i;
430
431	for (i = 0; i < tp->nr; i++)
432		zfree(&tp->nodes[i].cpus);
433
434	free(tp);
435}
436
437static int load_hybrid_node(struct hybrid_topology_node *node,
438			    struct perf_pmu *pmu)
439{
440	char *buf = NULL, *p;
441	FILE *fp;
442	size_t len = 0;
443
444	node->pmu_name = strdup(pmu->name);
445	if (!node->pmu_name)
446		return -1;
447
448	fp = perf_pmu__open_file(pmu, "cpus");
449	if (!fp)
450		goto err;
451
452	if (getline(&buf, &len, fp) <= 0) {
453		fclose(fp);
454		goto err;
455	}
456
457	p = strchr(buf, '\n');
458	if (p)
459		*p = '\0';
460
461	fclose(fp);
462	node->cpus = buf;
463	return 0;
464
465err:
466	zfree(&node->pmu_name);
467	free(buf);
468	return -1;
469}
470
471struct hybrid_topology *hybrid_topology__new(void)
472{
473	struct perf_pmu *pmu = NULL;
474	struct hybrid_topology *tp = NULL;
475	int nr = perf_pmus__num_core_pmus(), i = 0;
476
477	if (nr <= 1)
478		return NULL;
479
480	tp = zalloc(sizeof(*tp) + sizeof(tp->nodes[0]) * nr);
481	if (!tp)
482		return NULL;
483
484	tp->nr = nr;
485	while ((pmu = perf_pmus__scan_core(pmu)) != NULL) {
486		if (load_hybrid_node(&tp->nodes[i], pmu)) {
487			hybrid_topology__delete(tp);
488			return NULL;
489		}
490		i++;
491	}
492
493	return tp;
494}
495
496void hybrid_topology__delete(struct hybrid_topology *tp)
497{
498	u32 i;
499
500	for (i = 0; i < tp->nr; i++) {
501		zfree(&tp->nodes[i].pmu_name);
502		zfree(&tp->nodes[i].cpus);
503	}
504
505	free(tp);
506}
507