[PATCH v3 net-next 03/15] net: enetc: add link status message support to PF driver

From: wei . fang

Date: Sun Aug 30 2026 - 23:23:51 EST


From: Wei Fang <wei.fang@xxxxxxx>

Add link status message support to the PF driver using three command IDs
under message class 0x80 (ENETC_MSG_CLASS_ID_LINK_STATUS):

1. ENETC_MSG_GET_CURRENT_LINK_STATUS (cmd_id 0)
The VF queries the current PF link status synchronously. This command is
not used by the Linux VF driver but is intended for DPDK-owned VFs.

2. ENETC_MSG_REGISTER_LINK_CHANGE_NOTIFIER (cmd_id 1)
The VF registers for link change notification. Upon registration, the PF
immediately notifies the VF of the current link status via a PSI-to-VSI
message, and continues to do so on every subsequent link state change.

3. ENETC_MSG_UNREGISTER_LINK_CHANGE_NOTIFIER (cmd_id 2)
The VF unregisters from link change notification.

For link status message, the PSI-to-VSI message is 16 bits wide: the
upper 8 bits carry the message class ID, and the lower 8 bits carry the
class code. Bit 0 of the class code indicates the link state (1 = link
down, 0 = link up), and bit 1 indicates whether TX PAUSE is enabled on
the PF (1 = enabled, 0 = disabled).

The TX PAUSE state is included because VF RX BD rings support congestion
mode, but whether the hardware can actually send PAUSE frames depends on
whether TX PAUSE is enabled on the PF. By conveying the PF TX PAUSE state
in the link status message, the VF can determine whether to enable
congestion mode on its RX BD rings.

PSI-to-VSI notifications are sent via the ENETC_PSIMSGSR register. A new
msg_lock mutex is introduced in struct enetc_pf to protect concurrent
access between the phylink callbacks and the VSI-to-PSI message handler.
The bitmask link_status_ms_mask tracks which VFs have registered for
notifications and is cleared when SR-IOV is disabled.

Two functions are exported for use by PF drivers:
enetc_pf_notify_vf_link_up()
enetc_pf_notify_vf_link_down()

Through this mechanism, VFs can accurately perceive the link status and
report it to upper layers such as the kernel network stack, containers,
and virtual machines.

Note that currently only the ENETC v4 driver supports this feature, while
v1 does not.

Signed-off-by: Wei Fang <wei.fang@xxxxxxx>
---
.../net/ethernet/freescale/enetc/enetc4_pf.c | 2 +
.../net/ethernet/freescale/enetc/enetc_hw.h | 5 +
.../ethernet/freescale/enetc/enetc_mailbox.h | 26 ++-
.../net/ethernet/freescale/enetc/enetc_msg.c | 214 +++++++++++++++++-
.../net/ethernet/freescale/enetc/enetc_pf.h | 6 +
.../freescale/enetc/enetc_pf_common.c | 2 +
.../freescale/enetc/enetc_pf_common.h | 10 +
7 files changed, 256 insertions(+), 9 deletions(-)

diff --git a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c
index 935a6a03b14f..17fd9ee27942 100644
--- a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c
+++ b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c
@@ -899,6 +899,7 @@ static void enetc4_pl_mac_link_up(struct phylink_config *config,
enetc4_set_rx_pause(pf, rx_pause);
enetc4_mac_tx_enable(pf);
enetc4_mac_rx_enable(pf);
+ enetc_pf_notify_vf_link_up(pf);
}

static void enetc4_pl_mac_link_down(struct phylink_config *config,
@@ -907,6 +908,7 @@ static void enetc4_pl_mac_link_down(struct phylink_config *config,
{
struct enetc_pf *pf = phylink_to_enetc_pf(config);

+ enetc_pf_notify_vf_link_down(pf);
enetc4_mac_rx_graceful_stop(pf);
enetc4_mac_tx_graceful_stop(pf);
}
diff --git a/drivers/net/ethernet/freescale/enetc/enetc_hw.h b/drivers/net/ethernet/freescale/enetc/enetc_hw.h
index 16da732dc5de..f97602714118 100644
--- a/drivers/net/ethernet/freescale/enetc/enetc_hw.h
+++ b/drivers/net/ethernet/freescale/enetc/enetc_hw.h
@@ -80,6 +80,11 @@ static inline u32 enetc_vsi_set_msize(u32 size)
#define ENETC_SIMSGSR_SET_MC(val) ((val) << 16)
#define ENETC_SIMSGSR_GET_MC(val) ((val) >> 16)

+#define ENETC_PSIMSGSR 0x208
+/* n is VF index, which is less than 15 */
+#define PSIMSGSR_MS(n) BIT((n) + 1)
+#define PSIMSGSR_MC GENMASK(31, 16)
+
/* SI statistics */
#define ENETC_SIROCT 0x300
#define ENETC_SIRFRM 0x308
diff --git a/drivers/net/ethernet/freescale/enetc/enetc_mailbox.h b/drivers/net/ethernet/freescale/enetc/enetc_mailbox.h
index d9677da38989..846998f07989 100644
--- a/drivers/net/ethernet/freescale/enetc/enetc_mailbox.h
+++ b/drivers/net/ethernet/freescale/enetc/enetc_mailbox.h
@@ -66,8 +66,8 @@
* 2) PSI_TX_control: PSIMSGSR[MC] - for PSI to VSI notification messages
* (async mode)
*
- * Note that for some GET messages, there is no COOKIE field, and the CLASS
- * CODE field is expanded to 8 bits.
+ * Note that for some PSI-to-VSI messages, there is no COOKIE field, and the
+ * CLASS CODE field is expanded to 8 bits.
*/

#ifndef __ENETC_MAILBOX_H
@@ -87,7 +87,11 @@
/* The fileds of PSI-to-VSI message, the message is only 16-bit */
#define ENETC_PF_MSG_COOKIE GENMASK(3, 0)
#define ENETC_PF_MSG_CLASS_CODE GENMASK(7, 4)
-/* Extend the class code to 8-bit for GET messages without COOKIE */
+/* Extend the class code to 8-bit for PSI-to-VSI messages without COOKIE
+ * The class code for the following messages is 8-bit.
+ * 1. Get IP revision messages
+ * 2. Link status messages
+ */
#define ENETC_PF_MSG_CLASS_CODE_U8 GENMASK(7, 0)
#define ENETC_PF_MSG_CLASS_ID GENMASK(15, 8)

@@ -107,6 +111,7 @@ enum enetc_msg_class_id {

/* Common Class ID for PSI-to-VSI and VSI-to-PSI messages */
ENETC_MSG_CLASS_ID_MAC_FILTER = 0x20,
+ ENETC_MSG_CLASS_ID_LINK_STATUS = 0x80,
ENETC_MSG_CLASS_ID_IP_REVISION = 0xf0,
};

@@ -118,11 +123,21 @@ enum enetc_msg_ip_revision_cmd_id {
ENETC_MSG_GET_IP_MN = 1,
};

+enum enetc_msg_link_status_cmd_id {
+ ENETC_MSG_GET_CURRENT_LINK_STATUS,
+ ENETC_MSG_REGISTER_LINK_CHANGE_NOTIFIER,
+ ENETC_MSG_UNREGISTER_LINK_CHANGE_NOTIFIER,
+};
+
/* Class-specific error return codes of MAC filter */
enum enetc_mac_filter_class_code {
ENETC_MF_CLASS_CODE_INVALID_MAC,
};

+/* Class-specific notifications/codes of link status */
+#define ENETC_CLASS_CODE_LINK_DOWN BIT(0)
+#define ENETC_CLASS_CODE_TX_PAUSE_EN BIT(1)
+
struct enetc_msg_swbd {
void *vaddr;
dma_addr_t dma;
@@ -161,6 +176,11 @@ struct enetc_msg_mac_exact_filter {
/* The generic message format applies to the following messages:
* Get IP revision message, class_id 0xf0.
* cmd_id 1: get IP minor revision
+ *
+ * Link status message, class id 0x80.
+ * cmd_id 0x0: get the current link status
+ * cmd_id 0x1: register link status change notification
+ * cmd_id 0x2: unregister link status change notification
*/
struct enetc_msg_generic {
struct enetc_msg_header hdr;
diff --git a/drivers/net/ethernet/freescale/enetc/enetc_msg.c b/drivers/net/ethernet/freescale/enetc/enetc_msg.c
index a89a5a418a23..e21414acdc0d 100644
--- a/drivers/net/ethernet/freescale/enetc/enetc_msg.c
+++ b/drivers/net/ethernet/freescale/enetc/enetc_msg.c
@@ -136,6 +136,154 @@ static u16 enetc_msg_handle_ip_revision(struct enetc_pf *pf, void *vf_msg)
}
}

+static void enetc_pf_reply_msg(struct enetc_hw *hw, int vf_id, u16 pf_msg)
+{
+ /* w1c to clear the corresponding VF MR bit */
+ enetc_wr(hw, ENETC_PSIIDR, ENETC_PSIMR_BIT(vf_id));
+ enetc_wr(hw, ENETC_PSIMSGRR, ENETC_SIMSGSR_SET_MC(pf_msg) |
+ ENETC_PSIMR_BIT(vf_id));
+}
+
+static u16 enetc_build_link_status_msg(struct enetc_ndev_priv *priv,
+ bool link_up)
+{
+ u8 status = 0;
+
+ if (link_up) {
+ spin_lock(&priv->si->gen_lock);
+ if (test_bit(ENETC_RXBDR_CM, &priv->flags))
+ status |= ENETC_CLASS_CODE_TX_PAUSE_EN;
+ spin_unlock(&priv->si->gen_lock);
+ } else {
+ status |= ENETC_CLASS_CODE_LINK_DOWN;
+ }
+
+ return FIELD_PREP(ENETC_PF_MSG_CLASS_ID,
+ ENETC_MSG_CLASS_ID_LINK_STATUS) |
+ FIELD_PREP(ENETC_PF_MSG_CLASS_CODE_U8, status);
+}
+
+static void enetc_msg_get_link_status(struct enetc_pf *pf, int vf_id)
+{
+ struct enetc_ndev_priv *priv = netdev_priv(pf->si->ndev);
+ u16 pf_msg;
+
+ mutex_lock(&pf->msg_lock);
+ pf_msg = enetc_build_link_status_msg(priv, pf->link_up);
+ enetc_pf_reply_msg(&pf->si->hw, vf_id, pf_msg);
+ mutex_unlock(&pf->msg_lock);
+}
+
+static int enetc_pf_send_msg(struct enetc_pf *pf, u32 msg_code, u16 ms_mask)
+{
+ struct enetc_hw *hw = &pf->si->hw;
+ u16 old_ms_mask = ms_mask;
+ u16 ms_status;
+ u32 val;
+
+ /* The MS bit is set, indicating that the corresponding VF has not
+ * read the last message, PF cannot send new message to the VF. To
+ * avoid sending messages to such a VF, the bit corresponding to VF
+ * is cleared from ms_mask. Because the MS bit can only be written
+ * as 1, writing a 0 has no effect. Writing a 1 when the bit is
+ * already set is undefined.
+ */
+ ms_status = enetc_rd(hw, ENETC_PSIMSGSR) & 0xffff;
+ ms_mask &= ~ms_status;
+ if (!ms_mask)
+ return -EIO;
+
+ if (ms_mask != old_ms_mask)
+ dev_warn_ratelimited(&pf->si->pdev->dev,
+ "PF cannot send message to VF(s) 0x%x\n",
+ ms_mask ^ old_ms_mask);
+
+ enetc_wr(hw, ENETC_PSIMSGSR,
+ FIELD_PREP(PSIMSGSR_MC, msg_code) | ms_mask);
+
+ return read_poll_timeout(enetc_rd, val, !(val & ms_mask), 1000,
+ 200000, false, hw, ENETC_PSIMSGSR);
+}
+
+static void enetc_msg_notify_vf_link_status(struct enetc_pf *pf, u16 ms_mask)
+{
+ struct enetc_ndev_priv *priv = netdev_priv(pf->si->ndev);
+ u16 pf_msg;
+
+ pf_msg = enetc_build_link_status_msg(priv, pf->link_up);
+ if (enetc_pf_send_msg(pf, pf_msg, ms_mask))
+ dev_err_ratelimited(&pf->si->pdev->dev,
+ "PF notifies link status failed\n");
+}
+
+static void enetc_msg_register_link_status_notifier(struct enetc_pf *pf,
+ int vf_id)
+{
+ u16 pf_msg = FIELD_PREP(ENETC_PF_MSG_CLASS_ID,
+ ENETC_MSG_CLASS_ID_CMD_SUCCESS);
+
+ mutex_lock(&pf->msg_lock);
+
+ enetc_pf_reply_msg(&pf->si->hw, vf_id, pf_msg);
+
+ /* SR-IOV is being disabled if pf->sriov_enabled is false, so no
+ * need to set link_status_ms_mask and notify the link status.
+ */
+ if (!pf->sriov_enabled)
+ goto msg_unlock;
+
+ pf->link_status_ms_mask |= PSIMSGSR_MS(vf_id);
+
+ /* Notify VF the current link status */
+ enetc_msg_notify_vf_link_status(pf, PSIMSGSR_MS(vf_id));
+
+msg_unlock:
+ mutex_unlock(&pf->msg_lock);
+}
+
+static void enetc_msg_unregister_link_status_notifier(struct enetc_pf *pf,
+ int vf_id)
+{
+ u16 pf_msg = FIELD_PREP(ENETC_PF_MSG_CLASS_ID,
+ ENETC_MSG_CLASS_ID_CMD_SUCCESS);
+
+ mutex_lock(&pf->msg_lock);
+
+ pf->link_status_ms_mask &= ~PSIMSGSR_MS(vf_id);
+ enetc_pf_reply_msg(&pf->si->hw, vf_id, pf_msg);
+
+ mutex_unlock(&pf->msg_lock);
+}
+
+static u16 enetc_msg_handle_link_status(struct enetc_pf *pf, int vf_id,
+ void *vf_msg)
+{
+ struct enetc_msg_header *msg_hdr = vf_msg;
+
+ switch (msg_hdr->cmd_id) {
+ case ENETC_MSG_GET_CURRENT_LINK_STATUS:
+ /* Currently, this message is intended only for
+ * DPDK-owned VFs.
+ */
+ enetc_msg_get_link_status(pf, vf_id);
+ break;
+ case ENETC_MSG_REGISTER_LINK_CHANGE_NOTIFIER:
+ enetc_msg_register_link_status_notifier(pf, vf_id);
+ break;
+ case ENETC_MSG_UNREGISTER_LINK_CHANGE_NOTIFIER:
+ enetc_msg_unregister_link_status_notifier(pf, vf_id);
+ break;
+ default:
+ return ENETC_PF_MSG_NOTSUPP;
+ }
+
+ return 0;
+}
+
+/* If *pf_msg is set to 0, it means that PF has responded to VF in
+ * enetc_msg_handle_rxmsg() through enetc_pf_reply_msg(), which also
+ * clears the corresponding VF MR bit in PSIIDR.
+ */
static void enetc_msg_handle_rxmsg(struct enetc_pf *pf, int vf_id,
u16 *pf_msg)
{
@@ -211,6 +359,9 @@ static void enetc_msg_handle_rxmsg(struct enetc_pf *pf, int vf_id,
case ENETC_MSG_CLASS_ID_IP_REVISION:
*pf_msg = enetc_msg_handle_ip_revision(pf, msg);
break;
+ case ENETC_MSG_CLASS_ID_LINK_STATUS:
+ *pf_msg = enetc_msg_handle_link_status(pf, vf_id, msg);
+ break;
default:
dev_err_ratelimited(dev,
"Unsupported message class ID: 0x%x\n",
@@ -236,7 +387,6 @@ static void enetc_msg_task(struct work_struct *work)
goto out;

for (i = 0; i < pf->num_vfs; i++) {
- u32 psimsgrr;
u16 msg_code;

if (!(ENETC_PSIMR_BIT(i) & mr_status))
@@ -244,12 +394,14 @@ static void enetc_msg_task(struct work_struct *work)

enetc_msg_handle_rxmsg(pf, i, &msg_code);

- /* w1c to clear the corresponding VF MR bit */
- enetc_wr(hw, ENETC_PSIIDR, ENETC_PSIMR_BIT(i));
+ /* If msg_code is 0, it means that PF has responded to VF
+ * in enetc_msg_handle_rxmsg() through enetc_pf_reply_msg(),
+ * which also clears the corresponding VF MR bit in PSIIDR.
+ */
+ if (!msg_code)
+ continue;

- psimsgrr = ENETC_SIMSGSR_SET_MC(msg_code);
- psimsgrr |= ENETC_PSIMR_BIT(i); /* w1c */
- enetc_wr(hw, ENETC_PSIMSGRR, psimsgrr);
+ enetc_pf_reply_msg(hw, i, msg_code);
}

out:
@@ -367,6 +519,11 @@ int enetc_sriov_configure(struct pci_dev *pdev, int num_vfs)
int err;

if (!num_vfs) {
+ mutex_lock(&pf->msg_lock);
+ pf->sriov_enabled = false;
+ pf->link_status_ms_mask = 0;
+ mutex_unlock(&pf->msg_lock);
+
pci_disable_sriov(pdev);
enetc_msg_psi_free(pf);
pf->num_vfs = 0;
@@ -379,6 +536,11 @@ int enetc_sriov_configure(struct pci_dev *pdev, int num_vfs)
goto err_msg_psi;
}

+ /* As PCI SR-IOV is not enabled at the moment, there is no
+ * concurrent access to sriov_enabled. So no need to use
+ * msg_lock to protect sriov_enabled.
+ */
+ pf->sriov_enabled = true;
err = pci_enable_sriov(pdev, num_vfs);
if (err) {
dev_err(&pdev->dev, "pci_enable_sriov err %d\n", err);
@@ -389,6 +551,15 @@ int enetc_sriov_configure(struct pci_dev *pdev, int num_vfs)
return num_vfs;

err_en_sriov:
+ /* If pci_enable_sriov() fails after partially creating VFs, a VF
+ * driver that successfully bound to one of the created VFs could
+ * have sent a registration message, setting its bit in
+ * link_status_ms_mask.
+ */
+ mutex_lock(&pf->msg_lock);
+ pf->sriov_enabled = false;
+ pf->link_status_ms_mask = 0;
+ mutex_unlock(&pf->msg_lock);
enetc_msg_psi_free(pf);
err_msg_psi:
pf->num_vfs = 0;
@@ -396,3 +567,34 @@ int enetc_sriov_configure(struct pci_dev *pdev, int num_vfs)
return err;
}
EXPORT_SYMBOL_GPL(enetc_sriov_configure);
+
+static void enetc_pf_notify_vf_link_status(struct enetc_pf *pf,
+ bool link_up)
+{
+ /* pf->msg_lock is initialized when pf->total_vfs is not 0 */
+ if (!pf->total_vfs)
+ return;
+
+ mutex_lock(&pf->msg_lock);
+
+ pf->link_up = link_up;
+ if (!pf->link_status_ms_mask)
+ goto msg_unlock;
+
+ enetc_msg_notify_vf_link_status(pf, pf->link_status_ms_mask);
+
+msg_unlock:
+ mutex_unlock(&pf->msg_lock);
+}
+
+void enetc_pf_notify_vf_link_up(struct enetc_pf *pf)
+{
+ enetc_pf_notify_vf_link_status(pf, true);
+}
+EXPORT_SYMBOL_GPL(enetc_pf_notify_vf_link_up);
+
+void enetc_pf_notify_vf_link_down(struct enetc_pf *pf)
+{
+ enetc_pf_notify_vf_link_status(pf, false);
+}
+EXPORT_SYMBOL_GPL(enetc_pf_notify_vf_link_down);
diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf.h b/drivers/net/ethernet/freescale/enetc/enetc_pf.h
index 142c911f1dfc..fb48a06e6c42 100644
--- a/drivers/net/ethernet/freescale/enetc/enetc_pf.h
+++ b/drivers/net/ethernet/freescale/enetc/enetc_pf.h
@@ -54,6 +54,12 @@ struct enetc_pf {

struct enetc_port_caps caps;
const struct enetc_pf_ops *ops;
+
+ /* Message lock, prevent concurrent access */
+ struct mutex msg_lock;
+ bool sriov_enabled;
+ bool link_up;
+ u16 link_status_ms_mask;
};

#define phylink_to_enetc_pf(config) \
diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c
index 519fc90d2647..345cb940aaa3 100644
--- a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c
+++ b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c
@@ -582,6 +582,8 @@ int enetc_init_sriov_resources(struct enetc_pf *pf)
for (int i = 0; i < pf->total_vfs; i++)
mutex_init(&pf->vf_state[i].lock);

+ mutex_init(&pf->msg_lock);
+
return 0;
}
EXPORT_SYMBOL_GPL(enetc_init_sriov_resources);
diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h
index 96a4dc63da57..693aee3c64b8 100644
--- a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h
+++ b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h
@@ -31,9 +31,19 @@ static inline u16 enetc_get_ip_revision(struct enetc_hw *hw)

#if IS_ENABLED(CONFIG_PCI_IOV)
int enetc_sriov_configure(struct pci_dev *pdev, int num_vfs);
+void enetc_pf_notify_vf_link_up(struct enetc_pf *pf);
+void enetc_pf_notify_vf_link_down(struct enetc_pf *pf);
#else
static inline int enetc_sriov_configure(struct pci_dev *pdev, int num_vfs)
{
return 0;
}
+
+static inline void enetc_pf_notify_vf_link_up(struct enetc_pf *pf)
+{
+}
+
+static inline void enetc_pf_notify_vf_link_down(struct enetc_pf *pf)
+{
+}
#endif
--
2.34.1