// SPDX-License-Identifier: GPL-2.0 /* * Re-arm a sampling event from its overflow signal handler with * PERF_EVENT_IOC_REFRESH and check that every overflow is delivered. */ #include #include #include #include #include #include #include #include #include #include #include "cloexec.h" #include "debug.h" #include "tests.h" #include "tp_pmu.h" #include // page_size #include "../perf-sys.h" #define NR_EVENTS 1000 /* A real-time signal is queued, so no overflow can be coalesced away. */ #define REFRESH_SIGNAL (SIGRTMIN + 1) static volatile sig_atomic_t overflows, hups, refresh_errno; static void sig_handler(int signum __maybe_unused, siginfo_t *info, void *uc __maybe_unused) { int saved_errno = errno; overflows++; if (info->si_code == POLL_HUP) hups++; if (ioctl(info->si_fd, PERF_EVENT_IOC_REFRESH, 1) < 0) refresh_errno = errno; errno = saved_errno; } /* Count overflow signals for NR_EVENTS calls of trigger(), period 1. */ static int count_overflows(struct perf_event_attr *attr, void (*trigger)(int i)) { struct sigaction sa, old_sa; uint64_t count = 0; int fd, i, err = TEST_FAIL; attr->size = sizeof(*attr); attr->sample_period = 1; attr->disabled = 1; fd = sys_perf_event_open(attr, 0, -1, -1, perf_event_open_cloexec_flag()); if (fd < 0) { int open_errno = errno; pr_debug("failed opening event: %s\n", strerror(open_errno)); return open_errno == EACCES || open_errno == EPERM ? TEST_SKIP : TEST_FAIL; } memset(&sa, 0, sizeof(sa)); sa.sa_sigaction = sig_handler; sa.sa_flags = SA_SIGINFO; if (sigaction(REFRESH_SIGNAL, &sa, &old_sa) < 0) { pr_debug("failed setting up signal handler\n"); close(fd); return TEST_FAIL; } if (fcntl(fd, F_SETFL, O_RDWR | O_NONBLOCK | O_ASYNC) < 0 || fcntl(fd, F_SETSIG, REFRESH_SIGNAL) < 0 || fcntl(fd, F_SETOWN, getpid()) < 0) { pr_debug("failed setting up signal delivery: %s\n", strerror(errno)); goto out; } overflows = 0; hups = 0; refresh_errno = 0; if (ioctl(fd, PERF_EVENT_IOC_REFRESH, 1) < 0) { pr_debug("PERF_EVENT_IOC_REFRESH failed: %s\n", strerror(errno)); goto out; } for (i = 0; i < NR_EVENTS; i++) trigger(i); ioctl(fd, PERF_EVENT_IOC_DISABLE, 0); if (read(fd, &count, sizeof(count)) != sizeof(count)) pr_debug("failed to read the event count\n"); pr_debug("%d events, count %" PRIu64 ", %d overflows (%d POLL_HUP), re-arm errno %d\n", NR_EVENTS, count, overflows, hups, refresh_errno); /* Unrelated page faults can only add overflows, never hide one. */ if (overflows >= NR_EVENTS) err = TEST_OK; out: /* Close first: a late signal must still find our handler. */ close(fd); sigaction(REFRESH_SIGNAL, &old_sa, NULL); return err; } static char *pages; static void touch_page(int i) { pages[(long)i * page_size] = 1; } static int test__event_refresh_sw(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { struct perf_event_attr attr = { .type = PERF_TYPE_SOFTWARE, .config = PERF_COUNT_SW_PAGE_FAULTS, }; int err; pages = mmap(NULL, (long)NR_EVENTS * page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (pages == MAP_FAILED) return TEST_FAIL; /* One fault per touched page: a huge page would cover many of them. */ madvise(pages, (long)NR_EVENTS * page_size, MADV_NOHUGEPAGE); err = count_overflows(&attr, touch_page); munmap(pages, (long)NR_EVENTS * page_size); return err; } static void call_getppid(int i __maybe_unused) { syscall(SYS_getppid); } static int test__event_refresh_tp(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { struct perf_event_attr attr = { .type = PERF_TYPE_TRACEPOINT, }; int id = tp_pmu__id("syscalls", "sys_enter_getppid"); if (id < 0) { pr_debug("no syscalls:sys_enter_getppid tracepoint\n"); return TEST_SKIP; } attr.config = id; return count_overflows(&attr, call_getppid); } static struct test_case tests__event_refresh[] = { TEST_CASE_REASON("Software event", event_refresh_sw, "permissions"), TEST_CASE_REASON("Tracepoint event", event_refresh_tp, "permissions"), { .name = NULL, } }; struct test_suite suite__event_refresh = { .desc = "Event re-armed with PERF_EVENT_IOC_REFRESH", .test_cases = tests__event_refresh, };