// SPDX-License-Identifier: MIT
/*
 * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved.
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 */

#include <sys/ioctl.h>
#include "cdma_u_cmd.h"
#include "cdma_u_db.h"
#include "cdma_u_log.h"
#include "cdma_u_jfc.h"

static int cdma_u_check_jfc_cfg(struct dma_context *ctx, dma_jfc_cfg_t *cfg)
{
	struct cdma_device_cap *cap;

	if (ctx == NULL || ctx->dma_dev == NULL || cfg == NULL ||
	    cfg->jfce == NULL) {
		CDMA_LOG_ERR("jfc ctx or cfg is null\n");
		return -EINVAL;
	}

	cap = &ctx->dma_dev->attr.dev_cap;
	if (cfg->depth == 0 || cfg->depth > cap->max_jfc_depth) {
		CDMA_LOG_ERR("invalid jfc cfg depth = %u, cap depth = %u\n",
			      cfg->depth, cap->max_jfc_depth);
		return -EINVAL;
	}

	if (cfg->ceqn >= cap->ceq_cnt) {
		CDMA_LOG_ERR("invalid cfg ceqn = %u, cap ceq_cnt = %u\n",
			      cfg->ceqn, cap->ceq_cnt);
		return -EINVAL;
	}

	return 0;
}

static int cdma_u_alloc_jfc_buf(struct cdma_u_context *cdma_ctx,
				dma_jfc_cfg_t *cfg, struct cdma_u_jfc *jfc)
{
	uint32_t depth;
	int ret;

	depth = cfg->depth < CDMA_U_MIN_JFC_DEPTH ? CDMA_U_MIN_JFC_DEPTH :
			cfg->depth;
	ret = cdma_u_alloc_queue_buf(&jfc->cq, depth, cdma_ctx->cqe_size,
								 CDMA_HW_PAGE_SIZE, false);
	if (ret) {
		CDMA_LOG_ERR("alloc user jfc wqe buf failed, ret = %d\n", ret);
		return ret;
	}

	jfc->sw_db = (uint32_t *)cdma_u_alloc_sw_db(cdma_ctx);
	if (!jfc->sw_db) {
		CDMA_LOG_ERR("alloc user jfc sw db failed\n");
		cdma_u_free_queue_buf(&jfc->cq);
		return -ENOMEM;
	}

	return 0;
}

static void cdma_u_free_jfc_buf(struct cdma_u_context *cdma_ctx,
				struct cdma_u_jfc *cdma_jfc)
{
	cdma_u_free_sw_db(cdma_ctx, cdma_jfc->sw_db);
	cdma_u_free_queue_buf(&cdma_jfc->cq);
}

static inline void cdma_ack_event_init(pthread_mutex_t *mutex,
				       pthread_cond_t *cond,
				       uint32_t *events_acked)
{
	*events_acked = 0;
	(void)pthread_mutex_init(mutex, NULL);
	(void)pthread_cond_init(cond, NULL);
}

static void cdma_fill_jfc(dma_jfc_t *jfc, struct dma_context *ctx,
			  dma_jfc_cfg_t *cfg,
			  struct cdma_cmd_create_jfc_args *arg)
{
	jfc->jfc_id = arg->out.id;
	jfc->handle = arg->out.handle;
	jfc->jfc_cfg = *cfg;
	jfc->jfc_cfg.depth = arg->out.depth;
	jfc->dma_ctx = ctx;
	jfc->comp_events_acked = 0;
	cdma_ack_event_init(&jfc->event_mutex, &jfc->event_cond,
						&jfc->async_events_acked);
}

static int cdma_cmd_create_jfc(struct dma_context *ctx, struct cdma_u_jfc *jfc,
			       dma_jfc_cfg_t *cfg)
{
	struct cdma_cmd_create_jfc_args arg = { 0 };
	struct cdma_create_jfc_ucmd cmd = { 0 };
	struct cdma_ioctl_hdr hdr = { 0 };
	int ret;

	if (ctx->dma_dev->fd < 0) {
		CDMA_LOG_ERR("invalid parameter\n");
		return -EINVAL;
	}

	cmd.buf_addr = (uintptr_t)jfc->cq.qbuf;
	cmd.db_addr = (uintptr_t)jfc->sw_db;
	cmd.buf_len = jfc->cq.qbuf_size;
	cmd.tid = ctx->tid;
	cdma_u_set_udata(&arg.udata, &cmd, sizeof(cmd), NULL, 0);

	hdr.command = (uint32_t)CDMA_CMD_CREATE_JFC;
	hdr.args_len = (uint32_t)sizeof(arg);
	hdr.args_addr = (uint64_t)&arg;

	arg.in.depth = cfg->depth;
	arg.in.jfce_fd = cfg->jfce->fd;
	arg.in.jfce_id = cfg->jfce->id;
	arg.in.ceqn = cfg->ceqn;
	arg.in.queue_id = cfg->queue_id;

	ret = ioctl(ctx->dma_dev->fd, CDMA_SYNC, &hdr);
	if (ret != 0) {
		CDMA_LOG_ERR("ioctl in create jfc, ret = %d %d, cmd = %u\n", ret,
			     errno, hdr.command);
		return ret;
	}

	cdma_fill_jfc(&jfc->base, ctx, cfg, &arg);

	return 0;
}

static int cdma_cmd_delete_jfc(struct dma_jfc *jfc)
{
	struct cdma_cmd_delete_jfc_args arg = { 0 };
	struct cdma_ioctl_hdr hdr = { 0 };
	int ret;

	if (jfc->dma_ctx->dma_dev->fd < 0) {
		CDMA_LOG_ERR("invalid fd parameter\n");
		return -EINVAL;
	}

	hdr.command = (uint32_t)CDMA_CMD_DELETE_JFC;
	hdr.args_len = (uint32_t)sizeof(arg);
	hdr.args_addr = (uint64_t)&arg;
	arg.in.handle = jfc->handle;
	arg.in.jfcn = jfc->jfc_id;
	arg.in.queue_id = jfc->jfc_cfg.queue_id;

	ret = ioctl(jfc->dma_ctx->dma_dev->fd, CDMA_SYNC, &hdr);
	if (ret != 0) {
		CDMA_LOG_ERR("ioctl in cdma_delete_jfc, ret = %d %d, cmd = %u\n", ret,
			     errno, hdr.command);
		return ret;
	}

	return 0;
}

static inline void *cdma_u_get_buf_entry(struct cdma_u_jetty_queue *cq,
					 uint32_t n)
{
	uint32_t cqe_index = n & cq->baseblk_mask;
	uint32_t cqe_size = 1U << cq->baseblk_shift;

	return (char *)cq->qbuf + (cqe_index * cqe_size);
}

static struct cdma_u_jfc_cqe *cdma_u_get_next_cqe(struct cdma_u_jfc *jfc,
						  uint32_t n)
{
	struct cdma_u_jfc_cqe *cqe;
	uint32_t valid_owner;

	cqe = (struct cdma_u_jfc_cqe *)cdma_u_get_buf_entry(&jfc->cq, n);
	valid_owner = (jfc->cq.ci >> align_power2(jfc->cq.baseblk_cnt)) &
		       CDMA_JFC_DB_VALID_OWNER_M;
	if (!(cqe->owner ^ valid_owner))
		return NULL;

	return cqe;
}

static struct cdma_u_jetty_queue *cdma_u_update_jetty_idx(struct cdma_u_jfc_cqe *cqe)
{
	struct cdma_u_jetty_queue *queue;

	queue = (struct cdma_u_jetty_queue *)((uint64_t)cqe->user_data[1] <<
			CDMA_ADDR_SHIFT | cqe->user_data[0]);
	if (!queue)
		return NULL;

	if (!!cqe->fd)
		return queue;

	queue->ci += (cqe->entry_idx - queue->ci) & queue->baseblk_mask;

	return queue;
}

static enum jfc_poll_state cdma_get_cr_status(uint8_t src_status,
					      uint8_t substatus,
					      enum dma_cr_status *cr_status)
{
	struct cdma_cqe_status {
		bool is_valid;
		enum dma_cr_status cr_status;
	};

	static const struct cdma_cqe_status
		map[CDMA_CQE_STATUS_NUM][CDMA_CQE_SUB_STATUS_NUM] = {
		{{true, DMA_CR_SUCCESS},
		 {false, DMA_CR_SUCCESS},
		 {false, DMA_CR_SUCCESS},
		 {false, DMA_CR_SUCCESS},
		 {false, DMA_CR_SUCCESS}},
		{{true, DMA_CR_UNSUPPORTED_OPCODE_ERR},
		 {false, DMA_CR_SUCCESS},
		 {false, DMA_CR_SUCCESS},
		 {false, DMA_CR_SUCCESS},
		 {false, DMA_CR_SUCCESS}},
		{{false, DMA_CR_SUCCESS},
		 {true, DMA_CR_LOC_LEN_ERR},
		 {true, DMA_CR_LOC_ACCESS_ERR},
		 {true, DMA_CR_REM_RESP_LEN_ERR},
		 {true, DMA_CR_LOC_DATA_POISON}},
		{{false, DMA_CR_SUCCESS},
		 {true, DMA_CR_REM_UNSUPPORTED_REQ_ERR},
		 {true, DMA_CR_REM_ACCESS_ABORT_ERR},
		 {false, DMA_CR_SUCCESS},
		 {true, DMA_CR_REM_DATA_POISON}},
		{{true, DMA_CR_RNR_RETRY_CNT_EXC_ERR},
		 {false, DMA_CR_SUCCESS},
		 {false, DMA_CR_SUCCESS},
		 {false, DMA_CR_SUCCESS},
		 {false, DMA_CR_SUCCESS}},
		{{true, DMA_CR_ACK_TIMEOUT_ERR},
		 {false, DMA_CR_SUCCESS},
		 {false, DMA_CR_SUCCESS},
		 {false, DMA_CR_SUCCESS},
		 {false, DMA_CR_SUCCESS}},
		{{true, DMA_CR_WR_FLUSH_ERR},
		 {false, DMA_CR_SUCCESS},
		 {false, DMA_CR_SUCCESS},
		 {false, DMA_CR_SUCCESS},
		 {false, DMA_CR_SUCCESS}}};

	if ((src_status < CDMA_CQE_STATUS_NUM) &&
	    (substatus < CDMA_CQE_SUB_STATUS_NUM) &&
	    map[src_status][substatus].is_valid) {
		*cr_status = map[src_status][substatus].cr_status;
		return JFC_OK;
	}

	CDMA_LOG_ERR("cqe_status (%u) substatus (%u) is invalid\n", src_status,
		     substatus);

	return JFC_POLL_ERR;
}

static enum jfc_poll_state cdma_u_update_flush_cr(struct cdma_u_jetty_queue *queue,
						  struct cdma_u_jfc_cqe *cqe,
						  struct dma_cr *cr)
{
	uint8_t flush_done;

	if (cdma_get_cr_status(cqe->status, cqe->substatus, &cr->status))
		return JFC_POLL_ERR;

	flush_done = cqe->fd;
	if (!!flush_done) {
		cr->status = DMA_CR_WR_FLUSH_ERR_DONE;
		queue->flush_flag = true;
	} else {
		queue->ci++;
	}

	return JFC_OK;
}

static enum jfc_poll_state cdma_parse_cqe_for_jfc(struct cdma_u_jfc_cqe *cqe,
						  struct dma_cr *cr)
{
	struct cdma_u_jetty_queue *queue;

	queue = cdma_u_update_jetty_idx(cqe);
	if (!queue) {
		CDMA_LOG_ERR("update jetty idx failed\n");
		return JFC_POLL_ERR;
	}

	cr->flag.bs.s_r = cqe->s_r;
	cr->flag.bs.jetty = cqe->is_jetty;
	cr->completion_len = cqe->byte_cnt;
	cr->tpn = cqe->tpn;
	cr->local_id = (cqe->local_num_h << CDMA_SRC_IDX_SHIFT) | cqe->local_num_l;
	cr->remote_id = cqe->rmt_idx;
	cr->user_ctx = queue->wrid[queue->ci & queue->baseblk_mask];

	if (cqe->status) {
		CDMA_LOG_WARN("get sq %u cqe status abnormal, ci = %u,"
			      " pi = %u, status = %u, substatus = %u\n", queue->idx,
			      queue->ci, queue->pi, cqe->status, cqe->substatus);
	}

	if (cdma_u_update_flush_cr(queue, cqe, cr))
		return JFC_POLL_ERR;

	return JFC_OK;
}

static enum jfc_poll_state cdma_u_poll_one(struct cdma_u_jfc *cdma_jfc,
					   struct dma_cr *cr)
{
	struct cdma_u_jfc_cqe *cqe;
	enum dma_cr_status status;

	cqe = cdma_u_get_next_cqe(cdma_jfc, cdma_jfc->cq.ci);
	if (!cqe)
		return JFC_EMPTY;

	++cdma_jfc->cq.ci;

	cdma_from_device_barrier();

	if (cdma_parse_cqe_for_jfc(cqe, cr))
		return JFC_POLL_ERR;

	status = cr->status;
	if (status == DMA_CR_WR_FLUSH_ERR_DONE ||
	    status == DMA_CR_WR_SUSPEND_DONE) {
		CDMA_LOG_INFO("poll cr flush/suspend done, jfc_id = %u, status = %u\n",
			       cdma_jfc->base.jfc_id, status);
		return JFC_EMPTY;
	}

	return JFC_OK;
}

static int cdma_cmd_wait_jfc(int jfce_fd, uint32_t jfc_cnt, int time_out)
{
	struct cdma_cmd_jfce_wait_args arg = { 0 };
	int ret;

	arg.in.max_event_cnt = jfc_cnt;
	arg.in.time_out = time_out;

	ret = ioctl(jfce_fd, CDMA_CMD_WAIT_JFC, &arg);
	if (ret) {
		CDMA_LOG_ERR("wait jfc ioctl, ret = %d %d\n", ret, errno);
		return -EFAULT;
	}

	return (int)arg.out.event_cnt;
}

static inline void cdma_ack_comp_event(pthread_mutex_t *mutex,
				       pthread_cond_t *cond,
				       uint32_t *events_acked,
				       uint32_t nevent)
{
	(void)pthread_mutex_lock(mutex);
	*events_acked = *events_acked + nevent;
	(void)pthread_cond_signal(cond);
	(void)pthread_mutex_unlock(mutex);
}

dma_jfce_t *cdma_u_create_jfce(struct dma_context *ctx)
{
	dma_jfce_t *jfce;
	int ret;

	jfce = (dma_jfce_t *)calloc(1, sizeof(*jfce));
	if (!jfce) {
		CDMA_LOG_ERR("alloc jfce failed\n");
		return NULL;
	}

	jfce->dma_ctx = ctx;
	ret = cdma_cmd_create_jfce(ctx, jfce);
	if (ret) {
		CDMA_LOG_ERR("cmd create jfce failed, ret = %d\n", ret);
		free(jfce);
		return NULL;
	}

	return jfce;
}

dma_status cdma_u_delete_jfce(dma_jfce_t *jfce)
{
	if (jfce == NULL || jfce->fd < 0) {
		CDMA_LOG_ERR("invalid parameter\n");
		return DMA_STATUS_INVAL;
	}

	(void)close(jfce->fd);
	free(jfce);

	return DMA_STATUS_OK;
}

dma_jfc_t *cdma_u_create_jfc(struct dma_context *ctx, dma_jfc_cfg_t *cfg)
{
	struct cdma_u_context *cdma_ctx;
	struct cdma_u_jfc *cdma_jfc;
	int ret;

	ret = cdma_u_check_jfc_cfg(ctx, cfg);
	if (ret)
		return NULL;

	cdma_ctx = to_cdma_u_ctx(ctx);

	cdma_jfc = (struct cdma_u_jfc *)calloc(1, sizeof(*cdma_jfc));
	if (!cdma_jfc) {
		CDMA_LOG_ERR("alloc user cdma jfc memory failed\n");
		return NULL;
	}

	if (pthread_spin_init(&cdma_jfc->cq.lock, PTHREAD_PROCESS_PRIVATE)) {
		CDMA_LOG_ERR("init user cdma jfc spinlock failed\n");
		goto err_init_lock;
	}

	ret = cdma_u_alloc_jfc_buf(cdma_ctx, cfg, cdma_jfc);
	if (ret) {
		CDMA_LOG_ERR("alloc jfc buf failed, ret = %d\n", ret);
		goto err_alloc_jfc;
	}

	ret = cdma_cmd_create_jfc(ctx, cdma_jfc, cfg);
	if (ret) {
		CDMA_LOG_ERR("create jfc failed, ret = %d\n", ret);
		goto err_create_jfc;
	}

	cdma_jfc->cq.idx = cdma_jfc->base.jfc_id;
	cdma_jfc->arm_sn = 1;

	return &cdma_jfc->base;

err_create_jfc:
	cdma_u_free_jfc_buf(cdma_ctx, cdma_jfc);
err_alloc_jfc:
	(void)pthread_spin_destroy(&cdma_jfc->cq.lock);
err_init_lock:
	free(cdma_jfc);
	return NULL;
}

dma_status cdma_u_delete_jfc(dma_jfc_t *jfc)
{
	struct cdma_u_context *cdma_ctx;
	struct cdma_u_jfc *cdma_jfc;

	if (jfc == NULL || jfc->dma_ctx == NULL) {
		CDMA_LOG_ERR("invalid parameter\n");
		return DMA_STATUS_INVAL;
	}

	cdma_ctx = to_cdma_u_ctx(jfc->dma_ctx);
	cdma_jfc = to_cdma_u_jfc(jfc);

	if (cdma_cmd_delete_jfc(jfc))
		CDMA_LOG_WARN("delete jfc cmd failed\n");

	cdma_u_free_jfc_buf(cdma_ctx, cdma_jfc);

	(void)pthread_spin_destroy(&cdma_jfc->cq.lock);

	free(cdma_jfc);

	return DMA_STATUS_OK;
}

int cdma_u_poll_jfc(dma_jfc_t *jfc, uint32_t cr_cnt, struct dma_cr *cr)
{
	enum jfc_poll_state ret = JFC_OK;
	struct cdma_u_jfc *cdma_jfc;
	uint32_t npolled;

	if (jfc == NULL) {
		CDMA_LOG_ERR("invalid parameter\n");
		return -EINVAL;
	}

	cdma_jfc = to_cdma_u_jfc(jfc);
	(void)pthread_spin_lock(&cdma_jfc->cq.lock);

	for (npolled = 0; npolled < cr_cnt; ++npolled) {
		ret = cdma_u_poll_one(cdma_jfc, cr + npolled);
		if (ret != JFC_OK)
			break;
	}

	if (npolled)
		*cdma_jfc->sw_db = cdma_jfc->cq.ci & CDMA_JFC_DB_CI_IDX_M;

	(void)pthread_spin_unlock(&cdma_jfc->cq.lock);

	return ret == JFC_POLL_ERR ? -CDMA_INTER_ERR : (int)npolled;
}

int cdma_u_wait_jfc(dma_jfce_t *jfce, uint32_t jfc_cnt, int time_out)
{
	if (jfce == NULL || jfce->fd < 0 || jfc_cnt == 0) {
		CDMA_LOG_ERR("invalid parameter\n");
		return -1;
	}

	return cdma_cmd_wait_jfc(jfce->fd, jfc_cnt, time_out);
}

void cdma_u_ack_jfc(dma_jfc_t *jfc, uint32_t events)
{
	struct cdma_u_jfc *cdma_jfc;

	if (!jfc) {
		CDMA_LOG_ERR("invalid parameter\n");
		return;
	}

	cdma_jfc = to_cdma_u_jfc(jfc);

	cdma_jfc->arm_sn++;
	cdma_ack_comp_event(&jfc->event_mutex, &jfc->event_cond,
			    &jfc->comp_events_acked, events);
}

dma_status cdma_u_rearm_jfc(dma_jfc_t *jfc, bool solicited_only)
{
	struct cdma_u_context *cdma_ctx;
	struct cdma_u_jfc *cdma_jfc;
	struct cdma_jfc_db db;

	if (jfc == NULL || jfc->dma_ctx == NULL) {
		CDMA_LOG_ERR("invalid parameter\n");
		return DMA_STATUS_INVAL;
	}

	cdma_jfc = to_cdma_u_jfc(jfc);
	cdma_ctx = to_cdma_u_ctx(jfc->dma_ctx);

	db.ci = cdma_jfc->cq.ci & CDMA_JFC_DB_CI_IDX_M;
	db.notify = solicited_only;
	db.arm_sn = cdma_jfc->arm_sn;
	db.type = CDMA_CQ_ARM_DB;
	db.jfcn = cdma_jfc->cq.idx;

	cdma_u_write64((uint64_t *)((char volatile *)cdma_ctx->db.addr + CDMA_JFC_HW_DB_OFFSET),
				   (uint64_t *)&db);

	return DMA_STATUS_OK;
}

void cdma_u_clean_jfc(dma_jfc_t *jfc, uint32_t jetty_id)
{
	struct cdma_u_jetty_queue *cq;
	struct cdma_u_jfc *cdma_jfc;
	struct cdma_u_jfc_cqe *dest;
	struct cdma_u_jfc_cqe *cqe;
	uint32_t nfreed = 0;
	uint32_t local_id;
	uint8_t owner_bit;
	uint32_t pi;

	if (jfc == NULL) {
		CDMA_LOG_ERR("invalid parameter\n");
		return;
	}

	cdma_jfc = to_cdma_u_jfc(jfc);
	cq = &cdma_jfc->cq;
	(void)pthread_spin_lock(&cq->lock);

	for (pi = cq->ci; cdma_u_get_next_cqe(cdma_jfc, pi) != NULL; ++pi) {
		if (pi > cq->ci + cq->baseblk_cnt)
			break;
	}

	while (((int) --pi - (int)cq->ci) >= 0) {
		cqe = (struct cdma_u_jfc_cqe *)cdma_u_get_buf_entry(cq, pi);
		cdma_from_device_barrier();
		local_id = cqe->local_num_h << CDMA_SRC_IDX_SHIFT | cqe->local_num_l;
		if (local_id == jetty_id) {
			++nfreed;
		} else if (!!nfreed) {
			dest = (struct cdma_u_jfc_cqe *)cdma_u_get_buf_entry(cq, pi + nfreed);
			cdma_from_device_barrier();
			owner_bit = dest->owner;
			*dest = *cqe;
			dest->owner = owner_bit;
		}
	}

	if (!!nfreed) {
		cq->ci += nfreed;
		cdma_to_device_barrier();
		*cdma_jfc->sw_db = cq->ci & (uint32_t)CDMA_JFC_DB_CI_IDX_M;
	}

	(void)pthread_spin_unlock(&cq->lock);
}