mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-22 01:13:00 +02:00
409 lines
14 KiB
Diff
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);
|
|
|