[PATCH v4 net-next 04/15] net: enetc: add link speed message support to PF driver

From: wei . fang

Date: Wed Sep 09 2026 - 06:45:19 EST


From: Wei Fang <wei.fang@xxxxxxx>

When a VF is driven by DPDK, its user space application needs accurate
link speed information to make correct forwarding and configuration
decisions. Add link speed message support so the PF replies with the
current link speed when it receives a get-link-speed message from a VF.

Use a new message class 0x81 (ENETC_MSG_CLASS_ID_LINK_SPEED). The
PSI-to-VSI message is 16 bits: the high 8 bits are the class ID and the
low 8 bits are the speed code, so up to 255 speed values are supported
(ENETC_MSG_SPEED_MAX = 0xff). Instead of enumerating every speed above
5Gbps, use a formula so future high speeds need no enum or switch
changes:

speed_code = (link_speed - 5000) / 1000 + ENETC_MSG_SPEED_5G

The speed is read via phylink_ethtool_ksettings_get() rather than the
speed passed to the mac_link_up() callback. When the MAC has a PCS
layer, mac_link_up() reports the PCS link speed, which may differ from
the external PHY link speed; phylink_ethtool_ksettings_get() returns the
actual external link speed.

Unlike the link status message (class 0x80), the get-link-speed message
is only permitted for trusted VFs. Reading the speed requires the PF to
take rtnl_lock(), so an untrusted VF spamming this query could cause
rtnl_lock contention and starve routine network configuration on the
host. An untrusted VF therefore receives a permission-deny response; a
VF can be marked trusted via 'ip link set <pf> vf N trust on'.

Note that link speed change notification is not supported yet.

Signed-off-by: Wei Fang <wei.fang@xxxxxxx>
---
.../ethernet/freescale/enetc/enetc_mailbox.h | 41 +++++++++
.../net/ethernet/freescale/enetc/enetc_msg.c | 90 +++++++++++++++++++
2 files changed, 131 insertions(+)

diff --git a/drivers/net/ethernet/freescale/enetc/enetc_mailbox.h b/drivers/net/ethernet/freescale/enetc/enetc_mailbox.h
index 846998f07989..832d2f6ddcd8 100644
--- a/drivers/net/ethernet/freescale/enetc/enetc_mailbox.h
+++ b/drivers/net/ethernet/freescale/enetc/enetc_mailbox.h
@@ -91,6 +91,7 @@
* The class code for the following messages is 8-bit.
* 1. Get IP revision messages
* 2. Link status messages
+ * 3. Link speed messages
*/
#define ENETC_PF_MSG_CLASS_CODE_U8 GENMASK(7, 0)
#define ENETC_PF_MSG_CLASS_ID GENMASK(15, 8)
@@ -112,6 +113,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_LINK_SPEED = 0x81,
ENETC_MSG_CLASS_ID_IP_REVISION = 0xf0,
};

@@ -129,6 +131,13 @@ enum enetc_msg_link_status_cmd_id {
ENETC_MSG_UNREGISTER_LINK_CHANGE_NOTIFIER,
};

+enum enetc_msg_link_speed_cmd_id {
+ ENETC_MSG_GET_CURRENT_LINK_SPEED,
+ /* The following command IDs are not currently supported */
+ ENETC_MSG_REGISTER_SPEED_CHANGE_NOTIFIER,
+ ENETC_MSG_UNREGISTER_SPEED_CHANGE_NOTIFIER,
+};
+
/* Class-specific error return codes of MAC filter */
enum enetc_mac_filter_class_code {
ENETC_MF_CLASS_CODE_INVALID_MAC,
@@ -138,6 +147,28 @@ enum enetc_mac_filter_class_code {
#define ENETC_CLASS_CODE_LINK_DOWN BIT(0)
#define ENETC_CLASS_CODE_TX_PAUSE_EN BIT(1)

+/* Class-specific notifications/codes of link speed */
+enum enetc_link_speed_class_code {
+ ENETC_MSG_SPEED_UNKNOWN,
+ ENETC_MSG_SPEED_10M_HD,
+ ENETC_MSG_SPEED_10M_FD,
+ ENETC_MSG_SPEED_100M_HD,
+ ENETC_MSG_SPEED_100M_FD,
+ ENETC_MSG_SPEED_1000M,
+ ENETC_MSG_SPEED_2500M,
+ ENETC_MSG_SPEED_5G,
+ /* Do not add enumeration values for any speed greater than
+ * 5Gbps. For any speed greater than 5Gbps, its speed class
+ * code should follow the formula below.
+ *
+ * SPEED = (link_speed - 5000) / 1000 + ENETC_MSG_SPEED_5G
+ *
+ * The unit of link_speed should be Mbps, the max SPEED
+ * should <= ENETC_MSG_SPEED_MAX.
+ */
+ ENETC_MSG_SPEED_MAX = 0xff,
+};
+
struct enetc_msg_swbd {
void *vaddr;
dma_addr_t dma;
@@ -181,6 +212,16 @@ struct enetc_msg_mac_exact_filter {
* cmd_id 0x0: get the current link status
* cmd_id 0x1: register link status change notification
* cmd_id 0x2: unregister link status change notification
+ *
+ * Link speed message, class_id 0x81.
+ * cmd_id 0x0: get the current link speed. Unlike the link status
+ * query (class 0x80), this query is only permitted for trusted VFs;
+ * an untrusted VF receives a permission-deny response. This is
+ * because the PF must take rtnl_lock() to read the link speed, so
+ * restricting it to trusted VFs avoids rtnl_lock contention on the
+ * host from a misbehaving VF.
+ * cmd_id 0x1: register link speed change notification, not supported yet
+ * cmd_id 0x2: unregister link speed change notification, not supported yet
*/
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 79dbaf72fcff..08a9ffdd3eb0 100644
--- a/drivers/net/ethernet/freescale/enetc/enetc_msg.c
+++ b/drivers/net/ethernet/freescale/enetc/enetc_msg.c
@@ -230,6 +230,93 @@ static u16 enetc_msg_handle_link_status(struct enetc_pf *pf, int vf_id,
return 0;
}

+static u16 enetc_build_link_speed_msg(int speed, int duplex)
+{
+ u32 speed_code = ENETC_MSG_SPEED_UNKNOWN;
+
+ switch (speed) {
+ case SPEED_10:
+ if (duplex == DUPLEX_HALF)
+ speed_code = ENETC_MSG_SPEED_10M_HD;
+ else if (duplex == DUPLEX_FULL)
+ speed_code = ENETC_MSG_SPEED_10M_FD;
+ break;
+ case SPEED_100:
+ if (duplex == DUPLEX_HALF)
+ speed_code = ENETC_MSG_SPEED_100M_HD;
+ else if (duplex == DUPLEX_FULL)
+ speed_code = ENETC_MSG_SPEED_100M_FD;
+ break;
+ case SPEED_1000:
+ speed_code = ENETC_MSG_SPEED_1000M;
+ break;
+ case SPEED_2500:
+ speed_code = ENETC_MSG_SPEED_2500M;
+ break;
+ case SPEED_5000:
+ speed_code = ENETC_MSG_SPEED_5G;
+ break;
+ default:
+ if (speed < SPEED_5000)
+ break;
+
+ speed_code = (speed - SPEED_5000) / SPEED_1000 +
+ ENETC_MSG_SPEED_5G;
+ if (speed_code > ENETC_MSG_SPEED_MAX)
+ speed_code = ENETC_MSG_SPEED_UNKNOWN;
+ }
+
+ return FIELD_PREP(ENETC_PF_MSG_CLASS_ID,
+ ENETC_MSG_CLASS_ID_LINK_SPEED) |
+ FIELD_PREP(ENETC_PF_MSG_CLASS_CODE_U8, speed_code);
+}
+
+static u16 enetc_msg_get_link_speed(struct enetc_pf *pf, int vf_id)
+{
+ struct enetc_ndev_priv *priv = netdev_priv(pf->si->ndev);
+ struct enetc_vf_state *vf_state = &pf->vf_state[vf_id];
+ struct ethtool_link_ksettings link_info = {};
+
+ /* A malicious or malfunctioning VM could potentially spam these
+ * messages in a tight loop causing global rtnl_lock contention,
+ * which may severely starve other processes on the host that
+ * require rtnl_lock for routine network configuration, resulting
+ * in a system-wide control-plane denial of service. Therefore,
+ * we expect the VF query for link speed to be trusted. There's no
+ * need to consider the transition from trusted to untrusted here,
+ * as this won't cause rtnl_lock() to be called frequently.
+ */
+ mutex_lock(&vf_state->lock);
+ if (!(vf_state->flags & ENETC_VF_FLAG_TRUSTED)) {
+ mutex_unlock(&vf_state->lock);
+
+ return ENETC_PF_MSG_PERM_DENY;
+ }
+ mutex_unlock(&vf_state->lock);
+
+ rtnl_lock();
+ phylink_ethtool_ksettings_get(priv->phylink, &link_info);
+ rtnl_unlock();
+
+ return enetc_build_link_speed_msg(link_info.base.speed,
+ link_info.base.duplex);
+}
+
+static u16 enetc_msg_handle_link_speed(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_SPEED:
+ return enetc_msg_get_link_speed(pf, vf_id);
+ case ENETC_MSG_REGISTER_SPEED_CHANGE_NOTIFIER:
+ case ENETC_MSG_UNREGISTER_SPEED_CHANGE_NOTIFIER:
+ default:
+ return ENETC_PF_MSG_NOTSUPP;
+ }
+}
+
/* 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.
@@ -312,6 +399,9 @@ static void enetc_msg_handle_rxmsg(struct enetc_pf *pf, int vf_id,
case ENETC_MSG_CLASS_ID_LINK_STATUS:
*pf_msg = enetc_msg_handle_link_status(pf, vf_id, msg);
break;
+ case ENETC_MSG_CLASS_ID_LINK_SPEED:
+ *pf_msg = enetc_msg_handle_link_speed(pf, vf_id, msg);
+ break;
default:
dev_err_ratelimited(dev,
"Unsupported message class ID: 0x%x\n",
--
2.34.1