diff --git a/drivers/net/ethernet/freescale/enetc/enetc.h b/drivers/net/ethernet/freescale/enetc/enetc.h index bc713a7c3aa1..0eff25b0c81b 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc.h +++ b/drivers/net/ethernet/freescale/enetc/enetc.h @@ -334,6 +334,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]; }; diff --git a/drivers/net/ethernet/freescale/enetc/enetc4_pf.c b/drivers/net/ethernet/freescale/enetc/enetc4_pf.c index 935a6a03b14f..e20989b6f8b4 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); } @@ -966,6 +968,36 @@ static void enetc4_link_deinit(struct enetc_ndev_priv *priv) enetc_mdiobus_destroy(pf); } +static void enetc4_pf_link_status_task(struct work_struct *work) +{ + struct enetc_pf *pf = container_of(work, struct enetc_pf, + link_status_task); + + enetc_pf_send_link_status_msg(pf); +} + +static int enetc4_pf_wq_task_init(struct enetc_si *si) +{ + struct enetc_pf *pf = enetc_si_priv(si); + + si->workqueue = alloc_ordered_workqueue("enetc-%s-wq", 0, + pci_name(si->pdev)); + if (!si->workqueue) + return -ENOMEM; + + INIT_WORK(&pf->link_status_task, enetc4_pf_link_status_task); + + return 0; +} + +static void enetc4_pf_wq_task_destroy(struct enetc_si *si) +{ + struct enetc_pf *pf = enetc_si_priv(si); + + disable_work_sync(&pf->link_status_task); + destroy_workqueue(si->workqueue); +} + static int enetc4_pf_netdev_create(struct enetc_si *si) { struct device *dev = &si->pdev->dev; @@ -1006,6 +1038,10 @@ static int enetc4_pf_netdev_create(struct enetc_si *si) if (err) goto err_link_init; + err = enetc4_pf_wq_task_init(si); + if (err) + goto err_wq_init; + err = register_netdev(ndev); if (err) { dev_err(dev, "Failed to register netdev\n"); @@ -1015,6 +1051,8 @@ static int enetc4_pf_netdev_create(struct enetc_si *si) return 0; err_reg_netdev: + enetc4_pf_wq_task_destroy(si); +err_wq_init: enetc4_link_deinit(priv); err_link_init: enetc_free_msix(priv); @@ -1032,6 +1070,7 @@ static void enetc4_pf_netdev_destroy(struct enetc_si *si) struct net_device *ndev = si->ndev; unregister_netdev(ndev); + enetc4_pf_wq_task_destroy(si); enetc4_link_deinit(priv); enetc_free_msix(priv); free_netdev(ndev); 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..79dbaf72fcff 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_msg.c +++ b/drivers/net/ethernet/freescale/enetc/enetc_msg.c @@ -136,6 +136,104 @@ 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) { + if (test_bit(ENETC_RXBDR_CM, &priv->flags)) + status |= ENETC_CLASS_CODE_TX_PAUSE_EN; + } 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); + struct enetc_si *si = pf->si; + u16 pf_msg; + + spin_lock(&si->gen_lock); + pf_msg = enetc_build_link_status_msg(priv, pf->link_up); + enetc_pf_reply_msg(&si->hw, vf_id, pf_msg); + spin_unlock(&si->gen_lock); +} + +static void enetc_msg_register_link_status_notifier(struct enetc_pf *pf, + int vf_id) +{ + struct enetc_si *si = pf->si; + + spin_lock(&si->gen_lock); + enetc_pf_reply_msg(&si->hw, vf_id, ENETC_PF_MSG_SUCCESS); + + /* 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) { + spin_unlock(&si->gen_lock); + return; + } + + pf->link_status_ms_mask |= PSIMSGSR_MS(vf_id); + spin_unlock(&si->gen_lock); + + /* Notify VF the current link status */ + queue_work(si->workqueue, &pf->link_status_task); +} + +static void enetc_msg_unregister_link_status_notifier(struct enetc_pf *pf, + int vf_id) +{ + spin_lock(&pf->si->gen_lock); + pf->link_status_ms_mask &= ~PSIMSGSR_MS(vf_id); + enetc_pf_reply_msg(&pf->si->hw, vf_id, ENETC_PF_MSG_SUCCESS); + spin_unlock(&pf->si->gen_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 +309,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 +337,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 +344,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: @@ -340,6 +442,22 @@ free_mbx: 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; + + /* 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; + + spin_lock(&si->gen_lock); + vf_state->msg_fail_cnt = 0; + spin_unlock(&si->gen_lock); +} + static void enetc_msg_psi_free(struct enetc_pf *pf) { struct enetc_si *si = pf->si; @@ -356,8 +474,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) @@ -367,6 +487,11 @@ int enetc_sriov_configure(struct pci_dev *pdev, int num_vfs) int err; if (!num_vfs) { + spin_lock(&si->gen_lock); + pf->sriov_enabled = false; + pf->link_status_ms_mask = 0; + spin_unlock(&si->gen_lock); + pci_disable_sriov(pdev); enetc_msg_psi_free(pf); pf->num_vfs = 0; @@ -379,6 +504,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 + * gen_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 +519,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. + */ + spin_lock(&si->gen_lock); + pf->sriov_enabled = false; + pf->link_status_ms_mask = 0; + spin_unlock(&si->gen_lock); enetc_msg_psi_free(pf); err_msg_psi: pf->num_vfs = 0; @@ -396,3 +535,114 @@ err_msg_psi: return err; } EXPORT_SYMBOL_GPL(enetc_sriov_configure); + +void enetc_pf_send_link_status_msg(struct enetc_pf *pf) +{ + struct enetc_ndev_priv *priv = netdev_priv(pf->si->ndev); + u16 pf_msg, ms_mask, new_ms_msk, ms_status; + struct enetc_si *si = pf->si; + int retry_num = 0; + +retry: + spin_lock(&si->gen_lock); + ms_mask = pf->link_status_ms_mask; + /* VFs have unregistered link status notification, return directly */ + if (!ms_mask) + goto unlock; + + /* 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(&si->hw, ENETC_PSIMSGSR) & 0xfffe; + if ((ms_mask & ms_status) && retry_num++ < 200) { + spin_unlock(&si->gen_lock); + /* Wait VFs to handle the last message */ + usleep_range(1000, 1020); + goto retry; + } + + /* None of the relevant VFs have processed the previous message, and + * the PF has tried 200 times. This situation indicates that VF has + * malfunctioned. + */ + new_ms_msk = ms_mask & (~ms_status); + if (!new_ms_msk) { + dev_err_ratelimited(&si->pdev->dev, + "All registered VFs (MS: 0x%x) are busy\n", + ms_mask); + goto ms_status_check; + } + + if (new_ms_msk != ms_mask) + dev_warn_ratelimited(&si->pdev->dev, + "Failed to notify link status to VFs (MS: 0x%x)\n", + ms_mask ^ new_ms_msk); + + pf_msg = enetc_build_link_status_msg(priv, pf->link_up); + enetc_wr(&si->hw, ENETC_PSIMSGSR, + FIELD_PREP(PSIMSGSR_MC, pf_msg) | new_ms_msk); + +ms_status_check: + /* If the PF fails to send messages to the corresponding VF for 10 + * consecutive times, clear that VF's bit in link_status_ms_mask. + */ + for (int i = 0; i < pf->num_vfs; i++) { + struct enetc_vf_state *vf_state = &pf->vf_state[i]; + + if (!(PSIMSGSR_MS(i) & ms_mask)) + continue; + + if (!(PSIMSGSR_MS(i) & ms_status)) { + vf_state->msg_fail_cnt = 0; + continue; + } + + if (vf_state->msg_fail_cnt++ < 10) + continue; + + vf_state->msg_fail_cnt = 0; + pf->link_status_ms_mask &= ~PSIMSGSR_MS(i); + dev_warn_ratelimited(&si->pdev->dev, + "Clear VF%d's link status MS bit\n", i); + } + +unlock: + spin_unlock(&si->gen_lock); +} +EXPORT_SYMBOL_GPL(enetc_pf_send_link_status_msg); + +static void enetc_pf_notify_vf_link_status(struct enetc_pf *pf, + bool link_up) +{ + struct enetc_si *si = pf->si; + + /* Currently we do not add link status message support for ENETC v1 */ + if (!pf->total_vfs || is_enetc_rev1(si)) + return; + + spin_lock(&si->gen_lock); + pf->link_up = link_up; + if (!pf->link_status_ms_mask) { + spin_unlock(&si->gen_lock); + return; + } + spin_unlock(&si->gen_lock); + + queue_work(si->workqueue, &pf->link_status_task); +} + +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..b3ad498f91a0 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf.h +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf.h @@ -15,6 +15,8 @@ enum enetc_vf_flags { struct enetc_vf_state { struct mutex lock; /* Prevent concurrent access */ enum enetc_vf_flags flags; + /* Number of consecutive failures to send PF-to-VF messages */ + int msg_fail_cnt; }; struct enetc_port_caps { @@ -54,6 +56,11 @@ struct enetc_pf { struct enetc_port_caps caps; const struct enetc_pf_ops *ops; + + struct work_struct link_status_task; + 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.h b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h index 96a4dc63da57..bcf113efc2ef 100644 --- a/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h +++ b/drivers/net/ethernet/freescale/enetc/enetc_pf_common.h @@ -31,9 +31,24 @@ 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_send_link_status_msg(struct enetc_pf *pf); +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_send_link_status_msg(struct enetc_pf *pf) +{ +} + +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