// SPDX-License-Identifier: GPL-2.0-or-later

/*
 * Copyright (C) 2018 Intel Corporation
 * Author: Ammy Yi (ammy.yi@intel.com)
 */

/*
 * This test will check if Intel PT(Intel Processer Trace) full trace mode is
 * working.
 *
 * Intel CPU of 5th-generation Core (Broadwell) or newer is required for the test.
 *
 * kconfig requirement: CONFIG_PERF_EVENTS
 */

#include <sched.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "tst_test.h"
#include "lapi/syscalls.h"
#include "config.h"

#ifdef HAVE_STRUCT_PERF_EVENT_MMAP_PAGE_AUX_HEAD
# include <linux/perf_event.h>

#define PAGESIZE 4096
#define INTEL_PT_MEMSIZE (17*PAGESIZE)

#define BIT(nr)                 (1UL << (nr))

#define INTEL_PT_PATH "/sys/devices/intel_pt"
#define INTEL_PT_PMU_TYPE "/sys/devices/intel_pt/type"
#define INTEL_PT_FORMAT_TSC "/sys/devices/intel_pt/format/tsc"
#define INTEL_PT_FORMAT_NRT "/sys/devices/intel_pt/format/noretcomp"

//Intel PT event handle
int fde = -1;
//map head and size
uint64_t **bufm;
long buhsz;

static uint64_t **create_map(int fde, long bufsize)
{
	uint64_t **buf_ev;
	struct perf_event_mmap_page *pc;

	buf_ev = SAFE_MALLOC(2*sizeof(uint64_t *));
	buf_ev[0] = NULL;
	buf_ev[1] = NULL;
	buf_ev[0] = SAFE_MMAP(NULL, INTEL_PT_MEMSIZE, PROT_READ | PROT_WRITE,
							MAP_SHARED, fde, 0);

	pc = (struct perf_event_mmap_page *)buf_ev[0];
	pc->aux_offset = INTEL_PT_MEMSIZE;
	pc->aux_size = bufsize;
	buf_ev[1] = SAFE_MMAP(NULL, bufsize, PROT_READ | PROT_WRITE,
					MAP_SHARED, fde, INTEL_PT_MEMSIZE);
	return buf_ev;
}


int intel_pt_pmu_value(char *dir)
{
	char *value;
	int val = 0;
	char delims[] = ":";

	SAFE_FILE_SCANF(dir, "%m[^\n]", &value);
	if (strstr(value, delims) == NULL) {
		val = atoi(value);
	} else {
		strsep(&value, delims);
		val = atoi(value);
	}
	return val;
}

static void del_map(uint64_t **buf_ev, long bufsize)
{
	if (buf_ev) {
		if (buf_ev[0])
			munmap(buf_ev[0], INTEL_PT_MEMSIZE);
		if (buf_ev[1])
			munmap(buf_ev[1], bufsize);
	}

	free(buf_ev);
}

static void intel_pt_full_trace_check(void)
{
	uint64_t aux_head = 0;
	struct perf_event_mmap_page *pmp;
	/* enable tracing */
	SAFE_IOCTL(fde, PERF_EVENT_IOC_RESET);
	SAFE_IOCTL(fde, PERF_EVENT_IOC_ENABLE);

	/* stop tracing */
	SAFE_IOCTL(fde, PERF_EVENT_IOC_DISABLE);

	/* check if there is some trace generated */
	pmp = (struct perf_event_mmap_page *)bufm[0];
	aux_head = *(volatile uint64_t *)&pmp->aux_head;
	if (aux_head == 0) {
		tst_res(TFAIL, "There is no trace!");
		return;
	}

	tst_res(TPASS, "perf trace full mode is passed!");
}

static void setup(void)
{
	struct perf_event_attr attr = {};

	buhsz = 2 * PAGESIZE;
	if (access(INTEL_PT_PATH, F_OK)) {
		tst_brk(TCONF,
			"Requires Intel Core 5th+ generation (Broadwell and newer)"
			" and CONFIG_PERF_EVENTS enabled.");
	}

	/* set attr for Intel PT trace */
	attr.type	= intel_pt_pmu_value(INTEL_PT_PMU_TYPE);
	attr.read_format = PERF_FORMAT_ID | PERF_FORMAT_TOTAL_TIME_RUNNING |
				PERF_FORMAT_TOTAL_TIME_ENABLED;
	attr.disabled	= 1;
	attr.config	= BIT(intel_pt_pmu_value(INTEL_PT_FORMAT_TSC)) |
				BIT(intel_pt_pmu_value(INTEL_PT_FORMAT_NRT));
	attr.size	= sizeof(struct perf_event_attr);
	attr.exclude_kernel		= 0;
	attr.exclude_user		= 0;
	attr.mmap			= 1;

	/* only get trace for own pid */
	fde = tst_syscall(__NR_perf_event_open, &attr, 0, -1, -1, 0);
	if (fde < 0) {
		tst_res(TINFO, "Open Intel PT event failed!");
		tst_res(TFAIL, "perf trace full mode is failed!");
		return;
	}
	bufm = NULL;
	bufm = create_map(fde, buhsz);

}

static void cleanup(void)
{
	if (fde != -1)
		close(fde);

	del_map(bufm, buhsz);
}

static struct tst_test test = {
	.test_all = intel_pt_full_trace_check,
	.min_kver = "4.1",
	.setup = setup,
	.cleanup = cleanup,
	.needs_root = 1,
};

#else
TST_TEST_TCONF("missing aux_* fields in struct perf_event_mmap_page");
#endif /* HAVE_STRUCT_PERF_EVENT_MMAP_PAGE_AUX_HEAD */