On 28/07/2026 13:28, Eelco Chaudron wrote:
External email: Use caution opening links or attachments


On 26 Jul 2026, at 17:33, Eli Britstein wrote:

Introduce a new netdev type - "doca".
The code is placed in new files.
- ovs-doca: initialization of doca library and utility functions that
   are used currently by netdev-doca and also will be used for future
   hw-offload code.
- netdev-doca: implementation of the new netdev.

Supported ports are mlx5 ports in switch-dev mode only that with a NIC
that supports hw-steering.

The netdev has the concept of ESW manager. A representor port is
functional only if its ESW manager is attached to OVS. In case it is
not, the representor appears as functional in ovs-vsctl show, but it is
not. Upon initializing of an ESW manager port, each representor is
reconfigured to be functional, and upon destruction, they are first stopped.

Steering infrastructure:
- RX packets of all ports are steered to a common queue. This queue is
   polled using dpdk API and the packets are classified to a per-port
   memory structure.
- TX packets are marked with the target port as metadata and sent to a
   common queue. The egress pipe matches on the metadata and forwards the
   packets accordingly.

Signed-off-by: Eli Britstein <[email protected]>
Hi Eli,

Thanks for sending the v6 series. I have some comments below.

I would suggest waiting for feedback from David before sending a v7.
I have acked all of the previous patches in the series, but he may
have additional comments.

Cheers,
Eelco

[...]

+static int
+netdev_doca_slowpath_esw_init(struct netdev *netdev)
+{
+    int rv;
+
+#define ESW_INIT_CMD(func)                                                \
+    do {                                                                  \
+        rv = (func)(netdev);                                              \
+        if (rv == DOCA_SUCCESS) {                                         \
+            break;                                                        \
+        }                                                                 \
+        VLOG_ERR("%s: eSwitch initialization failed, %s() with error: "   \
+                 "%d (%s)", netdev_get_name(netdev), #func, rv,           \
+                 doca_error_get_descr(rv));                               \
+        return rv;                                                        \
+    } while (0)
+
+    ESW_INIT_CMD(netdev_doca_egress_pipe_init);
+    ESW_INIT_CMD(netdev_doca_rss_pipe_init);
+    ESW_INIT_CMD(netdev_doca_meta_tag0_pipe_init);
+    ESW_INIT_CMD(netdev_doca_meta_tag0_rule_init);
+    ESW_INIT_CMD(netdev_doca_pre_miss_pipe_init);
+    ESW_INIT_CMD(netdev_doca_pre_miss_rules_init);
+    ESW_INIT_CMD(netdev_doca_root_pipe_init);
If a failure occurs in a later call, resources initialized by earlier
successful calls may not be cleaned up.  Should the macro call
netdev_doca_slowpath_esw_uninit() before returning on error?
netdev_doca_slowpath_esw_uninit() calls ovs_doca_destroy_pipe() for
each resource, which safely handles NULL pointers.
Ack

+    return 0;
+}
[...]

+static void
+netdev_doca_dev_close(struct netdev_doca *dev)
+{
+    struct netdev_dpdk_common *common = &dev->common;
+    struct netdev_doca_esw_ctx *esw = dev->esw_ctx;
+    struct rte_eth_dev_info dev_info;
+    char *pci_addr;
+    bool last;
+    int err;
+
+    memset(&dev_info, 0, sizeof dev_info);
+
+    if (rte_eth_dev_is_valid_port(common->port_id)) {
+        err = rte_eth_dev_info_get(common->port_id, &dev_info);
+        if (err) {
+            VLOG_ERR("Failed to get info of port "DPDK_PORT_ID_FMT": %s",
+                     common->port_id, rte_strerror(-err));
+        }
+
+        err = rte_eth_dev_close(common->port_id);
+        if (err) {
+            VLOG_ERR("Failed to close port "DPDK_PORT_ID_FMT": %s",
+                     common->port_id, rte_strerror(-err));
+        }
+    }
+
+    if (!esw) {
+        return;
+    }
+
+    pci_addr = xstrdup(esw->pci_addr);
+
+    last = refmap_unref(netdev_doca_esw_rfm, esw);
+    /* The last is the ESW. */
+    if (last && esw->dev) {
+        if (dev_info.device) {
+            /* The esw->cmd_fd is closed inside. */
+            err = rte_dev_remove(dev_info.device);
+            if (err) {
+                VLOG_ERR("Failed to remove device %s: %s", common->devargs,
+                         rte_strerror(-err));
+            }
+
+            esw->cmd_fd = -1;
+        }
+
+        VLOG_DBG("Closing '%s'", pci_addr);
+        err = doca_dev_close(esw->dev);
+        if (err != DOCA_SUCCESS) {
+            VLOG_ERR("Failed to close doca dev %s. Error: %d (%s)", pci_addr,
+                     err, doca_error_get_descr(err));
+        }
+
+        esw->dev = NULL;
+        if (esw->cmd_fd != -1) {
+            close(esw->cmd_fd);
I guess this needs;

                esw->cmd_fd = -1;

+        } else {
We do not need this else branch.
Good catch. Ack.

+            esw->cmd_fd = -1;
+        }
+    }
+
+    dev->esw_ctx = NULL;
+    free(pci_addr);
+}
[...]

+static int
+netdev_doca_reconfigure(struct netdev *netdev)
+{
+    struct netdev_doca *dev = netdev_doca_cast(netdev);
+    struct netdev_dpdk_common *common = &dev->common;
+    int err = 0;
+
+    /* If an ESW manager is not attached to OVS, a representor cannot be
+     * configured. */
+    if (!netdev_doca_is_esw_mgr(netdev) &&
+        netdev_doca_get_esw_mgr_port_id(netdev) ==
+        DPDK_ETH_PORT_ID_INVALID) {
+        return EOPNOTSUPP;
+    }
+
+    ovs_mutex_lock(&dpdk_common_mutex);
+    ovs_mutex_lock(&dev->common.mutex);
+
+    common->requested_n_rxq = common->user_n_rxq;
+
+    if (netdev->n_txq == common->requested_n_txq
+        && netdev->n_rxq == common->requested_n_rxq
+        && common->mtu == common->requested_mtu
+        && common->lsc_interrupt_mode == common->requested_lsc_interrupt_mode
+        && common->rxq_size == common->requested_rxq_size
+        && common->txq_size == common->requested_txq_size
+        && eth_addr_equals(common->hwaddr, common->requested_hwaddr)
+        && common->socket_id == common->requested_socket_id
+        && netdev_dpdk_is_started(common)) {
+        /* Reconfiguration is unnecessary. */
+        goto out;
+    }
+
+    netdev_doca_port_stop(netdev);
+
+    err = netdev_doca_mempool_configure(dev);
+    if (err) {
+        goto out;
+    }
+
+    common->lsc_interrupt_mode = common->requested_lsc_interrupt_mode;
+
+    netdev->n_txq = common->requested_n_txq;
+    netdev->n_rxq = common->requested_n_rxq;
+    if (!netdev_doca_is_esw_mgr(netdev)) {
+        int esw_n_rxq;
+
+        esw_n_rxq = dev->esw_ctx->n_rxq;
+        if (common->requested_n_rxq != esw_n_rxq) {
+            VLOG_WARN("%s: requested_n_rxq=%d is ignored. DOCA binds the "
+                      "number of rx queues to the esw's n_rxq=%d",
+                      netdev_get_name(netdev), common->requested_n_rxq,
+                      esw_n_rxq);
+        }
+
+        netdev->n_rxq = esw_n_rxq;
+    }
+
+    common->rxq_size = common->requested_rxq_size;
+    common->txq_size = common->requested_txq_size;
+
+    rte_free(common->tx_q);
+    common->tx_q = NULL;
+
+    if (!eth_addr_equals(common->hwaddr, common->requested_hwaddr)) {
+        err = netdev_dpdk_set_dev_etheraddr(&dev->common,
+                                            common->requested_hwaddr);
+        if (err) {
+            goto out;
+        }
+    }
+
+    err = doca_eth_dev_init(dev);
+    if (err) {
+        goto out;
+    }
+
+    netdev_dpdk_update_netdev_flags(&dev->common);
+
+    /* If both requested and actual hw-addr were previously
+     * unset (initialized to 0), then first device init above
+     * will have set actual hw-addr to something new.
+     * This would trigger spurious MAC reconfiguration unless
+     * the requested MAC is kept in sync.
+     *
+     * This is harmless in case requested_hwaddr was
+     * configured by the user, as netdev_dpdk_set_dev_etheraddr()
+     * will have succeeded to get to this point. */
+    common->requested_hwaddr = common->hwaddr;
+
+    common->tx_q = netdev_dpdk_alloc_txq(netdev->n_txq);
+    if (!common->tx_q) {
+        err = ENOMEM;
If the allocation fails, the port is left started with a NULL tx_q.
On the next reconfigure call, the "no change needed" guard sees
started=true and skips the allocation, leaving tx_q permanently NULL
and risking a crash in netdev_doca_send() when concurrent_txq is true.
right. will fix. btw, the same in netdev-dpdk.c.

+    }
+
+    netdev_change_seq_changed(netdev);
+
+out:
+    ovs_mutex_unlock(&dev->common.mutex);
+    ovs_mutex_unlock(&dpdk_common_mutex);
+    return err;
+}
[...]

+static enum doca_log_level
+get_buf_log_level(const char **pbuf, size_t *psize)
+{
+    const char *buf = *pbuf;
+    size_t size = *psize;
+    const char *p = buf;
+    int level;
+
+    /* Skip [timestamp][thread_id][DOCA], then parse [LEVEL] (INF/WRN/etc.). */
+    for (int i = 0; i < 4; i++) {
+        while (size && *p && *p != '[') {
+            size--;
+            p++;
+        }
+
+        if (!size || !*p) {
+            return DOCA_LOG_LEVEL_DISABLE;
+        }
+
+        size--;
+        p++;
+    }
+
Does this code need a "size >= 4" guard to safeguard the *psize
arithmetic below against underflow?  Placing it here also ensures
that at least 3 bytes remain for the strncmp in
ovs_doca_parse_log_level().

     if (size < 4) {
         return DOCA_LOG_LEVEL_DISABLE;
     }
Ack

+    level = ovs_doca_parse_log_level(NULL, p);
+    if (level < 0) {
+        return DOCA_LOG_LEVEL_DISABLE;
+    }
+
+    /* 'p' points to the level start which is 3 chars and another ']'
+     * after it.  For example "INF]".  Skip it. */
+    *pbuf = p + 4;
+    *psize -= *pbuf - buf;
+
+    return level;
+}
+
+static ssize_t
+ovs_doca_log_write(void *c OVS_UNUSED, const char *buf, size_t size)
+{
+    static struct vlog_rate_limit dbg_rl = VLOG_RATE_LIMIT_INIT(600, 600);
+    enum doca_log_level level = get_buf_log_level(&buf, &size);
+
+    switch (level) {
+        case DOCA_LOG_LEVEL_DISABLE:
+            VLOG_ERR("(Failed to parse level): %.*s", (int) size, buf);
+            break;
The coding style guide says 'case' labels should align with the
'switch' keyword, not be indented an extra level inside it.
Ack. btw, same in dpdk.c

+        case DOCA_LOG_LEVEL_TRACE:
+        case DOCA_LOG_LEVEL_DEBUG:
+            VLOG_DBG_RL(&dbg_rl, "%.*s", (int) size, buf);
+            break;
+        case DOCA_LOG_LEVEL_INFO:
+            VLOG_INFO_RL(&dbg_rl, "%.*s", (int) size, buf);
+            break;
+        case DOCA_LOG_LEVEL_WARNING:
+            VLOG_WARN_RL(&dbg_rl, "%.*s", (int) size, buf);
+            break;
+        case DOCA_LOG_LEVEL_ERROR:
+            VLOG_ERR_RL(&dbg_rl, "%.*s", (int) size, buf);
+            break;
+        case DOCA_LOG_LEVEL_CRIT:
+            VLOG_EMER("%.*s", (int) size, buf);
+            break;
+    }
+
+    return size;
+}
[...]

+/* Every doca-entry operation is asynchronous.  It must be processed using
+ * doca_flow_entries_process() API.  For each processed entry, this callback
+ * is called.  The 'qid' argument is the queue-id for which the entry was
+ * processed on (which is the same as the one of the initial operation).
+ * 'queues' is an array of queues.  Each entry is accessed only the its own
+ * queue (which is assigned to a thread), no locks are required here. */
The last sentence here needs some rewording.
Ack

+static void
+ovs_doca_entry_process(struct doca_flow_pipe_entry *entry,
+                       uint16_t qid,
+                       enum doca_flow_entry_status status,
+                       enum doca_flow_entry_op op,
+                       void *aux)
+{
+    static const char *status_desc[] = {
+        [DOCA_FLOW_ENTRY_STATUS_IN_PROCESS] = "in-process",
+        [DOCA_FLOW_ENTRY_STATUS_SUCCESS] = "success",
+        [DOCA_FLOW_ENTRY_STATUS_ERROR] = "failure",
+    };
+    static const char *op_desc[] = {
+        [DOCA_FLOW_ENTRY_OP_ADD] = "add",
+        [DOCA_FLOW_ENTRY_OP_DEL] = "del",
+        [DOCA_FLOW_ENTRY_OP_UPD] = "mod",
+        [DOCA_FLOW_ENTRY_OP_AGED] = "age",
+    };
+    bool error = status == DOCA_FLOW_ENTRY_STATUS_ERROR;
+    struct ovs_doca_steering_queue *queues = aux;
+
+    ovs_assert(status < ARRAY_SIZE(status_desc));
+    ovs_assert(op < ARRAY_SIZE(op_desc));
+
+    VLOG_RL(&rl, error ? VLL_ERR : VLL_DBG,
+            "%s: [qid:%" PRIu16 "] %s aux=%p entry %p %s",
+            __func__, qid, op_desc[op], aux, entry, status_desc[status]);
+
+    if (queues && status != DOCA_FLOW_ENTRY_STATUS_IN_PROCESS) {
+        queues[qid].n_waiting_entries--;
+    }
+}
[...]

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to