@@ -5561,6 +5561,9 @@ static inline void wiphy_unlock(struct wiphy *wiphy)
* @pmsr_free_wk: (private) peer measurements cleanup work
* @unprot_beacon_reported: (private) timestamp of last
* unprotected beacon report
+ * @mld_wdev: points to MLD wdev of type %NL80211_IFTYPE_STATION to which this
+ * MLO link wdev is affiliated to. Valid for iftype
+ * %NL80211_IFTYPE_MLO_LINK only.
*/
struct wireless_dev {
struct wiphy *wiphy;
@@ -5639,6 +5642,8 @@ struct wireless_dev {
struct work_struct pmsr_free_wk;
unsigned long unprot_beacon_reported;
+
+ struct wireless_dev *mld_wdev;
};
static inline const u8 *wdev_address(struct wireless_dev *wdev)
@@ -8198,6 +8203,35 @@ void cfg80211_stop_iface(struct wiphy *wiphy, struct wireless_dev *wdev,
gfp_t gfp);
/**
+ * cfg80211_register_sta_mlo_link - Register an MLO link wdev and affiliate
+ * with STA wdev.
+ * @sta_wdev: wireless device of a non-AP Station interface
+ * @link_wdev: wireless device of an MLO link affiliated to the Station
+ * Iface(@sta_wdev).
+ *
+ * Create a wdev interface for an MLO link and associate it with existing MLD
+ * STA wdev. Both MLD STA wdev and MLO link wdev must belong to same wiphy.
+ * Driver must register all the MLO link wdevs with MLD STA wdev before
+ * STA wdev interface is up. Callers must hold the RTNL and wiphy mutex lock.
+ *
+ * Return: A zero on success or a negative error code.
+ */
+int cfg80211_register_sta_mlo_link(struct wireless_dev *sta_wdev,
+ struct wireless_dev *link_wdev);
+
+/**
+ * cfg80211_unregister_sta_mlo_link - remove the given MLO link wdev
+ * @wdev: struct wireless_dev of a MLO link to remove
+ *
+ * This function removes the MLO link device so it can no longer be used.
+ * Requires the RTNL and wiphy mutex to be held.
+ */
+static inline void cfg80211_unregister_sta_mlo_link(struct wireless_dev *wdev)
+{
+ cfg80211_unregister_wdev(wdev);
+}
+
+/**
* cfg80211_shutdown_all_interfaces - shut down all interfaces for a wiphy
* @wiphy: the wiphy to shut down
*
@@ -2663,6 +2663,12 @@ enum nl80211_commands {
* association request when used with NL80211_CMD_NEW_STATION). Can be set
* only if %NL80211_STA_FLAG_WME is set.
*
+ * @NL80211_ATTR_MLO_LINK_INFO: MLO links information associated with
+ * %NL80211_IFTYPE_STA interface. This is used in
+ * %NL80211_CMD_GET/SET/NEW_INTERFACE response to indicate information of
+ * all the MLO links affiliated to %NL80211_IFTYPE_STATION interface.
+ * See &enum nl80211_mlo_link_info_attributes for details.
+ *
* @NUM_NL80211_ATTR: total number of nl80211_attrs available
* @NL80211_ATTR_MAX: highest attribute number currently defined
* @__NL80211_ATTR_AFTER_LAST: internal use
@@ -3175,6 +3181,8 @@ enum nl80211_attrs {
NL80211_ATTR_EHT_CAPABILITY,
+ NL80211_ATTR_MLO_LINK_INFO,
+
/* add attributes here, update the policy in nl80211.c */
__NL80211_ATTR_AFTER_LAST,
@@ -3262,6 +3270,12 @@ enum nl80211_attrs {
* @NL80211_IF_TYPE_OCB: Outside Context of a BSS
* This mode corresponds to the MIB variable dot11OCBActivated=true
* @NL80211_IFTYPE_NAN: NAN device interface type (not a netdev)
+ * @NL80211_IFTYPE_MLO_LINK: MLO link device interface type, this is not a
+ * netdev and can't be created in the normal ways. Drivers can
+ * register and associate this interface with iftype
+ * %NL80211_IFTYPE_STATION. Drivers shall indicate support for this
+ * interface mode in %NL80211_ATTR_SUPPORTED_IFTYPES when MLO supported in
+ * STA mode.
* @NL80211_IFTYPE_MAX: highest interface type number currently defined
* @NUM_NL80211_IFTYPES: number of defined interface types
*
@@ -3283,6 +3297,7 @@ enum nl80211_iftype {
NL80211_IFTYPE_P2P_DEVICE,
NL80211_IFTYPE_OCB,
NL80211_IFTYPE_NAN,
+ NL80211_IFTYPE_MLO_LINK,
/* keep last */
NUM_NL80211_IFTYPES,
@@ -7600,4 +7615,25 @@ enum nl80211_ap_settings_flags {
NL80211_AP_SETTINGS_SA_QUERY_OFFLOAD_SUPPORT = 1 << 1,
};
+/**
+ * enum nl80211_mlo_link_info_attributes - MLO link's information.
+ *
+ * @__NL80211_MLO_LINK_INFO_ATTR_INVALID: Invalid
+ *
+ * @NL80211_MLO_LINK_INFO_ATTR_WDEV: wireless device identifier for MLO link
+ * (u64)
+ *
+ * @__NL80211_MLO_LINK_INFO_ATTR_LAST: Internal
+ * @NL80211_MLO_LINK_INFO_ATTR_MAX: highest attribute
+ */
+enum nl80211_mlo_link_info_attributes {
+ __NL80211_MLO_LINK_INFO_ATTR_INVALID,
+
+ NL80211_MLO_LINK_INFO_ATTR_WDEV,
+
+ /* keep last */
+ __NL80211_MLO_LINK_INFO_ATTR_LAST,
+ NL80211_MLO_LINK_INFO_ATTR_MAX = __NL80211_MLO_LINK_INFO_ATTR_LAST - 1,
+};
+
#endif /* __LINUX_NL80211_H */
@@ -532,6 +532,7 @@ static int ieee80211_add_key(struct wiphy *wiphy, struct net_device *dev,
case NL80211_IFTYPE_P2P_CLIENT:
case NL80211_IFTYPE_P2P_GO:
case NL80211_IFTYPE_OCB:
+ case NL80211_IFTYPE_MLO_LINK:
/* shouldn't happen */
WARN_ON_ONCE(1);
break;
@@ -291,6 +291,7 @@ ieee80211_get_chanctx_max_required_bw(struct ieee80211_local *local,
case NL80211_IFTYPE_MONITOR:
case NL80211_IFTYPE_P2P_CLIENT:
case NL80211_IFTYPE_P2P_GO:
+ case NL80211_IFTYPE_MLO_LINK:
WARN_ON_ONCE(1);
}
max_bw = max(max_bw, width);
@@ -1094,6 +1095,7 @@ ieee80211_vif_chanctx_reservation_complete(struct ieee80211_sub_if_data *sdata)
case NL80211_IFTYPE_P2P_GO:
case NL80211_IFTYPE_P2P_DEVICE:
case NL80211_IFTYPE_NAN:
+ case NL80211_IFTYPE_MLO_LINK:
case NUM_NL80211_IFTYPES:
WARN_ON(1);
break;
@@ -1177,6 +1177,7 @@ int ieee80211_do_open(struct wireless_dev *wdev, bool coming_up)
case NL80211_IFTYPE_P2P_CLIENT:
case NL80211_IFTYPE_P2P_GO:
case NL80211_IFTYPE_WDS:
+ case NL80211_IFTYPE_MLO_LINK:
/* cannot happen */
WARN_ON(1);
break;
@@ -1714,6 +1715,7 @@ static void ieee80211_setup_sdata(struct ieee80211_sub_if_data *sdata,
break;
case NL80211_IFTYPE_UNSPECIFIED:
case NL80211_IFTYPE_WDS:
+ case NL80211_IFTYPE_MLO_LINK:
case NUM_NL80211_IFTYPES:
WARN_ON(1);
break;
@@ -2589,6 +2589,7 @@ int ieee80211_reconfig(struct ieee80211_local *local)
case NL80211_IFTYPE_P2P_CLIENT:
case NL80211_IFTYPE_P2P_GO:
case NL80211_IFTYPE_WDS:
+ case NL80211_IFTYPE_MLO_LINK:
WARN_ON(1);
break;
}
@@ -582,6 +582,7 @@ int cfg80211_chandef_dfs_required(struct wiphy *wiphy,
case NL80211_IFTYPE_AP_VLAN:
case NL80211_IFTYPE_P2P_DEVICE:
case NL80211_IFTYPE_NAN:
+ case NL80211_IFTYPE_MLO_LINK:
break;
case NL80211_IFTYPE_WDS:
case NL80211_IFTYPE_UNSPECIFIED:
@@ -728,6 +729,7 @@ bool cfg80211_beaconing_iface_active(struct wireless_dev *wdev)
case NL80211_IFTYPE_MONITOR:
case NL80211_IFTYPE_AP_VLAN:
case NL80211_IFTYPE_P2P_DEVICE:
+ case NL80211_IFTYPE_MLO_LINK:
/* Can NAN type be considered as beaconing interface? */
case NL80211_IFTYPE_NAN:
break;
@@ -1430,6 +1432,7 @@ cfg80211_get_chan_state(struct wireless_dev *wdev,
return;
case NL80211_IFTYPE_UNSPECIFIED:
case NL80211_IFTYPE_WDS:
+ case NL80211_IFTYPE_MLO_LINK:
case NUM_NL80211_IFTYPES:
WARN_ON(1);
}
@@ -261,6 +261,36 @@ void cfg80211_stop_nan(struct cfg80211_registered_device *rdev,
rdev->opencount--;
}
+void cfg80211_start_mlo_link(struct cfg80211_registered_device *rdev,
+ struct wireless_dev *wdev)
+{
+ lockdep_assert_held(&rdev->wiphy.mtx);
+
+ if (WARN_ON(wdev->iftype != NL80211_IFTYPE_MLO_LINK))
+ return;
+
+ if (wdev_running(wdev))
+ return;
+
+ wdev->is_running = true;
+ rdev->opencount++;
+}
+
+void cfg80211_stop_mlo_link(struct cfg80211_registered_device *rdev,
+ struct wireless_dev *wdev)
+{
+ lockdep_assert_held(&rdev->wiphy.mtx);
+
+ if (WARN_ON(wdev->iftype != NL80211_IFTYPE_MLO_LINK))
+ return;
+
+ if (!wdev_running(wdev))
+ return;
+
+ wdev->is_running = false;
+ rdev->opencount--;
+}
+
void cfg80211_shutdown_all_interfaces(struct wiphy *wiphy)
{
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
@@ -285,6 +315,9 @@ void cfg80211_shutdown_all_interfaces(struct wiphy *wiphy)
case NL80211_IFTYPE_NAN:
cfg80211_stop_nan(rdev, wdev);
break;
+ case NL80211_IFTYPE_MLO_LINK:
+ /* dev_close() of MLD STA wdev calls MLO link stop */
+ break;
default:
break;
}
@@ -1156,6 +1189,9 @@ static void _cfg80211_unregister_wdev(struct wireless_dev *wdev,
case NL80211_IFTYPE_NAN:
cfg80211_stop_nan(rdev, wdev);
break;
+ case NL80211_IFTYPE_MLO_LINK:
+ cfg80211_stop_mlo_link(rdev, wdev);
+ break;
default:
break;
}
@@ -1257,6 +1293,7 @@ void __cfg80211_leave(struct cfg80211_registered_device *rdev,
break;
case NL80211_IFTYPE_UNSPECIFIED:
case NL80211_IFTYPE_WDS:
+ case NL80211_IFTYPE_MLO_LINK:
case NUM_NL80211_IFTYPES:
/* invalid */
break;
@@ -1322,6 +1359,7 @@ void cfg80211_init_wdev(struct wireless_dev *wdev)
wdev->netdev->priv_flags |= IFF_DONT_BRIDGE;
INIT_WORK(&wdev->disconnect_wk, cfg80211_autodisconnect_wk);
+ wdev->mld_wdev = NULL;
}
void cfg80211_register_wdev(struct cfg80211_registered_device *rdev,
@@ -1383,11 +1421,48 @@ int cfg80211_register_netdevice(struct net_device *dev)
}
EXPORT_SYMBOL(cfg80211_register_netdevice);
+int cfg80211_register_sta_mlo_link(struct wireless_dev *sta_wdev,
+ struct wireless_dev *link_wdev)
+{
+ struct cfg80211_registered_device *rdev;
+ struct wireless_dev *wdev;
+
+ ASSERT_RTNL();
+
+ if (WARN_ON(link_wdev->iftype != NL80211_IFTYPE_MLO_LINK ||
+ sta_wdev->iftype != NL80211_IFTYPE_STATION ||
+ sta_wdev->wiphy != link_wdev->wiphy))
+ return -EINVAL;
+
+ rdev = wiphy_to_rdev(sta_wdev->wiphy);
+
+ lockdep_assert_held(&rdev->wiphy.mtx);
+
+ list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
+ if (sta_wdev == wdev)
+ break;
+ }
+
+ if (wdev != sta_wdev)
+ return -ENODEV;
+
+ if (wdev_running(sta_wdev))
+ return -EBUSY;
+
+ cfg80211_init_wdev(link_wdev);
+ link_wdev->mld_wdev = sta_wdev;
+ cfg80211_register_wdev(rdev, link_wdev);
+ nl80211_notify_iface(rdev, sta_wdev, NL80211_CMD_SET_INTERFACE);
+
+ return 0;
+}
+EXPORT_SYMBOL(cfg80211_register_sta_mlo_link);
+
static int cfg80211_netdev_notifier_call(struct notifier_block *nb,
unsigned long state, void *ptr)
{
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
- struct wireless_dev *wdev = dev->ieee80211_ptr;
+ struct wireless_dev *wdev = dev->ieee80211_ptr, *link_wdev, *tmp_wdev;
struct cfg80211_registered_device *rdev;
struct cfg80211_sched_scan_request *pos, *tmp;
@@ -1421,6 +1496,12 @@ static int cfg80211_netdev_notifier_call(struct notifier_block *nb,
*/
if (wdev->registered && !wdev->registering) {
wiphy_lock(&rdev->wiphy);
+ list_for_each_entry_safe(link_wdev, tmp_wdev, &rdev->wiphy.wdev_list, list) {
+ if (link_wdev->mld_wdev != wdev)
+ continue;
+
+ rdev_del_virtual_intf(rdev, link_wdev);
+ }
_cfg80211_unregister_wdev(wdev, false);
wiphy_unlock(&rdev->wiphy);
}
@@ -1448,6 +1529,14 @@ static int cfg80211_netdev_notifier_call(struct notifier_block *nb,
}
rdev->opencount--;
+
+ list_for_each_entry(link_wdev, &rdev->wiphy.wdev_list, list) {
+ if (link_wdev->mld_wdev != wdev)
+ continue;
+
+ cfg80211_stop_mlo_link(rdev, link_wdev);
+ }
+
wiphy_unlock(&rdev->wiphy);
wake_up(&rdev->dev_wait);
break;
@@ -1499,6 +1588,14 @@ static int cfg80211_netdev_notifier_call(struct notifier_block *nb,
/* assume this means it's off */
wdev->ps = false;
}
+
+ list_for_each_entry(link_wdev, &rdev->wiphy.wdev_list, list) {
+ if (link_wdev->mld_wdev != wdev)
+ continue;
+
+ cfg80211_start_mlo_link(rdev, link_wdev);
+ }
+
wiphy_unlock(&rdev->wiphy);
break;
case NETDEV_PRE_UP:
@@ -575,5 +575,9 @@ void cfg80211_cqm_config_free(struct wireless_dev *wdev);
void cfg80211_release_pmsr(struct wireless_dev *wdev, u32 portid);
void cfg80211_pmsr_wdev_down(struct wireless_dev *wdev);
void cfg80211_pmsr_free_wk(struct work_struct *work);
+void cfg80211_start_mlo_link(struct cfg80211_registered_device *rdev,
+ struct wireless_dev *wdev);
+void cfg80211_stop_mlo_link(struct cfg80211_registered_device *rdev,
+ struct wireless_dev *wdev);
#endif /* __NET_WIRELESS_CORE_H */
@@ -1505,6 +1505,7 @@ static int nl80211_key_allowed(struct wireless_dev *wdev)
case NL80211_IFTYPE_NAN:
case NL80211_IFTYPE_P2P_DEVICE:
case NL80211_IFTYPE_WDS:
+ case NL80211_IFTYPE_MLO_LINK:
case NUM_NL80211_IFTYPES:
return -EINVAL;
}
@@ -3660,6 +3661,10 @@ static int nl80211_send_iface(struct sk_buff *msg, u32 portid, u32 seq, int flag
{
struct net_device *dev = wdev->netdev;
void *hdr;
+ struct wireless_dev *link_wdev;
+ struct nlattr *nested, *nested_mlo_links;
+ int i = 0;
+
WARN_ON(cmd != NL80211_CMD_NEW_INTERFACE &&
cmd != NL80211_CMD_DEL_INTERFACE &&
@@ -3734,6 +3739,30 @@ static int nl80211_send_iface(struct sk_buff *msg, u32 portid, u32 seq, int flag
/* nothing */
break;
}
+
+ nested = nla_nest_start(msg, NL80211_ATTR_MLO_LINK_INFO);
+ if (!nested)
+ goto nla_put_failure_locked;
+
+ list_for_each_entry(link_wdev, &rdev->wiphy.wdev_list, list) {
+ if (link_wdev->iftype != NL80211_IFTYPE_MLO_LINK ||
+ link_wdev->mld_wdev != wdev)
+ continue;
+
+ nested_mlo_links = nla_nest_start(msg, i);
+ if (!nested_mlo_links)
+ goto nla_put_failure_locked;
+
+ if (nla_put_u64_64bit(msg, NL80211_MLO_LINK_INFO_ATTR_WDEV,
+ wdev_id(link_wdev), NL80211_ATTR_PAD))
+ goto nla_put_failure_locked;
+
+ nla_nest_end(msg, nested_mlo_links);
+ i++;
+ }
+
+ nla_nest_end(msg, nested);
+
wdev_unlock(wdev);
if (rdev->ops->get_txq_stats) {
@@ -3991,6 +4020,10 @@ static int nl80211_set_interface(struct sk_buff *skb, struct genl_info *info)
change = true;
}
+ if (otype == NL80211_IFTYPE_MLO_LINK ||
+ ntype == NL80211_IFTYPE_MLO_LINK)
+ return -EOPNOTSUPP;
+
if (info->attrs[NL80211_ATTR_MESH_ID]) {
struct wireless_dev *wdev = dev->ieee80211_ptr;
@@ -4062,6 +4095,9 @@ static int _nl80211_new_interface(struct sk_buff *skb, struct genl_info *info)
if (!rdev->ops->add_virtual_intf)
return -EOPNOTSUPP;
+ if (type == NL80211_IFTYPE_MLO_LINK)
+ return -EOPNOTSUPP;
+
if ((type == NL80211_IFTYPE_P2P_DEVICE || type == NL80211_IFTYPE_NAN ||
rdev->wiphy.features & NL80211_FEATURE_MAC_ON_CREATE) &&
info->attrs[NL80211_ATTR_MAC]) {
@@ -4161,6 +4197,9 @@ static int nl80211_del_interface(struct sk_buff *skb, struct genl_info *info)
if (!rdev->ops->del_virtual_intf)
return -EOPNOTSUPP;
+ if (wdev->iftype == NL80211_IFTYPE_MLO_LINK)
+ return -EOPNOTSUPP;
+
/*
* We hold RTNL, so this is safe, without RTNL opencount cannot
* reach 0, and thus the rdev cannot be deleted.
@@ -8566,7 +8605,8 @@ static int nl80211_trigger_scan(struct sk_buff *skb, struct genl_info *info)
wiphy = &rdev->wiphy;
- if (wdev->iftype == NL80211_IFTYPE_NAN)
+ if (wdev->iftype == NL80211_IFTYPE_NAN ||
+ wdev->iftype == NL80211_IFTYPE_MLO_LINK)
return -EOPNOTSUPP;
if (!rdev->ops->scan)
@@ -11692,6 +11732,7 @@ static int nl80211_register_mgmt(struct sk_buff *skb, struct genl_info *info)
case NL80211_IFTYPE_P2P_GO:
case NL80211_IFTYPE_P2P_DEVICE:
break;
+ case NL80211_IFTYPE_MLO_LINK:
case NL80211_IFTYPE_NAN:
default:
return -EOPNOTSUPP;
@@ -11748,6 +11789,7 @@ static int nl80211_tx_mgmt(struct sk_buff *skb, struct genl_info *info)
case NL80211_IFTYPE_AP_VLAN:
case NL80211_IFTYPE_MESH_POINT:
case NL80211_IFTYPE_P2P_GO:
+ case NL80211_IFTYPE_MLO_LINK:
break;
case NL80211_IFTYPE_NAN:
default:
@@ -11872,6 +11914,7 @@ static int nl80211_tx_mgmt_cancel_wait(struct sk_buff *skb, struct genl_info *in
case NL80211_IFTYPE_AP_VLAN:
case NL80211_IFTYPE_P2P_GO:
case NL80211_IFTYPE_P2P_DEVICE:
+ case NL80211_IFTYPE_MLO_LINK:
break;
case NL80211_IFTYPE_NAN:
default:
@@ -1110,6 +1110,7 @@ int cfg80211_change_iface(struct cfg80211_registered_device *rdev,
case NL80211_IFTYPE_P2P_DEVICE:
case NL80211_IFTYPE_WDS:
case NL80211_IFTYPE_NAN:
+ case NL80211_IFTYPE_MLO_LINK:
WARN_ON(1);
break;
}