18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci#include <errno.h>
38c2ecf20Sopenharmony_ci#include <unistd.h>
48c2ecf20Sopenharmony_ci#include <stdlib.h>
58c2ecf20Sopenharmony_ci#include <signal.h>
68c2ecf20Sopenharmony_ci#include <sys/mman.h>
78c2ecf20Sopenharmony_ci#include <sys/types.h>
88c2ecf20Sopenharmony_ci#include <sys/wait.h>
98c2ecf20Sopenharmony_ci#include <linux/string.h>
108c2ecf20Sopenharmony_ci#include <linux/types.h>
118c2ecf20Sopenharmony_ci#include "perf-sys.h"
128c2ecf20Sopenharmony_ci#include "debug.h"
138c2ecf20Sopenharmony_ci#include "tests/tests.h"
148c2ecf20Sopenharmony_ci#include "cloexec.h"
158c2ecf20Sopenharmony_ci#include "event.h"
168c2ecf20Sopenharmony_ci#include <internal/lib.h> // page_size
178c2ecf20Sopenharmony_ci#include "arch-tests.h"
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_cistatic u64 rdpmc(unsigned int counter)
208c2ecf20Sopenharmony_ci{
218c2ecf20Sopenharmony_ci	unsigned int low, high;
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci	asm volatile("rdpmc" : "=a" (low), "=d" (high) : "c" (counter));
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci	return low | ((u64)high) << 32;
268c2ecf20Sopenharmony_ci}
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_cistatic u64 rdtsc(void)
298c2ecf20Sopenharmony_ci{
308c2ecf20Sopenharmony_ci	unsigned int low, high;
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_ci	asm volatile("rdtsc" : "=a" (low), "=d" (high));
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_ci	return low | ((u64)high) << 32;
358c2ecf20Sopenharmony_ci}
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_cistatic u64 mmap_read_self(void *addr)
388c2ecf20Sopenharmony_ci{
398c2ecf20Sopenharmony_ci	struct perf_event_mmap_page *pc = addr;
408c2ecf20Sopenharmony_ci	u32 seq, idx, time_mult = 0, time_shift = 0;
418c2ecf20Sopenharmony_ci	u64 count, cyc = 0, time_offset = 0, enabled, running, delta;
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_ci	do {
448c2ecf20Sopenharmony_ci		seq = pc->lock;
458c2ecf20Sopenharmony_ci		barrier();
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci		enabled = pc->time_enabled;
488c2ecf20Sopenharmony_ci		running = pc->time_running;
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_ci		if (enabled != running) {
518c2ecf20Sopenharmony_ci			cyc = rdtsc();
528c2ecf20Sopenharmony_ci			time_mult = pc->time_mult;
538c2ecf20Sopenharmony_ci			time_shift = pc->time_shift;
548c2ecf20Sopenharmony_ci			time_offset = pc->time_offset;
558c2ecf20Sopenharmony_ci		}
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ci		idx = pc->index;
588c2ecf20Sopenharmony_ci		count = pc->offset;
598c2ecf20Sopenharmony_ci		if (idx)
608c2ecf20Sopenharmony_ci			count += rdpmc(idx - 1);
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci		barrier();
638c2ecf20Sopenharmony_ci	} while (pc->lock != seq);
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_ci	if (enabled != running) {
668c2ecf20Sopenharmony_ci		u64 quot, rem;
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_ci		quot = (cyc >> time_shift);
698c2ecf20Sopenharmony_ci		rem = cyc & (((u64)1 << time_shift) - 1);
708c2ecf20Sopenharmony_ci		delta = time_offset + quot * time_mult +
718c2ecf20Sopenharmony_ci			((rem * time_mult) >> time_shift);
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci		enabled += delta;
748c2ecf20Sopenharmony_ci		if (idx)
758c2ecf20Sopenharmony_ci			running += delta;
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci		quot = count / running;
788c2ecf20Sopenharmony_ci		rem = count % running;
798c2ecf20Sopenharmony_ci		count = quot * enabled + (rem * enabled) / running;
808c2ecf20Sopenharmony_ci	}
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_ci	return count;
838c2ecf20Sopenharmony_ci}
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci/*
868c2ecf20Sopenharmony_ci * If the RDPMC instruction faults then signal this back to the test parent task:
878c2ecf20Sopenharmony_ci */
888c2ecf20Sopenharmony_cistatic void segfault_handler(int sig __maybe_unused,
898c2ecf20Sopenharmony_ci			     siginfo_t *info __maybe_unused,
908c2ecf20Sopenharmony_ci			     void *uc __maybe_unused)
918c2ecf20Sopenharmony_ci{
928c2ecf20Sopenharmony_ci	exit(-1);
938c2ecf20Sopenharmony_ci}
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_cistatic int __test__rdpmc(void)
968c2ecf20Sopenharmony_ci{
978c2ecf20Sopenharmony_ci	volatile int tmp = 0;
988c2ecf20Sopenharmony_ci	u64 i, loops = 1000;
998c2ecf20Sopenharmony_ci	int n;
1008c2ecf20Sopenharmony_ci	int fd;
1018c2ecf20Sopenharmony_ci	void *addr;
1028c2ecf20Sopenharmony_ci	struct perf_event_attr attr = {
1038c2ecf20Sopenharmony_ci		.type = PERF_TYPE_HARDWARE,
1048c2ecf20Sopenharmony_ci		.config = PERF_COUNT_HW_INSTRUCTIONS,
1058c2ecf20Sopenharmony_ci		.exclude_kernel = 1,
1068c2ecf20Sopenharmony_ci	};
1078c2ecf20Sopenharmony_ci	u64 delta_sum = 0;
1088c2ecf20Sopenharmony_ci        struct sigaction sa;
1098c2ecf20Sopenharmony_ci	char sbuf[STRERR_BUFSIZE];
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_ci	sigfillset(&sa.sa_mask);
1128c2ecf20Sopenharmony_ci	sa.sa_sigaction = segfault_handler;
1138c2ecf20Sopenharmony_ci	sa.sa_flags = 0;
1148c2ecf20Sopenharmony_ci	sigaction(SIGSEGV, &sa, NULL);
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci	fd = sys_perf_event_open(&attr, 0, -1, -1,
1178c2ecf20Sopenharmony_ci				 perf_event_open_cloexec_flag());
1188c2ecf20Sopenharmony_ci	if (fd < 0) {
1198c2ecf20Sopenharmony_ci		pr_err("Error: sys_perf_event_open() syscall returned "
1208c2ecf20Sopenharmony_ci		       "with %d (%s)\n", fd,
1218c2ecf20Sopenharmony_ci		       str_error_r(errno, sbuf, sizeof(sbuf)));
1228c2ecf20Sopenharmony_ci		return -1;
1238c2ecf20Sopenharmony_ci	}
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ci	addr = mmap(NULL, page_size, PROT_READ, MAP_SHARED, fd, 0);
1268c2ecf20Sopenharmony_ci	if (addr == (void *)(-1)) {
1278c2ecf20Sopenharmony_ci		pr_err("Error: mmap() syscall returned with (%s)\n",
1288c2ecf20Sopenharmony_ci		       str_error_r(errno, sbuf, sizeof(sbuf)));
1298c2ecf20Sopenharmony_ci		goto out_close;
1308c2ecf20Sopenharmony_ci	}
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci	for (n = 0; n < 6; n++) {
1338c2ecf20Sopenharmony_ci		u64 stamp, now, delta;
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci		stamp = mmap_read_self(addr);
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci		for (i = 0; i < loops; i++)
1388c2ecf20Sopenharmony_ci			tmp++;
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci		now = mmap_read_self(addr);
1418c2ecf20Sopenharmony_ci		loops *= 10;
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_ci		delta = now - stamp;
1448c2ecf20Sopenharmony_ci		pr_debug("%14d: %14Lu\n", n, (long long)delta);
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_ci		delta_sum += delta;
1478c2ecf20Sopenharmony_ci	}
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ci	munmap(addr, page_size);
1508c2ecf20Sopenharmony_ci	pr_debug("   ");
1518c2ecf20Sopenharmony_ciout_close:
1528c2ecf20Sopenharmony_ci	close(fd);
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_ci	if (!delta_sum)
1558c2ecf20Sopenharmony_ci		return -1;
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci	return 0;
1588c2ecf20Sopenharmony_ci}
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ciint test__rdpmc(struct test *test __maybe_unused, int subtest __maybe_unused)
1618c2ecf20Sopenharmony_ci{
1628c2ecf20Sopenharmony_ci	int status = 0;
1638c2ecf20Sopenharmony_ci	int wret = 0;
1648c2ecf20Sopenharmony_ci	int ret;
1658c2ecf20Sopenharmony_ci	int pid;
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci	pid = fork();
1688c2ecf20Sopenharmony_ci	if (pid < 0)
1698c2ecf20Sopenharmony_ci		return -1;
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_ci	if (!pid) {
1728c2ecf20Sopenharmony_ci		ret = __test__rdpmc();
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_ci		exit(ret);
1758c2ecf20Sopenharmony_ci	}
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ci	wret = waitpid(pid, &status, 0);
1788c2ecf20Sopenharmony_ci	if (wret < 0 || status)
1798c2ecf20Sopenharmony_ci		return -1;
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_ci	return 0;
1828c2ecf20Sopenharmony_ci}
183