#ifdef WITH_GDS
#include "nvfs-dma.h"
#else
#include <lnet/lnet_gds.h>
#endif

#include <lnet/lnet_rdma.h>
#include <libcfs/libcfs.h>

/* MAX / MIN conflict */
#include <lnet/lib-lnet.h>

#define REGSTR2(x) x##_register_nvfs_dma_ops
#define REGSTR(x)  REGSTR2(x)

#define UNREGSTR2(x) x##_unregister_nvfs_dma_ops
#define UNREGSTR(x)  UNREGSTR2(x)

#define MODULE_PREFIX lustre_v1

#define REGISTER_FUNC REGSTR(MODULE_PREFIX)
#define UNREGISTER_FUNC UNREGSTR(MODULE_PREFIX)

#define NVFS_IO_ERR			-1
#define NVFS_CPU_REQ			-2

#define NVFS_HOLD_TIME_MS 1000

#define ERROR_PRINT_DEADLINE 3600

atomic_t nvfs_shutdown = ATOMIC_INIT(1);
struct nvfs_dma_rw_ops *nvfs_ops = NULL;
struct percpu_counter nvfs_n_ops;

static inline long nvfs_count_ops(void)
{
	return percpu_counter_sum(&nvfs_n_ops);
}

static struct nvfs_dma_rw_ops *nvfs_get_ops(void)
{
	if (!nvfs_ops || atomic_read(&nvfs_shutdown))
		return NULL;

	percpu_counter_inc(&nvfs_n_ops);

	return nvfs_ops;
}

static inline void nvfs_put_ops(void)
{
	percpu_counter_dec(&nvfs_n_ops);
}

static inline bool nvfs_check_feature_set(struct nvfs_dma_rw_ops *ops)
{
	bool supported = true;
	static time64_t last_printed;

	if (unlikely(!NVIDIA_FS_CHECK_FT_SGLIST_PREP(ops))) {
		if ((ktime_get_seconds() - last_printed) > ERROR_PRINT_DEADLINE)
			CDEBUG(D_CONSOLE,
			       "NVFS sg list preparation callback missing\n");
		supported = false;
	}
	if (unlikely(!NVIDIA_FS_CHECK_FT_SGLIST_DMA(ops))) {
		if ((ktime_get_seconds() - last_printed) > ERROR_PRINT_DEADLINE)
			CDEBUG(D_CONSOLE,
			       "NVFS DMA mapping callbacks missing\n");
		supported = false;
	}
	if (unlikely(!NVIDIA_FS_CHECK_FT_GPU_PAGE(ops))) {
		if ((ktime_get_seconds() - last_printed) > ERROR_PRINT_DEADLINE)
			CDEBUG(D_CONSOLE,
			       "NVFS page identification callback missing\n");
		supported = false;
	}
	if (unlikely(!NVIDIA_FS_CHECK_FT_DEVICE_PRIORITY(ops))) {
		if ((ktime_get_seconds() - last_printed) > ERROR_PRINT_DEADLINE)
			CDEBUG(D_CONSOLE,
			       "NVFS device priority callback not missing\n");
		supported = false;
	}

	if (unlikely(!supported &&
		     ((ktime_get_seconds() - last_printed) > ERROR_PRINT_DEADLINE)))
		last_printed = ktime_get_seconds();
	else if (supported)
		last_printed = 0;

	return supported;
}

int REGISTER_FUNC(struct nvfs_dma_rw_ops *ops)
{
	if (!ops || !nvfs_check_feature_set(ops))
		return -EINVAL;

	nvfs_ops = ops;
	(void)percpu_counter_init(&nvfs_n_ops, 0, GFP_KERNEL);
	atomic_set(&nvfs_shutdown, 0);
	CDEBUG(D_NET, "registering nvfs %p\n", ops);
	return 0;
}
EXPORT_SYMBOL(REGISTER_FUNC);

void UNREGISTER_FUNC(void)
{
	(void)atomic_cmpxchg(&nvfs_shutdown, 0, 1);
	do {
		CDEBUG(D_NET, "Attempting to de-register nvfs: %ld\n",
		       nvfs_count_ops());
		msleep(NVFS_HOLD_TIME_MS);
	} while (nvfs_count_ops());
	nvfs_ops = NULL;
	percpu_counter_destroy(&nvfs_n_ops);
}
EXPORT_SYMBOL(UNREGISTER_FUNC);

unsigned int
lnet_get_dev_prio(struct device *dev, unsigned int dev_idx)
{
	unsigned int dev_prio = UINT_MAX;
	struct nvfs_dma_rw_ops *nvfs_ops;

	if (!dev)
		return dev_prio;

	nvfs_ops = nvfs_get_ops();
	if (!nvfs_ops)
		return dev_prio;

	dev_prio = nvfs_ops->nvfs_device_priority (dev, dev_idx);

	nvfs_put_ops();
	return dev_prio;
}
EXPORT_SYMBOL(lnet_get_dev_prio);

unsigned int
lnet_get_dev_idx(struct page *page)
{
	unsigned int dev_idx = UINT_MAX;
	struct nvfs_dma_rw_ops *nvfs_ops;

	nvfs_ops = nvfs_get_ops();
	if (!nvfs_ops)
		return dev_idx;

	dev_idx = nvfs_ops->nvfs_gpu_index(page);

	nvfs_put_ops();
	return dev_idx;
}

int lnet_rdma_map_sg_attrs(struct device *dev, struct scatterlist *sg,
			   int nents, enum dma_data_direction direction)
{
	struct nvfs_dma_rw_ops *nvfs_ops = nvfs_get_ops();

	if (nvfs_ops) {
		int count;

		count = nvfs_ops->nvfs_dma_map_sg_attrs(dev,
				sg, nents, direction,
				DMA_ATTR_NO_WARN);

		if (unlikely((count == NVFS_IO_ERR))) {
			nvfs_put_ops();
			return -EIO;
		}

		if (unlikely(count == NVFS_CPU_REQ))
			nvfs_put_ops();
		else
			return count;
	}

	return 0;
}
EXPORT_SYMBOL(lnet_rdma_map_sg_attrs);

int lnet_rdma_unmap_sg(struct device *dev,
		       struct scatterlist *sg, int nents,
		       enum dma_data_direction direction)
{
	struct nvfs_dma_rw_ops *nvfs_ops = nvfs_get_ops();

	if (nvfs_ops) {
		int count;

		count = nvfs_ops->nvfs_dma_unmap_sg(dev, sg,
						    nents, direction);

		/* drop the count we got by calling nvfs_get_ops() */
		nvfs_put_ops();

		if (count) {
			nvfs_put_ops();
			return count;
		}
	}

	return 0;
}
EXPORT_SYMBOL(lnet_rdma_unmap_sg);

bool
lnet_is_rdma_only_page(struct page *page)
{
	bool found = false;
	struct nvfs_dma_rw_ops *nvfs_ops;

	if (!page)
		return found;

	nvfs_ops = nvfs_get_ops();
	if (!nvfs_ops)
		return found;

	if (!nvfs_ops->nvfs_is_gpu_page(page))
		goto out;

	found = true;

out:
	nvfs_put_ops();
	return found;
}
EXPORT_SYMBOL(lnet_is_rdma_only_page);
