// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
 *
 * (C) COPYRIGHT 2021-2023 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.
 *
 */
#include <mali_kbase.h>
#include "mali_kbase_csf_event.h"

/**
 * struct kbase_csf_event_cb - CSF event callback.
 *
 * @link:      Link to the rest of the list.
 * @kctx:      Pointer to the Kbase context this event belongs to.
 * @callback:  Callback function to call when a CSF event is signalled.
 * @param:     Parameter to pass to the callback function.
 *
 * This structure belongs to the list of events which is part of a Kbase
 * context, and describes a callback function with a custom parameter to pass
 * to it when a CSF event is signalled.
 */
struct kbase_csf_event_cb {
	struct list_head link;
	struct kbase_context *kctx;
	kbase_csf_event_callback *callback;
	void *param;
};

int kbase_csf_event_wait_add(struct kbase_context *kctx, kbase_csf_event_callback *callback,
			     void *param)
{
	int err = -ENOMEM;
	struct kbase_csf_event_cb *event_cb =
		kzalloc(sizeof(struct kbase_csf_event_cb), GFP_KERNEL);

	if (event_cb) {
		unsigned long flags;

		event_cb->kctx = kctx;
		event_cb->callback = callback;
		event_cb->param = param;

		spin_lock_irqsave(&kctx->csf.event.lock, flags);
		list_add_tail(&event_cb->link, &kctx->csf.event.callback_list);
		dev_dbg(kctx->kbdev->dev, "Added event handler %pK with param %pK\n", event_cb,
			event_cb->param);
		spin_unlock_irqrestore(&kctx->csf.event.lock, flags);

		err = 0;
	}

	return err;
}

void kbase_csf_event_wait_remove(struct kbase_context *kctx, kbase_csf_event_callback *callback,
				 void *param)
{
	struct kbase_csf_event_cb *event_cb;
	unsigned long flags;

	spin_lock_irqsave(&kctx->csf.event.lock, flags);

	list_for_each_entry(event_cb, &kctx->csf.event.callback_list, link) {
		if ((event_cb->callback == callback) && (event_cb->param == param)) {
			list_del(&event_cb->link);
			dev_dbg(kctx->kbdev->dev, "Removed event handler %pK with param %pK\n",
				event_cb, event_cb->param);
			kfree(event_cb);
			break;
		}
	}
	spin_unlock_irqrestore(&kctx->csf.event.lock, flags);
}

static void sync_update_notify_gpu(struct kbase_context *kctx)
{
	bool can_notify_gpu;
	unsigned long flags;

	spin_lock_irqsave(&kctx->kbdev->hwaccess_lock, flags);
	can_notify_gpu = kctx->kbdev->pm.backend.gpu_powered;
#ifdef KBASE_PM_RUNTIME
	if (kctx->kbdev->pm.backend.gpu_sleep_mode_active)
		can_notify_gpu = false;
#endif

	if (can_notify_gpu) {
		kbase_csf_ring_doorbell(kctx->kbdev, CSF_KERNEL_DOORBELL_NR);
		KBASE_KTRACE_ADD(kctx->kbdev, CSF_SYNC_UPDATE_NOTIFY_GPU_EVENT, kctx, 0u);
	}

	spin_unlock_irqrestore(&kctx->kbdev->hwaccess_lock, flags);
}

void kbase_csf_event_signal(struct kbase_context *kctx, bool notify_gpu)
{
	struct kbase_csf_event_cb *event_cb, *next_event_cb;
	unsigned long flags;

	dev_dbg(kctx->kbdev->dev, "Signal event (%s GPU notify) for context %pK\n",
		notify_gpu ? "with" : "without", (void *)kctx);

	/* First increment the signal count and wake up event thread.
	 */
	atomic_set(&kctx->event_count, 1);
	kbase_event_wakeup(kctx);

	/* Signal the CSF firmware. This is to ensure that pending command
	 * stream synch object wait operations are re-evaluated.
	 * Write to GLB_DOORBELL would suffice as spec says that all pending
	 * synch object wait operations are re-evaluated on a write to any
	 * CS_DOORBELL/GLB_DOORBELL register.
	 */
	if (notify_gpu)
		sync_update_notify_gpu(kctx);

	/* Now invoke the callbacks registered on backend side.
	 * Allow item removal inside the loop, if requested by the callback.
	 */
	spin_lock_irqsave(&kctx->csf.event.lock, flags);

	list_for_each_entry_safe(event_cb, next_event_cb, &kctx->csf.event.callback_list, link) {
		enum kbase_csf_event_callback_action action;

		dev_dbg(kctx->kbdev->dev, "Calling event handler %pK with param %pK\n",
			(void *)event_cb, event_cb->param);
		action = event_cb->callback(event_cb->param);
		if (action == KBASE_CSF_EVENT_CALLBACK_REMOVE) {
			list_del(&event_cb->link);
			kfree(event_cb);
		}
	}

	spin_unlock_irqrestore(&kctx->csf.event.lock, flags);
}

void kbase_csf_event_term(struct kbase_context *kctx)
{
	struct kbase_csf_event_cb *event_cb, *next_event_cb;
	unsigned long flags;

	spin_lock_irqsave(&kctx->csf.event.lock, flags);

	list_for_each_entry_safe(event_cb, next_event_cb, &kctx->csf.event.callback_list, link) {
		list_del(&event_cb->link);
		dev_warn(kctx->kbdev->dev, "Removed event handler %pK with param %pK\n",
			 (void *)event_cb, event_cb->param);
		kfree(event_cb);
	}

	WARN(!list_empty(&kctx->csf.event.error_list), "Error list not empty for ctx %d_%d\n",
	     kctx->tgid, kctx->id);

	spin_unlock_irqrestore(&kctx->csf.event.lock, flags);
}

void kbase_csf_event_init(struct kbase_context *const kctx)
{
	INIT_LIST_HEAD(&kctx->csf.event.callback_list);
	INIT_LIST_HEAD(&kctx->csf.event.error_list);
	spin_lock_init(&kctx->csf.event.lock);
}

void kbase_csf_event_remove_error(struct kbase_context *kctx, struct kbase_csf_notification *error)
{
	unsigned long flags;

	spin_lock_irqsave(&kctx->csf.event.lock, flags);
	list_del_init(&error->link);
	spin_unlock_irqrestore(&kctx->csf.event.lock, flags);
}

bool kbase_csf_event_read_error(struct kbase_context *kctx,
				struct base_csf_notification *event_data)
{
	struct kbase_csf_notification *error_data = NULL;
	unsigned long flags;

	spin_lock_irqsave(&kctx->csf.event.lock, flags);
	if (likely(!list_empty(&kctx->csf.event.error_list))) {
		error_data = list_first_entry(&kctx->csf.event.error_list,
					      struct kbase_csf_notification, link);
		list_del_init(&error_data->link);
		*event_data = error_data->data;
		dev_dbg(kctx->kbdev->dev, "Dequeued error %pK in context %pK\n", (void *)error_data,
			(void *)kctx);
	}
	spin_unlock_irqrestore(&kctx->csf.event.lock, flags);
	return !!error_data;
}

void kbase_csf_event_add_error(struct kbase_context *const kctx,
			       struct kbase_csf_notification *const error,
			       struct base_csf_notification const *const data)
{
	unsigned long flags;

	if (WARN_ON(!kctx))
		return;

	if (WARN_ON(!error))
		return;

	if (WARN_ON(!data))
		return;

	spin_lock_irqsave(&kctx->csf.event.lock, flags);
	if (list_empty(&error->link)) {
		error->data = *data;
		list_add_tail(&error->link, &kctx->csf.event.error_list);
		dev_dbg(kctx->kbdev->dev, "Added error %pK of type %d in context %pK\n",
			(void *)error, data->type, (void *)kctx);
	} else {
		dev_dbg(kctx->kbdev->dev, "Error %pK of type %d already pending in context %pK",
			(void *)error, error->data.type, (void *)kctx);
	}
	spin_unlock_irqrestore(&kctx->csf.event.lock, flags);
}

bool kbase_csf_event_error_pending(struct kbase_context *kctx)
{
	bool error_pending = false;
	unsigned long flags;

	/* Withhold the error event if the dump on fault is ongoing.
	 * This would prevent the Userspace from taking error recovery actions
	 * (which can potentially affect the state that is being dumped).
	 * Event handling thread would eventually notice the error event.
	 */
	if (unlikely(!kbase_debug_csf_fault_dump_complete(kctx->kbdev)))
		return false;

	spin_lock_irqsave(&kctx->csf.event.lock, flags);
	error_pending = !list_empty(&kctx->csf.event.error_list);

	dev_dbg(kctx->kbdev->dev, "%s error is pending in context %pK\n",
		error_pending ? "An" : "No", (void *)kctx);

	spin_unlock_irqrestore(&kctx->csf.event.lock, flags);

	return error_pending;
}
