/* SPDX-License-Identifier: GPL-2.0 */
/*
 *
 * (C) COPYRIGHT 2019-2021 ARM Limited. All rights reserved.
 *
 * This program is free software and is provided to you under the terms of the
 * GNU General Public License version 2 as published by the Free Software
 * Foundation, and any use by you of this program is subject to the terms
 * of such GNU license.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, you can access it online at
 * http://www.gnu.org/licenses/gpl-2.0.html.
 *
 */

/*
 * mali_kbase_kinstr_jm.h
 * Kernel driver public interface to job manager atom tracing. This API provides
 * a method to get the atom state changes into user space.
 *
 * The flow of operation is:
 *
 * | kernel                              | user                                |
 * | ----------------------------------- | ----------------------------------- |
 * | Initialize API with                 |                                     |
 * | kbase_kinstr_jm_init()              |                                     |
 * |                                     |                                     |
 * | Kernel code injects states with     |                                     |
 * | kbase_kinstr_jm_atom_state_*() APIs |                                     |
 * |                                     | Call ioctl() to get file descriptor |
 * |                                     | via KBASE_IOCTL_KINSTR_JM_FD        |
 * | Allocates a reader attached to FD   |                                     |
 * | Allocates circular buffer and       |                                     |
 * | patches, via ASM goto, the          |                                     |
 * | kbase_kinstr_jm_atom_state_*()      |                                     |
 * |                                     | loop:                               |
 * |                                     |   Call poll() on FD for POLLIN      |
 * |   When threshold of changes is hit, |                                     |
 * |   the poll is interrupted with      |                                     |
 * |   POLLIN. If circular buffer is     |                                     |
 * |   full then store the missed count  |                                     |
 * |   and interrupt poll                |   Call read() to get data from      |
 * |                                     |   circular buffer via the fd        |
 * |   Kernel advances tail of circular  |                                     |
 * |   buffer                            |                                     |
 * |                                     | Close file descriptor               |
 * | Deallocates circular buffer         |                                     |
 * |                                     |                                     |
 * | Terminate API with                  |                                     |
 * | kbase_kinstr_jm_term()              |                                     |
 *
 * All tracepoints are guarded on a static key. The static key is activated when
 * a user space reader gets created. This means that there is negligible cost
 * inserting the tracepoints into code when there are no readers.
 */

#ifndef _KBASE_KINSTR_JM_H_
#define _KBASE_KINSTR_JM_H_

#include <uapi/gpu/arm/midgard/mali_kbase_kinstr_jm_reader.h>

#ifdef __KERNEL__
#include <linux/version.h>
#include <linux/static_key.h>
#else
/* empty wrapper macros for userspace */
#define static_branch_unlikely(key) (1)
#define KERNEL_VERSION(a, b, c) (0)
#define LINUX_VERSION_CODE (1)
#endif /* __KERNEL__ */

/* Forward declarations */
struct kbase_context;
struct kbase_kinstr_jm;
struct kbase_jd_atom;
union kbase_kinstr_jm_fd;

/**
 * kbase_kinstr_jm_init() - Initialise an instrumentation job manager context.
 * @ctx: Non-NULL pointer to where the pointer to the created context will
 *       be stored on success.
 *
 * Return: 0 on success, else error code.
 */
int kbase_kinstr_jm_init(struct kbase_kinstr_jm **ctx);

/**
 * kbase_kinstr_jm_term() - Terminate an instrumentation job manager context.
 * @ctx: Pointer to context to be terminated.
 */
void kbase_kinstr_jm_term(struct kbase_kinstr_jm *ctx);

/**
 * kbase_kinstr_jm_get_fd() - Retrieves a file descriptor that can be used to
 * read the atom state changes from userspace
 *
 * @ctx: Pointer to the initialized context
 * @jm_fd_arg: Pointer to the union containing the in/out params
 * Return: -1 on failure, valid file descriptor on success
 */
int kbase_kinstr_jm_get_fd(struct kbase_kinstr_jm *const ctx,
			   union kbase_kinstr_jm_fd *jm_fd_arg);

/**
 * kbasep_kinstr_jm_atom_state() - Signifies that an atom has changed state
 * @atom: The atom that has changed state
 * @state: The new state of the atom
 *
 * This performs the actual storage of the state ready for user space to
 * read the data. It is only called when the static key is enabled from
 * kbase_kinstr_jm_atom_state(). There is almost never a need to invoke this
 * function directly.
 */
void kbasep_kinstr_jm_atom_state(
	struct kbase_jd_atom *const atom,
	const enum kbase_kinstr_jm_reader_atom_state state);

/* Allows ASM goto patching to reduce tracing overhead. This is
 * incremented/decremented when readers are created and terminated. This really
 * shouldn't be changed externally, but if you do, make sure you use
 * a static_key_inc()/static_key_dec() pair.
 */
extern struct static_key_false basep_kinstr_jm_reader_static_key;

/**
 * kbase_kinstr_jm_atom_state() - Signifies that an atom has changed state
 * @atom: The atom that has changed state
 * @state: The new state of the atom
 *
 * This uses a static key to reduce overhead when tracing is disabled
 */
static inline void kbase_kinstr_jm_atom_state(
	struct kbase_jd_atom *const atom,
	const enum kbase_kinstr_jm_reader_atom_state state)
{
	if (static_branch_unlikely(&basep_kinstr_jm_reader_static_key))
		kbasep_kinstr_jm_atom_state(atom, state);
}

/**
 * kbase_kinstr_jm_atom_state_queue() - Signifies that an atom has entered a
 *                                      hardware or software queue.
 * @atom: The atom that has changed state
 */
static inline void kbase_kinstr_jm_atom_state_queue(
	struct kbase_jd_atom *const atom)
{
	kbase_kinstr_jm_atom_state(
		atom, KBASE_KINSTR_JM_READER_ATOM_STATE_QUEUE);
}

/**
 * kbase_kinstr_jm_atom_state_start() - Signifies that work has started on an
 *                                      atom
 * @atom: The atom that has changed state
 */
static inline void kbase_kinstr_jm_atom_state_start(
	struct kbase_jd_atom *const atom)
{
	kbase_kinstr_jm_atom_state(
		atom, KBASE_KINSTR_JM_READER_ATOM_STATE_START);
}

/**
 * kbase_kinstr_jm_atom_state_stop() - Signifies that work has stopped on an
 *                                     atom
 * @atom: The atom that has changed state
 */
static inline void kbase_kinstr_jm_atom_state_stop(
	struct kbase_jd_atom *const atom)
{
	kbase_kinstr_jm_atom_state(
		atom, KBASE_KINSTR_JM_READER_ATOM_STATE_STOP);
}

/**
 * kbase_kinstr_jm_atom_state_complete() - Signifies that all work has completed
 *                                         on an atom
 * @atom: The atom that has changed state
 */
static inline void kbase_kinstr_jm_atom_state_complete(
	struct kbase_jd_atom *const atom)
{
	kbase_kinstr_jm_atom_state(
		atom, KBASE_KINSTR_JM_READER_ATOM_STATE_COMPLETE);
}

/**
 * kbase_kinstr_jm_atom_queue() - A software *or* hardware atom is queued for
 *                                execution
 * @atom: The atom that has changed state
 */
static inline void kbase_kinstr_jm_atom_queue(struct kbase_jd_atom *const atom)
{
	kbase_kinstr_jm_atom_state_queue(atom);
}

/**
 * kbase_kinstr_jm_atom_complete() - A software *or* hardware atom is fully
 *                                   completed
 * @atom: The atom that has changed state
 */
static inline void kbase_kinstr_jm_atom_complete(
	struct kbase_jd_atom *const atom)
{
	kbase_kinstr_jm_atom_state_complete(atom);
}

/**
 * kbase_kinstr_jm_atom_sw_start() - A software atom has started work
 * @atom: The atom that has changed state
 */
static inline void kbase_kinstr_jm_atom_sw_start(
	struct kbase_jd_atom *const atom)
{
	kbase_kinstr_jm_atom_state_start(atom);
}

/**
 * kbase_kinstr_jm_atom_sw_stop() - A software atom has stopped work
 * @atom: The atom that has changed state
 */
static inline void kbase_kinstr_jm_atom_sw_stop(
	struct kbase_jd_atom *const atom)
{
	kbase_kinstr_jm_atom_state_stop(atom);
}

/**
 * kbasep_kinstr_jm_atom_hw_submit() - A hardware atom has been submitted
 * @atom: The atom that has been submitted
 *
 * This private implementation should not be called directly, it is protected
 * by a static key in kbase_kinstr_jm_atom_hw_submit(). Use that instead.
 */
void kbasep_kinstr_jm_atom_hw_submit(struct kbase_jd_atom *const atom);

/**
 * kbase_kinstr_jm_atom_hw_submit() - A hardware atom has been submitted
 * @atom: The atom that has been submitted
 */
static inline void kbase_kinstr_jm_atom_hw_submit(
	struct kbase_jd_atom *const atom)
{
	if (static_branch_unlikely(&basep_kinstr_jm_reader_static_key))
		kbasep_kinstr_jm_atom_hw_submit(atom);
}

/**
 * kbasep_kinstr_jm_atom_hw_release() - A hardware atom has been released
 * @atom: The atom that has been released
 *
 * This private implementation should not be called directly, it is protected
 * by a static key in kbase_kinstr_jm_atom_hw_release(). Use that instead.
 */
void kbasep_kinstr_jm_atom_hw_release(struct kbase_jd_atom *const atom);

/**
 * kbase_kinstr_jm_atom_hw_release() - A hardware atom has been released
 * @atom: The atom that has been released
 */
static inline void kbase_kinstr_jm_atom_hw_release(
	struct kbase_jd_atom *const atom)
{
	if (static_branch_unlikely(&basep_kinstr_jm_reader_static_key))
		kbasep_kinstr_jm_atom_hw_release(atom);
}

#endif /* _KBASE_KINSTR_JM_H_ */
