NL80211_CHAN_HT20 : NL80211_CHAN_NO_HT);
 
        mutex_lock(&vif->wdev.mtx);
-       cfg80211_ch_switch_notify(vif->ndev, &chandef);
+       cfg80211_ch_switch_notify(vif->ndev, &chandef, 0);
        mutex_unlock(&vif->wdev.mtx);
 }
 
        return ath6kl_set_ies(vif, beacon);
 }
 
-static int ath6kl_stop_ap(struct wiphy *wiphy, struct net_device *dev)
+static int ath6kl_stop_ap(struct wiphy *wiphy, struct net_device *dev,
+                         unsigned int link_id)
 {
        struct ath6kl *ar = ath6kl_priv(dev);
        struct ath6kl_vif *vif = netdev_priv(dev);
 
 static int ath6kl_cfg80211_set_bitrate(struct wiphy *wiphy,
                                       struct net_device *dev,
+                                      unsigned int link_id,
                                       const u8 *addr,
                                       const struct cfg80211_bitrate_mask *mask)
 {
 
                             bcon->tail_len))
                privacy = 1;
 
-       memcpy(vif->ssid, wdev->ssid, wdev->ssid_len);
-       vif->ssid_len = wdev->ssid_len;
+       memcpy(vif->ssid, wdev->u.ap.ssid, wdev->u.ap.ssid_len);
+       vif->ssid_len = wdev->u.ap.ssid_len;
 
        /* in case privacy has changed, need to restart the AP */
        if (vif->privacy != privacy) {
 
                rc = _wil_cfg80211_start_ap(wiphy, ndev, vif->ssid,
                                            vif->ssid_len, privacy,
-                                           wdev->beacon_interval,
+                                           wdev->links[0].ap.beacon_interval,
                                            vif->channel,
                                            vif->wmi_edmg_channel, bcon,
                                            vif->hidden_ssid,
 }
 
 static int wil_cfg80211_stop_ap(struct wiphy *wiphy,
-                               struct net_device *ndev)
+                               struct net_device *ndev,
+                               unsigned int link_id)
 {
        struct wil6210_priv *wil = wiphy_to_wil(wiphy);
        struct wil6210_vif *vif = ndev_to_vif(ndev);
 
        return err;
 }
 
-static int brcmf_cfg80211_stop_ap(struct wiphy *wiphy, struct net_device *ndev)
+static int brcmf_cfg80211_stop_ap(struct wiphy *wiphy, struct net_device *ndev,
+                                 unsigned int link_id)
 {
        struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy);
        struct brcmf_if *ifp = netdev_priv(ndev);
 
 static int brcmf_cfg80211_get_channel(struct wiphy *wiphy,
                                      struct wireless_dev *wdev,
+                                     unsigned int link_id,
                                      struct cfg80211_chan_def *chandef)
 {
        struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy);
 
 
                if (priv->mesh_dev) {
                        mesh_wdev = priv->mesh_dev->ieee80211_ptr;
-                       ie->val.mesh_id_len = mesh_wdev->mesh_id_up_len;
-                       memcpy(ie->val.mesh_id, mesh_wdev->ssid,
-                                               mesh_wdev->mesh_id_up_len);
+                       ie->val.mesh_id_len = mesh_wdev->u.mesh.id_up_len;
+                       memcpy(ie->val.mesh_id, mesh_wdev->u.mesh.id,
+                                               mesh_wdev->u.mesh.id_up_len);
                }
 
                ie->len = sizeof(struct mrvl_meshie_val) -
        mesh_wdev->wiphy = priv->wdev->wiphy;
 
        if (priv->mesh_tlv) {
-               sprintf(mesh_wdev->ssid, "mesh");
-               mesh_wdev->mesh_id_up_len = 4;
+               sprintf(mesh_wdev->u.mesh.id, "mesh");
+               mesh_wdev->u.mesh.id_up_len = 4;
        }
 
        mesh_wdev->netdev = mesh_dev;
 
        mwifiex_dbg(priv->adapter, MSG,
                    "indicating channel switch completion to kernel\n");
        mutex_lock(&priv->wdev.mtx);
-       cfg80211_ch_switch_notify(priv->netdev, &priv->dfs_chandef);
+       cfg80211_ch_switch_notify(priv->netdev, &priv->dfs_chandef, 0);
        mutex_unlock(&priv->wdev.mtx);
 }
 
  * Function configures data rates to firmware using bitrate mask
  * provided by cfg80211.
  */
-static int mwifiex_cfg80211_set_bitrate_mask(struct wiphy *wiphy,
-                               struct net_device *dev,
-                               const u8 *peer,
-                               const struct cfg80211_bitrate_mask *mask)
+static int
+mwifiex_cfg80211_set_bitrate_mask(struct wiphy *wiphy,
+                                 struct net_device *dev,
+                                 unsigned int link_id,
+                                 const u8 *peer,
+                                 const struct cfg80211_bitrate_mask *mask)
 {
        struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev);
        u16 bitmap_rates[MAX_BITMAP_RATES_SIZE];
 /* cfg80211 operation handler for stop ap.
  * Function stops BSS running at uAP interface.
  */
-static int mwifiex_cfg80211_stop_ap(struct wiphy *wiphy, struct net_device *dev)
+static int mwifiex_cfg80211_stop_ap(struct wiphy *wiphy, struct net_device *dev,
+                                   unsigned int link_id)
 {
        struct mwifiex_private *priv = mwifiex_netdev_get_priv(dev);
 
                return -EINVAL;
        }
 
-       if (priv->wdev.current_bss) {
+       if (priv->wdev.connected) {
                mwifiex_dbg(adapter, ERROR,
                            "%s: already connected\n", dev->name);
                return -EALREADY;
                return -EBUSY;
        }
 
-       if (!priv->wdev.current_bss && priv->scan_block)
+       if (!priv->wdev.connected && priv->scan_block)
                priv->scan_block = false;
 
        if (!mwifiex_stop_bg_scan(priv))
 
 static int mwifiex_cfg80211_get_channel(struct wiphy *wiphy,
                                        struct wireless_dev *wdev,
+                                       unsigned int link_id,
                                        struct cfg80211_chan_def *chandef)
 {
        struct mwifiex_private *priv = mwifiex_netdev_get_priv(wdev->netdev);
 
        return wilc_add_beacon(vif, 0, 0, beacon);
 }
 
-static int stop_ap(struct wiphy *wiphy, struct net_device *dev)
+static int stop_ap(struct wiphy *wiphy, struct net_device *dev,
+                  unsigned int link_id)
 {
        int ret;
        struct wilc_vif *vif = netdev_priv(dev);
 
        return ret;
 }
 
-static int qtnf_stop_ap(struct wiphy *wiphy, struct net_device *dev)
+static int qtnf_stop_ap(struct wiphy *wiphy, struct net_device *dev,
+                       unsigned int link_id)
 {
        struct qtnf_vif *vif = qtnf_netdev_get_priv(dev);
        int ret;
 
        switch (vif->wdev.iftype) {
        case NL80211_IFTYPE_STATION:
-               if (idx != 0 || !vif->wdev.current_bss)
+               if (idx != 0 || !vif->wdev.connected)
                        return -ENOENT;
 
                ether_addr_copy(mac, vif->bssid);
                pr_err("VIF%u.%u: failed to disconnect\n",
                       mac->macid, vif->vifid);
 
-       if (vif->wdev.current_bss) {
+       if (vif->wdev.connected) {
                netif_carrier_off(vif->netdev);
                cfg80211_disconnected(vif->netdev, reason_code,
                                      NULL, 0, true, GFP_KERNEL);
        struct qtnf_wmac *mac = wiphy_priv(wiphy);
        struct wireless_dev *wdev = dev->ieee80211_ptr;
        struct ieee80211_supported_band *sband;
-       const struct cfg80211_chan_def *chandef = &wdev->chandef;
+       const struct cfg80211_chan_def *chandef = wdev_chandef(wdev, 0);
        struct ieee80211_channel *chan;
        int ret;
 
+
        sband = wiphy->bands[NL80211_BAND_2GHZ];
        if (sband && idx >= sband->n_channels) {
                idx -= sband->n_channels;
        survey->channel = chan;
        survey->filled = 0x0;
 
-       if (chan == chandef->chan)
+       if (chandef && chan == chandef->chan)
                survey->filled = SURVEY_INFO_IN_USE;
 
        ret = qtnf_cmd_get_chan_stats(mac, chan->center_freq, survey);
 
 static int
 qtnf_get_channel(struct wiphy *wiphy, struct wireless_dev *wdev,
-                struct cfg80211_chan_def *chandef)
+                unsigned int link_id, struct cfg80211_chan_def *chandef)
 {
        struct net_device *ndev = wdev->netdev;
        struct qtnf_vif *vif;
 
                dwell_active = scan_req->duration;
                dwell_passive = scan_req->duration;
        } else if (wdev->iftype == NL80211_IFTYPE_STATION &&
-                  wdev->current_bss) {
+                  wdev->connected) {
                /* let device select dwell based on traffic conditions */
                dwell_active = QTNF_SCAN_TIME_AUTO;
                dwell_passive = QTNF_SCAN_TIME_AUTO;
 
                        vif->mac->macid, vif->vifid,
                        join_info->bssid, chandef.chan->hw_value);
 
-               if (!vif->wdev.ssid_len) {
+               if (!vif->wdev.u.client.ssid_len) {
                        pr_warn("VIF%u.%u: SSID unknown for BSS:%pM\n",
                                vif->mac->macid, vif->vifid,
                                join_info->bssid);
                        goto done;
                }
 
-               ie = kzalloc(2 + vif->wdev.ssid_len, GFP_KERNEL);
+               ie = kzalloc(2 + vif->wdev.u.client.ssid_len, GFP_KERNEL);
                if (!ie) {
                        pr_warn("VIF%u.%u: IE alloc failed for BSS:%pM\n",
                                vif->mac->macid, vif->vifid,
                }
 
                ie[0] = WLAN_EID_SSID;
-               ie[1] = vif->wdev.ssid_len;
-               memcpy(ie + 2, vif->wdev.ssid, vif->wdev.ssid_len);
+               ie[1] = vif->wdev.u.client.ssid_len;
+               memcpy(ie + 2, vif->wdev.u.client.ssid,
+                      vif->wdev.u.client.ssid_len);
 
                bss = cfg80211_inform_bss(wiphy, chandef.chan,
                                          CFG80211_BSS_FTYPE_UNKNOWN,
                                          join_info->bssid, 0,
                                          WLAN_CAPABILITY_ESS, 100,
-                                         ie, 2 + vif->wdev.ssid_len,
+                                         ie, 2 + vif->wdev.u.client.ssid_len,
                                          0, GFP_KERNEL);
                if (!bss) {
                        pr_warn("VIF%u.%u: can't connect to unknown BSS: %pM\n",
                        continue;
 
                if (vif->wdev.iftype == NL80211_IFTYPE_STATION &&
-                   !vif->wdev.current_bss)
+                   !vif->wdev.connected)
                        continue;
 
                if (!vif->netdev)
                        continue;
 
                mutex_lock(&vif->wdev.mtx);
-               cfg80211_ch_switch_notify(vif->netdev, &chandef);
+               cfg80211_ch_switch_notify(vif->netdev, &chandef, 0);
                mutex_unlock(&vif->wdev.mtx);
        }
 
 
 }
 
 static int cfg80211_rtw_get_channel(struct wiphy *wiphy, struct wireless_dev *wdev,
+                                   unsigned int link_id,
                                    struct cfg80211_chan_def *chandef)
 {
        struct adapter *adapter = wiphy_to_adapter(wiphy);
        return rtw_add_beacon(adapter, info->head, info->head_len, info->tail, info->tail_len);
 }
 
-static int cfg80211_rtw_stop_ap(struct wiphy *wiphy, struct net_device *ndev)
+static int cfg80211_rtw_stop_ap(struct wiphy *wiphy, struct net_device *ndev,
+                               unsigned int link_id)
 {
        return 0;
 }
 
        IEEE80211_RANGE_PARAMS_MAX_TOTAL_LTF_UNSPECIFIED,
 };
 
+/* multi-link device */
+#define IEEE80211_MLD_MAX_NUM_LINKS    15
+
 #endif /* LINUX_IEEE80211_H */
 
 
 /**
  * struct cfg80211_beacon_data - beacon data
+ * @link_id: the link ID for the AP MLD link sending this beacon
  * @head: head portion of beacon (before TIM IE)
  *     or %NULL if not changed
  * @tail: tail portion of beacon (after TIM IE)
  *     attribute is present in beacon data or not.
  */
 struct cfg80211_beacon_data {
+       unsigned int link_id;
+
        const u8 *head, *tail;
        const u8 *beacon_ies;
        const u8 *proberesp_ies;
                            struct cfg80211_ap_settings *settings);
        int     (*change_beacon)(struct wiphy *wiphy, struct net_device *dev,
                                 struct cfg80211_beacon_data *info);
-       int     (*stop_ap)(struct wiphy *wiphy, struct net_device *dev);
+       int     (*stop_ap)(struct wiphy *wiphy, struct net_device *dev,
+                          unsigned int link_id);
 
 
        int     (*add_station)(struct wiphy *wiphy, struct net_device *dev,
 
        int     (*set_bitrate_mask)(struct wiphy *wiphy,
                                    struct net_device *dev,
+                                   unsigned int link_id,
                                    const u8 *peer,
                                    const struct cfg80211_bitrate_mask *mask);
 
 
        int     (*get_channel)(struct wiphy *wiphy,
                               struct wireless_dev *wdev,
+                              unsigned int link_id,
                               struct cfg80211_chan_def *chandef);
 
        int     (*start_p2p_device)(struct wiphy *wiphy,
                               struct cfg80211_qos_map *qos_map);
 
        int     (*set_ap_chanwidth)(struct wiphy *wiphy, struct net_device *dev,
+                                   unsigned int link_id,
                                    struct cfg80211_chan_def *chandef);
 
        int     (*add_tx_ts)(struct wiphy *wiphy, struct net_device *dev,
  * @WIPHY_FLAG_HAS_STATIC_WEP: The device supports static WEP key installation
  *     before connection.
  * @WIPHY_FLAG_SUPPORTS_EXT_KEK_KCK: The device supports bigger kek and kck keys
+ * @WIPHY_FLAG_SUPPORTS_MLO: This is a temporary flag gating the MLO APIs,
+ *     in order to not have them reachable in normal drivers, until we have
+ *     complete feature/interface combinations/etc. advertisement. No driver
+ *     should set this flag for now.
  */
 enum wiphy_flags {
        WIPHY_FLAG_SUPPORTS_EXT_KEK_KCK         = BIT(0),
-       /* use hole at 1 */
+       WIPHY_FLAG_SUPPORTS_MLO                 = BIT(1),
        WIPHY_FLAG_SPLIT_SCAN_6GHZ              = BIT(2),
        WIPHY_FLAG_NETNS_OK                     = BIT(3),
        WIPHY_FLAG_PS_ON_BY_DEFAULT             = BIT(4),
  * @netdev: (private) Used to reference back to the netdev, may be %NULL
  * @identifier: (private) Identifier used in nl80211 to identify this
  *     wireless device if it has no netdev
+ * @connected_addr: (private) BSSID or AP MLD address if connected
+ * @connected: indicates if connected or not (STA mode)
  * @current_bss: (private) Used by the internal configuration code
  * @chandef: (private) Used by the internal configuration code to track
  *     the user-set channel definition.
        u8 address[ETH_ALEN] __aligned(sizeof(u16));
 
        /* currently used for IBSS and SME - might be rearranged later */
-       u8 ssid[IEEE80211_MAX_SSID_LEN];
-       u8 ssid_len, mesh_id_len, mesh_id_up_len;
        struct cfg80211_conn *conn;
        struct cfg80211_cached_keys *connect_keys;
        enum ieee80211_bss_type conn_bss_type;
        struct list_head event_list;
        spinlock_t event_lock;
 
-       struct cfg80211_internal_bss *current_bss; /* associated / joined */
-       struct cfg80211_chan_def preset_chandef;
-       struct cfg80211_chan_def chandef;
+       u8 connected:1;
 
        bool ps;
        int ps_timeout;
 
-       int beacon_interval;
-
        u32 ap_unexpected_nlportid;
 
        u32 owner_nlportid;
        bool nl_owner_dead;
 
+       /* FIXME: need to rework radar detection for MLO */
        bool cac_started;
        unsigned long cac_start_time;
        unsigned int cac_time_ms;
        struct work_struct pmsr_free_wk;
 
        unsigned long unprot_beacon_reported;
+
+       union {
+               struct {
+                       u8 connected_addr[ETH_ALEN] __aligned(2);
+                       u8 ssid[IEEE80211_MAX_SSID_LEN];
+                       u8 ssid_len;
+               } client;
+               struct {
+                       int beacon_interval;
+                       struct cfg80211_chan_def preset_chandef;
+                       struct cfg80211_chan_def chandef;
+                       u8 id[IEEE80211_MAX_SSID_LEN];
+                       u8 id_len, id_up_len;
+               } mesh;
+               struct {
+                       struct cfg80211_chan_def preset_chandef;
+                       u8 ssid[IEEE80211_MAX_SSID_LEN];
+                       u8 ssid_len;
+               } ap;
+               struct {
+                       struct cfg80211_internal_bss *current_bss;
+                       struct cfg80211_chan_def chandef;
+                       int beacon_interval;
+                       u8 ssid[IEEE80211_MAX_SSID_LEN];
+                       u8 ssid_len;
+               } ibss;
+               struct {
+                       struct cfg80211_chan_def chandef;
+               } ocb;
+       } u;
+
+       struct {
+               u8 addr[ETH_ALEN] __aligned(2);
+               union {
+                       struct {
+                               unsigned int beacon_interval;
+                               struct cfg80211_chan_def chandef;
+                       } ap;
+                       struct {
+                               struct cfg80211_internal_bss *current_bss;
+                       } client;
+               };
+       } links[IEEE80211_MLD_MAX_NUM_LINKS];
+       u16 valid_links;
 };
 
 static inline const u8 *wdev_address(struct wireless_dev *wdev)
        return wiphy_priv(wdev->wiphy);
 }
 
+/**
+ * wdev_chandef - return chandef pointer from wireless_dev
+ * @wdev: the wdev
+ * @link_id: the link ID for MLO
+ *
+ * Return: The chandef depending on the mode, or %NULL.
+ */
+struct cfg80211_chan_def *wdev_chandef(struct wireless_dev *wdev,
+                                      unsigned int link_id);
+
+static inline void WARN_INVALID_LINK_ID(struct wireless_dev *wdev,
+                                       unsigned int link_id)
+{
+       WARN_ON(link_id && !wdev->valid_links);
+       WARN_ON(wdev->valid_links &&
+               !(wdev->valid_links & BIT(link_id)));
+}
+
+#define for_each_valid_link(wdev, link_id)                                     \
+       for (link_id = 0;                                                       \
+            link_id < ((wdev)->valid_links ? ARRAY_SIZE((wdev)->links) : 1);   \
+            link_id++)                                                         \
+               if (!(wdev)->valid_links ||                                     \
+                   ((wdev)->valid_links & BIT(link_id)))
+
 /**
  * DOC: Utility functions
  *
  * cfg80211_ch_switch_notify - update wdev channel and notify userspace
  * @dev: the device which switched channels
  * @chandef: the new channel definition
+ * @link_id: the link ID for MLO, must be 0 for non-MLO
  *
  * Caller must acquire wdev_lock, therefore must only be called from sleepable
  * driver context!
  */
 void cfg80211_ch_switch_notify(struct net_device *dev,
-                              struct cfg80211_chan_def *chandef);
+                              struct cfg80211_chan_def *chandef,
+                              unsigned int link_id);
 
 /*
  * cfg80211_ch_switch_started_notify - notify channel switch start
 
  * Once the association is done, the driver cleans the FILS AAD data.
  */
 
+/**
+ * DOC: Multi-Link Operation
+ *
+ * In Multi-Link Operation, a connection between to MLDs utilizes multiple
+ * links. To use this in nl80211, various commands and responses now need
+ * to or will include the new %NL80211_ATTR_MLO_LINKS attribute.
+ * Additionally, various commands that need to operate on a specific link
+ * now need to be given the %NL80211_ATTR_MLO_LINK_ID attribute, e.g. to
+ * use %NL80211_CMD_START_AP or similar functions.
+ */
+
 /**
  * enum nl80211_commands - supported nl80211 commands
  *
  *      to describe the BSSID address of the AP and %NL80211_ATTR_TIMEOUT to
  *      specify the timeout value.
  *
+ * @NL80211_CMD_ADD_LINK: Add a new link to an interface. The
+ *     %NL80211_ATTR_MLO_LINK_ID attribute is used for the new link.
+ * @NL80211_CMD_REMOVE_LINK: Remove a link from an interface. This may come
+ *     without %NL80211_ATTR_MLO_LINK_ID as an easy way to remove all links
+ *     in preparation for e.g. roaming to a regular (non-MLO) AP.
+ *
  * @NL80211_CMD_MAX: highest used command number
  * @__NL80211_CMD_AFTER_LAST: internal use
  */
 
        NL80211_CMD_ASSOC_COMEBACK,
 
+       NL80211_CMD_ADD_LINK,
+       NL80211_CMD_REMOVE_LINK,
+
        /* add new commands above here */
 
        /* used to define NL80211_CMD_MAX below */
  *     association request when used with NL80211_CMD_NEW_STATION). Can be set
  *     only if %NL80211_STA_FLAG_WME is set.
  *
+ * @NL80211_ATTR_MLO_LINK_ID: A (u8) link ID for use with MLO, to be used with
+ *     various commands that need a link ID to operate.
+ * @NL80211_ATTR_MLO_LINKS: A nested array of links, each containing some
+ *     per-link information and a link ID.
+ *
  * @NUM_NL80211_ATTR: total number of nl80211_attrs available
  * @NL80211_ATTR_MAX: highest attribute number currently defined
  * @__NL80211_ATTR_AFTER_LAST: internal use
 
        NL80211_ATTR_DISABLE_EHT,
 
+       NL80211_ATTR_MLO_LINKS,
+       NL80211_ATTR_MLO_LINK_ID,
+
        /* add attributes here, update the policy in nl80211.c */
 
        __NL80211_ATTR_AFTER_LAST,
 
        sdata->u.ap.next_beacon = NULL;
 }
 
-static int ieee80211_stop_ap(struct wiphy *wiphy, struct net_device *dev)
+static int ieee80211_stop_ap(struct wiphy *wiphy, struct net_device *dev,
+                            unsigned int link_id)
 {
        struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
        struct ieee80211_sub_if_data *vlan;
 
 static int ieee80211_set_bitrate_mask(struct wiphy *wiphy,
                                      struct net_device *dev,
+                                     unsigned int link_id,
                                      const u8 *addr,
                                      const struct cfg80211_bitrate_mask *mask)
 {
        if (err)
                return err;
 
-       cfg80211_ch_switch_notify(sdata->dev, &sdata->csa_chandef);
+       cfg80211_ch_switch_notify(sdata->dev, &sdata->csa_chandef, 0);
 
        return 0;
 }
 
 static int ieee80211_cfg_get_channel(struct wiphy *wiphy,
                                     struct wireless_dev *wdev,
+                                    unsigned int link_id,
                                     struct cfg80211_chan_def *chandef)
 {
        struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev);
 
 static int ieee80211_set_ap_chanwidth(struct wiphy *wiphy,
                                      struct net_device *dev,
+                                     unsigned int link_id,
                                      struct cfg80211_chan_def *chandef)
 {
        struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
 
                return;
        }
 
-       cfg80211_ch_switch_notify(sdata->dev, &sdata->reserved_chandef);
+       cfg80211_ch_switch_notify(sdata->dev, &sdata->reserved_chandef, 0);
 }
 
 void ieee80211_chswitch_done(struct ieee80211_vif *vif, bool success)
 
 // SPDX-License-Identifier: GPL-2.0
+/*
+ * Parts of this file are
+ * Copyright (C) 2022 Intel Corporation
+ */
 #include <linux/ieee80211.h>
 #include <linux/export.h>
 #include <net/cfg80211.h>
 #include "rdev-ops.h"
 
 
-int __cfg80211_stop_ap(struct cfg80211_registered_device *rdev,
-                      struct net_device *dev, bool notify)
+static int ___cfg80211_stop_ap(struct cfg80211_registered_device *rdev,
+                              struct net_device *dev, unsigned int link_id,
+                              bool notify)
 {
        struct wireless_dev *wdev = dev->ieee80211_ptr;
        int err;
            dev->ieee80211_ptr->iftype != NL80211_IFTYPE_P2P_GO)
                return -EOPNOTSUPP;
 
-       if (!wdev->beacon_interval)
+       if (!wdev->links[link_id].ap.beacon_interval)
                return -ENOENT;
 
-       err = rdev_stop_ap(rdev, dev);
+       err = rdev_stop_ap(rdev, dev, link_id);
        if (!err) {
                wdev->conn_owner_nlportid = 0;
-               wdev->beacon_interval = 0;
-               memset(&wdev->chandef, 0, sizeof(wdev->chandef));
-               wdev->ssid_len = 0;
+               wdev->links[link_id].ap.beacon_interval = 0;
+               memset(&wdev->links[link_id].ap.chandef, 0,
+                      sizeof(wdev->links[link_id].ap.chandef));
+               wdev->u.ap.ssid_len = 0;
                rdev_set_qos_map(rdev, dev, NULL);
                if (notify)
                        nl80211_send_ap_stopped(wdev);
        return err;
 }
 
+int __cfg80211_stop_ap(struct cfg80211_registered_device *rdev,
+                      struct net_device *dev, int link_id,
+                      bool notify)
+{
+       unsigned int link;
+       int ret = 0;
+
+       if (link_id >= 0)
+               return ___cfg80211_stop_ap(rdev, dev, link_id, notify);
+
+       for_each_valid_link(dev->ieee80211_ptr, link) {
+               int ret1 = ___cfg80211_stop_ap(rdev, dev, link, notify);
+
+               if (ret1)
+                       ret = ret1;
+               /* try the next one also if one errored */
+       }
+
+       return ret;
+}
+
 int cfg80211_stop_ap(struct cfg80211_registered_device *rdev,
-                    struct net_device *dev, bool notify)
+                    struct net_device *dev, int link_id,
+                    bool notify)
 {
        struct wireless_dev *wdev = dev->ieee80211_ptr;
        int err;
 
        wdev_lock(wdev);
-       err = __cfg80211_stop_ap(rdev, dev, notify);
+       err = __cfg80211_stop_ap(rdev, dev, link_id, notify);
        wdev_unlock(wdev);
 
        return err;
 
  * range of chandef.
  */
 bool cfg80211_is_sub_chan(struct cfg80211_chan_def *chandef,
-                         struct ieee80211_channel *chan)
+                         struct ieee80211_channel *chan,
+                         bool primary_only)
 {
        int width;
        u32 freq;
 
+       if (!chandef->chan)
+               return false;
+
        if (chandef->chan->center_freq == chan->center_freq)
                return true;
 
+       if (primary_only)
+               return false;
+
        width = cfg80211_chandef_get_width(chandef);
        if (width <= 20)
                return false;
 
 bool cfg80211_beaconing_iface_active(struct wireless_dev *wdev)
 {
-       bool active = false;
+       unsigned int link;
 
        ASSERT_WDEV_LOCK(wdev);
 
-       if (!wdev->chandef.chan)
-               return false;
-
        switch (wdev->iftype) {
        case NL80211_IFTYPE_AP:
        case NL80211_IFTYPE_P2P_GO:
-               active = wdev->beacon_interval != 0;
+               for_each_valid_link(wdev, link) {
+                       if (wdev->links[link].ap.beacon_interval)
+                               return true;
+               }
                break;
        case NL80211_IFTYPE_ADHOC:
-               active = wdev->ssid_len != 0;
+               if (wdev->u.ibss.ssid_len)
+                       return true;
                break;
        case NL80211_IFTYPE_MESH_POINT:
-               active = wdev->mesh_id_len != 0;
+               if (wdev->u.mesh.id_len)
+                       return true;
                break;
        case NL80211_IFTYPE_STATION:
        case NL80211_IFTYPE_OCB:
                WARN_ON(1);
        }
 
-       return active;
+       return false;
+}
+
+bool cfg80211_wdev_on_sub_chan(struct wireless_dev *wdev,
+                              struct ieee80211_channel *chan,
+                              bool primary_only)
+{
+       unsigned int link;
+
+       switch (wdev->iftype) {
+       case NL80211_IFTYPE_AP:
+       case NL80211_IFTYPE_P2P_GO:
+               for_each_valid_link(wdev, link) {
+                       if (cfg80211_is_sub_chan(&wdev->links[link].ap.chandef,
+                                                chan, primary_only))
+                               return true;
+               }
+               break;
+       case NL80211_IFTYPE_ADHOC:
+               return cfg80211_is_sub_chan(&wdev->u.ibss.chandef, chan,
+                                           primary_only);
+       case NL80211_IFTYPE_MESH_POINT:
+               return cfg80211_is_sub_chan(&wdev->u.mesh.chandef, chan,
+                                           primary_only);
+       default:
+               break;
+       }
+
+       return false;
 }
 
 static bool cfg80211_is_wiphy_oper_chan(struct wiphy *wiphy,
                        continue;
                }
 
-               if (cfg80211_is_sub_chan(&wdev->chandef, chan)) {
+               if (cfg80211_wdev_on_sub_chan(wdev, chan, false)) {
                        wdev_unlock(wdev);
                        return true;
                }
        if (!cfg80211_chandef_valid(&rdev->background_radar_chandef))
                return false;
 
-       return cfg80211_is_sub_chan(&rdev->background_radar_chandef, channel);
+       return cfg80211_is_sub_chan(&rdev->background_radar_chandef, channel,
+                                   false);
 }
 
 bool cfg80211_any_wiphy_oper_chan(struct wiphy *wiphy,
 }
 EXPORT_SYMBOL(cfg80211_chandef_usable);
 
+static bool cfg80211_ir_permissive_check_wdev(enum nl80211_iftype iftype,
+                                             struct wireless_dev *wdev,
+                                             struct ieee80211_channel *chan)
+{
+       struct ieee80211_channel *other_chan = NULL;
+       unsigned int link_id;
+       int r1, r2;
+
+       for_each_valid_link(wdev, link_id) {
+               if (wdev->iftype == NL80211_IFTYPE_STATION &&
+                   wdev->links[link_id].client.current_bss)
+                       other_chan = wdev->links[link_id].client.current_bss->pub.channel;
+
+               /*
+                * If a GO already operates on the same GO_CONCURRENT channel,
+                * this one (maybe the same one) can beacon as well. We allow
+                * the operation even if the station we relied on with
+                * GO_CONCURRENT is disconnected now. But then we must make sure
+                * we're not outdoor on an indoor-only channel.
+                */
+               if (iftype == NL80211_IFTYPE_P2P_GO &&
+                   wdev->iftype == NL80211_IFTYPE_P2P_GO &&
+                   wdev->links[link_id].ap.beacon_interval &&
+                   !(chan->flags & IEEE80211_CHAN_INDOOR_ONLY))
+                       other_chan = wdev->links[link_id].ap.chandef.chan;
+
+               if (!other_chan)
+                       continue;
+
+               if (chan == other_chan)
+                       return true;
+
+               if (chan->band != NL80211_BAND_5GHZ &&
+                   chan->band != NL80211_BAND_6GHZ)
+                       continue;
+
+               r1 = cfg80211_get_unii(chan->center_freq);
+               r2 = cfg80211_get_unii(other_chan->center_freq);
+
+               if (r1 != -EINVAL && r1 == r2) {
+                       /*
+                        * At some locations channels 149-165 are considered a
+                        * bundle, but at other locations, e.g., Indonesia,
+                        * channels 149-161 are considered a bundle while
+                        * channel 165 is left out and considered to be in a
+                        * different bundle. Thus, in case that there is a
+                        * station interface connected to an AP on channel 165,
+                        * it is assumed that channels 149-161 are allowed for
+                        * GO operations. However, having a station interface
+                        * connected to an AP on channels 149-161, does not
+                        * allow GO operation on channel 165.
+                        */
+                       if (chan->center_freq == 5825 &&
+                           other_chan->center_freq != 5825)
+                               continue;
+                       return true;
+               }
+       }
+
+       return false;
+}
+
 /*
  * Check if the channel can be used under permissive conditions mandated by
  * some regulatory bodies, i.e., the channel is marked with
         * the current registered device.
         */
        list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
-               struct ieee80211_channel *other_chan = NULL;
-               int r1, r2;
+               bool ret;
 
                wdev_lock(wdev);
-               if (wdev->iftype == NL80211_IFTYPE_STATION &&
-                   wdev->current_bss)
-                       other_chan = wdev->current_bss->pub.channel;
-
-               /*
-                * If a GO already operates on the same GO_CONCURRENT channel,
-                * this one (maybe the same one) can beacon as well. We allow
-                * the operation even if the station we relied on with
-                * GO_CONCURRENT is disconnected now. But then we must make sure
-                * we're not outdoor on an indoor-only channel.
-                */
-               if (iftype == NL80211_IFTYPE_P2P_GO &&
-                   wdev->iftype == NL80211_IFTYPE_P2P_GO &&
-                   wdev->beacon_interval &&
-                   !(chan->flags & IEEE80211_CHAN_INDOOR_ONLY))
-                       other_chan = wdev->chandef.chan;
+               ret = cfg80211_ir_permissive_check_wdev(iftype, wdev, chan);
                wdev_unlock(wdev);
 
-               if (!other_chan)
-                       continue;
-
-               if (chan == other_chan)
-                       return true;
-
-               if (chan->band != NL80211_BAND_5GHZ &&
-                   chan->band != NL80211_BAND_6GHZ)
-                       continue;
-
-               r1 = cfg80211_get_unii(chan->center_freq);
-               r2 = cfg80211_get_unii(other_chan->center_freq);
-
-               if (r1 != -EINVAL && r1 == r2) {
-                       /*
-                        * At some locations channels 149-165 are considered a
-                        * bundle, but at other locations, e.g., Indonesia,
-                        * channels 149-161 are considered a bundle while
-                        * channel 165 is left out and considered to be in a
-                        * different bundle. Thus, in case that there is a
-                        * station interface connected to an AP on channel 165,
-                        * it is assumed that channels 149-161 are allowed for
-                        * GO operations. However, having a station interface
-                        * connected to an AP on channels 149-161, does not
-                        * allow GO operation on channel 165.
-                        */
-                       if (chan->center_freq == 5825 &&
-                           other_chan->center_freq != 5825)
-                               continue;
-                       return true;
-               }
+               if (ret)
+                       return ret;
        }
 
        return false;
        return false;
 }
 EXPORT_SYMBOL(cfg80211_any_usable_channels);
+
+struct cfg80211_chan_def *wdev_chandef(struct wireless_dev *wdev,
+                                      unsigned int link_id)
+{
+       ASSERT_WDEV_LOCK(wdev);
+
+       switch (wdev->iftype) {
+       case NL80211_IFTYPE_MESH_POINT:
+               return &wdev->u.mesh.chandef;
+       case NL80211_IFTYPE_ADHOC:
+               return &wdev->u.ibss.chandef;
+       case NL80211_IFTYPE_OCB:
+               return &wdev->u.ocb.chandef;
+       case NL80211_IFTYPE_AP:
+       case NL80211_IFTYPE_P2P_GO:
+               return &wdev->links[link_id].ap.chandef;
+       default:
+               return NULL;
+       }
+}
+EXPORT_SYMBOL(wdev_chandef);
 
                                      bool unregister_netdev)
 {
        struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
+       unsigned int link_id;
 
        ASSERT_RTNL();
        lockdep_assert_held(&rdev->wiphy.mtx);
         */
        cfg80211_process_wdev_events(wdev);
 
-       if (WARN_ON(wdev->current_bss)) {
-               cfg80211_unhold_bss(wdev->current_bss);
-               cfg80211_put_bss(wdev->wiphy, &wdev->current_bss->pub);
-               wdev->current_bss = NULL;
+       if (wdev->iftype == NL80211_IFTYPE_STATION ||
+           wdev->iftype == NL80211_IFTYPE_P2P_CLIENT) {
+               for (link_id = 0; link_id < ARRAY_SIZE(wdev->links); link_id++) {
+                       struct cfg80211_internal_bss *curbss;
+
+                       curbss = wdev->links[link_id].client.current_bss;
+
+                       if (WARN_ON(curbss)) {
+                               cfg80211_unhold_bss(curbss);
+                               cfg80211_put_bss(wdev->wiphy, &curbss->pub);
+                               wdev->links[link_id].client.current_bss = NULL;
+                       }
+               }
        }
+
+       wdev->connected = false;
 }
 
 void cfg80211_unregister_wdev(struct wireless_dev *wdev)
                break;
        case NL80211_IFTYPE_AP:
        case NL80211_IFTYPE_P2P_GO:
-               __cfg80211_stop_ap(rdev, dev, true);
+               __cfg80211_stop_ap(rdev, dev, -1, true);
                break;
        case NL80211_IFTYPE_OCB:
                __cfg80211_leave_ocb(rdev, dev);
                                memcpy(&setup, &default_mesh_setup,
                                                sizeof(setup));
                                 /* back compat only needed for mesh_id */
-                               setup.mesh_id = wdev->ssid;
-                               setup.mesh_id_len = wdev->mesh_id_up_len;
-                               if (wdev->mesh_id_up_len)
+                               setup.mesh_id = wdev->u.mesh.id;
+                               setup.mesh_id_len = wdev->u.mesh.id_up_len;
+                               if (wdev->u.mesh.id_up_len)
                                        __cfg80211_join_mesh(rdev, dev,
                                                        &setup,
                                                        &default_mesh_config);
 
 void cfg80211_bss_age(struct cfg80211_registered_device *rdev,
                       unsigned long age_secs);
 void cfg80211_update_assoc_bss_entry(struct wireless_dev *wdev,
+                                    unsigned int link,
                                     struct ieee80211_channel *channel);
 
 /* IBSS */
 
 /* AP */
 int __cfg80211_stop_ap(struct cfg80211_registered_device *rdev,
-                      struct net_device *dev, bool notify);
+                      struct net_device *dev, int link,
+                      bool notify);
 int cfg80211_stop_ap(struct cfg80211_registered_device *rdev,
-                    struct net_device *dev, bool notify);
+                    struct net_device *dev, int link,
+                    bool notify);
 
 /* MLME */
 int cfg80211_mlme_auth(struct cfg80211_registered_device *rdev,
 bool cfg80211_beaconing_iface_active(struct wireless_dev *wdev);
 
 bool cfg80211_is_sub_chan(struct cfg80211_chan_def *chandef,
-                         struct ieee80211_channel *chan);
+                         struct ieee80211_channel *chan,
+                         bool primary_only);
+bool cfg80211_wdev_on_sub_chan(struct wireless_dev *wdev,
+                              struct ieee80211_channel *chan,
+                              bool primary_only);
 
 static inline unsigned int elapsed_jiffies_msecs(unsigned long start)
 {
 
        if (WARN_ON(wdev->iftype != NL80211_IFTYPE_ADHOC))
                return;
 
-       if (!wdev->ssid_len)
+       if (!wdev->u.ibss.ssid_len)
                return;
 
        bss = cfg80211_get_bss(wdev->wiphy, channel, bssid, NULL, 0,
        if (WARN_ON(!bss))
                return;
 
-       if (wdev->current_bss) {
-               cfg80211_unhold_bss(wdev->current_bss);
-               cfg80211_put_bss(wdev->wiphy, &wdev->current_bss->pub);
+       if (wdev->u.ibss.current_bss) {
+               cfg80211_unhold_bss(wdev->u.ibss.current_bss);
+               cfg80211_put_bss(wdev->wiphy, &wdev->u.ibss.current_bss->pub);
        }
 
        cfg80211_hold_bss(bss_from_pub(bss));
-       wdev->current_bss = bss_from_pub(bss);
+       wdev->u.ibss.current_bss = bss_from_pub(bss);
 
        if (!(wdev->wiphy->flags & WIPHY_FLAG_HAS_STATIC_WEP))
                cfg80211_upload_connect_keys(wdev);
        lockdep_assert_held(&rdev->wiphy.mtx);
        ASSERT_WDEV_LOCK(wdev);
 
-       if (wdev->ssid_len)
+       if (wdev->u.ibss.ssid_len)
                return -EALREADY;
 
        if (!params->basic_rates) {
                kfree_sensitive(wdev->connect_keys);
        wdev->connect_keys = connkeys;
 
-       wdev->chandef = params->chandef;
+       wdev->u.ibss.chandef = params->chandef;
        if (connkeys) {
                params->wep_keys = connkeys->params;
                params->wep_tx_key = connkeys->def;
                return err;
        }
 
-       memcpy(wdev->ssid, params->ssid, params->ssid_len);
-       wdev->ssid_len = params->ssid_len;
+       memcpy(wdev->u.ibss.ssid, params->ssid, params->ssid_len);
+       wdev->u.ibss.ssid_len = params->ssid_len;
 
        return 0;
 }
                for (i = 0; i < 6; i++)
                        rdev_del_key(rdev, dev, i, false, NULL);
 
-       if (wdev->current_bss) {
-               cfg80211_unhold_bss(wdev->current_bss);
-               cfg80211_put_bss(wdev->wiphy, &wdev->current_bss->pub);
+       if (wdev->u.ibss.current_bss) {
+               cfg80211_unhold_bss(wdev->u.ibss.current_bss);
+               cfg80211_put_bss(wdev->wiphy, &wdev->u.ibss.current_bss->pub);
        }
 
-       wdev->current_bss = NULL;
-       wdev->ssid_len = 0;
-       memset(&wdev->chandef, 0, sizeof(wdev->chandef));
+       wdev->u.ibss.current_bss = NULL;
+       wdev->u.ibss.ssid_len = 0;
+       memset(&wdev->u.ibss.chandef, 0, sizeof(wdev->u.ibss.chandef));
 #ifdef CONFIG_CFG80211_WEXT
        if (!nowext)
                wdev->wext.ibss.ssid_len = 0;
 
        ASSERT_WDEV_LOCK(wdev);
 
-       if (!wdev->ssid_len)
+       if (!wdev->u.ibss.ssid_len)
                return -ENOLINK;
 
        err = rdev_leave_ibss(rdev, dev);
 
        wdev_lock(wdev);
        err = 0;
-       if (wdev->ssid_len)
+       if (wdev->u.ibss.ssid_len)
                err = __cfg80211_leave_ibss(rdev, dev, true);
        wdev_unlock(wdev);
 
                return -EINVAL;
 
        wdev_lock(wdev);
-       if (wdev->current_bss)
-               chan = wdev->current_bss->pub.channel;
+       if (wdev->u.ibss.current_bss)
+               chan = wdev->u.ibss.current_bss->pub.channel;
        else if (wdev->wext.ibss.chandef.chan)
                chan = wdev->wext.ibss.chandef.chan;
        wdev_unlock(wdev);
 
        wdev_lock(wdev);
        err = 0;
-       if (wdev->ssid_len)
+       if (wdev->u.ibss.ssid_len)
                err = __cfg80211_leave_ibss(rdev, dev, true);
        wdev_unlock(wdev);
 
        if (len > 0 && ssid[len - 1] == '\0')
                len--;
 
-       memcpy(wdev->ssid, ssid, len);
-       wdev->wext.ibss.ssid = wdev->ssid;
+       memcpy(wdev->u.ibss.ssid, ssid, len);
+       wdev->wext.ibss.ssid = wdev->u.ibss.ssid;
        wdev->wext.ibss.ssid_len = len;
 
        wdev_lock(wdev);
        data->flags = 0;
 
        wdev_lock(wdev);
-       if (wdev->ssid_len) {
+       if (wdev->u.ibss.ssid_len) {
                data->flags = 1;
-               data->length = wdev->ssid_len;
-               memcpy(ssid, wdev->ssid, data->length);
+               data->length = wdev->u.ibss.ssid_len;
+               memcpy(ssid, wdev->u.ibss.ssid, data->length);
        } else if (wdev->wext.ibss.ssid && wdev->wext.ibss.ssid_len) {
                data->flags = 1;
                data->length = wdev->wext.ibss.ssid_len;
 
        wdev_lock(wdev);
        err = 0;
-       if (wdev->ssid_len)
+       if (wdev->u.ibss.ssid_len)
                err = __cfg80211_leave_ibss(rdev, dev, true);
        wdev_unlock(wdev);
 
        ap_addr->sa_family = ARPHRD_ETHER;
 
        wdev_lock(wdev);
-       if (wdev->current_bss)
-               memcpy(ap_addr->sa_data, wdev->current_bss->pub.bssid, ETH_ALEN);
+       if (wdev->u.ibss.current_bss)
+               memcpy(ap_addr->sa_data, wdev->u.ibss.current_bss->pub.bssid,
+                      ETH_ALEN);
        else if (wdev->wext.ibss.bssid)
                memcpy(ap_addr->sa_data, wdev->wext.ibss.bssid, ETH_ALEN);
        else
 
 // SPDX-License-Identifier: GPL-2.0
+/*
+ * Portions
+ * Copyright (C) 2022 Intel Corporation
+ */
 #include <linux/ieee80211.h>
 #include <linux/export.h>
 #include <net/cfg80211.h>
              setup->is_secure)
                return -EOPNOTSUPP;
 
-       if (wdev->mesh_id_len)
+       if (wdev->u.mesh.id_len)
                return -EALREADY;
 
        if (!setup->mesh_id_len)
 
        if (!setup->chandef.chan) {
                /* if no channel explicitly given, use preset channel */
-               setup->chandef = wdev->preset_chandef;
+               setup->chandef = wdev->u.mesh.preset_chandef;
        }
 
        if (!setup->chandef.chan) {
 
        err = rdev_join_mesh(rdev, dev, conf, setup);
        if (!err) {
-               memcpy(wdev->ssid, setup->mesh_id, setup->mesh_id_len);
-               wdev->mesh_id_len = setup->mesh_id_len;
-               wdev->chandef = setup->chandef;
-               wdev->beacon_interval = setup->beacon_interval;
+               memcpy(wdev->u.mesh.id, setup->mesh_id, setup->mesh_id_len);
+               wdev->u.mesh.id_len = setup->mesh_id_len;
+               wdev->u.mesh.chandef = setup->chandef;
+               wdev->u.mesh.beacon_interval = setup->beacon_interval;
        }
 
        return err;
                err = rdev_libertas_set_mesh_channel(rdev, wdev->netdev,
                                                     chandef->chan);
                if (!err)
-                       wdev->chandef = *chandef;
+                       wdev->u.mesh.chandef = *chandef;
 
                return err;
        }
 
-       if (wdev->mesh_id_len)
+       if (wdev->u.mesh.id_len)
                return -EBUSY;
 
-       wdev->preset_chandef = *chandef;
+       wdev->u.mesh.preset_chandef = *chandef;
        return 0;
 }
 
        if (!rdev->ops->leave_mesh)
                return -EOPNOTSUPP;
 
-       if (!wdev->mesh_id_len)
+       if (!wdev->u.mesh.id_len)
                return -ENOTCONN;
 
        err = rdev_leave_mesh(rdev, dev);
        if (!err) {
                wdev->conn_owner_nlportid = 0;
-               wdev->mesh_id_len = 0;
-               wdev->beacon_interval = 0;
-               memset(&wdev->chandef, 0, sizeof(wdev->chandef));
+               wdev->u.mesh.id_len = 0;
+               wdev->u.mesh.beacon_interval = 0;
+               memset(&wdev->u.mesh.chandef, 0,
+                      sizeof(wdev->u.mesh.chandef));
                rdev_set_qos_map(rdev, dev, NULL);
                cfg80211_sched_dfs_chan_update(rdev);
        }
 
 
        nl80211_send_deauth(rdev, wdev->netdev, buf, len, reconnect, GFP_KERNEL);
 
-       if (!wdev->current_bss ||
-           !ether_addr_equal(wdev->current_bss->pub.bssid, bssid))
+       if (!wdev->connected || !ether_addr_equal(wdev->u.client.connected_addr, bssid))
                return;
 
        __cfg80211_disconnected(wdev->netdev, NULL, 0, reason_code, from_ap);
        nl80211_send_disassoc(rdev, wdev->netdev, buf, len, reconnect,
                              GFP_KERNEL);
 
-       if (WARN_ON(!wdev->current_bss ||
-                   !ether_addr_equal(wdev->current_bss->pub.bssid, bssid)))
+       if (WARN_ON(!wdev->connected ||
+                   !ether_addr_equal(wdev->u.client.connected_addr, bssid)))
                return;
 
        __cfg80211_disconnected(wdev->netdev, NULL, 0, reason_code, from_ap);
                if (!key || !key_len || key_idx < 0 || key_idx > 3)
                        return -EINVAL;
 
-       if (wdev->current_bss &&
-           ether_addr_equal(bssid, wdev->current_bss->pub.bssid))
+       if (wdev->connected &&
+           ether_addr_equal(bssid, wdev->u.client.connected_addr))
                return -EALREADY;
 
        req.bss = cfg80211_get_bss(&rdev->wiphy, chan, bssid, ssid, ssid_len,
 
        ASSERT_WDEV_LOCK(wdev);
 
-       if (wdev->current_bss &&
-           (!req->prev_bssid || !ether_addr_equal(wdev->current_bss->pub.bssid,
-                                                  req->prev_bssid)))
+       if (wdev->connected &&
+           (!req->prev_bssid ||
+            !ether_addr_equal(wdev->u.client.connected_addr, req->prev_bssid)))
                return -EALREADY;
 
        cfg80211_oper_and_ht_capa(&req->ht_capa_mask,
        ASSERT_WDEV_LOCK(wdev);
 
        if (local_state_change &&
-           (!wdev->current_bss ||
-            !ether_addr_equal(wdev->current_bss->pub.bssid, bssid)))
+           (!wdev->connected ||
+            !ether_addr_equal(wdev->u.client.connected_addr, bssid)))
                return 0;
 
        if (ether_addr_equal(wdev->disconnect_bssid, bssid) ||
-           (wdev->current_bss &&
-            ether_addr_equal(wdev->current_bss->pub.bssid, bssid)))
+           (wdev->connected &&
+            ether_addr_equal(wdev->u.client.connected_addr, bssid)))
                wdev->conn_owner_nlportid = 0;
 
        return rdev_deauth(rdev, dev, &req);
 
        ASSERT_WDEV_LOCK(wdev);
 
-       if (!wdev->current_bss)
+       if (!wdev->connected)
                return -ENOTCONN;
 
-       if (ether_addr_equal(wdev->current_bss->pub.bssid, bssid))
-               req.bss = &wdev->current_bss->pub;
+       if (ether_addr_equal(wdev->links[0].client.current_bss->pub.bssid,
+                            bssid))
+               req.bss = &wdev->links[0].client.current_bss->pub;
        else
                return -ENOTCONN;
 
                return err;
 
        /* driver should have reported the disassoc */
-       WARN_ON(wdev->current_bss);
+       WARN_ON(wdev->connected);
        return 0;
 }
 
        if (!rdev->ops->deauth)
                return;
 
-       if (!wdev->current_bss)
+       if (!wdev->connected)
                return;
 
-       memcpy(bssid, wdev->current_bss->pub.bssid, ETH_ALEN);
+       memcpy(bssid, wdev->u.client.connected_addr, ETH_ALEN);
        cfg80211_mlme_deauth(rdev, dev, bssid, NULL, 0,
                             WLAN_REASON_DEAUTH_LEAVING, false);
 }
 
                switch (wdev->iftype) {
                case NL80211_IFTYPE_ADHOC:
+                       /*
+                        * check for IBSS DA must be done by driver as
+                        * cfg80211 doesn't track the stations
+                        */
+                       if (!wdev->u.ibss.current_bss ||
+                           !ether_addr_equal(wdev->u.ibss.current_bss->pub.bssid,
+                                             mgmt->bssid)) {
+                               err = -ENOTCONN;
+                               break;
+                       }
+                       break;
                case NL80211_IFTYPE_STATION:
                case NL80211_IFTYPE_P2P_CLIENT:
-                       if (!wdev->current_bss) {
+                       if (!wdev->connected) {
                                err = -ENOTCONN;
                                break;
                        }
 
-                       if (!ether_addr_equal(wdev->current_bss->pub.bssid,
+                       /* FIXME: MLD may address this differently */
+
+                       if (!ether_addr_equal(wdev->u.client.connected_addr,
                                              mgmt->bssid)) {
                                err = -ENOTCONN;
                                break;
                        }
 
-                       /*
-                        * check for IBSS DA must be done by driver as
-                        * cfg80211 doesn't track the stations
-                        */
-                       if (wdev->iftype == NL80211_IFTYPE_ADHOC)
-                               break;
-
                        /* for station, check that DA is the AP */
-                       if (!ether_addr_equal(wdev->current_bss->pub.bssid,
+                       if (!ether_addr_equal(wdev->u.client.connected_addr,
                                              mgmt->da)) {
                                err = -ENOTCONN;
                                break;
                if (!ieee80211_is_action(mgmt->frame_control) ||
                    mgmt->u.action.category != WLAN_CATEGORY_PUBLIC)
                        return -EINVAL;
-               if (!wdev->current_bss &&
+               if (!wdev->connected &&
                    !wiphy_ext_feature_isset(
                            &rdev->wiphy,
                            NL80211_EXT_FEATURE_MGMT_TX_RANDOM_TA))
                        return -EINVAL;
-               if (wdev->current_bss &&
+               if (wdev->connected &&
                    !wiphy_ext_feature_isset(
                            &rdev->wiphy,
                            NL80211_EXT_FEATURE_MGMT_TX_RANDOM_TA_CONNECTED))
        struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
        unsigned long timeout;
 
+       /* not yet supported */
+       if (wdev->valid_links)
+               return;
+
        trace_cfg80211_cac_event(netdev, event);
 
        if (WARN_ON(!wdev->cac_started && event != NL80211_RADAR_CAC_STARTED))
                return;
 
-       if (WARN_ON(!wdev->chandef.chan))
+       if (WARN_ON(!wdev->links[0].ap.chandef.chan))
                return;
 
        switch (event) {
 
                                 NL80211_EHT_MIN_CAPABILITY_LEN,
                                 NL80211_EHT_MAX_CAPABILITY_LEN),
        [NL80211_ATTR_DISABLE_EHT] = { .type = NLA_FLAG },
+       [NL80211_ATTR_MLO_LINKS] =
+               NLA_POLICY_NESTED_ARRAY(nl80211_policy),
+       [NL80211_ATTR_MLO_LINK_ID] =
+               NLA_POLICY_RANGE(NLA_U8, 0, IEEE80211_MLD_MAX_NUM_LINKS),
 };
 
 /* policy for the key attributes */
 
 /* netlink command implementations */
 
+/**
+ * nl80211_link_id - return link ID
+ * @attrs: attributes to look at
+ *
+ * Returns: the link ID or 0 if not given
+ *
+ * Note this function doesn't do any validation of the link
+ * ID validity wrt. links that were actually added, so it must
+ * be called only from ops with %NL80211_FLAG_MLO_VALID_LINK_ID
+ * or if additional validation is done.
+ */
+static unsigned int nl80211_link_id(struct nlattr **attrs)
+{
+       struct nlattr *linkid = attrs[NL80211_ATTR_MLO_LINK_ID];
+
+       if (!linkid)
+               return 0;
+
+       return nla_get_u8(linkid);
+}
+
+static int nl80211_link_id_or_invalid(struct nlattr **attrs)
+{
+       struct nlattr *linkid = attrs[NL80211_ATTR_MLO_LINK_ID];
+
+       if (!linkid)
+               return -1;
+
+       return nla_get_u8(linkid);
+}
+
 struct key_parse {
        struct key_params p;
        int idx;
        case NL80211_IFTYPE_MESH_POINT:
                break;
        case NL80211_IFTYPE_ADHOC:
+               if (wdev->u.ibss.current_bss)
+                       return 0;
+               return -ENOLINK;
        case NL80211_IFTYPE_STATION:
        case NL80211_IFTYPE_P2P_CLIENT:
-               if (!wdev->current_bss)
-                       return -ENOLINK;
-               break;
+               /* for MLO, require driver validation of the link ID */
+               if (wdev->connected)
+                       return 0;
+               return -ENOLINK;
        case NL80211_IFTYPE_UNSPECIFIED:
        case NL80211_IFTYPE_OCB:
        case NL80211_IFTYPE_MONITOR:
 
 static int __nl80211_set_channel(struct cfg80211_registered_device *rdev,
                                 struct net_device *dev,
-                                struct genl_info *info)
+                                struct genl_info *info,
+                                int _link_id)
 {
        struct cfg80211_chan_def chandef;
        int result;
        enum nl80211_iftype iftype = NL80211_IFTYPE_MONITOR;
        struct wireless_dev *wdev = NULL;
+       int link_id = _link_id;
 
        if (dev)
                wdev = dev->ieee80211_ptr;
        if (wdev)
                iftype = wdev->iftype;
 
+       if (link_id < 0) {
+               if (wdev && wdev->valid_links)
+                       return -EINVAL;
+               link_id = 0;
+       }
+
        result = nl80211_parse_chandef(rdev, info, &chandef);
        if (result)
                return result;
        case NL80211_IFTYPE_AP:
        case NL80211_IFTYPE_P2P_GO:
                if (!cfg80211_reg_can_beacon_relax(&rdev->wiphy, &chandef,
-                                                  iftype)) {
-                       result = -EINVAL;
-                       break;
-               }
-               if (wdev->beacon_interval) {
+                                                  iftype))
+                       return -EINVAL;
+               if (wdev->links[link_id].ap.beacon_interval) {
+                       struct ieee80211_channel *cur_chan;
+
                        if (!dev || !rdev->ops->set_ap_chanwidth ||
                            !(rdev->wiphy.features &
-                             NL80211_FEATURE_AP_MODE_CHAN_WIDTH_CHANGE)) {
-                               result = -EBUSY;
-                               break;
-                       }
+                             NL80211_FEATURE_AP_MODE_CHAN_WIDTH_CHANGE))
+                               return -EBUSY;
 
                        /* Only allow dynamic channel width changes */
-                       if (chandef.chan != wdev->preset_chandef.chan) {
-                               result = -EBUSY;
-                               break;
-                       }
-                       result = rdev_set_ap_chanwidth(rdev, dev, &chandef);
+                       cur_chan = wdev->links[link_id].ap.chandef.chan;
+                       if (chandef.chan != cur_chan)
+                               return -EBUSY;
+
+                       result = rdev_set_ap_chanwidth(rdev, dev, link_id,
+                                                      &chandef);
                        if (result)
-                               break;
+                               return result;
+                       wdev->links[link_id].ap.chandef = chandef;
+               } else {
+                       wdev->u.ap.preset_chandef = chandef;
                }
-               wdev->preset_chandef = chandef;
-               result = 0;
-               break;
+               return 0;
        case NL80211_IFTYPE_MESH_POINT:
-               result = cfg80211_set_mesh_channel(rdev, wdev, &chandef);
-               break;
+               return cfg80211_set_mesh_channel(rdev, wdev, &chandef);
        case NL80211_IFTYPE_MONITOR:
-               result = cfg80211_set_monitor_channel(rdev, &chandef);
-               break;
+               return cfg80211_set_monitor_channel(rdev, &chandef);
        default:
-               result = -EINVAL;
+               break;
        }
 
-       return result;
+       return -EINVAL;
 }
 
 static int nl80211_set_channel(struct sk_buff *skb, struct genl_info *info)
 {
        struct cfg80211_registered_device *rdev = info->user_ptr[0];
+       int link_id = nl80211_link_id_or_invalid(info->attrs);
        struct net_device *netdev = info->user_ptr[1];
 
-       return __nl80211_set_channel(rdev, netdev, info);
+       return __nl80211_set_channel(rdev, netdev, info, link_id);
 }
 
 static int nl80211_set_wiphy(struct sk_buff *skb, struct genl_info *info)
                result = __nl80211_set_channel(
                        rdev,
                        nl80211_can_set_dev_channel(wdev) ? netdev : NULL,
-                       info);
+                       info, -1);
                if (result)
                        goto out;
        }
            nla_put_u8(msg, NL80211_ATTR_4ADDR, wdev->use_4addr))
                goto nla_put_failure;
 
-       if (rdev->ops->get_channel) {
-               int ret;
+       if (rdev->ops->get_channel && !wdev->valid_links) {
                struct cfg80211_chan_def chandef = {};
+               int ret;
 
-               ret = rdev_get_channel(rdev, wdev, &chandef);
-               if (ret == 0) {
-                       if (nl80211_send_chandef(msg, &chandef))
-                               goto nla_put_failure;
-               }
+               ret = rdev_get_channel(rdev, wdev, 0, &chandef);
+               if (ret == 0 && nl80211_send_chandef(msg, &chandef))
+                       goto nla_put_failure;
        }
 
        if (rdev->ops->get_tx_power) {
        switch (wdev->iftype) {
        case NL80211_IFTYPE_AP:
        case NL80211_IFTYPE_P2P_GO:
-               if (wdev->ssid_len &&
-                   nla_put(msg, NL80211_ATTR_SSID, wdev->ssid_len, wdev->ssid))
+               if (wdev->u.ap.ssid_len &&
+                   nla_put(msg, NL80211_ATTR_SSID, wdev->u.ap.ssid_len,
+                           wdev->u.ap.ssid))
                        goto nla_put_failure_locked;
                break;
        case NL80211_IFTYPE_STATION:
        case NL80211_IFTYPE_P2P_CLIENT:
-       case NL80211_IFTYPE_ADHOC: {
-               const struct element *ssid_elem;
-
-               if (!wdev->current_bss)
-                       break;
-               rcu_read_lock();
-               ssid_elem = ieee80211_bss_get_elem(&wdev->current_bss->pub,
-                                                  WLAN_EID_SSID);
-               if (ssid_elem &&
-                   nla_put(msg, NL80211_ATTR_SSID, ssid_elem->datalen,
-                           ssid_elem->data))
-                       goto nla_put_failure_rcu_locked;
-               rcu_read_unlock();
+               if (wdev->u.client.ssid_len &&
+                   nla_put(msg, NL80211_ATTR_SSID, wdev->u.client.ssid_len,
+                           wdev->u.client.ssid))
+                       goto nla_put_failure_locked;
+               break;
+       case NL80211_IFTYPE_ADHOC:
+               if (wdev->u.ibss.ssid_len &&
+                   nla_put(msg, NL80211_ATTR_SSID, wdev->u.ibss.ssid_len,
+                           wdev->u.ibss.ssid))
+                       goto nla_put_failure_locked;
                break;
-               }
        default:
                /* nothing */
                break;
        genlmsg_end(msg, hdr);
        return 0;
 
- nla_put_failure_rcu_locked:
-       rcu_read_unlock();
  nla_put_failure_locked:
        wdev_unlock(wdev);
  nla_put_failure:
                wdev_lock(wdev);
                BUILD_BUG_ON(IEEE80211_MAX_SSID_LEN !=
                             IEEE80211_MAX_MESH_ID_LEN);
-               wdev->mesh_id_up_len =
+               wdev->u.mesh.id_up_len =
                        nla_len(info->attrs[NL80211_ATTR_MESH_ID]);
-               memcpy(wdev->ssid, nla_data(info->attrs[NL80211_ATTR_MESH_ID]),
-                      wdev->mesh_id_up_len);
+               memcpy(wdev->u.mesh.id,
+                      nla_data(info->attrs[NL80211_ATTR_MESH_ID]),
+                      wdev->u.mesh.id_up_len);
                wdev_unlock(wdev);
        }
 
                wdev_lock(wdev);
                BUILD_BUG_ON(IEEE80211_MAX_SSID_LEN !=
                             IEEE80211_MAX_MESH_ID_LEN);
-               wdev->mesh_id_up_len =
+               wdev->u.mesh.id_up_len =
                        nla_len(info->attrs[NL80211_ATTR_MESH_ID]);
-               memcpy(wdev->ssid, nla_data(info->attrs[NL80211_ATTR_MESH_ID]),
-                      wdev->mesh_id_up_len);
+               memcpy(wdev->u.mesh.id,
+                      nla_data(info->attrs[NL80211_ATTR_MESH_ID]),
+                      wdev->u.mesh.id_up_len);
                wdev_unlock(wdev);
                break;
        case NL80211_IFTYPE_NAN:
            dev->ieee80211_ptr->iftype != NL80211_IFTYPE_P2P_GO)
                return -EOPNOTSUPP;
 
-       if (!dev->ieee80211_ptr->beacon_interval)
+       if (!dev->ieee80211_ptr->links[0].ap.beacon_interval)
                return -EINVAL;
 
        acl = parse_acl_data(&rdev->wiphy, info);
        }
 }
 
-static u16 he_get_txmcsmap(struct genl_info *info,
+static u16 he_get_txmcsmap(struct genl_info *info, unsigned int link_id,
                           const struct ieee80211_sta_he_cap *he_cap)
 {
        struct net_device *dev = info->user_ptr[1];
        struct wireless_dev *wdev = dev->ieee80211_ptr;
-       __le16  tx_mcs;
+       struct cfg80211_chan_def *chandef;
+       __le16 tx_mcs;
 
-       switch (wdev->chandef.width) {
+       chandef = wdev_chandef(wdev, link_id);
+       if (!chandef) {
+               /*
+                * This is probably broken, but we never maintained
+                * a chandef in these cases, so it always was.
+                */
+               return le16_to_cpu(he_cap->he_mcs_nss_supp.tx_mcs_80);
+       }
+
+       switch (chandef->width) {
        case NL80211_CHAN_WIDTH_80P80:
                tx_mcs = he_cap->he_mcs_nss_supp.tx_mcs_80p80;
                break;
                tx_mcs = he_cap->he_mcs_nss_supp.tx_mcs_80;
                break;
        }
+
        return le16_to_cpu(tx_mcs);
 }
 
                            struct wireless_dev *wdev,
                            struct ieee80211_supported_band *sband,
                            struct nl80211_txrate_he *txrate,
-                           u16 mcs[NL80211_HE_NSS_MAX])
+                           u16 mcs[NL80211_HE_NSS_MAX],
+                           unsigned int link_id)
 {
        const struct ieee80211_sta_he_cap *he_cap;
        u16 tx_mcs_mask[NL80211_HE_NSS_MAX] = {};
 
        memset(mcs, 0, sizeof(u16) * NL80211_HE_NSS_MAX);
 
-       tx_mcs_map = he_get_txmcsmap(info, he_cap);
+       tx_mcs_map = he_get_txmcsmap(info, link_id, he_cap);
 
        /* Build he_mcs_mask from HE capabilities */
        he_build_mcs_mask(tx_mcs_map, tx_mcs_mask);
                                         enum nl80211_attrs attr,
                                         struct cfg80211_bitrate_mask *mask,
                                         struct net_device *dev,
-                                        bool default_all_enabled)
+                                        bool default_all_enabled,
+                                        unsigned int link_id)
 {
        struct nlattr *tb[NL80211_TXRATE_MAX + 1];
        struct cfg80211_registered_device *rdev = info->user_ptr[0];
                if (!he_cap)
                        continue;
 
-               he_tx_mcs_map = he_get_txmcsmap(info, he_cap);
+               he_tx_mcs_map = he_get_txmcsmap(info, link_id, he_cap);
                he_build_mcs_mask(he_tx_mcs_map, mask->control[i].he_mcs);
 
                mask->control[i].he_gi = 0xFF;
                if (tb[NL80211_TXRATE_HE] &&
                    !he_set_mcs_mask(info, wdev, sband,
                                     nla_data(tb[NL80211_TXRATE_HE]),
-                                    mask->control[band].he_mcs))
+                                    mask->control[band].he_mcs,
+                                    link_id))
                        return -EINVAL;
 
                if (tb[NL80211_TXRATE_HE_GI])
 
        memset(bcn, 0, sizeof(*bcn));
 
+       bcn->link_id = nl80211_link_id(attrs);
+
        if (attrs[NL80211_ATTR_BEACON_HEAD]) {
                bcn->head = nla_data(attrs[NL80211_ATTR_BEACON_HEAD]);
                bcn->head_len = nla_len(attrs[NL80211_ATTR_BEACON_HEAD]);
                                   struct cfg80211_ap_settings *params)
 {
        struct wireless_dev *wdev;
-       bool ret = false;
 
        list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
                if (wdev->iftype != NL80211_IFTYPE_AP &&
                    wdev->iftype != NL80211_IFTYPE_P2P_GO)
                        continue;
 
-               if (!wdev->preset_chandef.chan)
+               if (!wdev->u.ap.preset_chandef.chan)
                        continue;
 
-               params->chandef = wdev->preset_chandef;
-               ret = true;
-               break;
+               params->chandef = wdev->u.ap.preset_chandef;
+               return true;
        }
 
-       return ret;
+       return false;
 }
 
 static bool nl80211_valid_auth_type(struct cfg80211_registered_device *rdev,
 static int nl80211_start_ap(struct sk_buff *skb, struct genl_info *info)
 {
        struct cfg80211_registered_device *rdev = info->user_ptr[0];
+       unsigned int link_id = nl80211_link_id(info->attrs);
        struct net_device *dev = info->user_ptr[1];
        struct wireless_dev *wdev = dev->ieee80211_ptr;
        struct cfg80211_ap_settings *params;
        if (!rdev->ops->start_ap)
                return -EOPNOTSUPP;
 
-       if (wdev->beacon_interval)
+       if (wdev->links[link_id].ap.beacon_interval)
                return -EALREADY;
 
        /* these are required for START_AP */
                        err = -EINVAL;
                        goto out;
                }
+
+               if (wdev->u.ap.ssid_len &&
+                   (wdev->u.ap.ssid_len != params->ssid_len ||
+                    memcmp(wdev->u.ap.ssid, params->ssid, params->ssid_len))) {
+                       /* require identical SSID for MLO */
+                       err = -EINVAL;
+                       goto out;
+               }
+       } else if (wdev->valid_links) {
+               /* require SSID for MLO */
+               err = -EINVAL;
+               goto out;
        }
 
        if (info->attrs[NL80211_ATTR_HIDDEN_SSID])
                err = nl80211_parse_chandef(rdev, info, ¶ms->chandef);
                if (err)
                        goto out;
-       } else if (wdev->preset_chandef.chan) {
-               params->chandef = wdev->preset_chandef;
+       } else if (wdev->valid_links) {
+               /* with MLD need to specify the channel configuration */
+               err = -EINVAL;
+               goto out;
+       } else if (wdev->u.ap.preset_chandef.chan) {
+               params->chandef = wdev->u.ap.preset_chandef;
        } else if (!nl80211_get_ap_channel(rdev, params)) {
                err = -EINVAL;
                goto out;
                err = nl80211_parse_tx_bitrate_mask(info, info->attrs,
                                                    NL80211_ATTR_TX_RATES,
                                                    ¶ms->beacon_rate,
-                                                   dev, false);
+                                                   dev, false, link_id);
                if (err)
                        goto out;
 
                params->flags |= NL80211_AP_SETTINGS_EXTERNAL_AUTH_SUPPORT;
 
        wdev_lock(wdev);
+       if (wdev->conn_owner_nlportid &&
+           info->attrs[NL80211_ATTR_SOCKET_OWNER] &&
+           wdev->conn_owner_nlportid != info->snd_portid) {
+               err = -EINVAL;
+               goto out_unlock;
+       }
+
+       /* FIXME: validate MLO/link-id against driver capabilities */
+
        err = rdev_start_ap(rdev, dev, params);
        if (!err) {
-               wdev->preset_chandef = params->chandef;
-               wdev->beacon_interval = params->beacon_interval;
-               wdev->chandef = params->chandef;
-               wdev->ssid_len = params->ssid_len;
-               memcpy(wdev->ssid, params->ssid, wdev->ssid_len);
+               wdev->links[link_id].ap.beacon_interval = params->beacon_interval;
+               wdev->links[link_id].ap.chandef = params->chandef;
+               wdev->u.ap.ssid_len = params->ssid_len;
+               memcpy(wdev->u.ap.ssid, params->ssid,
+                      params->ssid_len);
 
                if (info->attrs[NL80211_ATTR_SOCKET_OWNER])
                        wdev->conn_owner_nlportid = info->snd_portid;
        }
+out_unlock:
        wdev_unlock(wdev);
-
 out:
        kfree(params->acl);
        kfree(params->beacon.mbssid_ies);
 static int nl80211_set_beacon(struct sk_buff *skb, struct genl_info *info)
 {
        struct cfg80211_registered_device *rdev = info->user_ptr[0];
+       unsigned int link_id = nl80211_link_id(info->attrs);
        struct net_device *dev = info->user_ptr[1];
        struct wireless_dev *wdev = dev->ieee80211_ptr;
        struct cfg80211_beacon_data params;
        if (!rdev->ops->change_beacon)
                return -EOPNOTSUPP;
 
-       if (!wdev->beacon_interval)
+       if (!wdev->links[link_id].ap.beacon_interval)
                return -EINVAL;
 
        err = nl80211_parse_beacon(rdev, info->attrs, ¶ms);
 static int nl80211_stop_ap(struct sk_buff *skb, struct genl_info *info)
 {
        struct cfg80211_registered_device *rdev = info->user_ptr[0];
+       unsigned int link_id = nl80211_link_id(info->attrs);
        struct net_device *dev = info->user_ptr[1];
 
-       return cfg80211_stop_ap(rdev, dev, false);
+       return cfg80211_stop_ap(rdev, dev, link_id, false);
 }
 
 static const struct nla_policy sta_flags_policy[NL80211_STA_FLAG_MAX + 1] = {
 
        wdev_lock(wdev);
        /* If not connected, get default parameters */
-       if (!wdev->mesh_id_len)
+       if (!wdev->u.mesh.id_len)
                memcpy(&cur_params, &default_mesh_config, sizeof(cur_params));
        else
                err = rdev_get_mesh_config(rdev, dev, &cur_params);
                return err;
 
        wdev_lock(wdev);
-       if (!wdev->mesh_id_len)
+       if (!wdev->u.mesh.id_len)
                err = -ENOLINK;
 
        if (!err)
        return 0;
 }
 
-static bool cfg80211_off_channel_oper_allowed(struct wireless_dev *wdev)
+static bool cfg80211_off_channel_oper_allowed(struct wireless_dev *wdev,
+                                             struct ieee80211_channel *chan)
 {
+       unsigned int link_id;
+       bool all_ok = true;
+
        ASSERT_WDEV_LOCK(wdev);
 
        if (!cfg80211_beaconing_iface_active(wdev))
                return true;
 
-       if (!(wdev->chandef.chan->flags & IEEE80211_CHAN_RADAR))
+       /*
+        * FIXME: check if we have a free HW resource/link for chan
+        *
+        * This, as well as the FIXME below, requires knowing the link
+        * capabilities of the hardware.
+        */
+
+       /* we cannot leave radar channels */
+       for_each_valid_link(wdev, link_id) {
+               struct cfg80211_chan_def *chandef;
+
+               chandef = wdev_chandef(wdev, link_id);
+               if (!chandef)
+                       continue;
+
+               /*
+                * FIXME: don't require all_ok, but rather check only the
+                *        correct HW resource/link onto which 'chan' falls,
+                *        as only that link leaves the channel for doing
+                *        the off-channel operation.
+                */
+
+               if (chandef->chan->flags & IEEE80211_CHAN_RADAR)
+                       all_ok = false;
+       }
+
+       if (all_ok)
                return true;
 
        return regulatory_pre_cac_allowed(wdev->wiphy);
                int err;
 
                if (!(wiphy->features & randomness_flag) ||
-                   (wdev && wdev->current_bss))
+                   (wdev && wdev->connected))
                        return -EOPNOTSUPP;
 
                err = nl80211_parse_random_mac(attrs, mac_addr, mac_addr_mask);
        request->n_channels = i;
 
        wdev_lock(wdev);
-       if (!cfg80211_off_channel_oper_allowed(wdev)) {
-               struct ieee80211_channel *chan;
+       for (i = 0; i < request->n_channels; i++) {
+               struct ieee80211_channel *chan = request->channels[i];
 
-               if (request->n_channels != 1) {
-                       wdev_unlock(wdev);
-                       err = -EBUSY;
-                       goto out_free;
-               }
+               /* if we can go off-channel to the target channel we're good */
+               if (cfg80211_off_channel_oper_allowed(wdev, chan))
+                       continue;
 
-               chan = request->channels[0];
-               if (chan->center_freq != wdev->chandef.chan->center_freq) {
+               if (!cfg80211_wdev_on_sub_chan(wdev, chan, true)) {
                        wdev_unlock(wdev);
                        err = -EBUSY;
                        goto out_free;
 
        err = rdev_start_radar_detection(rdev, dev, &chandef, cac_time_ms);
        if (!err) {
-               wdev->chandef = chandef;
+               wdev->links[0].ap.chandef = chandef;
                wdev->cac_started = true;
                wdev->cac_start_time = jiffies;
                wdev->cac_time_ms = cac_time_ms;
 static int nl80211_channel_switch(struct sk_buff *skb, struct genl_info *info)
 {
        struct cfg80211_registered_device *rdev = info->user_ptr[0];
+       unsigned int link_id = nl80211_link_id(info->attrs);
        struct net_device *dev = info->user_ptr[1];
        struct wireless_dev *wdev = dev->ieee80211_ptr;
        struct cfg80211_csa_settings params;
                need_handle_dfs_flag = false;
 
                /* useless if AP is not running */
-               if (!wdev->beacon_interval)
+               if (!wdev->links[link_id].ap.beacon_interval)
                        return -ENOTCONN;
                break;
        case NL80211_IFTYPE_ADHOC:
-               if (!wdev->ssid_len)
+               if (!wdev->u.ibss.ssid_len)
                        return -ENOTCONN;
                break;
        case NL80211_IFTYPE_MESH_POINT:
-               if (!wdev->mesh_id_len)
+               if (!wdev->u.mesh.id_len)
                        return -ENOTCONN;
                break;
        default:
 {
        struct cfg80211_bss *res = &intbss->pub;
        const struct cfg80211_bss_ies *ies;
+       unsigned int link_id;
        void *hdr;
        struct nlattr *bss;
 
        switch (wdev->iftype) {
        case NL80211_IFTYPE_P2P_CLIENT:
        case NL80211_IFTYPE_STATION:
-               if (intbss == wdev->current_bss &&
-                   nla_put_u32(msg, NL80211_BSS_STATUS,
-                               NL80211_BSS_STATUS_ASSOCIATED))
-                       goto nla_put_failure;
+               for_each_valid_link(wdev, link_id) {
+                       if (intbss == wdev->links[link_id].client.current_bss &&
+                           nla_put_u32(msg, NL80211_BSS_STATUS,
+                                       NL80211_BSS_STATUS_ASSOCIATED))
+                               goto nla_put_failure;
+               }
                break;
        case NL80211_IFTYPE_ADHOC:
-               if (intbss == wdev->current_bss &&
+               if (intbss == wdev->u.ibss.current_bss &&
                    nla_put_u32(msg, NL80211_BSS_STATUS,
                                NL80211_BSS_STATUS_IBSS_JOINED))
                        goto nla_put_failure;
        }
 
        wdev_lock(dev->ieee80211_ptr);
-       if (!wdev->current_bss)
+       if (!wdev->connected)
                ret = -ENOLINK;
        else
                ret = rdev_update_connect_params(rdev, dev, &connect, changed);
                                     struct genl_info *info)
 {
        struct cfg80211_registered_device *rdev = info->user_ptr[0];
+       unsigned int link_id = nl80211_link_id(info->attrs);
        struct wireless_dev *wdev = info->user_ptr[1];
        struct cfg80211_chan_def chandef;
-       const struct cfg80211_chan_def *compat_chandef;
        struct sk_buff *msg;
        void *hdr;
        u64 cookie;
                return err;
 
        wdev_lock(wdev);
-       if (!cfg80211_off_channel_oper_allowed(wdev) &&
-           !cfg80211_chandef_identical(&wdev->chandef, &chandef)) {
-               compat_chandef = cfg80211_chandef_compatible(&wdev->chandef,
-                                                            &chandef);
+       if (!cfg80211_off_channel_oper_allowed(wdev, chandef.chan)) {
+               const struct cfg80211_chan_def *oper_chandef, *compat_chandef;
+
+               oper_chandef = wdev_chandef(wdev, link_id);
+
+               if (WARN_ON(!oper_chandef)) {
+                       /* cannot happen since we must beacon to get here */
+                       WARN_ON(1);
+                       wdev_unlock(wdev);
+                       return -EBUSY;
+               }
+
+               /* note: returns first one if identical chandefs */
+               compat_chandef = cfg80211_chandef_compatible(&chandef,
+                                                            oper_chandef);
+
                if (compat_chandef != &chandef) {
                        wdev_unlock(wdev);
                        return -EBUSY;
                                       struct genl_info *info)
 {
        struct cfg80211_bitrate_mask mask;
+       unsigned int link_id = nl80211_link_id(info->attrs);
        struct cfg80211_registered_device *rdev = info->user_ptr[0];
        struct net_device *dev = info->user_ptr[1];
        struct wireless_dev *wdev = dev->ieee80211_ptr;
        wdev_lock(wdev);
        err = nl80211_parse_tx_bitrate_mask(info, info->attrs,
                                            NL80211_ATTR_TX_RATES, &mask,
-                                           dev, true);
+                                           dev, true, link_id);
        if (err)
                goto out;
 
-       err = rdev_set_bitrate_mask(rdev, dev, NULL, &mask);
+       err = rdev_set_bitrate_mask(rdev, dev, link_id, NULL, &mask);
 out:
        wdev_unlock(wdev);
        return err;
                return -EINVAL;
 
        wdev_lock(wdev);
-       if (params.offchan && !cfg80211_off_channel_oper_allowed(wdev)) {
+       if (params.offchan &&
+           !cfg80211_off_channel_oper_allowed(wdev, chandef.chan)) {
                wdev_unlock(wdev);
                return -EBUSY;
        }
         * connection is established and enough beacons received to calculate
         * the average.
         */
-       if (!wdev->cqm_config->last_rssi_event_value && wdev->current_bss &&
+       if (!wdev->cqm_config->last_rssi_event_value &&
+           wdev->links[0].client.current_bss &&
            rdev->ops->get_station) {
                struct station_info sinfo = {};
                u8 *mac_addr;
 
-               mac_addr = wdev->current_bss->pub.bssid;
+               mac_addr = wdev->links[0].client.current_bss->pub.bssid;
 
                err = rdev_get_station(rdev, dev, mac_addr, &sinfo);
                if (err)
                err = nl80211_parse_tx_bitrate_mask(info, info->attrs,
                                                    NL80211_ATTR_TX_RATES,
                                                    &setup.beacon_rate,
-                                                   dev, false);
+                                                   dev, false, 0);
                if (err)
                        return err;
 
                rekey_data.akm = nla_get_u32(tb[NL80211_REKEY_DATA_AKM]);
 
        wdev_lock(wdev);
-       if (!wdev->current_bss) {
+       if (!wdev->connected) {
                err = -ENOTCONN;
                goto out;
        }
        switch (wdev->iftype) {
        case NL80211_IFTYPE_STATION:
        case NL80211_IFTYPE_P2P_CLIENT:
-               if (wdev->current_bss)
+               if (wdev->connected)
                        break;
                err = -ENOTCONN;
                goto out;
                return -EINVAL;
 
        wdev_lock(wdev);
-       if (!wdev->current_bss) {
+       if (!wdev->connected) {
                ret = -ENOTCONN;
                goto out;
        }
 
        pmk_conf.aa = nla_data(info->attrs[NL80211_ATTR_MAC]);
-       if (memcmp(pmk_conf.aa, wdev->current_bss->pub.bssid, ETH_ALEN)) {
+       if (memcmp(pmk_conf.aa, wdev->u.client.connected_addr, ETH_ALEN)) {
                ret = -EINVAL;
                goto out;
        }
        case NL80211_IFTYPE_MESH_POINT:
                break;
        case NL80211_IFTYPE_ADHOC:
+               if (wdev->u.ibss.current_bss)
+                       break;
+               err = -ENOTCONN;
+               goto out;
        case NL80211_IFTYPE_STATION:
        case NL80211_IFTYPE_P2P_CLIENT:
-               if (wdev->current_bss)
+               if (wdev->connected)
                        break;
                err = -ENOTCONN;
                goto out;
        struct net_device *dev = info->user_ptr[1];
        struct wireless_dev *wdev = dev->ieee80211_ptr;
        struct cfg80211_ftm_responder_stats ftm_stats = {};
+       unsigned int link_id = nl80211_link_id(info->attrs);
        struct sk_buff *msg;
        void *hdr;
        struct nlattr *ftm_stats_attr;
        int err;
 
-       if (wdev->iftype != NL80211_IFTYPE_AP || !wdev->beacon_interval)
+       if (wdev->iftype != NL80211_IFTYPE_AP ||
+           !wdev->links[link_id].ap.beacon_interval)
                return -EOPNOTSUPP;
 
        err = rdev_get_ftm_responder_stats(rdev, dev, &ftm_stats);
 static int parse_tid_conf(struct cfg80211_registered_device *rdev,
                          struct nlattr *attrs[], struct net_device *dev,
                          struct cfg80211_tid_cfg *tid_conf,
-                         struct genl_info *info, const u8 *peer)
+                         struct genl_info *info, const u8 *peer,
+                         unsigned int link_id)
 {
        struct netlink_ext_ack *extack = info->extack;
        u64 mask;
                        attr = NL80211_TID_CONFIG_ATTR_TX_RATE;
                        err = nl80211_parse_tx_bitrate_mask(info, attrs, attr,
                                                    &tid_conf->txrate_mask, dev,
-                                                   true);
+                                                   true, link_id);
                        if (err)
                                return err;
 
 {
        struct cfg80211_registered_device *rdev = info->user_ptr[0];
        struct nlattr *attrs[NL80211_TID_CONFIG_ATTR_MAX + 1];
+       unsigned int link_id = nl80211_link_id(info->attrs);
        struct net_device *dev = info->user_ptr[1];
        struct cfg80211_tid_config *tid_config;
        struct nlattr *tid;
 
                ret = parse_tid_conf(rdev, attrs, dev,
                                     &tid_config->tid_conf[conf_idx],
-                                    info, tid_config->peer);
+                                    info, tid_config->peer, link_id);
                if (ret)
                        goto bad_tid_conf;
 
        return rdev_set_fils_aad(rdev, dev, &fils_aad);
 }
 
+static int nl80211_add_link(struct sk_buff *skb, struct genl_info *info)
+{
+       unsigned int link_id = nl80211_link_id(info->attrs);
+       struct net_device *dev = info->user_ptr[1];
+       struct wireless_dev *wdev = dev->ieee80211_ptr;
+
+       if (!(wdev->wiphy->flags & WIPHY_FLAG_SUPPORTS_MLO))
+               return -EINVAL;
+
+       switch (wdev->iftype) {
+       case NL80211_IFTYPE_AP:
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       if (!info->attrs[NL80211_ATTR_MAC] ||
+           !is_valid_ether_addr(nla_data(info->attrs[NL80211_ATTR_MAC])))
+               return -EINVAL;
+
+       wdev_lock(wdev);
+       wdev->valid_links |= BIT(link_id);
+       ether_addr_copy(wdev->links[link_id].addr,
+                       nla_data(info->attrs[NL80211_ATTR_MAC]));
+       wdev_unlock(wdev);
+
+       return 0;
+}
+
+static int nl80211_remove_link(struct sk_buff *skb, struct genl_info *info)
+{
+       unsigned int link_id = nl80211_link_id(info->attrs);
+       struct net_device *dev = info->user_ptr[1];
+       struct wireless_dev *wdev = dev->ieee80211_ptr;
+
+       /* cannot remove if there's no link */
+       if (!info->attrs[NL80211_ATTR_MLO_LINK_ID])
+               return -EINVAL;
+
+       switch (wdev->iftype) {
+       case NL80211_IFTYPE_AP:
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       /* FIXME: stop the link operations first */
+
+       wdev_lock(wdev);
+       wdev->valid_links &= ~BIT(link_id);
+       eth_zero_addr(wdev->links[link_id].addr);
+       wdev_unlock(wdev);
+
+       return 0;
+}
+
 #define NL80211_FLAG_NEED_WIPHY                0x01
 #define NL80211_FLAG_NEED_NETDEV       0x02
 #define NL80211_FLAG_NEED_RTNL         0x04
                                         NL80211_FLAG_CHECK_NETDEV_UP)
 #define NL80211_FLAG_CLEAR_SKB         0x20
 #define NL80211_FLAG_NO_WIPHY_MTX      0x40
+#define NL80211_FLAG_MLO_VALID_LINK_ID 0x80
+#define NL80211_FLAG_MLO_UNSUPPORTED   0x100
 
 #define INTERNAL_FLAG_SELECTORS(__sel)                 \
        SELECTOR(__sel, NONE, 0) /* must be first */    \
                 NL80211_FLAG_NEED_WDEV)                \
        SELECTOR(__sel, NETDEV,                         \
                 NL80211_FLAG_NEED_NETDEV)              \
+       SELECTOR(__sel, NETDEV_LINK,                    \
+                NL80211_FLAG_NEED_NETDEV |             \
+                NL80211_FLAG_MLO_VALID_LINK_ID)        \
+       SELECTOR(__sel, NETDEV_NO_MLO,                  \
+                NL80211_FLAG_NEED_NETDEV |             \
+                NL80211_FLAG_MLO_UNSUPPORTED)  \
        SELECTOR(__sel, WIPHY_RTNL,                     \
                 NL80211_FLAG_NEED_WIPHY |              \
                 NL80211_FLAG_NEED_RTNL)                \
                 NL80211_FLAG_NEED_RTNL)                \
        SELECTOR(__sel, NETDEV_UP,                      \
                 NL80211_FLAG_NEED_NETDEV_UP)           \
+       SELECTOR(__sel, NETDEV_UP_LINK,                 \
+                NL80211_FLAG_NEED_NETDEV_UP |          \
+                NL80211_FLAG_MLO_VALID_LINK_ID)        \
+       SELECTOR(__sel, NETDEV_UP_NO_MLO,               \
+                NL80211_FLAG_NEED_NETDEV_UP |          \
+                NL80211_FLAG_MLO_UNSUPPORTED)          \
+       SELECTOR(__sel, NETDEV_UP_NO_MLO_CLEAR,         \
+                NL80211_FLAG_NEED_NETDEV_UP |          \
+                NL80211_FLAG_CLEAR_SKB |               \
+                NL80211_FLAG_MLO_UNSUPPORTED)          \
        SELECTOR(__sel, NETDEV_UP_NOTMX,                \
                 NL80211_FLAG_NEED_NETDEV_UP |          \
                 NL80211_FLAG_NO_WIPHY_MTX)             \
+       SELECTOR(__sel, NETDEV_UP_NOTMX_NOMLO,          \
+                NL80211_FLAG_NEED_NETDEV_UP |          \
+                NL80211_FLAG_NO_WIPHY_MTX |            \
+                NL80211_FLAG_MLO_UNSUPPORTED)          \
        SELECTOR(__sel, NETDEV_UP_CLEAR,                \
                 NL80211_FLAG_NEED_NETDEV_UP |          \
                 NL80211_FLAG_CLEAR_SKB)                \
        SELECTOR(__sel, WDEV_UP,                        \
                 NL80211_FLAG_NEED_WDEV_UP)             \
+       SELECTOR(__sel, WDEV_UP_LINK,                   \
+                NL80211_FLAG_NEED_WDEV_UP |            \
+                NL80211_FLAG_MLO_VALID_LINK_ID)        \
        SELECTOR(__sel, WDEV_UP_RTNL,                   \
                 NL80211_FLAG_NEED_WDEV_UP |            \
                 NL80211_FLAG_NEED_RTNL)                \
                            struct genl_info *info)
 {
        struct cfg80211_registered_device *rdev = NULL;
-       struct wireless_dev *wdev;
-       struct net_device *dev;
+       struct wireless_dev *wdev = NULL;
+       struct net_device *dev = NULL;
        u32 internal_flags;
+       int err;
 
        if (WARN_ON(ops->internal_flags >= ARRAY_SIZE(nl80211_internal_flags)))
                return -EINVAL;
        if (internal_flags & NL80211_FLAG_NEED_WIPHY) {
                rdev = cfg80211_get_dev_from_info(genl_info_net(info), info);
                if (IS_ERR(rdev)) {
-                       rtnl_unlock();
-                       return PTR_ERR(rdev);
+                       err = PTR_ERR(rdev);
+                       goto out_unlock;
                }
                info->user_ptr[0] = rdev;
        } else if (internal_flags & NL80211_FLAG_NEED_NETDEV ||
                wdev = __cfg80211_wdev_from_attrs(NULL, genl_info_net(info),
                                                  info->attrs);
                if (IS_ERR(wdev)) {
-                       rtnl_unlock();
-                       return PTR_ERR(wdev);
+                       err = PTR_ERR(wdev);
+                       goto out_unlock;
                }
 
                dev = wdev->netdev;
+               dev_hold(dev);
                rdev = wiphy_to_rdev(wdev->wiphy);
 
                if (internal_flags & NL80211_FLAG_NEED_NETDEV) {
                        if (!dev) {
-                               rtnl_unlock();
-                               return -EINVAL;
+                               err = -EINVAL;
+                               goto out_unlock;
                        }
 
                        info->user_ptr[1] = dev;
 
                if (internal_flags & NL80211_FLAG_CHECK_NETDEV_UP &&
                    !wdev_running(wdev)) {
-                       rtnl_unlock();
-                       return -ENETDOWN;
+                       err = -ENETDOWN;
+                       goto out_unlock;
                }
 
-               dev_hold(dev);
                info->user_ptr[0] = rdev;
        }
 
+       if (internal_flags & NL80211_FLAG_MLO_VALID_LINK_ID) {
+               struct nlattr *link_id = info->attrs[NL80211_ATTR_MLO_LINK_ID];
+
+               if (!wdev) {
+                       err = -EINVAL;
+                       goto out_unlock;
+               }
+
+               /* MLO -> require valid link ID */
+               if (wdev->valid_links &&
+                   (!link_id ||
+                    !(wdev->valid_links & BIT(nla_get_u16(link_id))))) {
+                       err = -EINVAL;
+                       goto out_unlock;
+               }
+
+               /* non-MLO -> no link ID attribute accepted */
+               if (!wdev->valid_links && link_id) {
+                       err = -EINVAL;
+                       goto out_unlock;
+               }
+       }
+
+       if (internal_flags & NL80211_FLAG_MLO_UNSUPPORTED) {
+               if (info->attrs[NL80211_ATTR_MLO_LINK_ID] ||
+                   (wdev && wdev->valid_links)) {
+                       err = -EINVAL;
+                       goto out_unlock;
+               }
+       }
+
        if (rdev && !(internal_flags & NL80211_FLAG_NO_WIPHY_MTX)) {
                wiphy_lock(&rdev->wiphy);
                /* we keep the mutex locked until post_doit */
                rtnl_unlock();
 
        return 0;
+out_unlock:
+       rtnl_unlock();
+       dev_put(dev);
+       return err;
 }
 
 static void nl80211_post_doit(const struct genl_ops *ops, struct sk_buff *skb,
                .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
                .doit = nl80211_set_key,
                .flags = GENL_UNS_ADMIN_PERM,
+               /* cannot use NL80211_FLAG_MLO_VALID_LINK_ID, depends on key */
                .internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV_UP |
                                         NL80211_FLAG_CLEAR_SKB),
        },
                .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
                .flags = GENL_UNS_ADMIN_PERM,
                .doit = nl80211_set_beacon,
-               .internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV_UP),
+               .internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV_UP |
+                                        NL80211_FLAG_MLO_VALID_LINK_ID),
        },
        {
                .cmd = NL80211_CMD_START_AP,
                .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
                .flags = GENL_UNS_ADMIN_PERM,
                .doit = nl80211_start_ap,
-               .internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV_UP),
+               .internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV_UP |
+                                        NL80211_FLAG_MLO_VALID_LINK_ID),
        },
        {
                .cmd = NL80211_CMD_STOP_AP,
                .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
                .flags = GENL_UNS_ADMIN_PERM,
                .doit = nl80211_stop_ap,
-               .internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV_UP),
+               .internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV_UP |
+                                        NL80211_FLAG_MLO_VALID_LINK_ID),
        },
        {
                .cmd = NL80211_CMD_GET_STATION,
                .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
                .doit = nl80211_remain_on_channel,
                .flags = GENL_UNS_ADMIN_PERM,
-               .internal_flags = IFLAGS(NL80211_FLAG_NEED_WDEV_UP),
+               /* FIXME: requiring a link ID here is probably not good */
+               .internal_flags = IFLAGS(NL80211_FLAG_NEED_WDEV_UP |
+                                        NL80211_FLAG_MLO_VALID_LINK_ID),
        },
        {
                .cmd = NL80211_CMD_CANCEL_REMAIN_ON_CHANNEL,
                .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
                .doit = nl80211_set_tx_bitrate_mask,
                .flags = GENL_UNS_ADMIN_PERM,
-               .internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV),
+               .internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV |
+                                        NL80211_FLAG_MLO_VALID_LINK_ID),
        },
        {
                .cmd = NL80211_CMD_REGISTER_FRAME,
                .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
                .doit = nl80211_set_channel,
                .flags = GENL_UNS_ADMIN_PERM,
-               .internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV),
+               .internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV |
+                                        NL80211_FLAG_MLO_VALID_LINK_ID),
        },
        {
                .cmd = NL80211_CMD_JOIN_MESH,
                .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
                .doit = nl80211_set_mac_acl,
                .flags = GENL_UNS_ADMIN_PERM,
-               .internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV),
+               .internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV |
+                                        NL80211_FLAG_MLO_UNSUPPORTED),
        },
        {
                .cmd = NL80211_CMD_RADAR_DETECT,
                .doit = nl80211_start_radar_detection,
                .flags = GENL_UNS_ADMIN_PERM,
                .internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV_UP |
-                                        NL80211_FLAG_NO_WIPHY_MTX),
+                                        NL80211_FLAG_NO_WIPHY_MTX |
+                                        NL80211_FLAG_MLO_UNSUPPORTED),
        },
        {
                .cmd = NL80211_CMD_GET_PROTOCOL_FEATURES,
                .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
                .doit = nl80211_channel_switch,
                .flags = GENL_UNS_ADMIN_PERM,
-               .internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV_UP),
+               .internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV_UP |
+                                        NL80211_FLAG_MLO_VALID_LINK_ID),
        },
        {
                .cmd = NL80211_CMD_VENDOR,
                .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
                .doit = nl80211_add_tx_ts,
                .flags = GENL_UNS_ADMIN_PERM,
-               .internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV_UP),
+               .internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV_UP |
+                                        NL80211_FLAG_MLO_UNSUPPORTED),
        },
        {
                .cmd = NL80211_CMD_DEL_TX_TS,
                .cmd = NL80211_CMD_GET_FTM_RESPONDER_STATS,
                .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
                .doit = nl80211_get_ftm_responder_stats,
-               .internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV),
+               .internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV |
+                                        NL80211_FLAG_MLO_VALID_LINK_ID),
        },
        {
                .cmd = NL80211_CMD_PEER_MEASUREMENT_START,
                .cmd = NL80211_CMD_SET_TID_CONFIG,
                .doit = nl80211_set_tid_config,
                .flags = GENL_UNS_ADMIN_PERM,
-               .internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV),
+               .internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV |
+                                        NL80211_FLAG_MLO_VALID_LINK_ID),
        },
        {
                .cmd = NL80211_CMD_SET_SAR_SPECS,
                .flags = GENL_UNS_ADMIN_PERM,
                .internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV_UP),
        },
+       {
+               .cmd = NL80211_CMD_ADD_LINK,
+               .doit = nl80211_add_link,
+               .flags = GENL_UNS_ADMIN_PERM,
+               .internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV_UP),
+       },
+       {
+               .cmd = NL80211_CMD_REMOVE_LINK,
+               .doit = nl80211_remove_link,
+               .flags = GENL_UNS_ADMIN_PERM,
+               .internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV_UP |
+                                        NL80211_FLAG_MLO_VALID_LINK_ID),
+       },
 };
 
 static struct genl_family nl80211_fam __ro_after_init = {
 }
 
 void cfg80211_ch_switch_notify(struct net_device *dev,
-                              struct cfg80211_chan_def *chandef)
+                              struct cfg80211_chan_def *chandef,
+                              unsigned int link_id)
 {
        struct wireless_dev *wdev = dev->ieee80211_ptr;
        struct wiphy *wiphy = wdev->wiphy;
        struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
 
        ASSERT_WDEV_LOCK(wdev);
+       WARN_INVALID_LINK_ID(wdev, link_id);
 
-       trace_cfg80211_ch_switch_notify(dev, chandef);
-
-       wdev->chandef = *chandef;
-       wdev->preset_chandef = *chandef;
+       trace_cfg80211_ch_switch_notify(dev, chandef, link_id);
 
-       if ((wdev->iftype == NL80211_IFTYPE_STATION ||
-            wdev->iftype == NL80211_IFTYPE_P2P_CLIENT) &&
-           !WARN_ON(!wdev->current_bss))
-               cfg80211_update_assoc_bss_entry(wdev, chandef->chan);
+       switch (wdev->iftype) {
+       case NL80211_IFTYPE_STATION:
+       case NL80211_IFTYPE_P2P_CLIENT:
+               if (!WARN_ON(!wdev->links[link_id].client.current_bss))
+                       cfg80211_update_assoc_bss_entry(wdev, link_id,
+                                                       chandef->chan);
+               break;
+       case NL80211_IFTYPE_MESH_POINT:
+               wdev->u.mesh.chandef = *chandef;
+               wdev->u.mesh.preset_chandef = *chandef;
+               break;
+       case NL80211_IFTYPE_AP:
+       case NL80211_IFTYPE_P2P_GO:
+               wdev->links[link_id].ap.chandef = *chandef;
+               break;
+       default:
+               WARN_ON(1);
+               break;
+       }
 
        cfg80211_sched_dfs_chan_update(rdev);
 
 
  *
  * Copyright: (c) 2014 Czech Technical University in Prague
  *            (c) 2014 Volkswagen Group Research
+ * Copyright (C) 2022 Intel Corporation
  * Author:    Rostislav Lisovy <rostislav.lisovy@fel.cvut.cz>
  * Funded by: Volkswagen Group Research
  */
 
        err = rdev_join_ocb(rdev, dev, setup);
        if (!err)
-               wdev->chandef = setup->chandef;
+               wdev->u.ocb.chandef = setup->chandef;
 
        return err;
 }
 
        err = rdev_leave_ocb(rdev, dev);
        if (!err)
-               memset(&wdev->chandef, 0, sizeof(wdev->chandef));
+               memset(&wdev->u.ocb.chandef, 0, sizeof(wdev->u.ocb.chandef));
 
        return err;
 }
 
 /* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Portions of this file
+ * Copyright(c) 2016-2017 Intel Deutschland GmbH
+ * Copyright (C) 2018, 2021-2022 Intel Corporation
+ */
 #ifndef __CFG80211_RDEV_OPS
 #define __CFG80211_RDEV_OPS
 
 }
 
 static inline int rdev_stop_ap(struct cfg80211_registered_device *rdev,
-                              struct net_device *dev)
+                              struct net_device *dev, unsigned int link_id)
 {
        int ret;
-       trace_rdev_stop_ap(&rdev->wiphy, dev);
-       ret = rdev->ops->stop_ap(&rdev->wiphy, dev);
+       trace_rdev_stop_ap(&rdev->wiphy, dev, link_id);
+       ret = rdev->ops->stop_ap(&rdev->wiphy, dev, link_id);
        trace_rdev_return_int(&rdev->wiphy, ret);
        return ret;
 }
 
 static inline int
 rdev_set_bitrate_mask(struct cfg80211_registered_device *rdev,
-                     struct net_device *dev, const u8 *peer,
+                     struct net_device *dev, unsigned int link_id,
+                     const u8 *peer,
                      const struct cfg80211_bitrate_mask *mask)
 {
        int ret;
-       trace_rdev_set_bitrate_mask(&rdev->wiphy, dev, peer, mask);
-       ret = rdev->ops->set_bitrate_mask(&rdev->wiphy, dev, peer, mask);
+       trace_rdev_set_bitrate_mask(&rdev->wiphy, dev, link_id, peer, mask);
+       ret = rdev->ops->set_bitrate_mask(&rdev->wiphy, dev, link_id,
+                                         peer, mask);
        trace_rdev_return_int(&rdev->wiphy, ret);
        return ret;
 }
 static inline int
 rdev_get_channel(struct cfg80211_registered_device *rdev,
                 struct wireless_dev *wdev,
+                unsigned int link_id,
                 struct cfg80211_chan_def *chandef)
 {
        int ret;
 
-       trace_rdev_get_channel(&rdev->wiphy, wdev);
-       ret = rdev->ops->get_channel(&rdev->wiphy, wdev, chandef);
+       trace_rdev_get_channel(&rdev->wiphy, wdev, link_id);
+       ret = rdev->ops->get_channel(&rdev->wiphy, wdev, link_id, chandef);
        trace_rdev_return_chandef(&rdev->wiphy, ret, chandef);
 
        return ret;
 
 static inline int
 rdev_set_ap_chanwidth(struct cfg80211_registered_device *rdev,
-                     struct net_device *dev, struct cfg80211_chan_def *chandef)
+                     struct net_device *dev,
+                     unsigned int link_id,
+                     struct cfg80211_chan_def *chandef)
 {
        int ret;
 
-       trace_rdev_set_ap_chanwidth(&rdev->wiphy, dev, chandef);
-       ret = rdev->ops->set_ap_chanwidth(&rdev->wiphy, dev, chandef);
+       trace_rdev_set_ap_chanwidth(&rdev->wiphy, dev, link_id, chandef);
+       ret = rdev->ops->set_ap_chanwidth(&rdev->wiphy, dev, link_id, chandef);
        trace_rdev_return_int(&rdev->wiphy, ret);
 
        return ret;
 
  * Copyright 2008-2011 Luis R. Rodriguez <mcgrof@qca.qualcomm.com>
  * Copyright 2013-2014  Intel Mobile Communications GmbH
  * Copyright      2017  Intel Deutschland GmbH
- * Copyright (C) 2018 - 2021 Intel Corporation
+ * Copyright (C) 2018 - 2022 Intel Corporation
  *
  * Permission to use, copy, modify, and/or distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
        struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
        enum nl80211_iftype iftype;
        bool ret;
+       int link;
 
        wdev_lock(wdev);
        iftype = wdev->iftype;
        if (!wdev->netdev || !netif_running(wdev->netdev))
                goto wdev_inactive_unlock;
 
-       switch (iftype) {
-       case NL80211_IFTYPE_AP:
-       case NL80211_IFTYPE_P2P_GO:
-       case NL80211_IFTYPE_MESH_POINT:
-               if (!wdev->beacon_interval)
-                       goto wdev_inactive_unlock;
-               chandef = wdev->chandef;
-               break;
-       case NL80211_IFTYPE_ADHOC:
-               if (!wdev->ssid_len)
-                       goto wdev_inactive_unlock;
-               chandef = wdev->chandef;
-               break;
-       case NL80211_IFTYPE_STATION:
-       case NL80211_IFTYPE_P2P_CLIENT:
-               if (!wdev->current_bss ||
-                   !wdev->current_bss->pub.channel)
-                       goto wdev_inactive_unlock;
-
-               if (!rdev->ops->get_channel ||
-                   rdev_get_channel(rdev, wdev, &chandef))
-                       cfg80211_chandef_create(&chandef,
-                                               wdev->current_bss->pub.channel,
-                                               NL80211_CHAN_NO_HT);
-               break;
-       case NL80211_IFTYPE_MONITOR:
-       case NL80211_IFTYPE_AP_VLAN:
-       case NL80211_IFTYPE_P2P_DEVICE:
-               /* no enforcement required */
-               break;
-       default:
-               /* others not implemented for now */
-               WARN_ON(1);
-               break;
-       }
+       for (link = 0; link < ARRAY_SIZE(wdev->links); link++) {
+               struct ieee80211_channel *chan;
 
-       wdev_unlock(wdev);
+               if (!wdev->valid_links && link > 0)
+                       break;
+               if (!(wdev->valid_links & BIT(link)))
+                       continue;
+               switch (iftype) {
+               case NL80211_IFTYPE_AP:
+               case NL80211_IFTYPE_P2P_GO:
+               case NL80211_IFTYPE_MESH_POINT:
+                       if (!wdev->u.mesh.beacon_interval)
+                               continue;
+                       chandef = wdev->u.mesh.chandef;
+                       break;
+               case NL80211_IFTYPE_ADHOC:
+                       if (!wdev->u.ibss.ssid_len)
+                               continue;
+                       chandef = wdev->u.ibss.chandef;
+                       break;
+               case NL80211_IFTYPE_STATION:
+               case NL80211_IFTYPE_P2P_CLIENT:
+                       /* Maybe we could consider disabling that link only? */
+                       if (!wdev->links[link].client.current_bss)
+                               continue;
 
-       switch (iftype) {
-       case NL80211_IFTYPE_AP:
-       case NL80211_IFTYPE_P2P_GO:
-       case NL80211_IFTYPE_ADHOC:
-       case NL80211_IFTYPE_MESH_POINT:
-               wiphy_lock(wiphy);
-               ret = cfg80211_reg_can_beacon_relax(wiphy, &chandef, iftype);
-               wiphy_unlock(wiphy);
+                       chan = wdev->links[link].client.current_bss->pub.channel;
+                       if (!chan)
+                               continue;
 
-               return ret;
-       case NL80211_IFTYPE_STATION:
-       case NL80211_IFTYPE_P2P_CLIENT:
-               return cfg80211_chandef_usable(wiphy, &chandef,
-                                              IEEE80211_CHAN_DISABLED);
-       default:
-               break;
+                       if (!rdev->ops->get_channel ||
+                           rdev_get_channel(rdev, wdev, link, &chandef))
+                               cfg80211_chandef_create(&chandef, chan,
+                                                       NL80211_CHAN_NO_HT);
+                       break;
+               case NL80211_IFTYPE_MONITOR:
+               case NL80211_IFTYPE_AP_VLAN:
+               case NL80211_IFTYPE_P2P_DEVICE:
+                       /* no enforcement required */
+                       break;
+               default:
+                       /* others not implemented for now */
+                       WARN_ON(1);
+                       break;
+               }
+
+               wdev_unlock(wdev);
+
+               switch (iftype) {
+               case NL80211_IFTYPE_AP:
+               case NL80211_IFTYPE_P2P_GO:
+               case NL80211_IFTYPE_ADHOC:
+               case NL80211_IFTYPE_MESH_POINT:
+                       wiphy_lock(wiphy);
+                       ret = cfg80211_reg_can_beacon_relax(wiphy, &chandef,
+                                                           iftype);
+                       wiphy_unlock(wiphy);
+
+                       if (!ret)
+                               return ret;
+                       break;
+               case NL80211_IFTYPE_STATION:
+               case NL80211_IFTYPE_P2P_CLIENT:
+                       ret = cfg80211_chandef_usable(wiphy, &chandef,
+                                                     IEEE80211_CHAN_DISABLED);
+                       if (!ret)
+                               return ret;
+                       break;
+               default:
+                       break;
+               }
+
+               wdev_lock(wdev);
        }
 
+       wdev_unlock(wdev);
+
        return true;
 
 wdev_inactive_unlock:
         * In both cases we should end the CAC on the wdev.
         */
        list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
-               if (wdev->cac_started &&
-                   !cfg80211_chandef_dfs_usable(&rdev->wiphy, &wdev->chandef))
+               struct cfg80211_chan_def *chandef;
+
+               if (!wdev->cac_started)
+                       continue;
+
+               /* FIXME: radar detection is tied to link 0 for now */
+               chandef = wdev_chandef(wdev, 0);
+               if (!chandef)
+                       continue;
+
+               if (!cfg80211_chandef_dfs_usable(&rdev->wiphy, chandef))
                        rdev_end_cac(rdev, wdev->netdev);
        }
 }
 
  * Copyright 2008 Johannes Berg <johannes@sipsolutions.net>
  * Copyright 2013-2014  Intel Mobile Communications GmbH
  * Copyright 2016      Intel Deutschland GmbH
- * Copyright (C) 2018-2021 Intel Corporation
+ * Copyright (C) 2018-2022 Intel Corporation
  */
 #include <linux/kernel.h>
 #include <linux/slab.h>
        spin_lock_bh(&rdev->bss_lock);
 
        list_for_each_entry(bss, &rdev->bss_list, list) {
-               if (!chandef || cfg80211_is_sub_chan(chandef, bss->pub.channel))
+               if (!chandef || cfg80211_is_sub_chan(chandef, bss->pub.channel,
+                                                    false))
                        iter(wiphy, &bss->pub, iter_data);
        }
 
 EXPORT_SYMBOL(cfg80211_bss_iter);
 
 void cfg80211_update_assoc_bss_entry(struct wireless_dev *wdev,
+                                    unsigned int link_id,
                                     struct ieee80211_channel *chan)
 {
        struct wiphy *wiphy = wdev->wiphy;
        struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
-       struct cfg80211_internal_bss *cbss = wdev->current_bss;
+       struct cfg80211_internal_bss *cbss = wdev->links[link_id].client.current_bss;
        struct cfg80211_internal_bss *new = NULL;
        struct cfg80211_internal_bss *bss;
        struct cfg80211_bss *nontrans_bss;
 
  * (for nl80211's connect() and wext)
  *
  * Copyright 2009      Johannes Berg <johannes@sipsolutions.net>
- * Copyright (C) 2009, 2020 Intel Corporation. All rights reserved.
+ * Copyright (C) 2009, 2020, 2022 Intel Corporation. All rights reserved.
  * Copyright 2017      Intel Deutschland GmbH
  */
 
        schedule_work(&rdev->conn_work);
 }
 
+static void cfg80211_wdev_release_bsses(struct wireless_dev *wdev)
+{
+       unsigned int link;
+
+       for_each_valid_link(wdev, link) {
+               if (!wdev->links[link].client.current_bss)
+                       continue;
+               cfg80211_unhold_bss(wdev->links[link].client.current_bss);
+               cfg80211_put_bss(wdev->wiphy,
+                                &wdev->links[link].client.current_bss->pub);
+               wdev->links[link].client.current_bss = NULL;
+       }
+}
+
 static int cfg80211_sme_get_conn_ies(struct wireless_dev *wdev,
                                     const u8 *ies, size_t ies_len,
                                     const u8 **out_ies, size_t *out_ies_len)
        if (!rdev->ops->auth || !rdev->ops->assoc)
                return -EOPNOTSUPP;
 
-       if (wdev->current_bss) {
-               cfg80211_unhold_bss(wdev->current_bss);
-               cfg80211_put_bss(wdev->wiphy, &wdev->current_bss->pub);
-               wdev->current_bss = NULL;
+       cfg80211_wdev_release_bsses(wdev);
 
+       if (wdev->connected) {
                cfg80211_sme_free(wdev);
+               wdev->connected = false;
        }
 
        if (wdev->conn)
                wdev->conn->auto_auth = false;
        }
 
-       wdev->conn->params.ssid = wdev->ssid;
-       wdev->conn->params.ssid_len = wdev->ssid_len;
+       wdev->conn->params.ssid = wdev->u.client.ssid;
+       wdev->conn->params.ssid_len = wdev->u.client.ssid_len;
 
        /* see if we have the bss already */
        bss = cfg80211_get_conn_bss(wdev);
        list_for_each_entry(rdev, &cfg80211_rdev_list, list) {
                list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
                        wdev_lock(wdev);
-                       if (wdev->conn || wdev->current_bss ||
+                       if (wdev->conn || wdev->connected ||
                            cfg80211_beaconing_iface_active(wdev))
                                is_all_idle = false;
                        wdev_unlock(wdev);
 
 DECLARE_WORK(cfg80211_disconnect_work, disconnect_work);
 
-
 /*
  * API calls for drivers implementing connect/disconnect and
  * SME event handling
        if (!cr->bss && (cr->status == WLAN_STATUS_SUCCESS)) {
                WARN_ON_ONCE(!wiphy_to_rdev(wdev->wiphy)->ops->connect);
                cr->bss = cfg80211_get_bss(wdev->wiphy, NULL, cr->bssid,
-                                          wdev->ssid, wdev->ssid_len,
+                                          wdev->u.client.ssid, wdev->u.client.ssid_len,
                                           wdev->conn_bss_type,
                                           IEEE80211_PRIVACY_ANY);
                if (cr->bss)
                        cfg80211_hold_bss(bss_from_pub(cr->bss));
        }
 
-       if (wdev->current_bss) {
-               cfg80211_unhold_bss(wdev->current_bss);
-               cfg80211_put_bss(wdev->wiphy, &wdev->current_bss->pub);
-               wdev->current_bss = NULL;
-       }
+       cfg80211_wdev_release_bsses(wdev);
 
        if (cr->status != WLAN_STATUS_SUCCESS) {
                kfree_sensitive(wdev->connect_keys);
                wdev->connect_keys = NULL;
-               wdev->ssid_len = 0;
+               wdev->u.client.ssid_len = 0;
                wdev->conn_owner_nlportid = 0;
                if (cr->bss) {
                        cfg80211_unhold_bss(bss_from_pub(cr->bss));
        if (WARN_ON(!cr->bss))
                return;
 
-       wdev->current_bss = bss_from_pub(cr->bss);
+       wdev->links[0].client.current_bss = bss_from_pub(cr->bss);
+       wdev->connected = true;
+       ether_addr_copy(wdev->u.client.connected_addr, cr->bss->bssid);
 
        if (!(wdev->wiphy->flags & WIPHY_FLAG_HAS_STATIC_WEP))
                cfg80211_upload_connect_keys(wdev);
 
                        found = cfg80211_get_bss(wdev->wiphy, NULL,
                                                 params->bss->bssid,
-                                                wdev->ssid, wdev->ssid_len,
+                                                wdev->u.client.ssid, wdev->u.client.ssid_len,
                                                 wdev->conn_bss_type,
                                                 IEEE80211_PRIVACY_ANY);
                        if (found) {
                    wdev->iftype != NL80211_IFTYPE_P2P_CLIENT))
                goto out;
 
-       if (WARN_ON(!wdev->current_bss))
+       if (WARN_ON(!wdev->connected))
                goto out;
 
-       cfg80211_unhold_bss(wdev->current_bss);
-       cfg80211_put_bss(wdev->wiphy, &wdev->current_bss->pub);
-       wdev->current_bss = NULL;
+       cfg80211_wdev_release_bsses(wdev);
 
        if (WARN_ON(!info->bss))
                return;
 
        cfg80211_hold_bss(bss_from_pub(info->bss));
-       wdev->current_bss = bss_from_pub(info->bss);
+       wdev->links[0].client.current_bss = bss_from_pub(info->bss);
+       ether_addr_copy(wdev->u.client.connected_addr, info->bss->bssid);
 
        wdev->unprot_beacon_reported = 0;
        nl80211_send_roamed(wiphy_to_rdev(wdev->wiphy),
 
        if (!info->bss) {
                info->bss = cfg80211_get_bss(wdev->wiphy, info->channel,
-                                            info->bssid, wdev->ssid,
-                                            wdev->ssid_len,
+                                            info->bssid, wdev->u.client.ssid,
+                                            wdev->u.client.ssid_len,
                                             wdev->conn_bss_type,
                                             IEEE80211_PRIVACY_ANY);
        }
        if (WARN_ON(wdev->iftype != NL80211_IFTYPE_STATION))
                return;
 
-       if (WARN_ON(!wdev->current_bss) ||
-           WARN_ON(!ether_addr_equal(wdev->current_bss->pub.bssid, bssid)))
+       if (WARN_ON(!wdev->connected) ||
+           WARN_ON(!ether_addr_equal(wdev->u.client.connected_addr, bssid)))
                return;
 
        nl80211_send_port_authorized(wiphy_to_rdev(wdev->wiphy), wdev->netdev,
                    wdev->iftype != NL80211_IFTYPE_P2P_CLIENT))
                return;
 
-       if (wdev->current_bss) {
-               cfg80211_unhold_bss(wdev->current_bss);
-               cfg80211_put_bss(wdev->wiphy, &wdev->current_bss->pub);
-       }
-
-       wdev->current_bss = NULL;
-       wdev->ssid_len = 0;
+       cfg80211_wdev_release_bsses(wdev);
+       wdev->connected = false;
+       wdev->u.client.ssid_len = 0;
        wdev->conn_owner_nlportid = 0;
        kfree_sensitive(wdev->connect_keys);
        wdev->connect_keys = NULL;
         * already connected, so reject a new SSID unless it's the
         * same (which is the case for re-association.)
         */
-       if (wdev->ssid_len &&
-           (wdev->ssid_len != connect->ssid_len ||
-            memcmp(wdev->ssid, connect->ssid, wdev->ssid_len)))
+       if (wdev->u.client.ssid_len &&
+           (wdev->u.client.ssid_len != connect->ssid_len ||
+            memcmp(wdev->u.client.ssid, connect->ssid, wdev->u.client.ssid_len)))
                return -EALREADY;
 
        /*
         * If connected, reject (re-)association unless prev_bssid
         * matches the current BSSID.
         */
-       if (wdev->current_bss) {
+       if (wdev->connected) {
                if (!prev_bssid)
                        return -EALREADY;
-               if (!ether_addr_equal(prev_bssid, wdev->current_bss->pub.bssid))
+               if (!ether_addr_equal(prev_bssid,
+                                     wdev->u.client.connected_addr))
                        return -ENOTCONN;
        }
 
        }
 
        wdev->connect_keys = connkeys;
-       memcpy(wdev->ssid, connect->ssid, connect->ssid_len);
-       wdev->ssid_len = connect->ssid_len;
+       memcpy(wdev->u.client.ssid, connect->ssid, connect->ssid_len);
+       wdev->u.client.ssid_len = connect->ssid_len;
 
        wdev->conn_bss_type = connect->pbss ? IEEE80211_BSS_TYPE_PBSS :
                                              IEEE80211_BSS_TYPE_ESS;
                 * This could be reassoc getting refused, don't clear
                 * ssid_len in that case.
                 */
-               if (!wdev->current_bss)
-                       wdev->ssid_len = 0;
+               if (!wdev->connected)
+                       wdev->u.client.ssid_len = 0;
                return err;
        }
 
                err = cfg80211_sme_disconnect(wdev, reason);
        else if (!rdev->ops->disconnect)
                cfg80211_mlme_down(rdev, dev);
-       else if (wdev->ssid_len)
+       else if (wdev->u.client.ssid_len)
                err = rdev_disconnect(rdev, dev, reason);
 
        /*
         * in which case cfg80211_disconnected() will take care of
         * this later.
         */
-       if (!wdev->current_bss)
-               wdev->ssid_len = 0;
+       if (!wdev->connected)
+               wdev->u.client.ssid_len = 0;
 
        return err;
 }
                        break;
                case NL80211_IFTYPE_AP:
                case NL80211_IFTYPE_P2P_GO:
-                       __cfg80211_stop_ap(rdev, wdev->netdev, false);
+                       __cfg80211_stop_ap(rdev, wdev->netdev, -1, false);
                        break;
                case NL80211_IFTYPE_MESH_POINT:
                        __cfg80211_leave_mesh(rdev, wdev->netdev);
                         * ops->disconnect not implemented.  Otherwise we can
                         * use cfg80211_disconnect.
                         */
-                       if (rdev->ops->disconnect || wdev->current_bss)
+                       if (rdev->ops->disconnect || wdev->connected)
                                cfg80211_disconnect(rdev, wdev->netdev,
                                                    WLAN_REASON_DEAUTH_LEAVING,
                                                    true);
 
                __field(bool, privacy)
                __field(enum nl80211_auth_type, auth_type)
                __field(int, inactivity_timeout)
+               __field(unsigned int, link_id)
        ),
        TP_fast_assign(
                WIPHY_ASSIGN;
                __entry->inactivity_timeout = settings->inactivity_timeout;
                memset(__entry->ssid, 0, IEEE80211_MAX_SSID_LEN + 1);
                memcpy(__entry->ssid, settings->ssid, settings->ssid_len);
+               __entry->link_id = settings->beacon.link_id;
        ),
        TP_printk(WIPHY_PR_FMT ", " NETDEV_PR_FMT ", AP settings - ssid: %s, "
                  CHAN_DEF_PR_FMT ", beacon interval: %d, dtim period: %d, "
                  "hidden ssid: %d, wpa versions: %u, privacy: %s, "
-                 "auth type: %d, inactivity timeout: %d",
+                 "auth type: %d, inactivity timeout: %d, link_id: %d",
                  WIPHY_PR_ARG, NETDEV_PR_ARG, __entry->ssid, CHAN_DEF_PR_ARG,
                  __entry->beacon_interval, __entry->dtim_period,
                  __entry->hidden_ssid, __entry->wpa_ver,
                  BOOL_TO_STR(__entry->privacy), __entry->auth_type,
-                 __entry->inactivity_timeout)
+                 __entry->inactivity_timeout, __entry->link_id)
 );
 
 TRACE_EVENT(rdev_change_beacon,
        TP_STRUCT__entry(
                WIPHY_ENTRY
                NETDEV_ENTRY
+               __field(int, link_id)
                __dynamic_array(u8, head, info ? info->head_len : 0)
                __dynamic_array(u8, tail, info ? info->tail_len : 0)
                __dynamic_array(u8, beacon_ies, info ? info->beacon_ies_len : 0)
                WIPHY_ASSIGN;
                NETDEV_ASSIGN;
                if (info) {
+                       __entry->link_id = info->link_id;
                        if (info->head)
                                memcpy(__get_dynamic_array(head), info->head,
                                       info->head_len);
                        if (info->probe_resp)
                                memcpy(__get_dynamic_array(probe_resp),
                                       info->probe_resp, info->probe_resp_len);
+               } else {
+                       __entry->link_id = -1;
                }
        ),
-       TP_printk(WIPHY_PR_FMT ", " NETDEV_PR_FMT, WIPHY_PR_ARG, NETDEV_PR_ARG)
+       TP_printk(WIPHY_PR_FMT ", " NETDEV_PR_FMT ", link_id:%d",
+                 WIPHY_PR_ARG, NETDEV_PR_ARG, __entry->link_id)
+);
+
+TRACE_EVENT(rdev_stop_ap,
+       TP_PROTO(struct wiphy *wiphy, struct net_device *netdev,
+                unsigned int link_id),
+       TP_ARGS(wiphy, netdev, link_id),
+       TP_STRUCT__entry(
+               WIPHY_ENTRY
+               NETDEV_ENTRY
+               __field(unsigned int, link_id)
+       ),
+       TP_fast_assign(
+               WIPHY_ASSIGN;
+               NETDEV_ASSIGN;
+               __entry->link_id = link_id;
+       ),
+       TP_printk(WIPHY_PR_FMT ", " NETDEV_PR_FMT ", link_id: %d",
+                 WIPHY_PR_ARG, NETDEV_PR_ARG, __entry->link_id)
 );
 
 DECLARE_EVENT_CLASS(wiphy_netdev_evt,
        TP_printk(WIPHY_PR_FMT ", " NETDEV_PR_FMT, WIPHY_PR_ARG, NETDEV_PR_ARG)
 );
 
-DEFINE_EVENT(wiphy_netdev_evt, rdev_stop_ap,
-       TP_PROTO(struct wiphy *wiphy, struct net_device *netdev),
-       TP_ARGS(wiphy, netdev)
-);
-
 DEFINE_EVENT(wiphy_netdev_evt, rdev_set_rekey_data,
        TP_PROTO(struct wiphy *wiphy, struct net_device *netdev),
        TP_ARGS(wiphy, netdev)
 
 TRACE_EVENT(rdev_set_bitrate_mask,
        TP_PROTO(struct wiphy *wiphy, struct net_device *netdev,
+                unsigned int link_id,
                 const u8 *peer, const struct cfg80211_bitrate_mask *mask),
-       TP_ARGS(wiphy, netdev, peer, mask),
+       TP_ARGS(wiphy, netdev, link_id, peer, mask),
        TP_STRUCT__entry(
                WIPHY_ENTRY
                NETDEV_ENTRY
+               __field(unsigned int, link_id)
                MAC_ENTRY(peer)
        ),
        TP_fast_assign(
                WIPHY_ASSIGN;
                NETDEV_ASSIGN;
+               __entry->link_id = link_id;
                MAC_ASSIGN(peer, peer);
        ),
-       TP_printk(WIPHY_PR_FMT ", " NETDEV_PR_FMT ", peer: " MAC_PR_FMT,
-                 WIPHY_PR_ARG, NETDEV_PR_ARG, MAC_PR_ARG(peer))
+       TP_printk(WIPHY_PR_FMT ", " NETDEV_PR_FMT ", link_id: %d, peer: " MAC_PR_FMT,
+                 WIPHY_PR_ARG, NETDEV_PR_ARG, __entry->link_id,
+                 MAC_PR_ARG(peer))
 );
 
 TRACE_EVENT(rdev_update_mgmt_frame_registrations,
                  WIPHY_PR_ARG, NETDEV_PR_ARG, __entry->noack_map)
 );
 
-DEFINE_EVENT(wiphy_wdev_evt, rdev_get_channel,
-       TP_PROTO(struct wiphy *wiphy, struct wireless_dev *wdev),
-       TP_ARGS(wiphy, wdev)
+TRACE_EVENT(rdev_get_channel,
+       TP_PROTO(struct wiphy *wiphy, struct wireless_dev *wdev,
+                unsigned int link_id),
+       TP_ARGS(wiphy, wdev, link_id),
+       TP_STRUCT__entry(
+               WIPHY_ENTRY
+               WDEV_ENTRY
+               __field(unsigned int, link_id)
+       ),
+       TP_fast_assign(
+               WIPHY_ASSIGN;
+               WDEV_ASSIGN;
+               __entry->link_id = link_id;
+       ),
+       TP_printk(WIPHY_PR_FMT ", " WDEV_PR_FMT ", link_id: %u",
+                 WIPHY_PR_ARG, WDEV_PR_ARG, __entry->link_id)
 );
 
 TRACE_EVENT(rdev_return_chandef,
 
 TRACE_EVENT(rdev_set_ap_chanwidth,
        TP_PROTO(struct wiphy *wiphy, struct net_device *netdev,
+                unsigned int link_id,
                 struct cfg80211_chan_def *chandef),
-       TP_ARGS(wiphy, netdev, chandef),
+       TP_ARGS(wiphy, netdev, link_id, chandef),
        TP_STRUCT__entry(
                WIPHY_ENTRY
                NETDEV_ENTRY
                CHAN_DEF_ENTRY
+               __field(unsigned int, link_id)
        ),
        TP_fast_assign(
                WIPHY_ASSIGN;
                NETDEV_ASSIGN;
                CHAN_DEF_ASSIGN(chandef);
+               __entry->link_id = link_id;
        ),
-       TP_printk(WIPHY_PR_FMT ", " NETDEV_PR_FMT ", " CHAN_DEF_PR_FMT,
-                 WIPHY_PR_ARG, NETDEV_PR_ARG, CHAN_DEF_PR_ARG)
+       TP_printk(WIPHY_PR_FMT ", " NETDEV_PR_FMT ", " CHAN_DEF_PR_FMT ", link:%d",
+                 WIPHY_PR_ARG, NETDEV_PR_ARG, CHAN_DEF_PR_ARG,
+                 __entry->link_id)
 );
 
 TRACE_EVENT(rdev_add_tx_ts,
 
 TRACE_EVENT(cfg80211_ch_switch_notify,
        TP_PROTO(struct net_device *netdev,
-                struct cfg80211_chan_def *chandef),
-       TP_ARGS(netdev, chandef),
+                struct cfg80211_chan_def *chandef,
+                unsigned int link_id),
+       TP_ARGS(netdev, chandef, link_id),
        TP_STRUCT__entry(
                NETDEV_ENTRY
                CHAN_DEF_ENTRY
+               __field(unsigned int, link_id)
        ),
        TP_fast_assign(
                NETDEV_ASSIGN;
                CHAN_DEF_ASSIGN(chandef);
+               __entry->link_id = link_id;
        ),
-       TP_printk(NETDEV_PR_FMT ", " CHAN_DEF_PR_FMT,
-                 NETDEV_PR_ARG, CHAN_DEF_PR_ARG)
+       TP_printk(NETDEV_PR_FMT ", " CHAN_DEF_PR_FMT ", link:%d",
+                 NETDEV_PR_ARG, CHAN_DEF_PR_ARG, __entry->link_id)
 );
 
 TRACE_EVENT(cfg80211_ch_switch_started_notify,
 
  * Copyright 2007-2009 Johannes Berg <johannes@sipsolutions.net>
  * Copyright 2013-2014  Intel Mobile Communications GmbH
  * Copyright 2017      Intel Deutschland GmbH
- * Copyright (C) 2018-2021 Intel Corporation
+ * Copyright (C) 2018-2022 Intel Corporation
  */
 #include <linux/export.h>
 #include <linux/bitops.h>
                        return -EBUSY;
 
                dev->ieee80211_ptr->use_4addr = false;
-               dev->ieee80211_ptr->mesh_id_up_len = 0;
                wdev_lock(dev->ieee80211_ptr);
                rdev_set_qos_map(rdev, dev, NULL);
                wdev_unlock(dev->ieee80211_ptr);
                switch (otype) {
                case NL80211_IFTYPE_AP:
                case NL80211_IFTYPE_P2P_GO:
-                       cfg80211_stop_ap(rdev, dev, true);
+                       cfg80211_stop_ap(rdev, dev, -1, true);
                        break;
                case NL80211_IFTYPE_ADHOC:
                        cfg80211_leave_ibss(rdev, dev, false);
 
                cfg80211_process_rdev_events(rdev);
                cfg80211_mlme_purge_registrations(dev->ieee80211_ptr);
+
+               memset(&dev->ieee80211_ptr->u, 0,
+                      sizeof(dev->ieee80211_ptr->u));
+               memset(&dev->ieee80211_ptr->links, 0,
+                      sizeof(dev->ieee80211_ptr->links));
        }
 
        err = rdev_change_virtual_intf(rdev, dev, ntype, params);
 }
 EXPORT_SYMBOL(ieee80211_chandef_to_operating_class);
 
+static int cfg80211_wdev_bi(struct wireless_dev *wdev)
+{
+       switch (wdev->iftype) {
+       case NL80211_IFTYPE_AP:
+       case NL80211_IFTYPE_P2P_GO:
+               WARN_ON(wdev->valid_links);
+               return wdev->links[0].ap.beacon_interval;
+       case NL80211_IFTYPE_MESH_POINT:
+               return wdev->u.mesh.beacon_interval;
+       case NL80211_IFTYPE_ADHOC:
+               return wdev->u.ibss.beacon_interval;
+       default:
+               break;
+       }
+
+       return 0;
+}
+
 static void cfg80211_calculate_bi_data(struct wiphy *wiphy, u32 new_beacon_int,
                                       u32 *beacon_int_gcd,
                                       bool *beacon_int_different)
        *beacon_int_different = false;
 
        list_for_each_entry(wdev, &wiphy->wdev_list, list) {
-               if (!wdev->beacon_interval)
+               int wdev_bi;
+
+               /* this feature isn't supported with MLO */
+               if (wdev->valid_links)
+                       continue;
+
+               wdev_bi = cfg80211_wdev_bi(wdev);
+
+               if (!wdev_bi)
                        continue;
 
                if (!*beacon_int_gcd) {
-                       *beacon_int_gcd = wdev->beacon_interval;
+                       *beacon_int_gcd = wdev_bi;
                        continue;
                }
 
-               if (wdev->beacon_interval == *beacon_int_gcd)
+               if (wdev_bi == *beacon_int_gcd)
                        continue;
 
                *beacon_int_different = true;
-               *beacon_int_gcd = gcd(*beacon_int_gcd, wdev->beacon_interval);
+               *beacon_int_gcd = gcd(*beacon_int_gcd, wdev_bi);
        }
 
        if (new_beacon_int && *beacon_int_gcd != new_beacon_int) {
 
  * we directly assign the wireless handlers of wireless interfaces.
  *
  * Copyright 2008-2009 Johannes Berg <johannes@sipsolutions.net>
- * Copyright (C) 2019-2021 Intel Corporation
+ * Copyright (C) 2019-2022 Intel Corporation
  */
 
 #include <linux/export.h>
        int err, i;
        bool rejoin = false;
 
+       if (wdev->valid_links)
+               return -EINVAL;
+
        if (pairwise && !addr)
                return -EINVAL;
 
                return -EOPNOTSUPP;
 
        if (params->cipher == WLAN_CIPHER_SUITE_AES_CMAC) {
-               if (!wdev->current_bss)
+               if (!wdev->connected)
                        return -ENOLINK;
 
                if (!rdev->ops->set_default_mgmt_key)
 
        if (remove) {
                err = 0;
-               if (wdev->current_bss) {
+               if (wdev->connected ||
+                   (wdev->iftype == NL80211_IFTYPE_ADHOC &&
+                    wdev->u.ibss.current_bss)) {
                        /*
                         * If removing the current TX key, we will need to
                         * join a new IBSS without the privacy bit clear.
                return -EINVAL;
 
        err = 0;
-       if (wdev->current_bss)
+       if (wdev->connected ||
+           (wdev->iftype == NL80211_IFTYPE_ADHOC &&
+            wdev->u.ibss.current_bss))
                err = rdev_add_key(rdev, dev, idx, pairwise, addr, params);
        else if (params->cipher != WLAN_CIPHER_SUITE_WEP40 &&
                 params->cipher != WLAN_CIPHER_SUITE_WEP104)
        if ((params->cipher == WLAN_CIPHER_SUITE_WEP40 ||
             params->cipher == WLAN_CIPHER_SUITE_WEP104) &&
            (tx_key || (!addr && wdev->wext.default_key == -1))) {
-               if (wdev->current_bss) {
+               if (wdev->connected ||
+                   (wdev->iftype == NL80211_IFTYPE_ADHOC &&
+                    wdev->u.ibss.current_bss)) {
                        /*
                         * If we are getting a new TX key from not having
                         * had one before we need to join a new IBSS with
 
        if (params->cipher == WLAN_CIPHER_SUITE_AES_CMAC &&
            (tx_key || (!addr && wdev->wext.default_mgmt_key == -1))) {
-               if (wdev->current_bss)
+               if (wdev->connected ||
+                   (wdev->iftype == NL80211_IFTYPE_ADHOC &&
+                    wdev->u.ibss.current_bss))
                        err = rdev_set_default_mgmt_key(rdev, dev, idx);
                if (!err)
                        wdev->wext.default_mgmt_key = idx;
                return -EOPNOTSUPP;
 
        wiphy_lock(&rdev->wiphy);
+       if (wdev->valid_links) {
+               err = -EOPNOTSUPP;
+               goto out;
+       }
+
        idx = erq->flags & IW_ENCODE_INDEX;
        if (idx == 0) {
                idx = wdev->wext.default_key;
                /* No key data - just set the default TX key index */
                err = 0;
                wdev_lock(wdev);
-               if (wdev->current_bss)
+               if (wdev->connected ||
+                   (wdev->iftype == NL80211_IFTYPE_ADHOC &&
+                    wdev->u.ibss.current_bss))
                        err = rdev_set_default_key(rdev, dev, idx, true,
                                                   true);
                if (!err)
                        break;
                }
 
-               ret = rdev_get_channel(rdev, wdev, &chandef);
+               ret = rdev_get_channel(rdev, wdev, 0, &chandef);
                if (ret)
                        break;
                freq->m = chandef.chan->center_freq;
                return -EINVAL;
 
        wiphy_lock(&rdev->wiphy);
-       ret = rdev_set_bitrate_mask(rdev, dev, NULL, &mask);
+       if (dev->ieee80211_ptr->valid_links)
+               ret = -EOPNOTSUPP;
+       else
+               ret = rdev_set_bitrate_mask(rdev, dev, 0, NULL, &mask);
        wiphy_unlock(&rdev->wiphy);
 
        return ret;
 
        err = 0;
        wdev_lock(wdev);
-       if (wdev->current_bss)
-               memcpy(addr, wdev->current_bss->pub.bssid, ETH_ALEN);
+       if (!wdev->valid_links && wdev->links[0].client.current_bss)
+               memcpy(addr, wdev->links[0].client.current_bss->pub.bssid,
+                      ETH_ALEN);
        else
                err = -EOPNOTSUPP;
        wdev_unlock(wdev);
 
        /* Grab BSSID of current BSS, if any */
        wdev_lock(wdev);
-       if (!wdev->current_bss) {
+       if (wdev->valid_links || !wdev->links[0].client.current_bss) {
                wdev_unlock(wdev);
                return NULL;
        }
-       memcpy(bssid, wdev->current_bss->pub.bssid, ETH_ALEN);
+       memcpy(bssid, wdev->links[0].client.current_bss->pub.bssid, ETH_ALEN);
        wdev_unlock(wdev);
 
        memset(&sinfo, 0, sizeof(sinfo));
 
  * cfg80211 wext compat for managed mode.
  *
  * Copyright 2009      Johannes Berg <johannes@sipsolutions.net>
- * Copyright (C) 2009, 2020-2021 Intel Corporation.
+ * Copyright (C) 2009, 2020-2022 Intel Corporation
  */
 
 #include <linux/export.h>
        if (WARN_ON(wdev->iftype != NL80211_IFTYPE_STATION))
                return -EINVAL;
 
+       if (wdev->valid_links)
+               return -EOPNOTSUPP;
+
        wdev_lock(wdev);
-       if (wdev->current_bss)
-               chan = wdev->current_bss->pub.channel;
+       if (wdev->links[0].client.current_bss)
+               chan = wdev->links[0].client.current_bss->pub.channel;
        else if (wdev->wext.connect.channel)
                chan = wdev->wext.connect.channel;
        wdev_unlock(wdev);
        if (WARN_ON(wdev->iftype != NL80211_IFTYPE_STATION))
                return -EINVAL;
 
+       if (wdev->valid_links)
+               return -EINVAL;
+
        data->flags = 0;
 
        wdev_lock(wdev);
-       if (wdev->current_bss) {
+       if (wdev->links[0].client.current_bss) {
                const struct element *ssid_elem;
 
                rcu_read_lock();
-               ssid_elem = ieee80211_bss_get_elem(&wdev->current_bss->pub,
-                                                  WLAN_EID_SSID);
+               ssid_elem = ieee80211_bss_get_elem(
+                               &wdev->links[0].client.current_bss->pub,
+                               WLAN_EID_SSID);
                if (ssid_elem) {
                        data->flags = 1;
                        data->length = ssid_elem->datalen;
        ap_addr->sa_family = ARPHRD_ETHER;
 
        wdev_lock(wdev);
-       if (wdev->current_bss)
-               memcpy(ap_addr->sa_data, wdev->current_bss->pub.bssid, ETH_ALEN);
+       if (wdev->valid_links) {
+               wdev_unlock(wdev);
+               return -EOPNOTSUPP;
+       }
+       if (wdev->links[0].client.current_bss)
+               memcpy(ap_addr->sa_data,
+                      wdev->links[0].client.current_bss->pub.bssid,
+                      ETH_ALEN);
        else
                eth_zero_addr(ap_addr->sa_data);
        wdev_unlock(wdev);