[RFC] net/ring: add veth-like peer link state tracking
Stephen Hemminger
stephen at networkplumber.org
Thu Feb 12 01:08:33 CET 2026
The ring PMD currently reports link-up unconditionally when a port is
started, making it impossible for applications to detect whether the
other end of a ring-based virtual link is ready.
Add an optional peer-awareness mechanism modelled on the Linux veth
driver's carrier semantics.
Introduce the experimental API rte_eth_ring_attach_peer()
which pairs two ring PMD ports so that each port's link state
reflects whether its peer is started and administratively up.
Unpaired ports retain the existing behaviour.
Signed-off-by: Stephen Hemminger <stephen at networkplumber.org>
---
app/test/test_pmd_ring.c | 191 +++++++++++++++++++++++
doc/guides/nics/ring.rst | 42 +++++
doc/guides/rel_notes/release_26_03.rst | 8 +
drivers/net/ring/rte_eth_ring.c | 205 ++++++++++++++++++++++++-
drivers/net/ring/rte_eth_ring.h | 26 ++++
5 files changed, 468 insertions(+), 4 deletions(-)
diff --git a/app/test/test_pmd_ring.c b/app/test/test_pmd_ring.c
index cb08dcf1d9..c0ebc4fe29 100644
--- a/app/test/test_pmd_ring.c
+++ b/app/test/test_pmd_ring.c
@@ -425,6 +425,196 @@ test_pmd_ring_pair_create_attach(void)
return TEST_SUCCESS;
}
+static int
+test_pmd_ring_link_status(void)
+{
+ struct rte_eth_conf null_conf;
+ struct rte_eth_link link_d, link_e;
+ struct rte_ring *ring_de[1], *ring_ed[1];
+ int portd, porte;
+ int ret;
+
+ printf("Testing veth-like link state via rte_eth_ring_attach_peer\n");
+
+ memset(&null_conf, 0, sizeof(struct rte_eth_conf));
+
+ /*
+ * Create two cross-connected ring ports:
+ * portd TX -> ring_de -> porte RX
+ * porte TX -> ring_ed -> portd RX
+ * Then pair them so link state reflects peer status.
+ */
+ ring_de[0] = rte_ring_create("VETH_DE", RING_SIZE, SOCKET0,
+ RING_F_SP_ENQ | RING_F_SC_DEQ);
+ ring_ed[0] = rte_ring_create("VETH_ED", RING_SIZE, SOCKET0,
+ RING_F_SP_ENQ | RING_F_SC_DEQ);
+ if (ring_de[0] == NULL || ring_ed[0] == NULL) {
+ printf("Error creating veth rings\n");
+ return TEST_FAILED;
+ }
+
+ /* portd: RX from ring_ed, TX into ring_de */
+ portd = rte_eth_from_rings("net_vethd", ring_ed, 1, ring_de, 1, SOCKET0);
+ /* porte: RX from ring_de, TX into ring_ed */
+ porte = rte_eth_from_rings("net_vethe", ring_de, 1, ring_ed, 1, SOCKET0);
+ if (portd < 0 || porte < 0) {
+ printf("Error creating veth ethdev ports\n");
+ return TEST_FAILED;
+ }
+
+ /* Pair the ports for veth-like carrier detection */
+ ret = rte_eth_ring_attach_peer(portd, porte);
+ if (ret != 0) {
+ printf("Error: rte_eth_ring_attach_peer failed\n");
+ return TEST_FAILED;
+ }
+
+ /* Configure and set up queues */
+ if ((rte_eth_dev_configure(portd, 1, 1, &null_conf) < 0) ||
+ (rte_eth_dev_configure(porte, 1, 1, &null_conf) < 0)) {
+ printf("Configure failed for veth pair\n");
+ return TEST_FAILED;
+ }
+
+ if ((rte_eth_tx_queue_setup(portd, 0, RING_SIZE, SOCKET0, NULL) < 0) ||
+ (rte_eth_tx_queue_setup(porte, 0, RING_SIZE, SOCKET0, NULL) < 0)) {
+ printf("TX queue setup failed for veth pair\n");
+ return TEST_FAILED;
+ }
+
+ if ((rte_eth_rx_queue_setup(portd, 0, RING_SIZE, SOCKET0, NULL, mp) < 0) ||
+ (rte_eth_rx_queue_setup(porte, 0, RING_SIZE, SOCKET0, NULL, mp) < 0)) {
+ printf("RX queue setup failed for veth pair\n");
+ return TEST_FAILED;
+ }
+
+ /*
+ * Test 1: neither side started – both links should be down.
+ */
+ printf(" Test 1: both stopped -> both links down\n");
+ ret = rte_eth_link_get_nowait(portd, &link_d);
+ TEST_ASSERT(ret >= 0, "Link get failed: %s", rte_strerror(-ret));
+ ret = rte_eth_link_get_nowait(porte, &link_e);
+ TEST_ASSERT(ret >= 0, "Link get failed: %s", rte_strerror(-ret));
+ if (link_d.link_status != RTE_ETH_LINK_DOWN ||
+ link_e.link_status != RTE_ETH_LINK_DOWN) {
+ printf("Error: expected both links DOWN before start\n");
+ return TEST_FAILED;
+ }
+
+ /*
+ * Test 2: start only one side – that side should report link down
+ * because the peer is not started yet (veth semantics).
+ */
+ printf(" Test 2: start portd only -> portd link down (no peer)\n");
+ if (rte_eth_dev_start(portd) < 0) {
+ printf("Error starting portd\n");
+ return TEST_FAILED;
+ }
+ ret = rte_eth_link_get_nowait(portd, &link_d);
+ TEST_ASSERT(ret >= 0, "Link get failed: %s", rte_strerror(-ret));
+ ret = rte_eth_link_get_nowait(porte, &link_e);
+ TEST_ASSERT(ret >= 0, "Link get failed: %s", rte_strerror(-ret));
+ if (link_d.link_status != RTE_ETH_LINK_DOWN) {
+ printf("Error: portd link should be DOWN (peer not started)\n");
+ return TEST_FAILED;
+ }
+ if (link_e.link_status != RTE_ETH_LINK_DOWN) {
+ printf("Error: porte link should be DOWN (not started)\n");
+ return TEST_FAILED;
+ }
+
+ /*
+ * Test 3: start the second side – both should now have carrier.
+ */
+ printf(" Test 3: start porte -> both links up\n");
+ if (rte_eth_dev_start(porte) < 0) {
+ printf("Error starting porte\n");
+ return TEST_FAILED;
+ }
+ ret = rte_eth_link_get_nowait(portd, &link_d);
+ TEST_ASSERT(ret >= 0, "Link get failed: %s", rte_strerror(-ret));
+ ret = rte_eth_link_get_nowait(porte, &link_e);
+ TEST_ASSERT(ret >= 0, "Link get failed: %s", rte_strerror(-ret));
+ if (link_d.link_status != RTE_ETH_LINK_UP) {
+ printf("Error: portd link should be UP\n");
+ return TEST_FAILED;
+ }
+ if (link_e.link_status != RTE_ETH_LINK_UP) {
+ printf("Error: porte link should be UP\n");
+ return TEST_FAILED;
+ }
+
+ /*
+ * Test 4: stop one side – peer should lose carrier.
+ */
+ printf(" Test 4: stop portd -> both links down\n");
+ ret = rte_eth_dev_stop(portd);
+ if (ret != 0) {
+ printf("Error stopping portd: %s\n", rte_strerror(-ret));
+ return TEST_FAILED;
+ }
+ ret = rte_eth_link_get_nowait(portd, &link_d);
+ TEST_ASSERT(ret >= 0, "Link get failed: %s", rte_strerror(-ret));
+ ret = rte_eth_link_get_nowait(porte, &link_e);
+ TEST_ASSERT(ret >= 0, "Link get failed: %s", rte_strerror(-ret));
+ if (link_d.link_status != RTE_ETH_LINK_DOWN) {
+ printf("Error: portd link should be DOWN after stop\n");
+ return TEST_FAILED;
+ }
+ if (link_e.link_status != RTE_ETH_LINK_DOWN) {
+ printf("Error: porte should lose carrier when peer stops\n");
+ return TEST_FAILED;
+ }
+
+ /*
+ * Test 5: restart the stopped side – both should come back up
+ * (porte was still started).
+ */
+ printf(" Test 5: restart portd -> both links up again\n");
+ if (rte_eth_dev_start(portd) < 0) {
+ printf("Error restarting portd\n");
+ return TEST_FAILED;
+ }
+ ret = rte_eth_link_get_nowait(portd, &link_d);
+ TEST_ASSERT(ret >= 0, "Link get failed: %s", rte_strerror(-ret));
+ ret = rte_eth_link_get_nowait(porte, &link_e);
+ TEST_ASSERT(ret >= 0, "Link get failed: %s", rte_strerror(-ret));
+ if (link_d.link_status != RTE_ETH_LINK_UP ||
+ link_e.link_status != RTE_ETH_LINK_UP) {
+ printf("Error: both links should be UP after restart\n");
+ return TEST_FAILED;
+ }
+
+ /*
+ * Test 6: admin set_link_down on one side – peer should lose carrier.
+ */
+ printf(" Test 6: set_link_down portd -> both links down\n");
+ rte_eth_dev_set_link_down(portd);
+ ret = rte_eth_link_get_nowait(portd, &link_d);
+ TEST_ASSERT(ret >= 0, "Link get failed: %s", rte_strerror(-ret));
+ ret = rte_eth_link_get_nowait(porte, &link_e);
+ TEST_ASSERT(ret >= 0, "Link get failed: %s", rte_strerror(-ret));
+ if (link_d.link_status != RTE_ETH_LINK_DOWN) {
+ printf("Error: portd link should be DOWN after set_link_down\n");
+ return TEST_FAILED;
+ }
+ if (link_e.link_status != RTE_ETH_LINK_DOWN) {
+ printf("Error: porte should lose carrier on peer set_link_down\n");
+ return TEST_FAILED;
+ }
+
+ /* Clean up */
+ rte_eth_dev_stop(portd);
+ rte_eth_dev_stop(porte);
+ rte_vdev_uninit("net_ring_net_vethd");
+ rte_vdev_uninit("net_ring_net_vethe");
+ rte_ring_free(ring_de[0]);
+ rte_ring_free(ring_ed[0]);
+
+ return TEST_SUCCESS;
+}
+
static void
test_cleanup_resources(void)
{
@@ -582,6 +772,7 @@ unit_test_suite test_pmd_ring_suite = {
TEST_CASE(test_get_stats_for_port),
TEST_CASE(test_stats_reset_for_port),
TEST_CASE(test_pmd_ring_pair_create_attach),
+ TEST_CASE(test_pmd_ring_link_status),
TEST_CASE(test_command_line_ring_port),
TEST_CASES_END()
}
diff --git a/doc/guides/nics/ring.rst b/doc/guides/nics/ring.rst
index a6b2458a7f..caa461a2f2 100644
--- a/doc/guides/nics/ring.rst
+++ b/doc/guides/nics/ring.rst
@@ -104,6 +104,48 @@ the final two lines can be changed as follows:
This type of configuration is useful in a pipeline model where inter-core communication
using pseudo Ethernet devices is preferred over raw rings for API consistency.
+Peer Link State (veth-like Carrier Detection)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+By default, a ring-based port reports link-up as soon as it is started,
+regardless of the state of any other port. For use cases that model a
+virtual Ethernet cable between two ports, this can be changed by pairing
+the ports with ``rte_eth_ring_attach_peer()``.
+
+Once two ports are paired, their link state follows the same rules as the
+Linux veth driver:
+
+* The link comes up only when **both** sides are started.
+* Stopping, closing, or administratively setting link-down on one side
+ causes the other side to report link-down as well.
+* When the stopped side is restarted, both sides regain carrier.
+
+Pairing is supported for any two ring-based ports, whether they were created
+with ``rte_eth_from_rings()`` or via the ``--vdev=net_ring`` EAL option.
+
+.. code-block:: c
+
+ struct rte_ring *ring_ab, *ring_ba;
+
+ ring_ab = rte_ring_create("AB", 1024, 0, RING_F_SP_ENQ | RING_F_SC_DEQ);
+ ring_ba = rte_ring_create("BA", 1024, 0, RING_F_SP_ENQ | RING_F_SC_DEQ);
+
+ /* Port A: TX into ring_ab, RX from ring_ba */
+ int port_a = rte_eth_from_rings("veth_a", &ring_ba, 1, &ring_ab, 1, 0);
+ /* Port B: TX into ring_ba, RX from ring_ab */
+ int port_b = rte_eth_from_rings("veth_b", &ring_ab, 1, &ring_ba, 1, 0);
+
+ /* Enable veth-like link state tracking */
+ rte_eth_ring_attach_peer(port_a, port_b);
+
+ /* At this point both links are down.
+ * Starting port_a alone still shows link-down (peer is not ready).
+ * Starting port_b as well brings both links up.
+ */
+
+Unpaired ports (the default) are unaffected and retain the original
+behaviour where link-up is reported immediately on start.
+
Enqueuing and dequeuing items from an ``rte_ring``
using the ring-based PMD may be slower than using the native ring API.
DPDK Ethernet drivers use function pointers
diff --git a/doc/guides/rel_notes/release_26_03.rst b/doc/guides/rel_notes/release_26_03.rst
index 5c2a4bb32e..33fd354be8 100644
--- a/doc/guides/rel_notes/release_26_03.rst
+++ b/doc/guides/rel_notes/release_26_03.rst
@@ -82,6 +82,14 @@ New Features
* NEA5, NIA5, NCA5: AES 256 confidentiality, integrity and AEAD modes.
* NEA6, NIA6, NCA6: ZUC 256 confidentiality, integrity and AEAD modes.
+* **Updated ring-based PMD with veth-like link state.**
+
+ Added peer link state tracking to the ring-based PMD (``net_ring``).
+ Two ring PMD ports can now be paired via the new experimental
+ ``rte_eth_ring_attach_peer()`` API so that each port's link status
+ reflects whether its peer is started, mirroring the carrier semantics
+ of the Linux veth driver. Unpaired ports retain the existing behaviour.
+
Removed Items
-------------
diff --git a/drivers/net/ring/rte_eth_ring.c b/drivers/net/ring/rte_eth_ring.c
index b639544eab..d7a8ee8796 100644
--- a/drivers/net/ring/rte_eth_ring.c
+++ b/drivers/net/ring/rte_eth_ring.c
@@ -59,6 +59,9 @@ struct pmd_internals {
struct rte_ether_addr address;
enum dev_action action;
+
+ uint16_t peer_port_id; /**< port id of the peer, or RTE_MAX_ETHPORTS */
+ uint8_t link_admin_down; /**< true when set_link_down has been called */
};
static struct rte_eth_link pmd_link = {
@@ -111,13 +114,41 @@ eth_dev_configure(struct rte_eth_dev *dev __rte_unused) { return 0; }
static int
eth_dev_start(struct rte_eth_dev *dev)
{
- dev->data->dev_link.link_status = RTE_ETH_LINK_UP;
+ struct pmd_internals *internals = dev->data->dev_private;
+
+ internals->link_admin_down = 0;
+
+ if (internals->peer_port_id < RTE_MAX_ETHPORTS) {
+ struct rte_eth_dev *peer = &rte_eth_devices[internals->peer_port_id];
+ struct pmd_internals *peer_internals = peer->data->dev_private;
+
+ /*
+ * Veth-like carrier: link comes up only when the peer
+ * is also started and not administratively down.
+ * When we start, we also give the peer carrier if it
+ * was already started.
+ */
+ if (peer_internals != NULL &&
+ peer_internals->peer_port_id == dev->data->port_id &&
+ peer->data->dev_started &&
+ !peer_internals->link_admin_down) {
+ dev->data->dev_link.link_status = RTE_ETH_LINK_UP;
+ peer->data->dev_link.link_status = RTE_ETH_LINK_UP;
+ } else {
+ dev->data->dev_link.link_status = RTE_ETH_LINK_DOWN;
+ }
+ } else {
+ /* Unpaired port: link follows admin state directly */
+ dev->data->dev_link.link_status = RTE_ETH_LINK_UP;
+ }
+
return 0;
}
static int
eth_dev_stop(struct rte_eth_dev *dev)
{
+ struct pmd_internals *internals = dev->data->dev_private;
uint16_t i;
dev->data->dev_started = 0;
@@ -127,14 +158,30 @@ eth_dev_stop(struct rte_eth_dev *dev)
dev->data->rx_queue_state[i] = RTE_ETH_QUEUE_STATE_STARTED;
for (i = 0; i < dev->data->nb_tx_queues; i++)
dev->data->tx_queue_state[i] = RTE_ETH_QUEUE_STATE_STARTED;
+
+ /*
+ * When this side stops, the peer loses carrier,
+ * mirroring Linux veth behavior.
+ */
+ if (internals->peer_port_id < RTE_MAX_ETHPORTS) {
+ struct rte_eth_dev *peer = &rte_eth_devices[internals->peer_port_id];
+ struct pmd_internals *peer_internals = peer->data->dev_private;
+
+ if (peer_internals != NULL &&
+ peer_internals->peer_port_id == dev->data->port_id)
+ peer->data->dev_link.link_status = RTE_ETH_LINK_DOWN;
+ }
+
return 0;
}
static int
eth_dev_set_link_down(struct rte_eth_dev *dev)
{
+ struct pmd_internals *internals = dev->data->dev_private;
uint16_t i;
+ internals->link_admin_down = 1;
dev->data->dev_link.link_status = RTE_ETH_LINK_DOWN;
for (i = 0; i < dev->data->nb_rx_queues; i++)
@@ -142,13 +189,47 @@ eth_dev_set_link_down(struct rte_eth_dev *dev)
for (i = 0; i < dev->data->nb_tx_queues; i++)
dev->data->tx_queue_state[i] = RTE_ETH_QUEUE_STATE_STOPPED;
+ /* Peer also loses carrier when this side is forced down */
+ if (internals->peer_port_id < RTE_MAX_ETHPORTS) {
+ struct rte_eth_dev *peer = &rte_eth_devices[internals->peer_port_id];
+ struct pmd_internals *peer_internals = peer->data->dev_private;
+
+ if (peer_internals != NULL &&
+ peer_internals->peer_port_id == dev->data->port_id)
+ peer->data->dev_link.link_status = RTE_ETH_LINK_DOWN;
+ }
+
return 0;
}
static int
eth_dev_set_link_up(struct rte_eth_dev *dev)
{
- dev->data->dev_link.link_status = RTE_ETH_LINK_UP;
+ struct pmd_internals *internals = dev->data->dev_private;
+
+ internals->link_admin_down = 0;
+
+ if (internals->peer_port_id < RTE_MAX_ETHPORTS) {
+ struct rte_eth_dev *peer = &rte_eth_devices[internals->peer_port_id];
+ struct pmd_internals *peer_internals = peer->data->dev_private;
+
+ /*
+ * With a peer, link can only come up if the peer is also
+ * started and not administratively down.
+ */
+ if (peer_internals != NULL &&
+ peer_internals->peer_port_id == dev->data->port_id &&
+ peer->data->dev_started &&
+ !peer_internals->link_admin_down) {
+ dev->data->dev_link.link_status = RTE_ETH_LINK_UP;
+ peer->data->dev_link.link_status = RTE_ETH_LINK_UP;
+ } else {
+ dev->data->dev_link.link_status = RTE_ETH_LINK_DOWN;
+ }
+ } else {
+ dev->data->dev_link.link_status = RTE_ETH_LINK_UP;
+ }
+
return 0;
}
@@ -277,8 +358,38 @@ eth_allmulticast_disable(struct rte_eth_dev *dev __rte_unused)
}
static int
-eth_link_update(struct rte_eth_dev *dev __rte_unused,
- int wait_to_complete __rte_unused) { return 0; }
+eth_link_update(struct rte_eth_dev *dev,
+ int wait_to_complete __rte_unused)
+{
+ struct pmd_internals *internals = dev->data->dev_private;
+ struct rte_eth_link link;
+
+ link = pmd_link;
+
+ if (!dev->data->dev_started || internals->link_admin_down) {
+ link.link_status = RTE_ETH_LINK_DOWN;
+ } else if (internals->peer_port_id < RTE_MAX_ETHPORTS) {
+ /*
+ * Paired port: carrier depends on peer being started
+ * and not administratively down, similar to Linux veth.
+ */
+ struct rte_eth_dev *peer = &rte_eth_devices[internals->peer_port_id];
+ struct pmd_internals *peer_internals = peer->data->dev_private;
+
+ if (peer_internals != NULL &&
+ peer_internals->peer_port_id == dev->data->port_id &&
+ peer->data->dev_started &&
+ !peer_internals->link_admin_down)
+ link.link_status = RTE_ETH_LINK_UP;
+ else
+ link.link_status = RTE_ETH_LINK_DOWN;
+ } else {
+ /* Unpaired port: link follows admin state */
+ link.link_status = dev->data->dev_link.link_status;
+ }
+
+ return rte_eth_linkstatus_set(dev, &link);
+}
static int
eth_dev_close(struct rte_eth_dev *dev)
@@ -294,6 +405,19 @@ eth_dev_close(struct rte_eth_dev *dev)
ret = eth_dev_stop(dev);
internals = dev->data->dev_private;
+
+ /* Unlink peer so it no longer references this port */
+ if (internals->peer_port_id < RTE_MAX_ETHPORTS) {
+ struct rte_eth_dev *peer = &rte_eth_devices[internals->peer_port_id];
+ struct pmd_internals *peer_internals = peer->data->dev_private;
+
+ if (peer_internals != NULL &&
+ peer_internals->peer_port_id == dev->data->port_id)
+ peer_internals->peer_port_id = RTE_MAX_ETHPORTS;
+
+ internals->peer_port_id = RTE_MAX_ETHPORTS;
+ }
+
if (internals->action == DEV_CREATE) {
/*
* it is only necessary to delete the rings in rx_queues because
@@ -420,6 +544,7 @@ do_eth_dev_ring_create(const char *name,
internals->action = action;
internals->max_rx_queues = nb_rx_queues;
internals->max_tx_queues = nb_tx_queues;
+ internals->peer_port_id = RTE_MAX_ETHPORTS;
for (i = 0; i < nb_rx_queues; i++) {
internals->rx_ring_queues[i].rng = rx_queues[i];
internals->rx_ring_queues[i].in_port = -1;
@@ -527,6 +652,49 @@ rte_eth_from_ring(struct rte_ring *r)
r->memzone ? r->memzone->socket_id : SOCKET_ID_ANY);
}
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_eth_ring_attach_peer, 26.03)
+int
+rte_eth_ring_attach_peer(uint16_t port_id_a, uint16_t port_id_b)
+{
+ struct rte_eth_dev *dev_a, *dev_b;
+ struct pmd_internals *internals_a, *internals_b;
+
+ if (port_id_a >= RTE_MAX_ETHPORTS || port_id_b >= RTE_MAX_ETHPORTS ||
+ port_id_a == port_id_b) {
+ rte_errno = EINVAL;
+ return -1;
+ }
+
+ dev_a = &rte_eth_devices[port_id_a];
+ dev_b = &rte_eth_devices[port_id_b];
+
+ /* Verify both are ring PMD ports */
+ if (dev_a->dev_ops != &ops || dev_b->dev_ops != &ops) {
+ rte_errno = EINVAL;
+ return -1;
+ }
+
+ internals_a = dev_a->data->dev_private;
+ internals_b = dev_b->data->dev_private;
+
+ if (internals_a == NULL || internals_b == NULL) {
+ rte_errno = EINVAL;
+ return -1;
+ }
+
+ /* Neither port may already have a peer */
+ if (internals_a->peer_port_id < RTE_MAX_ETHPORTS ||
+ internals_b->peer_port_id < RTE_MAX_ETHPORTS) {
+ rte_errno = EBUSY;
+ return -1;
+ }
+
+ internals_a->peer_port_id = port_id_b;
+ internals_b->peer_port_id = port_id_a;
+
+ return 0;
+}
+
static int
eth_dev_ring_create(const char *name,
struct rte_vdev_device *vdev,
@@ -564,6 +732,35 @@ eth_dev_ring_create(const char *name,
numa_node, action, eth_dev) < 0)
return -1;
+ /*
+ * For ATTACH, find the CREATE peer that shares our rings and
+ * establish a bidirectional peer link. This allows veth-like
+ * carrier detection: each side's link state reflects whether
+ * the other side is started.
+ */
+ if (action == DEV_ATTACH) {
+ struct pmd_internals *internals = (*eth_dev)->data->dev_private;
+ uint16_t pid;
+
+ RTE_ETH_FOREACH_DEV(pid) {
+ struct rte_eth_dev *candidate = &rte_eth_devices[pid];
+ struct pmd_internals *ci;
+
+ if (candidate == *eth_dev)
+ continue;
+ ci = candidate->data->dev_private;
+ if (ci == NULL || ci->action != DEV_CREATE)
+ continue;
+
+ /* Check if the candidate shares our rings */
+ if (ci->rx_ring_queues[0].rng == internals->rx_ring_queues[0].rng) {
+ internals->peer_port_id = pid;
+ ci->peer_port_id = (*eth_dev)->data->port_id;
+ break;
+ }
+ }
+ }
+
return 0;
}
diff --git a/drivers/net/ring/rte_eth_ring.h b/drivers/net/ring/rte_eth_ring.h
index 98292c7b33..5bb619198a 100644
--- a/drivers/net/ring/rte_eth_ring.h
+++ b/drivers/net/ring/rte_eth_ring.h
@@ -5,6 +5,7 @@
#ifndef _RTE_ETH_RING_H_
#define _RTE_ETH_RING_H_
+#include <rte_compat.h>
#include <rte_ring.h>
#ifdef __cplusplus
@@ -50,6 +51,31 @@ int rte_eth_from_rings(const char *name,
*/
int rte_eth_from_ring(struct rte_ring *r);
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * Attach two ring-backed ethdev ports as peers.
+ *
+ * After this call the link state of each port reflects whether its
+ * peer is started, similar to how carrier is handled on Linux veth
+ * devices. Stopping, closing, or setting link-down on one side will
+ * cause the other side to report link-down as well.
+ *
+ * Only ring-backed ports (created by rte_eth_from_rings or the
+ * net_ring vdev driver) can be paired. A port that already has a
+ * peer must be un-paired first (by closing or removing it).
+ *
+ * @param port_id_a
+ * port id of the first ring-backed ethdev
+ * @param port_id_b
+ * port id of the second ring-backed ethdev
+ * @return
+ * 0 on success, -1 on error (rte_errno is set).
+ */
+__rte_experimental
+int rte_eth_ring_attach_peer(uint16_t port_id_a, uint16_t port_id_b);
+
#ifdef __cplusplus
}
#endif
--
2.51.0
More information about the dev
mailing list