[PATCH v2 net-next 14/15] net: enetc: add PSI-to-VSI link status notification support for VF
From: wei . fang
Date: Wed Jun 10 2026 - 05:53:06 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 | 28 ++-
drivers/net/ethernet/freescale/enetc/enetc.h | 5 +
.../net/ethernet/freescale/enetc/enetc_hw.h | 9 +
.../net/ethernet/freescale/enetc/enetc_vf.c | 209 +++++++++++++++++-
4 files changed, 249 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/freescale/enetc/enetc.c b/drivers/net/ethernet/freescale/enetc/enetc.c
index fdceaf36daa7..fd5885b0fa81 100644
--- a/drivers/net/ethernet/freescale/enetc/enetc.c
+++ b/drivers/net/ethernet/freescale/enetc/enetc.c
@@ -2892,11 +2892,20 @@ 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) {
+ if (!si->ops->vf_reg_link_status_notifier(si))
+ return 0;
+
+ dev_warn(&ndev->dev,
+ "Link status notifier registration failed\n");
+ }
+
netif_carrier_on(ndev);
return 0;
}
@@ -2968,6 +2977,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;
@@ -3008,8 +3018,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:
@@ -3050,6 +3067,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);
@@ -3057,6 +3075,14 @@ 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)) {
+ if (si->ops->vf_unreg_link_status_notifier(si))
+ dev_warn(&ndev->dev,
+ "Link status notifier unregistration failed\n");
+ }
+
netif_carrier_off(ndev);
}
diff --git a/drivers/net/ethernet/freescale/enetc/enetc.h b/drivers/net/ethernet/freescale/enetc/enetc.h
index 24d9f89aee73..a8c845e5f863 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 */
@@ -423,6 +427,7 @@ enum enetc_active_offloads {
enum enetc_flags_bit {
ENETC_TX_ONESTEP_TSTAMP_IN_PROGRESS = 0,
ENETC_TX_DOWN,
+ 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 47de179e17c8..78282dbbcfa3 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 dc9a0f74a2e3..ba7686fb32ce 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)
{
@@ -415,6 +461,122 @@ 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_hw *hw)
+{
+ u32 val = enetc_rd(hw, ENETC_VSIIER) | VSIIER_MRIE;
+
+ enetc_wr(hw, ENETC_VSIIER, val);
+}
+
+static void enetc_vf_disable_mr_int(struct enetc_hw *hw)
+{
+ u32 val = enetc_rd(hw, ENETC_VSIIER) & (~VSIIER_MRIE);
+
+ enetc_wr(hw, ENETC_VSIIER, val);
+}
+
+static void enetc_vf_msg_handle_link_status(struct enetc_si *si, u8 class_code)
+{
+ struct net_device *ndev = si->ndev;
+
+ switch (class_code) {
+ case ENETC_LINK_STATUS_CLASS_CODE_UP:
+ if (netif_running(ndev) && !netif_carrier_ok(ndev)) {
+ netif_carrier_on(ndev);
+ netdev_info(ndev, "Link is Up\n");
+ }
+ break;
+ case ENETC_LINK_STATUS_CLASS_CODE_DOWN:
+ if (netif_carrier_ok(ndev)) {
+ netif_carrier_off(ndev);
+ netdev_info(ndev, "Link is Down\n");
+ }
+ break;
+ }
+}
+
+static void enetc_vf_msg_task(struct work_struct *work)
+{
+ struct enetc_si *si = container_of(work, struct enetc_si, msg_task);
+ struct net_device *ndev = si->ndev;
+ struct enetc_hw *hw = &si->hw;
+ u8 class_id, class_code;
+ u16 pf_msg;
+
+ pf_msg = FIELD_GET(VSIMSGRR_MC, enetc_rd(hw, ENETC_VSIMSGRR));
+ /* W1C to clear the message received interrupt event */
+ enetc_wr(hw, ENETC_VSIIDR, VSIIDR_MR);
+
+ /* If ndev is in the NETREG_UNREGISTERED state, return directly
+ * without processing the message or enabling MR interrupt. Any
+ * future caller that reuses this work item after the device
+ * returns to a registered state should enable MR interrupt again.
+ */
+ if (ndev->reg_state == NETREG_UNREGISTERED)
+ return;
+
+ class_id = FIELD_GET(ENETC_PF_MSG_CLASS_ID, pf_msg);
+ class_code = FIELD_GET(ENETC_PF_MSG_CLASS_CODE, pf_msg);
+
+ switch (class_id) {
+ case ENETC_MSG_CLASS_ID_LINK_STATUS:
+ 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(hw);
+}
+
+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->hw);
+ 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);
+
+ /* Enable message received interrupt */
+ enetc_vf_enable_mr_int(&si->hw);
+
+ return 0;
+}
+
+static void enetc_vf_free_msg_msix(struct enetc_si *si)
+{
+ if (is_enetc_rev1(si))
+ return;
+
+ enetc_vf_disable_mr_int(&si->hw);
+ 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,
@@ -427,8 +589,36 @@ 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)
+{
+ char wq_name[24];
+
+ if (is_enetc_rev1(si))
+ return 0;
+
+ snprintf(wq_name, sizeof(wq_name), "enetc-%s", pci_name(si->pdev));
+ si->workqueue = create_singlethread_workqueue(wq_name);
+ 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);
+ destroy_workqueue(si->workqueue);
+}
+
static int enetc_vf_probe(struct pci_dev *pdev,
const struct pci_device_id *ent)
{
@@ -493,6 +683,18 @@ 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;
@@ -502,6 +704,10 @@ static int enetc_vf_probe(struct pci_dev *pdev,
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:
@@ -528,7 +734,8 @@ static void enetc_vf_remove(struct pci_dev *pdev)
priv = netdev_priv(si->ndev);
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