Branch data Line data Source code
1 : : /* SPDX-License-Identifier: BSD-3-Clause
2 : : * Copyright(C) 2025 Marvell International Ltd.
3 : : */
4 : :
5 : : #include <errno.h>
6 : : #include <ctype.h>
7 : : #include <dirent.h>
8 : : #include <stdlib.h>
9 : : #include <unistd.h>
10 : : #include <sys/mman.h>
11 : : #include <sys/queue.h>
12 : : #include <sys/ioctl.h>
13 : : #include <sys/syscall.h>
14 : :
15 : : #include <eal_export.h>
16 : : #include <rte_bitops.h>
17 : : #include <rte_tailq.h>
18 : : #include <rte_log.h>
19 : :
20 : : #include "rte_pmu.h"
21 : : #include "pmu_private.h"
22 : :
23 : : #define EVENT_SOURCE_DEVICES_PATH "/sys/bus/event_source/devices"
24 : :
25 : : #define FIELD_PREP(m, v) (((uint64_t)(v) << (rte_ffs64(m) - 1)) & (m))
26 : :
27 [ - + ]: 254 : RTE_LOG_REGISTER_DEFAULT(rte_pmu_logtype, INFO)
28 : : #define RTE_LOGTYPE_PMU rte_pmu_logtype
29 : :
30 : : #define PMU_LOG(level, ...) \
31 : : RTE_LOG_LINE(level, PMU, ## __VA_ARGS__)
32 : :
33 : : /* A structure describing an event */
34 : : struct rte_pmu_event {
35 : : char *name;
36 : : unsigned int index;
37 : : TAILQ_ENTRY(rte_pmu_event) next;
38 : : };
39 : :
40 : : RTE_EXPORT_INTERNAL_SYMBOL(rte_pmu)
41 : : struct rte_pmu rte_pmu;
42 : :
43 : : /* Stubs for arch-specific functions */
44 : : #if !defined(RTE_PMU_SUPPORTED) || defined(RTE_ARCH_X86_64)
45 : : int
46 : 0 : pmu_arch_init(void)
47 : : {
48 : 0 : return 0;
49 : : }
50 : : void
51 : 0 : pmu_arch_fini(void)
52 : : {
53 : 0 : }
54 : : void
55 : 0 : pmu_arch_fixup_config(uint64_t __rte_unused config[3])
56 : : {
57 : 0 : }
58 : : #endif
59 : :
60 : : static int
61 : 0 : get_term_format(const char *name, int *num, uint64_t *mask)
62 : : {
63 : : char path[PATH_MAX];
64 : 0 : char *config = NULL;
65 : : int high, low, ret;
66 : : FILE *fp;
67 : :
68 : 0 : *num = *mask = 0;
69 : 0 : snprintf(path, sizeof(path), EVENT_SOURCE_DEVICES_PATH "/%s/format/%s", rte_pmu.name, name);
70 : 0 : fp = fopen(path, "r");
71 [ # # ]: 0 : if (fp == NULL)
72 : 0 : return -errno;
73 : :
74 : 0 : errno = 0;
75 : 0 : ret = fscanf(fp, "%m[^:]:%d-%d", &config, &low, &high);
76 [ # # ]: 0 : if (ret < 2) {
77 : : ret = -ENODATA;
78 : 0 : goto out;
79 : : }
80 [ # # ]: 0 : if (errno) {
81 : 0 : ret = -errno;
82 : 0 : goto out;
83 : : }
84 : :
85 [ # # ]: 0 : if (ret == 2)
86 : 0 : high = low;
87 : :
88 : 0 : *mask = RTE_GENMASK64(high, low);
89 : : /* Last digit should be [012]. If last digit is missing 0 is implied. */
90 : 0 : *num = config[strlen(config) - 1];
91 [ # # ]: 0 : *num = isdigit(*num) ? *num - '0' : 0;
92 : :
93 : : ret = 0;
94 : 0 : out:
95 : 0 : free(config);
96 : 0 : fclose(fp);
97 : :
98 : 0 : return ret;
99 : : }
100 : :
101 : : static int
102 : 0 : parse_event(char *buf, uint64_t config[3])
103 : : {
104 : : char *token, *term;
105 : : int num, ret, val;
106 : : uint64_t mask;
107 : : char *tmp;
108 : :
109 : 0 : config[0] = config[1] = config[2] = 0;
110 : :
111 : 0 : token = strtok_r(buf, ",", &tmp);
112 [ # # ]: 0 : while (token) {
113 : 0 : errno = 0;
114 : : /* <term>=<value> */
115 : 0 : ret = sscanf(token, "%m[^=]=%i", &term, &val);
116 [ # # ]: 0 : if (ret < 1)
117 : : return -ENODATA;
118 [ # # ]: 0 : if (errno)
119 : 0 : return -errno;
120 [ # # ]: 0 : if (ret == 1)
121 : 0 : val = 1;
122 : :
123 : 0 : ret = get_term_format(term, &num, &mask);
124 : 0 : free(term);
125 [ # # ]: 0 : if (ret)
126 : 0 : return ret;
127 : :
128 : 0 : config[num] |= FIELD_PREP(mask, val);
129 : 0 : token = strtok_r(NULL, ",", &tmp);
130 : : }
131 : :
132 : : return 0;
133 : : }
134 : :
135 : : static int
136 : 0 : get_event_config(const char *name, uint64_t config[3])
137 : : {
138 : : char path[PATH_MAX], buf[BUFSIZ];
139 : : FILE *fp;
140 : : int ret;
141 : :
142 : 0 : snprintf(path, sizeof(path), EVENT_SOURCE_DEVICES_PATH "/%s/events/%s", rte_pmu.name, name);
143 : 0 : fp = fopen(path, "r");
144 [ # # ]: 0 : if (fp == NULL)
145 : 0 : return -errno;
146 : :
147 : 0 : ret = fread(buf, 1, sizeof(buf), fp);
148 [ # # ]: 0 : if (ret == 0) {
149 : 0 : fclose(fp);
150 : :
151 : 0 : return -EINVAL;
152 : : }
153 : 0 : fclose(fp);
154 : 0 : buf[ret] = '\0';
155 : :
156 : 0 : return parse_event(buf, config);
157 : : }
158 : :
159 : : static int
160 : 0 : do_perf_event_open(uint64_t config[3], int group_fd)
161 : : {
162 : 0 : struct perf_event_attr attr = {
163 : : .size = sizeof(struct perf_event_attr),
164 : : .type = PERF_TYPE_RAW,
165 : : .exclude_kernel = 1,
166 : : .exclude_hv = 1,
167 : : .disabled = 1,
168 : 0 : .pinned = group_fd == -1,
169 : : };
170 : :
171 : 0 : pmu_arch_fixup_config(config);
172 : :
173 : 0 : attr.config = config[0];
174 : 0 : attr.config1 = config[1];
175 : 0 : attr.config2 = config[2];
176 : :
177 : 0 : return syscall(SYS_perf_event_open, &attr, 0, -1, group_fd, 0);
178 : : }
179 : :
180 : : static int
181 : 0 : open_events(struct rte_pmu_event_group *group)
182 : : {
183 : : struct rte_pmu_event *event;
184 : : uint64_t config[3];
185 : : int num = 0, ret;
186 : :
187 : : /* group leader gets created first, with fd = -1 */
188 : 0 : group->fds[0] = -1;
189 : :
190 [ # # ]: 0 : TAILQ_FOREACH(event, &rte_pmu.event_list, next) {
191 : 0 : ret = get_event_config(event->name, config);
192 [ # # ]: 0 : if (ret)
193 : 0 : continue;
194 : :
195 : 0 : ret = do_perf_event_open(config, group->fds[0]);
196 [ # # ]: 0 : if (ret == -1) {
197 : 0 : ret = -errno;
198 : 0 : goto out;
199 : : }
200 : :
201 : 0 : group->fds[event->index] = ret;
202 : 0 : num++;
203 : : }
204 : :
205 : : return 0;
206 : : out:
207 [ # # ]: 0 : for (--num; num >= 0; num--) {
208 : 0 : close(group->fds[num]);
209 : 0 : group->fds[num] = -1;
210 : : }
211 : :
212 : : return ret;
213 : : }
214 : :
215 : : /* Inspired by rte_mem_page_size() from /lib/eal/unix/eal_unix_memory.c */
216 : : static size_t
217 : 0 : mem_page_size(void)
218 : : {
219 : : static size_t page_size;
220 : :
221 [ # # ]: 0 : if (unlikely(page_size == 0)) {
222 : : /*
223 : : * When the sysconf value cannot be determined, sysconf()
224 : : * returns -1 without setting errno.
225 : : * To distinguish an indeterminate value from an error,
226 : : * clear errno before calling sysconf(), and check whether
227 : : * errno has been set if sysconf() returns -1.
228 : : */
229 : 0 : errno = 0;
230 : 0 : page_size = sysconf(_SC_PAGESIZE);
231 [ # # # # ]: 0 : if ((ssize_t)page_size < 0 && errno == 0)
232 : 0 : errno = ENOENT;
233 : : }
234 : :
235 : 0 : return page_size;
236 : : }
237 : :
238 : : static int
239 : 0 : mmap_events(struct rte_pmu_event_group *group)
240 : : {
241 : 0 : size_t page_size = mem_page_size();
242 : : unsigned int i;
243 : : void *addr;
244 : : int ret;
245 : :
246 [ # # ]: 0 : if ((ssize_t)page_size < 0)
247 : 0 : return -errno;
248 : :
249 [ # # ]: 0 : for (i = 0; i < rte_pmu.num_group_events; i++) {
250 : 0 : addr = mmap(0, page_size, PROT_READ, MAP_SHARED, group->fds[i], 0);
251 [ # # ]: 0 : if (addr == MAP_FAILED) {
252 : 0 : ret = -errno;
253 : 0 : goto out;
254 : : }
255 : :
256 : 0 : group->mmap_pages[i] = addr;
257 : : }
258 : :
259 : : return 0;
260 : : out:
261 [ # # ]: 0 : for (; i; i--) {
262 : 0 : munmap(group->mmap_pages[i - 1], page_size);
263 : 0 : group->mmap_pages[i - 1] = NULL;
264 : : }
265 : :
266 : : return ret;
267 : : }
268 : :
269 : : static void
270 : 0 : cleanup_events(struct rte_pmu_event_group *group)
271 : : {
272 : 0 : size_t page_size = mem_page_size();
273 : : unsigned int i;
274 : :
275 [ # # ]: 0 : if (group->fds[0] != -1)
276 : 0 : ioctl(group->fds[0], PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP);
277 : :
278 [ # # ]: 0 : for (i = 0; i < rte_pmu.num_group_events; i++) {
279 [ # # ]: 0 : if (group->mmap_pages[i]) {
280 [ # # ]: 0 : __rte_assume((ssize_t)page_size >= 0);
281 : 0 : munmap(group->mmap_pages[i], page_size);
282 : 0 : group->mmap_pages[i] = NULL;
283 : : }
284 : :
285 [ # # ]: 0 : if (group->fds[i] != -1) {
286 : 0 : close(group->fds[i]);
287 : 0 : group->fds[i] = -1;
288 : : }
289 : : }
290 : :
291 : 0 : group->enabled = false;
292 : 0 : }
293 : :
294 : : RTE_EXPORT_EXPERIMENTAL_SYMBOL(__rte_pmu_enable_group, 25.07)
295 : : int
296 : 0 : __rte_pmu_enable_group(struct rte_pmu_event_group *group)
297 : : {
298 : : int ret;
299 : :
300 [ # # ]: 0 : if (rte_pmu.num_group_events == 0)
301 : : return -ENODEV;
302 : :
303 : 0 : ret = open_events(group);
304 [ # # ]: 0 : if (ret)
305 : 0 : goto out;
306 : :
307 : 0 : ret = mmap_events(group);
308 [ # # ]: 0 : if (ret)
309 : 0 : goto out;
310 : :
311 [ # # ]: 0 : if (ioctl(group->fds[0], PERF_EVENT_IOC_RESET, PERF_IOC_FLAG_GROUP) == -1) {
312 : 0 : ret = -errno;
313 : 0 : goto out;
314 : : }
315 : :
316 [ # # ]: 0 : if (ioctl(group->fds[0], PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP) == -1) {
317 : 0 : ret = -errno;
318 : 0 : goto out;
319 : : }
320 : :
321 : 0 : group->enabled = true;
322 : :
323 : 0 : return 0;
324 : 0 : out:
325 : 0 : cleanup_events(group);
326 : :
327 : 0 : return ret;
328 : : }
329 : :
330 : : static int
331 : 0 : scan_pmus(void)
332 : : {
333 : : char path[PATH_MAX];
334 : : struct dirent *dent;
335 : : const char *name;
336 : : DIR *dirp;
337 : :
338 : 0 : dirp = opendir(EVENT_SOURCE_DEVICES_PATH);
339 [ # # ]: 0 : if (dirp == NULL)
340 : 0 : return -errno;
341 : :
342 [ # # ]: 0 : while ((dent = readdir(dirp))) {
343 : 0 : name = dent->d_name;
344 [ # # ]: 0 : if (name[0] == '.')
345 : 0 : continue;
346 : :
347 : : /* sysfs entry should either contain cpus or be a cpu */
348 [ # # ]: 0 : if (!strcmp(name, "cpu"))
349 : : break;
350 : :
351 : : snprintf(path, sizeof(path), EVENT_SOURCE_DEVICES_PATH "/%s/cpus", name);
352 [ # # ]: 0 : if (access(path, F_OK) == 0)
353 : : break;
354 : : }
355 : :
356 [ # # ]: 0 : if (dent) {
357 : 0 : rte_pmu.name = strdup(name);
358 [ # # ]: 0 : if (rte_pmu.name == NULL) {
359 : 0 : closedir(dirp);
360 : :
361 : 0 : return -ENOMEM;
362 : : }
363 : : }
364 : :
365 : 0 : closedir(dirp);
366 : :
367 [ # # ]: 0 : return rte_pmu.name ? 0 : -ENODEV;
368 : : }
369 : :
370 : : static struct rte_pmu_event *
371 : 0 : new_event(const char *name)
372 : : {
373 : : struct rte_pmu_event *event;
374 : :
375 : 0 : event = calloc(1, sizeof(*event));
376 [ # # ]: 0 : if (event == NULL)
377 : 0 : goto out;
378 : :
379 : 0 : event->name = strdup(name);
380 [ # # ]: 0 : if (event->name == NULL) {
381 : 0 : free(event);
382 : : event = NULL;
383 : : }
384 : :
385 : 0 : out:
386 : 0 : return event;
387 : : }
388 : :
389 : : static void
390 : : free_event(struct rte_pmu_event *event)
391 : : {
392 : 0 : free(event->name);
393 : 0 : free(event);
394 : : }
395 : :
396 : : RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_pmu_add_event, 25.07)
397 : : int
398 : 0 : rte_pmu_add_event(const char *name)
399 : : {
400 : : struct rte_pmu_event *event;
401 : : char path[PATH_MAX];
402 : :
403 [ # # ]: 0 : if (!rte_pmu.initialized) {
404 : 0 : PMU_LOG(ERR, "PMU is not initialized");
405 : 0 : return -ENODEV;
406 : : }
407 : :
408 [ # # ]: 0 : if (rte_pmu.num_group_events + 1 >= RTE_MAX_NUM_GROUP_EVENTS) {
409 : 0 : PMU_LOG(ERR, "Excessive number of events in a group (%d > %d)",
410 : : rte_pmu.num_group_events, RTE_MAX_NUM_GROUP_EVENTS);
411 : 0 : return -ENOSPC;
412 : : }
413 : :
414 : 0 : snprintf(path, sizeof(path), EVENT_SOURCE_DEVICES_PATH "/%s/events/%s", rte_pmu.name, name);
415 [ # # ]: 0 : if (access(path, R_OK)) {
416 : 0 : PMU_LOG(ERR, "Cannot access %s", path);
417 : 0 : return -ENODEV;
418 : : }
419 : :
420 [ # # ]: 0 : TAILQ_FOREACH(event, &rte_pmu.event_list, next) {
421 [ # # ]: 0 : if (strcmp(event->name, name))
422 : : continue;
423 : :
424 : 0 : return event->index;
425 : : }
426 : :
427 : 0 : event = new_event(name);
428 [ # # ]: 0 : if (event == NULL) {
429 : 0 : PMU_LOG(ERR, "Failed to create event %s", name);
430 : 0 : return -ENOMEM;
431 : : }
432 : :
433 : 0 : event->index = rte_pmu.num_group_events++;
434 : 0 : TAILQ_INSERT_TAIL(&rte_pmu.event_list, event, next);
435 : :
436 : 0 : return event->index;
437 : : }
438 : :
439 : : RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_pmu_init, 25.07)
440 : : int
441 : 0 : rte_pmu_init(void)
442 : : {
443 : : int ret;
444 : :
445 [ # # ]: 0 : if (rte_pmu.initialized)
446 : : return 0;
447 : :
448 : 0 : ret = scan_pmus();
449 [ # # ]: 0 : if (ret) {
450 : 0 : PMU_LOG(ERR, "Failed to scan for event sources");
451 : 0 : goto out;
452 : : }
453 : :
454 : 0 : ret = pmu_arch_init();
455 [ # # ]: 0 : if (ret) {
456 : 0 : PMU_LOG(ERR, "Failed to setup arch internals");
457 : 0 : goto out;
458 : : }
459 : :
460 : 0 : TAILQ_INIT(&rte_pmu.event_list);
461 : 0 : rte_pmu.initialized = 1;
462 : 0 : out:
463 [ # # ]: 0 : if (ret) {
464 : 0 : free(rte_pmu.name);
465 : 0 : rte_pmu.name = NULL;
466 : : }
467 : :
468 : : return ret;
469 : : }
470 : :
471 : : RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_pmu_fini, 25.07)
472 : : void
473 : 0 : rte_pmu_fini(void)
474 : : {
475 : : struct rte_pmu_event *event, *tmp_event;
476 : : struct rte_pmu_event_group *group;
477 : : unsigned int i;
478 : :
479 [ # # ]: 0 : if (!rte_pmu.initialized)
480 : : return;
481 : :
482 [ # # ]: 0 : RTE_TAILQ_FOREACH_SAFE(event, &rte_pmu.event_list, next, tmp_event) {
483 [ # # ]: 0 : TAILQ_REMOVE(&rte_pmu.event_list, event, next);
484 : : free_event(event);
485 : : }
486 : :
487 [ # # ]: 0 : for (i = 0; i < RTE_DIM(rte_pmu.event_groups); i++) {
488 : 0 : group = &rte_pmu.event_groups[i];
489 [ # # ]: 0 : if (!group->enabled)
490 : 0 : continue;
491 : :
492 : 0 : cleanup_events(group);
493 : : }
494 : :
495 : 0 : pmu_arch_fini();
496 : 0 : free(rte_pmu.name);
497 : 0 : rte_pmu.name = NULL;
498 : 0 : rte_pmu.num_group_events = 0;
499 : : }
|