[PATCH v19 23/25] net/pcap: add EOF notification via link status change
Stephen Hemminger
stephen at networkplumber.org
Tue Mar 10 03:47:58 CET 2026
Add an "eof" devarg for rx_pcap mode that signals end-of-file by
setting link down and generating an LSC event. This allows
applications to detect when a pcap file has been fully consumed
using the standard ethdev callback mechanism.
The eof and infinite_rx options are mutually exclusive. On device
restart, the EOF state is reset so the file can be replayed.
Signed-off-by: Stephen Hemminger <stephen at networkplumber.org>
---
doc/guides/nics/pcap.rst | 12 ++++
doc/guides/rel_notes/release_26_03.rst | 2 +
drivers/net/pcap/pcap_ethdev.c | 80 +++++++++++++++++++++++++-
3 files changed, 91 insertions(+), 3 deletions(-)
diff --git a/doc/guides/nics/pcap.rst b/doc/guides/nics/pcap.rst
index 72f2250790..18a9a04652 100644
--- a/doc/guides/nics/pcap.rst
+++ b/doc/guides/nics/pcap.rst
@@ -161,6 +161,18 @@ Runtime Config Options
so all queues on a device will either have this enabled or disabled.
This option should only be provided once per device.
+* Signal end-of-file via link status change
+
+ In case ``rx_pcap=`` configuration is set, the user may want to be notified when
+ all packets in the pcap file have been read. This can be done with the ``eof``
+ devarg, for example::
+
+ --vdev 'net_pcap0,rx_pcap=file_rx.pcap,eof=1'
+
+ When enabled, the driver sets link down and generates an LSC event at end of file.
+ If the device is stopped and restarted, the EOF state is reset.
+ This option cannot be combined with ``infinite_rx``.
+
* Drop all packets on transmit
To drop all packets on transmit for a device,
diff --git a/doc/guides/rel_notes/release_26_03.rst b/doc/guides/rel_notes/release_26_03.rst
index c1bb808195..aa95b5885c 100644
--- a/doc/guides/rel_notes/release_26_03.rst
+++ b/doc/guides/rel_notes/release_26_03.rst
@@ -113,6 +113,8 @@ New Features
* Receive timestamps support nanosecond precision.
* Added ``snaplen`` devarg to configure packet capture snapshot length.
* Added support for Link State interrupt in ``iface`` mode.
+ * Added ``eof`` devarg to use link state to signal end of receive
+ file input.
Removed Items
diff --git a/drivers/net/pcap/pcap_ethdev.c b/drivers/net/pcap/pcap_ethdev.c
index 2d64032474..feefcf922d 100644
--- a/drivers/net/pcap/pcap_ethdev.c
+++ b/drivers/net/pcap/pcap_ethdev.c
@@ -42,6 +42,7 @@
#define ETH_PCAP_IFACE_ARG "iface"
#define ETH_PCAP_PHY_MAC_ARG "phy_mac"
#define ETH_PCAP_INFINITE_RX_ARG "infinite_rx"
+#define ETH_PCAP_EOF_ARG "eof"
#define ETH_PCAP_SNAPSHOT_LEN_ARG "snaplen"
#define ETH_PCAP_SNAPSHOT_LEN_DEFAULT 65535
@@ -115,6 +116,8 @@ struct pmd_internals {
bool single_iface;
bool phy_mac;
bool infinite_rx;
+ bool eof;
+ RTE_ATOMIC(bool) eof_signaled;
bool vlan_strip;
bool rx_scatter;
bool timestamp_offloading;
@@ -150,6 +153,7 @@ struct pmd_devargs_all {
bool is_rx_pcap;
bool is_rx_iface;
bool infinite_rx;
+ bool eof;
};
static const char *valid_arguments[] = {
@@ -161,6 +165,7 @@ static const char *valid_arguments[] = {
ETH_PCAP_IFACE_ARG,
ETH_PCAP_PHY_MAC_ARG,
ETH_PCAP_INFINITE_RX_ARG,
+ ETH_PCAP_EOF_ARG,
ETH_PCAP_SNAPSHOT_LEN_ARG,
NULL
};
@@ -308,15 +313,33 @@ eth_pcap_rx_infinite(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
return i;
}
+/*
+ * Deferred EOF alarm callback.
+ *
+ * Scheduled from the RX burst path when end-of-file is reached,
+ * so that rte_eth_dev_callback_process() runs outside the datapath.
+ * This avoids holding any locks that the application callback
+ * might also need, preventing potential deadlocks.
+ */
+static void
+eth_pcap_eof_alarm(void *arg)
+{
+ struct rte_eth_dev *dev = arg;
+
+ rte_eth_dev_callback_process(dev, RTE_ETH_EVENT_INTR_LSC, NULL);
+}
+
static uint16_t
eth_pcap_rx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
{
+ struct pcap_rx_queue *pcap_q = queue;
+ struct rte_eth_dev *dev = &rte_eth_devices[pcap_q->port_id];
+ struct pmd_internals *internals = dev->data->dev_private;
unsigned int i;
struct pcap_pkthdr *header;
struct pmd_process_private *pp;
const u_char *packet;
struct rte_mbuf *mbuf;
- struct pcap_rx_queue *pcap_q = queue;
uint16_t num_rx = 0;
uint32_t rx_bytes = 0;
pcap_t *pcap;
@@ -337,6 +360,23 @@ eth_pcap_rx(void *queue, struct rte_mbuf **bufs, uint16_t nb_pkts)
if (ret == PCAP_ERROR)
pcap_q->rx_stat.err_pkts++;
+ /*
+ * EOF: if eof mode is enabled, set link down and
+ * defer notification via alarm to avoid calling
+ * rte_eth_dev_callback_process() from the datapath.
+ */
+ else if (ret == PCAP_ERROR_BREAK) {
+ bool expected = false;
+
+ if (internals->eof &&
+ rte_atomic_compare_exchange_strong_explicit(
+ &internals->eof_signaled, &expected, true,
+ rte_memory_order_relaxed, rte_memory_order_relaxed)) {
+ eth_link_update(dev, 0);
+ rte_eal_alarm_set(1, eth_pcap_eof_alarm, dev);
+ }
+ }
+
break;
}
@@ -875,6 +915,7 @@ eth_dev_start(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_STARTED;
+ rte_atomic_store_explicit(&internals->eof_signaled, false, rte_memory_order_relaxed);
dev->data->dev_link.link_status = RTE_ETH_LINK_UP;
/* Start LSC polling for iface mode if application requested it */
@@ -898,6 +939,7 @@ eth_dev_stop(struct rte_eth_dev *dev)
unsigned int i;
struct pmd_internals *internals = dev->data->dev_private;
struct pmd_process_private *pp = dev->process_private;
+ bool expected;
/* Special iface case. Single pcap is open and shared between tx/rx. */
if (internals->single_iface) {
@@ -937,6 +979,13 @@ eth_dev_stop(struct rte_eth_dev *dev)
}
status_down:
+ /* Cancel any pending EOF alarm */
+ expected = true;
+ if (rte_atomic_compare_exchange_strong_explicit(
+ &internals->eof_signaled, &expected, false,
+ rte_memory_order_relaxed, rte_memory_order_relaxed))
+ rte_eal_alarm_cancel(eth_pcap_eof_alarm, dev);
+
for (i = 0; i < dev->data->nb_rx_queues; i++)
dev->data->rx_queue_state[i] = RTE_ETH_QUEUE_STATE_STOPPED;
@@ -1121,9 +1170,10 @@ eth_link_update(struct rte_eth_dev *dev, int wait_to_complete __rte_unused)
if (internals->single_iface) {
link.link_status = (osdep_iface_link_status(internals->rx_queue[0].name) > 0) ?
RTE_ETH_LINK_UP : RTE_ETH_LINK_DOWN;
+ } else if (rte_atomic_load_explicit(&internals->eof_signaled, rte_memory_order_relaxed)) {
+ link.link_status = RTE_ETH_LINK_DOWN;
} else {
- link.link_status = dev->data->dev_started ?
- RTE_ETH_LINK_UP : RTE_ETH_LINK_DOWN;
+ link.link_status = dev->data->dev_started ? RTE_ETH_LINK_UP : RTE_ETH_LINK_DOWN;
}
return rte_eth_linkstatus_set(dev, &link);
@@ -1715,8 +1765,13 @@ eth_from_pcaps(struct rte_vdev_device *vdev,
}
internals->infinite_rx = infinite_rx;
+ internals->eof = devargs_all->eof;
internals->snapshot_len = devargs_all->snapshot_len;
+ /* Enable LSC for eof mode (already set above for single_iface) */
+ if (internals->eof)
+ eth_dev->data->dev_flags |= RTE_ETH_DEV_INTR_LSC;
+
/* Assign rx ops. */
if (infinite_rx)
eth_dev->rx_pkt_burst = eth_pcap_rx_infinite;
@@ -1912,6 +1967,24 @@ pmd_pcap_probe(struct rte_vdev_device *dev)
"for %s", name);
}
+ /*
+ * Check whether to signal EOF via link status change.
+ */
+ if (rte_kvargs_count(kvlist, ETH_PCAP_EOF_ARG) == 1) {
+ ret = rte_kvargs_process(kvlist, ETH_PCAP_EOF_ARG,
+ &process_bool_flag,
+ &devargs_all.eof);
+ if (ret < 0)
+ goto free_kvlist;
+ }
+
+ if (devargs_all.infinite_rx && devargs_all.eof) {
+ PMD_LOG(ERR, "Cannot use both infinite_rx and eof for %s",
+ name);
+ ret = -EINVAL;
+ goto free_kvlist;
+ }
+
ret = rte_kvargs_process(kvlist, ETH_PCAP_RX_PCAP_ARG,
&open_rx_pcap, &pcaps);
} else if (devargs_all.is_rx_iface) {
@@ -2051,4 +2124,5 @@ RTE_PMD_REGISTER_PARAM_STRING(net_pcap,
ETH_PCAP_IFACE_ARG "=<ifc> "
ETH_PCAP_PHY_MAC_ARG "=<0|1> "
ETH_PCAP_INFINITE_RX_ARG "=<0|1> "
+ ETH_PCAP_EOF_ARG "=<0|1> "
ETH_PCAP_SNAPSHOT_LEN_ARG "=<int>");
--
2.51.0
More information about the dev
mailing list