All of lore.kernel.org
 help / color / mirror / Atom feed
From: Pravin Pathak <pravinpathakdev@gmail.com>
To: dev@dpdk.org
Cc: jerinj@marvell.com, mike.ximing.chen@intel.com,
	bruce.richardson@intel.com, thomas@monjalon.net,
	david.marchand@redhat.com, nipun.gupta@amd.com,
	chenbox@nvidia.com, tirthendu.sarkar@intel.com,
	Pravin Pathak <pravin.pathak.dev@gmail.com>
Subject: [PATCH v1 1/8] event/slb: introduce software load balancer PMD
Date: Fri,  6 Nov 2026 20:30:03 -0500	[thread overview]
Message-ID: <20261107013011.2443138-2-pravinpathakdev@gmail.com> (raw)
In-Reply-To: <20261107013011.2443138-1-pravinpathakdev@gmail.com>

From: Pravin Pathak <pravin.pathak.dev@gmail.com>

Add basic skeleton and initial features for the Software Load Balancer
(SLB) eventdev PMD.

This initial commit provides:
- Virtual device driver registration (event_slb) and device probing.
- Device configuration and capability reporting for parallel queues,
  burst mode, multi-queue ports, and runtime port linking.
- Parallel queue support (multi-producer/multi-consumer rings) and
  single-link queue support (multi-producer/single-consumer rings).
- Event port configuration, linking/unlinking, and multi-priority
  queue scheduling.
- Central in-flight event credit management with batched loans
  (SLB_MIN_CREDIT_LOAN = 512).
- High-performance event enqueue and dequeue burst processing.
- Meson build system integration and MAINTAINERS entry.

Signed-off-by: Pravin Pathak <pravin.pathak.dev@gmail.com>
---
 MAINTAINERS                   |   4 +
 drivers/event/meson.build     |   1 +
 drivers/event/slb/meson.build |  15 +
 drivers/event/slb/slb_evdev.c | 585 ++++++++++++++++++++++++++++++++++
 drivers/event/slb/slb_evdev.h | 176 ++++++++++
 drivers/event/slb/slb_event.c | 279 ++++++++++++++++
 6 files changed, 1060 insertions(+)
 create mode 100644 drivers/event/slb/meson.build
 create mode 100644 drivers/event/slb/slb_evdev.c
 create mode 100644 drivers/event/slb/slb_evdev.h
 create mode 100644 drivers/event/slb/slb_event.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 93ad53027d..89ea53695a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1533,6 +1533,10 @@ M: Mattias Rönnblom <mattias.ronnblom@ericsson.com>
 F: drivers/event/dsw/
 F: doc/guides/eventdevs/dsw.rst
 
+Software Load Balancer (SLB) Eventdev PMD
+M: Pravin Pathak <pravin.pathak.dev@gmail.com>
+F: drivers/event/slb/
+
 Software OPDL Eventdev PMD
 M: Liang Ma <liangma@liangbit.com>
 M: Peter Mccarthy <peter.mccarthy@intel.com>
diff --git a/drivers/event/meson.build b/drivers/event/meson.build
index ddaa0300ac..ece490dfb5 100644
--- a/drivers/event/meson.build
+++ b/drivers/event/meson.build
@@ -15,5 +15,6 @@ drivers = [
         'opdl',
         'skeleton',
         'sw',
+        'slb',
 ]
 std_deps = ['eventdev', 'kvargs']
diff --git a/drivers/event/slb/meson.build b/drivers/event/slb/meson.build
new file mode 100644
index 0000000000..9a1dff524f
--- /dev/null
+++ b/drivers/event/slb/meson.build
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2026 Pravin Pathak <pravin.pathak.dev@gmail.com>
+
+if is_windows
+    build = false
+    reason = 'not supported on Windows'
+    subdir_done()
+endif
+
+sources = files(
+        'slb_evdev.c',
+        'slb_event.c',
+)
+
+deps += ['bus_vdev', 'ring']
diff --git a/drivers/event/slb/slb_evdev.c b/drivers/event/slb/slb_evdev.c
new file mode 100644
index 0000000000..53af113293
--- /dev/null
+++ b/drivers/event/slb/slb_evdev.c
@@ -0,0 +1,585 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Pravin Pathak <pravin.pathak.dev@gmail.com>
+ */
+
+#include <stdbool.h>
+#include <inttypes.h>
+#include <rte_bitops.h>
+#include <rte_cycles.h>
+#include <rte_malloc.h>
+#include <rte_ring_elem.h>
+#include <rte_errno.h>
+#include <rte_stdatomic.h>
+#include <eventdev_pmd.h>
+#include <eventdev_pmd_vdev.h>
+
+#include "slb_evdev.h"
+
+#define EVENTDEV_NAME_SLB_PMD event_slb
+
+/* Setup Eventdev port. Reset all mapped queues */
+static int
+slb_port_setup(struct rte_eventdev *dev, uint8_t port_id,
+	       const struct rte_event_port_conf *conf)
+{
+	struct slb_evdev *slb = slb_pmd_priv(dev);
+	struct slb_port *port;
+	bool implicit_release;
+	uint16_t deq_depth;
+	uint16_t enq_depth;
+
+	port = &slb->ports[port_id];
+
+	implicit_release =
+	    !(conf->event_port_cfg & RTE_EVENT_PORT_CFG_DISABLE_IMPL_REL);
+
+	deq_depth = conf->dequeue_depth;
+	if (deq_depth == 0 || deq_depth > SLB_MAX_PORT_DEQUEUE_DEPTH)
+		deq_depth = SLB_MAX_PORT_DEQUEUE_DEPTH;
+
+	enq_depth = conf->enqueue_depth;
+	if (enq_depth == 0 || enq_depth > SLB_MAX_PORT_ENQUEUE_DEPTH)
+		enq_depth = SLB_MAX_PORT_ENQUEUE_DEPTH;
+
+	*port = (struct slb_port) {
+		.id = port_id,
+		.slb = slb,
+		.dequeue_depth = deq_depth,
+		.enqueue_depth = enq_depth,
+		.new_event_threshold = conf->new_event_threshold,
+		.implicit_release = implicit_release,
+		.is_single_linked = conf->event_port_cfg &
+				   RTE_EVENT_PORT_CFG_SINGLE_LINK,
+		.inflight_credits = 0,
+		.credits_drained = false,
+		.total_dequeues = 0,
+		.empty_dequeues = 0,
+		.single_linked_q = NULL,
+		.single_linked_ring = NULL,
+	};
+
+	port->remap_input_q = false;
+	rte_atomic_store_explicit(&port->active_queues, 0,
+				  rte_memory_order_relaxed);
+
+	dev->data->ports[port_id] = port;
+
+	/* Reset all queues mapped to this port */
+	for (int p = 0; p < SLB_MAX_PRIORITIES; p++) {
+		port->next_q[p] = 0;
+		port->num_q[p] = 0;
+		memset(port->q_entries[p], 0, sizeof(port->q_entries[p]));
+	}
+
+	return 0;
+}
+
+/* Default Eventdev port configuration for SLB */
+static void
+slb_port_def_conf(struct rte_eventdev *dev __rte_unused,
+		  uint8_t port_id __rte_unused,
+		  struct rte_event_port_conf *port_conf)
+{
+	*port_conf = (struct rte_event_port_conf) {
+		.new_event_threshold = 1024,
+		.dequeue_depth = SLB_MAX_PORT_DEQUEUE_DEPTH / 4,
+		.enqueue_depth = SLB_MAX_PORT_ENQUEUE_DEPTH / 4
+	};
+}
+
+static void
+slb_port_release(void *p __rte_unused)
+{
+}
+
+/* Called to free queue resources on device close or reconfiguration */
+static void
+slb_queue_release(struct rte_eventdev *dev, uint8_t queue_id)
+{
+	struct slb_evdev *slb = slb_pmd_priv(dev);
+	struct slb_queue *queue = &slb->queues[queue_id];
+
+	if (queue->q_ring) {
+		rte_event_ring_free(queue->q_ring);
+		queue->q_ring = NULL;
+	}
+}
+
+/* Setup Eventdev queue. Eventdev queues are implemented as DPDK rings */
+static int
+slb_queue_setup(struct rte_eventdev *dev, uint8_t queue_id,
+		const struct rte_event_queue_conf *conf)
+{
+	struct slb_evdev *slb = slb_pmd_priv(dev);
+	struct slb_queue *queue = &slb->queues[queue_id];
+	struct rte_event_ring *ring;
+	char ring_name[RTE_RING_NAMESIZE];
+	uint32_t q_size = (slb->max_inflight > 0) ?
+			  (uint32_t)slb->max_inflight : SLB_IN_RING_SIZE;
+
+	/* Release existing queue resources if reconfiguring */
+	slb_queue_release(dev, queue_id);
+
+	snprintf(ring_name, sizeof(ring_name), "slb%d_q%u", dev->data->dev_id,
+		 queue_id);
+
+	if (conf->event_queue_cfg & RTE_EVENT_QUEUE_CFG_SINGLE_LINK) {
+		/* MP -> SC ring for single-link queues */
+		queue->is_single_link = true;
+		ring = rte_event_ring_create(ring_name, q_size,
+			dev->data->socket_id, RING_F_SC_DEQ | RING_F_EXACT_SZ);
+	} else if (conf->schedule_type == RTE_SCHED_TYPE_PARALLEL) {
+		/* MP -> MC ring for parallel queues */
+		queue->is_single_link = false;
+		ring = rte_event_ring_create(ring_name, q_size,
+			dev->data->socket_id, RING_F_EXACT_SZ);
+	} else {
+		SLB_LOG_DP_LINE(ERR, "Unsupported schedule_type %u",
+				conf->schedule_type);
+		return -ENOTSUP;
+	}
+
+	if (ring == NULL)
+		return -ENOMEM;
+
+	queue->q_ring = ring;
+	queue->schedule_type = conf->schedule_type;
+	queue->serving_ports_mask = 0;
+	queue->num_serving_ports = 0;
+
+	return 0;
+}
+
+/* Settings for default Eventdev queue configuration */
+static void
+slb_queue_def_conf(struct rte_eventdev *dev __rte_unused,
+		   uint8_t queue_id __rte_unused,
+		   struct rte_event_queue_conf *queue_conf)
+{
+	*queue_conf = (struct rte_event_queue_conf) {
+		.nb_atomic_flows = 4096,
+		.schedule_type = RTE_SCHED_TYPE_PARALLEL,
+		.priority = RTE_EVENT_DEV_PRIORITY_NORMAL
+	};
+}
+
+static void
+slb_port_unlink_queue_from_prio(struct slb_port *port, int prio, int slot)
+{
+	for (int k = slot; k < port->num_q[prio] - 1; k++)
+		port->q_entries[prio][k] = port->q_entries[prio][k + 1];
+
+	port->num_q[prio]--;
+	memset(&port->q_entries[prio][port->num_q[prio]], 0,
+	       sizeof(struct slb_port_q_entry));
+	if (port->next_q[prio] >= port->num_q[prio])
+		port->next_q[prio] = 0;
+}
+
+static int
+slb_port_find_queue_slot(struct slb_port *port, int prio, uint8_t qid)
+{
+	for (int s = 0; s < port->num_q[prio]; s++) {
+		if (port->q_entries[prio][s].queue_id == qid)
+			return s;
+	}
+	return -1;
+}
+
+static int
+slb_port_link_one(struct slb_port *port, struct slb_queue *queue,
+		  uint8_t qid, uint8_t app_prio, bool has_prio)
+{
+	int prio;
+	bool found = false;
+
+	if (port->is_single_linked) {
+		if (port->single_linked_q != NULL &&
+		    port->single_linked_q != queue) {
+			rte_errno = EINVAL;
+			return -EINVAL;
+		}
+		port->single_linked_q = queue;
+		port->single_linked_ring = queue->q_ring;
+	} else {
+		if (has_prio)
+			prio = (app_prio < RTE_EVENT_DEV_PRIORITY_NORMAL) ? 0 : 1;
+		else
+			prio = SLB_MAX_PRIORITIES - 1;
+
+		for (int pr = 0; pr < SLB_MAX_PRIORITIES; pr++) {
+			int s = slb_port_find_queue_slot(port, pr, qid);
+
+			if (s >= 0) {
+				if (pr == prio)
+					found = true;
+				else
+					slb_port_unlink_queue_from_prio(port, pr, s);
+				break;
+			}
+		}
+		if (!found) {
+			uint16_t slot = port->num_q[prio];
+
+			if (slot >= SLB_PORT_MAX_Q_MAP_PER_PRIO) {
+				rte_errno = ENOSPC;
+				return -ENOSPC;
+			}
+			port->q_entries[prio][slot].dq_ring = queue->q_ring;
+			port->q_entries[prio][slot].queue = queue;
+			port->q_entries[prio][slot].queue_id = qid;
+			port->q_entries[prio][slot].schedule_type =
+				queue->schedule_type;
+			port->num_q[prio]++;
+		}
+
+		rte_atomic_fetch_or_explicit(&port->active_queues,
+					     (1ULL << qid),
+					     rte_memory_order_release);
+	}
+
+	queue->serving_ports_mask |= (1ULL << port->id);
+	queue->num_serving_ports = rte_popcount64(queue->serving_ports_mask);
+	return 0;
+}
+
+static int
+slb_port_unlink_one(struct slb_port *port, struct slb_queue *queue, uint8_t qid)
+{
+	bool found = false;
+
+	if (port->is_single_linked) {
+		if (port->single_linked_q != queue) {
+			rte_errno = EINVAL;
+			return -EINVAL;
+		}
+		port->single_linked_q = NULL;
+		port->single_linked_ring = NULL;
+	} else {
+		for (int prio = 0; prio < SLB_MAX_PRIORITIES; prio++) {
+			int s = slb_port_find_queue_slot(port, prio, qid);
+
+			if (s >= 0) {
+				slb_port_unlink_queue_from_prio(port, prio, s);
+				found = true;
+				break;
+			}
+		}
+		if (!found) {
+			rte_errno = EINVAL;
+			return -EINVAL;
+		}
+		rte_atomic_fetch_and_explicit(&port->active_queues,
+					      ~(1ULL << qid),
+					      rte_memory_order_release);
+		port->remap_input_q = true;
+	}
+
+	queue->serving_ports_mask &= ~(1ULL << port->id);
+	queue->num_serving_ports = rte_popcount64(queue->serving_ports_mask);
+	return 0;
+}
+
+static int
+slb_port_link(struct rte_eventdev *dev, void *p, const uint8_t queues[],
+	      const uint8_t priorities[], uint16_t num)
+{
+	struct slb_port *port = p;
+	struct slb_evdev *slb = slb_pmd_priv(dev);
+	uint16_t i;
+
+	for (i = 0; i < num; i++) {
+		uint8_t qid = queues[i];
+		struct slb_queue *queue = &slb->queues[qid];
+		uint8_t prio = (priorities != NULL) ? priorities[i] : 0;
+		int ret = slb_port_link_one(port, queue, qid, prio,
+					    priorities != NULL);
+
+		if (ret < 0)
+			return i;
+	}
+	return num;
+}
+
+static int
+slb_port_unlink(struct rte_eventdev *dev, void *p, uint8_t queues[],
+		uint16_t num)
+{
+	struct slb_port *port = p;
+	struct slb_evdev *slb = slb_pmd_priv(dev);
+	uint16_t i;
+
+	for (i = 0; i < num; i++) {
+		uint8_t qid = queues[i];
+		struct slb_queue *queue = &slb->queues[qid];
+		int ret = slb_port_unlink_one(port, queue, qid);
+
+		if (ret < 0)
+			return i;
+	}
+	return num;
+}
+
+static void
+slb_info_get(struct rte_eventdev *dev __rte_unused,
+	     struct rte_event_dev_info *info)
+{
+	*info = (struct rte_event_dev_info) {
+		.driver_name = SLB_PMD_NAME,
+		.max_event_queues = SLB_MAX_QUEUES,
+		.max_event_queue_flows = SLB_MAX_FLOWS,
+		.max_event_queue_priority_levels = SLB_MAX_PRIORITIES,
+		.max_event_priority_levels = SLB_MAX_PRIORITIES,
+		.max_event_ports = SLB_MAX_PORTS,
+		.max_event_port_dequeue_depth = SLB_MAX_PORT_DEQUEUE_DEPTH,
+		.max_event_port_enqueue_depth = SLB_MAX_PORT_ENQUEUE_DEPTH,
+		.max_num_events = SLB_MAX_EVENTS,
+		.max_profiles_per_port = 1,
+		.event_dev_cap = RTE_EVENT_DEV_CAP_BURST_MODE |
+		RTE_EVENT_DEV_CAP_PARALLEL |
+		RTE_EVENT_DEV_CAP_DISTRIBUTED_SCHED |
+		RTE_EVENT_DEV_CAP_IMPLICIT_RELEASE_DISABLE |
+		RTE_EVENT_DEV_CAP_NONSEQ_MODE |
+		RTE_EVENT_DEV_CAP_MULTIPLE_QUEUE_PORT |
+		RTE_EVENT_DEV_CAP_RUNTIME_PORT_LINK |
+		RTE_EVENT_DEV_CAP_CARRY_FLOW_ID |
+		RTE_EVENT_DEV_CAP_MAINTENANCE_FREE |
+		RTE_EVENT_DEV_CAP_INDEPENDENT_ENQ
+	};
+}
+
+static int
+slb_configure(const struct rte_eventdev *dev)
+{
+	struct slb_evdev *slb = slb_pmd_priv(dev);
+	const struct rte_event_dev_config *conf = &dev->data->dev_conf;
+	int32_t min_max_in_flight;
+	uint32_t q_size;
+
+	if (conf->nb_events_limit <= 0 ||
+	    conf->nb_events_limit > SLB_MAX_EVENTS) {
+		SLB_LOG_DP_LINE(INFO,
+			"nb_events_limit (%d) invalid, max is %d",
+			conf->nb_events_limit, SLB_MAX_EVENTS);
+		return -EINVAL;
+	}
+
+	slb->num_ports = conf->nb_event_ports;
+	slb->num_queues = conf->nb_event_queues;
+
+	min_max_in_flight = conf->nb_event_ports * SLB_PORT_MAX_CREDITS;
+	if (min_max_in_flight > SLB_MAX_EVENTS)
+		min_max_in_flight = SLB_MAX_EVENTS;
+
+	q_size = rte_align32pow2((uint32_t)RTE_MAX(conf->nb_events_limit,
+						   min_max_in_flight));
+	if (q_size > SLB_MAX_EVENTS)
+		q_size = SLB_MAX_EVENTS;
+
+	slb->max_inflight = (int32_t)q_size;
+	return 0;
+}
+
+static int
+slb_start(struct rte_eventdev *dev)
+{
+	struct slb_evdev *slb = slb_pmd_priv(dev);
+
+	for (int i = 0; i < slb->num_queues; i++) {
+		if (slb->queues[i].serving_ports_mask == 0)
+			SLB_LOG_DP_LINE(INFO,
+				"Queue %d has no linked ports on start", i);
+	}
+
+	rte_atomic_store_explicit(&slb->credits_on_loan, 0,
+				  rte_memory_order_relaxed);
+
+	for (int i = 0; i < slb->num_ports; i++) {
+		struct slb_port *port = &slb->ports[i];
+		port->inflight_credits = 0;
+		port->credits_drained = false;
+		for (int p = 0; p < SLB_MAX_PRIORITIES; p++)
+			port->next_q[p] = 0;
+	}
+
+	return 0;
+}
+
+static void
+slb_drain_ring(uint8_t dev_id, struct rte_event_ring *ring,
+	       eventdev_stop_flush_t flush, void *flush_arg)
+{
+	struct rte_event ev;
+
+	if (ring == NULL)
+		return;
+
+	while (rte_event_ring_dequeue_burst(ring, &ev, 1, NULL)) {
+		if (flush != NULL)
+			flush(dev_id, ev, flush_arg);
+	}
+	rte_ring_reset(&ring->r);
+}
+
+static void
+slb_stop(struct rte_eventdev *dev)
+{
+	struct slb_evdev *slb = slb_pmd_priv(dev);
+	uint8_t dev_id = dev->data->dev_id;
+	eventdev_stop_flush_t flush = dev->dev_ops->dev_stop_flush;
+	void *flush_arg = dev->data->dev_stop_flush_arg;
+
+	for (uint8_t qid = 0; qid < slb->num_queues; qid++) {
+		struct slb_queue *q = &slb->queues[qid];
+		if (q->q_ring != NULL)
+			slb_drain_ring(dev_id, q->q_ring, flush, flush_arg);
+	}
+
+	for (int i = 0; i < slb->num_ports; i++) {
+		struct slb_port *port = &slb->ports[i];
+		port->inflight_credits = 0;
+		port->credits_drained = false;
+		for (int p = 0; p < SLB_MAX_PRIORITIES; p++)
+			port->next_q[p] = 0;
+	}
+
+	rte_atomic_store_explicit(&slb->credits_on_loan, 0,
+				  rte_memory_order_relaxed);
+}
+
+static int
+slb_close(struct rte_eventdev *dev)
+{
+	struct slb_evdev *slb = slb_pmd_priv(dev);
+
+	for (int i = 0; i < slb->num_queues; i++)
+		slb_queue_release(dev, i);
+
+	return 0;
+}
+
+void
+slb_dump(struct rte_eventdev *dev, FILE *f)
+{
+	struct slb_evdev *slb = slb_pmd_priv(dev);
+	int32_t pool_on_loan, pool_avail;
+
+	if (dev == NULL || f == NULL)
+		return;
+
+	pool_on_loan = rte_atomic_load_explicit(&slb->credits_on_loan,
+						rte_memory_order_relaxed);
+	pool_avail = slb->max_inflight - pool_on_loan;
+
+	fprintf(f, "\n[SLB EVENTDEV DEBUG STATS]\n");
+	fprintf(f, "  Device ID: %u, Driver: %s\n", dev->data->dev_id,
+		SLB_PMD_NAME);
+	fprintf(f, "  Credit Pool: %d on_loan / %d max_inflight (%d avail)\n",
+		pool_on_loan, slb->max_inflight, pool_avail);
+
+	fprintf(f, "\n  PORT STATISTICS & QUEUE LINKAGES:\n");
+	for (int i = 0; i < slb->num_ports; i++) {
+		struct slb_port *p = &slb->ports[i];
+		uint64_t aq = rte_atomic_load_explicit(&p->active_queues,
+						       rte_memory_order_acquire);
+		fprintf(f, "    Port %2u (credits=%3d, active=0x%08" PRIx64 "): ",
+			i, p->inflight_credits, aq);
+		int linked_count = 0;
+		if (p->is_single_linked) {
+			if (p->single_linked_q) {
+				int qid = p->single_linked_q - slb->queues;
+				fprintf(f, "Q%d (single-link)", qid);
+				linked_count++;
+			}
+		} else {
+			for (int pr = 0; pr < SLB_MAX_PRIORITIES; pr++) {
+				for (int s = 0; s < p->num_q[pr]; s++) {
+					int qid = p->q_entries[pr][s].queue_id;
+					if (linked_count > 0)
+						fprintf(f, ", ");
+					fprintf(f, "Q%d(p%d)", qid, pr);
+					linked_count++;
+				}
+			}
+		}
+		if (linked_count == 0)
+			fprintf(f, "(no queues linked)");
+		fprintf(f, "\n");
+	}
+
+	fprintf(f, "\n  ALL QUEUES STATUS:\n");
+	for (int i = 0; i < slb->num_queues; i++) {
+		struct slb_queue *q = &slb->queues[i];
+		uint32_t count = rte_event_ring_count(q->q_ring);
+		uint32_t ring_size = rte_event_ring_get_capacity(q->q_ring);
+		fprintf(f, "    Queue %2u: %6u/%6u events, serving_ports=0x%016" PRIx64 ", sched=%u, single=%d\n",
+			i, count, ring_size, q->serving_ports_mask,
+			q->schedule_type, q->is_single_link);
+	}
+	fprintf(f, "[END DEBUG]\n\n");
+	fflush(f);
+}
+
+static struct eventdev_ops slb_evdev_ops = {
+	.port_setup = slb_port_setup,
+	.port_def_conf = slb_port_def_conf,
+	.port_release = slb_port_release,
+	.queue_setup = slb_queue_setup,
+	.queue_def_conf = slb_queue_def_conf,
+	.queue_release = slb_queue_release,
+	.port_link = slb_port_link,
+	.port_unlink = slb_port_unlink,
+	.dev_infos_get = slb_info_get,
+	.dev_configure = slb_configure,
+	.dev_start = slb_start,
+	.dev_stop = slb_stop,
+	.dev_close = slb_close,
+	.dump = slb_dump,
+};
+
+static int
+slb_probe(struct rte_vdev_device *vdev)
+{
+	const char *name = rte_vdev_device_name(vdev);
+	struct rte_eventdev *dev;
+	struct slb_evdev *slb;
+
+	dev = rte_event_pmd_vdev_init(name, sizeof(struct slb_evdev),
+				      rte_socket_id(), vdev);
+	if (dev == NULL)
+		return -EFAULT;
+
+	dev->dev_ops = &slb_evdev_ops;
+	dev->enqueue_burst = slb_event_enqueue_burst;
+	dev->enqueue_new_burst = slb_event_enqueue_new_burst;
+	dev->enqueue_forward_burst = slb_event_enqueue_forward_burst;
+	dev->dequeue_burst = slb_event_dequeue_burst;
+
+	if (rte_eal_process_type() != RTE_PROC_PRIMARY)
+		return 0;
+
+	slb = dev->data->dev_private;
+	slb->data = dev->data;
+
+	event_dev_probing_finish(dev);
+	return 0;
+}
+
+static int
+slb_remove(struct rte_vdev_device *vdev)
+{
+	const char *name = rte_vdev_device_name(vdev);
+
+	if (name == NULL)
+		return -EINVAL;
+
+	return rte_event_pmd_vdev_uninit(name);
+}
+
+static struct rte_vdev_driver evdev_slb_pmd_drv = {
+	.probe = slb_probe,
+	.remove = slb_remove
+};
+
+RTE_PMD_REGISTER_VDEV(EVENTDEV_NAME_SLB_PMD, evdev_slb_pmd_drv);
+RTE_LOG_REGISTER_DEFAULT(event_slb_logtype, NOTICE);
diff --git a/drivers/event/slb/slb_evdev.h b/drivers/event/slb/slb_evdev.h
new file mode 100644
index 0000000000..d7be6746ef
--- /dev/null
+++ b/drivers/event/slb/slb_evdev.h
@@ -0,0 +1,176 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Pravin Pathak <pravin.pathak.dev@gmail.com>
+ */
+
+#ifndef _SLB_EVDEV_H_
+#define _SLB_EVDEV_H_
+
+#include <rte_bitops.h>
+#include <rte_bitset.h>
+#include <rte_event_ring.h>
+#include <rte_eventdev.h>
+#include <rte_stdatomic.h>
+#include <eventdev_pmd.h>
+
+#define SLB_PMD_NAME RTE_STR(event_slb)
+
+#define SLB_MAX_PORTS (64)
+#define SLB_MAX_PORT_DEQUEUE_DEPTH (128)
+#define SLB_MAX_PORT_ENQUEUE_DEPTH (128)
+
+#define SLB_MAX_QUEUES (64)
+
+#define SLB_MAX_EVENTS (16384)
+
+#define SLB_MAX_FLOWS_BITS (16)
+#define SLB_MAX_FLOWS (1 << (SLB_MAX_FLOWS_BITS))
+#define SLB_MAX_FLOWS_MASK (SLB_MAX_FLOWS - 1)
+
+/* Avoid making small 'loans' from the central in-flight event credit pool */
+#define SLB_MIN_CREDIT_LOAN (512)
+#define SLB_PORT_MAX_CREDITS (2 * SLB_MIN_CREDIT_LOAN)
+#define SLB_PORT_MIN_CREDITS (SLB_MIN_CREDIT_LOAN)
+
+#define SLB_IN_RING_SIZE (SLB_MAX_EVENTS)
+
+/* Maximum number of queues that can be linked to a port per priority */
+#define SLB_PORT_MAX_Q_MAP_PER_PRIO (SLB_MAX_QUEUES)
+/* Number of priority levels supported by SLB. Configure 1..4. */
+#ifndef SLB_MAX_PRIORITIES
+#define SLB_MAX_PRIORITIES 2
+#endif
+
+#if (SLB_MAX_PRIORITIES < 1) || (SLB_MAX_PRIORITIES > 4)
+#error "SLB_MAX_PRIORITIES must be between 1 and 4"
+#endif
+
+/* Total max mapping slots (per-prio * priorities) */
+#define SLB_PORT_MAX_Q_MAP (SLB_PORT_MAX_Q_MAP_PER_PRIO * SLB_MAX_PRIORITIES)
+
+struct slb_queue;
+
+/* Pre-resolved queue dequeue entry for core-local port access */
+struct slb_port_q_entry {
+	struct rte_event_ring *dq_ring;
+	struct slb_queue *queue;
+	uint8_t queue_id;
+	uint8_t schedule_type;
+	uint8_t pad[6];
+};
+
+struct __rte_cache_aligned slb_port {
+	/* --- Cacheline 0: Hot Rx & Tx scheduling control (64 bytes) --- */
+	uint16_t id;
+	int32_t inflight_credits;
+	int32_t new_event_threshold;
+	RTE_ATOMIC(uint64_t) active_queues;
+	uint16_t num_q[SLB_MAX_PRIORITIES];
+	uint16_t next_q[SLB_MAX_PRIORITIES];
+	bool is_single_linked;
+	bool implicit_release;
+	bool credits_drained;
+	bool remap_input_q;
+	uint8_t pad_port[4];
+	struct rte_event_ring *single_linked_ring;
+	struct slb_evdev *slb;
+
+	/* --- Cachelines 1..6: Pre-resolved queue mappings for dequeue --- */
+	alignas(RTE_CACHE_LINE_SIZE)
+	struct slb_port_q_entry q_entries[SLB_MAX_PRIORITIES][SLB_PORT_MAX_Q_MAP_PER_PRIO];
+
+	/* --- Cold Section: Statistics counters and config --- */
+	alignas(RTE_CACHE_LINE_SIZE)
+	uint16_t dequeue_depth;
+	uint16_t enqueue_depth;
+	struct slb_queue *single_linked_q;
+	uint64_t enqueue_calls;
+	uint64_t new_enqueued;
+	uint64_t forward_enqueued;
+	uint64_t release_enqueued;
+	uint64_t dequeue_calls;
+	uint64_t dequeued;
+	uint64_t empty_dequeues;
+	uint64_t total_dequeues;
+	uint64_t queue_enqueued[SLB_MAX_QUEUES];
+	uint64_t queue_dequeued[SLB_MAX_QUEUES];
+};
+
+struct slb_queue {
+	uint8_t schedule_type;
+	bool is_single_link;
+	uint16_t num_serving_ports;
+	uint64_t serving_ports_mask;
+	alignas(RTE_CACHE_LINE_SIZE) struct rte_event_ring *q_ring;
+};
+
+/* Limited by the size of 'serving_ports_mask' and 'active_queues' bitmasks */
+static_assert(SLB_MAX_PORTS <= 64, "Max compile-time port count exceeded");
+static_assert(SLB_MAX_QUEUES <= 64, "Max compile-time queue count exceeded");
+
+struct slb_evdev {
+	struct rte_eventdev_data *data;
+
+	struct slb_port ports[SLB_MAX_PORTS];
+	uint16_t num_ports;
+	struct slb_queue queues[SLB_MAX_QUEUES];
+	uint8_t num_queues;
+	int32_t max_inflight;
+
+	alignas(RTE_CACHE_LINE_SIZE) RTE_ATOMIC(int32_t) credits_on_loan;
+};
+
+uint16_t slb_event_enqueue_burst(void *port,
+				 const struct rte_event events[],
+				 uint16_t events_len);
+uint16_t slb_event_enqueue_new_burst(void *port,
+				     const struct rte_event events[],
+				     uint16_t events_len);
+uint16_t slb_event_enqueue_forward_burst(void *port,
+					 const struct rte_event events[],
+					 uint16_t events_len);
+
+uint16_t slb_event_dequeue_burst(void *port, struct rte_event *events,
+				 uint16_t num, uint64_t wait);
+
+void slb_port_return_credits(struct slb_evdev *slb, struct slb_port *port,
+			     int32_t credits);
+
+void slb_dump(struct rte_eventdev *dev, FILE *f);
+
+static inline struct slb_evdev *
+slb_pmd_priv(const struct rte_eventdev *eventdev)
+{
+	return eventdev->data->dev_private;
+}
+
+extern int event_slb_logtype;
+#define RTE_LOGTYPE_EVENT_SLB event_slb_logtype
+#define SLB_LOG_DP_LINE(level, fmt, ...)				\
+	RTE_LOG_DP_LINE(level, EVENT_SLB, "%s() line %u: " fmt,		\
+		   __func__, __LINE__, ## __VA_ARGS__)
+
+#define SLB_LOG_DP_PORT_LINE(level, port_id, fmt, ...)			\
+	SLB_LOG_DP_LINE(level, "<Port %d> " fmt, port_id, ## __VA_ARGS__)
+
+/* Obtain serving port masks for the specified queue and set corresponding
+ * active queue bits for all served ports.
+ */
+static inline void
+slb_port_wakeup_queues(struct slb_evdev *slb, struct slb_queue *queue)
+{
+	uint8_t queue_id = queue - slb->queues;
+	uint64_t port_mask = queue->serving_ports_mask;
+	struct slb_port *port;
+	uint8_t port_id;
+
+	while (port_mask) {
+		port_id = rte_ctz64(port_mask);
+		port = &slb->ports[port_id];
+		rte_atomic_fetch_or_explicit(&port->active_queues,
+					     (1ULL << queue_id),
+					     rte_memory_order_release);
+		port_mask &= ~(1ULL << port_id);
+	}
+}
+
+#endif /* _SLB_EVDEV_H_ */
diff --git a/drivers/event/slb/slb_event.c b/drivers/event/slb/slb_event.c
new file mode 100644
index 0000000000..f55262154e
--- /dev/null
+++ b/drivers/event/slb/slb_event.c
@@ -0,0 +1,279 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Pravin Pathak <pravin.pathak.dev@gmail.com>
+ */
+
+#include <stdbool.h>
+#include <rte_cycles.h>
+#include <rte_ring_elem.h>
+#include <rte_stdatomic.h>
+
+#include "slb_evdev.h"
+
+/* Acquire credits for new events */
+static bool
+slb_port_acquire_credits(struct slb_evdev *slb, struct slb_port *port,
+			 int32_t credits)
+{
+	int32_t inflight_credits = port->inflight_credits;
+	int32_t remaining_credits = inflight_credits - credits;
+	int32_t total_inflight;
+	int32_t available;
+	int32_t required_credits;
+	int32_t new_total_inflight;
+
+	/* If port has enough credits, update and return true */
+	if (likely(remaining_credits >= 0)) {
+		port->inflight_credits = remaining_credits;
+		return true;
+	}
+
+	total_inflight = rte_atomic_load_explicit(&slb->credits_on_loan,
+						  rte_memory_order_relaxed);
+	available = slb->max_inflight - total_inflight;
+	required_credits = RTE_MAX(abs(remaining_credits), SLB_PORT_MIN_CREDITS);
+
+	if (available < required_credits)
+		return false;
+
+	new_total_inflight =
+		rte_atomic_fetch_add_explicit(&slb->credits_on_loan,
+					      required_credits,
+					      rte_memory_order_relaxed) +
+					      required_credits;
+
+	if (unlikely(new_total_inflight > slb->max_inflight)) {
+		rte_atomic_fetch_sub_explicit(&slb->credits_on_loan,
+					      required_credits,
+					      rte_memory_order_relaxed);
+		return false;
+	}
+
+	port->inflight_credits += required_credits;
+	port->inflight_credits -= credits;
+
+	return true;
+}
+
+/* Return accumulated credits (Terminated events) */
+void
+slb_port_return_credits(struct slb_evdev *slb, struct slb_port *port,
+			int32_t credits)
+{
+	port->inflight_credits += credits;
+
+	if (unlikely(port->inflight_credits >= SLB_PORT_MAX_CREDITS)) {
+		int32_t return_credits = port->inflight_credits -
+					 SLB_PORT_MIN_CREDITS;
+		port->inflight_credits = SLB_PORT_MIN_CREDITS;
+
+		rte_atomic_fetch_sub_explicit(&slb->credits_on_loan,
+					      return_credits,
+					      rte_memory_order_release);
+	}
+}
+
+static void
+slb_port_enqueue_stats(struct slb_port *port, uint16_t num_new,
+		       uint16_t num_forward, uint16_t num_release)
+{
+	port->new_enqueued += num_new;
+	port->forward_enqueued += num_forward;
+	port->release_enqueued += num_release;
+}
+
+static void
+slb_port_queue_enqueue_burst_stats(struct slb_port *source_port,
+				   uint8_t queue_id, uint16_t num)
+{
+	source_port->queue_enqueued[queue_id] += num;
+}
+
+static inline uint16_t
+slb_event_enqueue_burst_generic(struct slb_port *source_port,
+				const struct rte_event events[],
+				uint16_t events_len, bool op_types_known,
+				uint16_t num_new, uint16_t num_forward,
+				uint16_t num_release)
+{
+	struct slb_evdev *slb = source_port->slb;
+	uint16_t enqueued = 0;
+	uint16_t idx = 0;
+
+	if (unlikely(events_len == 0))
+		return 0;
+
+	if (!op_types_known) {
+		for (uint16_t i = 0; i < events_len; i++) {
+			switch (events[i].op) {
+			case RTE_EVENT_OP_NEW:
+				num_new++;
+				break;
+			case RTE_EVENT_OP_FORWARD:
+				num_forward++;
+				break;
+			case RTE_EVENT_OP_RELEASE:
+				num_release++;
+				break;
+			}
+		}
+	}
+
+	/* Check credit threshold for NEW events */
+	if (num_new > 0) {
+		if (unlikely(rte_atomic_load_explicit(&slb->credits_on_loan,
+						      rte_memory_order_relaxed) >
+			     source_port->new_event_threshold))
+			return 0;
+
+		if (unlikely(!slb_port_acquire_credits(slb, source_port,
+						       num_new)))
+			return 0;
+	}
+
+	if (num_release > 0)
+		slb_port_return_credits(slb, source_port, num_release);
+
+	slb_port_enqueue_stats(source_port, num_new, num_forward, num_release);
+
+	/* Enqueue contiguous chunks targeting the same queue */
+	while (idx < events_len) {
+		if (events[idx].op == RTE_EVENT_OP_RELEASE) {
+			idx++;
+			enqueued++;
+			continue;
+		}
+
+		uint8_t qid = events[idx].queue_id;
+		uint16_t chunk_len = 1;
+
+		while (idx + chunk_len < events_len &&
+		       events[idx + chunk_len].queue_id == qid &&
+		       events[idx + chunk_len].op != RTE_EVENT_OP_RELEASE) {
+			chunk_len++;
+		}
+
+		struct slb_queue *queue = &slb->queues[qid];
+		uint16_t actual_enq = rte_event_ring_enqueue_burst(queue->q_ring,
+								  &events[idx],
+								  chunk_len,
+								  NULL);
+		slb_port_queue_enqueue_burst_stats(source_port, qid, actual_enq);
+		enqueued += actual_enq;
+
+		if (actual_enq < chunk_len) {
+			/* If queue ring is full, return unused credits for dropped NEW events */
+			uint16_t dropped_new = 0;
+			for (uint16_t d = actual_enq; d < chunk_len; d++) {
+				if (events[idx + d].op == RTE_EVENT_OP_NEW)
+					dropped_new++;
+			}
+			if (dropped_new > 0)
+				slb_port_return_credits(slb, source_port, dropped_new);
+			break;
+		}
+
+		idx += chunk_len;
+	}
+
+	return enqueued;
+}
+
+uint16_t
+slb_event_enqueue_burst(void *port, const struct rte_event events[],
+			uint16_t events_len)
+{
+	struct slb_port *source_port = port;
+	return slb_event_enqueue_burst_generic(source_port, events, events_len,
+					       false, 0, 0, 0);
+}
+
+uint16_t
+slb_event_enqueue_new_burst(void *port, const struct rte_event events[],
+			    uint16_t events_len)
+{
+	struct slb_port *source_port = port;
+	return slb_event_enqueue_burst_generic(source_port, events, events_len,
+					       true, events_len, 0, 0);
+}
+
+uint16_t
+slb_event_enqueue_forward_burst(void *port, const struct rte_event events[],
+				uint16_t events_len)
+{
+	struct slb_port *source_port = port;
+	return slb_event_enqueue_burst_generic(source_port, events, events_len,
+					       true, 0, events_len, 0);
+}
+
+static inline uint16_t
+slb_port_dequeue(struct slb_port *port, struct rte_event events[],
+		 uint16_t num)
+{
+	uint16_t total_deq = 0;
+
+	/* Single-link direct fast path */
+	if (port->is_single_linked) {
+		if (unlikely(!port->single_linked_ring))
+			return 0;
+
+		total_deq = rte_event_ring_dequeue_burst(port->single_linked_ring,
+							events, num, NULL);
+		if (total_deq > 0) {
+			port->total_dequeues += total_deq;
+			if (port->implicit_release)
+				slb_port_return_credits(port->slb, port,
+							total_deq);
+		}
+		return total_deq;
+	}
+
+	/* Parallel queue dequeue across priority levels */
+	for (int p = 0; p < SLB_MAX_PRIORITIES; p++) {
+		uint16_t num_q = port->num_q[p];
+		if (num_q == 0)
+			continue;
+
+		uint16_t q_idx = port->next_q[p];
+		for (uint16_t i = 0; i < num_q; i++) {
+			struct slb_port_q_entry *entry = &port->q_entries[p][q_idx];
+			uint16_t req = num - total_deq;
+			uint16_t deq = rte_event_ring_dequeue_burst(entry->dq_ring,
+								   &events[total_deq],
+								   req, NULL);
+			if (deq > 0) {
+				port->queue_dequeued[entry->queue_id] += deq;
+				total_deq += deq;
+			}
+
+			q_idx++;
+			if (q_idx >= num_q)
+				q_idx = 0;
+
+			if (total_deq == num)
+				break;
+		}
+		port->next_q[p] = q_idx;
+
+		if (total_deq > 0)
+			break;
+	}
+
+	port->total_dequeues += total_deq;
+	return total_deq;
+}
+
+uint16_t
+slb_event_dequeue_burst(void *p, struct rte_event events[], uint16_t num,
+			uint64_t wait __rte_unused)
+{
+	struct slb_port *port = p;
+
+	port->dequeue_calls++;
+	uint16_t dequeued = slb_port_dequeue(port, events, num);
+	port->dequeued += dequeued;
+
+	if (dequeued == 0)
+		port->empty_dequeues++;
+
+	return dequeued;
+}
-- 
2.53.0


  reply	other threads:[~2026-11-07  1:30 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-07  1:30 [PATCH v1 0/8] event/slb: introducing software load balancer eventdev PMD Pravin Pathak
2026-11-07  1:30 ` Pravin Pathak [this message]
2026-11-07  1:30 ` [PATCH v1 2/8] event/slb: add support for ordered queues Pravin Pathak
2026-11-07  1:30 ` [PATCH v1 3/8] event/slb: add support for atomic queues Pravin Pathak
2026-11-07  1:30 ` [PATCH v1 4/8] event/slb: add dynamic flow migration " Pravin Pathak
2026-11-07  1:30 ` [PATCH v1 5/8] event/slb: add extended statistics and diagnostics Pravin Pathak
2026-11-07  1:30 ` [PATCH v1 6/8] event/slb: add self-test and documentation Pravin Pathak
2026-11-07  1:30 ` [PATCH v1 7/8] event/slb: update resets and credit defaults Pravin Pathak
2026-11-07  1:30 ` [PATCH v1 8/8] event/slb: improve port linking, config checks and atomic refill Pravin Pathak
2026-11-07  7:29 ` [PATCH v1 0/8] event/slb: introducing software load balancer eventdev PMD Jerin Jacob
2026-11-08 17:10   ` Stephen Hemminger
2026-11-09  2:30   ` Pravin Pathak
2026-11-09 12:55     ` Jerin Jacob
2026-11-11 14:22       ` Sarkar, Tirthendu

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261107013011.2443138-2-pravinpathakdev@gmail.com \
    --to=pravinpathakdev@gmail.com \
    --cc=bruce.richardson@intel.com \
    --cc=chenbox@nvidia.com \
    --cc=david.marchand@redhat.com \
    --cc=dev@dpdk.org \
    --cc=jerinj@marvell.com \
    --cc=mike.ximing.chen@intel.com \
    --cc=nipun.gupta@amd.com \
    --cc=pravin.pathak.dev@gmail.com \
    --cc=thomas@monjalon.net \
    --cc=tirthendu.sarkar@intel.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.