// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2017-2023 NVIDIA Corporation.  All rights reserved.

/**
 * @file drivers/media/platform/tegra/camera/fusa-capture/capture-common.c
 *
 * @brief VI/ISP channel common operations for the T186/T194 Camera RTCPU
 * platform.
 */

#include <nvidia/conftest.h>

#include <linux/dma-buf.h>
#include <linux/dma-mapping.h>
#include <linux/nospec.h>
#include <linux/nvhost.h>
#include <linux/slab.h>
#include <linux/hashtable.h>
#include <linux/atomic.h>
#include <media/mc_common.h>

#include <media/fusa-capture/capture-common.h>


/**
 * @brief Capture buffer management table.
 */
struct capture_buffer_table {
	struct device *dev; /**< Originating device (VI or ISP) */
	struct kmem_cache *cache; /**< SLAB allocator cache */
	rwlock_t hlock; /**< Reader/writer lock on table contents */
	DECLARE_HASHTABLE(hhead, 4U); /**< Buffer hashtable head */
};

/**
 * @brief Capture surface NvRm and IOVA addresses handle.
 */
union capture_surface {
	uint64_t raw; /**< Pinned VI or ISP IOVA address */
	struct {
		uint32_t offset; /**< NvRm handle (upper 32 bits) */
		uint32_t hmem;
			/**<
			 * Offset of surface or pushbuffer address in descriptor
			 * (lower 32 bits) [byte]
			 */
	};
};

/**
 * @brief Capture buffer mapping (pinned).
 */
struct capture_mapping {
	struct hlist_node hnode; /**< Hash table node struct */
	atomic_t refcnt; /**< Capture mapping reference count */
	struct dma_buf *buf; /** Capture mapping dma_buf */
	struct dma_buf_attachment *atch;
		/**< dma_buf attachment (VI or ISP device) */
	struct sg_table *sgt; /**< Scatterlist to dma_buf attachment */
	unsigned int flag; /**< Bitmask access flag */
};

/**
 * @brief Determine whether all the bits of @a other are set in @a self.
 *
 * @param[in]	self	Bitmask flag to be compared
 * @param[in]	other	Bitmask value(s) to compare
 *
 * @retval	true	compatible
 * @retval	false	not compatible
 */
static inline bool flag_compatible(
	unsigned int self,
	unsigned int other)
{
	return (self & other) == other;
}

/**
 * @brief Determine whether BUFFER_RDWR is set in @a flag.
 *
 * @param[in]	flag	Bitmask flag to be compared
 *
 * @retval	true	BUFFER_RDWR set
 * @retval	false	BUFFER_RDWR not set
 */
static inline unsigned int flag_access_mode(
	unsigned int flag)
{
	return flag & BUFFER_RDWR;
}

/**
 * @brief Map capture common buffer access flag to a Linux dma_data_direction.
 *
 * @param[in]	flag	Bitmask access flag of capture common buffer
 *
 * @returns	@ref dma_data_direction mapping
 */
static inline enum dma_data_direction flag_dma_direction(
	unsigned int flag)
{
	static const enum dma_data_direction dir[4U] = {
		[0U] = DMA_BIDIRECTIONAL,
		[BUFFER_READ] = DMA_TO_DEVICE,
		[BUFFER_WRITE] = DMA_FROM_DEVICE,
		[BUFFER_RDWR] = DMA_BIDIRECTIONAL,
	};

	return dir[flag_access_mode(flag)];
}

/**
 * @brief Retrieve the scatterlist IOVA address of the capture surface mapping.
 *
 * @param[in]	pin	The capture_mapping of the buffer
 *
 * @returns	Physical address of scatterlist mapping
 */
static inline dma_addr_t mapping_iova(
	const struct capture_mapping *pin,
	uint64_t mem_offset)
{
	dma_addr_t iova = 0;
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 10, 0)
	iova = (sg_dma_address(pin->sgt->sgl) != 0) ? sg_dma_address(pin->sgt->sgl) :
		sg_phys(pin->sgt->sgl);
	iova += mem_offset;
#else
	struct scatterlist *sg;
	uint64_t mem_offset_adjusted = mem_offset;
	int i;

	/* Traverse the scatterlist and adjust the offset
	 * as per the page block. This is needed in case
	 * where memory spans across multiple pages and
	 * is non-contiguous
	 */
	for_each_sgtable_dma_sg(pin->sgt, sg, i) {
		if (mem_offset_adjusted < sg_dma_len(sg)) {
			iova = (sg_dma_address(sg) == 0) ? sg_phys(sg) : sg_dma_address(sg);
			iova += mem_offset_adjusted;
			if (iova < mem_offset_adjusted) {
				/** It means iova has wrapped */
				return 0;
			}
			break;
		}
		mem_offset_adjusted -=  sg_dma_len(sg);
	}
#endif

	return iova;
}

/**
 * @brief Retrieve the dma_buf pointer of a capture surface mapping.
 *
 * @param[in]	pin	The capture_mapping of the buffer
 *
 * @returns	Pointer to the capture_mapping @ref dma_buf
 */
static inline struct dma_buf *mapping_buf(
	const struct capture_mapping *pin)
{
	return pin->buf;
}

/**
 * @brief Determine whether BUFFER_ADD is set in the capture surface mapping's
 * access flag.
 *
 * @param[in]	pin	The capture_mapping of the buffer
 *
 * @retval	true	BUFFER_ADD set
 * @retval	false	BUFFER_ADD not set
 */
static inline bool mapping_preserved(
	const struct capture_mapping *pin)
{
	return (bool)(pin->flag & BUFFER_ADD);
}

/**
 * @brief Set or unset the BUFFER_ADD bit in the capture surface mapping's
 * access flag, and correspondingly increment or decrement the mapping's refcnt.
 *
 * @param[in]	pin	The capture_mapping of the buffer
 * @param[in]	val	The capture_mapping of the buffer
 *
 * @retval	true	BUFFER_ADD set
 * @retval	false	BUFFER_ADD not set
 */
static inline void set_mapping_preservation(
	struct capture_mapping *pin,
	bool val)
{
	if (val) {
		pin->flag |= BUFFER_ADD;
		atomic_inc(&pin->refcnt);
	} else {
		pin->flag &= (~BUFFER_ADD);
		atomic_dec(&pin->refcnt);
	}
}

/**
 * @brief Iteratively search a capture buffer management table to find the entry
 * with @a buf, and @a flag bits set in the capture mapping.
 *
 * On success, the capture mapping is incremented by one if it is non-zero.
 *
 * @param[in]	tab	The capture buffer management table
 * @param[in]	buf	The mapping dma_buf pointer to match
 * @param[in]	flag	The mapping bitmask access flag to compare
 *
 * @returns	@ref capture_mapping pointer (success), NULL (failure)
 */
static struct capture_mapping *find_mapping(
	struct capture_buffer_table *tab,
	struct dma_buf *buf,
	unsigned int flag)
{
	struct capture_mapping *pin;
	bool success;

	read_lock(&tab->hlock);

	hash_for_each_possible(tab->hhead, pin, hnode, (unsigned long)buf) {
		if (
			(pin->buf == buf) &&
			flag_compatible(pin->flag, flag)
		) {
			success =  atomic_inc_not_zero(&pin->refcnt);
			if (success) {
				read_unlock(&tab->hlock);
				return pin;
			}
		}
	}

	read_unlock(&tab->hlock);

	return NULL;
}

/**
 * @brief Add an NvRm buffer to the buffer management table and initialize its
 * refcnt to 1.
 *
 * @param[in]	tab	The capture buffer management table
 * @param[in]	fd	The NvRm handle
 * @param[in]	flag	The mapping bitmask access flag to set
 *
 * @returns	@ref capture_mapping pointer (success), PTR_ERR (failure)
 */
static struct capture_mapping *get_mapping(
	struct capture_buffer_table *tab,
	uint32_t fd,
	unsigned int flag)
{
	struct capture_mapping *pin;
	struct dma_buf *buf;
	void *err;

	if (unlikely(tab == NULL)) {
		pr_err("%s: invalid buffer table\n", __func__);
		return ERR_PTR(-EINVAL);
	}

	buf = dma_buf_get((int)fd);
	if (IS_ERR(buf)) {
		dev_err(tab->dev, "%s:%d: invalid memfd %u; errno %ld \n",
			 __func__, __LINE__, fd, PTR_ERR(buf));
		return ERR_CAST(buf);
	}

	pin = find_mapping(tab, buf, flag);
	if (pin != NULL) {
		dma_buf_put(buf);
		return pin;
	}

	pin = kmem_cache_alloc(tab->cache, GFP_KERNEL);
	if (unlikely(pin == NULL)) {
		err = ERR_PTR(-ENOMEM);
		goto err0;
	}

	pin->atch = dma_buf_attach(buf, tab->dev);
	if (unlikely(IS_ERR(pin->atch))) {
		err = pin->atch;
		goto err1;
	}

	pin->sgt = dma_buf_map_attachment(pin->atch, flag_dma_direction(flag));
	if (unlikely(IS_ERR(pin->sgt))) {
		err = pin->sgt;
		goto err2;
	}

	pin->flag = flag;
	pin->buf = buf;
	atomic_set(&pin->refcnt, 1U);
	INIT_HLIST_NODE(&pin->hnode);

	write_lock(&tab->hlock);
	hash_add(tab->hhead, &pin->hnode, (unsigned long)pin->buf);
	write_unlock(&tab->hlock);

	return pin;
err2:
	dma_buf_detach(buf, pin->atch);
err1:
	kmem_cache_free(tab->cache, pin);
err0:
	dma_buf_put(buf);
	dev_err(tab->dev, "%s:%d: memfd %u, flag %u; errno %ld \n",
		__func__, __LINE__,fd, flag, PTR_ERR(buf));
	return err;
}

struct capture_buffer_table *create_buffer_table(
	struct device *dev)
{
	struct capture_buffer_table *tab;

	tab = kmalloc(sizeof(*tab), GFP_KERNEL);

	if (likely(tab != NULL)) {
		tab->cache = KMEM_CACHE(capture_mapping, 0U);

		if (likely(tab->cache != NULL)) {
			tab->dev = dev;
			hash_init(tab->hhead);
			rwlock_init(&tab->hlock);
		} else {
			kfree(tab);
			tab = NULL;
		}
	}

	return tab;
}
EXPORT_SYMBOL_GPL(create_buffer_table);

void destroy_buffer_table(
	struct capture_buffer_table *tab)
{
	size_t bkt;
	struct hlist_node *next;
	struct capture_mapping *pin;

	if (unlikely(tab == NULL))
		return;


	hash_for_each_safe(tab->hhead, bkt, next, pin, hnode) {
		write_lock(&tab->hlock);
		hash_del(&pin->hnode);
		write_unlock(&tab->hlock);
		dma_buf_unmap_attachment(
			pin->atch, pin->sgt, flag_dma_direction(pin->flag));
		dma_buf_detach(pin->buf, pin->atch);
		dma_buf_put(pin->buf);
		kmem_cache_free(tab->cache, pin);
	}


	kmem_cache_destroy(tab->cache);
	kfree(tab);
}
EXPORT_SYMBOL_GPL(destroy_buffer_table);

static DEFINE_MUTEX(req_lock);

int capture_buffer_request(
	struct capture_buffer_table *tab,
	uint32_t memfd,
	uint32_t flag)
{
	struct capture_mapping *pin;
	struct dma_buf *buf;
	bool add = (bool)(flag & BUFFER_ADD);
	int err = 0;

	if (unlikely(tab == NULL)) {
		pr_err("%s: invalid buffer table\n", __func__);
		return -EINVAL;
	}

	mutex_lock(&req_lock);

	if (add) {
		pin = get_mapping(tab, memfd, flag_access_mode(flag));
		if (IS_ERR(pin)) {
			err = PTR_ERR_OR_ZERO(pin);
			dev_err(tab->dev, "%s:%d: memfd %u, flag %u; errno %d",
				__func__, __LINE__, memfd, flag, err);
			goto end;
		}

		if (mapping_preserved(pin)) {
			err = -EEXIST;
			dev_err(tab->dev, "%s:%d: memfd %u exists; errno %d",
				__func__, __LINE__, memfd, err);
			put_mapping(tab, pin);
			goto end;
		}
	} else {
		buf = dma_buf_get((int)memfd);
		if (IS_ERR(buf)) {
			err = PTR_ERR_OR_ZERO(buf);
			dev_err(tab->dev, "%s:%d: invalid memfd %u; errno %d",
				__func__, __LINE__, memfd, err);
			goto end;
		}

		pin = find_mapping(tab, buf, BUFFER_ADD);
		if (pin == NULL) {
			err = -ENOENT;
			dev_err(tab->dev, "%s:%d: memfd %u not exists; errno %d",
				__func__, __LINE__, memfd, err);
			dma_buf_put(buf);
			goto end;
		}
		dma_buf_put(buf);
	}

	set_mapping_preservation(pin, add);
	put_mapping(tab, pin);

end:
	mutex_unlock(&req_lock);
	return err;
}
EXPORT_SYMBOL_GPL(capture_buffer_request);

int capture_buffer_add(
	struct capture_buffer_table *t,
	uint32_t fd)
{
	return capture_buffer_request(t, fd, BUFFER_ADD | BUFFER_RDWR);
}
EXPORT_SYMBOL_GPL(capture_buffer_add);

void put_mapping(
	struct capture_buffer_table *t,
	struct capture_mapping *pin)
{
	bool zero;

	zero = atomic_dec_and_test(&pin->refcnt);
	if (zero) {
		if (unlikely(mapping_preserved(pin))) {
			dev_err(t->dev, "%s:%d: unexpected put for a preserved mapping",
				__func__, __LINE__);
			atomic_inc(&pin->refcnt);
			return;
		}

		write_lock(&t->hlock);
		hash_del(&pin->hnode);
		write_unlock(&t->hlock);

		dma_buf_unmap_attachment(
			pin->atch, pin->sgt, flag_dma_direction(pin->flag));
		dma_buf_detach(pin->buf, pin->atch);
		dma_buf_put(pin->buf);
		kmem_cache_free(t->cache, pin);
	}
}
EXPORT_SYMBOL_GPL(put_mapping);

int capture_common_pin_and_get_iova(struct capture_buffer_table *buf_ctx,
		uint32_t mem_handle, uint64_t mem_offset,
		uint64_t *meminfo_base_address, uint64_t *meminfo_size,
		struct capture_common_unpins *unpins)
{
	struct capture_mapping *map;
	struct dma_buf *buf;
	uint64_t size;
	uint64_t iova;

	/* NULL is a valid unput indicating unused data field */
	if (!mem_handle) {
		return 0;
	}

	if (unpins->num_unpins >= MAX_PIN_BUFFER_PER_REQUEST) {
		pr_err("%s: too many buffers per request\n", __func__);
			return -ENOMEM;
	}

	map = get_mapping(buf_ctx, mem_handle, BUFFER_RDWR);

	if (IS_ERR(map)) {
		pr_err("%s: cannot get mapping\n", __func__);
		return -EINVAL;
	}

	buf = mapping_buf(map);
	size = buf->size;
	if (mem_offset >= size) {
		pr_err("%s: offset is out of bounds\n", __func__);
		return -EINVAL;
	}

	iova = mapping_iova(map, mem_offset);
	if (iova == 0) {
		pr_err("%s: Invalid iova\n", __func__);
		return -EINVAL;
	}

	*meminfo_base_address = iova;
	*meminfo_size = size - mem_offset;

	unpins->data[unpins->num_unpins] = map;
	unpins->num_unpins++;
	return 0;
}
EXPORT_SYMBOL_GPL(capture_common_pin_and_get_iova);

int capture_common_setup_progress_status_notifier(
	struct capture_common_status_notifier *status_notifier,
	uint32_t mem,
	uint32_t buffer_size,
	uint32_t mem_offset)
{
	struct dma_buf *dmabuf;
#if defined(NV_LINUX_IOSYS_MAP_H_PRESENT)
	struct iosys_map map = {0};
#else
	struct dma_buf_map map = {0};
#endif
	void *va;
	int err = 0;

	/* take reference for the userctx */
	dmabuf = dma_buf_get(mem);
	if (IS_ERR(dmabuf))
		return PTR_ERR(dmabuf);

	if (buffer_size > U32_MAX - mem_offset) {
		pr_err("%s: buffer_size or mem_offset too large\n", __func__);
		return -EINVAL;
	}

	if ((buffer_size + mem_offset) > dmabuf->size) {
		dma_buf_put(dmabuf);
		pr_err("%s: invalid offset\n", __func__);
		return -EINVAL;
	}

	/* map handle and clear error notifier struct */
	err = dma_buf_vmap(dmabuf, &map);
  	va = err ? NULL : map.vaddr;
	if (!va) {
		dma_buf_put(dmabuf);
		pr_err("%s: Cannot map notifier handle\n", __func__);
		return -ENOMEM;
	}

	memset(va, 0, buffer_size);

	status_notifier->buf = dmabuf;
	status_notifier->va = va;
	status_notifier->offset = mem_offset;
	return 0;
}
EXPORT_SYMBOL_GPL(capture_common_setup_progress_status_notifier);

int capture_common_release_progress_status_notifier(
	struct capture_common_status_notifier *progress_status_notifier)
{
	struct dma_buf *dmabuf = progress_status_notifier->buf;
	void *va = progress_status_notifier->va;
#if defined(NV_LINUX_IOSYS_MAP_H_PRESENT)
	struct iosys_map map = IOSYS_MAP_INIT_VADDR(va);
#else
	struct dma_buf_map map = DMA_BUF_MAP_INIT_VADDR(va);
#endif

	if (dmabuf != NULL) {
		if (va != NULL)
			dma_buf_vunmap(dmabuf, &map);

		dma_buf_put(dmabuf);
	}

	progress_status_notifier->buf = NULL;
	progress_status_notifier->va = NULL;
	progress_status_notifier->offset = 0;

	return 0;
}
EXPORT_SYMBOL_GPL(capture_common_release_progress_status_notifier);

int capture_common_set_progress_status(
	struct capture_common_status_notifier *progress_status_notifier,
	uint32_t buffer_slot,
	uint32_t buffer_depth,
	uint8_t new_val)
{
	uint32_t *status_notifier = (uint32_t *) (progress_status_notifier->va +
			progress_status_notifier->offset);

	if (buffer_slot >= buffer_depth) {
		pr_err("%s: Invalid offset!", __func__);
		return -EINVAL;
	}
	buffer_slot = array_index_nospec(buffer_slot, buffer_depth);

	/*
	 * Since UMD and KMD can both write to the shared progress status
	 * notifier buffer, insert memory barrier here to ensure that any
	 * other store operations to the buffer would be done before the
	 * write below.
	 */
	wmb();

	status_notifier[buffer_slot] = new_val;

	return 0;
}
EXPORT_SYMBOL_GPL(capture_common_set_progress_status);

int capture_common_pin_memory(
	struct device *dev,
	uint32_t mem,
	struct capture_common_buf *unpin_data)
{
	struct dma_buf *buf;
#if defined(NV_LINUX_IOSYS_MAP_H_PRESENT)
	struct iosys_map map = {0};
#else
	struct dma_buf_map map = {0};
#endif
	struct dma_buf_attachment *attach;
	struct sg_table *sgt;
	int err = 0;

	buf = dma_buf_get(mem);
	if (IS_ERR(buf)) {
		err = PTR_ERR(buf);
		goto fail;
	}

	attach = dma_buf_attach(buf, dev);
	if (IS_ERR(attach)) {
		err = PTR_ERR(attach);
		goto fail;
	}

	sgt = dma_buf_map_attachment(attach, DMA_BIDIRECTIONAL);
	if (IS_ERR(sgt)) {
		err = PTR_ERR(sgt);
		goto fail;
	}

	if (sg_dma_address(sgt->sgl) == 0)
		sg_dma_address(sgt->sgl) = sg_phys(sgt->sgl);

	err = dma_buf_vmap(buf, &map);
	unpin_data->va = err ? NULL : map.vaddr;
	if (unpin_data->va == NULL) {
		pr_err("%s: failed to map pinned memory\n", __func__);
		goto fail;
	}

	unpin_data->iova = sg_dma_address(sgt->sgl);
	unpin_data->buf = buf;
	unpin_data->attach = attach;
	unpin_data->sgt = sgt;

	return 0;

fail:
	capture_common_unpin_memory(unpin_data);
	return err;
}
EXPORT_SYMBOL_GPL(capture_common_pin_memory);

void capture_common_unpin_memory(
	struct capture_common_buf *unpin_data)
{
#if defined(NV_LINUX_IOSYS_MAP_H_PRESENT)
	struct iosys_map map = IOSYS_MAP_INIT_VADDR(unpin_data->va);
#else
	struct dma_buf_map map = DMA_BUF_MAP_INIT_VADDR(unpin_data->va);
#endif

	if (unpin_data->va)
		dma_buf_vunmap(unpin_data->buf, &map);

	if (unpin_data->sgt != NULL)
		dma_buf_unmap_attachment(unpin_data->attach, unpin_data->sgt,
				DMA_BIDIRECTIONAL);
	if (unpin_data->attach != NULL)
		dma_buf_detach(unpin_data->buf, unpin_data->attach);
	if (unpin_data->buf != NULL)
		dma_buf_put(unpin_data->buf);

	unpin_data->sgt = NULL;
	unpin_data->attach = NULL;
	unpin_data->buf = NULL;
	unpin_data->iova = 0;
	unpin_data->va = NULL;
}
EXPORT_SYMBOL_GPL(capture_common_unpin_memory);
