diff --git a/drivers/net/ethernet/meta/fbnic/fbnic.h b/drivers/net/ethernet/meta/fbnic/fbnic.h index 0e7ae1def5bf..1f9fbd71447f 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic.h +++ b/drivers/net/ethernet/meta/fbnic/fbnic.h @@ -161,7 +161,12 @@ void fbnic_fw_wr32(struct fbnic_dev *fbd, u32 reg, u32 val); static inline bool fbnic_bmc_present(struct fbnic_dev *fbd) { - return fbd->fw_cap.bmc_present; + return test_bit(FBNIC_FW_CAP_F_BMC_PRESENT, &fbd->fw_cap.state); +} + +static inline bool fbnic_bmc_all_multi(struct fbnic_dev *fbd) +{ + return test_bit(FBNIC_FW_CAP_F_BMC_ALL_MULTI, &fbd->fw_cap.state); } static inline bool fbnic_init_failure(struct fbnic_dev *fbd) diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_fw.c b/drivers/net/ethernet/meta/fbnic/fbnic_fw.c index 47894b45676b..49b8813a3564 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_fw.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_fw.c @@ -620,11 +620,55 @@ static int fbnic_fw_parse_bmc_addrs(u8 bmc_mac_addr[][ETH_ALEN], return 0; } +static int fbnic_fw_parse_bmc_cap(struct fbnic_dev *fbd, + struct fbnic_tlv_msg **results) +{ + struct fbnic_tlv_msg *attr; + bool bmc_present = false; + u32 all_multi = 0; + int err; + + /* FW reports the BMC present once its NC-SI channel is enabled, before + * it has a MAC. No MAC array means nothing to program, so treat it as + * absent rather than rejecting the message. + */ + attr = results[FBNIC_FW_CAP_RESP_BMC_PRESENT] ? + results[FBNIC_FW_CAP_RESP_BMC_MAC_ARRAY] : NULL; + if (attr) { + err = fbnic_fw_parse_bmc_addrs(fbd->fw_cap.bmc_mac_addr, + attr, 4); + if (err) + return err; + + all_multi = fta_get_uint(results, + FBNIC_FW_CAP_RESP_BMC_ALL_MULTI); + bmc_present = true; + } else { + memset(fbd->fw_cap.bmc_mac_addr, 0, + sizeof(fbd->fw_cap.bmc_mac_addr)); + } + + /* all_multi is only read out of the message when a BMC is present, so + * it is never set on its own and callers can test it without pairing + * it with the presence check. + */ + assign_bit(FBNIC_FW_CAP_F_BMC_PRESENT, &fbd->fw_cap.state, bmc_present); + assign_bit(FBNIC_FW_CAP_F_BMC_ALL_MULTI, &fbd->fw_cap.state, all_multi); + + /* Always assume we need a BMC reinit. The barrier orders the BMC state + * published above ahead of the flag, pairing with the ordering implied + * by the test_and_clear_bit() in fbnic_bmc_rpc_check(). + */ + smp_mb__before_atomic(); + set_bit(FBNIC_FW_CAP_F_BMC_TCAM_REINIT, &fbd->fw_cap.state); + + return 0; +} + static int fbnic_fw_parse_cap_resp(void *opaque, struct fbnic_tlv_msg **results) { - u32 all_multi = 0, version = 0; struct fbnic_dev *fbd = opaque; - bool bmc_present; + u32 version = 0; int err; version = fta_get_uint(results, FBNIC_FW_CAP_RESP_VERSION); @@ -684,37 +728,13 @@ static int fbnic_fw_parse_cap_resp(void *opaque, struct fbnic_tlv_msg **results) fbd->fw_cap.link_fec = fta_get_uint(results, FBNIC_FW_CAP_RESP_FW_LINK_FEC); - bmc_present = !!results[FBNIC_FW_CAP_RESP_BMC_PRESENT]; - if (bmc_present) { - struct fbnic_tlv_msg *attr; - - attr = results[FBNIC_FW_CAP_RESP_BMC_MAC_ARRAY]; - if (!attr) - return -EINVAL; - - err = fbnic_fw_parse_bmc_addrs(fbd->fw_cap.bmc_mac_addr, - attr, 4); - if (err) - return err; - - all_multi = - fta_get_uint(results, FBNIC_FW_CAP_RESP_BMC_ALL_MULTI); - } else { - memset(fbd->fw_cap.bmc_mac_addr, 0, - sizeof(fbd->fw_cap.bmc_mac_addr)); - } - - fbd->fw_cap.bmc_present = bmc_present; - - if (results[FBNIC_FW_CAP_RESP_BMC_ALL_MULTI] || !bmc_present) - fbd->fw_cap.all_multi = all_multi; + err = fbnic_fw_parse_bmc_cap(fbd, results); + if (err) + return err; fbd->fw_cap.anti_rollback_version = fta_get_uint(results, FBNIC_FW_CAP_RESP_ANTI_ROLLBACK_VERSION); - /* Always assume we need a BMC reinit */ - fbd->fw_cap.need_bmc_tcam_reinit = true; - return 0; } diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_fw.h b/drivers/net/ethernet/meta/fbnic/fbnic_fw.h index 5f9969247e30..f47669129c9d 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_fw.h +++ b/drivers/net/ethernet/meta/fbnic/fbnic_fw.h @@ -45,7 +45,22 @@ struct fbnic_fw_ver { char commit[FBNIC_FW_CAP_RESP_COMMIT_MAX_SIZE]; }; +/* Bits in fbnic_fw_cap.state. The firmware mailbox publishes the BMC state + * and the service task consumes it, so they are updated with atomic bitops. + */ +enum { + FBNIC_FW_CAP_F_BMC_PRESENT, + FBNIC_FW_CAP_F_BMC_ALL_MULTI, + FBNIC_FW_CAP_F_BMC_TCAM_REINIT, + FBNIC_FW_CAP_F_BMC_MACDA_SYNC, +}; + +#define FBNIC_FW_CAP_BMC_PENDING \ + (BIT(FBNIC_FW_CAP_F_BMC_TCAM_REINIT) | \ + BIT(FBNIC_FW_CAP_F_BMC_MACDA_SYNC)) + struct fbnic_fw_cap { + unsigned long state; struct { struct fbnic_fw_ver mgmt, bootloader; } running; @@ -54,10 +69,6 @@ struct fbnic_fw_cap { } stored; u8 active_slot; u8 bmc_mac_addr[4][ETH_ALEN]; - u8 bmc_present : 1; - u8 need_bmc_tcam_reinit : 1; - u8 need_bmc_macda_sync : 1; - u8 all_multi : 1; u8 link_speed; u8 link_fec; u32 anti_rollback_version; diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_rpc.c b/drivers/net/ethernet/meta/fbnic/fbnic_rpc.c index ab6e342ec3fa..939d56585b26 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_rpc.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_rpc.c @@ -117,7 +117,7 @@ void fbnic_bmc_rpc_all_multi_config(struct fbnic_dev *fbd, * BMC. */ mac_addr = &fbd->mac_addr[fbd->mac_addr_boundary - 1]; - if (fbnic_bmc_present(fbd) && fbd->fw_cap.all_multi) { + if (fbnic_bmc_all_multi(fbd)) { if (mac_addr->state != FBNIC_TCAM_S_VALID) { eth_zero_addr(mac_addr->value.addr8); eth_broadcast_addr(mac_addr->mask.addr8); @@ -143,9 +143,11 @@ void fbnic_bmc_rpc_all_multi_config(struct fbnic_dev *fbd, /* If we are not enabling the rule just delete it. We will fall * back to the RSS rules that support the multicast addresses. + * Delete in any live state, not just VALID, as ifdown demotes it to + * UPDATE and a miss there leaves it to be rewritten to hardware. */ - if (!fbnic_bmc_present(fbd) || !fbd->fw_cap.all_multi || enable_host) { - if (act_tcam->state == FBNIC_TCAM_S_VALID) + if (!fbnic_bmc_all_multi(fbd) || enable_host) { + if (act_tcam->state != FBNIC_TCAM_S_DISABLED) act_tcam->state = FBNIC_TCAM_S_DELETE; return; } @@ -234,25 +236,121 @@ void fbnic_bmc_rpc_init(struct fbnic_dev *fbd) act_tcam->state = FBNIC_TCAM_S_UPDATE; } +/** + * fbnic_bmc_rules_present - is the BMC currently programmed into the filters? + * @fbd: Pointer to fbnic device struct + * + * The BMC tag is only set while a BMC is present, so it doubles as the state. + * + * Return: true if any MACDA entry carries the BMC tag, false otherwise. + */ +static bool fbnic_bmc_rules_present(struct fbnic_dev *fbd) +{ + int idx; + + for (idx = ARRAY_SIZE(fbd->mac_addr); idx--;) { + struct fbnic_mac_addr *mac_addr = &fbd->mac_addr[idx]; + + if (mac_addr->state == FBNIC_TCAM_S_DISABLED) + continue; + + if (test_bit(FBNIC_MAC_ADDR_T_BMC, mac_addr->act_tcam)) + return true; + } + + return false; +} + +/** + * fbnic_bmc_rpc_disable - remove the BMC MAC and action rules + * @fbd: Pointer to fbnic device struct + * + * Undo fbnic_bmc_rpc_init() when the BMC drops its NC-SI channel while the + * host interface stays up. Only marks shadow state; the caller's + * __fbnic_set_rx_mode() pushes it to hardware, so ordering here is moot. + */ +static void fbnic_bmc_rpc_disable(struct fbnic_dev *fbd) +{ + struct fbnic_act_tcam *act_tcam; + int idx; + + /* Drop the BMC's claim; entries the host also uses survive */ + for (idx = ARRAY_SIZE(fbd->mac_addr); idx--;) { + struct fbnic_mac_addr *mac_addr = &fbd->mac_addr[idx]; + + if (mac_addr->state == FBNIC_TCAM_S_DISABLED) + continue; + + __fbnic_xc_unsync(mac_addr, FBNIC_MAC_ADDR_T_BMC); + } + + /* Delete in any live state, not just VALID: init leaves it UPDATE and + * ifdown demotes VALID to UPDATE. A miss leaks the rule for good, as + * fbnic_bmc_rules_present() keys off the MACDA tag just cleared above. + */ + act_tcam = &fbd->act_tcam[FBNIC_RPC_ACT_TBL_BMC_OFFSET]; + if (act_tcam->state != FBNIC_TCAM_S_DISABLED) + act_tcam->state = FBNIC_TCAM_S_DELETE; + + /* fbnic_rss_reinit() only programs the host-unicast entries while a BMC + * is present, so the reinit after this would leave them stale and valid + * in hardware. Delete them to converge on the no-BMC layout. + */ + for (idx = 0; idx < FBNIC_RSS_EN_NUM_UNICAST; idx++) { + act_tcam = &fbd->act_tcam[FBNIC_RPC_ACT_TBL_RSS_OFFSET + idx]; + if (act_tcam->state != FBNIC_TCAM_S_DISABLED) + act_tcam->state = FBNIC_TCAM_S_DELETE; + } +} + void fbnic_bmc_rpc_check(struct fbnic_dev *fbd) { + struct fbnic_net *fbn = netdev_priv(fbd->netdev); int err; - if (fbd->fw_cap.need_bmc_tcam_reinit) { - fbnic_bmc_rpc_init(fbd); + /* Nothing to do unless the firmware raised one of the flags. Test + * before taking the lock to keep it off the common path; one raised + * after this is picked up on the next pass. + */ + if (!(READ_ONCE(fbd->fw_cap.state) & FBNIC_FW_CAP_BMC_PENDING)) + return; + + /* The rx mode work and the ethtool paths rewrite the MACDA and action + * TCAM shadows under the instance lock, and this runs from the service + * task under RTNL only, so the two do not exclude each other. + */ + netdev_lock(fbd->netdev); + + /* Consume the flag before the state it advertises. The ordering + * implied by test_and_clear_bit() pairs with the barrier in + * fbnic_fw_parse_bmc_cap(), and claiming it atomically means a set + * racing with us is kept and retried rather than overwritten. + */ + if (test_and_clear_bit(FBNIC_FW_CAP_F_BMC_TCAM_REINIT, + &fbd->fw_cap.state)) { + if (!fbnic_bmc_present(fbd) && fbnic_bmc_rules_present(fbd)) + fbnic_bmc_rpc_disable(fbd); + else + fbnic_bmc_rpc_init(fbd); + + /* Neither path touches the RSS actions, which carry the mc/bc + * copy to the BMC, so recompute them alongside. + */ + fbnic_rss_reinit(fbd, fbn); netif_addr_lock_bh(fbd->netdev); __fbnic_set_rx_mode(fbd, &fbd->netdev->uc, &fbd->netdev->mc); netif_addr_unlock_bh(fbd->netdev); - fbd->fw_cap.need_bmc_tcam_reinit = false; } - if (fbd->fw_cap.need_bmc_macda_sync) { + if (test_and_clear_bit(FBNIC_FW_CAP_F_BMC_MACDA_SYNC, + &fbd->fw_cap.state)) { err = fbnic_fw_xmit_rpc_macda_sync(fbd); if (err) dev_warn(fbd->dev, "Writing MACDA table to FW failed, err: %d\n", err); - fbd->fw_cap.need_bmc_macda_sync = false; } + + netdev_unlock(fbd->netdev); } #define FBNIC_ACT1_INIT(_l4, _udp, _ip, _v6) \ @@ -484,8 +582,7 @@ void fbnic_promisc_sync(struct fbnic_dev *fbd, mac_addr->act_tcam); mac_addr->state = FBNIC_TCAM_S_ADD; } - } else if (mc_promisc && - (!fbnic_bmc_present(fbd) || !fbd->fw_cap.all_multi)) { + } else if (mc_promisc && !fbnic_bmc_all_multi(fbd)) { /* We have to add a special handler for multicast as the * BMC may have an all-multi rule already in place. As such * adding a rule ourselves won't do any good so we will have @@ -656,12 +753,12 @@ void fbnic_write_macda(struct fbnic_dev *fbd) } /* If reinitializing the BMC TCAM we are doing an initial update */ - if (fbd->fw_cap.need_bmc_tcam_reinit) + if (test_bit(FBNIC_FW_CAP_F_BMC_TCAM_REINIT, &fbd->fw_cap.state)) updates++; /* If needed notify firmware of changes to MACDA TCAM */ if (updates != 0 && fbnic_bmc_present(fbd)) - fbd->fw_cap.need_bmc_macda_sync = true; + set_bit(FBNIC_FW_CAP_F_BMC_MACDA_SYNC, &fbd->fw_cap.state); } static void fbnic_clear_act_tcam(struct fbnic_dev *fbd, unsigned int idx)