/*
 * Copyright (c) 2014-2019, Intel Corporation
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  * Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *  * Neither the name of Intel Corporation nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef PEVENT_H
#define PEVENT_H

#include <linux/perf_event.h>

#include "intel-pt.h"

#include <stdint.h>
#include <stddef.h>
#include <string.h>


/* A perf event configuration. */
struct pev_config {
	/* The size of the config structure in bytes. */
	size_t size;

	/* The respective field in struct perf_event_attr.
	 *
	 * We require sample_id_all in struct perf_event_attr to be set.
	 */
	uint64_t sample_type;

	/* The respective fields in struct perf_event_mmap_page. */
	uint16_t time_shift;
	uint32_t time_mult;
	uint64_t time_zero;
};

static inline void pev_config_init(struct pev_config *config)
{
	memset(config, 0, sizeof(*config));
	config->size = sizeof(*config);
}


/* The MMAP perf event record. */
struct pev_record_mmap {
	uint32_t pid, tid;
	uint64_t addr;
	uint64_t len;
	uint64_t pgoff;
	char filename[];
};

/* The LOST perf event record. */
struct pev_record_lost {
	uint64_t id;
	uint64_t lost;
};

/* The COMM perf event record. */
struct pev_record_comm {
	uint32_t pid, tid;
	char comm[];
};

/* The EXIT perf event record. */
struct pev_record_exit {
	uint32_t pid, ppid;
	uint32_t tid, ptid;
	uint64_t time;
};

/* The THROTTLE and UNTHROTTLE perf event records. */
struct pev_record_throttle {
	uint64_t time;
	uint64_t id;
	uint64_t stream_id;
};

/* The FORK perf event record. */
struct pev_record_fork {
	uint32_t pid, ppid;
	uint32_t tid, ptid;
	uint64_t time;
};

/* The MMAP2 perf event record. */
struct pev_record_mmap2 {
	uint32_t pid, tid;
	uint64_t addr;
	uint64_t len;
	uint64_t pgoff;
	uint32_t maj, min;
	uint64_t ino;
	uint64_t ino_generation;
	uint32_t prot, flags;
	char filename[];
};

/* The AUX perf event record. */
struct pev_record_aux {
	uint64_t aux_offset;
	uint64_t aux_size;
	uint64_t flags;
};

/* The ITRACE_START perf event record. */
struct pev_record_itrace_start {
	uint32_t pid, tid;
};

/* The LOST_SAMPLES perf event record. */
struct pev_record_lost_samples {
	uint64_t lost;
};

/* The SWITCH_CPU_WIDE perf event record. */
struct pev_record_switch_cpu_wide {
	uint32_t next_prev_pid;
	uint32_t next_prev_tid;
};

/* A perf event record. */
struct pev_event {
	/* The record type (enum perf_event_type). */
	uint32_t type;

	/* The misc field of the perf event header. */
	uint16_t misc;

	/* The perf event record. */
	union {
		/* @type = PERF_RECORD_MMAP. */
		const struct pev_record_mmap *mmap;

		/* @type = PERF_RECORD_LOST. */
		const struct pev_record_lost *lost;

		/* @type = PERF_RECORD_COMM. */
		const struct pev_record_comm *comm;

		/* @type = PERF_RECORD_EXIT. */
		const struct pev_record_exit *exit;

		/* @type = PERF_RECORD_(UN)THROTTLE. */
		const struct pev_record_throttle *throttle;

		/* @type = PERF_RECORD_FORK. */
		const struct pev_record_fork *fork;

		/* @type = PERF_RECORD_MMAP2. */
		const struct pev_record_mmap2 *mmap2;

		/* @type = PERF_RECORD_AUX. */
		const struct pev_record_aux *aux;

		/* @type = PERF_RECORD_ITRACE_START. */
		const struct pev_record_itrace_start *itrace_start;

		/* @type = PERF_RECORD_LOST_SAMPLES. */
		const struct pev_record_lost_samples *lost_samples;

		/* @type = PERF_RECORD_SWITCH_CPU_WIDE. */
		const struct pev_record_switch_cpu_wide *switch_cpu_wide;
	} record;

	/* The additional samples. */
	struct {
		/* The sampled pid and tid. */
		const uint32_t *pid;
		const uint32_t *tid;

		/* The sampled time in perf_event format. */
		const uint64_t *time;

		/* The sampled time in TSC format - if @time is not NULL. */
		uint64_t tsc;

		/* The sampled id. */
		const uint64_t *id;

		/* The sampled stream id. */
		const uint64_t *stream_id;

		/* The sampled cpu. */
		const uint32_t *cpu;

		/* The sample identifier. */
		const uint64_t *identifier;
	} sample;
};

static inline void pev_event_init(struct pev_event *event)
{
	memset(event, 0, sizeof(*event));
}

/* Convert perf_event time to TSC.
 *
 * Converts @time in perf_event format to @tsc.
 *
 * Returns zero on success, a negative error code otherwise.
 * Returns -pte_bad_config if @config->size is too small.
 * Returns -pte_bad_config if @config->time_mult is zero.
 * Returns -pte_internal if @tsc or @config is NULL.
 */
extern int pev_time_to_tsc(uint64_t *tsc, uint64_t time,
			   const struct pev_config *config);

/* Convert TSC to perf_event time.
 *
 * Converts @tsc into @time in perf_event format.
 *
 * Returns zero on success, a negative error code otherwise.
 * Returns -pte_bad_config if @config->size is too small.
 * Returns -pte_bad_config if @config->time_mult is zero.
 * Returns -pte_internal if @time or @config is NULL.
 */
extern int pev_time_from_tsc(uint64_t *time, uint64_t tsc,
			     const struct pev_config *config);

/* Read a perf_event record.
 *
 * Reads one perf_event record from [@begin; @end[ into @event.
 *
 * Returns the number of bytes read on success, a negative error code otherwise.
 * Returns -pte_bad_config if @config->size is too small.
 * Returns -pte_eos if the event does not fit into [@begin; @end[.
 * Returns -pte_internal if @event, @config, @begin, or @end is NULL.
 */
extern int pev_read(struct pev_event *event, const uint8_t *begin,
		    const uint8_t *end, const struct pev_config *config);

/* Write a perf_event record.
 *
 * Writes @event into [@begin; @end[.
 *
 * Returns the number of bytes written on success, a negative error code
 * otherwise.
 * Returns -pte_bad_config if @config->size is too small.
 * Returns -pte_bad_opc if the event type is not known.
 * Returns -pte_eos if the event does not fit into [@begin; @end[.
 * Returns -pte_internal if @begin, @end, @event, or @config is NULL.
 */
extern int pev_write(const struct pev_event *event, uint8_t *begin,
		     uint8_t *end, const struct pev_config *config);

#endif /* PEVENT_H */
