[PATCH v15 16/18] net/pcap: add link status change support for iface mode

Stephen Hemminger
2026-02-13T17:05:42Z

Add LSC interrupt support for pass-through (iface=) mode so
applications can receive link state change notifications via
the standard ethdev callback mechanism.

Uses alarm-based polling to periodically check the underlying
interface state via osdep_iface_link_get(). The LSC flag is
advertised only for iface mode devices, and polling is gated
on the application enabling intr_conf.lsc in port configuration.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
doc/guides/nics/features/pcap.ini | 1 +
doc/guides/rel_notes/release_26_03.rst | 1 +
drivers/net/pcap/pcap_ethdev.c | 45 +++++++++++++++++++++++++-
3 files changed, 46 insertions(+), 1 deletion(-)

diff --git a/doc/guides/nics/features/pcap.ini b/doc/guides/nics/features/pcap.ini
index 814bc2119f..084fefbbbb 100644
--- a/doc/guides/nics/features/pcap.ini
+++ b/doc/guides/nics/features/pcap.ini
@@ -5,6 +5,7 @@
;
[Features]
Link status = Y
+Link status event = Y
Queue start/stop = Y
Scattered Rx = Y
Timestamp offload = Y
diff --git a/doc/guides/rel_notes/release_26_03.rst b/doc/guides/rel_notes/release_26_03.rst
index 70179a454e..1db54a005a 100644
--- a/doc/guides/rel_notes/release_26_03.rst
+++ b/doc/guides/rel_notes/release_26_03.rst
@@ -94,6 +94,7 @@ New Features
* Receive timestamp offload is only done if offload flag set.
* Receive timestamps support nanosecond precision.
* Added ``snaplen`` devarg to configure packet capture snapshot length.
+ * Added support for Link State interrupt in ``iface`` mode.

Removed Items
diff --git a/drivers/net/pcap/pcap_ethdev.c b/drivers/net/pcap/pcap_ethdev.c
index e9200f9aac..00f6e4e33d 100644
--- a/drivers/net/pcap/pcap_ethdev.c
+++ b/drivers/net/pcap/pcap_ethdev.c
@@ -17,6 +17,7 @@
#include <net/if.h>
#include <pcap.h>

+#include <rte_alarm.h>
#include <rte_cycles.h>
#include <rte_ring.h>
#include <rte_ethdev.h>
@@ -48,6 +49,8 @@
/* This is defined in libpcap but not exposed in headers */
#define ETH_PCAP_MAXIMUM_SNAPLEN 262144

+#define ETH_PCAP_LSC_POLL_INTERVAL_US (1000 * 1000) /* 1 second */
+
#define ETH_PCAP_ARG_MAXLEN 64

#define RTE_PMD_PCAP_MAX_QUEUES 16
@@ -113,6 +116,7 @@ struct pmd_internals {
bool infinite_rx;
bool vlan_strip;
bool timestamp_offloading;
+ bool lsc_active;
};

struct pmd_process_private {
@@ -159,9 +163,10 @@ static const char *valid_arguments[] = {

RTE_LOG_REGISTER_DEFAULT(eth_pcap_logtype, NOTICE);

-/* Forward declaration */
+/* Forward declarations */
static inline int set_iface_direction(const char *iface, pcap_t *pcap,
pcap_direction_t direction);
+static int eth_link_update(struct rte_eth_dev *dev, int wait_to_complete);

static struct queue_missed_stat*
queue_missed_stat_update(struct rte_eth_dev *dev, unsigned int qid)
@@ -783,6 +788,28 @@ count_packets_in_pcap(pcap_t **pcap, struct pcap_rx_queue *pcap_q)
return pcap_pkt_count;
}

+/*
+ * Periodic alarm to poll link state.
+ * Enabled when link state interrupt is enabled in single_iface mode.
+ */
+static void
+eth_pcap_lsc_alarm(void *arg)
+{
+ struct rte_eth_dev *dev = arg;
+ struct pmd_internals *internals = dev->data->dev_private;
+ struct rte_eth_link old_link, new_link;
+
+ rte_eth_linkstatus_get(dev, &old_link);
+ eth_link_update(dev, 0);
+ rte_eth_linkstatus_get(dev, &new_link);
+
+ if (old_link.link_status != new_link.link_status)
+ rte_eth_dev_callback_process(dev, RTE_ETH_EVENT_INTR_LSC, NULL);
+
+ if (internals->lsc_active)
+ rte_eal_alarm_set(ETH_PCAP_LSC_POLL_INTERVAL_US, eth_pcap_lsc_alarm, dev);
+}
+
static int
eth_dev_start(struct rte_eth_dev *dev)
{
@@ -850,6 +877,13 @@ eth_dev_start(struct rte_eth_dev *dev)

dev->data->dev_link.link_status = RTE_ETH_LINK_UP;

+ /* Start LSC polling for iface mode if application requested it */
+ if (internals->single_iface && dev->data->dev_conf.intr_conf.lsc) {
+ internals->lsc_active = true;
+ rte_eal_alarm_set(ETH_PCAP_LSC_POLL_INTERVAL_US,
+ eth_pcap_lsc_alarm, dev);
+ }
+
return 0;
}

@@ -867,6 +901,12 @@ eth_dev_stop(struct rte_eth_dev *dev)

/* Special iface case. Single pcap is open and shared between tx/rx. */
if (internals->single_iface) {
+ /* Cancel LSC polling before closing pcap handles */
+ if (internals->lsc_active) {
+ internals->lsc_active = false;
+ rte_eal_alarm_cancel(eth_pcap_lsc_alarm, dev);
+ }
+
queue_missed_stat_on_stop_update(dev, 0);
if (pp->tx_pcap[0] != NULL) {
pcap_close(pp->tx_pcap[0]);
@@ -1704,6 +1744,9 @@ eth_from_pcaps(struct rte_vdev_device *vdev,
internals->if_index =
osdep_iface_index_get(rx_queues->queue[0].name);

+ /* Enable LSC interrupt support for iface mode */
+ eth_dev->data->dev_flags |= RTE_ETH_DEV_INTR_LSC;
+
/* phy_mac arg is applied only if "iface" devarg is provided */
if (rx_queues->phy_mac) {
if (eth_pcap_update_mac(rx_queues->queue[0].name,
--
2.51.0