// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
/*
 * Copyright (c) 2023 MediaTek Inc.
 */

#include <linux/version.h>
#include <linux/notifier.h>
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/oom.h>
#include "mtk_iommu_internal.h"
#include "mtk_iommu_common.h"
#include "mtk_iommu_dtv.h"
#include "mtk_iommu_statist.h"

#define MAX_INDEX 3
#define MTK_IOMMU_PANIC_NOTIFIER_PRIOR 150

static void show_iommu_order_page(void)
{
	struct mtk_iommu_buf_handle *handle;
	unsigned long flags = 0;
	struct scatterlist *sg = NULL;
	int i, index;
	int page_num[MAX_INDEX + 1] = {};
	unsigned int non_buddy_size = 0;
	struct mtk_dtv_iommu_data *data;
	int carv_size = get_carv_size();
	phys_addr_t carv_pa = get_carv_pa();

	if (get_iommu_data(&data))
		return;

	for (i = 0; i < MAX_INDEX + 1; i++)
		page_num[i] = 0;

	spin_lock_irqsave(&(data->buf_lock), flags);
	list_for_each_entry(handle, &data->buf_list_head, buf_list_node) {
		if (!handle || !handle->sgt)
			continue;
		for_each_sg(handle->sgt->sgl, sg, handle->sgt->orig_nents, i) {
			if (page_to_phys(sg_page(sg)) >= carv_pa
				&& page_to_phys(sg_page(sg)) < carv_pa + carv_size) {
				non_buddy_size += sg->length;
				continue;
			}
			index = compound_order(sg_page(sg));
			index = min_t(int, index, MAX_INDEX);
			page_num[index]++;
		}
	}
	spin_unlock_irqrestore(&(data->buf_lock), flags);
	for (i = 0; i < MAX_INDEX; i++)
		pr_info("[IOMMU DUMP] order %d: %d pages\n", i, page_num[i]);
	pr_info("[IOMMU DUMP] other orders: %d pages\n", page_num[MAX_INDEX]);
	pr_info("[IOMMU DUMP] non buddy size : 0x%x\n", non_buddy_size);
}

static void show_iommu_meminfo(void)
{
	struct file *file = NULL;
	struct mtk_iommu_buf_handle *handle;
	struct mtk_dtv_iommu_data *data;
	int ret;
	int total = 0;
	unsigned long flags = 0;
	void *vaddr;
	int vmap_count;
	struct dma_buf *db;
	bool inner = true;

	ret = get_iommu_data(&data);
	if (ret)
		return;

	pr_info(
	"IOMMU: %16s %10s %9s %5s %5s %5s %16s %6s %6s %5s %18s %5s %8s\n",
			"buf_tag", "addr",
			"size", "pid", "tgid", "mpid", "comm",
			"secure", "2XIOVA", "v_cnt", "vmap_ptr", "f_cnt",
			"is_inner");
	spin_lock_irqsave(&(data->buf_lock), flags);
	list_for_each_entry(handle, &data->buf_list_head, buf_list_node) {
		if (handle->dmabuf) {
			inner = false;
			db = handle->dmabuf;

		} else
			db = handle->b.db_ion;

		if (!db)
			continue;
		if (db->file)
			file = db->file;
		vmap_count = db->vmapping_counter;
		if (vmap_count)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0)
			vaddr = db->vmap_ptr.vaddr;
#else
			vaddr = db->vmap_ptr;
#endif
		pr_info(
		"IOMMU: %16.s %10llx %9x %5d %5d %5d %16.s %6d %6d %5d %18pK %5ld %8d\n",
		handle->buf_tag, handle->addr,
		(u32)handle->length, handle->pid, handle->tgid, handle->map_pid,
		handle->comm,
		handle->is_secure, handle->iova2x,
		vmap_count, vaddr, file_count(file),
		inner);
		total += handle->length;
	}
	spin_unlock_irqrestore(&(data->buf_lock), flags);
	pr_info("IOMMU: 0x%x\n", total);
}

/* IOMMU notifier */
static int mtk_iommu_panic_handler(struct notifier_block *this,
			       unsigned long event, void *unused)
{
	show_iommu_meminfo();
	return 0;
}

static int mtk_iommu_oom_handler(struct notifier_block *this,
			       unsigned long event, void *unused)
{
	show_statistics();
	show_iommu_meminfo();
	show_iommu_order_page();
	return 0;
}

static struct notifier_block mtk_iommu_panic_notifier = {
	.notifier_call  = mtk_iommu_panic_handler,
	.next           = NULL,
	.priority       = MTK_IOMMU_PANIC_NOTIFIER_PRIOR
};

static struct notifier_block mtk_iommu_oom_notifier = {
	.notifier_call  = mtk_iommu_oom_handler,
};

static void mtk_iommu_notifier_register(void)
{
	atomic_notifier_chain_register(&panic_notifier_list,
				       &mtk_iommu_panic_notifier);
	register_oom_notifier(&mtk_iommu_oom_notifier);
}

static void mtk_iommu_notifier_unregister(void)
{
	unregister_oom_notifier(&mtk_iommu_oom_notifier);
	atomic_notifier_chain_unregister(&panic_notifier_list,
				       &mtk_iommu_panic_notifier);
}

void mtk_iommu_notifier_init(void)
{
	mtk_iommu_notifier_register();
}

void mtk_iommu_notifier_deinit(void)
{
	mtk_iommu_notifier_unregister();
}
