From 8c51a0724ae7b1ad5115282c954d4edc81364585 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/33] wifi: brcmfmac: support deletion and recreation of
 primary interface
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Wires

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);
 
