// SPDX-License-Identifier: GPL-2.0-only
/* Copyright(c) 2014 - 2022 Intel Corporation */
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/pci.h>
#include <linux/scatterlist.h>
#include <linux/slab.h>
#include <linux/types.h>
#include "adf_accel_devices.h"
#include "qat_bl.h"
#include "qat_crypto.h"

void qat_bl_free_bufl(struct adf_accel_dev *accel_dev,
		      struct qat_request_buffs *buf)
{
	struct device *dev = &GET_DEV(accel_dev);
	struct qat_alg_buf_list *bl = buf->bl;
	struct qat_alg_buf_list *blout = buf->blout;
	dma_addr_t blp = buf->blp;
	dma_addr_t blpout = buf->bloutp;
	size_t sz = buf->sz;
	size_t sz_out = buf->sz_out;
	int bl_dma_dir;
	int i;

	bl_dma_dir = blp != blpout ? DMA_TO_DEVICE : DMA_BIDIRECTIONAL;

	for (i = 0; i < bl->num_bufs; i++)
		dma_unmap_single(dev, bl->buffers[i].addr,
				 bl->buffers[i].len, bl_dma_dir);

	dma_unmap_single(dev, blp, sz, DMA_TO_DEVICE);

	if (!buf->sgl_src_valid)
		kfree(bl);

	if (blp != blpout) {
		for (i = 0; i < blout->num_mapped_bufs; i++) {
			dma_unmap_single(dev, blout->buffers[i].addr,
					 blout->buffers[i].len,
					 DMA_FROM_DEVICE);
		}
		dma_unmap_single(dev, blpout, sz_out, DMA_TO_DEVICE);

		if (!buf->sgl_dst_valid)
			kfree(blout);
	}
}

static int __qat_bl_sgl_to_bufl(struct adf_accel_dev *accel_dev,
				struct scatterlist *sgl,
				struct scatterlist *sglout,
				struct qat_request_buffs *buf,
				dma_addr_t extra_dst_buff,
				size_t sz_extra_dst_buff,
				unsigned int sskip,
				unsigned int dskip,
				gfp_t flags)
{
	struct device *dev = &GET_DEV(accel_dev);
	int i, sg_nctr = 0;
	int n = sg_nents(sgl);
	struct qat_alg_buf_list *bufl;
	struct qat_alg_buf_list *buflout = NULL;
	dma_addr_t blp = DMA_MAPPING_ERROR;
	dma_addr_t bloutp = DMA_MAPPING_ERROR;
	struct scatterlist *sg;
	size_t sz_out, sz = struct_size(bufl, buffers, n);
	int node = dev_to_node(&GET_DEV(accel_dev));
	unsigned int left;
	int bufl_dma_dir;

	if (unlikely(!n))
		return -EINVAL;

	buf->sgl_src_valid = false;
	buf->sgl_dst_valid = false;

	if (n > QAT_MAX_BUFF_DESC) {
		bufl = kzalloc_node(sz, flags, node);
		if (unlikely(!bufl))
			return -ENOMEM;
	} else {
		bufl = &buf->sgl_src.sgl_hdr;
		memset(bufl, 0, sizeof(struct qat_alg_buf_list));
		buf->sgl_src_valid = true;
	}

	bufl_dma_dir = sgl != sglout ? DMA_TO_DEVICE : DMA_BIDIRECTIONAL;

	for (i = 0; i < n; i++)
		bufl->buffers[i].addr = DMA_MAPPING_ERROR;

	left = sskip;

	for_each_sg(sgl, sg, n, i) {
		int y = sg_nctr;

		if (!sg->length)
			continue;

		if (left >= sg->length) {
			left -= sg->length;
			continue;
		}
		bufl->buffers[y].addr = dma_map_single(dev, sg_virt(sg) + left,
						       sg->length - left,
						       bufl_dma_dir);
		bufl->buffers[y].len = sg->length;
		if (unlikely(dma_mapping_error(dev, bufl->buffers[y].addr)))
			goto err_in;
		sg_nctr++;
		if (left) {
			bufl->buffers[y].len -= left;
			left = 0;
		}
	}
	bufl->num_bufs = sg_nctr;
	blp = dma_map_single(dev, bufl, sz, DMA_TO_DEVICE);
	if (unlikely(dma_mapping_error(dev, blp)))
		goto err_in;
	buf->bl = bufl;
	buf->blp = blp;
	buf->sz = sz;
	/* Handle out of place operation */
	if (sgl != sglout) {
		struct qat_alg_buf *buffers;
		int extra_buff = extra_dst_buff ? 1 : 0;
		int n_sglout = sg_nents(sglout);

		n = n_sglout + extra_buff;
		sz_out = struct_size(buflout, buffers, n);
		left = dskip;

		sg_nctr = 0;

		if (n > QAT_MAX_BUFF_DESC) {
			buflout = kzalloc_node(sz_out, flags, node);
			if (unlikely(!buflout))
				goto err_in;
		} else {
			buflout = &buf->sgl_dst.sgl_hdr;
			memset(buflout, 0, sizeof(struct qat_alg_buf_list));
			buf->sgl_dst_valid = true;
		}

		buffers = buflout->buffers;
		for (i = 0; i < n; i++)
			buffers[i].addr = DMA_MAPPING_ERROR;

		for_each_sg(sglout, sg, n_sglout, i) {
			int y = sg_nctr;

			if (!sg->length)
				continue;

			if (left >= sg->length) {
				left -= sg->length;
				continue;
			}
			buffers[y].addr = dma_map_single(dev, sg_virt(sg) + left,
							 sg->length - left,
							 DMA_FROM_DEVICE);
			if (unlikely(dma_mapping_error(dev, buffers[y].addr)))
				goto err_out;
			buffers[y].len = sg->length;
			sg_nctr++;
			if (left) {
				buffers[y].len -= left;
				left = 0;
			}
		}
		if (extra_buff) {
			buffers[sg_nctr].addr = extra_dst_buff;
			buffers[sg_nctr].len = sz_extra_dst_buff;
		}

		buflout->num_bufs = sg_nctr;
		buflout->num_bufs += extra_buff;
		buflout->num_mapped_bufs = sg_nctr;
		bloutp = dma_map_single(dev, buflout, sz_out, DMA_TO_DEVICE);
		if (unlikely(dma_mapping_error(dev, bloutp)))
			goto err_out;
		buf->blout = buflout;
		buf->bloutp = bloutp;
		buf->sz_out = sz_out;
	} else {
		/* Otherwise set the src and dst to the same address */
		buf->bloutp = buf->blp;
		buf->sz_out = 0;
	}
	return 0;

err_out:
	if (!dma_mapping_error(dev, bloutp))
		dma_unmap_single(dev, bloutp, sz_out, DMA_TO_DEVICE);

	n = sg_nents(sglout);
	for (i = 0; i < n; i++) {
		if (buflout->buffers[i].addr == extra_dst_buff)
			break;
		if (!dma_mapping_error(dev, buflout->buffers[i].addr))
			dma_unmap_single(dev, buflout->buffers[i].addr,
					 buflout->buffers[i].len,
					 DMA_FROM_DEVICE);
	}

	if (!buf->sgl_dst_valid)
		kfree(buflout);

err_in:
	if (!dma_mapping_error(dev, blp))
		dma_unmap_single(dev, blp, sz, DMA_TO_DEVICE);

	n = sg_nents(sgl);
	for (i = 0; i < n; i++)
		if (!dma_mapping_error(dev, bufl->buffers[i].addr))
			dma_unmap_single(dev, bufl->buffers[i].addr,
					 bufl->buffers[i].len,
					 bufl_dma_dir);

	if (!buf->sgl_src_valid)
		kfree(bufl);

	dev_err(dev, "Failed to map buf for dma\n");
	return -ENOMEM;
}

int qat_bl_sgl_to_bufl(struct adf_accel_dev *accel_dev,
		       struct scatterlist *sgl,
		       struct scatterlist *sglout,
		       struct qat_request_buffs *buf,
		       struct qat_sgl_to_bufl_params *params,
		       gfp_t flags)
{
	dma_addr_t extra_dst_buff = 0;
	size_t sz_extra_dst_buff = 0;
	unsigned int sskip = 0;
	unsigned int dskip = 0;

	if (params) {
		extra_dst_buff = params->extra_dst_buff;
		sz_extra_dst_buff = params->sz_extra_dst_buff;
		sskip = params->sskip;
		dskip = params->dskip;
	}

	return __qat_bl_sgl_to_bufl(accel_dev, sgl, sglout, buf,
				    extra_dst_buff, sz_extra_dst_buff,
				    sskip, dskip, flags);
}

static void qat_bl_sgl_unmap(struct adf_accel_dev *accel_dev,
			     struct qat_alg_buf_list *bl)
{
	struct device *dev = &GET_DEV(accel_dev);
	int n = bl->num_bufs;
	int i;

	for (i = 0; i < n; i++)
		if (!dma_mapping_error(dev, bl->buffers[i].addr))
			dma_unmap_single(dev, bl->buffers[i].addr,
					 bl->buffers[i].len, DMA_FROM_DEVICE);
}

static int qat_bl_sgl_map(struct adf_accel_dev *accel_dev,
			  struct scatterlist *sgl,
			  struct qat_alg_buf_list **bl)
{
	struct device *dev = &GET_DEV(accel_dev);
	struct qat_alg_buf_list *bufl;
	int node = dev_to_node(dev);
	struct scatterlist *sg;
	int n, i, sg_nctr;
	size_t sz;

	n = sg_nents(sgl);
	sz = struct_size(bufl, buffers, n);
	bufl = kzalloc_node(sz, GFP_KERNEL, node);
	if (unlikely(!bufl))
		return -ENOMEM;

	for (i = 0; i < n; i++)
		bufl->buffers[i].addr = DMA_MAPPING_ERROR;

	sg_nctr = 0;
	for_each_sg(sgl, sg, n, i) {
		int y = sg_nctr;

		if (!sg->length)
			continue;

		bufl->buffers[y].addr = dma_map_single(dev, sg_virt(sg),
						       sg->length,
						       DMA_FROM_DEVICE);
		bufl->buffers[y].len = sg->length;
		if (unlikely(dma_mapping_error(dev, bufl->buffers[y].addr)))
			goto err_map;
		sg_nctr++;
	}
	bufl->num_bufs = sg_nctr;
	bufl->num_mapped_bufs = sg_nctr;

	*bl = bufl;

	return 0;

err_map:
	for (i = 0; i < n; i++)
		if (!dma_mapping_error(dev, bufl->buffers[i].addr))
			dma_unmap_single(dev, bufl->buffers[i].addr,
					 bufl->buffers[i].len,
					 DMA_FROM_DEVICE);
	kfree(bufl);
	*bl = NULL;

	return -ENOMEM;
}

static void qat_bl_sgl_free_unmap(struct adf_accel_dev *accel_dev,
				  struct scatterlist *sgl,
				  struct qat_alg_buf_list *bl,
				  bool free_bl)
{
	if (bl) {
		qat_bl_sgl_unmap(accel_dev, bl);

		if (free_bl)
			kfree(bl);
	}
	if (sgl)
		sgl_free(sgl);
}

static int qat_bl_sgl_alloc_map(struct adf_accel_dev *accel_dev,
				struct scatterlist **sgl,
				struct qat_alg_buf_list **bl,
				unsigned int dlen,
				gfp_t gfp)
{
	struct scatterlist *dst;
	int ret;

	dst = sgl_alloc(dlen, gfp, NULL);
	if (!dst) {
		dev_err(&GET_DEV(accel_dev), "sg_alloc failed\n");
		return -ENOMEM;
	}

	ret = qat_bl_sgl_map(accel_dev, dst, bl);
	if (ret)
		goto err;

	*sgl = dst;

	return 0;

err:
	sgl_free(dst);
	*sgl = NULL;
	return ret;
}

int qat_bl_realloc_map_new_dst(struct adf_accel_dev *accel_dev,
			       struct scatterlist **sg,
			       unsigned int dlen,
			       struct qat_request_buffs *qat_bufs,
			       gfp_t gfp)
{
	struct device *dev = &GET_DEV(accel_dev);
	dma_addr_t new_blp = DMA_MAPPING_ERROR;
	struct qat_alg_buf_list *new_bl;
	struct scatterlist *new_sg;
	size_t new_bl_size;
	int ret;

	ret = qat_bl_sgl_alloc_map(accel_dev, &new_sg, &new_bl, dlen, gfp);
	if (ret)
		return ret;

	new_bl_size = struct_size(new_bl, buffers, new_bl->num_bufs);

	/* Map new firmware SGL descriptor */
	new_blp = dma_map_single(dev, new_bl, new_bl_size, DMA_TO_DEVICE);
	if (unlikely(dma_mapping_error(dev, new_blp)))
		goto err;

	/* Unmap old firmware SGL descriptor */
	dma_unmap_single(dev, qat_bufs->bloutp, qat_bufs->sz_out, DMA_TO_DEVICE);

	/* Free and unmap old scatterlist */
	qat_bl_sgl_free_unmap(accel_dev, *sg, qat_bufs->blout,
			      !qat_bufs->sgl_dst_valid);

	qat_bufs->sgl_dst_valid = false;
	qat_bufs->blout = new_bl;
	qat_bufs->bloutp = new_blp;
	qat_bufs->sz_out = new_bl_size;

	*sg = new_sg;

	return 0;
err:
	qat_bl_sgl_free_unmap(accel_dev, new_sg, new_bl, true);

	if (!dma_mapping_error(dev, new_blp))
		dma_unmap_single(dev, new_blp, new_bl_size, DMA_TO_DEVICE);

	return -ENOMEM;
}
