summaryrefslogtreecommitdiff
path: root/net/wireless
diff options
context:
space:
mode:
authorJakub Kicinski <kuba@kernel.org>2026-03-26 18:17:14 -0700
committerJakub Kicinski <kuba@kernel.org>2026-03-26 18:17:14 -0700
commitdbd94b9831bc52a1efb7ff3de841ffc3457428ce (patch)
treec542864d16b4f97130ce1d2aa8afcc1bebc85b62 /net/wireless
parent7d89349fb8849a6147cc7310fcf9059c1504f50f (diff)
parent7dd6f81f4ef801b57f6ce7b0eee32aef5c488538 (diff)
Merge tag 'wireless-next-2026-03-26' of https://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless-next
Johannes Berg says: ==================== A fairly big set of changes all over, notably with: - cfg80211: new APIs for NAN (Neighbor Aware Networking, aka Wi-Fi Aware) so less work must be in firmware - mt76: - mt7996/mt7925 MLO fixes/improvements - mt7996 NPU support (HW eth/wifi traffic offload) - iwlwifi: UNII-9 and continuing UHR work * tag 'wireless-next-2026-03-26' of https://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless-next: (230 commits) wifi: mac80211: ignore reserved bits in reconfiguration status wifi: cfg80211: allow protected action frame TX for NAN wifi: ieee80211: Add some missing NAN definitions wifi: nl80211: Add a notification to notify NAN channel evacuation wifi: nl80211: add NL80211_CMD_NAN_ULW_UPDATE notification wifi: nl80211: allow reporting spurious NAN Data frames wifi: cfg80211: allow ToDS=0/FromDS=0 data frames on NAN data interfaces wifi: nl80211: define an API for configuring the NAN peer's schedule wifi: nl80211: add support for NAN stations wifi: cfg80211: separately store HT, VHT and HE capabilities for NAN wifi: cfg80211: add support for NAN data interface wifi: cfg80211: make sure NAN chandefs are valid wifi: cfg80211: Add an API to configure local NAN schedule wifi: mac80211: cleanup error path of ieee80211_do_open wifi: mac80211: extract channel logic from link logic wifi: iwlwifi: mld: set RX_FLAG_RADIOTAP_TLV_AT_END generically wifi: iwlwifi: reduce the number of prints upon firmware crash wifi: iwlwifi: fix the description of SESSION_PROTECTION_CMD wifi: iwlwifi: mld: introduce iwl_mld_vif_fw_id_valid wifi: iwlwifi: mld: block EMLSR during TDLS connections ... ==================== Link: https://patch.msgid.link/20260326152021.305959-3-johannes@sipsolutions.net Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Diffstat (limited to 'net/wireless')
-rw-r--r--net/wireless/chan.c6
-rw-r--r--net/wireless/core.c130
-rw-r--r--net/wireless/core.h10
-rw-r--r--net/wireless/mlme.c13
-rw-r--r--net/wireless/nl80211.c905
-rw-r--r--net/wireless/rdev-ops.h32
-rw-r--r--net/wireless/reg.c27
-rw-r--r--net/wireless/sysfs.c27
-rw-r--r--net/wireless/trace.h105
-rw-r--r--net/wireless/util.c28
10 files changed, 1214 insertions, 69 deletions
diff --git a/net/wireless/chan.c b/net/wireless/chan.c
index fa0764ede9c5..8b94c0de80ad 100644
--- a/net/wireless/chan.c
+++ b/net/wireless/chan.c
@@ -317,7 +317,7 @@ static bool cfg80211_valid_center_freq(u32 center,
int step;
/* We only do strict verification on 6 GHz */
- if (center < 5955 || center > 7115)
+ if (center < 5955 || center > 7215)
return true;
bw = nl80211_chan_width_to_mhz(width);
@@ -325,7 +325,7 @@ static bool cfg80211_valid_center_freq(u32 center,
return false;
/* Validate that the channels bw is entirely within the 6 GHz band */
- if (center - bw / 2 < 5945 || center + bw / 2 > 7125)
+ if (center - bw / 2 < 5945 || center + bw / 2 > 7225)
return false;
/* With 320 MHz the permitted channels overlap */
@@ -816,6 +816,7 @@ int cfg80211_chandef_dfs_required(struct wiphy *wiphy,
case NL80211_IFTYPE_MONITOR:
case NL80211_IFTYPE_AP_VLAN:
case NL80211_IFTYPE_P2P_DEVICE:
+ case NL80211_IFTYPE_NAN_DATA:
break;
case NL80211_IFTYPE_WDS:
case NL80211_IFTYPE_UNSPECIFIED:
@@ -939,6 +940,7 @@ bool cfg80211_beaconing_iface_active(struct wireless_dev *wdev)
case NL80211_IFTYPE_P2P_DEVICE:
/* Can NAN type be considered as beaconing interface? */
case NL80211_IFTYPE_NAN:
+ case NL80211_IFTYPE_NAN_DATA:
break;
case NL80211_IFTYPE_UNSPECIFIED:
case NL80211_IFTYPE_WDS:
diff --git a/net/wireless/core.c b/net/wireless/core.c
index 23afc250bc10..6783e0672dcb 100644
--- a/net/wireless/core.c
+++ b/net/wireless/core.c
@@ -5,7 +5,7 @@
* Copyright 2006-2010 Johannes Berg <johannes@sipsolutions.net>
* Copyright 2013-2014 Intel Mobile Communications GmbH
* Copyright 2015-2017 Intel Deutschland GmbH
- * Copyright (C) 2018-2025 Intel Corporation
+ * Copyright (C) 2018-2026 Intel Corporation
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
@@ -254,6 +254,8 @@ void cfg80211_stop_p2p_device(struct cfg80211_registered_device *rdev,
void cfg80211_stop_nan(struct cfg80211_registered_device *rdev,
struct wireless_dev *wdev)
{
+ struct cfg80211_nan_local_sched empty_sched = {};
+
lockdep_assert_held(&rdev->wiphy.mtx);
if (WARN_ON(wdev->iftype != NL80211_IFTYPE_NAN))
@@ -262,6 +264,15 @@ void cfg80211_stop_nan(struct cfg80211_registered_device *rdev,
if (!wdev_running(wdev))
return;
+ /*
+ * If there is a scheduled update pending, mark it as canceled, so the
+ * empty schedule will be accepted
+ */
+ wdev->u.nan.sched_update_pending = false;
+
+ /* Unschedule all */
+ cfg80211_nan_set_local_schedule(rdev, wdev, &empty_sched);
+
rdev_stop_nan(rdev, wdev);
wdev->is_running = false;
@@ -270,6 +281,47 @@ void cfg80211_stop_nan(struct cfg80211_registered_device *rdev,
rdev->opencount--;
}
+int cfg80211_nan_set_local_schedule(struct cfg80211_registered_device *rdev,
+ struct wireless_dev *wdev,
+ struct cfg80211_nan_local_sched *sched)
+{
+ int ret;
+
+ lockdep_assert_held(&rdev->wiphy.mtx);
+
+ if (wdev->iftype != NL80211_IFTYPE_NAN || !wdev_running(wdev))
+ return -EINVAL;
+
+ if (wdev->u.nan.sched_update_pending)
+ return -EBUSY;
+
+ ret = rdev_nan_set_local_sched(rdev, wdev, sched);
+ if (ret)
+ return ret;
+
+ wdev->u.nan.sched_update_pending = sched->deferred;
+
+ kfree(wdev->u.nan.chandefs);
+ wdev->u.nan.chandefs = NULL;
+ wdev->u.nan.n_channels = 0;
+
+ if (!sched->n_channels)
+ return 0;
+
+ wdev->u.nan.chandefs = kcalloc(sched->n_channels,
+ sizeof(*wdev->u.nan.chandefs),
+ GFP_KERNEL);
+ if (!wdev->u.nan.chandefs)
+ return -ENOMEM;
+
+ for (int i = 0; i < sched->n_channels; i++)
+ wdev->u.nan.chandefs[i] = sched->nan_channels[i].chandef;
+
+ wdev->u.nan.n_channels = sched->n_channels;
+
+ return 0;
+}
+
void cfg80211_shutdown_all_interfaces(struct wiphy *wiphy)
{
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
@@ -277,16 +329,21 @@ void cfg80211_shutdown_all_interfaces(struct wiphy *wiphy)
ASSERT_RTNL();
+ /*
+ * Some netdev interfaces need to be closed before some non-netdev
+ * ones, i.e. NAN_DATA interfaces need to be closed before the NAN
+ * interface
+ */
list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
if (wdev->netdev) {
dev_close(wdev->netdev);
continue;
}
+ }
- /* otherwise, check iftype */
-
- guard(wiphy)(wiphy);
+ guard(wiphy)(wiphy);
+ list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
switch (wdev->iftype) {
case NL80211_IFTYPE_P2P_DEVICE:
cfg80211_stop_p2p_device(rdev, wdev);
@@ -344,6 +401,8 @@ void cfg80211_destroy_ifaces(struct cfg80211_registered_device *rdev)
list_for_each_entry_safe(wdev, tmp, &rdev->wiphy.wdev_list, list) {
if (wdev->nl_owner_dead) {
+ cfg80211_close_dependents(rdev, wdev);
+
if (wdev->netdev)
dev_close(wdev->netdev);
@@ -354,6 +413,21 @@ void cfg80211_destroy_ifaces(struct cfg80211_registered_device *rdev)
}
}
+void cfg80211_close_dependents(struct cfg80211_registered_device *rdev,
+ struct wireless_dev *wdev)
+{
+ ASSERT_RTNL();
+
+ if (wdev->iftype != NL80211_IFTYPE_NAN)
+ return;
+
+ /* Close all NAN DATA interfaces */
+ list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
+ if (wdev->iftype == NL80211_IFTYPE_NAN_DATA)
+ dev_close(wdev->netdev);
+ }
+}
+
static void cfg80211_destroy_iface_wk(struct work_struct *work)
{
struct cfg80211_registered_device *rdev;
@@ -761,6 +835,10 @@ int wiphy_register(struct wiphy *wiphy)
!(wiphy->nan_supported_bands & BIT(NL80211_BAND_2GHZ)))))
return -EINVAL;
+ if (WARN_ON((wiphy->interface_modes & BIT(NL80211_IFTYPE_NAN_DATA)) &&
+ !wiphy->nan_capa.phy.ht.ht_supported))
+ return -EINVAL;
+
if (WARN_ON(wiphy->interface_modes & BIT(NL80211_IFTYPE_WDS)))
return -EINVAL;
@@ -1367,9 +1445,8 @@ void cfg80211_update_iface_num(struct cfg80211_registered_device *rdev,
rdev->num_running_monitor_ifaces += num;
}
-void cfg80211_leave(struct cfg80211_registered_device *rdev,
- struct wireless_dev *wdev,
- int link_id)
+void cfg80211_leave_locked(struct cfg80211_registered_device *rdev,
+ struct wireless_dev *wdev, int link_id)
{
struct net_device *dev = wdev->netdev;
struct cfg80211_sched_scan_request *pos, *tmp;
@@ -1420,6 +1497,7 @@ void cfg80211_leave(struct cfg80211_registered_device *rdev,
break;
case NL80211_IFTYPE_AP_VLAN:
case NL80211_IFTYPE_MONITOR:
+ case NL80211_IFTYPE_NAN_DATA:
/* nothing to do */
break;
case NL80211_IFTYPE_UNSPECIFIED:
@@ -1430,6 +1508,19 @@ void cfg80211_leave(struct cfg80211_registered_device *rdev,
}
}
+void cfg80211_leave(struct cfg80211_registered_device *rdev,
+ struct wireless_dev *wdev, int link_id)
+{
+ ASSERT_RTNL();
+
+ /* NAN_DATA interfaces must be closed before stopping NAN */
+ cfg80211_close_dependents(rdev, wdev);
+
+ guard(wiphy)(&rdev->wiphy);
+
+ cfg80211_leave_locked(rdev, wdev, link_id);
+}
+
void cfg80211_stop_link(struct wiphy *wiphy, struct wireless_dev *wdev,
int link_id, gfp_t gfp)
{
@@ -1445,6 +1536,9 @@ void cfg80211_stop_link(struct wiphy *wiphy, struct wireless_dev *wdev,
trace_cfg80211_stop_link(wiphy, wdev, link_id);
+ if (wdev->iftype == NL80211_IFTYPE_NAN)
+ return;
+
ev = kzalloc_obj(*ev, gfp);
if (!ev)
return;
@@ -1595,10 +1689,9 @@ static int cfg80211_netdev_notifier_call(struct notifier_block *nb,
}
break;
case NETDEV_GOING_DOWN:
- scoped_guard(wiphy, &rdev->wiphy) {
- cfg80211_leave(rdev, wdev, -1);
+ cfg80211_leave(rdev, wdev, -1);
+ scoped_guard(wiphy, &rdev->wiphy)
cfg80211_remove_links(wdev);
- }
/* since we just did cfg80211_leave() nothing to do there */
cancel_work_sync(&wdev->disconnect_wk);
cancel_work_sync(&wdev->pmsr_free_wk);
@@ -1679,6 +1772,23 @@ static int cfg80211_netdev_notifier_call(struct notifier_block *nb,
if (rfkill_blocked(rdev->wiphy.rfkill))
return notifier_from_errno(-ERFKILL);
+
+ /* NAN_DATA interfaces require a running NAN interface */
+ if (wdev->iftype == NL80211_IFTYPE_NAN_DATA) {
+ struct wireless_dev *iter;
+ bool nan_started = false;
+
+ list_for_each_entry(iter, &rdev->wiphy.wdev_list, list) {
+ if (iter->iftype == NL80211_IFTYPE_NAN &&
+ wdev_running(iter)) {
+ nan_started = true;
+ break;
+ }
+ }
+
+ if (!nan_started)
+ return notifier_from_errno(-ENOLINK);
+ }
break;
default:
return NOTIFY_DONE;
diff --git a/net/wireless/core.h b/net/wireless/core.h
index 6cace846d7a3..ae2d56d3ad90 100644
--- a/net/wireless/core.h
+++ b/net/wireless/core.h
@@ -318,6 +318,9 @@ void cfg80211_cqm_rssi_notify_work(struct wiphy *wiphy,
void cfg80211_destroy_ifaces(struct cfg80211_registered_device *rdev);
+void cfg80211_close_dependents(struct cfg80211_registered_device *rdev,
+ struct wireless_dev *wdev);
+
/* free object */
void cfg80211_dev_free(struct cfg80211_registered_device *rdev);
@@ -541,6 +544,9 @@ int cfg80211_validate_beacon_int(struct cfg80211_registered_device *rdev,
void cfg80211_update_iface_num(struct cfg80211_registered_device *rdev,
enum nl80211_iftype iftype, int num);
+void cfg80211_leave_locked(struct cfg80211_registered_device *rdev,
+ struct wireless_dev *wdev, int link_id);
+
void cfg80211_leave(struct cfg80211_registered_device *rdev,
struct wireless_dev *wdev,
int link_id);
@@ -551,6 +557,10 @@ void cfg80211_stop_p2p_device(struct cfg80211_registered_device *rdev,
void cfg80211_stop_nan(struct cfg80211_registered_device *rdev,
struct wireless_dev *wdev);
+int cfg80211_nan_set_local_schedule(struct cfg80211_registered_device *rdev,
+ struct wireless_dev *wdev,
+ struct cfg80211_nan_local_sched *sched);
+
struct cfg80211_internal_bss *
cfg80211_bss_update(struct cfg80211_registered_device *rdev,
struct cfg80211_internal_bss *tmp,
diff --git a/net/wireless/mlme.c b/net/wireless/mlme.c
index 5cd86253a62e..bd72317c4964 100644
--- a/net/wireless/mlme.c
+++ b/net/wireless/mlme.c
@@ -4,7 +4,7 @@
*
* Copyright (c) 2009, Jouni Malinen <j@w1.fi>
* Copyright (c) 2015 Intel Deutschland GmbH
- * Copyright (C) 2019-2020, 2022-2025 Intel Corporation
+ * Copyright (C) 2019-2020, 2022-2026 Intel Corporation
*/
#include <linux/kernel.h>
@@ -782,8 +782,8 @@ void cfg80211_mlme_unregister_socket(struct wireless_dev *wdev, u32 nlportid)
rdev_crit_proto_stop(rdev, wdev);
}
- if (nlportid == wdev->ap_unexpected_nlportid)
- wdev->ap_unexpected_nlportid = 0;
+ if (nlportid == wdev->unexpected_nlportid)
+ wdev->unexpected_nlportid = 0;
}
void cfg80211_mlme_purge_registrations(struct wireless_dev *wdev)
@@ -933,12 +933,17 @@ int cfg80211_mlme_mgmt_tx(struct cfg80211_registered_device *rdev,
* cfg80211 doesn't track the stations
*/
break;
+ case NL80211_IFTYPE_NAN:
+ case NL80211_IFTYPE_NAN_DATA:
+ if (mgmt->u.action.category !=
+ WLAN_CATEGORY_PROTECTED_DUAL_OF_ACTION)
+ err = -EOPNOTSUPP;
+ break;
case NL80211_IFTYPE_P2P_DEVICE:
/*
* fall through, P2P device only supports
* public action frames
*/
- case NL80211_IFTYPE_NAN:
default:
err = -EOPNOTSUPP;
break;
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index e15cd26f3a79..f334cdef8958 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -333,6 +333,97 @@ static int validate_nan_cluster_id(const struct nlattr *attr,
return 0;
}
+static int validate_nan_avail_blob(const struct nlattr *attr,
+ struct netlink_ext_ack *extack)
+{
+ const u8 *data = nla_data(attr);
+ unsigned int len = nla_len(attr);
+ u16 attr_len;
+
+ /* Need at least: Attr ID (1) + Length (2) */
+ if (len < 3) {
+ NL_SET_ERR_MSG_FMT(extack,
+ "NAN Availability: Too short (need at least 3 bytes, have %u)",
+ len);
+ return -EINVAL;
+ }
+
+ if (data[0] != 0x12) {
+ NL_SET_ERR_MSG_FMT(extack,
+ "NAN Availability: Invalid Attribute ID 0x%02x (expected 0x12)",
+ data[0]);
+ return -EINVAL;
+ }
+
+ attr_len = get_unaligned_le16(&data[1]);
+
+ if (attr_len != len - 3) {
+ NL_SET_ERR_MSG_FMT(extack,
+ "NAN Availability: Length field (%u) doesn't match data length (%u)",
+ attr_len, len - 3);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int validate_nan_ulw(const struct nlattr *attr,
+ struct netlink_ext_ack *extack)
+{
+ const u8 *data = nla_data(attr);
+ unsigned int len = nla_len(attr);
+ unsigned int pos = 0;
+
+ while (pos < len) {
+ u16 attr_len;
+
+ /* Need at least: Attr ID (1) + Length (2) */
+ if (pos + 3 > len) {
+ NL_SET_ERR_MSG_FMT(extack,
+ "ULW: Incomplete header (need 3 bytes, have %u)",
+ len - pos);
+ return -EINVAL;
+ }
+
+ if (data[pos] != 0x17) {
+ NL_SET_ERR_MSG_FMT(extack,
+ "ULW: Invalid Attribute ID 0x%02x (expected 0x17)",
+ data[pos]);
+ return -EINVAL;
+ }
+ pos++;
+
+ /* Length is in little-endian format */
+ attr_len = get_unaligned_le16(&data[pos]);
+ pos += 2;
+
+ /*
+ * Check if length is one of the valid values: 16 (no
+ * channel/band entry included), 18 (band entry included),
+ * 21 (channel entry included without Auxiliary channel bitmap),
+ * or 23 (channel entry included with Auxiliary channel bitmap).
+ */
+ if (attr_len != 16 && attr_len != 18 && attr_len != 21 &&
+ attr_len != 23) {
+ NL_SET_ERR_MSG_FMT(extack,
+ "ULW: Invalid length %u (must be 16, 18, 21, or 23)",
+ attr_len);
+ return -EINVAL;
+ }
+
+ if (pos + attr_len > len) {
+ NL_SET_ERR_MSG_FMT(extack,
+ "ULW: Length field (%u) exceeds remaining data (%u)",
+ attr_len, len - pos);
+ return -EINVAL;
+ }
+
+ pos += attr_len;
+ }
+
+ return 0;
+}
+
static int validate_uhr_capa(const struct nlattr *attr,
struct netlink_ext_ack *extack)
{
@@ -556,6 +647,13 @@ nl80211_nan_band_conf_policy[NL80211_NAN_BAND_CONF_ATTR_MAX + 1] = {
};
static const struct nla_policy
+nl80211_nan_peer_map_policy[NL80211_NAN_PEER_MAP_ATTR_MAX + 1] = {
+ [NL80211_NAN_PEER_MAP_ATTR_MAP_ID] = NLA_POLICY_MAX(NLA_U8, 15),
+ [NL80211_NAN_PEER_MAP_ATTR_TIME_SLOTS] =
+ NLA_POLICY_EXACT_LEN(CFG80211_NAN_SCHED_NUM_TIME_SLOTS),
+};
+
+static const struct nla_policy
nl80211_nan_conf_policy[NL80211_NAN_CONF_ATTR_MAX + 1] = {
[NL80211_NAN_CONF_CLUSTER_ID] =
NLA_POLICY_VALIDATE_FN(NLA_BINARY, validate_nan_cluster_id,
@@ -962,6 +1060,22 @@ static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = {
[NL80211_ATTR_DISABLE_UHR] = { .type = NLA_FLAG },
[NL80211_ATTR_UHR_OPERATION] =
NLA_POLICY_VALIDATE_FN(NLA_BINARY, validate_uhr_operation),
+ [NL80211_ATTR_NAN_CHANNEL] = NLA_POLICY_NESTED(nl80211_policy),
+ [NL80211_ATTR_NAN_CHANNEL_ENTRY] = NLA_POLICY_EXACT_LEN(6),
+ [NL80211_ATTR_NAN_RX_NSS] = { .type = NLA_U8 },
+ [NL80211_ATTR_NAN_TIME_SLOTS] =
+ NLA_POLICY_EXACT_LEN(CFG80211_NAN_SCHED_NUM_TIME_SLOTS),
+ [NL80211_ATTR_NAN_AVAIL_BLOB] =
+ NLA_POLICY_VALIDATE_FN(NLA_BINARY, validate_nan_avail_blob),
+ [NL80211_ATTR_NAN_SCHED_DEFERRED] = { .type = NLA_FLAG },
+ [NL80211_ATTR_NAN_NMI_MAC] = NLA_POLICY_ETH_ADDR,
+ [NL80211_ATTR_NAN_ULW] =
+ NLA_POLICY_VALIDATE_FN(NLA_BINARY, validate_nan_ulw),
+ [NL80211_ATTR_NAN_COMMITTED_DW] = { .type = NLA_U16 },
+ [NL80211_ATTR_NAN_SEQ_ID] = { .type = NLA_U8 },
+ [NL80211_ATTR_NAN_MAX_CHAN_SWITCH_TIME] = { .type = NLA_U16 },
+ [NL80211_ATTR_NAN_PEER_MAPS] =
+ NLA_POLICY_NESTED_ARRAY(nl80211_nan_peer_map_policy),
};
/* policy for the key attributes */
@@ -1722,6 +1836,7 @@ static int nl80211_key_allowed(struct wireless_dev *wdev)
return 0;
return -ENOLINK;
case NL80211_IFTYPE_NAN:
+ case NL80211_IFTYPE_NAN_DATA:
if (wiphy_ext_feature_isset(wdev->wiphy,
NL80211_EXT_FEATURE_SECURE_NAN))
return 0;
@@ -2678,6 +2793,68 @@ fail:
return -ENOBUFS;
}
+static int nl80211_put_nan_phy_cap(struct wiphy *wiphy, struct sk_buff *msg)
+{
+ struct nlattr *nl_phy_cap;
+ const struct ieee80211_sta_ht_cap *ht_cap;
+ const struct ieee80211_sta_vht_cap *vht_cap;
+ const struct ieee80211_sta_he_cap *he_cap;
+
+ if (!cfg80211_iftype_allowed(wiphy, NL80211_IFTYPE_NAN_DATA, false, 0))
+ return 0;
+
+ ht_cap = &wiphy->nan_capa.phy.ht;
+ vht_cap = &wiphy->nan_capa.phy.vht;
+ he_cap = &wiphy->nan_capa.phy.he;
+
+ /* HT is mandatory */
+ if (WARN_ON(!ht_cap->ht_supported))
+ return 0;
+
+ nl_phy_cap = nla_nest_start_noflag(msg, NL80211_NAN_CAPA_PHY);
+ if (!nl_phy_cap)
+ return -ENOBUFS;
+
+ if (nla_put(msg, NL80211_NAN_PHY_CAP_ATTR_HT_MCS_SET,
+ sizeof(ht_cap->mcs), &ht_cap->mcs) ||
+ nla_put_u16(msg, NL80211_NAN_PHY_CAP_ATTR_HT_CAPA, ht_cap->cap) ||
+ nla_put_u8(msg, NL80211_NAN_PHY_CAP_ATTR_HT_AMPDU_FACTOR,
+ ht_cap->ampdu_factor) ||
+ nla_put_u8(msg, NL80211_NAN_PHY_CAP_ATTR_HT_AMPDU_DENSITY,
+ ht_cap->ampdu_density))
+ goto fail;
+
+ if (vht_cap->vht_supported) {
+ if (nla_put(msg, NL80211_NAN_PHY_CAP_ATTR_VHT_MCS_SET,
+ sizeof(vht_cap->vht_mcs), &vht_cap->vht_mcs) ||
+ nla_put_u32(msg, NL80211_NAN_PHY_CAP_ATTR_VHT_CAPA,
+ vht_cap->cap))
+ goto fail;
+ }
+
+ if (he_cap->has_he) {
+ if (nla_put(msg, NL80211_NAN_PHY_CAP_ATTR_HE_MAC,
+ sizeof(he_cap->he_cap_elem.mac_cap_info),
+ he_cap->he_cap_elem.mac_cap_info) ||
+ nla_put(msg, NL80211_NAN_PHY_CAP_ATTR_HE_PHY,
+ sizeof(he_cap->he_cap_elem.phy_cap_info),
+ he_cap->he_cap_elem.phy_cap_info) ||
+ nla_put(msg, NL80211_NAN_PHY_CAP_ATTR_HE_MCS_SET,
+ sizeof(he_cap->he_mcs_nss_supp),
+ &he_cap->he_mcs_nss_supp) ||
+ nla_put(msg, NL80211_NAN_PHY_CAP_ATTR_HE_PPE,
+ sizeof(he_cap->ppe_thres), he_cap->ppe_thres))
+ goto fail;
+ }
+
+ nla_nest_end(msg, nl_phy_cap);
+ return 0;
+
+fail:
+ nla_nest_cancel(msg, nl_phy_cap);
+ return -ENOBUFS;
+}
+
static int nl80211_put_nan_capa(struct wiphy *wiphy, struct sk_buff *msg)
{
struct nlattr *nan_caps;
@@ -2704,6 +2881,9 @@ static int nl80211_put_nan_capa(struct wiphy *wiphy, struct sk_buff *msg)
wiphy->nan_capa.dev_capabilities))
goto fail;
+ if (nl80211_put_nan_phy_cap(wiphy, msg))
+ goto fail;
+
nla_nest_end(msg, nan_caps);
return 0;
@@ -4879,6 +5059,8 @@ static int nl80211_del_interface(struct sk_buff *skb, struct genl_info *info)
else
dev_close(wdev->netdev);
+ cfg80211_close_dependents(rdev, wdev);
+
mutex_lock(&rdev->wiphy.mtx);
return cfg80211_remove_virtual_intf(rdev, wdev);
@@ -7123,6 +7305,26 @@ static int parse_station_flags(struct genl_info *info,
if ((params->sta_flags_mask |
params->sta_flags_set) & BIT(__NL80211_STA_FLAG_INVALID))
return -EINVAL;
+
+ if ((iftype == NL80211_IFTYPE_NAN ||
+ iftype == NL80211_IFTYPE_NAN_DATA) &&
+ params->sta_flags_mask &
+ ~(BIT(NL80211_STA_FLAG_AUTHENTICATED) |
+ BIT(NL80211_STA_FLAG_ASSOCIATED) |
+ BIT(NL80211_STA_FLAG_AUTHORIZED) |
+ BIT(NL80211_STA_FLAG_MFP)))
+ return -EINVAL;
+
+ /* WME is always used in NAN */
+ if (iftype == NL80211_IFTYPE_NAN_DATA) {
+ /* but don't let userspace control it */
+ if (params->sta_flags_mask & BIT(NL80211_STA_FLAG_WME))
+ return -EINVAL;
+
+ params->sta_flags_mask |= BIT(NL80211_STA_FLAG_WME);
+ params->sta_flags_set |= BIT(NL80211_STA_FLAG_WME);
+ }
+
return 0;
}
@@ -8005,7 +8207,7 @@ static int nl80211_dump_station(struct sk_buff *skb,
/* nl80211_prepare_wdev_dump acquired it in the successful case */
__acquire(&rdev->wiphy.mtx);
- if (!wdev->netdev) {
+ if (!wdev->netdev && wdev->iftype != NL80211_IFTYPE_NAN) {
err = -EINVAL;
goto out_err;
}
@@ -8192,10 +8394,12 @@ int cfg80211_check_station_change(struct wiphy *wiphy,
return -EINVAL;
if (params->link_sta_params.supported_rates)
return -EINVAL;
- if (params->ext_capab || params->link_sta_params.ht_capa ||
- params->link_sta_params.vht_capa ||
- params->link_sta_params.he_capa ||
- params->link_sta_params.eht_capa ||
+ if (statype != CFG80211_STA_NAN_MGMT &&
+ (params->link_sta_params.ht_capa ||
+ params->link_sta_params.vht_capa ||
+ params->link_sta_params.he_capa))
+ return -EINVAL;
+ if (params->ext_capab || params->link_sta_params.eht_capa ||
params->link_sta_params.uhr_capa)
return -EINVAL;
if (params->sta_flags_mask & BIT(NL80211_STA_FLAG_SPP_AMSDU))
@@ -8267,6 +8471,19 @@ int cfg80211_check_station_change(struct wiphy *wiphy,
params->plink_action != NL80211_PLINK_ACTION_BLOCK)
return -EINVAL;
break;
+ case CFG80211_STA_NAN_MGMT:
+ if (params->sta_flags_mask &
+ ~(BIT(NL80211_STA_FLAG_AUTHORIZED) |
+ BIT(NL80211_STA_FLAG_MFP)))
+ return -EINVAL;
+ break;
+ case CFG80211_STA_NAN_DATA:
+ if (params->sta_flags_mask &
+ ~(BIT(NL80211_STA_FLAG_AUTHORIZED) |
+ BIT(NL80211_STA_FLAG_MFP) |
+ BIT(NL80211_STA_FLAG_WME)))
+ return -EINVAL;
+ break;
}
/*
@@ -8481,7 +8698,8 @@ static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info)
memset(&params, 0, sizeof(params));
- if (!dev)
+ if (!dev && wdev->iftype != NL80211_IFTYPE_NAN &&
+ wdev->iftype != NL80211_IFTYPE_NAN_DATA)
return -EINVAL;
if (!rdev->ops->change_station)
@@ -8624,6 +8842,8 @@ static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info)
case NL80211_IFTYPE_STATION:
case NL80211_IFTYPE_ADHOC:
case NL80211_IFTYPE_MESH_POINT:
+ case NL80211_IFTYPE_NAN:
+ case NL80211_IFTYPE_NAN_DATA:
break;
default:
err = -EOPNOTSUPP;
@@ -8652,7 +8872,7 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info)
memset(&params, 0, sizeof(params));
- if (!dev)
+ if (!dev && wdev->iftype != NL80211_IFTYPE_NAN)
return -EINVAL;
if (!rdev->ops->add_station)
@@ -8661,15 +8881,31 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info)
if (!info->attrs[NL80211_ATTR_MAC])
return -EINVAL;
- if (!info->attrs[NL80211_ATTR_STA_LISTEN_INTERVAL])
- return -EINVAL;
+ if (wdev->iftype == NL80211_IFTYPE_NAN ||
+ wdev->iftype == NL80211_IFTYPE_NAN_DATA) {
+ if (info->attrs[NL80211_ATTR_STA_SUPPORTED_RATES])
+ return -EINVAL;
+ if (wdev->iftype == NL80211_IFTYPE_NAN_DATA) {
+ if (!info->attrs[NL80211_ATTR_NAN_NMI_MAC])
+ return -EINVAL;
- if (!info->attrs[NL80211_ATTR_STA_SUPPORTED_RATES])
- return -EINVAL;
+ /* Only NMI stations receive the HT/VHT/HE capabilities */
+ if (info->attrs[NL80211_ATTR_HT_CAPABILITY] ||
+ info->attrs[NL80211_ATTR_VHT_CAPABILITY] ||
+ info->attrs[NL80211_ATTR_HE_CAPABILITY])
+ return -EINVAL;
+ }
+ } else {
+ if (!info->attrs[NL80211_ATTR_STA_LISTEN_INTERVAL])
+ return -EINVAL;
- if (!info->attrs[NL80211_ATTR_STA_AID] &&
- !info->attrs[NL80211_ATTR_PEER_AID])
- return -EINVAL;
+ if (!info->attrs[NL80211_ATTR_STA_SUPPORTED_RATES])
+ return -EINVAL;
+
+ if (!info->attrs[NL80211_ATTR_STA_AID] &&
+ !info->attrs[NL80211_ATTR_PEER_AID])
+ return -EINVAL;
+ }
params.link_sta_params.link_id =
nl80211_link_id_or_invalid(info->attrs);
@@ -8685,12 +8921,16 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info)
mac_addr = nla_data(info->attrs[NL80211_ATTR_MAC]);
}
- params.link_sta_params.supported_rates =
- nla_data(info->attrs[NL80211_ATTR_STA_SUPPORTED_RATES]);
- params.link_sta_params.supported_rates_len =
- nla_len(info->attrs[NL80211_ATTR_STA_SUPPORTED_RATES]);
- params.listen_interval =
- nla_get_u16(info->attrs[NL80211_ATTR_STA_LISTEN_INTERVAL]);
+ if (info->attrs[NL80211_ATTR_STA_SUPPORTED_RATES]) {
+ params.link_sta_params.supported_rates =
+ nla_data(info->attrs[NL80211_ATTR_STA_SUPPORTED_RATES]);
+ params.link_sta_params.supported_rates_len =
+ nla_len(info->attrs[NL80211_ATTR_STA_SUPPORTED_RATES]);
+ }
+
+ if (info->attrs[NL80211_ATTR_STA_LISTEN_INTERVAL])
+ params.listen_interval =
+ nla_get_u16(info->attrs[NL80211_ATTR_STA_LISTEN_INTERVAL]);
if (info->attrs[NL80211_ATTR_VLAN_ID])
params.vlan_id = nla_get_u16(info->attrs[NL80211_ATTR_VLAN_ID]);
@@ -8709,7 +8949,7 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info)
if (info->attrs[NL80211_ATTR_PEER_AID])
params.aid = nla_get_u16(info->attrs[NL80211_ATTR_PEER_AID]);
- else
+ else if (info->attrs[NL80211_ATTR_STA_AID])
params.aid = nla_get_u16(info->attrs[NL80211_ATTR_STA_AID]);
if (info->attrs[NL80211_ATTR_STA_CAPABILITY]) {
@@ -8830,6 +9070,16 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info)
return -EINVAL;
}
+ if (wdev->iftype == NL80211_IFTYPE_NAN ||
+ wdev->iftype == NL80211_IFTYPE_NAN_DATA) {
+ if (params.sta_modify_mask & STATION_PARAM_APPLY_UAPSD)
+ return -EINVAL;
+ /* NAN NMI station must be added in associated or authorized state */
+ if (!(params.sta_flags_set & (BIT(NL80211_STA_FLAG_ASSOCIATED) |
+ BIT(NL80211_STA_FLAG_AUTHENTICATED))))
+ return -EINVAL;
+ }
+
/* Ensure that HT/VHT capabilities are not set for 6 GHz HE STA */
if (params.link_sta_params.he_6ghz_capa &&
(params.link_sta_params.ht_capa || params.link_sta_params.vht_capa))
@@ -8922,6 +9172,11 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info)
*/
params.sta_flags_mask &= ~BIT(NL80211_STA_FLAG_AUTHORIZED);
break;
+ case NL80211_IFTYPE_NAN:
+ break;
+ case NL80211_IFTYPE_NAN_DATA:
+ params.nmi_mac = nla_data(info->attrs[NL80211_ATTR_NAN_NMI_MAC]);
+ break;
default:
return -EOPNOTSUPP;
}
@@ -8963,7 +9218,7 @@ static int nl80211_del_station(struct sk_buff *skb, struct genl_info *info)
memset(&params, 0, sizeof(params));
- if (!dev)
+ if (!dev && wdev->iftype != NL80211_IFTYPE_NAN)
return -EINVAL;
if (info->attrs[NL80211_ATTR_MAC])
@@ -8974,6 +9229,8 @@ static int nl80211_del_station(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_NAN:
+ case NL80211_IFTYPE_NAN_DATA:
/* always accept these */
break;
case NL80211_IFTYPE_ADHOC:
@@ -13950,6 +14207,7 @@ static int nl80211_register_mgmt(struct sk_buff *skb, struct genl_info *info)
case NL80211_IFTYPE_P2P_DEVICE:
break;
case NL80211_IFTYPE_NAN:
+ case NL80211_IFTYPE_NAN_DATA:
if (!wiphy_ext_feature_isset(wdev->wiphy,
NL80211_EXT_FEATURE_SECURE_NAN) &&
!(wdev->wiphy->nan_capa.flags &
@@ -14013,6 +14271,7 @@ static int nl80211_tx_mgmt(struct sk_buff *skb, struct genl_info *info)
case NL80211_IFTYPE_P2P_GO:
break;
case NL80211_IFTYPE_NAN:
+ case NL80211_IFTYPE_NAN_DATA:
if (!wiphy_ext_feature_isset(wdev->wiphy,
NL80211_EXT_FEATURE_SECURE_NAN) &&
!(wdev->wiphy->nan_capa.flags &
@@ -15520,13 +15779,14 @@ static int nl80211_register_unexpected_frame(struct sk_buff *skb,
struct wireless_dev *wdev = dev->ieee80211_ptr;
if (wdev->iftype != NL80211_IFTYPE_AP &&
- wdev->iftype != NL80211_IFTYPE_P2P_GO)
+ wdev->iftype != NL80211_IFTYPE_P2P_GO &&
+ wdev->iftype != NL80211_IFTYPE_NAN_DATA)
return -EINVAL;
- if (wdev->ap_unexpected_nlportid)
+ if (wdev->unexpected_nlportid)
return -EBUSY;
- wdev->ap_unexpected_nlportid = info->snd_portid;
+ wdev->unexpected_nlportid = info->snd_portid;
return 0;
}
@@ -15922,6 +16182,10 @@ static int nl80211_stop_nan(struct sk_buff *skb, struct genl_info *info)
if (wdev->iftype != NL80211_IFTYPE_NAN)
return -EOPNOTSUPP;
+ cfg80211_close_dependents(rdev, wdev);
+
+ guard(wiphy)(&rdev->wiphy);
+
cfg80211_stop_nan(rdev, wdev);
return 0;
@@ -16421,6 +16685,482 @@ nla_put_failure:
}
EXPORT_SYMBOL(cfg80211_nan_func_terminated);
+void cfg80211_nan_sched_update_done(struct wireless_dev *wdev, bool success,
+ gfp_t gfp)
+{
+ struct wiphy *wiphy = wdev->wiphy;
+ struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
+ struct sk_buff *msg;
+ void *hdr;
+
+ trace_cfg80211_nan_sched_update_done(wiphy, wdev, success);
+
+ /* Can happen if we stopped NAN */
+ if (!wdev->u.nan.sched_update_pending)
+ return;
+
+ wdev->u.nan.sched_update_pending = false;
+
+ if (!wdev->owner_nlportid)
+ return;
+
+ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, gfp);
+ if (!msg)
+ return;
+
+ hdr = nl80211hdr_put(msg, 0, 0, 0, NL80211_CMD_NAN_SCHED_UPDATE_DONE);
+ if (!hdr)
+ goto nla_put_failure;
+
+ if (nla_put_u32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx) ||
+ nla_put_u64_64bit(msg, NL80211_ATTR_WDEV, wdev_id(wdev),
+ NL80211_ATTR_PAD) ||
+ (success &&
+ nla_put_flag(msg, NL80211_ATTR_NAN_SCHED_UPDATE_SUCCESS)))
+ goto nla_put_failure;
+
+ genlmsg_end(msg, hdr);
+
+ genlmsg_unicast(wiphy_net(wiphy), msg, wdev->owner_nlportid);
+
+ return;
+
+nla_put_failure:
+ nlmsg_free(msg);
+}
+EXPORT_SYMBOL(cfg80211_nan_sched_update_done);
+
+static int nl80211_parse_nan_channel(struct cfg80211_registered_device *rdev,
+ struct nlattr *channel,
+ struct genl_info *info,
+ struct cfg80211_nan_channel *nan_channels,
+ u8 index, bool local)
+{
+ struct nlattr **channel_parsed __free(kfree) = NULL;
+ struct cfg80211_chan_def chandef;
+ u8 n_rx_nss;
+ int ret;
+
+ channel_parsed = kcalloc(NL80211_ATTR_MAX + 1, sizeof(*channel_parsed),
+ GFP_KERNEL);
+ if (!channel_parsed)
+ return -ENOMEM;
+
+ ret = nla_parse_nested(channel_parsed, NL80211_ATTR_MAX, channel, NULL,
+ info->extack);
+ if (ret)
+ return ret;
+
+ ret = nl80211_parse_chandef(rdev, info->extack, channel_parsed,
+ &chandef);
+ if (ret)
+ return ret;
+
+ if (chandef.chan->band == NL80211_BAND_6GHZ) {
+ NL_SET_ERR_MSG(info->extack,
+ "6 GHz band is not supported");
+ return -EOPNOTSUPP;
+ }
+
+ if (!cfg80211_reg_can_beacon(&rdev->wiphy, &chandef,
+ NL80211_IFTYPE_NAN)) {
+ NL_SET_ERR_MSG_ATTR(info->extack, channel,
+ "Channel in NAN schedule is not allowed for NAN operation");
+ return -EINVAL;
+ }
+
+ if (local) {
+ for (int i = 0; i < index; i++) {
+ if (cfg80211_chandef_compatible(&nan_channels[i].chandef,
+ &chandef)) {
+ NL_SET_ERR_MSG_ATTR(info->extack, channel,
+ "Channels in NAN schedule must be mutually incompatible");
+ return -EINVAL;
+ }
+ }
+ }
+
+ if (!channel_parsed[NL80211_ATTR_NAN_CHANNEL_ENTRY]) {
+ NL_SET_ERR_MSG(info->extack,
+ "Missing NAN channel entry attribute");
+ return -EINVAL;
+ }
+
+ nan_channels[index].channel_entry =
+ nla_data(channel_parsed[NL80211_ATTR_NAN_CHANNEL_ENTRY]);
+
+ if (!channel_parsed[NL80211_ATTR_NAN_RX_NSS]) {
+ NL_SET_ERR_MSG(info->extack,
+ "Missing NAN RX NSS attribute");
+ return -EINVAL;
+ }
+
+ nan_channels[index].rx_nss =
+ nla_get_u8(channel_parsed[NL80211_ATTR_NAN_RX_NSS]);
+
+ n_rx_nss = u8_get_bits(rdev->wiphy.nan_capa.n_antennas, 0x03);
+ if ((local && nan_channels[index].rx_nss > n_rx_nss) ||
+ !nan_channels[index].rx_nss) {
+ NL_SET_ERR_MSG_ATTR(info->extack, channel,
+ "Invalid RX NSS in NAN channel definition");
+ return -EINVAL;
+ }
+
+ nan_channels[index].chandef = chandef;
+
+ return 0;
+}
+
+static int
+nl80211_parse_nan_schedule(struct genl_info *info, struct nlattr *slots_attr,
+ u8 schedule[CFG80211_NAN_SCHED_NUM_TIME_SLOTS],
+ u8 n_channels)
+{
+ if (WARN_ON(nla_len(slots_attr) != CFG80211_NAN_SCHED_NUM_TIME_SLOTS))
+ return -EINVAL;
+
+ memcpy(schedule, nla_data(slots_attr), nla_len(slots_attr));
+
+ for (int slot = 0; slot < CFG80211_NAN_SCHED_NUM_TIME_SLOTS; slot++) {
+ if (schedule[slot] != NL80211_NAN_SCHED_NOT_AVAIL_SLOT &&
+ schedule[slot] >= n_channels) {
+ NL_SET_ERR_MSG_FMT(info->extack,
+ "Invalid time slot: slot %d refers to channel index %d, n_channels=%d",
+ slot, schedule[slot], n_channels);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int
+nl80211_parse_nan_peer_map(struct genl_info *info, struct nlattr *map_attr,
+ struct cfg80211_nan_peer_map *map, u8 n_channels)
+{
+ struct nlattr *tb[NL80211_NAN_PEER_MAP_ATTR_MAX + 1];
+ int ret;
+
+ ret = nla_parse_nested(tb, NL80211_NAN_PEER_MAP_ATTR_MAX, map_attr,
+ nl80211_nan_peer_map_policy, info->extack);
+ if (ret)
+ return ret;
+
+ if (!tb[NL80211_NAN_PEER_MAP_ATTR_MAP_ID] ||
+ !tb[NL80211_NAN_PEER_MAP_ATTR_TIME_SLOTS]) {
+ NL_SET_ERR_MSG(info->extack,
+ "Missing required peer map attributes");
+ return -EINVAL;
+ }
+
+ map->map_id = nla_get_u8(tb[NL80211_NAN_PEER_MAP_ATTR_MAP_ID]);
+
+ /* Parse schedule */
+ return nl80211_parse_nan_schedule(info,
+ tb[NL80211_NAN_PEER_MAP_ATTR_TIME_SLOTS],
+ map->schedule, n_channels);
+}
+
+static int nl80211_nan_validate_map_pair(struct wiphy *wiphy,
+ struct genl_info *info,
+ const struct cfg80211_nan_peer_map *map1,
+ const struct cfg80211_nan_peer_map *map2,
+ struct cfg80211_nan_channel *nan_channels)
+{
+ /* Check for duplicate map_id */
+ if (map1->map_id == map2->map_id) {
+ NL_SET_ERR_MSG_FMT(info->extack, "Duplicate map_id %u",
+ map1->map_id);
+ return -EINVAL;
+ }
+
+ /* Check for compatible channels between maps */
+ for (int i = 0; i < ARRAY_SIZE(map1->schedule); i++) {
+ if (map1->schedule[i] == NL80211_NAN_SCHED_NOT_AVAIL_SLOT)
+ continue;
+
+ for (int j = 0; j < ARRAY_SIZE(map2->schedule); j++) {
+ u8 ch1 = map1->schedule[i];
+ u8 ch2 = map2->schedule[j];
+
+ if (ch2 == NL80211_NAN_SCHED_NOT_AVAIL_SLOT)
+ continue;
+
+ if (cfg80211_chandef_compatible(&nan_channels[ch1].chandef,
+ &nan_channels[ch2].chandef)) {
+ NL_SET_ERR_MSG_FMT(info->extack,
+ "Maps %u and %u have compatible channels %d and %d",
+ map1->map_id, map2->map_id,
+ ch1, ch2);
+ return -EINVAL;
+ }
+ }
+ }
+
+ /*
+ * Check for conflicting time slots between maps.
+ * Only check for single-radio devices (n_radio <= 1) which cannot
+ * operate on multiple channels simultaneously.
+ */
+ if (wiphy->n_radio > 1)
+ return 0;
+
+ for (int i = 0; i < ARRAY_SIZE(map1->schedule); i++) {
+ if (map1->schedule[i] != NL80211_NAN_SCHED_NOT_AVAIL_SLOT &&
+ map2->schedule[i] != NL80211_NAN_SCHED_NOT_AVAIL_SLOT) {
+ NL_SET_ERR_MSG_FMT(info->extack,
+ "Maps %u and %u both schedule slot %d",
+ map1->map_id, map2->map_id, i);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int nl80211_nan_set_peer_sched(struct sk_buff *skb,
+ struct genl_info *info)
+{
+ struct cfg80211_registered_device *rdev = info->user_ptr[0];
+ struct cfg80211_nan_channel *nan_channels __free(kfree) = NULL;
+ struct cfg80211_nan_peer_sched sched = {};
+ struct wireless_dev *wdev = info->user_ptr[1];
+ struct nlattr *map_attr, *channel;
+ int ret, n_maps = 0, n_channels = 0, i = 0, rem;
+
+ if (wdev->iftype != NL80211_IFTYPE_NAN)
+ return -EOPNOTSUPP;
+
+ if (!info->attrs[NL80211_ATTR_MAC] ||
+ !info->attrs[NL80211_ATTR_NAN_COMMITTED_DW]) {
+ NL_SET_ERR_MSG(info->extack,
+ "Required NAN peer schedule attributes are missing");
+ return -EINVAL;
+ }
+
+ /* First count how many channel attributes we got */
+ nlmsg_for_each_attr_type(channel, NL80211_ATTR_NAN_CHANNEL,
+ info->nlhdr, GENL_HDRLEN, rem)
+ n_channels++;
+
+ if (!((info->attrs[NL80211_ATTR_NAN_SEQ_ID] &&
+ info->attrs[NL80211_ATTR_NAN_PEER_MAPS] && n_channels) ||
+ ((!info->attrs[NL80211_ATTR_NAN_SEQ_ID] &&
+ !info->attrs[NL80211_ATTR_NAN_PEER_MAPS] && !n_channels)))) {
+ NL_SET_ERR_MSG(info->extack,
+ "Either provide all of: seq id, channels and maps, or none");
+ return -EINVAL;
+ }
+
+ /*
+ * Limit the number of peer channels to:
+ * local_channels * 4 (possible BWs) * 2 (possible NSS values)
+ */
+ if (n_channels && n_channels > wdev->u.nan.n_channels * 4 * 2) {
+ NL_SET_ERR_MSG_FMT(info->extack,
+ "Too many peer channels: %d (max %d)",
+ n_channels,
+ wdev->u.nan.n_channels * 4 * 2);
+ return -EINVAL;
+ }
+
+ if (n_channels) {
+ nan_channels = kcalloc(n_channels, sizeof(*nan_channels),
+ GFP_KERNEL);
+ if (!nan_channels)
+ return -ENOMEM;
+ }
+
+ /* Parse peer channels */
+ nlmsg_for_each_attr_type(channel, NL80211_ATTR_NAN_CHANNEL,
+ info->nlhdr, GENL_HDRLEN, rem) {
+ bool compatible = false;
+
+ ret = nl80211_parse_nan_channel(rdev, channel, info,
+ nan_channels, i, false);
+ if (ret)
+ return ret;
+
+ /* Verify channel is compatible with at least one local channel */
+ for (int j = 0; j < wdev->u.nan.n_channels; j++) {
+ if (cfg80211_chandef_compatible(&nan_channels[i].chandef,
+ &wdev->u.nan.chandefs[j])) {
+ compatible = true;
+ break;
+ }
+ }
+ if (!compatible) {
+ NL_SET_ERR_MSG_FMT(info->extack,
+ "Channel %d not compatible with any local channel",
+ i);
+ return -EINVAL;
+ }
+ i++;
+ }
+
+ sched.n_channels = n_channels;
+ sched.nan_channels = nan_channels;
+ sched.peer_addr = nla_data(info->attrs[NL80211_ATTR_MAC]);
+ sched.seq_id = nla_get_u8_default(info->attrs[NL80211_ATTR_NAN_SEQ_ID], 0);
+ sched.committed_dw = nla_get_u16(info->attrs[NL80211_ATTR_NAN_COMMITTED_DW]);
+ sched.max_chan_switch =
+ nla_get_u16_default(info->attrs[NL80211_ATTR_NAN_MAX_CHAN_SWITCH_TIME], 0);
+
+ if (info->attrs[NL80211_ATTR_NAN_ULW]) {
+ sched.ulw_size = nla_len(info->attrs[NL80211_ATTR_NAN_ULW]);
+ sched.init_ulw = nla_data(info->attrs[NL80211_ATTR_NAN_ULW]);
+ }
+
+ /* Initialize all maps as invalid */
+ for (int j = 0; j < ARRAY_SIZE(sched.maps); j++)
+ sched.maps[j].map_id = CFG80211_NAN_INVALID_MAP_ID;
+
+ if (info->attrs[NL80211_ATTR_NAN_PEER_MAPS]) {
+ /* Parse each map */
+ nla_for_each_nested(map_attr, info->attrs[NL80211_ATTR_NAN_PEER_MAPS],
+ rem) {
+ if (n_maps >= ARRAY_SIZE(sched.maps)) {
+ NL_SET_ERR_MSG(info->extack, "Too many peer maps");
+ return -EINVAL;
+ }
+
+ ret = nl80211_parse_nan_peer_map(info, map_attr,
+ &sched.maps[n_maps],
+ n_channels);
+ if (ret)
+ return ret;
+
+ /* Validate against previous maps */
+ for (int j = 0; j < n_maps; j++) {
+ ret = nl80211_nan_validate_map_pair(&rdev->wiphy, info,
+ &sched.maps[j],
+ &sched.maps[n_maps],
+ nan_channels);
+ if (ret)
+ return ret;
+ }
+
+ n_maps++;
+ }
+ }
+
+ /* Verify each channel is scheduled at least once */
+ for (int ch = 0; ch < n_channels; ch++) {
+ bool scheduled = false;
+
+ for (int m = 0; m < n_maps && !scheduled; m++) {
+ for (int s = 0; s < ARRAY_SIZE(sched.maps[m].schedule); s++) {
+ if (sched.maps[m].schedule[s] == ch) {
+ scheduled = true;
+ break;
+ }
+ }
+ }
+ if (!scheduled) {
+ NL_SET_ERR_MSG_FMT(info->extack,
+ "Channel %d is not scheduled in any map",
+ ch);
+ return -EINVAL;
+ }
+ }
+
+ return rdev_nan_set_peer_sched(rdev, wdev, &sched);
+}
+
+static bool nl80211_nan_is_sched_empty(struct cfg80211_nan_local_sched *sched)
+{
+ if (!sched->n_channels)
+ return true;
+
+ for (int i = 0; i < ARRAY_SIZE(sched->schedule); i++) {
+ if (sched->schedule[i] != NL80211_NAN_SCHED_NOT_AVAIL_SLOT)
+ return false;
+ }
+
+ return true;
+}
+
+static int nl80211_nan_set_local_sched(struct sk_buff *skb,
+ struct genl_info *info)
+{
+ struct cfg80211_registered_device *rdev = info->user_ptr[0];
+ struct cfg80211_nan_local_sched *sched __free(kfree) = NULL;
+ struct wireless_dev *wdev = info->user_ptr[1];
+ int rem, i = 0, n_channels = 0, ret;
+ struct nlattr *channel;
+ bool sched_empty;
+
+ if (wdev->iftype != NL80211_IFTYPE_NAN)
+ return -EOPNOTSUPP;
+
+ if (!wdev_running(wdev))
+ return -ENOTCONN;
+
+ if (!info->attrs[NL80211_ATTR_NAN_TIME_SLOTS])
+ return -EINVAL;
+
+ /* First count how many channel attributes we got */
+ nlmsg_for_each_attr_type(channel, NL80211_ATTR_NAN_CHANNEL,
+ info->nlhdr, GENL_HDRLEN, rem)
+ n_channels++;
+
+ sched = kzalloc(struct_size(sched, nan_channels, n_channels),
+ GFP_KERNEL);
+ if (!sched)
+ return -ENOMEM;
+
+ sched->n_channels = n_channels;
+
+ nlmsg_for_each_attr_type(channel, NL80211_ATTR_NAN_CHANNEL,
+ info->nlhdr, GENL_HDRLEN, rem) {
+ ret = nl80211_parse_nan_channel(rdev, channel, info,
+ sched->nan_channels, i, true);
+
+ if (ret)
+ return ret;
+ i++;
+ }
+
+ /* Parse and validate schedule */
+ ret = nl80211_parse_nan_schedule(info,
+ info->attrs[NL80211_ATTR_NAN_TIME_SLOTS],
+ sched->schedule, sched->n_channels);
+ if (ret)
+ return ret;
+
+ sched_empty = nl80211_nan_is_sched_empty(sched);
+
+ sched->deferred =
+ nla_get_flag(info->attrs[NL80211_ATTR_NAN_SCHED_DEFERRED]);
+
+ if (sched_empty) {
+ if (sched->deferred) {
+ NL_SET_ERR_MSG(info->extack,
+ "Schedule cannot be deferred if all time slots are unavailable");
+ return -EINVAL;
+ }
+
+ if (info->attrs[NL80211_ATTR_NAN_AVAIL_BLOB]) {
+ NL_SET_ERR_MSG(info->extack,
+ "NAN Availability blob must be empty if all time slots are unavailable");
+ return -EINVAL;
+ }
+ } else {
+ if (!info->attrs[NL80211_ATTR_NAN_AVAIL_BLOB]) {
+ NL_SET_ERR_MSG(info->extack,
+ "NAN Availability blob attribute is required");
+ return -EINVAL;
+ }
+
+ sched->nan_avail_blob =
+ nla_data(info->attrs[NL80211_ATTR_NAN_AVAIL_BLOB]);
+ sched->nan_avail_blob_len =
+ nla_len(info->attrs[NL80211_ATTR_NAN_AVAIL_BLOB]);
+ }
+
+ return cfg80211_nan_set_local_schedule(rdev, wdev, sched);
+}
+
static int nl80211_get_protocol_features(struct sk_buff *skb,
struct genl_info *info)
{
@@ -18096,7 +18836,11 @@ nl80211_epcs_cfg(struct sk_buff *skb, struct genl_info *info)
NL80211_FLAG_NEED_RTNL) \
SELECTOR(__sel, WIPHY_CLEAR, \
NL80211_FLAG_NEED_WIPHY | \
- NL80211_FLAG_CLEAR_SKB)
+ NL80211_FLAG_CLEAR_SKB) \
+ SELECTOR(__sel, WDEV_UP_RTNL_NOMTX, \
+ NL80211_FLAG_NEED_WDEV_UP | \
+ NL80211_FLAG_NO_WIPHY_MTX | \
+ NL80211_FLAG_NEED_RTNL)
enum nl80211_internal_flags_selector {
#define SELECTOR(_, name, value) NL80211_IFL_SEL_##name,
@@ -18933,6 +19677,7 @@ static const struct genl_small_ops nl80211_small_ops[] = {
.doit = nl80211_stop_nan,
.flags = GENL_ADMIN_PERM,
.internal_flags = IFLAGS(NL80211_FLAG_NEED_WDEV_UP |
+ NL80211_FLAG_NO_WIPHY_MTX |
NL80211_FLAG_NEED_RTNL),
},
{
@@ -19227,6 +19972,18 @@ static const struct genl_small_ops nl80211_small_ops[] = {
.flags = GENL_UNS_ADMIN_PERM,
.internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV_UP),
},
+ {
+ .cmd = NL80211_CMD_NAN_SET_LOCAL_SCHED,
+ .doit = nl80211_nan_set_local_sched,
+ .flags = GENL_ADMIN_PERM,
+ .internal_flags = IFLAGS(NL80211_FLAG_NEED_WDEV_UP),
+ },
+ {
+ .cmd = NL80211_CMD_NAN_SET_PEER_SCHED,
+ .doit = nl80211_nan_set_peer_sched,
+ .flags = GENL_ADMIN_PERM,
+ .internal_flags = IFLAGS(NL80211_FLAG_NEED_WDEV_UP),
+ },
};
static struct genl_family nl80211_fam __ro_after_init = {
@@ -20527,7 +21284,7 @@ static bool __nl80211_unexpected_frame(struct net_device *dev, u8 cmd,
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
struct sk_buff *msg;
void *hdr;
- u32 nlportid = READ_ONCE(wdev->ap_unexpected_nlportid);
+ u32 nlportid = READ_ONCE(wdev->unexpected_nlportid);
if (!nlportid)
return false;
@@ -20567,7 +21324,8 @@ bool cfg80211_rx_spurious_frame(struct net_device *dev, const u8 *addr,
trace_cfg80211_rx_spurious_frame(dev, addr, link_id);
if (WARN_ON(wdev->iftype != NL80211_IFTYPE_AP &&
- wdev->iftype != NL80211_IFTYPE_P2P_GO)) {
+ wdev->iftype != NL80211_IFTYPE_P2P_GO &&
+ wdev->iftype != NL80211_IFTYPE_NAN_DATA)) {
trace_cfg80211_return_bool(false);
return false;
}
@@ -22137,6 +22895,97 @@ void cfg80211_nan_cluster_joined(struct wireless_dev *wdev,
}
EXPORT_SYMBOL(cfg80211_nan_cluster_joined);
+void cfg80211_nan_ulw_update(struct wireless_dev *wdev,
+ const u8 *ulw, size_t ulw_len, gfp_t gfp)
+{
+ struct wiphy *wiphy = wdev->wiphy;
+ struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
+ struct sk_buff *msg;
+ void *hdr;
+
+ trace_cfg80211_nan_ulw_update(wiphy, wdev, ulw, ulw_len);
+
+ if (!wdev->owner_nlportid)
+ return;
+
+ /* 32 for the wiphy idx, 64 for the wdev id, 100 for padding */
+ msg = nlmsg_new(nla_total_size(sizeof(u32)) +
+ nla_total_size(ulw_len) +
+ nla_total_size(sizeof(u64)) + 100,
+ gfp);
+ if (!msg)
+ return;
+
+ hdr = nl80211hdr_put(msg, 0, 0, 0, NL80211_CMD_NAN_ULW_UPDATE);
+ if (!hdr)
+ goto nla_put_failure;
+
+ if (nla_put_u32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx) ||
+ nla_put_u64_64bit(msg, NL80211_ATTR_WDEV, wdev_id(wdev),
+ NL80211_ATTR_PAD) ||
+ (ulw && ulw_len &&
+ nla_put(msg, NL80211_ATTR_NAN_ULW, ulw_len, ulw)))
+ goto nla_put_failure;
+
+ genlmsg_end(msg, hdr);
+
+ genlmsg_unicast(wiphy_net(wiphy), msg, wdev->owner_nlportid);
+
+ return;
+
+ nla_put_failure:
+ nlmsg_free(msg);
+}
+EXPORT_SYMBOL(cfg80211_nan_ulw_update);
+
+void cfg80211_nan_channel_evac(struct wireless_dev *wdev,
+ const struct cfg80211_chan_def *chandef,
+ gfp_t gfp)
+{
+ struct wiphy *wiphy = wdev->wiphy;
+ struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
+ struct sk_buff *msg;
+ struct nlattr *chan_attr;
+ void *hdr;
+
+ trace_cfg80211_nan_channel_evac(wiphy, wdev, chandef);
+
+ if (!wdev->owner_nlportid)
+ return;
+
+ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, gfp);
+ if (!msg)
+ return;
+
+ hdr = nl80211hdr_put(msg, 0, 0, 0, NL80211_CMD_NAN_CHANNEL_EVAC);
+ if (!hdr)
+ goto nla_put_failure;
+
+ if (nla_put_u32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx) ||
+ nla_put_u64_64bit(msg, NL80211_ATTR_WDEV, wdev_id(wdev),
+ NL80211_ATTR_PAD))
+ goto nla_put_failure;
+
+ chan_attr = nla_nest_start(msg, NL80211_ATTR_NAN_CHANNEL);
+ if (!chan_attr)
+ goto nla_put_failure;
+
+ if (nl80211_send_chandef(msg, chandef))
+ goto nla_put_failure;
+
+ nla_nest_end(msg, chan_attr);
+
+ genlmsg_end(msg, hdr);
+
+ genlmsg_unicast(wiphy_net(wiphy), msg, wdev->owner_nlportid);
+
+ return;
+
+ nla_put_failure:
+ nlmsg_free(msg);
+}
+EXPORT_SYMBOL(cfg80211_nan_channel_evac);
+
/* initialisation/exit functions */
int __init nl80211_init(void)
diff --git a/net/wireless/rdev-ops.h b/net/wireless/rdev-ops.h
index 2bad8b60b7c9..bba239a068f6 100644
--- a/net/wireless/rdev-ops.h
+++ b/net/wireless/rdev-ops.h
@@ -1060,6 +1060,38 @@ rdev_nan_change_conf(struct cfg80211_registered_device *rdev,
return ret;
}
+static inline int
+rdev_nan_set_local_sched(struct cfg80211_registered_device *rdev,
+ struct wireless_dev *wdev,
+ struct cfg80211_nan_local_sched *sched)
+{
+ int ret;
+
+ trace_rdev_nan_set_local_sched(&rdev->wiphy, wdev, sched);
+ if (rdev->ops->nan_set_local_sched)
+ ret = rdev->ops->nan_set_local_sched(&rdev->wiphy, wdev, sched);
+ else
+ ret = -EOPNOTSUPP;
+ trace_rdev_return_int(&rdev->wiphy, ret);
+ return ret;
+}
+
+static inline int
+rdev_nan_set_peer_sched(struct cfg80211_registered_device *rdev,
+ struct wireless_dev *wdev,
+ struct cfg80211_nan_peer_sched *sched)
+{
+ int ret;
+
+ trace_rdev_nan_set_peer_sched(&rdev->wiphy, wdev, sched);
+ if (rdev->ops->nan_set_peer_sched)
+ ret = rdev->ops->nan_set_peer_sched(&rdev->wiphy, wdev, sched);
+ else
+ ret = -EOPNOTSUPP;
+ trace_rdev_return_int(&rdev->wiphy, ret);
+ return ret;
+}
+
static inline int rdev_set_mac_acl(struct cfg80211_registered_device *rdev,
struct net_device *dev,
struct cfg80211_acl_data *params)
diff --git a/net/wireless/reg.c b/net/wireless/reg.c
index 20bba7e491c5..5db2121c0b57 100644
--- a/net/wireless/reg.c
+++ b/net/wireless/reg.c
@@ -2348,6 +2348,18 @@ static bool reg_wdev_chan_valid(struct wiphy *wiphy, struct wireless_dev *wdev)
if (!wdev->netdev || !netif_running(wdev->netdev))
return true;
+ /* NAN doesn't have links, handle it separately */
+ if (iftype == NL80211_IFTYPE_NAN) {
+ for (int i = 0; i < wdev->u.nan.n_channels; i++) {
+ ret = cfg80211_reg_can_beacon(wiphy,
+ &wdev->u.nan.chandefs[i],
+ NL80211_IFTYPE_NAN);
+ if (!ret)
+ return false;
+ }
+ return true;
+ }
+
for (link = 0; link < ARRAY_SIZE(wdev->links); link++) {
struct ieee80211_channel *chan;
@@ -2397,9 +2409,9 @@ static bool reg_wdev_chan_valid(struct wiphy *wiphy, struct wireless_dev *wdev)
continue;
chandef = wdev->u.ocb.chandef;
break;
- case NL80211_IFTYPE_NAN:
- /* we have no info, but NAN is also pretty universal */
- continue;
+ case NL80211_IFTYPE_NAN_DATA:
+ /* NAN channels are checked in NL80211_IFTYPE_NAN interface */
+ break;
default:
/* others not implemented for now */
WARN_ON_ONCE(1);
@@ -2436,11 +2448,14 @@ static void reg_leave_invalid_chans(struct wiphy *wiphy)
struct wireless_dev *wdev;
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
- guard(wiphy)(wiphy);
+ list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
+ bool valid;
- list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list)
- if (!reg_wdev_chan_valid(wiphy, wdev))
+ scoped_guard(wiphy, wiphy)
+ valid = reg_wdev_chan_valid(wiphy, wdev);
+ if (!valid)
cfg80211_leave(rdev, wdev, -1);
+ }
}
static void reg_check_chans_work(struct work_struct *work)
diff --git a/net/wireless/sysfs.c b/net/wireless/sysfs.c
index 3385a27468f7..d45ddc457c30 100644
--- a/net/wireless/sysfs.c
+++ b/net/wireless/sysfs.c
@@ -102,25 +102,26 @@ static int wiphy_suspend(struct device *dev)
if (!rdev->wiphy.registered)
goto out_unlock_rtnl;
- wiphy_lock(&rdev->wiphy);
if (rdev->wiphy.wowlan_config) {
- cfg80211_process_wiphy_works(rdev, NULL);
- if (rdev->ops->suspend)
- ret = rdev_suspend(rdev, rdev->wiphy.wowlan_config);
- if (ret <= 0)
- goto out_unlock_wiphy;
+ scoped_guard(wiphy, &rdev->wiphy) {
+ cfg80211_process_wiphy_works(rdev, NULL);
+ if (rdev->ops->suspend)
+ ret = rdev_suspend(rdev,
+ rdev->wiphy.wowlan_config);
+ if (ret <= 0)
+ goto out_unlock_rtnl;
+ }
}
/* Driver refused to configure wowlan (ret = 1) or no wowlan */
cfg80211_leave_all(rdev);
- cfg80211_process_rdev_events(rdev);
- cfg80211_process_wiphy_works(rdev, NULL);
- if (rdev->ops->suspend)
- ret = rdev_suspend(rdev, NULL);
-
-out_unlock_wiphy:
- wiphy_unlock(&rdev->wiphy);
+ scoped_guard(wiphy, &rdev->wiphy) {
+ cfg80211_process_rdev_events(rdev);
+ cfg80211_process_wiphy_works(rdev, NULL);
+ if (rdev->ops->suspend)
+ ret = rdev_suspend(rdev, NULL);
+ }
out_unlock_rtnl:
if (ret == 0)
rdev->suspended = true;
diff --git a/net/wireless/trace.h b/net/wireless/trace.h
index af23f4fca90a..eb5bedf9c92a 100644
--- a/net/wireless/trace.h
+++ b/net/wireless/trace.h
@@ -2410,6 +2410,55 @@ TRACE_EVENT(rdev_del_nan_func,
WIPHY_PR_ARG, WDEV_PR_ARG, __entry->cookie)
);
+TRACE_EVENT(rdev_nan_set_local_sched,
+ TP_PROTO(struct wiphy *wiphy, struct wireless_dev *wdev,
+ struct cfg80211_nan_local_sched *sched),
+ TP_ARGS(wiphy, wdev, sched),
+ TP_STRUCT__entry(
+ WIPHY_ENTRY
+ WDEV_ENTRY
+ __array(u8, schedule, CFG80211_NAN_SCHED_NUM_TIME_SLOTS)
+ ),
+ TP_fast_assign(
+ WIPHY_ASSIGN;
+ WDEV_ASSIGN;
+ memcpy(__entry->schedule, sched->schedule,
+ CFG80211_NAN_SCHED_NUM_TIME_SLOTS);
+ ),
+ TP_printk(WIPHY_PR_FMT ", " WDEV_PR_FMT ", schedule: %s",
+ WIPHY_PR_ARG, WDEV_PR_ARG,
+ __print_array(__entry->schedule,
+ CFG80211_NAN_SCHED_NUM_TIME_SLOTS, 1))
+);
+
+TRACE_EVENT(rdev_nan_set_peer_sched,
+ TP_PROTO(struct wiphy *wiphy, struct wireless_dev *wdev,
+ struct cfg80211_nan_peer_sched *sched),
+ TP_ARGS(wiphy, wdev, sched),
+ TP_STRUCT__entry(
+ WIPHY_ENTRY
+ WDEV_ENTRY
+ __array(u8, peer_addr, ETH_ALEN)
+ __field(u8, seq_id)
+ __field(u16, committed_dw)
+ __field(u16, max_chan_switch)
+ ),
+ TP_fast_assign(
+ WIPHY_ASSIGN;
+ WDEV_ASSIGN;
+ memcpy(__entry->peer_addr, sched->peer_addr, ETH_ALEN);
+ __entry->seq_id = sched->seq_id;
+ __entry->committed_dw = sched->committed_dw;
+ __entry->max_chan_switch = sched->max_chan_switch;
+ ),
+ TP_printk(WIPHY_PR_FMT ", " WDEV_PR_FMT
+ ", peer: %pM, seq_id: %u, committed_dw: 0x%x, max_chan_switch: %u",
+ WIPHY_PR_ARG, WDEV_PR_ARG, __entry->peer_addr,
+ __entry->seq_id, __entry->committed_dw,
+ __entry->max_chan_switch
+ )
+);
+
TRACE_EVENT(rdev_set_mac_acl,
TP_PROTO(struct wiphy *wiphy, struct net_device *netdev,
struct cfg80211_acl_data *params),
@@ -4276,6 +4325,62 @@ TRACE_EVENT(cfg80211_incumbent_signal_notify,
TP_printk(WIPHY_PR_FMT ", " CHAN_DEF_PR_FMT ", signal_interference_bitmap=0x%x",
WIPHY_PR_ARG, CHAN_DEF_PR_ARG, __entry->signal_interference_bitmap)
);
+
+TRACE_EVENT(cfg80211_nan_sched_update_done,
+ TP_PROTO(struct wiphy *wiphy, struct wireless_dev *wdev, bool success),
+ TP_ARGS(wiphy, wdev, success),
+ TP_STRUCT__entry(
+ WIPHY_ENTRY
+ WDEV_ENTRY
+ __field(bool, success)
+ ),
+ TP_fast_assign(
+ WIPHY_ASSIGN;
+ WDEV_ASSIGN;
+ __entry->success = success;
+ ),
+ TP_printk(WIPHY_PR_FMT ", " WDEV_PR_FMT " success=%d",
+ WIPHY_PR_ARG, WDEV_PR_ARG, __entry->success)
+);
+
+TRACE_EVENT(cfg80211_nan_ulw_update,
+ TP_PROTO(struct wiphy *wiphy, struct wireless_dev *wdev,
+ const u8 *ulw, size_t ulw_len),
+ TP_ARGS(wiphy, wdev, ulw, ulw_len),
+ TP_STRUCT__entry(
+ WIPHY_ENTRY
+ WDEV_ENTRY
+ __dynamic_array(u8, ulw, ulw_len)
+ ),
+ TP_fast_assign(
+ WIPHY_ASSIGN;
+ WDEV_ASSIGN;
+ if (ulw && ulw_len)
+ memcpy(__get_dynamic_array(ulw), ulw, ulw_len);
+ ),
+ TP_printk(WIPHY_PR_FMT ", " WDEV_PR_FMT " ulw: %s",
+ WIPHY_PR_ARG, WDEV_PR_ARG,
+ __print_array(__get_dynamic_array(ulw),
+ __get_dynamic_array_len(ulw), 1))
+);
+
+TRACE_EVENT(cfg80211_nan_channel_evac,
+ TP_PROTO(struct wiphy *wiphy, struct wireless_dev *wdev,
+ const struct cfg80211_chan_def *chandef),
+ TP_ARGS(wiphy, wdev, chandef),
+ TP_STRUCT__entry(
+ WDEV_ENTRY
+ WIPHY_ENTRY
+ CHAN_DEF_ENTRY
+ ),
+ TP_fast_assign(
+ WDEV_ASSIGN;
+ WIPHY_ASSIGN;
+ CHAN_DEF_ASSIGN(chandef);
+ ),
+ TP_printk(WDEV_PR_FMT ", " WIPHY_PR_FMT ", " CHAN_DEF_PR_FMT,
+ WDEV_PR_ARG, WIPHY_PR_ARG, CHAN_DEF_PR_ARG)
+);
#endif /* !__RDEV_OPS_TRACE || TRACE_HEADER_MULTI_READ */
#undef TRACE_INCLUDE_PATH
diff --git a/net/wireless/util.c b/net/wireless/util.c
index 0a0cea018fc5..cff5a1bd95cc 100644
--- a/net/wireless/util.c
+++ b/net/wireless/util.c
@@ -90,7 +90,7 @@ u32 ieee80211_channel_to_freq_khz(int chan, enum nl80211_band band)
/* see 802.11ax D6.1 27.3.23.2 */
if (chan == 2)
return MHZ_TO_KHZ(5935);
- if (chan <= 233)
+ if (chan <= 253)
return MHZ_TO_KHZ(5950 + chan * 5);
break;
case NL80211_BAND_60GHZ:
@@ -625,8 +625,9 @@ int ieee80211_data_to_8023_exthdr(struct sk_buff *skb, struct ethhdr *ehdr,
case cpu_to_le16(0):
if (iftype != NL80211_IFTYPE_ADHOC &&
iftype != NL80211_IFTYPE_STATION &&
- iftype != NL80211_IFTYPE_OCB)
- return -1;
+ iftype != NL80211_IFTYPE_OCB &&
+ iftype != NL80211_IFTYPE_NAN_DATA)
+ return -1;
break;
}
@@ -1144,8 +1145,15 @@ void cfg80211_process_wdev_events(struct wireless_dev *wdev)
ev->ij.channel);
break;
case EVENT_STOPPED:
- cfg80211_leave(wiphy_to_rdev(wdev->wiphy), wdev,
- ev->link_id);
+ /*
+ * for NAN interfaces cfg80211_leave must be called but
+ * locking here doesn't allow this.
+ */
+ if (WARN_ON(wdev->iftype == NL80211_IFTYPE_NAN))
+ break;
+
+ cfg80211_leave_locked(wiphy_to_rdev(wdev->wiphy), wdev,
+ ev->link_id);
break;
case EVENT_PORT_AUTHORIZED:
__cfg80211_port_authorized(wdev, ev->pa.peer_addr,
@@ -1184,6 +1192,13 @@ int cfg80211_change_iface(struct cfg80211_registered_device *rdev,
if (otype == NL80211_IFTYPE_AP_VLAN)
return -EOPNOTSUPP;
+ /*
+ * for NAN interfaces cfg80211_leave must be called for leaving,
+ * but locking here doesn't allow this.
+ */
+ if (otype == NL80211_IFTYPE_NAN)
+ return -EOPNOTSUPP;
+
/* cannot change into P2P device or NAN */
if (ntype == NL80211_IFTYPE_P2P_DEVICE ||
ntype == NL80211_IFTYPE_NAN)
@@ -1204,7 +1219,7 @@ int cfg80211_change_iface(struct cfg80211_registered_device *rdev,
dev->ieee80211_ptr->use_4addr = false;
rdev_set_qos_map(rdev, dev, NULL);
- cfg80211_leave(rdev, dev->ieee80211_ptr, -1);
+ cfg80211_leave_locked(rdev, dev->ieee80211_ptr, -1);
cfg80211_process_rdev_events(rdev);
cfg80211_mlme_purge_registrations(dev->ieee80211_ptr);
@@ -1232,6 +1247,7 @@ int cfg80211_change_iface(struct cfg80211_registered_device *rdev,
case NL80211_IFTYPE_OCB:
case NL80211_IFTYPE_P2P_CLIENT:
case NL80211_IFTYPE_ADHOC:
+ case NL80211_IFTYPE_NAN_DATA:
dev->priv_flags |= IFF_DONT_BRIDGE;
break;
case NL80211_IFTYPE_P2P_GO: