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
next prev parent 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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox