Files
infix/patches/linux/6.18.39/0029-wifi-brcmfmac-support-deletion-and-recreation-of-pri.patch
T

409 lines
14 KiB
Diff

From 8cd6f1604dc816fab887eee9f297bf0df4befbd1 Mon Sep 17 00:00:00 2001
From: Mattias Walström <lazzer@gmail.com>
Date: Thu, 15 Jan 2026 22:47:37 +0100
Subject: [PATCH 29/50] wifi: brcmfmac: support deletion and recreation of
primary interface
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The Broadcom FullMAC firmware does not allow the primary interface
(bsscfgidx 0) to be deleted - it always exists in firmware. However,
userspace may want to delete and recreate the primary interface with
a different name or configuration.
Add support for this by:
1. Allow deletion of primary interface by removing local driver
structures while keeping firmware interface. Send BRCMF_C_DOWN to
clear firmware state before removing local structures.
2. Add brcmf_cfg80211_add_primary_iface() to recreate the primary
interface by rebuilding local structures and reinitializing the
firmware interface with BRCMF_C_UP and brcmf_config_dongle().
3. When deleting primary interface while secondary interfaces exist,
bring dongle back UP since BRCMF_C_DOWN affects the entire dongle.
4. Fix brcmf_cfg80211_dump_survey() to use the correct interface
(the one being surveyed) rather than always using the primary.
Also ensure BRCMF_C_UP is called before channel operations.
5. Make cfg_to_ndev() return NULL when primary interface is deleted,
and add NULL checks in callers.
6. Fix rtnl locking in brcmf_del_if() to avoid deadlock when called
with rtnl already held.
Signed-off-by: Mattias Walström <lazzer@gmail.com>
---
.../broadcom/brcm80211/brcmfmac/cfg80211.c | 180 ++++++++++++++++--
.../broadcom/brcm80211/brcmfmac/cfg80211.h | 4 +-
.../broadcom/brcm80211/brcmfmac/core.c | 6 +-
.../broadcom/brcm80211/brcmfmac/p2p.c | 16 +-
4 files changed, 186 insertions(+), 20 deletions(-)
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
index bb96b87b2a6e..bcd8a4724625 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
@@ -124,6 +124,9 @@ struct cca_msrmnt_query {
u32 time_req;
};
+/* Forward declaration */
+static s32 brcmf_config_dongle(struct brcmf_cfg80211_info *cfg);
+
static bool check_vif_up(struct brcmf_cfg80211_vif *vif)
{
if (!test_bit(BRCMF_VIF_STATUS_READY, &vif->sme_state)) {
@@ -822,11 +825,18 @@ struct wireless_dev *brcmf_apsta_add_vif(struct wiphy *wiphy, const char *name,
enum nl80211_iftype type)
{
struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy);
- struct brcmf_if *ifp = netdev_priv(cfg_to_ndev(cfg));
+ struct net_device *ndev = cfg_to_ndev(cfg);
struct brcmf_pub *drvr = cfg->pub;
struct brcmf_cfg80211_vif *vif;
+ struct brcmf_if *ifp;
int err;
+ if (!ndev) {
+ bphy_err(drvr, "primary interface not available\n");
+ return ERR_PTR(-ENODEV);
+ }
+ ifp = netdev_priv(ndev);
+
if (type != NL80211_IFTYPE_STATION && type != NL80211_IFTYPE_AP)
return ERR_PTR(-EINVAL);
@@ -868,6 +878,13 @@ struct wireless_dev *brcmf_apsta_add_vif(struct wiphy *wiphy, const char *name,
goto fail;
}
+ /*
+ * Bring the firmware interface UP. When firmware reuses a bsscfgidx
+ * from a previously deleted interface, it may retain stale state that
+ * affects channel operations. Bringing it UP ensures proper init.
+ */
+ brcmf_fil_cmd_int_set(ifp, BRCMF_C_UP, 1);
+
strscpy(ifp->ndev->name, name, sizeof(ifp->ndev->name));
err = brcmf_net_attach(ifp, true);
if (err) {
@@ -974,6 +991,85 @@ static int brcmf_mon_del_vif(struct wiphy *wiphy, struct wireless_dev *wdev)
return 0;
}
+/**
+ * brcmf_cfg80211_add_primary_iface() - recreate the primary interface
+ *
+ * @wiphy: wiphy device of new interface.
+ * @name: name of the new interface.
+ * @type: interface type (STATION or AP).
+ *
+ * The primary interface (bsscfgidx 0) cannot be created in firmware
+ * as it always exists. This function recreates the local driver
+ * structures after a previous deletion.
+ *
+ * Return: pointer to new wdev on success, ERR_PTR(-errno) on failure
+ */
+static struct wireless_dev *brcmf_cfg80211_add_primary_iface(struct wiphy *wiphy,
+ const char *name,
+ enum nl80211_iftype type)
+{
+ struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy);
+ struct brcmf_pub *drvr = cfg->pub;
+ struct brcmf_cfg80211_vif *vif;
+ struct brcmf_if *ifp;
+ int err;
+
+ brcmf_dbg(INFO, "Recreating primary interface \"%s\" at bsscfgidx 0\n", name);
+
+ /* Create the interface structure for bsscfgidx 0 */
+ ifp = brcmf_add_if(drvr, 0, 0, false, name, drvr->mac);
+ if (IS_ERR(ifp))
+ return ERR_CAST(ifp);
+
+ brcmf_proto_add_if(drvr, ifp);
+
+ /* Allocate and set up the vif */
+ vif = brcmf_alloc_vif(cfg, type);
+ if (IS_ERR(vif)) {
+ err = PTR_ERR(vif);
+ goto fail_ifp;
+ }
+
+ vif->ifp = ifp;
+ vif->wdev.netdev = ifp->ndev;
+ ifp->ndev->ieee80211_ptr = &vif->wdev;
+ SET_NETDEV_DEV(ifp->ndev, wiphy_dev(cfg->wiphy));
+ ifp->vif = vif;
+
+ err = brcmf_net_attach(ifp, true);
+ if (err) {
+ bphy_err(drvr, "Registering netdevice failed\n");
+ goto fail_vif;
+ }
+
+ /*
+ * Bring the firmware interface UP. Since we're reusing bsscfgidx 0
+ * which the firmware never truly deletes, bringing it UP ensures
+ * proper initialization for channel operations.
+ */
+ brcmf_fil_cmd_int_set(ifp, BRCMF_C_UP, 1);
+
+ /*
+ * Initialize the firmware interface. This would normally happen
+ * on ndo_open via brcmf_cfg80211_up, but operations like channel
+ * survey may occur before the interface is opened. We call
+ * brcmf_config_dongle directly since dongle_up was reset when
+ * the primary interface was deleted.
+ */
+ set_bit(BRCMF_VIF_STATUS_READY, &vif->sme_state);
+ err = brcmf_config_dongle(cfg);
+ if (err)
+ bphy_err(drvr, "dongle configuration failed: %d\n", err);
+
+ return &vif->wdev;
+
+fail_vif:
+ brcmf_free_vif(vif);
+fail_ifp:
+ brcmf_remove_interface(ifp, true);
+ return ERR_PTR(err);
+}
+
static struct wireless_dev *brcmf_cfg80211_add_iface(struct wiphy *wiphy,
const char *name,
unsigned char name_assign_type,
@@ -1001,7 +1097,11 @@ static struct wireless_dev *brcmf_cfg80211_add_iface(struct wiphy *wiphy,
return brcmf_mon_add_vif(wiphy, name);
case NL80211_IFTYPE_STATION:
case NL80211_IFTYPE_AP:
- wdev = brcmf_apsta_add_vif(wiphy, name, params, type);
+ /* Check if primary interface slot is available for recreation */
+ if (!drvr->iflist[0])
+ wdev = brcmf_cfg80211_add_primary_iface(wiphy, name, type);
+ else
+ wdev = brcmf_apsta_add_vif(wiphy, name, params, type);
break;
case NL80211_IFTYPE_P2P_CLIENT:
case NL80211_IFTYPE_P2P_GO:
@@ -1270,6 +1370,9 @@ static int brcmf_cfg80211_del_apsta_iface(struct wiphy *wiphy,
int ret;
int err;
+ /* Bring interface DOWN to clear firmware state before removal */
+ brcmf_fil_cmd_int_set(ifp, BRCMF_C_DOWN, 1);
+
brcmf_cfg80211_arm_vif_event(cfg, ifp->vif);
err = brcmf_fil_bsscfg_data_set(ifp, "interface_remove", NULL, 0);
@@ -1294,14 +1397,56 @@ static int brcmf_cfg80211_del_apsta_iface(struct wiphy *wiphy,
return err;
}
+static int brcmf_cfg80211_del_primary_iface(struct wiphy *wiphy,
+ struct wireless_dev *wdev)
+{
+ struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy);
+ struct net_device *ndev = wdev->netdev;
+ struct brcmf_if *ifp = netdev_priv(ndev);
+ struct brcmf_pub *drvr = cfg->pub;
+ struct brcmf_if *other_ifp;
+ int i;
+
+ /*
+ * The primary interface (bsscfgidx 0) cannot be removed from
+ * firmware, but we can remove the local interface structures
+ * to allow recreation via add_virtual_intf.
+ *
+ * Bring interface DOWN to clear firmware state before removing
+ * local structures, ensuring clean state when recreated.
+ */
+ brcmf_abort_scanning(cfg);
+ brcmf_fil_cmd_int_set(ifp, BRCMF_C_DOWN, 1);
+
+ brcmf_remove_interface(ifp, true);
+
+ /*
+ * BRCMF_C_DOWN affects the entire dongle. If other interfaces
+ * still exist, bring the dongle back UP so they remain functional.
+ */
+ for (i = 0; i < BRCMF_MAX_IFS; i++) {
+ other_ifp = drvr->iflist[i];
+ if (other_ifp) {
+ brcmf_fil_cmd_int_set(other_ifp, BRCMF_C_UP, 1);
+ break;
+ }
+ }
+
+ /*
+ * Reset dongle_up so that when the interface is recreated,
+ * brcmf_config_dongle() will reinitialize the firmware.
+ */
+ cfg->dongle_up = false;
+
+ return 0;
+}
+
static
int brcmf_cfg80211_del_iface(struct wiphy *wiphy, struct wireless_dev *wdev)
{
struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy);
struct net_device *ndev = wdev->netdev;
-
- if (ndev && ndev == cfg_to_ndev(cfg))
- return -ENOTSUPP;
+ bool is_primary = ndev && ndev == cfg_to_ndev(cfg);
/* vif event pending in firmware */
if (brcmf_cfg80211_vif_event_armed(cfg))
@@ -1326,6 +1471,8 @@ int brcmf_cfg80211_del_iface(struct wiphy *wiphy, struct wireless_dev *wdev)
return brcmf_mon_del_vif(wiphy, wdev);
case NL80211_IFTYPE_STATION:
case NL80211_IFTYPE_AP:
+ if (is_primary)
+ return brcmf_cfg80211_del_primary_iface(wiphy, wdev);
return brcmf_cfg80211_del_apsta_iface(wiphy, wdev);
case NL80211_IFTYPE_P2P_CLIENT:
case NL80211_IFTYPE_P2P_GO:
@@ -7790,6 +7937,9 @@ static s32 brcmf_config_dongle(struct brcmf_cfg80211_info *cfg)
return err;
ndev = cfg_to_ndev(cfg);
+ if (!ndev)
+ return -ENODEV;
+
wdev = ndev->ieee80211_ptr;
ifp = netdev_priv(ndev);
@@ -8114,11 +8264,11 @@ brcmf_dump_obss(struct brcmf_if *ifp, struct cca_msrmnt_query req,
}
static s32
-brcmf_set_channel(struct brcmf_cfg80211_info *cfg, struct ieee80211_channel *chan)
+brcmf_set_channel(struct brcmf_cfg80211_info *cfg, struct brcmf_if *ifp,
+ struct ieee80211_channel *chan)
{
u16 chspec = 0;
int err = 0;
- struct brcmf_if *ifp = netdev_priv(cfg_to_ndev(cfg));
if (chan->flags & IEEE80211_CHAN_DISABLED)
return -EINVAL;
@@ -8144,7 +8294,7 @@ brcmf_cfg80211_dump_survey(struct wiphy *wiphy, struct net_device *ndev,
int idx, struct survey_info *info)
{
struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy);
- struct brcmf_if *ifp = netdev_priv(cfg_to_ndev(cfg));
+ struct brcmf_if *ifp = netdev_priv(ndev);
struct brcmf_dump_survey survey = {};
struct ieee80211_supported_band *band;
enum nl80211_band band_id;
@@ -8175,21 +8325,21 @@ brcmf_cfg80211_dump_survey(struct wiphy *wiphy, struct net_device *ndev,
if (band_id == NUM_NL80211_BANDS)
return -ENOENT;
- /* Setting current channel to the requested channel */
- info->filled = 0;
- if (brcmf_set_channel(cfg, info->channel))
- return 0;
-
/* Disable mpc */
brcmf_set_mpc(ifp, 0);
- /* Set interface up, explicitly. */
+ /* Set interface up before channel operations */
err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_UP, 1);
if (err) {
- brcmf_err("set interface up failed, err = %d\n", err);
+ brcmf_err("BRCMF_C_UP failed in survey, err = %d\n", err);
goto exit;
}
+ /* Setting current channel to the requested channel */
+ info->filled = 0;
+ if (brcmf_set_channel(cfg, ifp, info->channel))
+ return 0;
+
/* Get noise value */
err = brcmf_fil_cmd_int_get(ifp, BRCMF_C_GET_PHY_NOISE, &noise);
if (err) {
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h
index 273c80f2d483..ee7e9dc17dce 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h
@@ -433,7 +433,9 @@ static inline struct brcmf_cfg80211_vif *wdev_to_vif(struct wireless_dev *wdev)
static inline
struct net_device *cfg_to_ndev(struct brcmf_cfg80211_info *cfg)
{
- return brcmf_get_ifp(cfg->pub, 0)->ndev;
+ struct brcmf_if *ifp = brcmf_get_ifp(cfg->pub, 0);
+
+ return ifp ? ifp->ndev : NULL;
}
static inline struct brcmf_cfg80211_info *ndev_to_cfg(struct net_device *ndev)
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
index 862a0336a0b5..08f265325ce4 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
@@ -933,9 +933,11 @@ static void brcmf_del_if(struct brcmf_pub *drvr, s32 bsscfgidx,
if (ifp->ndev) {
if (bsscfgidx == 0) {
if (ifp->ndev->netdev_ops == &brcmf_netdev_ops_pri) {
- rtnl_lock();
+ if (!locked)
+ rtnl_lock();
brcmf_netdev_stop(ifp->ndev);
- rtnl_unlock();
+ if (!locked)
+ rtnl_unlock();
}
} else {
netif_stop_queue(ifp->ndev);
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c
index e1752a513c73..2dc0fbba271d 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c
@@ -2222,8 +2222,13 @@ static struct wireless_dev *brcmf_p2p_create_p2pdev(struct brcmf_p2p_info *p2p,
static int brcmf_p2p_get_conn_idx(struct brcmf_cfg80211_info *cfg)
{
int i;
- struct brcmf_if *ifp = netdev_priv(cfg_to_ndev(cfg));
+ struct net_device *ndev = cfg_to_ndev(cfg);
+ struct brcmf_if *ifp;
+
+ if (!ndev)
+ return -ENODEV;
+ ifp = netdev_priv(ndev);
if (!ifp)
return -ENODEV;
@@ -2255,14 +2260,21 @@ struct wireless_dev *brcmf_p2p_add_vif(struct wiphy *wiphy, const char *name,
struct vif_params *params)
{
struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy);
- struct brcmf_if *ifp = netdev_priv(cfg_to_ndev(cfg));
+ struct net_device *ndev = cfg_to_ndev(cfg);
struct brcmf_pub *drvr = cfg->pub;
struct brcmf_cfg80211_vif *vif;
enum brcmf_fil_p2p_if_types iftype;
+ struct brcmf_if *ifp;
int err = 0;
int connidx;
u8 *p2p_intf_addr;
+ if (!ndev) {
+ bphy_err(drvr, "primary interface not available\n");
+ return ERR_PTR(-ENODEV);
+ }
+ ifp = netdev_priv(ndev);
+
if (brcmf_cfg80211_vif_event_armed(cfg))
return ERR_PTR(-EBUSY);