/* SPDX-License-Identifier: BSD-3-Clause
 * Copyright(c) 2016-2020 Intel Corporation
 */

#include <inttypes.h>

#include <rte_malloc.h>
#include <rte_eventdev.h>

#include "dlb2_priv.h"
#include "dlb2_inline_fns.h"
#include "pf/base/dlb2_regs.h"

enum dlb2_xstats_type {
	/* common to device and port */
	rx_ok,				/**< Receive an event */
	rx_drop,                        /**< Error bit set in received QE */
	rx_interrupt_wait,		/**< Wait on an interrupt */
	rx_umonitor_umwait,		/**< Block using umwait */
	tx_ok,				/**< Transmit an event */
	total_polls,			/**< Call dequeue_burst */
	zero_polls,			/**< Call dequeue burst and return 0 */
	tx_nospc_ldb_hw_credits,	/**< Insufficient LDB h/w credits */
	tx_nospc_dir_hw_credits,	/**< Insufficient DIR h/w credits */
	tx_nospc_hw_credits,		/**< Insufficient h/w credits */
	tx_nospc_inflight_max,		/**< Reach the new_event_threshold */
	tx_nospc_new_event_limit,	/**< Insufficient s/w credits */
	tx_nospc_inflight_credits,	/**< Port has too few s/w credits */
	/* device specific */
	nb_events_limit,
	inflight_events,
	ldb_pool_size,
	dir_pool_size,
	pool_size,
	/* port specific */
	tx_new,				/**< Send an OP_NEW event */
	tx_fwd,				/**< Send an OP_FORWARD event */
	tx_rel,				/**< Send an OP_RELEASE event */
	tx_implicit_rel,		/**< Issue an implicit event release */
	tx_sched_ordered,		/**< Send a SCHED_TYPE_ORDERED event */
	tx_sched_unordered,		/**< Send a SCHED_TYPE_PARALLEL event */
	tx_sched_atomic,		/**< Send a SCHED_TYPE_ATOMIC event */
	tx_sched_directed,		/**< Send a directed event */
	tx_invalid,                     /**< Send an event with an invalid op */
	outstanding_releases,		/**< # of releases a port owes */
	max_outstanding_releases,	/**< max # of releases a port can owe */
	rx_sched_ordered,		/**< Dequeue an ordered event */
	rx_sched_unordered,		/**< Dequeue an unordered event */
	rx_sched_atomic,		/**< Dequeue an atomic event */
	rx_sched_directed,		/**< Dequeue an directed event */
	rx_sched_invalid,               /**< Dequeue event sched type invalid */
	/* common to port and queue */
	is_configured,			/**< Port is configured */
	is_load_balanced,		/**< Port is LDB */
	hw_id,				/**< Hardware ID */
	/* queue specific */
	num_links,			/**< Number of ports linked */
	sched_type,			/**< Queue sched type */
	enq_ok,				/**< # events enqueued to the queue */
	current_depth,			/**< Current queue depth */
	depth_threshold,		/**< Programmed depth threshold */
	depth_le50_threshold,
	/**< Depth LE to 50% of the configured hardware threshold */
	depth_gt50_le75_threshold,
	/**< Depth GT 50%, but LE to 75% of the configured hardware threshold */
	depth_gt75_le100_threshold,
	/**< Depth GT 75%. but LE to the configured hardware threshold */
	depth_gt100_threshold
	/**< Depth GT 100% of the configured hw threshold */
};

typedef uint64_t (*dlb2_xstats_fn)(struct dlb2_eventdev *dlb2,
		uint16_t obj_idx, /* port or queue id */
		enum dlb2_xstats_type stat, int extra_arg);

enum dlb2_xstats_fn_type {
	DLB2_XSTATS_FN_DEV,
	DLB2_XSTATS_FN_PORT,
	DLB2_XSTATS_FN_QUEUE
};

struct dlb2_xstats_entry {
	struct rte_event_dev_xstats_name name;
	uint64_t reset_value; /* an offset to be taken away to emulate resets */
	enum dlb2_xstats_fn_type fn_id;
	enum dlb2_xstats_type stat;
	enum rte_event_dev_xstats_mode mode;
	int extra_arg;
	uint16_t obj_idx;
	uint8_t reset_allowed; /* when set, this value can be reset */
};

/* Some device stats are simply a summation of the corresponding port values */
static uint64_t
dlb2_device_traffic_stat_get(struct dlb2_eventdev *dlb2,
			     int which_stat)
{
	int i;
	uint64_t val = 0;

	for (i = 0; i < DLB2_MAX_NUM_PORTS(dlb2->version); i++) {
		struct dlb2_eventdev_port *port = &dlb2->ev_ports[i];

		if (!port->setup_done)
			continue;

		switch (which_stat) {
		case rx_ok:
			val += port->stats.traffic.rx_ok;
			break;
		case rx_drop:
			val += port->stats.traffic.rx_drop;
			break;
		case rx_interrupt_wait:
			val += port->stats.traffic.rx_interrupt_wait;
			break;
		case rx_umonitor_umwait:
			val += port->stats.traffic.rx_umonitor_umwait;
			break;
		case tx_ok:
			val += port->stats.traffic.tx_ok;
			break;
		case total_polls:
			val += port->stats.traffic.total_polls;
			break;
		case zero_polls:
			val += port->stats.traffic.zero_polls;
			break;
		case tx_nospc_ldb_hw_credits:
			val += port->stats.traffic.tx_nospc_ldb_hw_credits;
			break;
		case tx_nospc_dir_hw_credits:
			val += port->stats.traffic.tx_nospc_dir_hw_credits;
			break;
		case tx_nospc_hw_credits:
			val += port->stats.traffic.tx_nospc_hw_credits;
			break;
		case tx_nospc_inflight_max:
			val += port->stats.traffic.tx_nospc_inflight_max;
			break;
		case tx_nospc_new_event_limit:
			val += port->stats.traffic.tx_nospc_new_event_limit;
			break;
		case tx_nospc_inflight_credits:
			val += port->stats.traffic.tx_nospc_inflight_credits;
			break;
		default:
			return -1;
		}
	}
	return val;
}

static uint64_t
get_dev_stat(struct dlb2_eventdev *dlb2, uint16_t obj_idx __rte_unused,
	     enum dlb2_xstats_type type, int extra_arg __rte_unused)
{
	switch (type) {
	case rx_ok:
	case rx_drop:
	case rx_interrupt_wait:
	case rx_umonitor_umwait:
	case tx_ok:
	case total_polls:
	case zero_polls:
	case tx_nospc_ldb_hw_credits:
	case tx_nospc_dir_hw_credits:
	case tx_nospc_hw_credits:
	case tx_nospc_inflight_max:
	case tx_nospc_new_event_limit:
	case tx_nospc_inflight_credits:
		return dlb2_device_traffic_stat_get(dlb2, type);
	case nb_events_limit:
		return dlb2->new_event_limit;
	case inflight_events:
		return __atomic_load_n(&dlb2->inflights, __ATOMIC_SEQ_CST);
	case ldb_pool_size:
		return dlb2->num_ldb_credits;
	case dir_pool_size:
		return dlb2->num_dir_credits;
	case pool_size:
		return dlb2->num_credits;
	default: return -1;
	}
}

static uint64_t
get_port_stat(struct dlb2_eventdev *dlb2, uint16_t obj_idx,
	      enum dlb2_xstats_type type, int extra_arg __rte_unused)
{
	struct dlb2_eventdev_port *ev_port = &dlb2->ev_ports[obj_idx];

	switch (type) {
	case rx_ok: return ev_port->stats.traffic.rx_ok;

	case rx_drop: return ev_port->stats.traffic.rx_drop;

	case rx_interrupt_wait: return ev_port->stats.traffic.rx_interrupt_wait;

	case rx_umonitor_umwait:
		return ev_port->stats.traffic.rx_umonitor_umwait;

	case tx_ok: return ev_port->stats.traffic.tx_ok;

	case total_polls: return ev_port->stats.traffic.total_polls;

	case zero_polls: return ev_port->stats.traffic.zero_polls;

	case tx_nospc_ldb_hw_credits:
		return ev_port->stats.traffic.tx_nospc_ldb_hw_credits;

	case tx_nospc_dir_hw_credits:
		return ev_port->stats.traffic.tx_nospc_dir_hw_credits;

	case tx_nospc_hw_credits:
		return ev_port->stats.traffic.tx_nospc_hw_credits;

	case tx_nospc_inflight_max:
		return ev_port->stats.traffic.tx_nospc_inflight_max;

	case tx_nospc_new_event_limit:
		return ev_port->stats.traffic.tx_nospc_new_event_limit;

	case tx_nospc_inflight_credits:
		return ev_port->stats.traffic.tx_nospc_inflight_credits;

	case is_configured: return ev_port->setup_done;

	case is_load_balanced: return !ev_port->qm_port.is_directed;

	case hw_id: return ev_port->qm_port.id;

	case tx_new: return ev_port->stats.tx_op_cnt[RTE_EVENT_OP_NEW];

	case tx_fwd: return ev_port->stats.tx_op_cnt[RTE_EVENT_OP_FORWARD];

	case tx_rel: return ev_port->stats.tx_op_cnt[RTE_EVENT_OP_RELEASE];

	case tx_implicit_rel: return ev_port->stats.tx_implicit_rel;

	case tx_sched_ordered:
		return ev_port->stats.tx_sched_cnt[DLB2_SCHED_ORDERED];

	case tx_sched_unordered:
		return ev_port->stats.tx_sched_cnt[DLB2_SCHED_UNORDERED];

	case tx_sched_atomic:
		return ev_port->stats.tx_sched_cnt[DLB2_SCHED_ATOMIC];

	case tx_sched_directed:
		return ev_port->stats.tx_sched_cnt[DLB2_SCHED_DIRECTED];

	case tx_invalid: return ev_port->stats.tx_invalid;

	case outstanding_releases: return ev_port->outstanding_releases;

	case max_outstanding_releases:
		return DLB2_NUM_HIST_LIST_ENTRIES_PER_LDB_PORT;

	case rx_sched_ordered:
		return ev_port->stats.rx_sched_cnt[DLB2_SCHED_ORDERED];

	case rx_sched_unordered:
		return ev_port->stats.rx_sched_cnt[DLB2_SCHED_UNORDERED];

	case rx_sched_atomic:
		return ev_port->stats.rx_sched_cnt[DLB2_SCHED_ATOMIC];

	case rx_sched_directed:
		return ev_port->stats.rx_sched_cnt[DLB2_SCHED_DIRECTED];

	case rx_sched_invalid: return ev_port->stats.rx_sched_invalid;

	default: return -1;
	}
}

static uint64_t
dlb2_get_threshold_stat(struct dlb2_eventdev *dlb2, int qid, int stat)
{
	int port = 0;
	uint64_t tally = 0;

	for (port = 0; port < DLB2_MAX_NUM_PORTS(dlb2->version); port++)
		tally += dlb2->ev_ports[port].stats.queue[qid].qid_depth[stat];

	return tally;
}

static uint64_t
dlb2_get_enq_ok_stat(struct dlb2_eventdev *dlb2, int qid)
{
	int port = 0;
	uint64_t enq_ok_tally = 0;

	for (port = 0; port < DLB2_MAX_NUM_PORTS(dlb2->version); port++)
		enq_ok_tally += dlb2->ev_ports[port].stats.queue[qid].enq_ok;

	return enq_ok_tally;
}

static uint64_t
get_queue_stat(struct dlb2_eventdev *dlb2, uint16_t obj_idx,
	       enum dlb2_xstats_type type, int extra_arg __rte_unused)
{
	struct dlb2_eventdev_queue *ev_queue =
		&dlb2->ev_queues[obj_idx];

	switch (type) {
	case is_configured: return ev_queue->setup_done;

	case is_load_balanced: return !ev_queue->qm_queue.is_directed;

	case hw_id: return ev_queue->qm_queue.id;

	case num_links: return ev_queue->num_links;

	case sched_type: return ev_queue->qm_queue.sched_type;

	case enq_ok: return dlb2_get_enq_ok_stat(dlb2, obj_idx);

	case current_depth: return dlb2_get_queue_depth(dlb2, ev_queue);

	case depth_threshold: return ev_queue->depth_threshold;

	case depth_le50_threshold:
		return dlb2_get_threshold_stat(dlb2, ev_queue->id,
					       DLB2_QID_DEPTH_LE50);

	case depth_gt50_le75_threshold:
		return dlb2_get_threshold_stat(dlb2, ev_queue->id,
					       DLB2_QID_DEPTH_GT50_LE75);

	case depth_gt75_le100_threshold:
		return dlb2_get_threshold_stat(dlb2, ev_queue->id,
					       DLB2_QID_DEPTH_GT75_LE100);

	case depth_gt100_threshold:
		return dlb2_get_threshold_stat(dlb2, ev_queue->id,
					       DLB2_QID_DEPTH_GT100);

	default: return -1;
	}
}

int
dlb2_xstats_init(struct dlb2_eventdev *dlb2)
{
	/*
	 * define the stats names and types. Used to build up the device
	 * xstats array
	 * There are multiple set of stats:
	 *   - device-level,
	 *   - per-port,
	 *   - per-qid,
	 *
	 * For each of these sets, we have three parallel arrays, one for the
	 * names, the other for the stat type parameter to be passed in the fn
	 * call to get that stat. The third array allows resetting or not.
	 * All these arrays must be kept in sync
	 */
	static const char * const dev_stats[] = {
		"rx_ok",
		"rx_drop",
		"rx_interrupt_wait",
		"rx_umonitor_umwait",
		"tx_ok",
		"total_polls",
		"zero_polls",
		"tx_nospc_ldb_hw_credits",
		"tx_nospc_dir_hw_credits",
		"tx_nospc_hw_credits",
		"tx_nospc_inflight_max",
		"tx_nospc_new_event_limit",
		"tx_nospc_inflight_credits",
		"nb_events_limit",
		"inflight_events",
		"ldb_pool_size",
		"dir_pool_size",
		"pool_size",
	};
	static const enum dlb2_xstats_type dev_types[] = {
		rx_ok,
		rx_drop,
		rx_interrupt_wait,
		rx_umonitor_umwait,
		tx_ok,
		total_polls,
		zero_polls,
		tx_nospc_ldb_hw_credits,
		tx_nospc_dir_hw_credits,
		tx_nospc_hw_credits,
		tx_nospc_inflight_max,
		tx_nospc_new_event_limit,
		tx_nospc_inflight_credits,
		nb_events_limit,
		inflight_events,
		ldb_pool_size,
		dir_pool_size,
		pool_size,
	};
	/* Note: generated device stats are not allowed to be reset. */
	static const uint8_t dev_reset_allowed[] = {
		0, /* rx_ok */
		0, /* rx_drop */
		0, /* rx_interrupt_wait */
		0, /* rx_umonitor_umwait */
		0, /* tx_ok */
		0, /* total_polls */
		0, /* zero_polls */
		0, /* tx_nospc_ldb_hw_credits */
		0, /* tx_nospc_dir_hw_credits */
		0, /* tx_nospc_hw_credits */
		0, /* tx_nospc_inflight_max */
		0, /* tx_nospc_new_event_limit */
		0, /* tx_nospc_inflight_credits */
		0, /* nb_events_limit */
		0, /* inflight_events */
		0, /* ldb_pool_size */
		0, /* dir_pool_size */
		0, /* pool_size */
	};
	static const char * const port_stats[] = {
		"is_configured",
		"is_load_balanced",
		"hw_id",
		"rx_ok",
		"rx_drop",
		"rx_interrupt_wait",
		"rx_umonitor_umwait",
		"tx_ok",
		"total_polls",
		"zero_polls",
		"tx_nospc_ldb_hw_credits",
		"tx_nospc_dir_hw_credits",
		"tx_nospc_hw_credits",
		"tx_nospc_inflight_max",
		"tx_nospc_new_event_limit",
		"tx_nospc_inflight_credits",
		"tx_new",
		"tx_fwd",
		"tx_rel",
		"tx_implicit_rel",
		"tx_sched_ordered",
		"tx_sched_unordered",
		"tx_sched_atomic",
		"tx_sched_directed",
		"tx_invalid",
		"outstanding_releases",
		"max_outstanding_releases",
		"rx_sched_ordered",
		"rx_sched_unordered",
		"rx_sched_atomic",
		"rx_sched_directed",
		"rx_sched_invalid"
	};
	static const enum dlb2_xstats_type port_types[] = {
		is_configured,
		is_load_balanced,
		hw_id,
		rx_ok,
		rx_drop,
		rx_interrupt_wait,
		rx_umonitor_umwait,
		tx_ok,
		total_polls,
		zero_polls,
		tx_nospc_ldb_hw_credits,
		tx_nospc_dir_hw_credits,
		tx_nospc_hw_credits,
		tx_nospc_inflight_max,
		tx_nospc_new_event_limit,
		tx_nospc_inflight_credits,
		tx_new,
		tx_fwd,
		tx_rel,
		tx_implicit_rel,
		tx_sched_ordered,
		tx_sched_unordered,
		tx_sched_atomic,
		tx_sched_directed,
		tx_invalid,
		outstanding_releases,
		max_outstanding_releases,
		rx_sched_ordered,
		rx_sched_unordered,
		rx_sched_atomic,
		rx_sched_directed,
		rx_sched_invalid
	};
	static const uint8_t port_reset_allowed[] = {
		0, /* is_configured */
		0, /* is_load_balanced */
		0, /* hw_id */
		1, /* rx_ok */
		1, /* rx_drop */
		1, /* rx_interrupt_wait */
		1, /* rx_umonitor_umwait */
		1, /* tx_ok */
		1, /* total_polls */
		1, /* zero_polls */
		1, /* tx_nospc_ldb_hw_credits */
		1, /* tx_nospc_dir_hw_credits */
		1, /* tx_nospc_hw_credits */
		1, /* tx_nospc_inflight_max */
		1, /* tx_nospc_new_event_limit */
		1, /* tx_nospc_inflight_credits */
		1, /* tx_new */
		1, /* tx_fwd */
		1, /* tx_rel */
		1, /* tx_implicit_rel */
		1, /* tx_sched_ordered */
		1, /* tx_sched_unordered */
		1, /* tx_sched_atomic */
		1, /* tx_sched_directed */
		1, /* tx_invalid */
		0, /* outstanding_releases */
		0, /* max_outstanding_releases */
		1, /* rx_sched_ordered */
		1, /* rx_sched_unordered */
		1, /* rx_sched_atomic */
		1, /* rx_sched_directed */
		1  /* rx_sched_invalid */
	};

	/* QID specific stats */
	static const char * const qid_stats[] = {
		"is_configured",
		"is_load_balanced",
		"hw_id",
		"num_links",
		"sched_type",
		"enq_ok",
		"current_depth",
		"depth_threshold",
		"depth_le50_threshold",
		"depth_gt50_le75_threshold",
		"depth_gt75_le100_threshold",
		"depth_gt100_threshold",
	};
	static const enum dlb2_xstats_type qid_types[] = {
		is_configured,
		is_load_balanced,
		hw_id,
		num_links,
		sched_type,
		enq_ok,
		current_depth,
		depth_threshold,
		depth_le50_threshold,
		depth_gt50_le75_threshold,
		depth_gt75_le100_threshold,
		depth_gt100_threshold,
	};
	static const uint8_t qid_reset_allowed[] = {
		0, /* is_configured */
		0, /* is_load_balanced */
		0, /* hw_id */
		0, /* num_links */
		0, /* sched_type */
		1, /* enq_ok */
		0, /* current_depth */
		0, /* depth_threshold */
		1, /* depth_le50_threshold */
		1, /* depth_gt50_le75_threshold */
		1, /* depth_gt75_le100_threshold */
		1, /* depth_gt100_threshold */
	};

	/* ---- end of stat definitions ---- */

	/* check sizes, since a missed comma can lead to strings being
	 * joined by the compiler.
	 */
	RTE_BUILD_BUG_ON(RTE_DIM(dev_stats) != RTE_DIM(dev_types));
	RTE_BUILD_BUG_ON(RTE_DIM(port_stats) != RTE_DIM(port_types));
	RTE_BUILD_BUG_ON(RTE_DIM(qid_stats) != RTE_DIM(qid_types));

	RTE_BUILD_BUG_ON(RTE_DIM(dev_stats) != RTE_DIM(dev_reset_allowed));
	RTE_BUILD_BUG_ON(RTE_DIM(port_stats) != RTE_DIM(port_reset_allowed));
	RTE_BUILD_BUG_ON(RTE_DIM(qid_stats) != RTE_DIM(qid_reset_allowed));

	/* other vars */
	const unsigned int count = RTE_DIM(dev_stats) +
		DLB2_MAX_NUM_PORTS(dlb2->version) * RTE_DIM(port_stats) +
		DLB2_MAX_NUM_QUEUES(dlb2->version) * RTE_DIM(qid_stats);
	unsigned int i, port, qid, stat_id = 0;

	dlb2->xstats = rte_zmalloc_socket(NULL,
			sizeof(dlb2->xstats[0]) * count, 0,
			dlb2->qm_instance.info.socket_id);
	if (dlb2->xstats == NULL)
		return -ENOMEM;

#define sname dlb2->xstats[stat_id].name.name
	for (i = 0; i < RTE_DIM(dev_stats); i++, stat_id++) {
		dlb2->xstats[stat_id] = (struct dlb2_xstats_entry) {
			.fn_id = DLB2_XSTATS_FN_DEV,
			.stat = dev_types[i],
			.mode = RTE_EVENT_DEV_XSTATS_DEVICE,
			.reset_allowed = dev_reset_allowed[i],
		};
		snprintf(sname, sizeof(sname), "dev_%s", dev_stats[i]);
	}
	dlb2->xstats_count_mode_dev = stat_id;

	for (port = 0; port < DLB2_MAX_NUM_PORTS(dlb2->version); port++) {
		dlb2->xstats_offset_for_port[port] = stat_id;

		uint32_t count_offset = stat_id;

		for (i = 0; i < RTE_DIM(port_stats); i++, stat_id++) {
			dlb2->xstats[stat_id] = (struct dlb2_xstats_entry){
				.fn_id = DLB2_XSTATS_FN_PORT,
				.obj_idx = port,
				.stat = port_types[i],
				.mode = RTE_EVENT_DEV_XSTATS_PORT,
				.reset_allowed = port_reset_allowed[i],
			};
			snprintf(sname, sizeof(sname), "port_%u_%s",
				 port, port_stats[i]);
		}

		dlb2->xstats_count_per_port[port] = stat_id - count_offset;
	}

	dlb2->xstats_count_mode_port = stat_id - dlb2->xstats_count_mode_dev;

	for (qid = 0; qid < DLB2_MAX_NUM_QUEUES(dlb2->version); qid++) {
		uint32_t count_offset = stat_id;

		dlb2->xstats_offset_for_qid[qid] = stat_id;

		for (i = 0; i < RTE_DIM(qid_stats); i++, stat_id++) {
			dlb2->xstats[stat_id] = (struct dlb2_xstats_entry){
				.fn_id = DLB2_XSTATS_FN_QUEUE,
				.obj_idx = qid,
				.stat = qid_types[i],
				.mode = RTE_EVENT_DEV_XSTATS_QUEUE,
				.reset_allowed = qid_reset_allowed[i],
			};
			snprintf(sname, sizeof(sname), "qid_%u_%s",
				 qid, qid_stats[i]);
		}

		dlb2->xstats_count_per_qid[qid] = stat_id - count_offset;
	}

	dlb2->xstats_count_mode_queue = stat_id -
		(dlb2->xstats_count_mode_dev + dlb2->xstats_count_mode_port);
#undef sname

	dlb2->xstats_count = stat_id;

	return 0;
}

void
dlb2_xstats_uninit(struct dlb2_eventdev *dlb2)
{
	rte_free(dlb2->xstats);
	dlb2->xstats_count = 0;
}

int
dlb2_eventdev_xstats_get_names(const struct rte_eventdev *dev,
		enum rte_event_dev_xstats_mode mode, uint8_t queue_port_id,
		struct rte_event_dev_xstats_name *xstats_names,
		unsigned int *ids, unsigned int size)
{
	const struct dlb2_eventdev *dlb2 = dlb2_pmd_priv(dev);
	unsigned int i;
	unsigned int xidx = 0;
	uint32_t xstats_mode_count = 0;
	uint32_t start_offset = 0;

	switch (mode) {
	case RTE_EVENT_DEV_XSTATS_DEVICE:
		xstats_mode_count = dlb2->xstats_count_mode_dev;
		break;
	case RTE_EVENT_DEV_XSTATS_PORT:
		if (queue_port_id >= DLB2_MAX_NUM_PORTS(dlb2->version))
			break;
		xstats_mode_count = dlb2->xstats_count_per_port[queue_port_id];
		start_offset = dlb2->xstats_offset_for_port[queue_port_id];
		break;
	case RTE_EVENT_DEV_XSTATS_QUEUE:
		if (queue_port_id >= DLB2_MAX_NUM_QUEUES(dlb2->version) &&
		    (DLB2_MAX_NUM_QUEUES(dlb2->version) <= 255))
			break;
		xstats_mode_count = dlb2->xstats_count_per_qid[queue_port_id];
		start_offset = dlb2->xstats_offset_for_qid[queue_port_id];
		break;
	default:
		return -EINVAL;
	};

	if (xstats_mode_count > size || ids == NULL || xstats_names == NULL)
		return xstats_mode_count;

	for (i = 0; i < dlb2->xstats_count && xidx < size; i++) {
		if (dlb2->xstats[i].mode != mode)
			continue;

		if (mode != RTE_EVENT_DEV_XSTATS_DEVICE &&
		    queue_port_id != dlb2->xstats[i].obj_idx)
			continue;

		xstats_names[xidx] = dlb2->xstats[i].name;
		if (ids)
			ids[xidx] = start_offset + xidx;
		xidx++;
	}
	return xidx;
}

static int
dlb2_xstats_update(struct dlb2_eventdev *dlb2,
		enum rte_event_dev_xstats_mode mode,
		uint8_t queue_port_id, const unsigned int ids[],
		uint64_t values[], unsigned int n, const uint32_t reset)
{
	unsigned int i;
	unsigned int xidx = 0;
	uint32_t xstats_mode_count = 0;

	switch (mode) {
	case RTE_EVENT_DEV_XSTATS_DEVICE:
		xstats_mode_count = dlb2->xstats_count_mode_dev;
		break;
	case RTE_EVENT_DEV_XSTATS_PORT:
		if (queue_port_id >= DLB2_MAX_NUM_PORTS(dlb2->version))
			goto invalid_value;
		xstats_mode_count = dlb2->xstats_count_per_port[queue_port_id];
		break;
	case RTE_EVENT_DEV_XSTATS_QUEUE:
#if (DLB2_MAX_NUM_QUEUES(DLB2_HW_V2_5) <= 255) /* max 8 bit value */
		if (queue_port_id >= DLB2_MAX_NUM_QUEUES(dlb2->version))
			goto invalid_value;
#endif
		xstats_mode_count = dlb2->xstats_count_per_qid[queue_port_id];
		break;
	default:
		goto invalid_value;
	};

	for (i = 0; i < n && xidx < xstats_mode_count; i++) {
		struct dlb2_xstats_entry *xs = &dlb2->xstats[ids[i]];
		dlb2_xstats_fn fn;

		if (ids[i] > dlb2->xstats_count || xs->mode != mode)
			continue;

		if (mode != RTE_EVENT_DEV_XSTATS_DEVICE &&
		    queue_port_id != xs->obj_idx)
			continue;

		switch (xs->fn_id) {
		case DLB2_XSTATS_FN_DEV:
			fn = get_dev_stat;
			break;
		case DLB2_XSTATS_FN_PORT:
			fn = get_port_stat;
			break;
		case DLB2_XSTATS_FN_QUEUE:
			fn = get_queue_stat;
			break;
		default:
			DLB2_LOG_ERR("Unexpected xstat fn_id %d\n", xs->fn_id);
			goto invalid_value;
		}

		uint64_t val = fn(dlb2, xs->obj_idx, xs->stat,
				  xs->extra_arg) - xs->reset_value;

		if (values)
			values[xidx] = val;

		if (xs->reset_allowed && reset)
			xs->reset_value += val;

		xidx++;
	}

	return xidx;

invalid_value:
	return -EINVAL;
}

int
dlb2_eventdev_xstats_get(const struct rte_eventdev *dev,
		enum rte_event_dev_xstats_mode mode, uint8_t queue_port_id,
		const unsigned int ids[], uint64_t values[], unsigned int n)
{
	struct dlb2_eventdev *dlb2 = dlb2_pmd_priv(dev);
	const uint32_t reset = 0;

	return dlb2_xstats_update(dlb2, mode, queue_port_id, ids, values, n,
				  reset);
}

uint64_t
dlb2_eventdev_xstats_get_by_name(const struct rte_eventdev *dev,
				 const char *name, unsigned int *id)
{
	struct dlb2_eventdev *dlb2 = dlb2_pmd_priv(dev);
	unsigned int i;
	dlb2_xstats_fn fn;

	for (i = 0; i < dlb2->xstats_count; i++) {
		struct dlb2_xstats_entry *xs = &dlb2->xstats[i];

		if (strncmp(xs->name.name, name,
			    RTE_EVENT_DEV_XSTATS_NAME_SIZE) == 0){
			if (id != NULL)
				*id = i;

			switch (xs->fn_id) {
			case DLB2_XSTATS_FN_DEV:
				fn = get_dev_stat;
				break;
			case DLB2_XSTATS_FN_PORT:
				fn = get_port_stat;
				break;
			case DLB2_XSTATS_FN_QUEUE:
				fn = get_queue_stat;
				break;
			default:
				DLB2_LOG_ERR("Unexpected xstat fn_id %d\n",
					  xs->fn_id);
				return (uint64_t)-1;
			}

			return fn(dlb2, xs->obj_idx, xs->stat,
				  xs->extra_arg) - xs->reset_value;
		}
	}
	if (id != NULL)
		*id = (uint32_t)-1;
	return (uint64_t)-1;
}

static void
dlb2_xstats_reset_range(struct dlb2_eventdev *dlb2, uint32_t start,
			uint32_t num)
{
	uint32_t i;
	dlb2_xstats_fn fn;

	for (i = start; i < start + num; i++) {
		struct dlb2_xstats_entry *xs = &dlb2->xstats[i];

		if (!xs->reset_allowed)
			continue;

		switch (xs->fn_id) {
		case DLB2_XSTATS_FN_DEV:
			fn = get_dev_stat;
			break;
		case DLB2_XSTATS_FN_PORT:
			fn = get_port_stat;
			break;
		case DLB2_XSTATS_FN_QUEUE:
			fn = get_queue_stat;
			break;
		default:
			DLB2_LOG_ERR("Unexpected xstat fn_id %d\n", xs->fn_id);
			return;
		}

		uint64_t val = fn(dlb2, xs->obj_idx, xs->stat, xs->extra_arg);
		xs->reset_value = val;
	}
}

static int
dlb2_xstats_reset_queue(struct dlb2_eventdev *dlb2, uint8_t queue_id,
			const uint32_t ids[], uint32_t nb_ids)
{
	const uint32_t reset = 1;

	if (ids) {
		uint32_t nb_reset = dlb2_xstats_update(dlb2,
					RTE_EVENT_DEV_XSTATS_QUEUE,
					queue_id, ids, NULL, nb_ids,
					reset);
		return nb_reset == nb_ids ? 0 : -EINVAL;
	}

	if (ids == NULL)
		dlb2_xstats_reset_range(dlb2,
			dlb2->xstats_offset_for_qid[queue_id],
			dlb2->xstats_count_per_qid[queue_id]);

	return 0;
}

static int
dlb2_xstats_reset_port(struct dlb2_eventdev *dlb2, uint8_t port_id,
		       const uint32_t ids[], uint32_t nb_ids)
{
	const uint32_t reset = 1;
	int offset = dlb2->xstats_offset_for_port[port_id];
	int nb_stat = dlb2->xstats_count_per_port[port_id];

	if (ids) {
		uint32_t nb_reset = dlb2_xstats_update(dlb2,
					RTE_EVENT_DEV_XSTATS_PORT, port_id,
					ids, NULL, nb_ids,
					reset);
		return nb_reset == nb_ids ? 0 : -EINVAL;
	}

	dlb2_xstats_reset_range(dlb2, offset, nb_stat);
	return 0;
}

static int
dlb2_xstats_reset_dev(struct dlb2_eventdev *dlb2, const uint32_t ids[],
		      uint32_t nb_ids)
{
	uint32_t i;

	if (ids) {
		for (i = 0; i < nb_ids; i++) {
			uint32_t id = ids[i];

			if (id >= dlb2->xstats_count_mode_dev)
				return -EINVAL;
			dlb2_xstats_reset_range(dlb2, id, 1);
		}
	} else {
		for (i = 0; i < dlb2->xstats_count_mode_dev; i++)
			dlb2_xstats_reset_range(dlb2, i, 1);
	}

	return 0;
}

int
dlb2_eventdev_xstats_reset(struct rte_eventdev *dev,
			   enum rte_event_dev_xstats_mode mode,
			   int16_t queue_port_id,
			   const uint32_t ids[],
			   uint32_t nb_ids)
{
	struct dlb2_eventdev *dlb2 = dlb2_pmd_priv(dev);
	uint32_t i;

	/* handle -1 for queue_port_id here, looping over all ports/queues */
	switch (mode) {
	case RTE_EVENT_DEV_XSTATS_DEVICE:
		if (dlb2_xstats_reset_dev(dlb2, ids, nb_ids))
			return -EINVAL;
		break;
	case RTE_EVENT_DEV_XSTATS_PORT:
		if (queue_port_id == -1) {
			for (i = 0;
			     i < DLB2_MAX_NUM_PORTS(dlb2->version); i++) {
				if (dlb2_xstats_reset_port(dlb2, i,
							   ids, nb_ids))
					return -EINVAL;
			}
		} else if (queue_port_id < DLB2_MAX_NUM_PORTS(dlb2->version)) {
			if (dlb2_xstats_reset_port(dlb2, queue_port_id,
						   ids, nb_ids))
				return -EINVAL;
		}
		break;
	case RTE_EVENT_DEV_XSTATS_QUEUE:
		if (queue_port_id == -1) {
			for (i = 0;
			     i < DLB2_MAX_NUM_QUEUES(dlb2->version); i++) {
				if (dlb2_xstats_reset_queue(dlb2, i,
							    ids, nb_ids))
					return -EINVAL;
			}
		} else if (queue_port_id < DLB2_MAX_NUM_QUEUES(dlb2->version)) {
			if (dlb2_xstats_reset_queue(dlb2, queue_port_id,
						    ids, nb_ids))
				return -EINVAL;
		}
		break;
	};

	return 0;
}

void
dlb2_eventdev_dump(struct rte_eventdev *dev, FILE *f)
{
	struct dlb2_eventdev *dlb2;
	struct dlb2_hw_dev *handle;
	int i;

	dlb2 = dlb2_pmd_priv(dev);

	if (dlb2 == NULL) {
		fprintf(f, "DLB2 Event device cannot be dumped!\n");
		return;
	}

	if (!dlb2->configured)
		fprintf(f, "DLB2 Event device is not configured\n");

	handle = &dlb2->qm_instance;

	fprintf(f, "================\n");
	fprintf(f, "DLB2 Device Dump\n");
	fprintf(f, "================\n");

	fprintf(f, "Processor supports umonitor/umwait instructions = %s\n",
		dlb2->umwait_allowed ? "yes" : "no");

	/* Generic top level device information */

	fprintf(f, "device is configured and run state =");
	if (dlb2->run_state == DLB2_RUN_STATE_STOPPED)
		fprintf(f, "STOPPED\n");
	else if (dlb2->run_state == DLB2_RUN_STATE_STOPPING)
		fprintf(f, "STOPPING\n");
	else if (dlb2->run_state == DLB2_RUN_STATE_STARTING)
		fprintf(f, "STARTING\n");
	else if (dlb2->run_state == DLB2_RUN_STATE_STARTED)
		fprintf(f, "STARTED\n");
	else
		fprintf(f, "UNEXPECTED\n");

	fprintf(f, "domain ID=%u, socket_id=%u, evdev=%p\n",
		handle->domain_id, handle->info.socket_id, dlb2->event_dev);

	fprintf(f, "num dir ports=%u, num dir queues=%u\n",
		dlb2->num_dir_ports, dlb2->num_dir_queues);

	fprintf(f, "num ldb ports=%u, num ldb queues=%u\n",
		dlb2->num_ldb_ports, dlb2->num_ldb_queues);

	fprintf(f, "num atomic inflights=%u, hist list entries=%u\n",
		handle->cfg.resources.num_atomic_inflights,
		handle->cfg.resources.num_hist_list_entries);

	fprintf(f, "results from most recent hw resource query:\n");

	fprintf(f, "\tnum_sched_domains = %u\n",
		dlb2->hw_rsrc_query_results.num_sched_domains);

	fprintf(f, "\tnum_ldb_queues = %u\n",
		dlb2->hw_rsrc_query_results.num_ldb_queues);

	fprintf(f, "\tnum_ldb_ports = %u\n",
		dlb2->hw_rsrc_query_results.num_ldb_ports);

	fprintf(f, "\tnum_dir_ports = %u\n",
		dlb2->hw_rsrc_query_results.num_dir_ports);

	fprintf(f, "\tnum_atomic_inflights = %u\n",
		dlb2->hw_rsrc_query_results.num_atomic_inflights);

	fprintf(f, "\tnum_hist_list_entries = %u\n",
		dlb2->hw_rsrc_query_results.num_hist_list_entries);

	fprintf(f, "\tmax_contiguous_hist_list_entries = %u\n",
		dlb2->hw_rsrc_query_results.max_contiguous_hist_list_entries);

	fprintf(f, "\tnum_ldb_credits = %u\n",
		dlb2->hw_rsrc_query_results.num_ldb_credits);

	fprintf(f, "\tnum_dir_credits = %u\n",
		dlb2->hw_rsrc_query_results.num_dir_credits);

	fprintf(f, "\tnum_credits = %u\n",
		dlb2->hw_rsrc_query_results.num_credits);

	/* Port level information */

	for (i = 0; i < dlb2->num_ports; i++) {
		struct dlb2_eventdev_port *p = &dlb2->ev_ports[i];
		int j;

		if (!p->enq_configured)
			fprintf(f, "Port_%d is not configured\n", i);

		fprintf(f, "Port_%d\n", i);
		fprintf(f, "=======\n");

		fprintf(f, "\tevport_%u is configured, setup done=%d\n",
			p->id, p->setup_done);

		fprintf(f, "\tconfig state=%d, port state=%d\n",
			p->qm_port.config_state, p->qm_port.state);

		fprintf(f, "\tport is %s\n",
			p->qm_port.is_directed ? "directed" : "load balanced");

		fprintf(f, "\toutstanding releases=%u\n",
			p->outstanding_releases);

		fprintf(f, "\tinflight max=%u, inflight credits=%u\n",
			p->inflight_max, p->inflight_credits);

		fprintf(f, "\tcredit update quanta=%u, implicit release =%u\n",
			p->credit_update_quanta, p->implicit_release);

		fprintf(f, "\tnum_links=%d, queues -> ", p->num_links);

		for (j = 0; j < DLB2_MAX_NUM_QIDS_PER_LDB_CQ; j++) {
			if (p->link[j].valid)
				fprintf(f, "id=%u prio=%u ",
					p->link[j].queue_id,
					p->link[j].priority);
		}
		fprintf(f, "\n");

		fprintf(f, "\thardware port id=%u\n", p->qm_port.id);

		fprintf(f, "\tcached_ldb_credits=%u\n",
			p->qm_port.cached_ldb_credits);

		fprintf(f, "\tldb_credits = %u\n",
			p->qm_port.ldb_credits);

		fprintf(f, "\tcached_dir_credits = %u\n",
			p->qm_port.cached_dir_credits);

		fprintf(f, "\tdir_credits = %u\n",
			p->qm_port.dir_credits);

		fprintf(f, "\tcached_credits = %u\n",
			p->qm_port.cached_credits);

		fprintf(f, "\tdir_credits = %u\n",
			p->qm_port.credits);

		fprintf(f, "\tgenbit=%d, cq_idx=%d, cq_depth=%d\n",
			p->qm_port.gen_bit,
			p->qm_port.cq_idx,
			p->qm_port.cq_depth);

		fprintf(f, "\tinterrupt armed=%d\n",
			p->qm_port.int_armed);

		fprintf(f, "\tPort statistics\n");

		fprintf(f, "\t\trx_ok %" PRIu64 "\n",
			p->stats.traffic.rx_ok);

		fprintf(f, "\t\trx_drop %" PRIu64 "\n",
			p->stats.traffic.rx_drop);

		fprintf(f, "\t\trx_interrupt_wait %" PRIu64 "\n",
			p->stats.traffic.rx_interrupt_wait);

		fprintf(f, "\t\trx_umonitor_umwait %" PRIu64 "\n",
			p->stats.traffic.rx_umonitor_umwait);

		fprintf(f, "\t\ttx_ok %" PRIu64 "\n",
			p->stats.traffic.tx_ok);

		fprintf(f, "\t\ttotal_polls %" PRIu64 "\n",
			p->stats.traffic.total_polls);

		fprintf(f, "\t\tzero_polls %" PRIu64 "\n",
			p->stats.traffic.zero_polls);

		fprintf(f, "\t\ttx_nospc_ldb_hw_credits %" PRIu64 "\n",
			p->stats.traffic.tx_nospc_ldb_hw_credits);

		fprintf(f, "\t\ttx_nospc_dir_hw_credits %" PRIu64 "\n",
			p->stats.traffic.tx_nospc_dir_hw_credits);

		fprintf(f, "\t\ttx_nospc_hw_credits %" PRIu64 "\n",
			p->stats.traffic.tx_nospc_hw_credits);

		fprintf(f, "\t\ttx_nospc_inflight_max %" PRIu64 "\n",
			p->stats.traffic.tx_nospc_inflight_max);

		fprintf(f, "\t\ttx_nospc_new_event_limit %" PRIu64 "\n",
			p->stats.traffic.tx_nospc_new_event_limit);

		fprintf(f, "\t\ttx_nospc_inflight_credits %" PRIu64 "\n",
			p->stats.traffic.tx_nospc_inflight_credits);

		fprintf(f, "\t\ttx_new %" PRIu64 "\n",
			p->stats.tx_op_cnt[RTE_EVENT_OP_NEW]);

		fprintf(f, "\t\ttx_fwd %" PRIu64 "\n",
			p->stats.tx_op_cnt[RTE_EVENT_OP_FORWARD]);

		fprintf(f, "\t\ttx_rel %" PRIu64 "\n",
			p->stats.tx_op_cnt[RTE_EVENT_OP_RELEASE]);

		fprintf(f, "\t\ttx_implicit_rel %" PRIu64 "\n",
			p->stats.tx_implicit_rel);

		fprintf(f, "\t\ttx_sched_ordered %" PRIu64 "\n",
			p->stats.tx_sched_cnt[DLB2_SCHED_ORDERED]);

		fprintf(f, "\t\ttx_sched_unordered %" PRIu64 "\n",
			p->stats.tx_sched_cnt[DLB2_SCHED_UNORDERED]);

		fprintf(f, "\t\ttx_sched_atomic %" PRIu64 "\n",
			p->stats.tx_sched_cnt[DLB2_SCHED_ATOMIC]);

		fprintf(f, "\t\ttx_sched_directed %" PRIu64 "\n",
			p->stats.tx_sched_cnt[DLB2_SCHED_DIRECTED]);

		fprintf(f, "\t\ttx_invalid %" PRIu64 "\n",
			p->stats.tx_invalid);

		fprintf(f, "\t\trx_sched_ordered %" PRIu64 "\n",
			p->stats.rx_sched_cnt[DLB2_SCHED_ORDERED]);

		fprintf(f, "\t\trx_sched_unordered %" PRIu64 "\n",
			p->stats.rx_sched_cnt[DLB2_SCHED_UNORDERED]);

		fprintf(f, "\t\trx_sched_atomic %" PRIu64 "\n",
			p->stats.rx_sched_cnt[DLB2_SCHED_ATOMIC]);

		fprintf(f, "\t\trx_sched_directed %" PRIu64 "\n",
			p->stats.rx_sched_cnt[DLB2_SCHED_DIRECTED]);

		fprintf(f, "\t\trx_sched_invalid %" PRIu64 "\n",
			p->stats.rx_sched_invalid);
	}

	/* Queue level information */

	for (i = 0; i < dlb2->num_queues; i++) {
		struct dlb2_eventdev_queue *q = &dlb2->ev_queues[i];
		int j, k;

		if (!q->setup_done)
			fprintf(f, "Queue_%d is not configured\n", i);

		fprintf(f, "Queue_%d\n", i);
		fprintf(f, "========\n");

		fprintf(f, "\tevqueue_%u is set up\n", q->id);

		fprintf(f, "\tqueue is %s\n",
			q->qm_queue.is_directed ? "directed" : "load balanced");

		fprintf(f, "\tnum_links=%d, ports -> ", q->num_links);

		for (j = 0; j < dlb2->num_ports; j++) {
			struct dlb2_eventdev_port *p = &dlb2->ev_ports[j];

			for (k = 0; k < DLB2_MAX_NUM_QIDS_PER_LDB_CQ; k++) {
				if (p->link[k].valid &&
				    p->link[k].queue_id == q->id)
					fprintf(f, "id=%u prio=%u ",
						p->id, p->link[k].priority);
			}
		}
		fprintf(f, "\n");

		fprintf(f, "\tcurrent depth: %u events\n",
			dlb2_get_queue_depth(dlb2, q));

		fprintf(f, "\tnum qid inflights=%u, sched_type=%d\n",
			q->qm_queue.num_qid_inflights, q->qm_queue.sched_type);
	}
}
