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",