/* SPDX-License-Identifier: BSD-3-Clause
 * Copyright(C) 2019 Marvell International Ltd.
 */

#include <rte_atomic.h>
#include <rte_bus_pci.h>
#include <rte_common.h>
#include <rte_eal.h>
#include <rte_io.h>
#include <rte_kvargs.h>
#include <rte_malloc.h>
#include <rte_mbuf_pool_ops.h>
#include <rte_pci.h>

#include "otx2_common.h"
#include "otx2_dev.h"
#include "otx2_mempool.h"

#define OTX2_NPA_DEV_NAME	RTE_STR(otx2_npa_dev_)
#define OTX2_NPA_DEV_NAME_LEN	(sizeof(OTX2_NPA_DEV_NAME) + PCI_PRI_STR_SIZE)

static inline int
npa_lf_alloc(struct otx2_npa_lf *lf)
{
	struct otx2_mbox *mbox = lf->mbox;
	struct npa_lf_alloc_req *req;
	struct npa_lf_alloc_rsp *rsp;
	int rc;

	req = otx2_mbox_alloc_msg_npa_lf_alloc(mbox);
	req->aura_sz = lf->aura_sz;
	req->nr_pools = lf->nr_pools;

	rc = otx2_mbox_process_msg(mbox, (void *)&rsp);
	if (rc)
		return NPA_LF_ERR_ALLOC;

	lf->stack_pg_ptrs = rsp->stack_pg_ptrs;
	lf->stack_pg_bytes = rsp->stack_pg_bytes;
	lf->qints = rsp->qints;

	return 0;
}

static int
npa_lf_free(struct otx2_mbox *mbox)
{
	otx2_mbox_alloc_msg_npa_lf_free(mbox);

	return otx2_mbox_process(mbox);
}

static int
npa_lf_init(struct otx2_npa_lf *lf, uintptr_t base, uint8_t aura_sz,
	    uint32_t nr_pools, struct otx2_mbox *mbox)
{
	uint32_t i, bmp_sz;
	int rc;

	/* Sanity checks */
	if (!lf || !base || !mbox || !nr_pools)
		return NPA_LF_ERR_PARAM;

	if (base & AURA_ID_MASK)
		return NPA_LF_ERR_BASE_INVALID;

	if (aura_sz == NPA_AURA_SZ_0 || aura_sz >= NPA_AURA_SZ_MAX)
		return NPA_LF_ERR_PARAM;

	memset(lf, 0x0, sizeof(*lf));
	lf->base = base;
	lf->aura_sz = aura_sz;
	lf->nr_pools = nr_pools;
	lf->mbox = mbox;

	rc = npa_lf_alloc(lf);
	if (rc)
		goto exit;

	bmp_sz = rte_bitmap_get_memory_footprint(nr_pools);

	/* Allocate memory for bitmap */
	lf->npa_bmp_mem = rte_zmalloc("npa_bmp_mem", bmp_sz,
					RTE_CACHE_LINE_SIZE);
	if (lf->npa_bmp_mem == NULL) {
		rc = -ENOMEM;
		goto lf_free;
	}

	/* Initialize pool resource bitmap array */
	lf->npa_bmp = rte_bitmap_init(nr_pools, lf->npa_bmp_mem, bmp_sz);
	if (lf->npa_bmp == NULL) {
		rc = -EINVAL;
		goto bmap_mem_free;
	}

	/* Mark all pools available */
	for (i = 0; i < nr_pools; i++)
		rte_bitmap_set(lf->npa_bmp, i);

	/* Allocate memory for qint context */
	lf->npa_qint_mem = rte_zmalloc("npa_qint_mem",
			sizeof(struct otx2_npa_qint) * nr_pools, 0);
	if (lf->npa_qint_mem == NULL) {
		rc = -ENOMEM;
		goto bmap_free;
	}

	/* Allocate memory for nap_aura_lim memory */
	lf->aura_lim = rte_zmalloc("npa_aura_lim_mem",
			sizeof(struct npa_aura_lim) * nr_pools, 0);
	if (lf->aura_lim == NULL) {
		rc = -ENOMEM;
		goto qint_free;
	}

	/* Init aura start & end limits */
	for (i = 0; i < nr_pools; i++) {
		lf->aura_lim[i].ptr_start = UINT64_MAX;
		lf->aura_lim[i].ptr_end = 0x0ull;
	}

	return 0;

qint_free:
	rte_free(lf->npa_qint_mem);
bmap_free:
	rte_bitmap_free(lf->npa_bmp);
bmap_mem_free:
	rte_free(lf->npa_bmp_mem);
lf_free:
	npa_lf_free(lf->mbox);
exit:
	return rc;
}

static int
npa_lf_fini(struct otx2_npa_lf *lf)
{
	if (!lf)
		return NPA_LF_ERR_PARAM;

	rte_free(lf->aura_lim);
	rte_free(lf->npa_qint_mem);
	rte_bitmap_free(lf->npa_bmp);
	rte_free(lf->npa_bmp_mem);

	return npa_lf_free(lf->mbox);

}

static inline uint32_t
otx2_aura_size_to_u32(uint8_t val)
{
	if (val == NPA_AURA_SZ_0)
		return 128;
	if (val >= NPA_AURA_SZ_MAX)
		return BIT_ULL(20);

	return 1 << (val + 6);
}

static int
parse_max_pools(const char *key, const char *value, void *extra_args)
{
	RTE_SET_USED(key);
	uint32_t val;

	val = atoi(value);
	if (val < otx2_aura_size_to_u32(NPA_AURA_SZ_128))
		val = 128;
	if (val > otx2_aura_size_to_u32(NPA_AURA_SZ_1M))
		val = BIT_ULL(20);

	*(uint8_t *)extra_args = rte_log2_u32(val) - 6;
	return 0;
}

#define OTX2_MAX_POOLS "max_pools"

static uint8_t
otx2_parse_aura_size(struct rte_devargs *devargs)
{
	uint8_t aura_sz = NPA_AURA_SZ_128;
	struct rte_kvargs *kvlist;

	if (devargs == NULL)
		goto exit;
	kvlist = rte_kvargs_parse(devargs->args, NULL);
	if (kvlist == NULL)
		goto exit;

	rte_kvargs_process(kvlist, OTX2_MAX_POOLS, &parse_max_pools, &aura_sz);
	rte_kvargs_free(kvlist);
exit:
	return aura_sz;
}

static inline int
npa_lf_attach(struct otx2_mbox *mbox)
{
	struct rsrc_attach_req *req;

	req = otx2_mbox_alloc_msg_attach_resources(mbox);
	req->npalf = true;

	return otx2_mbox_process(mbox);
}

static inline int
npa_lf_detach(struct otx2_mbox *mbox)
{
	struct rsrc_detach_req *req;

	req = otx2_mbox_alloc_msg_detach_resources(mbox);
	req->npalf = true;

	return otx2_mbox_process(mbox);
}

static inline int
npa_lf_get_msix_offset(struct otx2_mbox *mbox, uint16_t *npa_msixoff)
{
	struct msix_offset_rsp *msix_rsp;
	int rc;

	/* Get NPA and NIX MSIX vector offsets */
	otx2_mbox_alloc_msg_msix_offset(mbox);

	rc = otx2_mbox_process_msg(mbox, (void *)&msix_rsp);

	*npa_msixoff = msix_rsp->npa_msixoff;

	return rc;
}

/**
 * @internal
 * Finalize NPA LF.
 */
int
otx2_npa_lf_fini(void)
{
	struct otx2_idev_cfg *idev;
	int rc = 0;

	idev = otx2_intra_dev_get_cfg();
	if (idev == NULL)
		return -ENOMEM;

	if (rte_atomic16_add_return(&idev->npa_refcnt, -1) == 0) {
		otx2_npa_unregister_irqs(idev->npa_lf);
		rc |= npa_lf_fini(idev->npa_lf);
		rc |= npa_lf_detach(idev->npa_lf->mbox);
		otx2_npa_set_defaults(idev);
	}

	return rc;
}

/**
 * @internal
 * Initialize NPA LF.
 */
int
otx2_npa_lf_init(struct rte_pci_device *pci_dev, void *otx2_dev)
{
	struct otx2_dev *dev = otx2_dev;
	struct otx2_idev_cfg *idev;
	struct otx2_npa_lf *lf;
	uint16_t npa_msixoff;
	uint32_t nr_pools;
	uint8_t aura_sz;
	int rc;

	idev = otx2_intra_dev_get_cfg();
	if (idev == NULL)
		return -ENOMEM;

	/* Is NPA LF initialized by any another driver? */
	if (rte_atomic16_add_return(&idev->npa_refcnt, 1) == 1) {

		rc = npa_lf_attach(dev->mbox);
		if (rc)
			goto fail;

		rc = npa_lf_get_msix_offset(dev->mbox, &npa_msixoff);
		if (rc)
			goto npa_detach;

		aura_sz = otx2_parse_aura_size(pci_dev->device.devargs);
		nr_pools = otx2_aura_size_to_u32(aura_sz);

		lf = &dev->npalf;
		rc = npa_lf_init(lf, dev->bar2 + (RVU_BLOCK_ADDR_NPA << 20),
					aura_sz, nr_pools, dev->mbox);

		if (rc)
			goto npa_detach;

		lf->pf_func = dev->pf_func;
		lf->npa_msixoff = npa_msixoff;
		lf->intr_handle = &pci_dev->intr_handle;
		lf->pci_dev = pci_dev;

		idev->npa_pf_func = dev->pf_func;
		idev->npa_lf = lf;
		rte_smp_wmb();
		rc = otx2_npa_register_irqs(lf);
		if (rc)
			goto npa_fini;

		rte_mbuf_set_platform_mempool_ops("octeontx2_npa");
		otx2_npa_dbg("npa_lf=%p pools=%d sz=%d pf_func=0x%x msix=0x%x",
			     lf, nr_pools, aura_sz, lf->pf_func, npa_msixoff);
	}

	return 0;

npa_fini:
	npa_lf_fini(idev->npa_lf);
npa_detach:
	npa_lf_detach(dev->mbox);
fail:
	rte_atomic16_dec(&idev->npa_refcnt);
	return rc;
}

static inline char*
otx2_npa_dev_to_name(struct rte_pci_device *pci_dev, char *name)
{
	snprintf(name, OTX2_NPA_DEV_NAME_LEN,
		 OTX2_NPA_DEV_NAME  PCI_PRI_FMT,
		 pci_dev->addr.domain, pci_dev->addr.bus,
		 pci_dev->addr.devid, pci_dev->addr.function);

	return name;
}

static int
otx2_npa_init(struct rte_pci_device *pci_dev)
{
	char name[OTX2_NPA_DEV_NAME_LEN];
	const struct rte_memzone *mz;
	struct otx2_dev *dev;
	int rc = -ENOMEM;

	mz = rte_memzone_reserve_aligned(otx2_npa_dev_to_name(pci_dev, name),
					 sizeof(*dev), SOCKET_ID_ANY,
					 0, OTX2_ALIGN);
	if (mz == NULL)
		goto error;

	dev = mz->addr;

	/* Initialize the base otx2_dev object */
	rc = otx2_dev_init(pci_dev, dev);
	if (rc)
		goto malloc_fail;

	/* Grab the NPA LF if required */
	rc = otx2_npa_lf_init(pci_dev, dev);
	if (rc)
		goto dev_uninit;

	dev->drv_inited = true;
	return 0;

dev_uninit:
	otx2_npa_lf_fini();
	otx2_dev_fini(pci_dev, dev);
malloc_fail:
	rte_memzone_free(mz);
error:
	otx2_err("Failed to initialize npa device rc=%d", rc);
	return rc;
}

static int
otx2_npa_fini(struct rte_pci_device *pci_dev)
{
	char name[OTX2_NPA_DEV_NAME_LEN];
	const struct rte_memzone *mz;
	struct otx2_dev *dev;

	mz = rte_memzone_lookup(otx2_npa_dev_to_name(pci_dev, name));
	if (mz == NULL)
		return -EINVAL;

	dev = mz->addr;
	if (!dev->drv_inited)
		goto dev_fini;

	dev->drv_inited = false;
	otx2_npa_lf_fini();

dev_fini:
	if (otx2_npa_lf_active(dev)) {
		otx2_info("%s: common resource in use by other devices",
			  pci_dev->name);
		return -EAGAIN;
	}

	otx2_dev_fini(pci_dev, dev);
	rte_memzone_free(mz);

	return 0;
}

static int
npa_remove(struct rte_pci_device *pci_dev)
{
	if (rte_eal_process_type() != RTE_PROC_PRIMARY)
		return 0;

	return otx2_npa_fini(pci_dev);
}

static int
npa_probe(struct rte_pci_driver *pci_drv, struct rte_pci_device *pci_dev)
{
	RTE_SET_USED(pci_drv);

	if (rte_eal_process_type() != RTE_PROC_PRIMARY)
		return 0;

	return otx2_npa_init(pci_dev);
}

static const struct rte_pci_id pci_npa_map[] = {
	{
		RTE_PCI_DEVICE(PCI_VENDOR_ID_CAVIUM,
					PCI_DEVID_OCTEONTX2_RVU_NPA_PF)
	},
	{
		RTE_PCI_DEVICE(PCI_VENDOR_ID_CAVIUM,
					PCI_DEVID_OCTEONTX2_RVU_NPA_VF)
	},
	{
		.vendor_id = 0,
	},
};

static struct rte_pci_driver pci_npa = {
	.id_table = pci_npa_map,
	.drv_flags = RTE_PCI_DRV_NEED_MAPPING | RTE_PCI_DRV_NEED_IOVA_AS_VA,
	.probe = npa_probe,
	.remove = npa_remove,
};

RTE_PMD_REGISTER_PCI(mempool_octeontx2, pci_npa);
RTE_PMD_REGISTER_PCI_TABLE(mempool_octeontx2, pci_npa_map);
RTE_PMD_REGISTER_KMOD_DEP(mempool_octeontx2, "vfio-pci");
RTE_PMD_REGISTER_PARAM_STRING(mempool_octeontx2,
			      OTX2_MAX_POOLS "=<128-1048576>");
