From: Claudiu Manoil Some mailbox messages require a higher privilege level to be executed on behalf of the requesting VF. Introduce a trusted VF flag (ENETC_VF_FLAG_TRUSTED) and wire up the ndo_set_vf_trust callback via enetc_pf_set_vf_trust(), which is shared between the enetc and enetc4 PF drivers. The first message gated on trust is the VF primary MAC address change. An untrusted VF that attempts to set its own MAC address will receive a ENETC_MSG_CLASS_ID_PERMISSION_DENY response and the hardware will not be programmed. To prevent a malicious VM from setting the VF address to the MAC address of other VFs or PF, thereby eavesdropping on the traffic of other SIs. Furthermore, a malicious VM that arbitrarily changes the VF's MAC address can achieve MAC address spoofing and bypass security policies. Signed-off-by: Claudiu Manoil Signed-off-by: Wei Fang --- .../net/ethernet/freescale/enetc/enetc4_pf.c | 7 +++- .../net/ethernet/freescale/enetc/enetc_msg.c | 38 ++++++++++++++----- .../net/ethernet/freescale/enetc/enetc_pf.c | 1 + .../net/ethernet/freescale/enetc/enetc_pf.h | 1 + .../freescale/enetc/enetc_pf_common.c | 23 +++++++++++ .../freescale/enetc/enetc_pf_common.h | 1 + 6 files changed, 61 insertions(+), 10 deletions(-) diff --git a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c index 9bb1004548ab..935a6a03b14f 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c @@ -225,11 +225,15 @@ static const struct enetc_pf_ops enetc4_pf_ops = { static int enetc4_pf_struct_init(struct enetc_si *si) { struct enetc_pf *pf = enetc_si_priv(si); + int err; pf->si = si; - pf->total_vfs = pci_sriov_get_totalvfs(si->pdev); pf->ops = &enetc4_pf_ops; + err = enetc_init_sriov_resources(pf); + if (err) + return err; + enetc4_get_port_caps(pf); enetc4_get_psi_hw_features(si); @@ -574,6 +578,7 @@ static const struct net_device_ops enetc4_ndev_ops = { .ndo_eth_ioctl = enetc_ioctl, .ndo_hwtstamp_get = enetc_hwtstamp_get, .ndo_hwtstamp_set = enetc_hwtstamp_set, + .ndo_set_vf_trust = enetc_pf_set_vf_trust, }; static struct phylink_pcs * diff --git a/drivers/net/ethernet/freescale/enetc/enetc_msg.c b/drivers/net/ethernet/freescale/enetc/enetc_msg.c index edc1277bb586..78114ab3e482 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_msg.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_msg.c @@ -7,6 +7,8 @@ ENETC_MSG_CLASS_ID_CMD_SUCCESS) #define ENETC_PF_MSG_NOTSUPP FIELD_PREP(ENETC_PF_MSG_CLASS_ID, \ ENETC_MSG_CLASS_ID_CMD_NOT_SUPPORT) +#define ENETC_PF_MSG_PERM_DENY FIELD_PREP(ENETC_PF_MSG_CLASS_ID, \ + ENETC_MSG_CLASS_ID_PERMISSION_DENY) static void enetc_msg_disable_mr_int(struct enetc_pf *pf) { @@ -61,31 +63,49 @@ static u16 enetc_msg_set_vf_primary_mac_addr(struct enetc_pf *pf, int vf_id, struct enetc_vf_state *vf_state = &pf->vf_state[vf_id]; struct enetc_msg_mac_exact_filter *msg = vf_msg; struct device *dev = &pf->si->pdev->dev; + u16 pf_msg = ENETC_PF_MSG_SUCCESS; char *addr = msg->mac[0].addr; + mutex_lock(&vf_state->lock); + + /* Untrusted VFs cannot set their MAC addresses by the mailbox + * messages. + */ + if (!(vf_state->flags & ENETC_VF_FLAG_TRUSTED)) { + pf_msg = ENETC_PF_MSG_PERM_DENY; + goto vf_state_unlock; + } + if (!is_valid_ether_addr(addr)) { dev_err_ratelimited(dev, "VF%d attempted to set invalid MAC\n", vf_id); - return (FIELD_PREP(ENETC_PF_MSG_CLASS_ID, - ENETC_MSG_CLASS_ID_MAC_FILTER) | - FIELD_PREP(ENETC_PF_MSG_CLASS_CODE, - ENETC_MF_CLASS_CODE_INVALID_MAC)); + pf_msg = FIELD_PREP(ENETC_PF_MSG_CLASS_ID, + ENETC_MSG_CLASS_ID_MAC_FILTER) | + FIELD_PREP(ENETC_PF_MSG_CLASS_CODE, + ENETC_MF_CLASS_CODE_INVALID_MAC); + goto vf_state_unlock; } - mutex_lock(&vf_state->lock); + /* PF has higher privileges. If PF has already modified the MAC + * address for VF through .ndo_set_vf_mac() interface, VF is not + * allowed to set its MAC address via mailbox messages, even if + * it is trusted. + */ if (vf_state->flags & ENETC_VF_FLAG_PF_SET_MAC) { - mutex_unlock(&vf_state->lock); dev_err_ratelimited(dev, "VF%d attempted to override PF set MAC\n", vf_id); - return FIELD_PREP(ENETC_PF_MSG_CLASS_ID, - ENETC_MSG_CLASS_ID_CMD_NOT_PERMITTED); + pf_msg = FIELD_PREP(ENETC_PF_MSG_CLASS_ID, + ENETC_MSG_CLASS_ID_CMD_NOT_PERMITTED); + goto vf_state_unlock; } enetc_set_si_hw_addr(pf, vf_id + 1, addr); + +vf_state_unlock: mutex_unlock(&vf_state->lock); - return ENETC_PF_MSG_SUCCESS; + return pf_msg; } static u16 enetc_msg_handle_mac_filter(struct enetc_pf *pf, int vf_id, diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf.c b/drivers/net/ethernet/freescale/enetc/enetc_pf.c index 55c07c528f22..a7bf4bfc25b7 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf.c @@ -488,6 +488,7 @@ static const struct net_device_ops enetc_ndev_ops = { .ndo_set_rx_mode = enetc_pf_set_rx_mode, .ndo_vlan_rx_add_vid = enetc_vlan_rx_add_vid, .ndo_vlan_rx_kill_vid = enetc_vlan_rx_del_vid, + .ndo_set_vf_trust = enetc_pf_set_vf_trust, .ndo_set_vf_mac = enetc_pf_set_vf_mac, .ndo_set_vf_vlan = enetc_pf_set_vf_vlan, .ndo_set_vf_spoofchk = enetc_pf_set_vf_spoofchk, diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf.h b/drivers/net/ethernet/freescale/enetc/enetc_pf.h index 56d23a8a11a0..6789f92d005e 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf.h +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf.h @@ -9,6 +9,7 @@ enum enetc_vf_flags { ENETC_VF_FLAG_PF_SET_MAC = BIT(0), + ENETC_VF_FLAG_TRUSTED = BIT(1), }; struct enetc_vf_state { diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c index d32a195a04c9..519fc90d2647 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c @@ -586,5 +586,28 @@ int enetc_init_sriov_resources(struct enetc_pf *pf) } EXPORT_SYMBOL_GPL(enetc_init_sriov_resources); +int enetc_pf_set_vf_trust(struct net_device *ndev, int vf, bool setting) +{ + struct enetc_ndev_priv *priv = netdev_priv(ndev); + struct enetc_pf *pf = enetc_si_priv(priv->si); + struct enetc_vf_state *vf_state; + + if (vf >= pf->total_vfs) + return -EINVAL; + + vf_state = &pf->vf_state[vf]; + mutex_lock(&vf_state->lock); + + if (setting) + vf_state->flags |= ENETC_VF_FLAG_TRUSTED; + else + vf_state->flags &= ~ENETC_VF_FLAG_TRUSTED; + + mutex_unlock(&vf_state->lock); + + return 0; +} +EXPORT_SYMBOL_GPL(enetc_pf_set_vf_trust); + MODULE_DESCRIPTION("NXP ENETC PF common functionality driver"); MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h index 8243ce0de57f..96a4dc63da57 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h @@ -22,6 +22,7 @@ void enetc_set_si_mc_promisc(struct enetc_si *si, int si_id, bool promisc); void enetc_set_si_uc_hash_filter(struct enetc_si *si, int si_id, u64 hash); void enetc_set_si_mc_hash_filter(struct enetc_si *si, int si_id, u64 hash); void enetc_set_si_vlan_promisc(struct enetc_si *si, int si_id, bool promisc); +int enetc_pf_set_vf_trust(struct net_device *ndev, int vf, bool setting); static inline u16 enetc_get_ip_revision(struct enetc_hw *hw) { -- 2.34.1 From: Wei Fang The ENETC PF currently uses msg_task and msg_int_name in struct enetc_pf to handle VSI-to-PSI mailbox messages via a workqueue and a dedicated interrupt. PSI-to-VSI message support will be added to the VF driver, which will require the same mechanism: a message interrupt and a workqueue handler. Since struct enetc_si is the common structure shared between PF and VF, move msg_task and msg_int_name from struct enetc_pf to struct enetc_si to allow both drivers to use them without duplication. Also relocate the ENETC_INT_NAME_MAX macro definition ahead of struct enetc_si so it can be used for the msg_int_name array declaration. Signed-off-by: Wei Fang --- drivers/net/ethernet/freescale/enetc/enetc.h | 4 +++- .../net/ethernet/freescale/enetc/enetc_msg.c | 19 ++++++++++--------- .../net/ethernet/freescale/enetc/enetc_pf.h | 3 --- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/drivers/net/ethernet/freescale/enetc/enetc.h b/drivers/net/ethernet/freescale/enetc/enetc.h index d1e9d9130057..bc713a7c3aa1 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc.h +++ b/drivers/net/ethernet/freescale/enetc/enetc.h @@ -25,6 +25,7 @@ #define ENETC_CBD_DATA_MEM_ALIGN 64 #define ENETC_MADDR_HASH_TBL_SZ 64 +#define ENETC_INT_NAME_MAX (IFNAMSIZ + 8) enum enetc_mac_addr_type {UC, MC, MADDR_TYPE}; @@ -333,6 +334,8 @@ struct enetc_si { struct dentry *debugfs_root; struct enetc_msg_swbd msg; /* Only valid for VSI */ + struct work_struct msg_task; + char msg_int_name[ENETC_INT_NAME_MAX]; }; #define ENETC_SI_ALIGN 32 @@ -374,7 +377,6 @@ static inline bool enetc_is_pseudo_mac(struct enetc_si *si) } #define ENETC_MAX_NUM_TXQS 8 -#define ENETC_INT_NAME_MAX (IFNAMSIZ + 8) struct enetc_int_vector { void __iomem *rbier; diff --git a/drivers/net/ethernet/freescale/enetc/enetc_msg.c b/drivers/net/ethernet/freescale/enetc/enetc_msg.c index 78114ab3e482..a89a5a418a23 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_msg.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_msg.c @@ -37,7 +37,7 @@ static irqreturn_t enetc_msg_psi_msix(int irq, void *data) struct enetc_pf *pf = enetc_si_priv(si); enetc_msg_disable_mr_int(pf); - schedule_work(&pf->msg_task); + schedule_work(&si->msg_task); return IRQ_HANDLED; } @@ -223,12 +223,13 @@ static void enetc_msg_handle_rxmsg(struct enetc_pf *pf, int vf_id, static void enetc_msg_task(struct work_struct *work) { - struct enetc_pf *pf = container_of(work, struct enetc_pf, msg_task); - u32 mr_mask = ENETC_PSIMR_MASK(pf->num_vfs); - struct enetc_hw *hw = &pf->si->hw; - u32 mr_status; + struct enetc_si *si = container_of(work, struct enetc_si, msg_task); + struct enetc_pf *pf = enetc_si_priv(si); + struct enetc_hw *hw = &si->hw; + u32 mr_status, mr_mask; int i; + mr_mask = ENETC_PSIMR_MASK(pf->num_vfs); mr_status = (enetc_rd(hw, ENETC_PSIMSGRR) & mr_mask) | (enetc_rd(hw, ENETC_PSIIDR) & mr_mask); if (!mr_status) @@ -311,13 +312,13 @@ static int enetc_msg_psi_init(struct enetc_pf *pf) } /* initialize PSI mailbox */ - INIT_WORK(&pf->msg_task, enetc_msg_task); + INIT_WORK(&si->msg_task, enetc_msg_task); /* register message passing interrupt handler */ - snprintf(pf->msg_int_name, sizeof(pf->msg_int_name), "%s-vfmsg", + snprintf(si->msg_int_name, sizeof(si->msg_int_name), "%s-vfmsg", si->ndev->name); vector = pci_irq_vector(si->pdev, ENETC_SI_INT_IDX); - err = request_irq(vector, enetc_msg_psi_msix, 0, pf->msg_int_name, si); + err = request_irq(vector, enetc_msg_psi_msix, 0, si->msg_int_name, si); if (err) { dev_err(&si->pdev->dev, "PSI messaging: request_irq() failed!\n"); @@ -350,7 +351,7 @@ static void enetc_msg_psi_free(struct enetc_pf *pf) /* de-register message passing interrupt handler */ free_irq(pci_irq_vector(si->pdev, ENETC_SI_INT_IDX), si); - cancel_work_sync(&pf->msg_task); + cancel_work_sync(&si->msg_task); /* MR interrupts may be re-enabled by workqueue */ enetc_msg_disable_mr_int(pf); diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf.h b/drivers/net/ethernet/freescale/enetc/enetc_pf.h index 6789f92d005e..142c911f1dfc 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf.h +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf.h @@ -40,10 +40,7 @@ struct enetc_pf { struct enetc_vf_state *vf_state; struct enetc_mac_filter mac_filter[MADDR_TYPE]; - struct enetc_msg_swbd *rxmsg; - struct work_struct msg_task; - char msg_int_name[ENETC_INT_NAME_MAX]; DECLARE_BITMAP(vlan_ht_filter, ENETC_VLAN_HT_SIZE); DECLARE_BITMAP(active_vlans, VLAN_N_VID); -- 2.34.1 From: Wei Fang 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 --- .../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 From: Wei Fang When a VF is driven by DPDK, the VF relies on the PF to provide accurate link speed information so that the VF-side user space application can make correct forwarding and configuration decisions. Therefore, add link speed message support for DPDK-owned VF. The PF will reply the current link speed when it receives the get link speed message from VF. With this enhancement, VFs controlled by DPDK can obtain real-time link speed information from the PF, improving overall link state visibility, synchronization between PF/VF, and enabling more accurate performance adjustments in VF-side applications. The PSI-to-VSI message is 16 bits and the high 8 bits are the message class ID. For link speed message, the low bits are the speed code, so the message supports up to 255 speed values (ENETC_MSG_SPEED_MAX = 0xff). Instead of enumerating every speed value greater than 5Gbps explicitly, introduce a formula-based approach: speed_code = (link_speed - 5000) / 1000 + ENETC_MSG_SPEED_5G This removes the explicit enum entries for 10G, 25G, 40G,50G, 100G and so on. Making it easy to support any future high speed without modifying the enum or the switch statement. Note that currently the link speed change notification is not supported. Signed-off-by: Wei Fang --- .../ethernet/freescale/enetc/enetc_mailbox.h | 36 ++++++++ .../net/ethernet/freescale/enetc/enetc_msg.c | 91 +++++++++++++++++++ 2 files changed, 127 insertions(+) diff --git a/drivers/net/ethernet/freescale/enetc/enetc_mailbox.h b/drivers/net/ethernet/freescale/enetc/enetc_mailbox.h index 846998f07989..bd669543e96c 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,11 @@ 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. + * 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 e21414acdc0d..c3ae4c024f34 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_msg.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_msg.c @@ -280,6 +280,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. @@ -362,6 +449,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", @@ -546,6 +636,7 @@ int enetc_sriov_configure(struct pci_dev *pdev, int num_vfs) dev_err(&pdev->dev, "pci_enable_sriov err %d\n", err); goto err_en_sriov; } + } return num_vfs; -- 2.34.1 From: Wei Fang Prepare for moving enetc_pf_set_vf_mac() into the enetc-pf-common driver by replacing enetc_pf_set_primary_mac_addr() with enetc_set_si_hw_addr(). This makes the VF primary MAC configuration path generic and allows future enetc v4 PF driver to reuse the same interface. Signed-off-by: Wei Fang --- drivers/net/ethernet/freescale/enetc/enetc_pf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf.c b/drivers/net/ethernet/freescale/enetc/enetc_pf.c index a7bf4bfc25b7..b74d965e403e 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf.c @@ -207,7 +207,7 @@ static int enetc_pf_set_vf_mac(struct net_device *ndev, int vf, u8 *mac) mutex_lock(&vf_state->lock); vf_state->flags |= ENETC_VF_FLAG_PF_SET_MAC; - enetc_pf_set_primary_mac_addr(&priv->si->hw, vf + 1, mac); + enetc_set_si_hw_addr(pf, vf + 1, mac); mutex_unlock(&vf_state->lock); return 0; -- 2.34.1 From: Wei Fang Move enetc_pf_set_vf_mac() into enetc-pf-common driver as a generic interface for both ENETC v1 and v4 PF driver to use. Signed-off-by: Wei Fang --- .../net/ethernet/freescale/enetc/enetc_pf.c | 22 ------------------ .../freescale/enetc/enetc_pf_common.c | 23 +++++++++++++++++++ .../freescale/enetc/enetc_pf_common.h | 1 + 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf.c b/drivers/net/ethernet/freescale/enetc/enetc_pf.c index b74d965e403e..c467dc05510e 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf.c @@ -191,28 +191,6 @@ static void enetc_set_loopback(struct net_device *ndev, bool en) } } -static int enetc_pf_set_vf_mac(struct net_device *ndev, int vf, u8 *mac) -{ - struct enetc_ndev_priv *priv = netdev_priv(ndev); - struct enetc_pf *pf = enetc_si_priv(priv->si); - struct enetc_vf_state *vf_state; - - if (vf >= pf->total_vfs) - return -EINVAL; - - if (!is_valid_ether_addr(mac)) - return -EADDRNOTAVAIL; - - vf_state = &pf->vf_state[vf]; - - mutex_lock(&vf_state->lock); - vf_state->flags |= ENETC_VF_FLAG_PF_SET_MAC; - enetc_set_si_hw_addr(pf, vf + 1, mac); - mutex_unlock(&vf_state->lock); - - return 0; -} - static int enetc_pf_set_vf_vlan(struct net_device *ndev, int vf, u16 vlan, u8 qos, __be16 proto) { diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c index 345cb940aaa3..7a11370d2b8e 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c @@ -611,5 +611,28 @@ int enetc_pf_set_vf_trust(struct net_device *ndev, int vf, bool setting) } EXPORT_SYMBOL_GPL(enetc_pf_set_vf_trust); +int enetc_pf_set_vf_mac(struct net_device *ndev, int vf, u8 *mac) +{ + struct enetc_ndev_priv *priv = netdev_priv(ndev); + struct enetc_pf *pf = enetc_si_priv(priv->si); + struct enetc_vf_state *vf_state; + + if (vf >= pf->total_vfs) + return -EINVAL; + + if (!is_valid_ether_addr(mac)) + return -EADDRNOTAVAIL; + + vf_state = &pf->vf_state[vf]; + + mutex_lock(&vf_state->lock); + vf_state->flags |= ENETC_VF_FLAG_PF_SET_MAC; + enetc_set_si_hw_addr(pf, vf + 1, mac); + mutex_unlock(&vf_state->lock); + + return 0; +} +EXPORT_SYMBOL_GPL(enetc_pf_set_vf_mac); + MODULE_DESCRIPTION("NXP ENETC PF common functionality driver"); MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h index 693aee3c64b8..1d35e906fc3a 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h @@ -23,6 +23,7 @@ void enetc_set_si_uc_hash_filter(struct enetc_si *si, int si_id, u64 hash); void enetc_set_si_mc_hash_filter(struct enetc_si *si, int si_id, u64 hash); void enetc_set_si_vlan_promisc(struct enetc_si *si, int si_id, bool promisc); int enetc_pf_set_vf_trust(struct net_device *ndev, int vf, bool setting); +int enetc_pf_set_vf_mac(struct net_device *ndev, int vf, u8 *mac); static inline u16 enetc_get_ip_revision(struct enetc_hw *hw) { -- 2.34.1 From: Wei Fang Add .ndo_set_vf_mac() to the enetc v4 driver to configure the MAC addresses of VFs. Signed-off-by: Wei Fang --- drivers/net/ethernet/freescale/enetc/enetc4_pf.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c index 17fd9ee27942..eeb70feeb773 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c @@ -579,6 +579,7 @@ static const struct net_device_ops enetc4_ndev_ops = { .ndo_hwtstamp_get = enetc_hwtstamp_get, .ndo_hwtstamp_set = enetc_hwtstamp_set, .ndo_set_vf_trust = enetc_pf_set_vf_trust, + .ndo_set_vf_mac = enetc_pf_set_vf_mac, }; static struct phylink_pcs * -- 2.34.1 From: Wei Fang The mac_filter array currently resides in struct enetc_pf and is used to track unicast and multicast MAC address filters for the PF. Since struct enetc_si is the common structure shared between the PF and VF drivers, move mac_filter into struct enetc_si to prepare for MAC filter support in the VF driver. Signed-off-by: Wei Fang --- drivers/net/ethernet/freescale/enetc/enetc.h | 2 ++ .../net/ethernet/freescale/enetc/enetc4_pf.c | 6 +++--- drivers/net/ethernet/freescale/enetc/enetc_pf.c | 17 ++++++++--------- drivers/net/ethernet/freescale/enetc/enetc_pf.h | 2 -- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/drivers/net/ethernet/freescale/enetc/enetc.h b/drivers/net/ethernet/freescale/enetc/enetc.h index bc713a7c3aa1..8d7c1790b672 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc.h +++ b/drivers/net/ethernet/freescale/enetc/enetc.h @@ -336,6 +336,8 @@ struct enetc_si { struct enetc_msg_swbd msg; /* Only valid for VSI */ struct work_struct msg_task; char msg_int_name[ENETC_INT_NAME_MAX]; + + struct enetc_mac_filter mac_filter[MADDR_TYPE]; }; #define ENETC_SI_ALIGN 32 diff --git a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c index eeb70feeb773..363ec562934e 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c @@ -137,7 +137,7 @@ static int enetc4_pf_add_maft_entries(struct enetc_pf *pf, static void enetc4_pf_set_uc_hash_filter(struct enetc_pf *pf, struct netdev_hw_addr_list *uc) { - struct enetc_mac_filter *mac_filter = &pf->mac_filter[UC]; + struct enetc_mac_filter *mac_filter = &pf->si->mac_filter[UC]; struct netdev_hw_addr *ha; u64 hash; @@ -172,7 +172,7 @@ static int enetc4_pf_set_uc_exact_filter(struct enetc_pf *pf, err = enetc4_pf_add_maft_entries(pf, uc); if (!err) { - enetc_reset_mac_addr_filter(&pf->mac_filter[UC]); + enetc_reset_mac_addr_filter(&si->mac_filter[UC]); enetc_set_si_uc_hash_filter(si, 0, 0); } @@ -182,7 +182,7 @@ static int enetc4_pf_set_uc_exact_filter(struct enetc_pf *pf, static void enetc4_pf_set_mc_hash_filter(struct enetc_pf *pf, struct netdev_hw_addr_list *mc) { - struct enetc_mac_filter *mac_filter = &pf->mac_filter[MC]; + struct enetc_mac_filter *mac_filter = &pf->si->mac_filter[MC]; struct netdev_hw_addr *ha; u64 hash; diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf.c b/drivers/net/ethernet/freescale/enetc/enetc_pf.c index c467dc05510e..523c71324780 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf.c @@ -60,10 +60,9 @@ static void enetc_add_mac_addr_em_filter(struct enetc_mac_filter *filter, filter->mac_addr_cnt++; } -static void enetc_sync_mac_filters(struct enetc_pf *pf) +static void enetc_sync_mac_filters(struct enetc_si *si) { - struct enetc_mac_filter *f = pf->mac_filter; - struct enetc_si *si = pf->si; + struct enetc_mac_filter *f = si->mac_filter; int i, pos; pos = EMETC_MAC_ADDR_FILT_RES; @@ -115,9 +114,9 @@ static void enetc_sync_mac_filters(struct enetc_pf *pf) static void enetc_pf_set_rx_mode(struct net_device *ndev) { struct enetc_ndev_priv *priv = netdev_priv(ndev); - struct enetc_pf *pf = enetc_si_priv(priv->si); bool uprom = false, mprom = false; struct enetc_mac_filter *filter; + struct enetc_si *si = priv->si; struct netdev_hw_addr *ha; bool em; @@ -133,7 +132,7 @@ static void enetc_pf_set_rx_mode(struct net_device *ndev) /* first 2 filter entries belong to PF */ if (!uprom) { /* Update unicast filters */ - filter = &pf->mac_filter[UC]; + filter = &si->mac_filter[UC]; enetc_reset_mac_addr_filter(filter); em = (netdev_uc_count(ndev) == 1); @@ -149,7 +148,7 @@ static void enetc_pf_set_rx_mode(struct net_device *ndev) if (!mprom) { /* Update multicast filters */ - filter = &pf->mac_filter[MC]; + filter = &si->mac_filter[MC]; enetc_reset_mac_addr_filter(filter); netdev_for_each_mc_addr(ha, ndev) { @@ -162,10 +161,10 @@ static void enetc_pf_set_rx_mode(struct net_device *ndev) if (!uprom || !mprom) /* update PF entries */ - enetc_sync_mac_filters(pf); + enetc_sync_mac_filters(si); - enetc_set_si_uc_promisc(priv->si, 0, uprom); - enetc_set_si_mc_promisc(priv->si, 0, mprom); + enetc_set_si_uc_promisc(si, 0, uprom); + enetc_set_si_mc_promisc(si, 0, mprom); } static void enetc_set_loopback(struct net_device *ndev, bool en) diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf.h b/drivers/net/ethernet/freescale/enetc/enetc_pf.h index fb48a06e6c42..06dc47164dc5 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf.h +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf.h @@ -38,8 +38,6 @@ struct enetc_pf { int num_vfs; /* number of active VFs, after sriov_init */ int total_vfs; /* max number of VFs, set for PF at probe */ struct enetc_vf_state *vf_state; - - struct enetc_mac_filter mac_filter[MADDR_TYPE]; struct enetc_msg_swbd *rxmsg; DECLARE_BITMAP(vlan_ht_filter, ENETC_VLAN_HT_SIZE); -- 2.34.1 From: Wei Fang The ENETC v4 VF hardware supports MAC address filtering, but the underlying hardware resources (PSIPMMR register and per-SI hash filter tables) are owned and managed exclusively by the PF driver. Add VSI-to-PSI mailbox message support so that VFs can request MAC filter configuration from the PF. Two new command IDs are introduced under the existing MAC filter message class (0x20): 1. ENETC_MSG_SET_MAC_HASH_TABLE (cmd_id 3): allows a trusted VF to program its unicast and/or multicast MAC hash filter table. The PF validates that the hardware-supported 64-bit table size is requested before applying the configuration via the per-SI hash filter registers. 2. ENETC_MSG_SET_MAC_PROMISC_MODE (cmd_id 5): allows a VF to enable or disable unicast/multicast promiscuous mode, and optionally flush the associated hash filter table. Enabling promiscuous mode requires the VF to be marked as trusted, since it widens the traffic received by the VF. Flushing the hash table without enabling promiscuous mode does not require elevated privilege. The PSIPMMR register holds promiscuous mode bits for all SIs and is modified by both enetc4_pf_set_rx_mode() and the VF message handler workqueue (enetc_msg_task). Since both functions can run concurrently on SMP systems and enetc_set_si_uc/mc_promisc() performs a non-atomic read-modify-write, protect all accesses to this register with pf->msg_lock to prevent lost updates. When a VF loses trusted status via ndo_set_vf_trust(), its unicast hash filter is cleared and promiscuous mode is disabled to prevent it from receiving traffic beyond its allowed scope. Signed-off-by: Wei Fang --- .../ethernet/freescale/enetc/enetc4_debugfs.c | 51 +++-- .../net/ethernet/freescale/enetc/enetc4_pf.c | 7 +- .../ethernet/freescale/enetc/enetc_mailbox.h | 42 ++++ .../net/ethernet/freescale/enetc/enetc_msg.c | 181 +++++++++++++++++- .../net/ethernet/freescale/enetc/enetc_pf.h | 1 + .../freescale/enetc/enetc_pf_common.c | 68 ++++++- .../freescale/enetc/enetc_pf_common.h | 12 ++ .../net/ethernet/freescale/enetc/enetc_vf.c | 6 +- 8 files changed, 332 insertions(+), 36 deletions(-) diff --git a/drivers/net/ethernet/freescale/enetc/enetc4_debugfs.c b/drivers/net/ethernet/freescale/enetc/enetc4_debugfs.c index 5029038bf99f..91af78ba4f74 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc4_debugfs.c +++ b/drivers/net/ethernet/freescale/enetc/enetc4_debugfs.c @@ -6,24 +6,48 @@ #include #include -#include "enetc_pf.h" +#include "enetc_pf_common.h" #include "enetc4_debugfs.h" -static void enetc_show_si_mac_hash_filter(struct seq_file *s, int i) +static void enetc_vf_state_lock(struct enetc_pf *pf, int vf_id) { - struct enetc_si *si = s->private; - struct enetc_hw *hw = &si->hw; + struct enetc_vf_state *vf_state; + + if (vf_id < 0) + return; + + vf_state = &pf->vf_state[vf_id]; + mutex_lock(&vf_state->lock); +} + +static void enetc_vf_state_unlock(struct enetc_pf *pf, int vf_id) +{ + struct enetc_vf_state *vf_state; + + if (vf_id < 0) + return; + + vf_state = &pf->vf_state[vf_id]; + mutex_unlock(&vf_state->lock); +} + +static void enetc_show_si_mac_hash_filter(struct seq_file *s, int si_id) +{ + struct enetc_pf *pf = enetc_si_priv(s->private); + struct enetc_hw *hw = &pf->si->hw; u32 hash_h, hash_l; - hash_l = enetc_port_rd(hw, ENETC4_PSIUMHFR0(i)); - hash_h = enetc_port_rd(hw, ENETC4_PSIUMHFR1(i)); + enetc_vf_state_lock(pf, si_id - 1); + hash_l = enetc_port_rd(hw, ENETC4_PSIUMHFR0(si_id)); + hash_h = enetc_port_rd(hw, ENETC4_PSIUMHFR1(si_id)); seq_printf(s, "SI %d unicast MAC hash filter: 0x%08x%08x\n", - i, hash_h, hash_l); + si_id, hash_h, hash_l); - hash_l = enetc_port_rd(hw, ENETC4_PSIMMHFR0(i)); - hash_h = enetc_port_rd(hw, ENETC4_PSIMMHFR1(i)); + hash_l = enetc_port_rd(hw, ENETC4_PSIMMHFR0(si_id)); + hash_h = enetc_port_rd(hw, ENETC4_PSIMMHFR1(si_id)); seq_printf(s, "SI %d multicast MAC hash filter: 0x%08x%08x\n", - i, hash_h, hash_l); + si_id, hash_h, hash_l); + enetc_vf_state_unlock(pf, si_id - 1); } static int enetc_mac_filter_show(struct seq_file *s, void *data) @@ -37,7 +61,11 @@ static int enetc_mac_filter_show(struct seq_file *s, void *data) int err = 0; int i; + /* Prevent concurrent access from causing PSIPMMR to be modified */ + enetc_pf_msg_lock(pf); val = enetc_port_rd(hw, ENETC4_PSIPMMR); + enetc_pf_msg_unlock(pf); + for (i = 0; i < num_si; i++) { seq_printf(s, "SI %d Unicast Promiscuous mode: %s\n", i, str_enabled_disabled(PSIPMMR_SI_MAC_UP(i) & val)); @@ -45,13 +73,12 @@ static int enetc_mac_filter_show(struct seq_file *s, void *data) str_enabled_disabled(PSIPMMR_SI_MAC_MP(i) & val)); } + rtnl_lock(); /* MAC hash filter table */ for (i = 0; i < num_si; i++) enetc_show_si_mac_hash_filter(s, i); user = &pf->si->ntmp_user; - rtnl_lock(); - if (bitmap_empty(user->maft_eid_bitmap, user->maft_num_entries)) goto unlock_rtnl; diff --git a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c index 363ec562934e..a4ffe1100bd7 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c @@ -12,11 +12,6 @@ #define ENETC_SI_MAX_RING_NUM 8 -#define ENETC_MAC_FILTER_TYPE_UC BIT(0) -#define ENETC_MAC_FILTER_TYPE_MC BIT(1) -#define ENETC_MAC_FILTER_TYPE_ALL (ENETC_MAC_FILTER_TYPE_UC | \ - ENETC_MAC_FILTER_TYPE_MC) - static void enetc4_get_port_caps(struct enetc_pf *pf) { struct enetc_hw *hw = &pf->si->hw; @@ -528,8 +523,10 @@ static int enetc4_pf_set_rx_mode(struct net_device *ndev, type = ENETC_MAC_FILTER_TYPE_ALL; } + enetc_pf_msg_lock(pf); enetc_set_si_uc_promisc(si, 0, uc_promisc); enetc_set_si_mc_promisc(si, 0, mc_promisc); + enetc_pf_msg_unlock(pf); if (uc_promisc) { enetc_set_si_uc_hash_filter(si, 0, 0); diff --git a/drivers/net/ethernet/freescale/enetc/enetc_mailbox.h b/drivers/net/ethernet/freescale/enetc/enetc_mailbox.h index bd669543e96c..200651e6ce85 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_mailbox.h +++ b/drivers/net/ethernet/freescale/enetc/enetc_mailbox.h @@ -96,6 +96,17 @@ #define ENETC_PF_MSG_CLASS_CODE_U8 GENMASK(7, 0) #define ENETC_PF_MSG_CLASS_ID GENMASK(15, 8) +#define ENETC_MAC_HASH_TABLE_SIZE_64 0 +#define ENETC_MSG_MAC_HASH_SIZE GENMASK(5, 0) +#define ENETC_MSG_MAC_TYPE GENMASK(7, 6) +#define ENETC_MAC_FILTER_TYPE_UC BIT(0) +#define ENETC_MAC_FILTER_TYPE_MC BIT(1) +#define ENETC_MAC_FILTER_TYPE_ALL (ENETC_MAC_FILTER_TYPE_UC | \ + ENETC_MAC_FILTER_TYPE_MC) + +#define ENETC_MSG_MAC_FLUSH_MACS BIT(0) +#define ENETC_MSG_MAC_PROMISC_MODE BIT(1) + enum enetc_msg_class_id { /* Class ID for PSI-to-VSI messages */ ENETC_MSG_CLASS_ID_CMD_SUCCESS = 1, @@ -119,6 +130,8 @@ enum enetc_msg_class_id { enum enetc_msg_mac_filter_cmd_id { ENETC_MSG_SET_PRIMARY_MAC, + ENETC_MSG_SET_MAC_HASH_TABLE = 3, + ENETC_MSG_SET_MAC_PROMISC_MODE = 5, }; enum enetc_msg_ip_revision_cmd_id { @@ -141,6 +154,9 @@ enum enetc_msg_link_speed_cmd_id { /* Class-specific error return codes of MAC filter */ enum enetc_mac_filter_class_code { ENETC_MF_CLASS_CODE_INVALID_MAC, + ENETC_MF_CLASS_CODE_INVALID_TYPE = 4, + /* Unicast Filter Is Denied */ + ENETC_MF_CLASS_CODE_UCF_DENY = 5, }; /* Class-specific notifications/codes of link status */ @@ -204,6 +220,32 @@ struct enetc_msg_mac_exact_filter { struct enetc_mac_addr mac[]; }; +/* message format of class_id 0x20 for hash MAC filter. + * cmd_id 0x3: set MAC hash table + */ +struct enetc_msg_mac_hash_filter { + struct enetc_msg_header hdr; + /* bit 0 ~ 5: ENETC_MSG_MAC_HASH_SIZE + * bit 6~7: ENETC_MSG_MAC_TYPE + */ + u8 sz_type; + u8 resv[3]; + u32 hash_tbl[]; +}; + +/* message format of class_id 0x20 for MAC promiscuous mode. + * cmd_id 0x5: set MAC promiscuous mode + */ +struct enetc_msg_mac_promisc_mode { + struct enetc_msg_header hdr; + /* bit 0: ENETC_MSG_MAC_FLUSH_MACS + * bit 1: ENETC_MSG_MAC_PROMISC_MODE + * bit 6~7: ENETC_MSG_MAC_TYPE + */ + u8 config; + u8 resv[15]; +}; + /* The generic message format applies to the following messages: * Get IP revision message, class_id 0xf0. * cmd_id 1: get IP minor revision diff --git a/drivers/net/ethernet/freescale/enetc/enetc_msg.c b/drivers/net/ethernet/freescale/enetc/enetc_msg.c index c3ae4c024f34..d58fbaeaf46c 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_msg.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_msg.c @@ -9,6 +9,11 @@ ENETC_MSG_CLASS_ID_CMD_NOT_SUPPORT) #define ENETC_PF_MSG_PERM_DENY FIELD_PREP(ENETC_PF_MSG_CLASS_ID, \ ENETC_MSG_CLASS_ID_PERMISSION_DENY) +#define ENETC_PF_MSG_INV_LEN FIELD_PREP(ENETC_PF_MSG_CLASS_ID, \ + ENETC_MSG_CLASS_ID_INVALID_MSG_LEN) +#define ENETC_PF_MSG_MF(code) (FIELD_PREP(ENETC_PF_MSG_CLASS_ID, \ + ENETC_MSG_CLASS_ID_MAC_FILTER) | \ + FIELD_PREP(ENETC_PF_MSG_CLASS_CODE, (code))) static void enetc_msg_disable_mr_int(struct enetc_pf *pf) { @@ -79,10 +84,7 @@ static u16 enetc_msg_set_vf_primary_mac_addr(struct enetc_pf *pf, int vf_id, if (!is_valid_ether_addr(addr)) { dev_err_ratelimited(dev, "VF%d attempted to set invalid MAC\n", vf_id); - pf_msg = FIELD_PREP(ENETC_PF_MSG_CLASS_ID, - ENETC_MSG_CLASS_ID_MAC_FILTER) | - FIELD_PREP(ENETC_PF_MSG_CLASS_CODE, - ENETC_MF_CLASS_CODE_INVALID_MAC); + pf_msg = ENETC_PF_MSG_MF(ENETC_MF_CLASS_CODE_INVALID_MAC); goto vf_state_unlock; } @@ -108,6 +110,132 @@ static u16 enetc_msg_set_vf_primary_mac_addr(struct enetc_pf *pf, int vf_id, return pf_msg; } +static u16 enetc_msg_set_vf_mac_hash_filter(struct enetc_pf *pf, int vf_id, + void *vf_msg) +{ + struct enetc_vf_state *vf_state = &pf->vf_state[vf_id]; + struct enetc_msg_mac_hash_filter *msg = vf_msg; + u16 pf_msg = ENETC_PF_MSG_SUCCESS; + struct enetc_si *si = pf->si; + int si_id = vf_id + 1; + u64 uc_hash, mc_hash; + bool trusted; + int type; + + /* Currently, hardware only supports 64 bits table size */ + if (FIELD_GET(ENETC_MSG_MAC_HASH_SIZE, msg->sz_type) != + ENETC_MAC_HASH_TABLE_SIZE_64) + return ENETC_PF_MSG_NOTSUPP; + + mutex_lock(&vf_state->lock); + + /* For an untrusted VF, unicast MAC hash filtering is not permitted. + * For multicast, the MAC hash filter is strictly limited to a maximum + * of 8 bits to satisfy its basic multicast communication requirements + * while preventing potential network abuse. + */ + trusted = !!(vf_state->flags & ENETC_VF_FLAG_TRUSTED); + type = FIELD_GET(ENETC_MSG_MAC_TYPE, msg->sz_type); + switch (type) { + case ENETC_MAC_FILTER_TYPE_UC: + if (!trusted) { + pf_msg = ENETC_PF_MSG_PERM_DENY; + goto vf_state_unlock; + } + + uc_hash = (u64)msg->hash_tbl[1] << 32 | msg->hash_tbl[0]; + enetc_set_si_uc_hash_filter(si, si_id, uc_hash); + break; + case ENETC_MAC_FILTER_TYPE_MC: + mc_hash = (u64)msg->hash_tbl[1] << 32 | msg->hash_tbl[0]; + if (!trusted && + hweight64(mc_hash) > ENETC_VF_MC_HASH_BITS_MAX) { + pf_msg = ENETC_PF_MSG_PERM_DENY; + goto vf_state_unlock; + } + + enetc_set_si_mc_hash_filter(si, si_id, mc_hash); + break; + case ENETC_MAC_FILTER_TYPE_ALL: + if (!msg->hdr.len) { + pf_msg = ENETC_PF_MSG_INV_LEN; + goto vf_state_unlock; + } + + uc_hash = (u64)msg->hash_tbl[1] << 32 | msg->hash_tbl[0]; + mc_hash = (u64)msg->hash_tbl[3] << 32 | msg->hash_tbl[2]; + + if (!trusted && + (hweight64(mc_hash) <= ENETC_VF_MC_HASH_BITS_MAX)) { + enetc_set_si_mc_hash_filter(si, si_id, mc_hash); + pf_msg = ENETC_PF_MSG_MF(ENETC_MF_CLASS_CODE_UCF_DENY); + goto vf_state_unlock; + } + + if (!trusted) { + pf_msg = ENETC_PF_MSG_PERM_DENY; + goto vf_state_unlock; + } + + enetc_set_si_uc_hash_filter(si, si_id, uc_hash); + enetc_set_si_mc_hash_filter(si, si_id, mc_hash); + break; + default: + pf_msg = ENETC_PF_MSG_MF(ENETC_MF_CLASS_CODE_INVALID_TYPE); + } + +vf_state_unlock: + mutex_unlock(&vf_state->lock); + + return pf_msg; +} + +static u16 enetc_msg_set_vf_mac_promisc_mode(struct enetc_pf *pf, int vf_id, + void *vf_msg) +{ + struct enetc_vf_state *vf_state = &pf->vf_state[vf_id]; + struct enetc_msg_mac_promisc_mode *msg = vf_msg; + u16 pf_msg = ENETC_PF_MSG_SUCCESS; + struct enetc_si *si = pf->si; + bool promisc, flush_macs; + int si_id = vf_id + 1; + int type; + + flush_macs = !!(msg->config & ENETC_MSG_MAC_FLUSH_MACS); + type = FIELD_GET(ENETC_MSG_MAC_TYPE, msg->config); + if (!type) + return ENETC_PF_MSG_MF(ENETC_MF_CLASS_CODE_INVALID_TYPE); + + mutex_lock(&vf_state->lock); + + promisc = !!(msg->config & ENETC_MSG_MAC_PROMISC_MODE); + if (promisc && !(vf_state->flags & ENETC_VF_FLAG_TRUSTED)) { + pf_msg = ENETC_PF_MSG_PERM_DENY; + goto vf_state_unlock; + } + + mutex_lock(&pf->msg_lock); + + if (type & ENETC_MAC_FILTER_TYPE_UC) + enetc_set_si_uc_promisc(si, si_id, promisc); + + if (type & ENETC_MAC_FILTER_TYPE_MC) + enetc_set_si_mc_promisc(si, si_id, promisc); + + mutex_unlock(&pf->msg_lock); + + if ((type & ENETC_MAC_FILTER_TYPE_UC) && flush_macs) + enetc_set_si_uc_hash_filter(si, si_id, 0); + + if ((type & ENETC_MAC_FILTER_TYPE_MC) && flush_macs) + enetc_set_si_mc_hash_filter(si, si_id, 0); + +vf_state_unlock: + mutex_unlock(&vf_state->lock); + + return pf_msg; +} + static u16 enetc_msg_handle_mac_filter(struct enetc_pf *pf, int vf_id, void *vf_msg) { @@ -116,6 +244,10 @@ static u16 enetc_msg_handle_mac_filter(struct enetc_pf *pf, int vf_id, switch (msg_hdr->cmd_id) { case ENETC_MSG_SET_PRIMARY_MAC: return enetc_msg_set_vf_primary_mac_addr(pf, vf_id, vf_msg); + case ENETC_MSG_SET_MAC_HASH_TABLE: + return enetc_msg_set_vf_mac_hash_filter(pf, vf_id, vf_msg); + case ENETC_MSG_SET_MAC_PROMISC_MODE: + return enetc_msg_set_vf_mac_promisc_mode(pf, vf_id, vf_msg); default: return ENETC_PF_MSG_NOTSUPP; } @@ -383,8 +515,7 @@ static void enetc_msg_handle_rxmsg(struct enetc_pf *pf, int vf_id, if (msg_size > ENETC_DEFAULT_MSG_SIZE) { dev_err_ratelimited(dev, "Invalid message size: %u\n", msg_size); - *pf_msg = FIELD_PREP(ENETC_PF_MSG_CLASS_ID, - ENETC_MSG_CLASS_ID_INVALID_MSG_LEN); + *pf_msg = ENETC_PF_MSG_INV_LEN; return; } @@ -402,6 +533,14 @@ static void enetc_msg_handle_rxmsg(struct enetc_pf *pf, int vf_id, } memcpy(msg, msg_swbd->vaddr, msg_size); + msg_hdr = (struct enetc_msg_header *)msg; + + /* Check message length whether is changed */ + if (ENETC_MSG_SIZE(msg_hdr->len) != msg_size) { + *pf_msg = ENETC_PF_MSG_INV_LEN; + goto free_msg; + } + if (!enetc_msg_check_crc16(msg, msg_size)) { dev_err_ratelimited(dev, "VSI to PSI Message CRC16 error\n"); *pf_msg = FIELD_PREP(ENETC_PF_MSG_CLASS_ID, @@ -412,7 +551,6 @@ static void enetc_msg_handle_rxmsg(struct enetc_pf *pf, int vf_id, /* Default to not supported */ *pf_msg = ENETC_PF_MSG_NOTSUPP; - msg_hdr = (struct enetc_msg_header *)msg; /* Currently, asynchronous actions are not supported */ if (FIELD_GET(ENETC_VF_MSG_COOKIE, msg_hdr->cookie)) { @@ -582,6 +720,31 @@ static int enetc_msg_psi_init(struct enetc_pf *pf) return err; } +static void enetc_msg_clear_vf_config(struct enetc_pf *pf, int vf_id) +{ + struct enetc_vf_state *vf_state = &pf->vf_state[vf_id]; + struct enetc_si *si = pf->si; + int si_id = vf_id + 1; + + /* For ENETC v1, we only support setting the VF's MAC address via + * VSI-to-PSI messages, so there is no configuration to clear. + */ + if (is_enetc_rev1(si)) + return; + + mutex_lock(&vf_state->lock); + + mutex_lock(&pf->msg_lock); + enetc_set_si_uc_promisc(si, si_id, false); + enetc_set_si_mc_promisc(si, si_id, false); + mutex_unlock(&pf->msg_lock); + + enetc_set_si_uc_hash_filter(si, si_id, 0); + enetc_set_si_mc_hash_filter(si, si_id, 0); + + mutex_unlock(&vf_state->lock); +} + static void enetc_msg_psi_free(struct enetc_pf *pf) { struct enetc_si *si = pf->si; @@ -598,8 +761,10 @@ static void enetc_msg_psi_free(struct enetc_pf *pf) /* MR interrupts may be re-enabled by workqueue */ enetc_msg_disable_mr_int(pf); - for (i = 0; i < pf->num_vfs; i++) + for (i = 0; i < pf->num_vfs; i++) { enetc_msg_free_mbx(si, i); + enetc_msg_clear_vf_config(pf, i); + } } int enetc_sriov_configure(struct pci_dev *pdev, int num_vfs) diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf.h b/drivers/net/ethernet/freescale/enetc/enetc_pf.h index 06dc47164dc5..12e67f611f77 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf.h +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf.h @@ -6,6 +6,7 @@ #define ENETC_PF_NUM_RINGS 8 #define ENETC_VLAN_HT_SIZE 64 +#define ENETC_VF_MC_HASH_BITS_MAX 8 /* For untrusted VFs */ enum enetc_vf_flags { ENETC_VF_FLAG_PF_SET_MAC = BIT(0), diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c index 7a11370d2b8e..8007dce90195 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c @@ -151,26 +151,44 @@ void enetc_set_si_uc_hash_filter(struct enetc_si *si, int si_id, u64 hash) } EXPORT_SYMBOL_GPL(enetc_set_si_uc_hash_filter); -void enetc_set_si_mc_hash_filter(struct enetc_si *si, int si_id, u64 hash) +static void enetc_get_psimmhfr_offsets(struct enetc_si *si, int si_id, + int *psimmhfr0, int *psimmhfr1) { - int psimmhfr0_off, psimmhfr1_off; - struct enetc_hw *hw = &si->hw; - if (is_enetc_rev1(si)) { bool err = si->errata & ENETC_ERR_UCMCSWP; - psimmhfr0_off = ENETC_PSIMMHFR0(si_id, err); - psimmhfr1_off = ENETC_PSIMMHFR1(si_id); + *psimmhfr0 = ENETC_PSIMMHFR0(si_id, err); + *psimmhfr1 = ENETC_PSIMMHFR1(si_id); } else { - psimmhfr0_off = ENETC4_PSIMMHFR0(si_id); - psimmhfr1_off = ENETC4_PSIMMHFR1(si_id); + *psimmhfr0 = ENETC4_PSIMMHFR0(si_id); + *psimmhfr1 = ENETC4_PSIMMHFR1(si_id); } +} +void enetc_set_si_mc_hash_filter(struct enetc_si *si, int si_id, u64 hash) +{ + int psimmhfr0_off, psimmhfr1_off; + struct enetc_hw *hw = &si->hw; + + enetc_get_psimmhfr_offsets(si, si_id, &psimmhfr0_off, &psimmhfr1_off); enetc_port_wr(hw, psimmhfr0_off, lower_32_bits(hash)); enetc_port_wr(hw, psimmhfr1_off, upper_32_bits(hash)); } EXPORT_SYMBOL_GPL(enetc_set_si_mc_hash_filter); +static u64 enetc_get_si_mc_hash_filter(struct enetc_si *si, int si_id) +{ + int psimmhfr0_off, psimmhfr1_off; + struct enetc_hw *hw = &si->hw; + u32 hash_h, hash_l; + + enetc_get_psimmhfr_offsets(si, si_id, &psimmhfr0_off, &psimmhfr1_off); + hash_l = enetc_port_rd(hw, psimmhfr0_off); + hash_h = enetc_port_rd(hw, psimmhfr1_off); + + return ((u64)hash_h << 32) | hash_l; +} + void enetc_set_si_vlan_promisc(struct enetc_si *si, int si_id, bool promisc) { struct enetc_hw *hw = &si->hw; @@ -593,6 +611,8 @@ int enetc_pf_set_vf_trust(struct net_device *ndev, int vf, bool setting) struct enetc_ndev_priv *priv = netdev_priv(ndev); struct enetc_pf *pf = enetc_si_priv(priv->si); struct enetc_vf_state *vf_state; + struct enetc_si *si = priv->si; + int si_id = vf + 1; if (vf >= pf->total_vfs) return -EINVAL; @@ -600,11 +620,39 @@ int enetc_pf_set_vf_trust(struct net_device *ndev, int vf, bool setting) vf_state = &pf->vf_state[vf]; mutex_lock(&vf_state->lock); - if (setting) + if (setting) { vf_state->flags |= ENETC_VF_FLAG_TRUSTED; - else + } else { + u64 hash; + vf_state->flags &= ~ENETC_VF_FLAG_TRUSTED; + /* For ENETC v1, we only support setting the VF's MAC address + * via VSI-to-PSI messages. Unicast and multicast promiscuous + * mode and hash filters are not supported, so there is no need + * to clear these configurations. + */ + if (is_enetc_rev1(si)) + goto vf_state_unlock; + + /* Disable unicast and multicast promiscuous modes */ + mutex_lock(&pf->msg_lock); + enetc_set_si_uc_promisc(si, si_id, false); + enetc_set_si_mc_promisc(si, si_id, false); + mutex_unlock(&pf->msg_lock); + + /* Clear unicast hash filter */ + enetc_set_si_uc_hash_filter(si, si_id, 0); + + /* Clear multicast hash filter if its set bits exceed + * ENETC_VF_MC_HASH_BITS_MAX. + */ + hash = enetc_get_si_mc_hash_filter(si, si_id); + if (hweight64(hash) > ENETC_VF_MC_HASH_BITS_MAX) + enetc_set_si_mc_hash_filter(si, si_id, 0); + } + +vf_state_unlock: mutex_unlock(&vf_state->lock); return 0; diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h index 1d35e906fc3a..91a9c339245a 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h @@ -30,6 +30,18 @@ static inline u16 enetc_get_ip_revision(struct enetc_hw *hw) return enetc_global_rd(hw, ENETC_G_EIPBRR0) & EIPBRR0_REVISION; } +static inline void enetc_pf_msg_lock(struct enetc_pf *pf) +{ + if (pf->total_vfs) + mutex_lock(&pf->msg_lock); +} + +static inline void enetc_pf_msg_unlock(struct enetc_pf *pf) +{ + if (pf->total_vfs) + mutex_unlock(&pf->msg_lock); +} + #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); diff --git a/drivers/net/ethernet/freescale/enetc/enetc_vf.c b/drivers/net/ethernet/freescale/enetc/enetc_vf.c index 7dcb4a0246f5..a60af40d8546 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_vf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_vf.c @@ -107,8 +107,12 @@ static int enetc_msg_vsi_send(struct enetc_si *si, struct enetc_msg_swbd *msg) case ENETC_MSG_CLASS_ID_CMD_TIMEOUT: err = -ETIME; break; - case ENETC_MSG_CLASS_ID_INVALID_MSG_LEN: case ENETC_MSG_CLASS_ID_MAC_FILTER: + if (FIELD_GET(ENETC_PF_MSG_CLASS_CODE, pf_msg) == + ENETC_MF_CLASS_CODE_UCF_DENY) + return -EACCES; + fallthrough; + case ENETC_MSG_CLASS_ID_INVALID_MSG_LEN: err = -EINVAL; break; case ENETC_MSG_CLASS_ID_CMD_NOT_PERMITTED: -- 2.34.1 From: Wei Fang The PSIIER register controls two categories of interrupt sources: message-receive (MR) interrupts, which fire when a VF sends a mailbox message to the PSI, and VF FLR interrupts, which fire when a VF performs a Function Level Reset. The current helpers enetc_msg_enable_mr_int() and enetc_msg_disable_mr_int() use a read-modify-write sequence to update only the MR bits in PSIIER, intending to preserve any other bits that may be set. However, VF FLR interrupt support is not yet implemented, so PSIIER only ever holds MR interrupt bits at this point. The read-modify-write is therefore unnecessary overhead. Simplify enetc_disable_psiier_interrupts() to write 0 directly to PSIIER, disabling all interrupt sources at once, and simplify enetc_enable_psiier_interrupts() to write the MR mask directly without reading the current register value first. Rename both helpers from the MR-specific names to names that reflect their true scope, i.e. managing all PSIIER interrupt sources rather than just the MR bits. This prepares the code for a future patch that adds VF FLR interrupt support, at which point enetc_enable_psiier_interrupts() will be extended to also set the corresponding FLR bits. Signed-off-by: Wei Fang --- .../net/ethernet/freescale/enetc/enetc_msg.c | 30 ++++++++----------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/drivers/net/ethernet/freescale/enetc/enetc_msg.c b/drivers/net/ethernet/freescale/enetc/enetc_msg.c index d58fbaeaf46c..4aabeb23a386 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_msg.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_msg.c @@ -15,23 +15,17 @@ ENETC_MSG_CLASS_ID_MAC_FILTER) | \ FIELD_PREP(ENETC_PF_MSG_CLASS_CODE, (code))) -static void enetc_msg_disable_mr_int(struct enetc_pf *pf) +static void enetc_disable_psiier_interrupts(struct enetc_pf *pf) { struct enetc_hw *hw = &pf->si->hw; - u32 psiier; - psiier = enetc_rd(hw, ENETC_PSIIER) & ~ENETC_PSIMR_MASK(pf->num_vfs); - - /* disable MR int source(s) */ - enetc_wr(hw, ENETC_PSIIER, psiier); + enetc_wr(hw, ENETC_PSIIER, 0); } -static void enetc_msg_enable_mr_int(struct enetc_pf *pf) +static void enetc_enable_psiier_interrupts(struct enetc_pf *pf) { + u32 psiier = ENETC_PSIMR_MASK(pf->num_vfs); struct enetc_hw *hw = &pf->si->hw; - u32 psiier; - - psiier = enetc_rd(hw, ENETC_PSIIER) | ENETC_PSIMR_MASK(pf->num_vfs); enetc_wr(hw, ENETC_PSIIER, psiier); } @@ -41,7 +35,7 @@ static irqreturn_t enetc_msg_psi_msix(int irq, void *data) struct enetc_si *si = (struct enetc_si *)data; struct enetc_pf *pf = enetc_si_priv(si); - enetc_msg_disable_mr_int(pf); + enetc_disable_psiier_interrupts(pf); schedule_work(&si->msg_task); return IRQ_HANDLED; @@ -633,7 +627,7 @@ static void enetc_msg_task(struct work_struct *work) } out: - enetc_msg_enable_mr_int(pf); + enetc_enable_psiier_interrupts(pf); } /* Init */ @@ -708,8 +702,8 @@ static int enetc_msg_psi_init(struct enetc_pf *pf) /* set one IRQ entry for PSI message receive notification (SI int) */ enetc_wr(&si->hw, ENETC_SIMSIVR, ENETC_SI_INT_IDX); - /* enable MR interrupts */ - enetc_msg_enable_mr_int(pf); + /* enable PSIIER interrupts */ + enetc_enable_psiier_interrupts(pf); return 0; @@ -750,16 +744,16 @@ static void enetc_msg_psi_free(struct enetc_pf *pf) struct enetc_si *si = pf->si; int i; - /* disable MR interrupts */ - enetc_msg_disable_mr_int(pf); + /* disable PSIIER interrupts */ + enetc_disable_psiier_interrupts(pf); /* de-register message passing interrupt handler */ free_irq(pci_irq_vector(si->pdev, ENETC_SI_INT_IDX), si); cancel_work_sync(&si->msg_task); - /* MR interrupts may be re-enabled by workqueue */ - enetc_msg_disable_mr_int(pf); + /* PSIIER interrupts may be re-enabled by workqueue */ + enetc_disable_psiier_interrupts(pf); for (i = 0; i < pf->num_vfs; i++) { enetc_msg_free_mbx(si, i); -- 2.34.1 From: Wei Fang On ENETC v4, when VF performs a PCI FLR, it resets PSIPMMR[SIn_MAC_UP] and PSIPMMR[SIn_MAC_MP] bits, which control the unicast and multicast promiscuous mode for the corresponding SI. The reset (default) value of these bits enables promiscuous mode, meaning that after a VF FLR, the SI is left in promiscuous mode regardless of the configuration set by the PF driver prior to the reset. This is a potential security vulnerability: a malicious VM could deliberately trigger a VF FLR to force promiscuous mode on its SI, allowing it to capture network traffic not destined for that VF. To mitigate this, make the following changes: - Add ENETC_VF_FLAG_UC_PROMISC and ENETC_VF_FLAG_MC_PROMISC to enetc_vf_flags to track the PF-managed promiscuous mode state for each VF. - Update enetc_msg_set_vf_mac_promisc_mode() to keep these flags in sync whenever a VF requests a promiscuous mode change via messaging. - Update enetc_pf_set_vf_trust() to clear both promisc flags when a VF is untrusted, so that a subsequent FLR cannot restore promiscuous mode that the PF has already revoked. - Add a vf_flr_handler callback to enetc_pf_ops. The ENETC v4 implementation re-applies the tracked UC/MC promiscuous mode settings to the hardware after each FLR, ensuring the hardware state matches the PF-managed policy rather than the insecure reset default. - Add enetc_vf_flr_handler() in enetc_msg.c to detect FLR events via the PSIIDR register and dispatch to the vf_flr_handler callback. Invoke it at the start of enetc_msg_task() before processing VF messages. - Enable FLR interrupts in PSIIER only when a vf_flr_handler callback is registered, keeping ENETC v1 behavior unchanged. Signed-off-by: Wei Fang --- .../net/ethernet/freescale/enetc/enetc4_pf.c | 20 ++++++++ .../net/ethernet/freescale/enetc/enetc_hw.h | 12 +++++ .../net/ethernet/freescale/enetc/enetc_msg.c | 50 +++++++++++++++++++ .../net/ethernet/freescale/enetc/enetc_pf.h | 3 ++ .../freescale/enetc/enetc_pf_common.c | 4 +- 5 files changed, 88 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c index a4ffe1100bd7..c421c0e7355b 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c @@ -212,9 +212,29 @@ static void enetc4_pf_set_mac_filter(struct enetc_pf *pf, int type, enetc4_pf_set_mc_hash_filter(pf, mc); } +static void enetc4_pf_vf_flr_handler(struct enetc_pf *pf, int vf_id) +{ + struct enetc_vf_state *vf_state; + bool uc_promisc, mc_promisc; + + vf_state = &pf->vf_state[vf_id]; + mutex_lock(&vf_state->lock); + + uc_promisc = !!(vf_state->flags & ENETC_VF_FLAG_UC_PROMISC); + mc_promisc = !!(vf_state->flags & ENETC_VF_FLAG_MC_PROMISC); + + mutex_lock(&pf->msg_lock); + enetc_set_si_uc_promisc(pf->si, vf_id + 1, uc_promisc); + enetc_set_si_mc_promisc(pf->si, vf_id + 1, mc_promisc); + mutex_unlock(&pf->msg_lock); + + mutex_unlock(&vf_state->lock); +} + static const struct enetc_pf_ops enetc4_pf_ops = { .set_si_primary_mac = enetc4_pf_set_si_primary_mac, .get_si_primary_mac = enetc4_pf_get_si_primary_mac, + .vf_flr_handler = enetc4_pf_vf_flr_handler, }; static int enetc4_pf_struct_init(struct enetc_si *si) diff --git a/drivers/net/ethernet/freescale/enetc/enetc_hw.h b/drivers/net/ethernet/freescale/enetc/enetc_hw.h index f97602714118..c18ad8b9b071 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_hw.h +++ b/drivers/net/ethernet/freescale/enetc/enetc_hw.h @@ -110,6 +110,18 @@ static inline u32 enetc_vsi_set_msize(u32 size) #define ENETC_PSIIER 0xa00 #define ENETC_PSIIDR 0xa08 + +/* VF FLR interrupt mask, n is the active number of VFs. + * It is available for ENETC_PSIIER and ENETC_PSIIDR registers. + */ +#define ENETC_VFFLR_MASK(n) \ + ({ typeof(n) _n = (n); (_n) ? GENMASK(16 + (_n), 17) : 0; }) + +/* VF FLR interrupt bit, n is VF index. It is available + * for ENETC_PSIIER and ENETC_PSIIDR registers. + */ +#define ENETC_VFFLR_BIT(n) BIT(17 + (n)) + #define ENETC_SITXIDR 0xa18 #define ENETC_SIRXIDR 0xa28 #define ENETC_SIMSIVR 0xa30 diff --git a/drivers/net/ethernet/freescale/enetc/enetc_msg.c b/drivers/net/ethernet/freescale/enetc/enetc_msg.c index 4aabeb23a386..55c23d4a73a8 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_msg.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_msg.c @@ -27,6 +27,9 @@ static void enetc_enable_psiier_interrupts(struct enetc_pf *pf) u32 psiier = ENETC_PSIMR_MASK(pf->num_vfs); struct enetc_hw *hw = &pf->si->hw; + if (pf->ops->vf_flr_handler) + psiier |= ENETC_VFFLR_MASK(pf->num_vfs); + enetc_wr(hw, ENETC_PSIIER, psiier); } @@ -208,6 +211,20 @@ static u16 enetc_msg_set_vf_mac_promisc_mode(struct enetc_pf *pf, int vf_id, goto vf_state_unlock; } + if (type & ENETC_MAC_FILTER_TYPE_UC) { + if (promisc) + vf_state->flags |= ENETC_VF_FLAG_UC_PROMISC; + else + vf_state->flags &= ~ENETC_VF_FLAG_UC_PROMISC; + } + + if (type & ENETC_MAC_FILTER_TYPE_MC) { + if (promisc) + vf_state->flags |= ENETC_VF_FLAG_MC_PROMISC; + else + vf_state->flags &= ~ENETC_VF_FLAG_MC_PROMISC; + } + mutex_lock(&pf->msg_lock); if (type & ENETC_MAC_FILTER_TYPE_UC) @@ -594,6 +611,29 @@ static void enetc_msg_handle_rxmsg(struct enetc_pf *pf, int vf_id, kfree(msg); } +static void enetc_vf_flr_handler(struct enetc_pf *pf) +{ + u32 flr_mask = ENETC_VFFLR_MASK(pf->num_vfs); + struct enetc_hw *hw = &pf->si->hw; + u32 flr_status; + + if (!pf->ops->vf_flr_handler) + return; + + flr_status = enetc_rd(hw, ENETC_PSIIDR) & flr_mask; + if (!flr_status) + return; + + for (int i = 0; i < pf->num_vfs; i++) { + if (!(ENETC_VFFLR_BIT(i) & flr_status)) + continue; + + /* Clear FLR interrupt status, W1C */ + enetc_wr(hw, ENETC_PSIIDR, ENETC_VFFLR_BIT(i)); + pf->ops->vf_flr_handler(pf, i); + } +} + static void enetc_msg_task(struct work_struct *work) { struct enetc_si *si = container_of(work, struct enetc_si, msg_task); @@ -602,6 +642,8 @@ static void enetc_msg_task(struct work_struct *work) u32 mr_status, mr_mask; int i; + enetc_vf_flr_handler(pf); + mr_mask = ENETC_PSIMR_MASK(pf->num_vfs); mr_status = (enetc_rd(hw, ENETC_PSIMSGRR) & mr_mask) | (enetc_rd(hw, ENETC_PSIIDR) & mr_mask); @@ -728,6 +770,14 @@ static void enetc_msg_clear_vf_config(struct enetc_pf *pf, int vf_id) mutex_lock(&vf_state->lock); + /* VF may set these flags by mailbox messages, so need to clear these + * flags when enetc_msg_psi_free() is called. PF-set flags (TRUSTED, + * PF_SET_MAC) are not cleared, because these flags are unrelated to + * whether SR-IOV is enabled or disabled. + */ + vf_state->flags &= ~(ENETC_VF_FLAG_UC_PROMISC | + ENETC_VF_FLAG_MC_PROMISC); + mutex_lock(&pf->msg_lock); enetc_set_si_uc_promisc(si, si_id, false); enetc_set_si_mc_promisc(si, si_id, false); diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf.h b/drivers/net/ethernet/freescale/enetc/enetc_pf.h index 12e67f611f77..6bf4105ee0e3 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf.h +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf.h @@ -11,6 +11,8 @@ enum enetc_vf_flags { ENETC_VF_FLAG_PF_SET_MAC = BIT(0), ENETC_VF_FLAG_TRUSTED = BIT(1), + ENETC_VF_FLAG_UC_PROMISC = BIT(2), + ENETC_VF_FLAG_MC_PROMISC = BIT(3), }; struct enetc_vf_state { @@ -32,6 +34,7 @@ struct enetc_pf_ops { struct phylink_pcs *(*create_pcs)(struct enetc_pf *pf, struct mii_bus *bus); void (*destroy_pcs)(struct phylink_pcs *pcs); int (*enable_psfp)(struct enetc_ndev_priv *priv); + void (*vf_flr_handler)(struct enetc_pf *pf, int vf_id); }; struct enetc_pf { diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c index 8007dce90195..10134d7a1f70 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c @@ -625,7 +625,9 @@ int enetc_pf_set_vf_trust(struct net_device *ndev, int vf, bool setting) } else { u64 hash; - vf_state->flags &= ~ENETC_VF_FLAG_TRUSTED; + vf_state->flags &= ~(ENETC_VF_FLAG_TRUSTED | + ENETC_VF_FLAG_UC_PROMISC | + ENETC_VF_FLAG_MC_PROMISC); /* For ENETC v1, we only support setting the VF's MAC address * via VSI-to-PSI messages. Unicast and multicast promiscuous -- 2.34.1 From: Wei Fang This patch adds VF support for i.MX94 and i.MX95 platforms. Compared to the LS1028A ENETC, the VF device ID has been updated to 0xef00. On i.MX95 (v4.1), each ENETC instance supports 2 VFs. The i.MX94 (v4.3) has two types of ENETC with different VF capabilities: - standalone ENETC (same as i.MX95): does not support VFs - internal ENETC connected to the CPU port of NETC switch: supports 3 VFs The driver is updated to recognize these SoC-specific VF capabilities and handle each ENETC instance accordingly. Signed-off-by: Wei Fang --- drivers/net/ethernet/freescale/enetc/Kconfig | 1 + drivers/net/ethernet/freescale/enetc/enetc.c | 15 ++++++++++++++ .../net/ethernet/freescale/enetc/enetc4_hw.h | 1 + .../net/ethernet/freescale/enetc/enetc4_pf.c | 4 ++++ .../ethernet/freescale/enetc/enetc_ethtool.c | 6 ++++++ .../net/ethernet/freescale/enetc/enetc_vf.c | 20 ++++++++++++++++++- 6 files changed, 46 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/freescale/enetc/Kconfig b/drivers/net/ethernet/freescale/enetc/Kconfig index db5c17a44613..f425f82a6213 100644 --- a/drivers/net/ethernet/freescale/enetc/Kconfig +++ b/drivers/net/ethernet/freescale/enetc/Kconfig @@ -69,6 +69,7 @@ config FSL_ENETC_VF depends on PCI_MSI select FSL_ENETC_CORE select FSL_ENETC_MDIO + select NXP_NTMP select PHYLINK select DIMLIB select CRC_ITU_T diff --git a/drivers/net/ethernet/freescale/enetc/enetc.c b/drivers/net/ethernet/freescale/enetc/enetc.c index 80f0082f6c63..803c5c541a5c 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc.c +++ b/drivers/net/ethernet/freescale/enetc/enetc.c @@ -3794,6 +3794,13 @@ static const struct enetc_drvdata enetc_vf_data = { .eth_ops = &enetc_vf_ethtool_ops, }; +static const struct enetc_drvdata enetc4_vf_data = { + .sysclk_freq = ENETC_CLK_333M, + .tx_csum = true, + .max_frags = ENETC4_MAX_SKB_FRAGS, + .eth_ops = &enetc_vf_ethtool_ops, +}; + static const struct enetc_platform_info enetc_info[] = { { .revision = ENETC_REV_1_0, .dev_id = ENETC_DEV_ID_PF, @@ -3807,6 +3814,10 @@ static const struct enetc_platform_info enetc_info[] = { .dev_id = ENETC_DEV_ID_VF, .data = &enetc_vf_data, }, + { .revision = ENETC_REV_4_1, + .dev_id = NXP_ENETC_VF_DEV_ID, + .data = &enetc4_vf_data, + }, { .revision = ENETC_REV_4_3, .dev_id = NXP_ENETC_PPM_DEV_ID, @@ -3816,6 +3827,10 @@ static const struct enetc_platform_info enetc_info[] = { .dev_id = NXP_ENETC_PF_DEV_ID, .data = &enetc4_pf_data, }, + { .revision = ENETC_REV_4_3, + .dev_id = NXP_ENETC_VF_DEV_ID, + .data = &enetc4_vf_data, + }, }; int enetc_get_driver_data(struct enetc_si *si) diff --git a/drivers/net/ethernet/freescale/enetc/enetc4_hw.h b/drivers/net/ethernet/freescale/enetc/enetc4_hw.h index 09025e7a2a3a..e23d8d82d2ba 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc4_hw.h +++ b/drivers/net/ethernet/freescale/enetc/enetc4_hw.h @@ -12,6 +12,7 @@ #define NXP_ENETC_VENDOR_ID 0x1131 #define NXP_ENETC_PF_DEV_ID 0xe101 #define NXP_ENETC_PPM_DEV_ID 0xe110 +#define NXP_ENETC_VF_DEV_ID 0xef00 /**********************Station interface registers************************/ /* Station interface LSO segmentation flag mask register 0/1 */ diff --git a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c index c421c0e7355b..a945a120c553 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c @@ -1121,6 +1121,9 @@ static void enetc4_pf_remove(struct pci_dev *pdev) struct enetc_si *si = pci_get_drvdata(pdev); struct enetc_pf *pf = enetc_si_priv(si); + if (pf->num_vfs) + enetc_sriov_configure(pdev, 0); + enetc_remove_debugfs(si); enetc4_pf_netdev_destroy(si); enetc4_pf_free(pf); @@ -1138,6 +1141,7 @@ static struct pci_driver enetc4_pf_driver = { .id_table = enetc4_pf_id_table, .probe = enetc4_pf_probe, .remove = enetc4_pf_remove, + .sriov_configure = enetc_sriov_configure, }; module_pci_driver(enetc4_pf_driver); diff --git a/drivers/net/ethernet/freescale/enetc/enetc_ethtool.c b/drivers/net/ethernet/freescale/enetc/enetc_ethtool.c index 07b7832f2427..7965dfd06f5f 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_ethtool.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_ethtool.c @@ -859,6 +859,9 @@ static int enetc_get_rxnfc(struct net_device *ndev, struct ethtool_rxnfc *rxnfc, struct enetc_ndev_priv *priv = netdev_priv(ndev); int i, j; + if (!is_enetc_rev1(priv->si)) + return -EOPNOTSUPP; + switch (rxnfc->cmd) { case ETHTOOL_GRXCLSRLCNT: /* total number of entries */ @@ -903,6 +906,9 @@ static int enetc_set_rxnfc(struct net_device *ndev, struct ethtool_rxnfc *rxnfc) struct enetc_ndev_priv *priv = netdev_priv(ndev); int err; + if (!is_enetc_rev1(priv->si)) + return -EOPNOTSUPP; + switch (rxnfc->cmd) { case ETHTOOL_SRXCLSRLINS: if (rxnfc->fs.location >= priv->si->num_fs_entries) diff --git a/drivers/net/ethernet/freescale/enetc/enetc_vf.c b/drivers/net/ethernet/freescale/enetc/enetc_vf.c index a60af40d8546..322705202d49 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_vf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_vf.c @@ -285,6 +285,12 @@ static void enetc_vf_netdev_setup(struct enetc_si *si, struct net_device *ndev, ndev->features |= NETIF_F_RXHASH; } + if (si->drvdata->tx_csum) + priv->active_offloads |= ENETC_F_TXCSUM; + + if (si->hw_features & ENETC_SI_F_LSO) + priv->active_offloads |= ENETC_F_LSO; + /* pick up primary MAC address from SI */ enetc_load_primary_mac_addr(&si->hw, ndev); } @@ -296,6 +302,13 @@ static const struct enetc_si_ops enetc_vsi_ops = { .teardown_cbdr = enetc_teardown_cbdr, }; +static const struct enetc_si_ops enetc4_vsi_ops = { + .get_rss_table = enetc4_get_rss_table, + .set_rss_table = enetc4_set_rss_table, + .setup_cbdr = enetc4_setup_cbdr, + .teardown_cbdr = enetc4_teardown_cbdr, +}; + static int enetc_vf_probe(struct pci_dev *pdev, const struct pci_device_id *ent) { @@ -311,7 +324,11 @@ static int enetc_vf_probe(struct pci_dev *pdev, si = pci_get_drvdata(pdev); enetc_vf_get_revision(si); - si->ops = &enetc_vsi_ops; + if (is_enetc_rev1(si)) + si->ops = &enetc_vsi_ops; + else + si->ops = &enetc4_vsi_ops; + err = enetc_get_driver_data(si); if (err) { dev_err_probe(&pdev->dev, err, @@ -413,6 +430,7 @@ static void enetc_vf_remove(struct pci_dev *pdev) static const struct pci_device_id enetc_vf_id_table[] = { { PCI_DEVICE(PCI_VENDOR_ID_FREESCALE, ENETC_DEV_ID_VF) }, + { PCI_DEVICE(NXP_ENETC_VENDOR_ID, NXP_ENETC_VF_DEV_ID) }, { 0, } /* End of table. */ }; MODULE_DEVICE_TABLE(pci, enetc_vf_id_table); -- 2.34.1 From: Wei Fang The ENETC VF communicates MAC filter changes to the PF driver via a VSI mailbox interface. The message send path in enetc_msg_vsi_send() polls for completion with a timeout up to 200ms, which requires a sleepable context. The legacy ndo_set_rx_mode callback is invoked with netif_addr_lock_bh held and BH disabled, making it incompatible with the VSI messaging path. Implement ndo_set_rx_mode_async instead, which runs from a workqueue with rtnl_lock held in a fully sleepable context, and receives pre-snapshotted unicast and multicast address lists from the networking core. Add enetc_vf_set_mac_promisc() to send a MAC promiscuous mode message to the PF. The message specifies the filter type (unicast, multicast, or both) and whether to enable promiscuous mode and clear existing MAC hash filter. Add enetc_vf_set_mac_hash_filter() to send a 64-bit Bloom filter hash table to the PF. Each filter type (UC or MC) contributes two u32 entries representing the low and high halves of its 64-bit hash bitmap. The function accepts pre-snapshotted address lists from the framework and iterates them with netdev_hw_addr_list_for_each(). When IFF_PROMISC is active, hash filter programming is skipped since promiscuous mode already accepts all frames. The ndo_set_rx_mode_async callback selects the appropriate filter configuration based on the current netdev flags: - IFF_PROMISC: enable full promiscuous mode for both unicast and multicast - IFF_ALLMULTI: enable multicast promiscuous mode, disable unicast promiscuous mode, and apply a unicast hash filter - otherwise: disable all promiscuous modes and apply both unicast and multicast hash filters Set IFF_UNICAST_FLT in priv_flags for ENETC v4 VFs so the network stack does not fall back to full promiscuous mode unnecessarily when unicast address filtering is supported by the hardware. This feature applies to ENETC v4 hardware only. ENETC v1 (LS1028A) does not support VF-PF MAC filter messaging and the callback returns early for such devices. Signed-off-by: Wei Fang --- .../net/ethernet/freescale/enetc/enetc_vf.c | 140 ++++++++++++++++++ 1 file changed, 140 insertions(+) diff --git a/drivers/net/ethernet/freescale/enetc/enetc_vf.c b/drivers/net/ethernet/freescale/enetc/enetc_vf.c index 322705202d49..4e717afba7f7 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_vf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_vf.c @@ -213,6 +213,142 @@ static int enetc_vf_setup_tc(struct net_device *ndev, enum tc_setup_type type, } } +static int enetc_vf_set_mac_promisc(struct enetc_si *si, int type, bool en) +{ + struct enetc_msg_mac_promisc_mode *msg; + struct device *dev = &si->pdev->dev; + struct enetc_msg_swbd msg_swbd; + + if (!(type & ENETC_MAC_FILTER_TYPE_ALL)) + return -EINVAL; + + msg_swbd.size = ALIGN(sizeof(*msg), ENETC_MSG_ALIGN); + msg_swbd.vaddr = dma_alloc_coherent(dev, msg_swbd.size, + &msg_swbd.dma, GFP_KERNEL); + if (!msg_swbd.vaddr) + return -ENOMEM; + + msg = (struct enetc_msg_mac_promisc_mode *)msg_swbd.vaddr; + msg->config = FIELD_PREP(ENETC_MSG_MAC_TYPE, + type & ENETC_MAC_FILTER_TYPE_ALL); + msg->config |= FIELD_PREP(ENETC_MSG_MAC_PROMISC_MODE, en); + msg->config |= FIELD_PREP(ENETC_MSG_MAC_FLUSH_MACS, en); + enetc_msg_fill_common_hdr(&msg_swbd, ENETC_MSG_CLASS_ID_MAC_FILTER, + ENETC_MSG_SET_MAC_PROMISC_MODE, 0, 0); + + return enetc_msg_vsi_send(si, &msg_swbd); +} + +static int enetc_vf_set_mac_hash_filter(struct enetc_si *si, + struct netdev_hw_addr_list *uc, + struct netdev_hw_addr_list *mc) +{ + struct enetc_msg_mac_hash_filter *msg; + struct enetc_mac_filter *mac_filter; + struct device *dev = &si->pdev->dev; + struct net_device *ndev = si->ndev; + struct enetc_msg_swbd msg_swbd; + struct netdev_hw_addr *ha; + u32 msg_size, tbl_cnt; + int mac_filter_type; + int i = 0; + + if (ndev->flags & IFF_PROMISC) + return 0; + + if (ndev->flags & IFF_ALLMULTI) { + tbl_cnt = 2; + mac_filter_type = ENETC_MAC_FILTER_TYPE_UC; + } else { + tbl_cnt = 4; + mac_filter_type = ENETC_MAC_FILTER_TYPE_ALL; + } + + msg_size = struct_size(msg, hash_tbl, tbl_cnt); + msg_swbd.size = ALIGN(msg_size, ENETC_MSG_ALIGN); + msg_swbd.vaddr = dma_alloc_coherent(dev, msg_swbd.size, + &msg_swbd.dma, GFP_KERNEL); + if (!msg_swbd.vaddr) + return -ENOMEM; + + msg = (struct enetc_msg_mac_hash_filter *)msg_swbd.vaddr; + msg->sz_type = FIELD_PREP(ENETC_MSG_MAC_TYPE, mac_filter_type); + msg->sz_type |= FIELD_PREP(ENETC_MSG_MAC_HASH_SIZE, + ENETC_MAC_HASH_TABLE_SIZE_64); + + if (mac_filter_type & ENETC_MAC_FILTER_TYPE_UC) { + mac_filter = &si->mac_filter[UC]; + enetc_reset_mac_addr_filter(mac_filter); + netdev_hw_addr_list_for_each(ha, uc) + enetc_add_mac_addr_ht_filter(mac_filter, ha->addr); + + bitmap_to_arr32(&msg->hash_tbl[i], mac_filter->mac_hash_table, + ENETC_MADDR_HASH_TBL_SZ); + i += 2; + } + + if (mac_filter_type & ENETC_MAC_FILTER_TYPE_MC) { + mac_filter = &si->mac_filter[MC]; + enetc_reset_mac_addr_filter(mac_filter); + netdev_hw_addr_list_for_each(ha, mc) + enetc_add_mac_addr_ht_filter(mac_filter, ha->addr); + + bitmap_to_arr32(&msg->hash_tbl[i], mac_filter->mac_hash_table, + ENETC_MADDR_HASH_TBL_SZ); + } + + enetc_msg_fill_common_hdr(&msg_swbd, ENETC_MSG_CLASS_ID_MAC_FILTER, + ENETC_MSG_SET_MAC_HASH_TABLE, 0, 0); + + return enetc_msg_vsi_send(si, &msg_swbd); +} + +static int enetc_vf_set_rx_mode(struct net_device *ndev, + struct netdev_hw_addr_list *uc, + struct netdev_hw_addr_list *mc) +{ + struct enetc_ndev_priv *priv = netdev_priv(ndev); + struct enetc_si *si = priv->si; + int err; + + /* For ENETC v1, we cannot return -EOPNOTSUPP or any other error, + * otherwise ndev->rx_mode_retry_timer will try to set rx_mode + * multiple times, which is pointless. + */ + if (is_enetc_rev1(si)) + return 0; + + if (ndev->flags & IFF_PROMISC) { + err = enetc_vf_set_mac_promisc(si, ENETC_MAC_FILTER_TYPE_ALL, + true); + } else if (ndev->flags & IFF_ALLMULTI) { + err = enetc_vf_set_mac_promisc(si, ENETC_MAC_FILTER_TYPE_UC, + false); + if (err) + goto out; + + err = enetc_vf_set_mac_promisc(si, ENETC_MAC_FILTER_TYPE_MC, + true); + } else { + err = enetc_vf_set_mac_promisc(si, ENETC_MAC_FILTER_TYPE_ALL, + false); + } + + if (err) + goto out; + + err = enetc_vf_set_mac_hash_filter(si, uc, mc); + +out: + /* If the error code is -EOPNOTSUPP or -EACCES or -EPERM, return 0 + * directly to avoid meaningless retries. + */ + if (err == -EOPNOTSUPP || err == -EACCES || err == -EPERM) + return 0; + + return err; +} + /* Probing/ Init */ static const struct net_device_ops enetc_ndev_ops = { .ndo_open = enetc_open, @@ -225,6 +361,7 @@ static const struct net_device_ops enetc_ndev_ops = { .ndo_setup_tc = enetc_vf_setup_tc, .ndo_hwtstamp_get = enetc_hwtstamp_get, .ndo_hwtstamp_set = enetc_hwtstamp_set, + .ndo_set_rx_mode_async = enetc_vf_set_rx_mode, }; static void enetc_vf_get_revision(struct enetc_si *si) @@ -280,6 +417,9 @@ static void enetc_vf_netdev_setup(struct enetc_si *si, struct net_device *ndev, ndev->vlan_features = NETIF_F_SG | NETIF_F_HW_CSUM | NETIF_F_TSO | NETIF_F_TSO6; + if (!is_enetc_rev1(si)) + ndev->priv_flags |= IFF_UNICAST_FLT; + if (si->num_rss) { ndev->hw_features |= NETIF_F_RXHASH; ndev->features |= NETIF_F_RXHASH; -- 2.34.1 From: Wei Fang 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 --- 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 From: Wei Fang Without ndo_get_vf_config(), userspace tools such as 'ip link show' cannot query the current VF configuration from the PF. To support this, extend struct enetc_vf_state to track the per-VF VLAN and spoofchk settings, and update the corresponding setter callbacks to persist their state when the hardware is programmed. enetc_pf_get_vf_config() reads back the persisted state and reports MAC address, VLAN parameters, spoofchk, and trust state through struct ifla_vf_info. Signed-off-by: Wei Fang --- .../net/ethernet/freescale/enetc/enetc4_pf.c | 1 + .../net/ethernet/freescale/enetc/enetc_pf.c | 24 ++++++++++++++ .../net/ethernet/freescale/enetc/enetc_pf.h | 4 +++ .../freescale/enetc/enetc_pf_common.c | 31 +++++++++++++++++++ .../freescale/enetc/enetc_pf_common.h | 2 ++ 5 files changed, 62 insertions(+) diff --git a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c index a945a120c553..b4d76505bc03 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c @@ -597,6 +597,7 @@ static const struct net_device_ops enetc4_ndev_ops = { .ndo_hwtstamp_set = enetc_hwtstamp_set, .ndo_set_vf_trust = enetc_pf_set_vf_trust, .ndo_set_vf_mac = enetc_pf_set_vf_mac, + .ndo_get_vf_config = enetc_pf_get_vf_config, }; static struct phylink_pcs * diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf.c b/drivers/net/ethernet/freescale/enetc/enetc_pf.c index 523c71324780..d77a07cece28 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf.c @@ -195,6 +195,7 @@ static int enetc_pf_set_vf_vlan(struct net_device *ndev, int vf, u16 vlan, { struct enetc_ndev_priv *priv = netdev_priv(ndev); struct enetc_pf *pf = enetc_si_priv(priv->si); + struct enetc_vf_state *vf_state; if (priv->si->errata & ENETC_ERR_VLAN_ISOL) return -EOPNOTSUPP; @@ -207,6 +208,17 @@ static int enetc_pf_set_vf_vlan(struct net_device *ndev, int vf, u16 vlan, return -EPROTONOSUPPORT; enetc_set_isol_vlan(&priv->si->hw, vf + 1, vlan, qos); + + vf_state = &pf->vf_state[vf]; + mutex_lock(&vf_state->lock); + /* Currently only C-tags is supported, so tpid is always 0, + * which indicates ETH_P_8021Q. + */ + vf_state->tpid = 0; + vf_state->qos = qos; + vf_state->vid = vlan; + mutex_unlock(&vf_state->lock); + return 0; } @@ -214,6 +226,7 @@ static int enetc_pf_set_vf_spoofchk(struct net_device *ndev, int vf, bool en) { struct enetc_ndev_priv *priv = netdev_priv(ndev); struct enetc_pf *pf = enetc_si_priv(priv->si); + struct enetc_vf_state *vf_state; u32 cfgr; if (vf >= pf->total_vfs) @@ -223,6 +236,16 @@ static int enetc_pf_set_vf_spoofchk(struct net_device *ndev, int vf, bool en) cfgr = (cfgr & ~ENETC_PSICFGR0_ASE) | (en ? ENETC_PSICFGR0_ASE : 0); enetc_port_wr(&priv->si->hw, ENETC_PSICFGR0(vf + 1), cfgr); + vf_state = &pf->vf_state[vf]; + mutex_lock(&vf_state->lock); + + if (en) + vf_state->flags |= ENETC_VF_FLAG_SPOOFCHK; + else + vf_state->flags &= ~ENETC_VF_FLAG_SPOOFCHK; + + mutex_unlock(&vf_state->lock); + return 0; } @@ -476,6 +499,7 @@ static const struct net_device_ops enetc_ndev_ops = { .ndo_xdp_xmit = enetc_xdp_xmit, .ndo_hwtstamp_get = enetc_hwtstamp_get, .ndo_hwtstamp_set = enetc_hwtstamp_set, + .ndo_get_vf_config = enetc_pf_get_vf_config, }; static struct phylink_pcs * diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf.h b/drivers/net/ethernet/freescale/enetc/enetc_pf.h index 6bf4105ee0e3..25e869d54365 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf.h +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf.h @@ -13,11 +13,15 @@ enum enetc_vf_flags { ENETC_VF_FLAG_TRUSTED = BIT(1), ENETC_VF_FLAG_UC_PROMISC = BIT(2), ENETC_VF_FLAG_MC_PROMISC = BIT(3), + ENETC_VF_FLAG_SPOOFCHK = BIT(4), }; struct enetc_vf_state { struct mutex lock; /* Prevent concurrent access */ enum enetc_vf_flags flags; + u8 tpid; /* SI-based VLAN TPID (0: 0x8100, 1: 0x88a8) */ + u8 qos; /* SI-based VLAN QOS (priority) bits */ + u16 vid; /* SI-based VLAN ID */ }; struct enetc_port_caps { diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c index 10134d7a1f70..264294a0cc23 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.c @@ -684,5 +684,36 @@ int enetc_pf_set_vf_mac(struct net_device *ndev, int vf, u8 *mac) } EXPORT_SYMBOL_GPL(enetc_pf_set_vf_mac); +int enetc_pf_get_vf_config(struct net_device *ndev, int vf, + struct ifla_vf_info *ivi) +{ + struct enetc_ndev_priv *priv = netdev_priv(ndev); + struct enetc_pf *pf = enetc_si_priv(priv->si); + struct enetc_vf_state *vf_state; + + if (vf >= pf->total_vfs) + return -EINVAL; + + vf_state = &pf->vf_state[vf]; + mutex_lock(&vf_state->lock); + + ivi->vf = vf; + ivi->spoofchk = !!(vf_state->flags & ENETC_VF_FLAG_SPOOFCHK); + ivi->trusted = !!(vf_state->flags & ENETC_VF_FLAG_TRUSTED); + enetc_get_si_hw_addr(pf, vf + 1, ivi->mac); + + if (vf_state->vid) { + ivi->vlan = vf_state->vid; + ivi->qos = vf_state->qos; + ivi->vlan_proto = vf_state->tpid ? htons(ETH_P_8021AD) : + htons(ETH_P_8021Q); + } + + mutex_unlock(&vf_state->lock); + + return 0; +} +EXPORT_SYMBOL_GPL(enetc_pf_get_vf_config); + MODULE_DESCRIPTION("NXP ENETC PF common functionality driver"); MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h index 91a9c339245a..f36f45450733 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h @@ -24,6 +24,8 @@ void enetc_set_si_mc_hash_filter(struct enetc_si *si, int si_id, u64 hash); void enetc_set_si_vlan_promisc(struct enetc_si *si, int si_id, bool promisc); int enetc_pf_set_vf_trust(struct net_device *ndev, int vf, bool setting); int enetc_pf_set_vf_mac(struct net_device *ndev, int vf, u8 *mac); +int enetc_pf_get_vf_config(struct net_device *ndev, int vf, + struct ifla_vf_info *ivi); static inline u16 enetc_get_ip_revision(struct enetc_hw *hw) { -- 2.34.1