[PATCH v3 net-next 14/15] net: enetc: add PSI-to-VSI link status notification support for VF

From: wei . fang

Date: Sun Aug 30 2026 - 23:31:55 EST


From: Wei Fang <wei.fang@xxxxxxx>

Add infrastructure for ENETC v4 VFs to track PF link status changes via
the PSI-to-VSI messaging channel. Two new ops,
vf_reg_link_status_notifier and vf_unreg_link_status_notifier, are added
to enetc_si_ops and wired into enetc_phylink_connect() and enetc_close()
for the phy-less path. The feature is populated only in enetc4_vsi_ops;
rev1 hardware is not affected.

On enetc_open(), the VF sends a REGISTER_LINK_CHANGE_NOTIFIER message to
the PF through the VSI-to-PSI messaging channel. The PF records the VF
in link_status_ms_mask, and immediately sends the current link status so
that the VF carrier quickly reflects the link status as soon as the
interface comes up. On every subsequent PF link transition the PF
broadcasts a 16-bit notification to all registered VFs.

On the VF side, a dedicated MSI-X vector handles incoming PSI-to-VSI
messages. The interrupt handler schedules a work item which parses the
notification and updates the carrier state via netif_carrier_on() or
netif_carrier_off() accordingly.

Signed-off-by: Wei Fang <wei.fang@xxxxxxx>
---
drivers/net/ethernet/freescale/enetc/enetc.c | 42 +++-
drivers/net/ethernet/freescale/enetc/enetc.h | 6 +
.../net/ethernet/freescale/enetc/enetc_hw.h | 9 +
.../net/ethernet/freescale/enetc/enetc_vf.c | 219 ++++++++++++++++++
4 files changed, 275 insertions(+), 1 deletion(-)

diff --git a/drivers/net/ethernet/freescale/enetc/enetc.c b/drivers/net/ethernet/freescale/enetc/enetc.c
index 803c5c541a5c..fb5df740650e 100644
--- a/drivers/net/ethernet/freescale/enetc/enetc.c
+++ b/drivers/net/ethernet/freescale/enetc/enetc.c
@@ -2935,11 +2935,31 @@ static void enetc_clear_interrupts(struct enetc_ndev_priv *priv)
static int enetc_phylink_connect(struct net_device *ndev)
{
struct enetc_ndev_priv *priv = netdev_priv(ndev);
+ struct enetc_si *si = priv->si;
struct ethtool_keee edata;
int err;

if (!priv->phylink) {
/* phy-less mode */
+ if (!si->ops->vf_reg_link_status_notifier)
+ goto carrier_on;
+
+ /* For phy-less VFs on ENETC v4, attempt to register a link
+ * status notifier with the PF via the VSI-to-PSI messaging
+ * channel. If registration succeeds, the PF will immediately
+ * send the current link status and broadcast future link
+ * transitions; carrier state is then managed in
+ * enetc_vf_msg_handle_link_status(). If registration fails,
+ * fall back to the LS1028A behaviour and assert carrier
+ * unconditionally via netif_carrier_on().
+ */
+ if (!si->ops->vf_reg_link_status_notifier(si))
+ return 0;
+
+ dev_warn(&ndev->dev,
+ "Link status notifier registration failed\n");
+
+carrier_on:
netif_carrier_on(ndev);
return 0;
}
@@ -3011,6 +3031,7 @@ int enetc_open(struct net_device *ndev)
{
struct enetc_ndev_priv *priv = netdev_priv(ndev);
struct enetc_bdr_resource *tx_res, *rx_res;
+ struct enetc_si *si = priv->si;
bool extended;
int err;

@@ -3051,8 +3072,15 @@ int enetc_open(struct net_device *ndev)
err_alloc_rx:
enetc_free_tx_resources(tx_res, priv->num_tx_rings);
err_alloc_tx:
- if (priv->phylink)
+ if (priv->phylink) {
phylink_disconnect_phy(priv->phylink);
+ } else if (si->ops->vf_unreg_link_status_notifier &&
+ test_bit(ENETC_LINK_STATUS_NOTIFIER_REGISTERED,
+ &priv->flags)) {
+ if (si->ops->vf_unreg_link_status_notifier(si))
+ dev_warn(&ndev->dev,
+ "Link status notifier unregistration failed\n");
+ }
err_phy_connect:
enetc_free_irqs(priv);
err_setup_irqs:
@@ -3093,6 +3121,7 @@ EXPORT_SYMBOL_GPL(enetc_stop);
int enetc_close(struct net_device *ndev)
{
struct enetc_ndev_priv *priv = netdev_priv(ndev);
+ struct enetc_si *si = priv->si;

enetc_stop(ndev);

@@ -3100,6 +3129,17 @@ int enetc_close(struct net_device *ndev)
phylink_stop(priv->phylink);
phylink_disconnect_phy(priv->phylink);
} else {
+ if (!si->ops->vf_unreg_link_status_notifier ||
+ !test_bit(ENETC_LINK_STATUS_NOTIFIER_REGISTERED,
+ &priv->flags))
+ goto carrier_off;
+
+ if (!si->ops->vf_unreg_link_status_notifier(si))
+ goto carrier_off;
+
+ dev_warn(&ndev->dev,
+ "Link status notifier unregistration failed\n");
+carrier_off:
netif_carrier_off(ndev);
}

diff --git a/drivers/net/ethernet/freescale/enetc/enetc.h b/drivers/net/ethernet/freescale/enetc/enetc.h
index 8d7c1790b672..52bd502976ee 100644
--- a/drivers/net/ethernet/freescale/enetc/enetc.h
+++ b/drivers/net/ethernet/freescale/enetc/enetc.h
@@ -300,6 +300,10 @@ struct enetc_si_ops {
int (*set_rss_table)(struct enetc_si *si, const u32 *table, int count);
int (*setup_cbdr)(struct enetc_si *si);
void (*teardown_cbdr)(struct enetc_si *si);
+
+ /* VSI-specific hooks */
+ int (*vf_reg_link_status_notifier)(struct enetc_si *si);
+ int (*vf_unreg_link_status_notifier)(struct enetc_si *si);
};

/* PCI IEP device data */
@@ -334,6 +338,7 @@ struct enetc_si {

struct dentry *debugfs_root;
struct enetc_msg_swbd msg; /* Only valid for VSI */
+ struct workqueue_struct *workqueue;
struct work_struct msg_task;
char msg_int_name[ENETC_INT_NAME_MAX];

@@ -429,6 +434,7 @@ enum enetc_flags_bit {
ENETC_TX_ONESTEP_TSTAMP_IN_PROGRESS = 0,
ENETC_TX_DOWN,
ENETC_RXBDR_CM,
+ ENETC_LINK_STATUS_NOTIFIER_REGISTERED,
};

/* interrupt coalescing modes */
diff --git a/drivers/net/ethernet/freescale/enetc/enetc_hw.h b/drivers/net/ethernet/freescale/enetc/enetc_hw.h
index c18ad8b9b071..2c9d9042eb0b 100644
--- a/drivers/net/ethernet/freescale/enetc/enetc_hw.h
+++ b/drivers/net/ethernet/freescale/enetc/enetc_hw.h
@@ -85,6 +85,9 @@ static inline u32 enetc_vsi_set_msize(u32 size)
#define PSIMSGSR_MS(n) BIT((n) + 1)
#define PSIMSGSR_MC GENMASK(31, 16)

+#define ENETC_VSIMSGRR 0x208
+#define VSIMSGRR_MC GENMASK(31, 16)
+
/* SI statistics */
#define ENETC_SIROCT 0x300
#define ENETC_SIRFRM 0x308
@@ -108,6 +111,12 @@ static inline u32 enetc_vsi_set_msize(u32 size)
#define ENETC_SICAPR0 0x900
#define ENETC_SICAPR1 0x904

+#define ENETC_VSIIER 0xa00
+#define VSIIER_MRIE BIT(9)
+
+#define ENETC_VSIIDR 0xa08
+#define VSIIDR_MR BIT(9)
+
#define ENETC_PSIIER 0xa00
#define ENETC_PSIIDR 0xa08

diff --git a/drivers/net/ethernet/freescale/enetc/enetc_vf.c b/drivers/net/ethernet/freescale/enetc/enetc_vf.c
index 4e717afba7f7..a4d0089ef1a9 100644
--- a/drivers/net/ethernet/freescale/enetc/enetc_vf.c
+++ b/drivers/net/ethernet/freescale/enetc/enetc_vf.c
@@ -135,6 +135,52 @@ static int enetc_msg_vsi_send(struct enetc_si *si, struct enetc_msg_swbd *msg)
return err;
}

+static int enetc_msg_link_status_notifier(struct enetc_si *si, bool reg)
+{
+ struct device *dev = &si->pdev->dev;
+ struct enetc_msg_swbd msg_swbd;
+ u8 cmd_id;
+
+ msg_swbd.size = ALIGN(sizeof(struct enetc_msg_generic),
+ ENETC_MSG_ALIGN);
+ msg_swbd.vaddr = dma_alloc_coherent(dev, msg_swbd.size,
+ &msg_swbd.dma, GFP_KERNEL);
+ if (!msg_swbd.vaddr)
+ return -ENOMEM;
+
+ cmd_id = reg ? ENETC_MSG_REGISTER_LINK_CHANGE_NOTIFIER :
+ ENETC_MSG_UNREGISTER_LINK_CHANGE_NOTIFIER;
+ enetc_msg_fill_common_hdr(&msg_swbd, ENETC_MSG_CLASS_ID_LINK_STATUS,
+ cmd_id, 0, 0);
+
+ return enetc_msg_vsi_send(si, &msg_swbd);
+}
+
+static int enetc_vf_reg_link_status_notifier(struct enetc_si *si)
+{
+ struct enetc_ndev_priv *priv = netdev_priv(si->ndev);
+ int err;
+
+ err = enetc_msg_link_status_notifier(si, true);
+ if (!err)
+ set_bit(ENETC_LINK_STATUS_NOTIFIER_REGISTERED, &priv->flags);
+
+ return err;
+}
+
+static int enetc_vf_unreg_link_status_notifier(struct enetc_si *si)
+{
+ struct enetc_ndev_priv *priv = netdev_priv(si->ndev);
+ int err;
+
+ err = enetc_msg_link_status_notifier(si, false);
+ if (!err)
+ clear_bit(ENETC_LINK_STATUS_NOTIFIER_REGISTERED,
+ &priv->flags);
+
+ return err;
+}
+
static int enetc_msg_vsi_set_primary_mac_addr(struct enetc_ndev_priv *priv,
struct sockaddr *saddr)
{
@@ -435,6 +481,128 @@ static void enetc_vf_netdev_setup(struct enetc_si *si, struct net_device *ndev,
enetc_load_primary_mac_addr(&si->hw, ndev);
}

+static void enetc_vf_enable_mr_int(struct enetc_si *si)
+{
+ if (is_enetc_rev1(si))
+ return;
+
+ enetc_wr(&si->hw, ENETC_VSIIER, VSIIER_MRIE);
+}
+
+static void enetc_vf_disable_mr_int(struct enetc_si *si)
+{
+ if (is_enetc_rev1(si))
+ return;
+
+ enetc_wr(&si->hw, ENETC_VSIIER, 0);
+}
+
+static void enetc_vf_msg_handle_link_status(struct enetc_si *si, u8 status)
+{
+ bool tx_pause = !!(status & ENETC_CLASS_CODE_TX_PAUSE_EN);
+ bool link_down = !!(status & ENETC_CLASS_CODE_LINK_DOWN);
+ struct enetc_ndev_priv *priv = netdev_priv(si->ndev);
+ struct net_device *ndev = si->ndev;
+
+ rtnl_lock();
+ if (!netif_running(ndev))
+ goto unlock_rtnl;
+
+ if (link_down) {
+ if (netif_carrier_ok(ndev)) {
+ netif_carrier_off(ndev);
+ netdev_info(ndev, "Link is Down\n");
+ }
+
+ goto unlock_rtnl;
+ }
+
+ /* Link is up */
+ enetc_set_congestion_mode(priv, tx_pause);
+
+ if (!netif_carrier_ok(ndev)) {
+ netif_carrier_on(ndev);
+ netdev_info(ndev, "Link is Up, tx pause %s\n",
+ tx_pause ? "on" : "off");
+ }
+
+unlock_rtnl:
+ rtnl_unlock();
+}
+
+static void enetc_vf_msg_task(struct work_struct *work)
+{
+ struct enetc_si *si = container_of(work, struct enetc_si, msg_task);
+ struct enetc_hw *hw = &si->hw;
+ u8 class_id, class_code;
+ u16 pf_msg;
+
+ /* W1C to clear the message received interrupt event */
+ enetc_wr(hw, ENETC_VSIIDR, VSIIDR_MR);
+
+ /* Reading VSIMSGRR retrieves the message data and acknowledges to
+ * the PF that the message was received and another message can be
+ * sent.
+ */
+ pf_msg = FIELD_GET(VSIMSGRR_MC, enetc_rd(hw, ENETC_VSIMSGRR));
+ class_id = FIELD_GET(ENETC_PF_MSG_CLASS_ID, pf_msg);
+
+ switch (class_id) {
+ case ENETC_MSG_CLASS_ID_LINK_STATUS:
+ class_code = FIELD_GET(ENETC_PF_MSG_CLASS_CODE_U8, pf_msg);
+ enetc_vf_msg_handle_link_status(si, class_code);
+ break;
+ default:
+ dev_err(&si->pdev->dev,
+ "Unsupported Message Class ID (0x%02x) from PF\n",
+ class_id);
+ }
+
+ enetc_vf_enable_mr_int(si);
+}
+
+static irqreturn_t enetc_vf_msg_msix_handler(int irq, void *data)
+{
+ struct enetc_si *si = (struct enetc_si *)data;
+
+ enetc_vf_disable_mr_int(si);
+ queue_work(si->workqueue, &si->msg_task);
+
+ return IRQ_HANDLED;
+}
+
+static int enetc_vf_register_msg_msix(struct enetc_si *si)
+{
+ int irq, err;
+
+ if (is_enetc_rev1(si))
+ return 0;
+
+ snprintf(si->msg_int_name, sizeof(si->msg_int_name), "%s-pfmsg",
+ pci_name(si->pdev));
+ irq = pci_irq_vector(si->pdev, ENETC_SI_INT_IDX);
+ err = request_irq(irq, enetc_vf_msg_msix_handler, 0,
+ si->msg_int_name, si);
+ if (err) {
+ dev_err(&si->pdev->dev,
+ "VF messaging: request_irq() failed!\n");
+ return err;
+ }
+
+ /* set one IRQ entry for PSI-to-VSI messaging */
+ enetc_wr(&si->hw, ENETC_SIMSIVR, ENETC_SI_INT_IDX);
+
+ return 0;
+}
+
+static void enetc_vf_free_msg_msix(struct enetc_si *si)
+{
+ if (is_enetc_rev1(si))
+ return;
+
+ free_irq(pci_irq_vector(si->pdev, ENETC_SI_INT_IDX), si);
+}
+
static const struct enetc_si_ops enetc_vsi_ops = {
.get_rss_table = enetc_get_rss_table,
.set_rss_table = enetc_set_rss_table,
@@ -447,8 +615,38 @@ static const struct enetc_si_ops enetc4_vsi_ops = {
.set_rss_table = enetc4_set_rss_table,
.setup_cbdr = enetc4_setup_cbdr,
.teardown_cbdr = enetc4_teardown_cbdr,
+ .vf_reg_link_status_notifier = enetc_vf_reg_link_status_notifier,
+ .vf_unreg_link_status_notifier = enetc_vf_unreg_link_status_notifier,
};

+static int enetc_vf_wq_task_init(struct enetc_si *si)
+{
+ if (is_enetc_rev1(si))
+ return 0;
+
+ si->workqueue = alloc_ordered_workqueue("enetc-%s-wq", WQ_MEM_RECLAIM,
+ pci_name(si->pdev));
+ if (!si->workqueue)
+ return -ENOMEM;
+
+ INIT_WORK(&si->msg_task, enetc_vf_msg_task);
+
+ return 0;
+}
+
+static void enetc_vf_wq_task_destroy(struct enetc_si *si)
+{
+ if (!si->workqueue)
+ return;
+
+ disable_work_sync(&si->msg_task);
+
+ /* The MR interrupt may be re-enabled by si->msg_task */
+ enetc_vf_disable_mr_int(si);
+
+ destroy_workqueue(si->workqueue);
+}
+
static int enetc_vf_probe(struct pci_dev *pdev,
const struct pci_device_id *ent)
{
@@ -520,15 +718,33 @@ static int enetc_vf_probe(struct pci_dev *pdev,
goto err_alloc_msix;
}

+ err = enetc_vf_wq_task_init(si);
+ if (err) {
+ dev_err(&pdev->dev, "Failed to init workqueue\n");
+ goto err_wq_init;
+ }
+
+ err = enetc_vf_register_msg_msix(si);
+ if (err) {
+ dev_err(&pdev->dev, "Failed to register msg irq\n");
+ goto err_register_msg_msix;
+ }
+
err = register_netdev(ndev);
if (err)
goto err_reg_netdev;

+ /* Enable message received interrupt */
+ enetc_vf_enable_mr_int(si);
netif_carrier_off(ndev);

return 0;

err_reg_netdev:
+ enetc_vf_free_msg_msix(si);
+err_register_msg_msix:
+ enetc_vf_wq_task_destroy(si);
+err_wq_init:
enetc_free_msix(priv);
err_config_si:
err_alloc_msix:
@@ -554,8 +770,11 @@ static void enetc_vf_remove(struct pci_dev *pdev)
struct enetc_msg_swbd msg;

priv = netdev_priv(si->ndev);
+ enetc_vf_disable_mr_int(si);
unregister_netdev(si->ndev);

+ enetc_vf_free_msg_msix(si);
+ enetc_vf_wq_task_destroy(si);
enetc_free_msix(priv);

enetc_free_si_resources(priv);
--
2.34.1