From: Pravin Pathak <[email protected]> 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 <[email protected]> --- 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 <[email protected]> F: drivers/event/dsw/ F: doc/guides/eventdevs/dsw.rst +Software Load Balancer (SLB) Eventdev PMD +M: Pravin Pathak <[email protected]> +F: drivers/event/slb/ + Software OPDL Eventdev PMD M: Liang Ma <[email protected]> M: Peter Mccarthy <[email protected]> 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 <[email protected]> + +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 <[email protected]> + */ + +#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 <[email protected]> + */ + +#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 <[email protected]> + */ + +#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

