162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci#include <errno.h>
362306a36Sopenharmony_ci#include <inttypes.h>
462306a36Sopenharmony_ci#include <string.h>
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#include <linux/compiler.h>
762306a36Sopenharmony_ci#include <linux/perf_event.h>
862306a36Sopenharmony_ci#include <linux/stddef.h>
962306a36Sopenharmony_ci#include <linux/types.h>
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include <asm/barrier.h>
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#include "event.h"
1462306a36Sopenharmony_ci#include "synthetic-events.h"
1562306a36Sopenharmony_ci#include "debug.h"
1662306a36Sopenharmony_ci#include "tsc.h"
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ciu64 perf_time_to_tsc(u64 ns, struct perf_tsc_conversion *tc)
1962306a36Sopenharmony_ci{
2062306a36Sopenharmony_ci	u64 t, quot, rem;
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci	t = ns - tc->time_zero;
2362306a36Sopenharmony_ci	quot = t / tc->time_mult;
2462306a36Sopenharmony_ci	rem  = t % tc->time_mult;
2562306a36Sopenharmony_ci	return (quot << tc->time_shift) +
2662306a36Sopenharmony_ci	       (rem << tc->time_shift) / tc->time_mult;
2762306a36Sopenharmony_ci}
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ciu64 tsc_to_perf_time(u64 cyc, struct perf_tsc_conversion *tc)
3062306a36Sopenharmony_ci{
3162306a36Sopenharmony_ci	u64 quot, rem;
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci	if (tc->cap_user_time_short)
3462306a36Sopenharmony_ci		cyc = tc->time_cycles +
3562306a36Sopenharmony_ci			((cyc - tc->time_cycles) & tc->time_mask);
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci	quot = cyc >> tc->time_shift;
3862306a36Sopenharmony_ci	rem  = cyc & (((u64)1 << tc->time_shift) - 1);
3962306a36Sopenharmony_ci	return tc->time_zero + quot * tc->time_mult +
4062306a36Sopenharmony_ci	       ((rem * tc->time_mult) >> tc->time_shift);
4162306a36Sopenharmony_ci}
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ciint perf_read_tsc_conversion(const struct perf_event_mmap_page *pc,
4462306a36Sopenharmony_ci			     struct perf_tsc_conversion *tc)
4562306a36Sopenharmony_ci{
4662306a36Sopenharmony_ci	u32 seq;
4762306a36Sopenharmony_ci	int i = 0;
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	while (1) {
5062306a36Sopenharmony_ci		seq = pc->lock;
5162306a36Sopenharmony_ci		rmb();
5262306a36Sopenharmony_ci		tc->time_mult = pc->time_mult;
5362306a36Sopenharmony_ci		tc->time_shift = pc->time_shift;
5462306a36Sopenharmony_ci		tc->time_zero = pc->time_zero;
5562306a36Sopenharmony_ci		tc->time_cycles = pc->time_cycles;
5662306a36Sopenharmony_ci		tc->time_mask = pc->time_mask;
5762306a36Sopenharmony_ci		tc->cap_user_time_zero = pc->cap_user_time_zero;
5862306a36Sopenharmony_ci		tc->cap_user_time_short	= pc->cap_user_time_short;
5962306a36Sopenharmony_ci		rmb();
6062306a36Sopenharmony_ci		if (pc->lock == seq && !(seq & 1))
6162306a36Sopenharmony_ci			break;
6262306a36Sopenharmony_ci		if (++i > 10000) {
6362306a36Sopenharmony_ci			pr_debug("failed to get perf_event_mmap_page lock\n");
6462306a36Sopenharmony_ci			return -EINVAL;
6562306a36Sopenharmony_ci		}
6662306a36Sopenharmony_ci	}
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	if (!tc->cap_user_time_zero)
6962306a36Sopenharmony_ci		return -EOPNOTSUPP;
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	return 0;
7262306a36Sopenharmony_ci}
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ciint perf_event__synth_time_conv(const struct perf_event_mmap_page *pc,
7562306a36Sopenharmony_ci				struct perf_tool *tool,
7662306a36Sopenharmony_ci				perf_event__handler_t process,
7762306a36Sopenharmony_ci				struct machine *machine)
7862306a36Sopenharmony_ci{
7962306a36Sopenharmony_ci	union perf_event event = {
8062306a36Sopenharmony_ci		.time_conv = {
8162306a36Sopenharmony_ci			.header = {
8262306a36Sopenharmony_ci				.type = PERF_RECORD_TIME_CONV,
8362306a36Sopenharmony_ci				.size = sizeof(struct perf_record_time_conv),
8462306a36Sopenharmony_ci			},
8562306a36Sopenharmony_ci		},
8662306a36Sopenharmony_ci	};
8762306a36Sopenharmony_ci	struct perf_tsc_conversion tc;
8862306a36Sopenharmony_ci	int err;
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	if (!pc)
9162306a36Sopenharmony_ci		return 0;
9262306a36Sopenharmony_ci	err = perf_read_tsc_conversion(pc, &tc);
9362306a36Sopenharmony_ci	if (err == -EOPNOTSUPP)
9462306a36Sopenharmony_ci		return 0;
9562306a36Sopenharmony_ci	if (err)
9662306a36Sopenharmony_ci		return err;
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	pr_debug2("Synthesizing TSC conversion information\n");
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	event.time_conv.time_mult  = tc.time_mult;
10162306a36Sopenharmony_ci	event.time_conv.time_shift = tc.time_shift;
10262306a36Sopenharmony_ci	event.time_conv.time_zero  = tc.time_zero;
10362306a36Sopenharmony_ci	event.time_conv.time_cycles = tc.time_cycles;
10462306a36Sopenharmony_ci	event.time_conv.time_mask = tc.time_mask;
10562306a36Sopenharmony_ci	event.time_conv.cap_user_time_zero = tc.cap_user_time_zero;
10662306a36Sopenharmony_ci	event.time_conv.cap_user_time_short = tc.cap_user_time_short;
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	return process(tool, &event, NULL, machine);
10962306a36Sopenharmony_ci}
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ciu64 __weak rdtsc(void)
11262306a36Sopenharmony_ci{
11362306a36Sopenharmony_ci	return 0;
11462306a36Sopenharmony_ci}
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_cisize_t perf_event__fprintf_time_conv(union perf_event *event, FILE *fp)
11762306a36Sopenharmony_ci{
11862306a36Sopenharmony_ci	struct perf_record_time_conv *tc = (struct perf_record_time_conv *)event;
11962306a36Sopenharmony_ci	size_t ret;
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	ret  = fprintf(fp, "\n... Time Shift      %" PRI_lu64 "\n", tc->time_shift);
12262306a36Sopenharmony_ci	ret += fprintf(fp, "... Time Muliplier  %" PRI_lu64 "\n", tc->time_mult);
12362306a36Sopenharmony_ci	ret += fprintf(fp, "... Time Zero       %" PRI_lu64 "\n", tc->time_zero);
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	/*
12662306a36Sopenharmony_ci	 * The event TIME_CONV was extended for the fields from "time_cycles"
12762306a36Sopenharmony_ci	 * when supported cap_user_time_short, for backward compatibility,
12862306a36Sopenharmony_ci	 * prints the extended fields only if they are contained in the event.
12962306a36Sopenharmony_ci	 */
13062306a36Sopenharmony_ci	if (event_contains(*tc, time_cycles)) {
13162306a36Sopenharmony_ci		ret += fprintf(fp, "... Time Cycles     %" PRI_lu64 "\n",
13262306a36Sopenharmony_ci			       tc->time_cycles);
13362306a36Sopenharmony_ci		ret += fprintf(fp, "... Time Mask       %#" PRI_lx64 "\n",
13462306a36Sopenharmony_ci			       tc->time_mask);
13562306a36Sopenharmony_ci		ret += fprintf(fp, "... Cap Time Zero   %" PRId32 "\n",
13662306a36Sopenharmony_ci			       tc->cap_user_time_zero);
13762306a36Sopenharmony_ci		ret += fprintf(fp, "... Cap Time Short  %" PRId32 "\n",
13862306a36Sopenharmony_ci			       tc->cap_user_time_short);
13962306a36Sopenharmony_ci	}
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	return ret;
14262306a36Sopenharmony_ci}
143