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

#include "roc_api.h"
#include "roc_priv.h"

int
roc_nix_tm_sq_aura_fc(struct roc_nix_sq *sq, bool enable)
{
	struct npa_cn20k_aq_enq_req *req_cn20k;
	struct npa_aq_enq_req *req;
	struct npa_aq_enq_rsp *rsp;
	uint64_t aura_handle;
	struct npa_lf *lf;
	struct mbox *mbox;
	int rc = -ENOSPC;

	plt_tm_dbg("Setting SQ %u SQB aura FC to %s", sq->qid,
		   enable ? "enable" : "disable");

	lf = idev_npa_obj_get();
	if (!lf)
		return NPA_ERR_DEVICE_NOT_BOUNDED;

	mbox = mbox_get(lf->mbox);
	/* Set/clear sqb aura fc_ena */
	aura_handle = sq->aura_handle;
	if (roc_model_is_cn20k()) {
		req_cn20k = mbox_alloc_msg_npa_cn20k_aq_enq(mbox);
		req = (struct npa_aq_enq_req *)req_cn20k;
	} else {
		req = mbox_alloc_msg_npa_aq_enq(mbox);
	}
	if (req == NULL)
		goto exit;

	req->aura_id = roc_npa_aura_handle_to_aura(aura_handle);
	req->ctype = NPA_AQ_CTYPE_AURA;
	req->op = NPA_AQ_INSTOP_WRITE;
	/* Below is not needed for aura writes but AF driver needs it */
	/* AF will translate to associated poolctx */
	req->aura.pool_addr = req->aura_id;

	req->aura.fc_ena = enable;
	req->aura_mask.fc_ena = 1;
	if (roc_model_is_cn9k() || roc_errata_npa_has_no_fc_stype_ststp()) {
		req->aura.fc_stype = 0x0;      /* STF */
		req->aura_mask.fc_stype = 0x0; /* STF */
	} else {
		req->aura.fc_stype = 0x3;      /* STSTP */
		req->aura_mask.fc_stype = 0x3; /* STSTP */
	}

	rc = mbox_process(mbox);
	if (rc)
		goto exit;

	/* Read back npa aura ctx */
	if (enable) {
		if (roc_model_is_cn20k()) {
			req_cn20k = mbox_alloc_msg_npa_cn20k_aq_enq(mbox);
			req = (struct npa_aq_enq_req *)req_cn20k;
		} else {
			req = mbox_alloc_msg_npa_aq_enq(mbox);
		}
		if (req == NULL) {
			rc = -ENOSPC;
			goto exit;
		}

		req->aura_id = roc_npa_aura_handle_to_aura(aura_handle);
		req->ctype = NPA_AQ_CTYPE_AURA;
		req->op = NPA_AQ_INSTOP_READ;

		rc = mbox_process_msg(mbox, (void *)&rsp);
		if (rc)
			goto exit;

		/* Init when enabled as there might be no triggers */
		*(volatile uint64_t *)sq->fc = rsp->aura.count;
	} else {
		*(volatile uint64_t *)sq->fc = sq->aura_sqb_bufs;
	}
	/* Sync write barrier */
	plt_wmb();
	rc = 0;
exit:
	mbox_put(mbox);
	return rc;
}

int
roc_nix_tm_free_resources(struct roc_nix *roc_nix, bool hw_only)
{
	struct nix *nix = roc_nix_to_nix_priv(roc_nix);

	if (nix->tm_flags & NIX_TM_HIERARCHY_ENA)
		return -EBUSY;

	return nix_tm_free_resources(roc_nix, BIT(ROC_NIX_TM_USER), hw_only);
}

static int
nix_tm_adjust_shaper_pps_rate(struct nix_tm_shaper_profile *profile)
{
	uint64_t min_rate = profile->commit.rate;

	if (!profile->pkt_mode)
		return 0;

	profile->pkt_mode_adj = 1;

	if (profile->commit.rate &&
	    (profile->commit.rate < NIX_TM_MIN_SHAPER_PPS_RATE ||
	     profile->commit.rate > NIX_TM_MAX_SHAPER_PPS_RATE))
		return NIX_ERR_TM_INVALID_COMMIT_RATE;

	if (profile->peak.rate &&
	    (profile->peak.rate < NIX_TM_MIN_SHAPER_PPS_RATE ||
	     profile->peak.rate > NIX_TM_MAX_SHAPER_PPS_RATE))
		return NIX_ERR_TM_INVALID_PEAK_RATE;

	if (profile->peak.rate && min_rate > profile->peak.rate)
		min_rate = profile->peak.rate;

	/* Each packet accumulate single count, whereas HW
	 * considers each unit as Byte, so we need convert
	 * user pps to bps
	 */
	profile->commit.rate = profile->commit.rate * 8;
	profile->peak.rate = profile->peak.rate * 8;
	min_rate = min_rate * 8;

	if (min_rate && (min_rate < NIX_TM_MIN_SHAPER_RATE)) {
		int adjust = NIX_TM_MIN_SHAPER_RATE / min_rate;

		if (adjust > NIX_TM_LENGTH_ADJUST_MAX)
			return NIX_ERR_TM_SHAPER_PKT_LEN_ADJUST;

		profile->pkt_mode_adj += adjust;
		profile->commit.rate += (adjust * profile->commit.rate);
		profile->peak.rate += (adjust * profile->peak.rate);
		/* Number of tokens freed after scheduling was proportional
		 * to adjust value
		 */
		profile->commit.size *= adjust;
		profile->peak.size *= adjust;
	}

	return 0;
}

static int
nix_tm_shaper_profile_add(struct roc_nix *roc_nix,
			  struct nix_tm_shaper_profile *profile, int skip_ins)
{
	struct nix *nix = roc_nix_to_nix_priv(roc_nix);
	uint64_t commit_rate, commit_sz;
	uint64_t min_burst, max_burst;
	uint64_t peak_rate, peak_sz;
	uint32_t id;
	int rc;

	id = profile->id;
	rc = nix_tm_adjust_shaper_pps_rate(profile);
	if (rc)
		return rc;

	commit_rate = profile->commit.rate;
	commit_sz = profile->commit.size;
	peak_rate = profile->peak.rate;
	peak_sz = profile->peak.size;

	min_burst = NIX_TM_MIN_SHAPER_BURST;
	max_burst = roc_nix_tm_max_shaper_burst_get();

	if (nix_tm_shaper_profile_search(nix, id) && !skip_ins)
		return NIX_ERR_TM_SHAPER_PROFILE_EXISTS;

	if (profile->pkt_len_adj < NIX_TM_LENGTH_ADJUST_MIN ||
	    profile->pkt_len_adj > NIX_TM_LENGTH_ADJUST_MAX)
		return NIX_ERR_TM_SHAPER_PKT_LEN_ADJUST;

	/* We cannot support both pkt length adjust and pkt mode */
	if (profile->pkt_mode && profile->pkt_len_adj)
		return NIX_ERR_TM_SHAPER_PKT_LEN_ADJUST;

	/* commit rate and burst size can be enabled/disabled */
	if (commit_rate || commit_sz) {
		if (commit_sz < min_burst || commit_sz > max_burst)
			return NIX_ERR_TM_INVALID_COMMIT_SZ;
		else if (!nix_tm_shaper_rate_conv(commit_rate, NULL, NULL, NULL,
						  profile->accuracy))
			return NIX_ERR_TM_INVALID_COMMIT_RATE;
	}

	/* Peak rate and burst size can be enabled/disabled */
	if (peak_sz || peak_rate) {
		if (peak_sz < min_burst || peak_sz > max_burst)
			return NIX_ERR_TM_INVALID_PEAK_SZ;
		else if (!nix_tm_shaper_rate_conv(peak_rate, NULL, NULL, NULL,
						  profile->accuracy))
			return NIX_ERR_TM_INVALID_PEAK_RATE;
	}

	/* If PIR and CIR are requested, PIR should always be larger than CIR */
	if (peak_rate && commit_rate && (commit_rate > peak_rate))
		return NIX_ERR_TM_INVALID_PEAK_RATE;

	if (!skip_ins)
		TAILQ_INSERT_TAIL(&nix->shaper_profile_list, profile, shaper);

	plt_tm_dbg("Added TM shaper profile %u, "
		   " pir %" PRIu64 " , pbs %" PRIu64 ", cir %" PRIu64
		   ", cbs %" PRIu64 " , adj %u, pkt_mode %u",
		   id, profile->peak.rate, profile->peak.size,
		   profile->commit.rate, profile->commit.size,
		   profile->pkt_len_adj, profile->pkt_mode);

	/* Always use PIR for single rate shaping */
	if (!peak_rate && commit_rate) {
		profile->peak.rate = profile->commit.rate;
		profile->peak.size = profile->commit.size;
		profile->commit.rate = 0;
		profile->commit.size = 0;
	}

	/* update min rate */
	nix->tm_rate_min = nix_tm_shaper_profile_rate_min(nix);
	return 0;
}

int
roc_nix_tm_shaper_profile_add(struct roc_nix *roc_nix,
			      struct roc_nix_tm_shaper_profile *roc_profile)
{
	struct nix_tm_shaper_profile *profile;

	profile = (struct nix_tm_shaper_profile *)roc_profile->reserved;

	profile->ref_cnt = 0;
	profile->id = roc_profile->id;
	profile->commit.rate = roc_profile->commit_rate;
	profile->peak.rate = roc_profile->peak_rate;
	profile->commit.size = roc_profile->commit_sz;
	profile->peak.size = roc_profile->peak_sz;
	profile->pkt_len_adj = roc_profile->pkt_len_adj;
	profile->pkt_mode = roc_profile->pkt_mode;
	profile->free_fn = roc_profile->free_fn;
	profile->accuracy = roc_profile->accuracy;

	return nix_tm_shaper_profile_add(roc_nix, profile, 0);
}

int
roc_nix_tm_shaper_profile_update(struct roc_nix *roc_nix,
				 struct roc_nix_tm_shaper_profile *roc_profile)
{
	struct nix_tm_shaper_profile *profile;

	profile = (struct nix_tm_shaper_profile *)roc_profile->reserved;

	profile->commit.rate = roc_profile->commit_rate;
	profile->peak.rate = roc_profile->peak_rate;
	profile->commit.size = roc_profile->commit_sz;
	profile->peak.size = roc_profile->peak_sz;
	profile->pkt_len_adj = roc_profile->pkt_len_adj;
	profile->accuracy = roc_profile->accuracy;

	return nix_tm_shaper_profile_add(roc_nix, profile, 1);
}

int
roc_nix_tm_shaper_profile_delete(struct roc_nix *roc_nix, uint32_t id)
{
	struct nix *nix = roc_nix_to_nix_priv(roc_nix);
	struct nix_tm_shaper_profile *profile;

	profile = nix_tm_shaper_profile_search(nix, id);
	if (!profile)
		return NIX_ERR_TM_INVALID_SHAPER_PROFILE;

	if (profile->ref_cnt)
		return NIX_ERR_TM_SHAPER_PROFILE_IN_USE;

	plt_tm_dbg("Removing TM shaper profile %u", id);
	TAILQ_REMOVE(&nix->shaper_profile_list, profile, shaper);
	nix_tm_shaper_profile_free(profile);

	/* update min rate */
	nix->tm_rate_min = nix_tm_shaper_profile_rate_min(nix);
	return 0;
}

int
roc_nix_tm_node_add(struct roc_nix *roc_nix, struct roc_nix_tm_node *roc_node)
{
	struct nix_tm_node *node;

	node = (struct nix_tm_node *)&roc_node->reserved;
	node->id = roc_node->id;
	node->priority = roc_node->priority;
	node->weight = roc_node->weight;
	node->lvl = roc_node->lvl;
	node->parent_id = roc_node->parent_id;
	node->shaper_profile_id = roc_node->shaper_profile_id;
	node->pkt_mode = roc_node->pkt_mode;
	node->pkt_mode_set = roc_node->pkt_mode_set;
	node->free_fn = roc_node->free_fn;
	node->tree = ROC_NIX_TM_USER;
	node->rel_chan = NIX_TM_CHAN_INVALID;

	return nix_tm_node_add(roc_nix, node);
}

int
roc_nix_tm_node_pkt_mode_update(struct roc_nix *roc_nix, uint32_t node_id,
				bool pkt_mode)
{
	struct nix *nix = roc_nix_to_nix_priv(roc_nix);
	struct nix_tm_node *node, *child;
	struct nix_tm_node_list *list;
	int num_children = 0;

	node = nix_tm_node_search(nix, node_id, ROC_NIX_TM_USER);
	if (!node)
		return NIX_ERR_TM_INVALID_NODE;

	if (node->pkt_mode == pkt_mode) {
		node->pkt_mode_set = true;
		return 0;
	}

	/* Check for any existing children, if there are any,
	 * then we cannot update the pkt mode as children's quantum
	 * are already taken in.
	 */
	list = nix_tm_node_list(nix, ROC_NIX_TM_USER);
	TAILQ_FOREACH(child, list, node) {
		if (child->parent == node)
			num_children++;
	}

	/* Cannot update mode if it has children or tree is enabled */
	if ((nix->tm_flags & NIX_TM_HIERARCHY_ENA) && num_children)
		return -EBUSY;

	if (node->pkt_mode_set && num_children)
		return NIX_ERR_TM_PKT_MODE_MISMATCH;

	node->pkt_mode = pkt_mode;
	node->pkt_mode_set = true;

	return 0;
}

int
roc_nix_tm_node_name_get(struct roc_nix *roc_nix, uint32_t node_id, char *buf,
			 size_t buflen)
{
	struct nix *nix = roc_nix_to_nix_priv(roc_nix);
	struct nix_tm_node *node;

	node = nix_tm_node_search(nix, node_id, ROC_NIX_TM_USER);
	if (!node) {
		plt_strlcpy(buf, "???", buflen);
		return NIX_ERR_TM_INVALID_NODE;
	}

	if (node->hw_lvl == NIX_TXSCH_LVL_CNT)
		snprintf(buf, buflen, "SQ_%d", node->id);
	else
		snprintf(buf, buflen, "%s_%d", nix_tm_hwlvl2str(node->hw_lvl),
			 node->hw_id);
	return 0;
}

int
roc_nix_tm_node_delete(struct roc_nix *roc_nix, uint32_t node_id, bool free)
{
	return nix_tm_node_delete(roc_nix, node_id, ROC_NIX_TM_USER, free);
}

int
roc_nix_smq_flush(struct roc_nix *roc_nix)
{
	struct nix *nix = roc_nix_to_nix_priv(roc_nix);
	struct nix_tm_node_list *list;
	enum roc_nix_tm_tree tree;
	struct nix_tm_node *node;
	int rc = 0;

	if (!(nix->tm_flags & NIX_TM_HIERARCHY_ENA))
		return 0;

	tree = nix->tm_tree;
	list = nix_tm_node_list(nix, tree);

	/* XOFF & Flush all SMQ's. HRM mandates
	 * all SQ's empty before SMQ flush is issued.
	 */
	TAILQ_FOREACH(node, list, node) {
		if (node->hw_lvl != NIX_TXSCH_LVL_SMQ)
			continue;
		if (!(node->flags & NIX_TM_NODE_HWRES))
			continue;

		rc = nix_tm_smq_xoff(nix, node, true);
		if (rc) {
			plt_err("Failed to enable smq %u, rc=%d", node->hw_id,
				rc);
			goto exit;
		}
	}

	/* XON all SMQ's */
	TAILQ_FOREACH(node, list, node) {
		if (node->hw_lvl != NIX_TXSCH_LVL_SMQ)
			continue;
		if (!(node->flags & NIX_TM_NODE_HWRES))
			continue;

		rc = nix_tm_smq_xoff(nix, node, false);
		if (rc) {
			plt_err("Failed to enable smq %u, rc=%d", node->hw_id,
				rc);
			goto exit;
		}
	}
exit:
	return rc;
}

int
roc_nix_tm_hierarchy_disable(struct roc_nix *roc_nix)
{
	struct nix *nix = roc_nix_to_nix_priv(roc_nix);
	uint16_t sqb_cnt, head_off, tail_off;
	uint16_t sq_cnt = nix->nb_tx_queues;
	struct mbox *mbox = (&nix->dev)->mbox;
	struct nix_tm_node_list *list;
	enum roc_nix_tm_tree tree;
	struct nix_tm_node *node;
	struct roc_nix_sq *sq;
	uint64_t wdata, val;
	uintptr_t regaddr;
	int rc = -1, i;

	if (!(nix->tm_flags & NIX_TM_HIERARCHY_ENA))
		return 0;

	plt_tm_dbg("Disabling hierarchy on %s", nix->pci_dev->name);

	tree = nix->tm_tree;
	list = nix_tm_node_list(nix, tree);

	/* Enable CGX RXTX to drain pkts */
	if (!roc_nix->io_enabled) {
		/* Though it enables both RX MCAM Entries and CGX Link
		 * we assume all the rx queues are stopped way back.
		 */
		mbox_alloc_msg_nix_lf_start_rx(mbox_get(mbox));
		rc = mbox_process(mbox);
		if (rc) {
			mbox_put(mbox);
			plt_err("cgx start failed, rc=%d", rc);
			return rc;
		}
		mbox_put(mbox);
	}

	/* XON all SMQ's */
	TAILQ_FOREACH(node, list, node) {
		if (node->hw_lvl != NIX_TXSCH_LVL_SMQ)
			continue;
		if (!(node->flags & NIX_TM_NODE_HWRES))
			continue;

		rc = nix_tm_smq_xoff(nix, node, false);
		if (rc) {
			plt_err("Failed to enable smq %u, rc=%d", node->hw_id,
				rc);
			goto cleanup;
		}
	}

	/* Disable backpressure, it will be enabled back if needed on
	 * hierarchy enable
	 */
	for (i = 0; i < sq_cnt; i++) {
		sq = nix->sqs[i];
		if (!sq)
			continue;

		rc = nix_tm_bp_config_set(roc_nix, sq->qid, 0, false);
		if (rc && rc != -ENOENT) {
			plt_err("Failed to disable backpressure, rc=%d", rc);
			goto cleanup;
		}
	}

	/* Flush all tx queues */
	for (i = 0; i < sq_cnt; i++) {
		sq = nix->sqs[i];
		if (!sq)
			continue;

		rc = roc_nix_sq_ena_dis(sq, false);
		if (rc) {
			plt_err("Failed to disable sqb aura fc, rc=%d", rc);
			goto cleanup;
		}

		/* Wait for sq entries to be flushed */
		rc = roc_nix_tm_sq_flush_spin(sq);
		if (rc) {
			plt_err("Failed to drain sq, rc=%d", rc);
			goto cleanup;
		}
	}

	/* XOFF & Flush all SMQ's. HRM mandates
	 * all SQ's empty before SMQ flush is issued.
	 */
	TAILQ_FOREACH(node, list, node) {
		if (node->hw_lvl != NIX_TXSCH_LVL_SMQ)
			continue;
		if (!(node->flags & NIX_TM_NODE_HWRES))
			continue;

		rc = nix_tm_smq_xoff(nix, node, true);
		if (rc) {
			plt_err("Failed to enable smq %u, rc=%d", node->hw_id,
				rc);
			goto cleanup;
		}

		node->flags &= ~NIX_TM_NODE_ENABLED;
	}

	/* Verify sanity of all tx queues */
	for (i = 0; i < sq_cnt; i++) {
		sq = nix->sqs[i];
		if (!sq)
			continue;

		wdata = ((uint64_t)sq->qid << 32);
		regaddr = nix->base + NIX_LF_SQ_OP_STATUS;
		val = roc_atomic64_add_nosync(wdata, (int64_t *)regaddr);

		sqb_cnt = val & 0xFFFF;
		head_off = (val >> 20) & 0x3F;
		tail_off = (val >> 28) & 0x3F;

		if (sqb_cnt > 1 || head_off != tail_off ||
		    (*(uint64_t *)sq->fc != sq->aura_sqb_bufs))
			plt_err("Failed to gracefully flush sq %u", sq->qid);
	}

	nix->tm_flags &= ~NIX_TM_HIERARCHY_ENA;
cleanup:
	/* Restore cgx state */
	if (!roc_nix->io_enabled) {
		mbox_alloc_msg_nix_lf_stop_rx(mbox_get(mbox));
		rc |= mbox_process(mbox);
		mbox_put(mbox);
	}
	return rc;
}

int
roc_nix_tm_hierarchy_xmit_enable(struct roc_nix *roc_nix, enum roc_nix_tm_tree tree)
{
	struct nix *nix = roc_nix_to_nix_priv(roc_nix);
	struct nix_tm_node_list *list;
	struct nix_tm_node *node;
	struct roc_nix_sq *sq;
	uint16_t sq_id;
	int rc;

	if (tree >= ROC_NIX_TM_TREE_MAX)
		return NIX_ERR_PARAM;

	list = nix_tm_node_list(nix, tree);

	/* Update SQ Sched Data while SQ is idle */
	TAILQ_FOREACH(node, list, node) {
		if (!nix_tm_is_leaf(nix, node->lvl))
			continue;

		rc = nix_tm_sq_sched_conf(nix, node, false);
		if (rc) {
			plt_err("SQ %u sched update failed, rc=%d", node->id,
				rc);
			return rc;
		}
	}

	/* Finally XON all SMQ's */
	TAILQ_FOREACH(node, list, node) {
		if (node->hw_lvl != NIX_TXSCH_LVL_SMQ)
			continue;

		rc = nix_tm_smq_xoff(nix, node, false);
		if (rc) {
			plt_err("Failed to enable smq %u, rc=%d", node->hw_id,
				rc);
			return rc;
		}
	}

	/* Enable xmit as all the topology is ready */
	TAILQ_FOREACH(node, list, node) {
		if (!nix_tm_is_leaf(nix, node->lvl))
			continue;

		sq_id = node->id;
		sq = nix->sqs[sq_id];

		rc = roc_nix_sq_ena_dis(sq, true);
		if (rc) {
			plt_err("TM sw xon failed on SQ %u, rc=%d", node->id,
				rc);
			return rc;
		}
		node->flags |= NIX_TM_NODE_ENABLED;
	}

	return 0;
}

int
roc_nix_tm_hierarchy_enable(struct roc_nix *roc_nix, enum roc_nix_tm_tree tree,
			    bool xmit_enable)
{
	struct nix *nix = roc_nix_to_nix_priv(roc_nix);
	struct nix_tm_node_list *list;
	struct nix_tm_node *node;
	uint32_t tree_mask;
	int rc;

	if (tree >= ROC_NIX_TM_TREE_MAX)
		return NIX_ERR_PARAM;

	if (nix->tm_flags & NIX_TM_HIERARCHY_ENA) {
		if (nix->tm_tree != tree)
			return -EBUSY;
		return 0;
	}

	plt_tm_dbg("Enabling hierarchy on %s, xmit_ena %u, tree %u",
		   nix->pci_dev->name, xmit_enable, tree);

	/* Free hw resources of other trees */
	tree_mask = NIX_TM_TREE_MASK_ALL;
	tree_mask &= ~BIT(tree);

	rc = nix_tm_free_resources(roc_nix, tree_mask, true);
	if (rc) {
		plt_err("failed to free resources of other trees, rc=%d", rc);
		return rc;
	}

	/* Update active tree before starting to do anything */
	nix->tm_tree = tree;

	nix_tm_update_parent_info(nix, tree);

	rc = nix_tm_alloc_txschq(nix, tree);
	if (rc) {
		plt_err("TM failed to alloc tm resources=%d", rc);
		return rc;
	}

	rc = nix_tm_assign_resources(nix, tree);
	if (rc) {
		plt_err("TM failed to assign tm resources=%d", rc);
		return rc;
	}

	rc = nix_tm_txsch_reg_config(nix, tree);
	if (rc) {
		plt_err("TM failed to configure sched registers=%d", rc);
		return rc;
	}

	list = nix_tm_node_list(nix, tree);
	/* Mark all non-leaf's as enabled */
	TAILQ_FOREACH(node, list, node) {
		if (!nix_tm_is_leaf(nix, node->lvl))
			node->flags |= NIX_TM_NODE_ENABLED;
	}

	if (xmit_enable)
		rc = roc_nix_tm_hierarchy_xmit_enable(roc_nix, tree);

	if (!rc)
		nix->tm_flags |= NIX_TM_HIERARCHY_ENA;
	return rc;
}

int
roc_nix_tm_node_suspend_resume(struct roc_nix *roc_nix, uint32_t node_id,
			       bool suspend)
{
	struct nix *nix = roc_nix_to_nix_priv(roc_nix);
	struct mbox *mbox = (&nix->dev)->mbox;
	struct nix_txschq_config *req;
	struct nix_tm_node *node;
	uint16_t flags;
	int rc;

	node = nix_tm_node_search(nix, node_id, ROC_NIX_TM_USER);
	if (!node)
		return NIX_ERR_TM_INVALID_NODE;

	flags = node->flags;
	flags = suspend ? (flags & ~NIX_TM_NODE_ENABLED) :
				(flags | NIX_TM_NODE_ENABLED);

	if (node->flags == flags)
		return 0;

	/* send mbox for state change */
	req = mbox_alloc_msg_nix_txschq_cfg(mbox_get(mbox));

	req->lvl = node->hw_lvl;
	req->num_regs =
		nix_tm_sw_xoff_prep(node, suspend, req->reg, req->regval);
	rc = mbox_process(mbox);
	mbox_put(mbox);
	if (!rc)
		node->flags = flags;
	return rc;
}

int
roc_nix_tm_prealloc_res(struct roc_nix *roc_nix, uint8_t lvl,
			uint16_t discontig, uint16_t contig)
{
	struct nix *nix = roc_nix_to_nix_priv(roc_nix);
	struct mbox *mbox = (&nix->dev)->mbox;
	struct nix_txsch_alloc_req *req;
	struct nix_txsch_alloc_rsp *rsp;
	uint8_t hw_lvl;
	int rc = -ENOSPC;

	hw_lvl = nix_tm_lvl2nix(nix, lvl);
	if (hw_lvl == NIX_TXSCH_LVL_CNT)
		return -EINVAL;

	/* Preallocate contiguous */
	if (nix->contig_rsvd[hw_lvl] < contig) {
		req = mbox_alloc_msg_nix_txsch_alloc(mbox_get(mbox));
		if (req == NULL) {
			mbox_put(mbox);
			return rc;
		}
		req->schq_contig[hw_lvl] = contig - nix->contig_rsvd[hw_lvl];

		rc = mbox_process_msg(mbox, (void *)&rsp);
		if (rc) {
			mbox_put(mbox);
			return rc;
		}

		nix_tm_copy_rsp_to_nix(nix, rsp);
		mbox_put(mbox);
	}

	/* Preallocate contiguous */
	if (nix->discontig_rsvd[hw_lvl] < discontig) {
		req = mbox_alloc_msg_nix_txsch_alloc(mbox_get(mbox));
		if (req == NULL) {
			mbox_put(mbox);
			return -ENOSPC;
		}
		req->schq[hw_lvl] = discontig - nix->discontig_rsvd[hw_lvl];

		rc = mbox_process_msg(mbox, (void *)&rsp);
		if (rc) {
			mbox_put(mbox);
			return rc;
		}

		nix_tm_copy_rsp_to_nix(nix, rsp);
		mbox_put(mbox);
	}

	/* Save thresholds */
	nix->contig_rsvd[hw_lvl] = contig;
	nix->discontig_rsvd[hw_lvl] = discontig;
	/* Release anything present above thresholds */
	nix_tm_release_resources(nix, hw_lvl, true, true);
	nix_tm_release_resources(nix, hw_lvl, false, true);
	return 0;
}

int
roc_nix_tm_node_shaper_update(struct roc_nix *roc_nix, uint32_t node_id,
			      uint32_t profile_id, bool force_update)
{
	struct nix *nix = roc_nix_to_nix_priv(roc_nix);
	struct nix_tm_shaper_profile *profile = NULL;
	struct mbox *mbox = (&nix->dev)->mbox;
	struct nix_txschq_config *req;
	struct nix_tm_node *node;
	uint8_t k;
	int rc;

	/* Shaper updates valid only for user nodes */
	node = nix_tm_node_search(nix, node_id, ROC_NIX_TM_USER);
	if (!node || nix_tm_is_leaf(nix, node->lvl))
		return NIX_ERR_TM_INVALID_NODE;

	if (profile_id != ROC_NIX_TM_SHAPER_PROFILE_NONE) {
		profile = nix_tm_shaper_profile_search(nix, profile_id);
		if (!profile)
			return NIX_ERR_TM_INVALID_SHAPER_PROFILE;
	}

	/* Pkt mode should match existing node's pkt mode */
	if (profile && profile->pkt_mode != node->pkt_mode)
		return NIX_ERR_TM_PKT_MODE_MISMATCH;

	if ((profile_id == node->shaper_profile_id) && !force_update) {
		return 0;
	} else if (profile_id != node->shaper_profile_id) {
		struct nix_tm_shaper_profile *old;

		/* Find old shaper profile and reduce ref count */
		old = nix_tm_shaper_profile_search(nix,
						   node->shaper_profile_id);
		if (old)
			old->ref_cnt--;

		if (profile)
			profile->ref_cnt++;

		/* Reduce older shaper ref count and increase new one */
		node->shaper_profile_id = profile_id;
	}

	/* Nothing to do if hierarchy not yet enabled */
	if (!(nix->tm_flags & NIX_TM_HIERARCHY_ENA))
		return 0;

	node->flags &= ~NIX_TM_NODE_ENABLED;

	/* Flush the specific node with SW_XOFF */
	req = mbox_alloc_msg_nix_txschq_cfg(mbox_get(mbox));
	req->lvl = node->hw_lvl;
	k = nix_tm_sw_xoff_prep(node, true, req->reg, req->regval);
	req->num_regs = k;

	rc = mbox_process(mbox);
	if (rc) {
		mbox_put(mbox);
		return rc;
	}
	mbox_put(mbox);

	/* Update the PIR/CIR and clear SW XOFF */
	req = mbox_alloc_msg_nix_txschq_cfg(mbox_get(mbox));
	req->lvl = node->hw_lvl;

	k = nix_tm_shaper_reg_prep(node, profile, req->reg, req->regval);

	k += nix_tm_sw_xoff_prep(node, false, &req->reg[k], &req->regval[k]);

	req->num_regs = k;
	rc = mbox_process(mbox);
	mbox_put(mbox);
	if (!rc)
		node->flags |= NIX_TM_NODE_ENABLED;
	return rc;
}

int
roc_nix_tm_node_parent_update(struct roc_nix *roc_nix, uint32_t node_id,
			      uint32_t new_parent_id, uint32_t priority,
			      uint32_t weight)
{
	struct nix *nix = roc_nix_to_nix_priv(roc_nix);
	struct mbox *mbox = (&nix->dev)->mbox;
	struct nix_tm_node *node, *sibling;
	struct nix_tm_node *new_parent;
	struct nix_txschq_config *req;
	struct nix_tm_node_list *list;
	uint8_t k;
	int rc;

	node = nix_tm_node_search(nix, node_id, ROC_NIX_TM_USER);
	if (!node)
		return NIX_ERR_TM_INVALID_NODE;

	/* Parent id valid only for non root nodes */
	if (node->hw_lvl != nix->tm_root_lvl) {
		new_parent =
			nix_tm_node_search(nix, new_parent_id, ROC_NIX_TM_USER);
		if (!new_parent)
			return NIX_ERR_TM_INVALID_PARENT;

		/* Current support is only for dynamic weight update */
		if (node->parent != new_parent || node->priority != priority)
			return NIX_ERR_TM_PARENT_PRIO_UPDATE;
	}

	list = nix_tm_node_list(nix, ROC_NIX_TM_USER);
	/* Skip if no change */
	if (node->weight == weight)
		return 0;

	node->weight = weight;

	/* Nothing to do if hierarchy not yet enabled */
	if (!(nix->tm_flags & NIX_TM_HIERARCHY_ENA))
		return 0;

	/* For leaf nodes, SQ CTX needs update */
	if (nix_tm_is_leaf(nix, node->lvl)) {
		/* Update SQ quantum data on the fly */
		rc = nix_tm_sq_sched_conf(nix, node, true);
		if (rc)
			return NIX_ERR_TM_SQ_UPDATE_FAIL;
	} else {
		/* XOFF Parent node */
		req = mbox_alloc_msg_nix_txschq_cfg(mbox_get(mbox));
		req->lvl = node->parent->hw_lvl;
		req->num_regs = nix_tm_sw_xoff_prep(node->parent, true,
						    req->reg, req->regval);
		rc = mbox_process(mbox);
		mbox_put(mbox);
		if (rc)
			return rc;

		/* XOFF this node and all other siblings */
		req = mbox_alloc_msg_nix_txschq_cfg(mbox_get(mbox));
		req->lvl = node->hw_lvl;

		k = 0;
		TAILQ_FOREACH(sibling, list, node) {
			if (sibling->parent != node->parent)
				continue;
			k += nix_tm_sw_xoff_prep(sibling, true, &req->reg[k], &req->regval[k]);
			if (k >= MAX_REGS_PER_MBOX_MSG) {
				req->num_regs = k;
				rc = mbox_process(mbox);
				mbox_put(mbox);
				if (rc)
					return rc;
				k = 0;
				req = mbox_alloc_msg_nix_txschq_cfg(mbox_get(mbox));
				req->lvl = node->hw_lvl;
			}
		}

		if (k) {
			req->num_regs = k;
			rc = mbox_process(mbox);
			mbox_put(mbox);
			if (rc)
				return rc;
			/* Update new weight for current node */
			req = mbox_alloc_msg_nix_txschq_cfg(mbox_get(mbox));
		}

		req->lvl = node->hw_lvl;
		req->num_regs = nix_tm_sched_reg_prep(nix, node, req->reg, req->regval);
		rc = mbox_process(mbox);
		mbox_put(mbox);
		if (rc)
			return rc;

		/* XON this node and all other siblings */
		req = mbox_alloc_msg_nix_txschq_cfg(mbox_get(mbox));
		req->lvl = node->hw_lvl;

		k = 0;
		TAILQ_FOREACH(sibling, list, node) {
			if (sibling->parent != node->parent)
				continue;
			k += nix_tm_sw_xoff_prep(sibling, false, &req->reg[k], &req->regval[k]);
			if (k >= MAX_REGS_PER_MBOX_MSG) {
				req->num_regs = k;
				rc = mbox_process(mbox);
				mbox_put(mbox);
				if (rc)
					return rc;
				k = 0;
				req = mbox_alloc_msg_nix_txschq_cfg(mbox_get(mbox));
				req->lvl = node->hw_lvl;
			}
		}

		if (k) {
			req->num_regs = k;
			rc = mbox_process(mbox);
			mbox_put(mbox);
			if (rc)
				return rc;
			/* XON Parent node */
			req = mbox_alloc_msg_nix_txschq_cfg(mbox_get(mbox));
		}

		req->lvl = node->parent->hw_lvl;
		req->num_regs = nix_tm_sw_xoff_prep(node->parent, false, req->reg, req->regval);
		rc = mbox_process(mbox);
		mbox_put(mbox);
		if (rc)
			return rc;
	}
	return 0;
}

int
roc_nix_tm_init(struct roc_nix *roc_nix)
{
	struct nix *nix = roc_nix_to_nix_priv(roc_nix);
	uint32_t tree_mask;
	int rc;

	if (nix->tm_flags & NIX_TM_HIERARCHY_ENA) {
		plt_err("Cannot init while existing hierarchy is enabled");
		return -EBUSY;
	}

	/* Free up all user resources already held */
	tree_mask = NIX_TM_TREE_MASK_ALL;
	rc = nix_tm_free_resources(roc_nix, tree_mask, false);
	if (rc) {
		plt_err("Failed to freeup all nodes and resources, rc=%d", rc);
		return rc;
	}

	/* Prepare default tree */
	rc = nix_tm_prepare_default_tree(roc_nix);
	if (rc) {
		plt_err("failed to prepare default tm tree, rc=%d", rc);
		return rc;
	}

	return rc;
}

int
roc_nix_tm_pfc_rlimit_sq(struct roc_nix *roc_nix, uint16_t qid, uint64_t rate)
{
	struct nix *nix = roc_nix_to_nix_priv(roc_nix);
	struct nix_tm_shaper_profile profile;
	struct mbox *mbox = (&nix->dev)->mbox;
	struct nix_tm_node *node, *parent;
	struct roc_nix_link_info link_info;

	volatile uint64_t *reg, *regval;
	struct nix_txschq_config *req;
	uint64_t tl2_rate = 0;
	uint16_t flags;
	uint8_t k = 0;
	int rc;

	if ((nix->tm_tree != ROC_NIX_TM_PFC) || !(nix->tm_flags & NIX_TM_HIERARCHY_ENA))
		return NIX_ERR_TM_INVALID_TREE;

	node = nix_tm_node_search(nix, qid, nix->tm_tree);

	/* check if we found a valid leaf node */
	if (!node || !nix_tm_is_leaf(nix, node->lvl) || !node->parent ||
	    node->parent->hw_id == NIX_TM_HW_ID_INVALID) {
		return NIX_ERR_TM_INVALID_NODE;
	}

	/* Get the link Speed */
	if (roc_nix_mac_link_info_get(roc_nix, &link_info))
		return -EINVAL;

	if (link_info.status)
		tl2_rate = link_info.speed * (uint64_t)1E6;

	/* Configure TL3 of leaf node with requested rate */
	parent = node->parent;	 /* SMQ/MDQ */
	parent = parent->parent; /* TL4 */
	parent = parent->parent; /* TL3 */
	flags = parent->flags;

	req = mbox_alloc_msg_nix_txschq_cfg(mbox_get(mbox));
	req->lvl = parent->hw_lvl;
	reg = req->reg;
	regval = req->regval;

	if (rate == 0) {
		k += nix_tm_sw_xoff_prep(parent, true, &reg[k], &regval[k]);
		flags &= ~NIX_TM_NODE_ENABLED;
		goto exit;
	}

	if (!(flags & NIX_TM_NODE_ENABLED)) {
		k += nix_tm_sw_xoff_prep(parent, false, &reg[k], &regval[k]);
		flags |= NIX_TM_NODE_ENABLED;
	}

	/* Use only PIR for rate limit */
	memset(&profile, 0, sizeof(profile));
	profile.peak.rate = rate;
	/* Minimum burst of ~4us Bytes of Tx */
	profile.peak.size =
		PLT_MAX((uint64_t)roc_nix_max_pkt_len(roc_nix), (4ul * rate) / ((uint64_t)1E6 * 8));
	if (!nix->tm_rate_min || nix->tm_rate_min > rate)
		nix->tm_rate_min = rate;

	k += nix_tm_shaper_reg_prep(parent, &profile, &reg[k], &regval[k]);
exit:
	req->num_regs = k;
	rc = mbox_process(mbox);
	mbox_put(mbox);
	if (rc)
		return rc;

	parent->flags = flags;

	/* If link is up then configure TL2 with link speed */
	if (tl2_rate && (flags & NIX_TM_NODE_ENABLED)) {
		k = 0;
		parent = parent->parent;
		req = mbox_alloc_msg_nix_txschq_cfg(mbox_get(mbox));
		req->lvl = parent->hw_lvl;
		reg = req->reg;
		regval = req->regval;

		/* Use only PIR for rate limit */
		memset(&profile, 0, sizeof(profile));
		profile.peak.rate = tl2_rate;
		/* Minimum burst of ~4us Bytes of Tx */
		profile.peak.size = PLT_MAX((uint64_t)roc_nix_max_pkt_len(roc_nix),
					    (4ul * tl2_rate) / ((uint64_t)1E6 * 8));
		k += nix_tm_shaper_reg_prep(parent, &profile, &reg[k], &regval[k]);
		req->num_regs = k;
		rc = mbox_process(mbox);
		mbox_put(mbox);
	}
	return rc;
}

int
roc_nix_tm_rlimit_sq(struct roc_nix *roc_nix, uint16_t qid, uint64_t rate)
{
	struct nix *nix = roc_nix_to_nix_priv(roc_nix);
	struct nix_tm_shaper_profile profile;
	struct mbox *mbox = (&nix->dev)->mbox;
	struct nix_tm_node *node, *parent;

	volatile uint64_t *reg, *regval;
	struct nix_txschq_config *req;
	uint16_t flags;
	uint8_t k = 0;
	int rc;

	if ((nix->tm_tree == ROC_NIX_TM_USER) ||
	    !(nix->tm_flags & NIX_TM_HIERARCHY_ENA)) {
		return NIX_ERR_TM_INVALID_TREE;
	}

	node = nix_tm_node_search(nix, qid, nix->tm_tree);

	/* check if we found a valid leaf node */
	if (!node || !nix_tm_is_leaf(nix, node->lvl) || !node->parent ||
	    node->parent->hw_id == NIX_TM_HW_ID_INVALID) {
		return NIX_ERR_TM_INVALID_NODE;
	}

	parent = node->parent;
	flags = parent->flags;

	req = mbox_alloc_msg_nix_txschq_cfg(mbox_get(mbox));
	req->lvl = NIX_TXSCH_LVL_MDQ;
	reg = req->reg;
	regval = req->regval;

	if (rate == 0) {
		k += nix_tm_sw_xoff_prep(parent, true, &reg[k], &regval[k]);
		flags &= ~NIX_TM_NODE_ENABLED;
		goto exit;
	}

	if (!(flags & NIX_TM_NODE_ENABLED)) {
		k += nix_tm_sw_xoff_prep(parent, false, &reg[k], &regval[k]);
		flags |= NIX_TM_NODE_ENABLED;
	}

	/* Use only PIR for rate limit */
	memset(&profile, 0, sizeof(profile));
	profile.peak.rate = rate;
	/* Minimum burst of ~4us Bytes of Tx */
	profile.peak.size = PLT_MAX((uint64_t)roc_nix_max_pkt_len(roc_nix),
				    (4ul * rate) / ((uint64_t)1E6 * 8));
	if (!nix->tm_rate_min || nix->tm_rate_min > rate)
		nix->tm_rate_min = rate;

	k += nix_tm_shaper_reg_prep(parent, &profile, &reg[k], &regval[k]);
exit:
	req->num_regs = k;
	rc = mbox_process(mbox);
	mbox_put(mbox);
	if (rc)
		return rc;

	parent->flags = flags;
	return 0;
}

void
roc_nix_tm_fini(struct roc_nix *roc_nix)
{
	struct nix *nix = roc_nix_to_nix_priv(roc_nix);
	struct mbox *mbox = (&nix->dev)->mbox;
	struct nix_txsch_free_req *req;
	uint32_t tree_mask;
	uint8_t hw_lvl;
	int rc;

	/* Xmit is assumed to be disabled */
	/* Free up resources already held */
	tree_mask = NIX_TM_TREE_MASK_ALL;
	rc = nix_tm_free_resources(roc_nix, tree_mask, false);
	if (rc)
		plt_err("Failed to freeup existing nodes or rsrcs, rc=%d", rc);

	/* Free all other hw resources */
	req = mbox_alloc_msg_nix_txsch_free(mbox_get(mbox));
	if (req == NULL) {
		mbox_put(mbox);
		return;
	}

	req->flags = TXSCHQ_FREE_ALL;
	rc = mbox_process(mbox);
	if (rc)
		plt_err("Failed to freeup all res, rc=%d", rc);
	mbox_put(mbox);

	for (hw_lvl = 0; hw_lvl < NIX_TXSCH_LVL_CNT; hw_lvl++) {
		plt_bitmap_reset(nix->schq_bmp[hw_lvl]);
		plt_bitmap_reset(nix->schq_contig_bmp[hw_lvl]);
		nix->contig_rsvd[hw_lvl] = 0;
		nix->discontig_rsvd[hw_lvl] = 0;
	}

	/* Clear shaper profiles */
	nix_tm_clear_shaper_profiles(nix);
	nix->tm_tree = 0;
	nix->tm_flags &= ~NIX_TM_HIERARCHY_ENA;
}

int
roc_nix_tm_rsrc_count(struct roc_nix *roc_nix, uint16_t schq[ROC_TM_LVL_MAX])
{
	struct nix *nix = roc_nix_to_nix_priv(roc_nix);
	struct mbox *mbox = mbox_get((&nix->dev)->mbox);
	struct free_rsrcs_rsp *rsp;
	uint8_t hw_lvl;
	int rc, i;

	/* Get the current free resources */
	mbox_alloc_msg_free_rsrc_cnt(mbox);
	rc = mbox_process_msg(mbox, (void *)&rsp);
	if (rc)
		goto exit;

	for (i = 0; i < ROC_TM_LVL_MAX; i++) {
		hw_lvl = nix_tm_lvl2nix(nix, i);
		if (hw_lvl == NIX_TXSCH_LVL_CNT)
			continue;

		schq[i] = (nix->is_nix1 ? rsp->schq_nix1[hw_lvl] :
						rsp->schq[hw_lvl]);
	}

	rc = 0;
exit:
	mbox_put(mbox);
	return rc;
}

void
roc_nix_tm_rsrc_max(bool pf, uint16_t schq[ROC_TM_LVL_MAX])
{
	uint8_t hw_lvl, i;
	uint16_t max;

	for (i = 0; i < ROC_TM_LVL_MAX; i++) {
		hw_lvl = pf ? nix_tm_lvl2nix_tl1_root(i) :
				    nix_tm_lvl2nix_tl2_root(i);

		switch (hw_lvl) {
		case NIX_TXSCH_LVL_SMQ:
			max = (roc_model_is_cn9k() ? NIX_CN9K_TXSCH_LVL_SMQ_MAX :
				(roc_model_is_cn10k() ? NIX_CN10K_TXSCH_LVL_SMQ_MAX :
				 NIX_TXSCH_LVL_SMQ_MAX));
			break;
		case NIX_TXSCH_LVL_TL4:
			max = (roc_model_is_cn9k() ? NIX_CN9K_TXSCH_LVL_TL4_MAX :
				(roc_model_is_cn10k() ? NIX_CN10K_TXSCH_LVL_TL4_MAX :
							NIX_TXSCH_LVL_TL4_MAX));
			break;
		case NIX_TXSCH_LVL_TL3:
			max = (roc_model_is_cn9k() ? NIX_CN9K_TXSCH_LVL_TL3_MAX :
				(roc_model_is_cn10k() ? NIX_CN10K_TXSCH_LVL_TL3_MAX :
							NIX_TXSCH_LVL_TL3_MAX));
			break;
		case NIX_TXSCH_LVL_TL2:
			max = pf ? NIX_TXSCH_LVL_TL2_MAX : 1;
			break;
		case NIX_TXSCH_LVL_TL1:
			max = pf ? 1 : 0;
			break;
		default:
			max = 0;
			break;
		}
		schq[i] = max;
	}
}

bool
roc_nix_tm_root_has_sp(struct roc_nix *roc_nix)
{
	struct nix *nix = roc_nix_to_nix_priv(roc_nix);

	if (nix->tm_flags & NIX_TM_TL1_NO_SP)
		return false;
	return true;
}

static inline struct nix *
pf_func_to_nix_get(uint16_t pf_func)
{
	struct roc_nix *roc_nix_tmp = NULL;
	struct roc_nix_list *nix_list;

	nix_list = roc_idev_nix_list_get();
	if (nix_list == NULL)
		return NULL;

	/* Find the NIX of given pf_func */
	TAILQ_FOREACH(roc_nix_tmp, nix_list, next) {
		struct nix *nix = roc_nix_to_nix_priv(roc_nix_tmp);

		if (nix->dev.pf_func == pf_func)
			return nix;
	}

	return NULL;
}

int
roc_nix_tm_egress_link_cfg_set(struct roc_nix *roc_nix, uint64_t dst_pf_func, bool enable)
{
	struct nix *src_nix = roc_nix_to_nix_priv(roc_nix), *dst_nix;
	struct mbox *mbox = (&src_nix->dev)->mbox;
	struct nix_txschq_config *req = NULL;
	struct nix_tm_node_list *list;
	struct nix_tm_node *node;
	int rc = 0, k;

	dst_nix = pf_func_to_nix_get(dst_pf_func);
	if (!dst_nix)
		return -EINVAL;

	if (dst_nix == src_nix)
		return 0;

	list = nix_tm_node_list(src_nix, src_nix->tm_tree);
	TAILQ_FOREACH(node, list, node) {
		if (node->hw_lvl != src_nix->tm_link_cfg_lvl)
			continue;

		if (!(node->flags & NIX_TM_NODE_HWRES))
			continue;

		/* Allocating TL3 request */
		req = mbox_alloc_msg_nix_txschq_cfg(mbox_get(mbox));
		req->lvl = src_nix->tm_link_cfg_lvl;
		k = 0;

		/* Enable PFC/pause on the identified TL3 */
		req->reg[k] = NIX_AF_TL3_TL2X_LINKX_CFG(node->hw_id, dst_nix->tx_link);
		if (enable)
			req->regval[k] |= BIT_ULL(12);
		else
			req->regval[k] &= ~(BIT_ULL(12));
		req->regval_mask[k] = ~(BIT_ULL(12));
		k++;

		req->num_regs = k;
		rc = mbox_process(mbox);
		mbox_put(mbox);
		if (rc)
			goto err;
	}
err:
	return rc;
}
