// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note OR MIT

/*
 * (C) COPYRIGHT 2023-2024 ARM Limited or its affiliates. All rights reserved.
 */

/*
 * Part of the Mali reference arbiter
 */

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/workqueue.h>

#include "mali_arbiter_gpu_subinstance.h"
#include "mali_arbiter_internal.h"
#include "mali_arbiter_scheduler.h"

#define DEFAULT_GSI_ID 0


/**
 * enum mali_arb_gsi_sched_state - gpu-subinstance scheduler states
 * @GSI_SCHED_STATE_NO_REQ: No VMs are requesting in current gpu-subinstance.
 * @GSI_SCHED_STATE_SINGLE_REQ: A single VM has requested GPU.
 * @GSI_SCHED_STATE_RUNNING: Multiple VMs are requesting GPU and arbitration
 *                     is required between them.
 * @GSI_SCHED_STATE_GPU_STOPPING: VM has been told to stop using GPU and
 *                          arbiter is waiting for a stopped event.
 *
 * Definition of the possible arbiter scheduler's states
 */
enum mali_arb_gsi_sched_state {
	GSI_SCHED_STATE_NO_REQ,
	GSI_SCHED_STATE_SINGLE_REQ,
	GSI_SCHED_STATE_RUNNING,
	GSI_SCHED_STATE_GPU_STOPPING,
};

/**
 * enum mali_arb_gsi_state - gpu-subinstance states
 * @GSI_STATE_STARTING: A gpu-subinstance start request is in progress
 * @GSI_STATE_STARTED: The gpu-subinstance is started
 * @GSI_STATE_STOPPING: A gpu-subinstance stop request is in progress
 * @GSI_STATE_STOPPED: The gpu-subinstance is stopped
 *
 * Definition of the possible arbiter's states
 */
enum mali_arb_gsi_state {
	GSI_STATE_STARTING,
	GSI_STATE_STARTED,
	GSI_STATE_STOPPING,
	GSI_STATE_STOPPED
};

/**
 * enum mali_arbiter_sched_type - types of schedulers
 * @MALI_ARBITER_SCHED_ROUND_ROBIN: Round robin scheduler
 * @MALI_ARBITER_SCHED_MAX: Maximum valid index.
 *
 * Different type of scheduling logics.
 */
enum mali_arbiter_sched_type {
	MALI_ARBITER_SCHED_ROUND_ROBIN,
	MALI_ARBITER_SCHED_MAX,
};

struct mali_arbiter_sched_funcs {
	void *(*create)(struct device *dev, void *gsi_ptr, u32 gsi_idx,
			struct mali_arb_gsi_cb gsi_cb, struct mali_arb *arb_hndl,
			struct mali_arb_gsi_sched_cb gsi_sched_cb,
			struct mali_arb_gsi_sched_ops *sched_ops);
	void (*destroy)(void *sched_ptr);
};

static struct mali_arbiter_sched_funcs sched_func_arr[MALI_ARBITER_SCHED_MAX] = {
	{ mali_arbiter_sched_create_rr, mali_arbiter_sched_destroy_rr }
};

/**
 * struct mali_arb_gpu_subinstance - Arbiter representation of a gpu-subinstance
 * @dev: Device for arbiter (only single Arbiter device supported).
 * @gsi_idx: gpu-subinstance index within its Resource Group
 * @gsi_flags: gpu-subinstance configuration set by arbiter-core
 * @vm_assign_if: VM assign interfaces
 * @arb_hndl: Arbiter private data handler for callback functions
 * @gsi_cb: Callback functions for gpu-subinstance
 * @sched_ptr: Scheduler which schedules the VMs on the gpu subinstance
 * @sched_ops: scheduler operations to schedule the VM requests,
 *             these are invoked from the gpu-subinstance to schedule
 *             the GPU requests on that gpu-subinstance from the VMs.
 * @gsi_state: Current gpu-subinstance state
 * @curr_gpu_freq: Store the current frequency on which the GPU is running
 */
struct mali_arb_gpu_subinstance {
	struct device *dev;
	u32 gsi_idx;
	u32 gsi_flags;
	/* Assign interface */
	struct vm_assign_interface vm_assign_if;
	/* Callbacks */
	struct mali_arb *arb_hndl;
	struct mali_arb_gsi_cb gsi_cb;
	/* Scheduling */
	void *sched_ptr;
	struct mali_arb_gsi_sched_ops sched_ops;

	enum mali_arb_gsi_state gsi_state;
	/* dvfs */
	u32 curr_gpu_freq;
};

/* Forward declaration */
static void *mali_arbiter_sched_create(struct device *dev, void *gsi_ptr, u32 gsi_idx,
				       struct mali_arb_gsi_cb gsi_cb, struct mali_arb *arb_hndl,
				       struct mali_arb_gsi_sched_cb gsi_sched_cb,
				       struct mali_arb_gsi_sched_ops *sched_ops);
static void mali_arbiter_sched_destroy(void *sched_ptr);


static void handle_gpu_stop(struct mali_arb_priv_data *arb_vm)
{
	arb_vm->info.ops.gpu_stop(arb_vm->vm);
}

static void handle_gpu_lost(struct mali_arb_priv_data *arb_vm)
{
	arb_vm->info.ops.gpu_lost(arb_vm->vm);
}

static int handle_unassign_vm(struct mali_arb_gpu_subinstance *gsi_ptr)
{
	struct vm_assign_ops *ops;

	ops = gsi_ptr->vm_assign_if.ops;

	if (!ops) {
		dev_err(gsi_ptr->dev, "P%u: VM assign interface not available.\n",
			gsi_ptr->gsi_idx);
		return -EPERM;
	}
	return ops->unassign_vm(gsi_ptr->vm_assign_if.dev);
}

static int handle_grant_gpu(struct mali_arb_gpu_subinstance *gsi_ptr,
			    struct mali_arb_priv_data *arb_vm)
{
	struct vm_assign_ops *ops;

	ops = gsi_ptr->vm_assign_if.ops;

	if (!ops) {
		dev_err(gsi_ptr->dev, "P%u: VM assign interface not available.\n",
			gsi_ptr->gsi_idx);
		return -EPERM;
	}

	if (gsi_ptr->gsi_flags & (1 << GSI_FLAG_SLICE_ASSIGNED)) {
		ops->assign_vm(gsi_ptr->vm_assign_if.dev, arb_vm->info.id);
	} else {
		dev_err(gsi_ptr->dev, "slice not assigned to GSI-#%d", gsi_ptr->gsi_idx);
		return -EFAULT;
	}

	arb_vm->info.ops.gpu_granted(arb_vm->vm, gsi_ptr->curr_gpu_freq);

	return 0;
}

static int handle_get_assigned_vm(struct mali_arb_gpu_subinstance *gsi_ptr, int32_t *vm_id)
{
	struct vm_assign_ops *ops;

	ops = gsi_ptr->vm_assign_if.ops;

	if (!ops || !ops->get_assigned_vm) {
		dev_err(gsi_ptr->dev, "P%u: VM assign interface not available.\n",
			gsi_ptr->gsi_idx);
		return -EPERM;
	}
	return ops->get_assigned_vm(gsi_ptr->vm_assign_if.dev, vm_id);
}

/** remove VM from gpu-subinstance
 */
bool mali_arbiter_gsi_remove_vm(struct mali_arb_gpu_subinstance *gsi_ptr,
				struct mali_arb_priv_data *rem_vm)
{
	bool requested = false;

	dev_dbg(gsi_ptr->dev, "gsi-remove-vm: VM-#%d from GSI-#%d", rem_vm->info.id,
		gsi_ptr->gsi_idx);

	requested = gsi_ptr->sched_ops.sched_remove_vm(gsi_ptr->sched_ptr, rem_vm, false);
	gsi_ptr->sched_ops.sched_resync_vm(gsi_ptr->sched_ptr, rem_vm);

	return requested;
}

/** gpu_stopped message handler
 */
void mali_arbiter_gsi_gpu_stopped(struct mali_arb_priv_data *arb_vm,
				  struct mali_arb_gpu_subinstance *gsi_ptr, bool req_again)
{
	if (WARN_ON(!arb_vm) || WARN_ON(!gsi_ptr))
		return;

	gsi_ptr->sched_ops.sched_remove_vm(gsi_ptr->sched_ptr, arb_vm, req_again);
}

/** gpu_request message handler
 */
void mali_arbiter_gsi_gpu_request(struct mali_arb_priv_data *arb_vm,
				  struct mali_arb_gpu_subinstance *gsi_ptr)
{

	if (WARN_ON(!arb_vm) || WARN_ON(!gsi_ptr))
		return;


	gsi_ptr->sched_ops.sched_add_vm(gsi_ptr->sched_ptr, arb_vm
	);
}

/** gpu_active message handler
 */
void mali_arbiter_gsi_gpu_active(struct mali_arb_priv_data *arb_vm,
				 struct mali_arb_gpu_subinstance *gsi_ptr)
{
	if (WARN_ON(!arb_vm) || WARN_ON(!gsi_ptr))
		return;
}

/** gpu_idle message handler
 */
void mali_arbiter_gsi_gpu_idle(struct mali_arb_priv_data *arb_vm,
			       struct mali_arb_gpu_subinstance *gsi_ptr)
{
	if (WARN_ON(!arb_vm) || WARN_ON(!gsi_ptr))
		return;

	gsi_ptr->sched_ops.sched_stop_idle_vm(gsi_ptr->sched_ptr, arb_vm);
}

/** Get gpu-subinstance utilization info
 */
void mali_arbiter_gsi_get_utilisation(struct mali_arb_gpu_subinstance *gsi_ptr, u32 *gsi_busytime,
				      u32 *gsi_totaltime)
{
	if (WARN_ON(!gsi_ptr))
		return;

	/* Check if lock is held */
	gsi_ptr->gsi_cb.cb_lock_assert_held(gsi_ptr->arb_hndl, gsi_ptr->gsi_idx);
	gsi_ptr->sched_ops.sched_get_utilisation(gsi_ptr->sched_ptr, gsi_busytime, gsi_totaltime);
}

/** Update gpu-subinstance clock frequency
 */
void mali_arbiter_gsi_update_freq(struct mali_arb_gpu_subinstance *gsi_ptr, u32 new_freq)
{
	struct mali_arb_priv_data *active_vm;

	if (WARN_ON(!gsi_ptr))
		return;

	active_vm = gsi_ptr->sched_ops.sched_get_active_vm(gsi_ptr->sched_ptr);
	/* Check if lock is held */
	gsi_ptr->gsi_cb.cb_lock_assert_held(gsi_ptr->arb_hndl, gsi_ptr->gsi_idx);

	/*
	 * Notify only a PE currently active on the gpu-subinstance and only
	 * if the gpu-subinstance is not stopping, otherwise there is a risk
	 * that the granted message could overwrite the stop message or
	 * give the VM the idea that the GPU is granted again, which is
	 * not the case.
	 */
	if (gsi_ptr->curr_gpu_freq != new_freq) {
		gsi_ptr->curr_gpu_freq = new_freq;
		if (active_vm && gsi_ptr->gsi_state != GSI_STATE_STOPPING)
			active_vm->info.ops.gpu_granted(active_vm->vm, new_freq);
	}
}

/** Set gpu-subinstance flag
 */
void mali_arbiter_gsi_flag_set(struct mali_arb_gpu_subinstance *gsi_ptr,
			       enum mali_arb_gsi_flags flag)
{

	if (flag >= GSI_FLAG_MAX) {
		dev_warn_once(gsi_ptr->dev, "P%u: Invalid set flag %u\n", gsi_ptr->gsi_idx, flag);
		return;
	}

	gsi_ptr->gsi_flags |= (u32)(1 << flag);

	dev_dbg(gsi_ptr->dev, "P%u: Set flag %u (Mask: %x).\n", gsi_ptr->gsi_idx, flag,
		gsi_ptr->gsi_flags);
}

/** Clear gpu-subinstance config
 */
void mali_arbiter_gsi_flag_clear(struct mali_arb_gpu_subinstance *gsi_ptr,
				 enum mali_arb_gsi_flags flag)
{

	if (flag >= GSI_FLAG_MAX) {
		dev_warn_once(gsi_ptr->dev, "P%u: Invalid clear flag %u\n", gsi_ptr->gsi_idx, flag);
		return;
	}
	gsi_ptr->gsi_flags &= ~(u32)(1 << flag);

	dev_dbg(gsi_ptr->dev, "P%u: Clear flag %u (Mask: %x).\n", gsi_ptr->gsi_idx, flag,
		gsi_ptr->gsi_flags);
}

/** Register VM assign interface
 */
int mali_arbiter_gsi_register_assign_if(struct mali_arb_gpu_subinstance *gsi_ptr,
					struct vm_assign_interface vm_assign)
{
	if (unlikely(gsi_ptr->vm_assign_if.ops)) {
		dev_warn(gsi_ptr->dev, "VM assign interface already registered\n");
		return -EFAULT;
	}

	/* register VM Assign interface */
	gsi_ptr->vm_assign_if = vm_assign;
	return 0;
}

/** Unregister VM assign interface
 */
void mali_arbiter_gsi_unregister_assign_if(struct mali_arb_gpu_subinstance *gsi_ptr)
{
	if (unlikely(!gsi_ptr->vm_assign_if.ops)) {
		dev_warn(gsi_ptr->dev, "VM assign interface already registered\n");
		return;
	}

	/* unregister VM Assign interface */
	gsi_ptr->vm_assign_if.if_id = DEFAULT_GSI_ID;
	gsi_ptr->vm_assign_if.dev = NULL;
	gsi_ptr->vm_assign_if.ops = NULL;
}

/** Start a gpu-subinstance
 */
void mali_arbiter_gsi_start(struct mali_arb_gpu_subinstance *gsi_ptr)
{
	if (gsi_ptr->gsi_state != GSI_STATE_STOPPED) {
		dev_err(gsi_ptr->dev, "GPU subinstance is in invalid state %d", gsi_ptr->gsi_state);
		return;
	}
	gsi_ptr->gsi_state = GSI_STATE_STARTING;
	gsi_ptr->sched_ops.sched_start(gsi_ptr->sched_ptr);
	gsi_ptr->gsi_state = GSI_STATE_STARTED;
}

/** Stop a gpu-subinstance
 */
void mali_arbiter_gsi_stop(struct mali_arb_gpu_subinstance *gsi_ptr)
{
	if (gsi_ptr->gsi_state != GSI_STATE_STARTED) {
		dev_err(gsi_ptr->dev, "GPU subinstance is in invalid state %d", gsi_ptr->gsi_state);
		return;
	}
	gsi_ptr->gsi_state = GSI_STATE_STOPPING;
	gsi_ptr->sched_ops.sched_stop(gsi_ptr->sched_ptr);
	gsi_ptr->gsi_state = GSI_STATE_STOPPED;
}

/** Creates a scheduler
 */
static void *mali_arbiter_sched_create(struct device *dev, void *gsi_ptr, u32 gsi_idx,
				       struct mali_arb_gsi_cb gsi_cb, struct mali_arb *arb_hndl,
				       struct mali_arb_gsi_sched_cb gsi_sched_cb,
				       struct mali_arb_gsi_sched_ops *sched_ops)
{
	void *sched_ptr;

	/* Selecting the round robin scheduler by default for now */
	sched_ptr = sched_func_arr[MALI_ARBITER_SCHED_ROUND_ROBIN].create(
		dev, gsi_ptr, gsi_idx, gsi_cb, arb_hndl, gsi_sched_cb, sched_ops);

	if (!sched_ptr)
		return NULL;
	return sched_ptr;
}

/** Destroys a scheduler
 */
static void mali_arbiter_sched_destroy(void *sched_ptr)
{
	/* Selecting the round robin scheduler by default for now */
	sched_func_arr[MALI_ARBITER_SCHED_ROUND_ROBIN].destroy(sched_ptr);
}

/** Creates a gpu-subinstance
 */
struct mali_arb_gpu_subinstance *mali_arbiter_gsi_create(struct device *dev, u32 gsi_idx,
							 struct mali_arb_gsi_cb gsi_cb,
							 struct mali_arb *arb_hndl)
{
	struct mali_arb_gpu_subinstance *gsi_ptr;
	struct mali_arb_gsi_sched_cb gsi_sched_cb = { .get_assigned_vm = handle_get_assigned_vm,
						      .grant_gpu = handle_grant_gpu,
						      .gpu_lost = handle_gpu_lost,
						      .gpu_stop = handle_gpu_stop,
						      .unassign_vm = handle_unassign_vm };

	if (!dev || !arb_hndl) {
		dev_err(dev, "P%u: Null parameter. (dev = %pK, arb_hndl = %pK)\n", gsi_idx, dev,
			arb_hndl);
		return NULL;
	}

	/* Function param check */
	if (!gsi_cb.cb_lock || !gsi_cb.cb_lock_assert_held || !gsi_cb.cb_unlock ||
	    !gsi_cb.cb_active || !gsi_cb.cb_idle) {
		dev_err(dev, "P%u: Empty callback.\n", gsi_idx);
		return NULL;
	}

	/* Allocate memory */
	gsi_ptr = kzalloc(sizeof(struct mali_arb_gpu_subinstance), GFP_KERNEL);
	if (!gsi_ptr)
		return NULL;

	/* Initialize fields */
	gsi_ptr->dev = dev;
	gsi_ptr->arb_hndl = arb_hndl;
	gsi_ptr->gsi_idx = gsi_idx;
	gsi_ptr->gsi_cb = gsi_cb;
	gsi_ptr->gsi_state = GSI_STATE_STARTED;
	gsi_ptr->sched_ptr = mali_arbiter_sched_create(dev, gsi_ptr, gsi_idx, gsi_cb, arb_hndl,
						       gsi_sched_cb, &gsi_ptr->sched_ops);

	if (gsi_ptr->sched_ptr == NULL) {
		dev_err(dev, "P%u: Failed to create scheduler.\n", gsi_idx);
		mali_arbiter_gsi_destroy(gsi_ptr);
		return NULL;
	}

	return gsi_ptr;
}

/** Destroy a gpu-subinstance
 */
void mali_arbiter_gsi_destroy(struct mali_arb_gpu_subinstance *gsi_ptr)
{
	if (!gsi_ptr)
		return;

	if (gsi_ptr->sched_ptr)
		mali_arbiter_sched_destroy(gsi_ptr->sched_ptr);

	kfree(gsi_ptr);
}
