// SPDX-License-Identifier: MIT
/*
 * Copyright © 2021 Intel Corporation
 */

#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/rculist.h>
#include <linux/rcupdate.h>
#include <linux/slab.h>
#include <linux/sysrq.h>

#include "gt/uc/intel_guc.h"

#include "gt/intel_engine.h"
#include "gt/intel_gt.h"
#include "gt/intel_gt_pm.h"
#include "gt/intel_timeline.h"

#include "i915_drv.h"
#include "i915_irq.h"
#include "i915_request.h"
#include "i915_sysrq.h"
#include "intel_memory_region.h"
#include "intel_wakeref.h"

static DEFINE_MUTEX(sysrq_mutex);
static LIST_HEAD(sysrq_list);

struct sysrq_cb {
	struct list_head link;
	struct rcu_head rcu;

	void (*fn)(void *data);
	void *data;
};

static void sysrq_handle_showgpu(int key)
{
	struct sysrq_cb *cb;

	rcu_read_lock();
	list_for_each_entry(cb, &sysrq_list, link)
		cb->fn(cb->data);
	rcu_read_unlock();
}

#ifdef BPM_CONST_SYSRQ_KEY_OP_NOT_PRESENT
static struct sysrq_key_op sysrq_showgpu_op = {
#else
static const struct sysrq_key_op sysrq_showgpu_op = {
#endif
        .handler        = sysrq_handle_showgpu,
        .help_msg       = "show-gpu(G)",
        .action_msg     = "Show GPU state",
        .enable_mask    = SYSRQ_ENABLE_DUMP,
};

static int register_sysrq(void (*fn)(void *data), void *data)
{
	struct sysrq_cb *cb;
	int ret = 0;

	cb = kmalloc(sizeof(*cb), GFP_KERNEL);
	if (!cb)
		return -ENOMEM;

	cb->fn = fn;
	cb->data = data;

	mutex_lock(&sysrq_mutex);
	if (list_empty(&sysrq_list))
		ret = register_sysrq_key('G', &sysrq_showgpu_op);
	if (ret == 0)
		list_add_tail_rcu(&cb->link, &sysrq_list);
	else
		kfree(cb);
	mutex_unlock(&sysrq_mutex);

	return ret;
}

static void unregister_sysrq(void (*fn)(void *data), void *data)
{
	struct sysrq_cb *cb;

	mutex_lock(&sysrq_mutex);
	list_for_each_entry(cb, &sysrq_list, link) {
		if (cb->fn == fn && cb->data == data) {
			list_del_rcu(&cb->link);
			if (list_empty(&sysrq_list))
				unregister_sysrq_key('G', &sysrq_showgpu_op);
			kfree_rcu(cb, rcu);
			break;
		}
	}
	mutex_unlock(&sysrq_mutex);

	/* Flush the handler before our caller can free fn/data */
	synchronize_rcu();
}

static void show_gpu_mem(struct drm_i915_private *i915, struct drm_printer *p)
{
	struct intel_memory_region *mr;
	enum intel_region_id id;

	for_each_memory_region(mr, i915, id)
		intel_memory_region_print(mr, 0, p);
}

static void show_ccs_mode(struct intel_gt *gt, struct drm_printer *p)
{
	struct intel_engine_cs *engine;
	enum intel_engine_id tmp;
	char buf[240], *s = buf;
	const char *prefix;

	if (!IS_PONTEVECCHIO(gt->i915))
		return;

	s += snprintf(s, sizeof(buf) - (s - buf), "mode:%08x, ", gt->ccs.mode);

	prefix =", ";
	s += snprintf(s, sizeof(buf) - (s - buf), "config:%08x", gt->ccs.config);
	if (gt->ccs.config) {
		prefix = " [";
		for_each_engine_masked(engine, gt, gt->ccs.config, tmp) {
			s += snprintf(s, sizeof(buf) - (s - buf), "%s%s", prefix, engine->name);
			prefix = ", ";
		}
		prefix = "], ";
	}
	s += snprintf(s, sizeof(buf) - (s - buf), "%s", prefix);

	prefix ="";
	s += snprintf(s, sizeof(buf) - (s - buf), "active:%08x", gt->ccs.active);
	if (gt->ccs.active) {
		prefix = " [";
		for_each_engine_masked(engine, gt, gt->ccs.active, tmp) {
			s += snprintf(s, sizeof(buf) - (s - buf), "%s%s", prefix, engine->name);
			prefix = ", ";
		}
		prefix = "]";
	}
	s += snprintf(s, sizeof(buf) - (s - buf), "%s", prefix);

	drm_printf(p, "multiCCS: { %s }\n", buf);
}

static void show_gt(struct intel_gt *gt, struct drm_printer *p)
{
	struct intel_engine_cs *engine;
	enum intel_engine_id id;
	intel_wakeref_t wakeref;

	drm_printf(p, "GT%d awake? %s [%d], %llums\n",
		   gt->info.id,
		   str_yes_no(gt->awake),
		   atomic_read(&gt->wakeref.count),
		   ktime_to_ms(intel_gt_get_awake_time(gt)));
	drm_printf(p, "Interrupts: { count: %lu, total: %lluns, avg: %luns, max: %luns }\n",
		   READ_ONCE(gt->stats.irq.count),
		   READ_ONCE(gt->stats.irq.total),
		   ewma_irq_time_read(&gt->stats.irq.avg),
		   READ_ONCE(gt->stats.irq.max));
	show_ccs_mode(gt, p);

	if (gt->awake)
		intel_wakeref_show(&gt->wakeref, p);

	with_intel_gt_pm_if_awake(gt, wakeref)
		intel_guc_print_info(&gt->uc.guc, p);

	for_each_engine(engine, gt, id) {
		if (intel_engine_is_idle(engine))
			continue;

		intel_engine_dump(engine, p, "%s\n", engine->name);
	}

	intel_gt_show_timelines(gt, p, i915_request_show_with_schedule);
}

static void show_gts(struct drm_i915_private *i915, struct drm_printer *p)
{
	struct intel_gt *gt;
	int i;

	for_each_gt(gt, i915, i)
		show_gt(gt, p);
}

static void show_rpm(struct drm_i915_private *i915, struct drm_printer *p)
{
#if IS_ENABLED(CPTCFG_DRM_I915_DISPLAY)
	drm_printf(p, "Runtime power status: %s\n",
		   str_enabled_disabled(!i915->power_domains.init_wakeref));
#endif
	print_intel_runtime_pm_wakeref(&i915->runtime_pm, p);
	drm_printf(p, "IRQs disabled: %s\n",
		   str_yes_no(!intel_irqs_enabled(i915)));
}

static void show_gpu(void *data)
{
	struct drm_i915_private *i915 = data;
	struct drm_printer p = drm_info_printer(i915->drm.dev);

	show_rpm(i915, &p);
	show_gts(i915, &p);
	show_gpu_mem(i915, &p);
}

int i915_register_sysrq(struct drm_i915_private *i915)
{
	return register_sysrq(show_gpu, i915);
}

void i915_unregister_sysrq(struct drm_i915_private *i915)
{
	unregister_sysrq(show_gpu, i915);
}
