@@ -2735,6 +2735,9 @@ struct cfg80211_auth_request {
* userspace if this flag is set. Only applicable for cfg80211_connect()
* request (connect callback).
* @ASSOC_REQ_DISABLE_HE: Disable HE
+ * @ASSOC_MLO_SUPPORT: Userspace indicates support for handling MLO links.
+ * Drivers shall disable MLO features for the current association if this
+ * flag is not set.
*/
enum cfg80211_assoc_req_flags {
ASSOC_REQ_DISABLE_HT = BIT(0),
@@ -2742,6 +2745,7 @@ enum cfg80211_assoc_req_flags {
ASSOC_REQ_USE_RRM = BIT(2),
CONNECT_REQ_EXTERNAL_AUTH_SUPPORT = BIT(3),
ASSOC_REQ_DISABLE_HE = BIT(4),
+ ASSOC_MLO_SUPPORT = BIT(5),
};
/**
@@ -5564,6 +5568,12 @@ static inline void wiphy_unlock(struct wiphy *wiphy)
* @mld_wdev: points to MLD wdev of type %NL80211_IFTYPE_STATION to which this
* MLO link wdev is affiliated to. Valid for iftype
* %NL80211_IFTYPE_MLO_LINK only.
+ * @link_bssid: BSSID of the AP associated with the MLO link wdev. Valid for
+ * iftype %NL80211_IFTYPE_MLO_LINK. Scan result of the BSS associated with
+ * the MLO link may not be available in rdev->bss_list so we can't use
+ * @current_bss.
+ * @link_id: AP's MLO link ID to which this non-AP station's MLO link wdev is
+ * associated. Valid only if iftype is %NL80211_IFTYPE_MLO_LINK.
*/
struct wireless_dev {
struct wiphy *wiphy;
@@ -5644,6 +5654,8 @@ struct wireless_dev {
unsigned long unprot_beacon_reported;
struct wireless_dev *mld_wdev;
+ u8 link_bssid[ETH_ALEN];
+ u8 link_id;
};
static inline const u8 *wdev_address(struct wireless_dev *wdev)
@@ -7172,6 +7184,18 @@ struct cfg80211_fils_resp_params {
};
/**
+ * struct cfg80211_mlo_link_params - MLO link device params.
+ * @wdev: the wireless device associated with the MLO link device.
+ * @bssid: BSSID of the MLO link to which this MLO link is connected to.
+ * @link_id: Link ID of the AP's MLO link to which this @wdev is connected to.
+ */
+struct cfg80211_mlo_link_params {
+ struct wireless_dev *wdev;
+ u8 bssid[ETH_ALEN];
+ u8 link_id;
+};
+
+/**
* struct cfg80211_connect_resp_params - Connection response params
* @status: Status code, %WLAN_STATUS_SUCCESS for successful connection, use
* %WLAN_STATUS_UNSPECIFIED_FAILURE if your device cannot give you
@@ -7199,6 +7223,8 @@ struct cfg80211_fils_resp_params {
* not known. This value is used only if @status < 0 to indicate that the
* failure is due to a timeout and not due to explicit rejection by the AP.
* This value is ignored in other cases (@status >= 0).
+ * @mlo_links: Array of each MLO link's connection parameters.
+ * @n_mlo_links: Number of valid links that are indicated in @mlo_links.
*/
struct cfg80211_connect_resp_params {
int status;
@@ -7210,6 +7236,8 @@ struct cfg80211_connect_resp_params {
size_t resp_ie_len;
struct cfg80211_fils_resp_params fils;
enum nl80211_timeout_reason timeout_reason;
+ const struct cfg80211_mlo_link_params *mlo_links;
+ int n_mlo_links;
};
/**
@@ -7359,6 +7387,8 @@ cfg80211_connect_timeout(struct net_device *dev, const u8 *bssid,
* @resp_ie: association response IEs (may be %NULL)
* @resp_ie_len: assoc response IEs length
* @fils: FILS related roaming information.
+ * @mlo_links: Array of each MLO link's connection parameters.
+ * @n_mlo_links: Number of valid links that are indicated in @mlo_links.
*/
struct cfg80211_roam_info {
struct ieee80211_channel *channel;
@@ -7369,6 +7399,8 @@ struct cfg80211_roam_info {
const u8 *resp_ie;
size_t resp_ie_len;
struct cfg80211_fils_resp_params fils;
+ const struct cfg80211_mlo_link_params *mlo_links;
+ int n_mlo_links;
};
/**
@@ -2667,7 +2667,15 @@ enum nl80211_commands {
* %NL80211_IFTYPE_STA interface. This is used in
* %NL80211_CMD_GET/SET/NEW_INTERFACE response to indicate information of
* all the MLO links affiliated to %NL80211_IFTYPE_STATION interface.
+ * This is also used with %NL80211_CMD_CONNECT and %NL80211_CMD_ROAM events
+ * to indicate associated MLO links information for current connection.
* See &enum nl80211_mlo_link_info_attributes for details.
+ * @NL80211_ATTR_MLO_SUPPORT: Flag attribute to indicate that the user space
+ * supports MLO connection. This is used with %NL80211_CMD_CONNECT or
+ * %NL80211_CMD_ASSOCIATE. The driver shall use MLO link wdevs in
+ * connection only when userpsace indicates support for MLO connection.
+ * Using MLO links without userspace support may lead to disconnection
+ * since RSN connection in MLO needs supplicant support.
*
* @NUM_NL80211_ATTR: total number of nl80211_attrs available
* @NL80211_ATTR_MAX: highest attribute number currently defined
@@ -3182,6 +3190,7 @@ enum nl80211_attrs {
NL80211_ATTR_EHT_CAPABILITY,
NL80211_ATTR_MLO_LINK_INFO,
+ NL80211_ATTR_MLO_SUPPORT,
/* add attributes here, update the policy in nl80211.c */
@@ -7622,6 +7631,9 @@ enum nl80211_ap_settings_flags {
*
* @NL80211_MLO_LINK_INFO_ATTR_WDEV: wireless device identifier for MLO link
* (u64)
+ * @NL80211_MLO_LINK_INFO_ATTR_BSSID: BSSID associated with this MLO link
+ * (6 octets).
+ * @NL80211_MLO_LINK_INFO_ATTR_LINK_ID: link id of associated BSSID (u8)
*
* @__NL80211_MLO_LINK_INFO_ATTR_LAST: Internal
* @NL80211_MLO_LINK_INFO_ATTR_MAX: highest attribute
@@ -7630,6 +7642,8 @@ enum nl80211_mlo_link_info_attributes {
__NL80211_MLO_LINK_INFO_ATTR_INVALID,
NL80211_MLO_LINK_INFO_ATTR_WDEV,
+ NL80211_MLO_LINK_INFO_ATTR_BSSID,
+ NL80211_MLO_LINK_INFO_ATTR_LINK_ID,
/* keep last */
__NL80211_MLO_LINK_INFO_ATTR_LAST,
@@ -1386,6 +1386,25 @@ cfg80211_get_chan_state(struct wireless_dev *wdev,
return;
}
break;
+ case NL80211_IFTYPE_MLO_LINK:
+ if (!is_zero_ether_addr(wdev->link_bssid)) {
+ if (!memcmp(wdev->link_bssid,
+ wdev->mld_wdev->current_bss->pub.bssid,
+ ETH_ALEN))
+ *chan = wdev->current_bss->pub.channel;
+ else
+ *chan = cfg80211_get_colocated_ap_chan(
+ wiphy_to_rdev(wdev->wiphy),
+ wdev->mld_wdev->current_bss,
+ wdev->link_bssid);
+
+ if (WARN_ON(!*chan))
+ return;
+
+ *chanmode = CHAN_MODE_SHARED;
+ return;
+ }
+ break;
case NL80211_IFTYPE_AP:
case NL80211_IFTYPE_P2P_GO:
if (wdev->cac_started) {
@@ -1432,7 +1451,6 @@ cfg80211_get_chan_state(struct wireless_dev *wdev,
return;
case NL80211_IFTYPE_UNSPECIFIED:
case NL80211_IFTYPE_WDS:
- case NL80211_IFTYPE_MLO_LINK:
case NUM_NL80211_IFTYPES:
WARN_ON(1);
}
@@ -1360,6 +1360,7 @@ void cfg80211_init_wdev(struct wireless_dev *wdev)
INIT_WORK(&wdev->disconnect_wk, cfg80211_autodisconnect_wk);
wdev->mld_wdev = NULL;
+ eth_zero_addr(wdev->link_bssid);
}
void cfg80211_register_wdev(struct cfg80211_registered_device *rdev,
@@ -579,5 +579,9 @@ void cfg80211_start_mlo_link(struct cfg80211_registered_device *rdev,
struct wireless_dev *wdev);
void cfg80211_stop_mlo_link(struct cfg80211_registered_device *rdev,
struct wireless_dev *wdev);
+struct ieee80211_channel *
+cfg80211_get_colocated_ap_chan(struct cfg80211_registered_device *rdev,
+ struct cfg80211_internal_bss *intbss,
+ const u8 *colocated_bssid);
#endif /* __NET_WIRELESS_CORE_H */
@@ -790,6 +790,7 @@ static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = {
NLA_POLICY_RANGE(NLA_BINARY,
NL80211_EHT_MIN_CAPABILITY_LEN,
NL80211_EHT_MAX_CAPABILITY_LEN),
+ [NL80211_ATTR_MLO_SUPPORT] = { .type = NLA_FLAG },
};
/* policy for the key attributes */
@@ -1499,13 +1500,16 @@ static int nl80211_key_allowed(struct wireless_dev *wdev)
if (!wdev->current_bss)
return -ENOLINK;
break;
+ case NL80211_IFTYPE_MLO_LINK:
+ if (is_zero_ether_addr(wdev->link_bssid))
+ return -ENOLINK;
+ break;
case NL80211_IFTYPE_UNSPECIFIED:
case NL80211_IFTYPE_OCB:
case NL80211_IFTYPE_MONITOR:
case NL80211_IFTYPE_NAN:
case NL80211_IFTYPE_P2P_DEVICE:
case NL80211_IFTYPE_WDS:
- case NL80211_IFTYPE_MLO_LINK:
case NUM_NL80211_IFTYPES:
return -EINVAL;
}
@@ -3757,6 +3761,13 @@ static int nl80211_send_iface(struct sk_buff *msg, u32 portid, u32 seq, int flag
wdev_id(link_wdev), NL80211_ATTR_PAD))
goto nla_put_failure_locked;
+ if (!is_zero_ether_addr(link_wdev->link_bssid) &&
+ (nla_put(msg, NL80211_MLO_LINK_INFO_ATTR_BSSID, ETH_ALEN,
+ link_wdev->link_bssid) ||
+ nla_put_u8(msg, NL80211_MLO_LINK_INFO_ATTR_LINK_ID,
+ link_wdev->link_id)))
+ goto nla_put_failure_locked;
+
nla_nest_end(msg, nested_mlo_links);
i++;
}
@@ -10439,6 +10450,9 @@ static int nl80211_associate(struct sk_buff *skb, struct genl_info *info)
req.flags |= ASSOC_REQ_USE_RRM;
}
+ if (nla_get_flag(info->attrs[NL80211_ATTR_MLO_SUPPORT]))
+ req.flags |= ASSOC_MLO_SUPPORT;
+
if (info->attrs[NL80211_ATTR_FILS_KEK]) {
req.fils_kek = nla_data(info->attrs[NL80211_ATTR_FILS_KEK]);
req.fils_kek_len = nla_len(info->attrs[NL80211_ATTR_FILS_KEK]);
@@ -11291,6 +11305,9 @@ static int nl80211_connect(struct sk_buff *skb, struct genl_info *info)
connect.flags |= CONNECT_REQ_EXTERNAL_AUTH_SUPPORT;
}
+ if (nla_get_flag(info->attrs[NL80211_ATTR_MLO_SUPPORT]))
+ connect.flags |= ASSOC_MLO_SUPPORT;
+
wdev_lock(dev->ieee80211_ptr);
err = cfg80211_connect(rdev, dev, &connect, connkeys,
@@ -16813,6 +16830,42 @@ void nl80211_send_assoc_timeout(struct cfg80211_registered_device *rdev,
addr, gfp);
}
+static int
+nl80211_put_mlo_link_params(struct sk_buff *msg,
+ const struct cfg80211_mlo_link_params *mlo_links,
+ int n_mlo_links)
+{
+ struct nlattr *nested, *nested_mlo_links;
+ int i;
+
+ if (n_mlo_links) {
+ nested = nla_nest_start(msg, NL80211_ATTR_MLO_LINK_INFO);
+ if (!nested)
+ return -ENOBUFS;
+
+ for (i = 0; i < n_mlo_links; i++) {
+ nested_mlo_links = nla_nest_start(msg, i + 1);
+ if (!nested_mlo_links)
+ return -ENOBUFS;
+
+ if (nla_put_u64_64bit(msg,
+ NL80211_MLO_LINK_INFO_ATTR_WDEV,
+ wdev_id(mlo_links[i].wdev),
+ NL80211_ATTR_PAD) ||
+ nla_put(msg, NL80211_MLO_LINK_INFO_ATTR_BSSID,
+ ETH_ALEN, mlo_links[i].bssid) ||
+ nla_put_u8(msg, NL80211_MLO_LINK_INFO_ATTR_LINK_ID,
+ mlo_links[i].link_id))
+ return -ENOBUFS;
+
+ nla_nest_end(msg, nested_mlo_links);
+ }
+ nla_nest_end(msg, nested);
+ }
+
+ return 0;
+}
+
void nl80211_send_connect_result(struct cfg80211_registered_device *rdev,
struct net_device *netdev,
struct cfg80211_connect_resp_params *cr,
@@ -16820,10 +16873,15 @@ void nl80211_send_connect_result(struct cfg80211_registered_device *rdev,
{
struct sk_buff *msg;
void *hdr;
+ int mlo_link_info_size = cr->n_mlo_links *
+ (nla_total_size_64bit(sizeof(u64)) +
+ nla_total_size(ETH_ALEN) +
+ nla_total_size(sizeof(u8)));
msg = nlmsg_new(100 + cr->req_ie_len + cr->resp_ie_len +
cr->fils.kek_len + cr->fils.pmk_len +
- (cr->fils.pmkid ? WLAN_PMKID_LEN : 0), gfp);
+ (cr->fils.pmkid ? WLAN_PMKID_LEN : 0) +
+ mlo_link_info_size, gfp);
if (!msg)
return;
@@ -16862,6 +16920,9 @@ void nl80211_send_connect_result(struct cfg80211_registered_device *rdev,
nla_put(msg, NL80211_ATTR_PMKID, WLAN_PMKID_LEN, cr->fils.pmkid)))))
goto nla_put_failure;
+ if (nl80211_put_mlo_link_params(msg, cr->mlo_links, cr->n_mlo_links))
+ goto nla_put_failure;
+
genlmsg_end(msg, hdr);
genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
@@ -16879,10 +16940,16 @@ void nl80211_send_roamed(struct cfg80211_registered_device *rdev,
struct sk_buff *msg;
void *hdr;
const u8 *bssid = info->bss ? info->bss->bssid : info->bssid;
-
- msg = nlmsg_new(100 + info->req_ie_len + info->resp_ie_len +
- info->fils.kek_len + info->fils.pmk_len +
- (info->fils.pmkid ? WLAN_PMKID_LEN : 0), gfp);
+ int mlo_link_info_size = info->n_mlo_links *
+ (nla_total_size_64bit(sizeof(u64)) +
+ nla_total_size(ETH_ALEN) +
+ nla_total_size(sizeof(u8)));
+
+ msg = nlmsg_new(100 + info->req_ie_len +
+ info->resp_ie_len + info->fils.kek_len +
+ info->fils.pmk_len +
+ (info->fils.pmkid ? WLAN_PMKID_LEN : 0) +
+ mlo_link_info_size, gfp);
if (!msg)
return;
@@ -16913,6 +16980,10 @@ void nl80211_send_roamed(struct cfg80211_registered_device *rdev,
nla_put(msg, NL80211_ATTR_PMKID, WLAN_PMKID_LEN, info->fils.pmkid)))
goto nla_put_failure;
+ if (nl80211_put_mlo_link_params(msg, info->mlo_links,
+ info->n_mlo_links))
+ goto nla_put_failure;
+
genlmsg_end(msg, hdr);
genlmsg_multicast_netns(&nl80211_fam, wiphy_net(&rdev->wiphy), msg, 0,
@@ -2706,6 +2706,31 @@ void cfg80211_update_assoc_bss_entry(struct wireless_dev *wdev,
spin_unlock_bh(&rdev->bss_lock);
}
+struct ieee80211_channel *
+cfg80211_get_colocated_ap_chan(struct cfg80211_registered_device *rdev,
+ struct cfg80211_internal_bss *intbss,
+ const u8 *colocated_bssid)
+{
+ struct cfg80211_colocated_ap *ap;
+ LIST_HEAD(coloc_ap_list);
+ struct cfg80211_bss *res = &intbss->pub;
+ const struct cfg80211_bss_ies *ies = rcu_access_pointer(res->ies);
+ struct ieee80211_channel *chan = NULL;
+
+ cfg80211_parse_colocated_ap(ies, &coloc_ap_list);
+
+ list_for_each_entry(ap, &coloc_ap_list, list) {
+ if (memcmp(colocated_bssid, ap->bssid, ETH_ALEN))
+ continue;
+
+ chan = ieee80211_get_channel(&rdev->wiphy, ap->center_freq);
+ break;
+ }
+
+ cfg80211_free_coloc_ap_list(&coloc_ap_list);
+ return chan;
+}
+
#ifdef CONFIG_CFG80211_WEXT
static struct cfg80211_registered_device *
cfg80211_get_dev_from_ifindex(struct net *net, int ifindex)
@@ -680,9 +680,12 @@ void __cfg80211_connect_result(struct net_device *dev,
bool wextev)
{
struct wireless_dev *wdev = dev->ieee80211_ptr;
+ struct wireless_dev *link_wdev;
+ struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
const struct element *country_elem;
const u8 *country_data;
u8 country_datalen;
+ int i;
#ifdef CONFIG_CFG80211_WEXT
union iwreq_data wrqu;
#endif
@@ -763,6 +766,25 @@ void __cfg80211_connect_result(struct net_device *dev,
if (!(wdev->wiphy->flags & WIPHY_FLAG_HAS_STATIC_WEP))
cfg80211_upload_connect_keys(wdev);
+ list_for_each_entry(link_wdev, &rdev->wiphy.wdev_list, list) {
+ if (link_wdev->mld_wdev != wdev)
+ continue;
+
+ eth_zero_addr(link_wdev->link_bssid);
+ }
+
+ for (i = 0; i < cr->n_mlo_links; i++) {
+ list_for_each_entry(link_wdev, &rdev->wiphy.wdev_list, list) {
+ if (link_wdev != cr->mlo_links[i].wdev)
+ continue;
+
+ link_wdev->link_id = cr->mlo_links[i].link_id;
+ memcpy(link_wdev->link_bssid, cr->mlo_links[i].bssid,
+ ETH_ALEN);
+ break;
+ }
+ }
+
rcu_read_lock();
country_elem = ieee80211_bss_get_elem(cr->bss, WLAN_EID_COUNTRY);
if (!country_elem) {
@@ -792,6 +814,8 @@ void cfg80211_connect_done(struct net_device *dev,
struct cfg80211_event *ev;
unsigned long flags;
u8 *next;
+ int mlo_link_params_size =
+ params->n_mlo_links * sizeof(struct cfg80211_mlo_link_params);
if (params->bss) {
struct cfg80211_internal_bss *ibss = bss_from_pub(params->bss);
@@ -830,7 +854,8 @@ void cfg80211_connect_done(struct net_device *dev,
ev = kzalloc(sizeof(*ev) + (params->bssid ? ETH_ALEN : 0) +
params->req_ie_len + params->resp_ie_len +
params->fils.kek_len + params->fils.pmk_len +
- (params->fils.pmkid ? WLAN_PMKID_LEN : 0), gfp);
+ (params->fils.pmkid ? WLAN_PMKID_LEN : 0) +
+ mlo_link_params_size, gfp);
if (!ev) {
cfg80211_put_bss(wdev->wiphy, params->bss);
return;
@@ -877,6 +902,13 @@ void cfg80211_connect_done(struct net_device *dev,
WLAN_PMKID_LEN);
next += WLAN_PMKID_LEN;
}
+ if (params->n_mlo_links) {
+ ev->cr.n_mlo_links = params->n_mlo_links;
+ ev->cr.mlo_links = (struct cfg80211_mlo_link_params *)next;
+ memcpy((void *)ev->cr.mlo_links, params->mlo_links,
+ mlo_link_params_size);
+ next += mlo_link_params_size;
+ }
ev->cr.fils.update_erp_next_seq_num = params->fils.update_erp_next_seq_num;
if (params->fils.update_erp_next_seq_num)
ev->cr.fils.erp_next_seq_num = params->fils.erp_next_seq_num;
@@ -900,6 +932,10 @@ void __cfg80211_roamed(struct wireless_dev *wdev,
#ifdef CONFIG_CFG80211_WEXT
union iwreq_data wrqu;
#endif
+ struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
+ struct wireless_dev *link_wdev;
+ int i;
+
ASSERT_WDEV_LOCK(wdev);
if (WARN_ON(wdev->iftype != NL80211_IFTYPE_STATION &&
@@ -923,6 +959,26 @@ void __cfg80211_roamed(struct wireless_dev *wdev,
nl80211_send_roamed(wiphy_to_rdev(wdev->wiphy),
wdev->netdev, info, GFP_KERNEL);
+ list_for_each_entry(link_wdev, &rdev->wiphy.wdev_list, list) {
+ if (link_wdev->mld_wdev != wdev)
+ continue;
+
+ eth_zero_addr(link_wdev->link_bssid);
+ }
+
+ for (i = 0; i < info->n_mlo_links; i++) {
+ list_for_each_entry(link_wdev, &rdev->wiphy.wdev_list, list) {
+ if (link_wdev != info->mlo_links[i].wdev)
+ continue;
+
+ link_wdev->link_id = info->mlo_links[i].link_id;
+ memcpy((void *)link_wdev->link_bssid,
+ info->mlo_links[i].bssid,
+ ETH_ALEN);
+ break;
+ }
+ }
+
#ifdef CONFIG_CFG80211_WEXT
if (info->req_ie) {
memset(&wrqu, 0, sizeof(wrqu));
@@ -960,6 +1016,8 @@ void cfg80211_roamed(struct net_device *dev, struct cfg80211_roam_info *info,
struct cfg80211_event *ev;
unsigned long flags;
u8 *next;
+ int mlo_link_params_size =
+ info->n_mlo_links * sizeof(struct cfg80211_mlo_link_params);
if (!info->bss) {
info->bss = cfg80211_get_bss(wdev->wiphy, info->channel,
@@ -974,7 +1032,8 @@ void cfg80211_roamed(struct net_device *dev, struct cfg80211_roam_info *info,
ev = kzalloc(sizeof(*ev) + info->req_ie_len + info->resp_ie_len +
info->fils.kek_len + info->fils.pmk_len +
- (info->fils.pmkid ? WLAN_PMKID_LEN : 0), gfp);
+ (info->fils.pmkid ? WLAN_PMKID_LEN : 0) +
+ mlo_link_params_size, gfp);
if (!ev) {
cfg80211_put_bss(wdev->wiphy, info->bss);
return;
@@ -1015,6 +1074,13 @@ void cfg80211_roamed(struct net_device *dev, struct cfg80211_roam_info *info,
WLAN_PMKID_LEN);
next += WLAN_PMKID_LEN;
}
+ if (info->n_mlo_links) {
+ ev->rm.n_mlo_links = info->n_mlo_links;
+ ev->rm.mlo_links = (struct cfg80211_mlo_link_params *)next;
+ memcpy((void *)ev->rm.mlo_links, info->mlo_links,
+ mlo_link_params_size);
+ next += mlo_link_params_size;
+ }
ev->rm.fils.update_erp_next_seq_num = info->fils.update_erp_next_seq_num;
if (info->fils.update_erp_next_seq_num)
ev->rm.fils.erp_next_seq_num = info->fils.erp_next_seq_num;
@@ -1074,7 +1140,7 @@ EXPORT_SYMBOL(cfg80211_port_authorized);
void __cfg80211_disconnected(struct net_device *dev, const u8 *ie,
size_t ie_len, u16 reason, bool from_ap)
{
- struct wireless_dev *wdev = dev->ieee80211_ptr;
+ struct wireless_dev *wdev = dev->ieee80211_ptr, *link_wdev;
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
int i;
#ifdef CONFIG_CFG80211_WEXT
@@ -1133,6 +1199,13 @@ void __cfg80211_disconnected(struct net_device *dev, const u8 *ie,
wdev->wext.connect.ssid_len = 0;
#endif
+ list_for_each_entry(link_wdev, &rdev->wiphy.wdev_list, list) {
+ if (link_wdev->mld_wdev != wdev)
+ continue;
+
+ eth_zero_addr(wdev->link_bssid);
+ }
+
schedule_work(&cfg80211_disconnect_work);
}