/* SPDX-License-Identifier: BSD-3-Clause
 * Copyright 2018-2019 NXP
 */

#include "pfe_logs.h"
#include "pfe_mod.h"

unsigned int emac_txq_cnt;

/*
 * @pfe_hal_lib.c
 * Common functions used by HIF client drivers
 */

/*HIF shared memory Global variable */
struct hif_shm ghif_shm;

/* Cleanup the HIF shared memory, release HIF rx_buffer_pool.
 * This function should be called after pfe_hif_exit
 *
 * @param[in] hif_shm		Shared memory address location in DDR
 */
void
pfe_hif_shm_clean(struct hif_shm *hif_shm)
{
	unsigned int i;
	void *pkt;

	for (i = 0; i < hif_shm->rx_buf_pool_cnt; i++) {
		pkt = hif_shm->rx_buf_pool[i];
		if (pkt)
			rte_pktmbuf_free((struct rte_mbuf *)pkt);
	}
}

/* Initialize shared memory used between HIF driver and clients,
 * allocate rx_buffer_pool required for HIF Rx descriptors.
 * This function should be called before initializing HIF driver.
 *
 * @param[in] hif_shm		Shared memory address location in DDR
 * @return			0 - on succes, <0 on fail to initialize
 */
int
pfe_hif_shm_init(struct hif_shm *hif_shm, struct rte_mempool *mb_pool)
{
	unsigned int i;
	struct rte_mbuf *mbuf;

	memset(hif_shm, 0, sizeof(struct hif_shm));
	hif_shm->rx_buf_pool_cnt = HIF_RX_DESC_NT;

	for (i = 0; i < hif_shm->rx_buf_pool_cnt; i++) {
		mbuf = rte_cpu_to_le_64(rte_pktmbuf_alloc(mb_pool));
		if (mbuf)
			hif_shm->rx_buf_pool[i] = mbuf;
		else
			goto err0;
	}

	return 0;

err0:
	PFE_PMD_ERR("Low memory");
	pfe_hif_shm_clean(hif_shm);
	return -ENOMEM;
}

/*This function sends indication to HIF driver
 *
 * @param[in] hif	hif context
 */
static void
hif_lib_indicate_hif(struct pfe_hif *hif, int req, int data1, int
		     data2)
{
	hif_process_client_req(hif, req, data1, data2);
}

void
hif_lib_indicate_client(struct hif_client_s *client, int event_type,
			int qno)
{
	if (!client || event_type >= HIF_EVENT_MAX ||
	    qno >= HIF_CLIENT_QUEUES_MAX)
		return;

	if (!test_and_set_bit(qno, &client->queue_mask[event_type]))
		client->event_handler(client->priv, event_type, qno);
}

/*This function releases Rx queue descriptors memory and pre-filled buffers
 *
 * @param[in] client	hif_client context
 */
static void
hif_lib_client_release_rx_buffers(struct hif_client_s *client)
{
	struct rte_mempool *pool;
	struct rte_pktmbuf_pool_private *mb_priv;
	struct rx_queue_desc *desc;
	unsigned int qno, ii;
	void *buf;

	pool = client->pfe->hif.shm->pool;
	mb_priv = rte_mempool_get_priv(pool);
	for (qno = 0; qno < client->rx_qn; qno++) {
		desc = client->rx_q[qno].base;

		for (ii = 0; ii < client->rx_q[qno].size; ii++) {
			buf = (void *)desc->data;
			if (buf) {
			/* Data pointer to mbuf pointer calculation:
			 * "Data - User private data - headroom - mbufsize"
			 * Actual data pointer given to HIF BDs was
			 * "mbuf->data_offset - PFE_PKT_HEADER_SZ"
			 */
				buf = buf + PFE_PKT_HEADER_SZ
					- sizeof(struct rte_mbuf)
					- RTE_PKTMBUF_HEADROOM
					- mb_priv->mbuf_priv_size;
				rte_pktmbuf_free((struct rte_mbuf *)buf);
				desc->ctrl = 0;
			}
			desc++;
		}
	}
	rte_free(client->rx_qbase);
}

/*This function allocates memory for the rxq descriptors and pre-fill rx queues
 * with buffers.
 * @param[in] client	client context
 * @param[in] q_size	size of the rxQ, all queues are of same size
 */
static int
hif_lib_client_init_rx_buffers(struct hif_client_s *client,
					  int q_size)
{
	struct rx_queue_desc *desc;
	struct hif_client_rx_queue *queue;
	unsigned int ii, qno;

	/*Allocate memory for the client queues */
	client->rx_qbase = rte_malloc(NULL, client->rx_qn * q_size *
			sizeof(struct rx_queue_desc), RTE_CACHE_LINE_SIZE);
	if (!client->rx_qbase)
		goto err;

	for (qno = 0; qno < client->rx_qn; qno++) {
		queue = &client->rx_q[qno];

		queue->base = client->rx_qbase + qno * q_size * sizeof(struct
				rx_queue_desc);
		queue->size = q_size;
		queue->read_idx = 0;
		queue->write_idx = 0;
		queue->queue_id = 0;
		queue->port_id = client->port_id;
		queue->priv = client->priv;
		PFE_PMD_DEBUG("rx queue: %d, base: %p, size: %d\n", qno,
			      queue->base, queue->size);
	}

	for (qno = 0; qno < client->rx_qn; qno++) {
		queue = &client->rx_q[qno];
		desc = queue->base;

		for (ii = 0; ii < queue->size; ii++) {
			desc->ctrl = CL_DESC_OWN;
			desc++;
		}
	}

	return 0;

err:
	return 1;
}


static void
hif_lib_client_cleanup_tx_queue(struct hif_client_tx_queue *queue)
{
	/*
	 * Check if there are any pending packets. Client must flush the tx
	 * queues before unregistering, by calling by calling
	 * hif_lib_tx_get_next_complete()
	 *
	 * Hif no longer calls since we are no longer registered
	 */
	if (queue->tx_pending)
		PFE_PMD_ERR("pending transmit packet");
}

static void
hif_lib_client_release_tx_buffers(struct hif_client_s *client)
{
	unsigned int qno;

	for (qno = 0; qno < client->tx_qn; qno++)
		hif_lib_client_cleanup_tx_queue(&client->tx_q[qno]);

	rte_free(client->tx_qbase);
}

static int
hif_lib_client_init_tx_buffers(struct hif_client_s *client, int
						q_size)
{
	struct hif_client_tx_queue *queue;
	unsigned int qno;

	client->tx_qbase = rte_malloc(NULL, client->tx_qn * q_size *
			sizeof(struct tx_queue_desc), RTE_CACHE_LINE_SIZE);
	if (!client->tx_qbase)
		return 1;

	for (qno = 0; qno < client->tx_qn; qno++) {
		queue = &client->tx_q[qno];

		queue->base = client->tx_qbase + qno * q_size * sizeof(struct
				tx_queue_desc);
		queue->size = q_size;
		queue->read_idx = 0;
		queue->write_idx = 0;
		queue->tx_pending = 0;
		queue->nocpy_flag = 0;
		queue->prev_tmu_tx_pkts = 0;
		queue->done_tmu_tx_pkts = 0;
		queue->priv = client->priv;
		queue->queue_id = 0;
		queue->port_id = client->port_id;

		PFE_PMD_DEBUG("tx queue: %d, base: %p, size: %d", qno,
			 queue->base, queue->size);
	}

	return 0;
}

static int
hif_lib_event_dummy(__rte_unused void *priv,
		__rte_unused int event_type, __rte_unused int qno)
{
	return 0;
}

int
hif_lib_client_register(struct hif_client_s *client)
{
	struct hif_shm *hif_shm;
	struct hif_client_shm *client_shm;
	int err, i;

	PMD_INIT_FUNC_TRACE();

	/*Allocate memory before spin_lock*/
	if (hif_lib_client_init_rx_buffers(client, client->rx_qsize)) {
		err = -ENOMEM;
		goto err_rx;
	}

	if (hif_lib_client_init_tx_buffers(client, client->tx_qsize)) {
		err = -ENOMEM;
		goto err_tx;
	}

	rte_spinlock_lock(&client->pfe->hif.lock);
	if (!(client->pfe) || client->id >= HIF_CLIENTS_MAX ||
	    client->pfe->hif_client[client->id]) {
		err = -EINVAL;
		goto err;
	}

	hif_shm = client->pfe->hif.shm;

	if (!client->event_handler)
		client->event_handler = hif_lib_event_dummy;

	/*Initialize client specific shared memory */
	client_shm = (struct hif_client_shm *)&hif_shm->client[client->id];
	client_shm->rx_qbase = (unsigned long)client->rx_qbase;
	client_shm->rx_qsize = client->rx_qsize;
	client_shm->tx_qbase = (unsigned long)client->tx_qbase;
	client_shm->tx_qsize = client->tx_qsize;
	client_shm->ctrl = (client->tx_qn << CLIENT_CTRL_TX_Q_CNT_OFST) |
				(client->rx_qn << CLIENT_CTRL_RX_Q_CNT_OFST);

	for (i = 0; i < HIF_EVENT_MAX; i++) {
		client->queue_mask[i] = 0;  /*
					     * By default all events are
					     * unmasked
					     */
	}

	/*Indicate to HIF driver*/
	hif_lib_indicate_hif(&client->pfe->hif, REQUEST_CL_REGISTER,
			client->id, 0);

	PFE_PMD_DEBUG("client: %p, client_id: %d, tx_qsize: %d, rx_qsize: %d",
		      client, client->id, client->tx_qsize, client->rx_qsize);

	client->cpu_id = -1;

	client->pfe->hif_client[client->id] = client;
	rte_spinlock_unlock(&client->pfe->hif.lock);

	return 0;

err:
	rte_spinlock_unlock(&client->pfe->hif.lock);
	hif_lib_client_release_tx_buffers(client);

err_tx:
	hif_lib_client_release_rx_buffers(client);

err_rx:
	return err;
}

int
hif_lib_client_unregister(struct hif_client_s *client)
{
	struct pfe *pfe = client->pfe;
	u32 client_id = client->id;

	PFE_PMD_INFO("client: %p, client_id: %d, txQ_depth: %d, rxQ_depth: %d",
		     client, client->id, client->tx_qsize, client->rx_qsize);

	rte_spinlock_lock(&pfe->hif.lock);
	hif_lib_indicate_hif(&pfe->hif, REQUEST_CL_UNREGISTER, client->id, 0);

	hif_lib_client_release_tx_buffers(client);
	hif_lib_client_release_rx_buffers(client);
	pfe->hif_client[client_id] = NULL;
	rte_spinlock_unlock(&pfe->hif.lock);

	return 0;
}

int
hif_lib_event_handler_start(struct hif_client_s *client, int event,
				int qno)
{
	struct hif_client_rx_queue *queue = &client->rx_q[qno];
	struct rx_queue_desc *desc = queue->base + queue->read_idx;

	if (event >= HIF_EVENT_MAX || qno >= HIF_CLIENT_QUEUES_MAX) {
		PFE_PMD_WARN("Unsupported event : %d  queue number : %d",
				event, qno);
		return -1;
	}

	test_and_clear_bit(qno, &client->queue_mask[event]);

	switch (event) {
	case EVENT_RX_PKT_IND:
		if (!(desc->ctrl & CL_DESC_OWN))
			hif_lib_indicate_client(client,
						EVENT_RX_PKT_IND, qno);
		break;

	case EVENT_HIGH_RX_WM:
	case EVENT_TXDONE_IND:
	default:
		break;
	}

	return 0;
}

#ifdef RTE_LIBRTE_PFE_SW_PARSE
static inline void
pfe_sw_parse_pkt(struct rte_mbuf *mbuf)
{
	struct rte_net_hdr_lens hdr_lens;

	mbuf->packet_type = rte_net_get_ptype(mbuf, &hdr_lens,
			RTE_PTYPE_L2_MASK | RTE_PTYPE_L3_MASK
			| RTE_PTYPE_L4_MASK);
	mbuf->l2_len = hdr_lens.l2_len;
	mbuf->l3_len = hdr_lens.l3_len;
}
#endif

/*
 * This function gets one packet from the specified client queue
 * It also refill the rx buffer
 */
int
hif_lib_receive_pkt(struct hif_client_rx_queue *queue,
		struct rte_mempool *pool, struct rte_mbuf **rx_pkts,
		uint16_t nb_pkts)
{
	struct rx_queue_desc *desc;
	struct pfe_eth_priv_s *priv = queue->priv;
	struct rte_pktmbuf_pool_private *mb_priv;
	struct rte_mbuf *mbuf, *p_mbuf = NULL, *first_mbuf = NULL;
	struct rte_eth_stats *stats = &priv->stats;
	int i, wait_for_last = 0;
#ifndef RTE_LIBRTE_PFE_SW_PARSE
	struct pfe_parse *parse_res;
#endif

	for (i = 0; i < nb_pkts;) {
		do {
			desc = queue->base + queue->read_idx;
			if ((desc->ctrl & CL_DESC_OWN)) {
				stats->ipackets += i;
				return i;
			}

			mb_priv = rte_mempool_get_priv(pool);

			mbuf = desc->data + PFE_PKT_HEADER_SZ
				- sizeof(struct rte_mbuf)
				- RTE_PKTMBUF_HEADROOM
				- mb_priv->mbuf_priv_size;
			mbuf->next = NULL;
			if (desc->ctrl & CL_DESC_FIRST) {
				/* TODO size of priv data if present in
				 * descriptor
				 */
				u16 size = 0;
				mbuf->pkt_len = CL_DESC_BUF_LEN(desc->ctrl)
						- PFE_PKT_HEADER_SZ - size;
				mbuf->data_len = mbuf->pkt_len;
				mbuf->port = queue->port_id;
#ifdef RTE_LIBRTE_PFE_SW_PARSE
				pfe_sw_parse_pkt(mbuf);
#else
				parse_res = (struct pfe_parse *)(desc->data +
					    PFE_HIF_SIZE);
				mbuf->packet_type = parse_res->packet_type;
#endif
				mbuf->nb_segs = 1;
				first_mbuf = mbuf;
				rx_pkts[i++] = first_mbuf;
			} else {
				mbuf->data_len = CL_DESC_BUF_LEN(desc->ctrl);
				mbuf->data_off = mbuf->data_off -
						 PFE_PKT_HEADER_SZ;
				first_mbuf->pkt_len += mbuf->data_len;
				first_mbuf->nb_segs++;
				p_mbuf->next = mbuf;
			}
			stats->ibytes += mbuf->data_len;
			p_mbuf = mbuf;

			if (desc->ctrl & CL_DESC_LAST)
				wait_for_last = 0;
			else
				wait_for_last = 1;
			/*
			 * Needed so we don't free a buffer/page
			 * twice on module_exit
			 */
			desc->data = NULL;

			/*
			 * Ensure everything else is written to DDR before
			 * writing bd->ctrl
			 */
			rte_wmb();

			desc->ctrl = CL_DESC_OWN;
			queue->read_idx = (queue->read_idx + 1) &
					  (queue->size - 1);
		} while (wait_for_last);
	}
	stats->ipackets += i;
	return i;
}

static inline void
hif_hdr_write(struct hif_hdr *pkt_hdr, unsigned int
	      client_id, unsigned int qno,
	      u32 client_ctrl)
{
	/* Optimize the write since the destination may be non-cacheable */
	if (!((unsigned long)pkt_hdr & 0x3)) {
		((u32 *)pkt_hdr)[0] = (client_ctrl << 16) | (qno << 8) |
					client_id;
	} else {
		((u16 *)pkt_hdr)[0] = (qno << 8) | (client_id & 0xFF);
		((u16 *)pkt_hdr)[1] = (client_ctrl & 0xFFFF);
	}
}

/*This function puts the given packet in the specific client queue */
void
hif_lib_xmit_pkt(struct hif_client_s *client, unsigned int qno,
		 void *data, void *data1, unsigned int len,
		 u32 client_ctrl, unsigned int flags, void *client_data)
{
	struct hif_client_tx_queue *queue = &client->tx_q[qno];
	struct tx_queue_desc *desc = queue->base + queue->write_idx;

	/* First buffer */
	if (flags & HIF_FIRST_BUFFER) {
		data1 -= PFE_HIF_SIZE;
		data -= PFE_HIF_SIZE;
		len += PFE_HIF_SIZE;

		hif_hdr_write(data1, client->id, qno, client_ctrl);
	}

	desc->data = client_data;
	desc->ctrl = CL_DESC_OWN | CL_DESC_FLAGS(flags);

	hif_xmit_pkt(&client->pfe->hif, client->id, qno, data, len, flags);

	queue->write_idx = (queue->write_idx + 1) & (queue->size - 1);

	queue->tx_pending++;
}

void *
hif_lib_tx_get_next_complete(struct hif_client_s *client, int qno,
				   unsigned int *flags, __rte_unused  int count)
{
	struct hif_client_tx_queue *queue = &client->tx_q[qno];
	struct tx_queue_desc *desc = queue->base + queue->read_idx;

	PFE_DP_LOG(DEBUG, "qno : %d rd_indx: %d pending:%d",
		   qno, queue->read_idx, queue->tx_pending);

	if (!queue->tx_pending)
		return NULL;

	if (queue->nocpy_flag && !queue->done_tmu_tx_pkts) {
		u32 tmu_tx_pkts = 0;

		if (queue->prev_tmu_tx_pkts > tmu_tx_pkts)
			queue->done_tmu_tx_pkts = UINT_MAX -
				queue->prev_tmu_tx_pkts + tmu_tx_pkts;
		else
			queue->done_tmu_tx_pkts = tmu_tx_pkts -
						queue->prev_tmu_tx_pkts;

		queue->prev_tmu_tx_pkts  = tmu_tx_pkts;

		if (!queue->done_tmu_tx_pkts)
			return NULL;
	}

	if (desc->ctrl & CL_DESC_OWN)
		return NULL;

	queue->read_idx = (queue->read_idx + 1) & (queue->size - 1);
	queue->tx_pending--;

	*flags = CL_DESC_GET_FLAGS(desc->ctrl);

	if (queue->done_tmu_tx_pkts && (*flags & HIF_LAST_BUFFER))
		queue->done_tmu_tx_pkts--;

	return desc->data;
}

int
pfe_hif_lib_init(struct pfe *pfe)
{
	PMD_INIT_FUNC_TRACE();

	emac_txq_cnt = EMAC_TXQ_CNT;
	pfe->hif.shm = &ghif_shm;

	return 0;
}

void
pfe_hif_lib_exit(__rte_unused struct pfe *pfe)
{
	PMD_INIT_FUNC_TRACE();
}
