* [PATCH wireless-next 11/15] wifi: mac80211: add support for TX over NAN_DATA interfaces
From: Miri Korenblit @ 2026-03-25 21:15 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260325211536.910411-1-miriam.rachel.korenblit@intel.com>
Add support for TXing frames over NAN_DATA interfaces:
- find the NDI station
- populoate the addresses fields
- use NUM_NL80211_BANDS for the band, similar to NAN interfaces.
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/tx.c | 32 +++++++++++++++++++++++++++++---
1 file changed, 29 insertions(+), 3 deletions(-)
diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c
index 730c208c3bdf..b0eacab6763d 100644
--- a/net/mac80211/tx.c
+++ b/net/mac80211/tx.c
@@ -2541,6 +2541,13 @@ int ieee80211_lookup_ra_sta(struct ieee80211_sub_if_data *sdata,
if (!sta)
return -ENOLINK;
break;
+ case NL80211_IFTYPE_NAN_DATA:
+ if (is_multicast_ether_addr(skb->data)) {
+ *sta_out = ERR_PTR(-ENOENT);
+ return 0;
+ }
+ sta = sta_info_get(sdata, skb->data);
+ break;
default:
return -EINVAL;
}
@@ -2834,18 +2841,37 @@ static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata,
memcpy(hdr.addr3, sdata->u.ibss.bssid, ETH_ALEN);
hdrlen = 24;
break;
+ case NL80211_IFTYPE_NAN_DATA: {
+ struct ieee80211_sub_if_data *nmi;
+
+ /* DA SA Cluster ID */
+ memcpy(hdr.addr1, skb->data, ETH_ALEN);
+ memcpy(hdr.addr2, skb->data + ETH_ALEN, ETH_ALEN);
+ nmi = rcu_dereference(sdata->u.nan_data.nmi);
+ if (!nmi) {
+ ret = -ENOTCONN;
+ goto free;
+ }
+ memcpy(hdr.addr3, nmi->wdev.u.nan.cluster_id, ETH_ALEN);
+ hdrlen = 24;
+ break;
+ }
default:
ret = -EINVAL;
goto free;
}
if (!chanctx_conf) {
- if (!ieee80211_vif_is_mld(&sdata->vif)) {
+ if (sdata->vif.type == NL80211_IFTYPE_NAN_DATA) {
+ /* NAN operates on multiple bands */
+ band = NUM_NL80211_BANDS;
+ } else if (!ieee80211_vif_is_mld(&sdata->vif)) {
ret = -ENOTCONN;
goto free;
+ } else {
+ /* MLD transmissions must not rely on the band */
+ band = 0;
}
- /* MLD transmissions must not rely on the band */
- band = 0;
} else {
band = chanctx_conf->def.chan->band;
}
--
2.34.1
^ permalink raw reply related
* [PATCH wireless-next 10/15] wifi: mac80211: update NAN data path state on schedule changes
From: Miri Korenblit @ 2026-03-25 21:15 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260325211536.910411-1-miriam.rachel.korenblit@intel.com>
A carrier of an NDI interface is turned on when there is at least one NDI
station that: (1) correlates to this interface (2) is authorized (3) the
NAN peer to which this station belongs has at least one common slot with
the local schedule. Otherwise, it is turned off.
(common slots are slots where both schedules are active on compatible
channels.)
Implement the calculation of the carrier state and trigger it when
needed.
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/cfg.c | 10 +++-
net/mac80211/ieee80211_i.h | 1 +
net/mac80211/nan.c | 108 +++++++++++++++++++++++++++++++++++++
net/mac80211/sta_info.c | 4 ++
4 files changed, 122 insertions(+), 1 deletion(-)
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index 493f7b5cfd6f..8cdbefac1bee 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -2502,7 +2502,15 @@ static int ieee80211_add_station(struct wiphy *wiphy, struct wireless_dev *wdev,
test_sta_flag(sta, WLAN_STA_ASSOC))
rate_control_rate_init_all_links(sta);
- return sta_info_insert(sta);
+ err = sta_info_insert(sta);
+
+ /*
+ * ieee80211_nan_update_ndi_carrier was called from sta_apply_parameters,
+ * but then we did not have the STA in the list.
+ */
+ if (!err && sdata->vif.type == NL80211_IFTYPE_NAN_DATA)
+ ieee80211_nan_update_ndi_carrier(sta->sdata);
+ return err;
}
static int ieee80211_del_station(struct wiphy *wiphy, struct wireless_dev *wdev,
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 23bf00472915..2a693406294b 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -2045,6 +2045,7 @@ int ieee80211_nan_set_local_sched(struct ieee80211_sub_if_data *sdata,
int ieee80211_nan_set_peer_sched(struct ieee80211_sub_if_data *sdata,
struct cfg80211_nan_peer_sched *sched);
void ieee80211_nan_free_peer_sched(struct ieee80211_nan_peer_sched *sched);
+void ieee80211_nan_update_ndi_carrier(struct ieee80211_sub_if_data *ndi_sdata);
/* scan/BSS handling */
void ieee80211_scan_work(struct wiphy *wiphy, struct wiphy_work *work);
diff --git a/net/mac80211/nan.c b/net/mac80211/nan.c
index 5e1f9bb7c49d..4e262b624521 100644
--- a/net/mac80211/nan.c
+++ b/net/mac80211/nan.c
@@ -220,6 +220,23 @@ ieee80211_nan_remove_channel(struct ieee80211_sub_if_data *sdata,
ieee80211_free_chanctx(sdata->local, ctx, false);
}
+static void
+ieee80211_nan_update_all_ndi_carriers(struct ieee80211_local *local)
+{
+ struct ieee80211_sub_if_data *sdata;
+
+ lockdep_assert_wiphy(local->hw.wiphy);
+
+ /* Iterate all interfaces and update carrier for NDI interfaces */
+ list_for_each_entry(sdata, &local->interfaces, list) {
+ if (!ieee80211_sdata_running(sdata) ||
+ sdata->vif.type != NL80211_IFTYPE_NAN_DATA)
+ continue;
+
+ ieee80211_nan_update_ndi_carrier(sdata);
+ }
+}
+
static struct ieee80211_nan_channel *
ieee80211_nan_find_free_channel(struct ieee80211_nan_sched_cfg *sched_cfg)
{
@@ -347,6 +364,7 @@ int ieee80211_nan_set_local_sched(struct ieee80211_sub_if_data *sdata,
if (sched_cfg->deferred)
return 0;
+ ieee80211_nan_update_all_ndi_carriers(sdata->local);
bitmap_zero(sdata->u.nan.removed_channels, IEEE80211_NAN_MAX_CHANNELS);
return 0;
@@ -409,6 +427,7 @@ int ieee80211_nan_set_local_sched(struct ieee80211_sub_if_data *sdata,
bitmap_zero(sdata->u.nan.removed_channels, IEEE80211_NAN_MAX_CHANNELS);
drv_vif_cfg_changed(sdata->local, sdata, BSS_CHANGED_NAN_LOCAL_SCHED);
+ ieee80211_nan_update_all_ndi_carriers(sdata->local);
return ret;
}
@@ -423,6 +442,8 @@ void ieee80211_nan_sched_update_done(struct ieee80211_vif *vif)
if (WARN_ON(!sched_cfg->deferred))
return;
+ ieee80211_nan_update_all_ndi_carriers(sdata->local);
+
/*
* Clear the deferred flag before removing channels. Removing channels
* will trigger another schedule update to the driver, and there is no
@@ -531,6 +552,91 @@ ieee80211_nan_init_peer_map(struct ieee80211_nan_peer_sched *peer_sched,
}
}
+/*
+ * Check if the local schedule and a peer schedule have at least one common
+ * slot - a slot where both schedules are active on compatible channels.
+ */
+static bool
+ieee80211_nan_has_common_slots(struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_nan_peer_sched *peer_sched)
+{
+ for (int slot = 0; slot < CFG80211_NAN_SCHED_NUM_TIME_SLOTS; slot++) {
+ struct ieee80211_nan_channel *local_chan =
+ sdata->vif.cfg.nan_sched.schedule[slot];
+
+ if (!local_chan || !local_chan->chanctx_conf)
+ continue;
+
+ /* Check all peer maps for this slot */
+ for (int m = 0; m < CFG80211_NAN_MAX_PEER_MAPS; m++) {
+ struct ieee80211_nan_peer_map *map = &peer_sched->maps[m];
+ struct ieee80211_nan_channel *peer_chan;
+
+ if (map->map_id == CFG80211_NAN_INVALID_MAP_ID)
+ continue;
+
+ peer_chan = map->slots[slot];
+ if (!peer_chan)
+ continue;
+
+ if (local_chan->chanctx_conf == peer_chan->chanctx_conf)
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void ieee80211_nan_update_ndi_carrier(struct ieee80211_sub_if_data *ndi_sdata)
+{
+ struct ieee80211_local *local = ndi_sdata->local;
+ struct ieee80211_sub_if_data *nmi_sdata;
+ struct sta_info *sta;
+
+ lockdep_assert_wiphy(local->hw.wiphy);
+
+ if (WARN_ON(ndi_sdata->vif.type != NL80211_IFTYPE_NAN_DATA ||
+ !ndi_sdata->dev) || !ieee80211_sdata_running(ndi_sdata))
+ return;
+
+ nmi_sdata = wiphy_dereference(local->hw.wiphy, ndi_sdata->u.nan_data.nmi);
+ if (WARN_ON(!nmi_sdata))
+ return;
+
+ list_for_each_entry(sta, &local->sta_list, list) {
+ struct ieee80211_sta *nmi_sta;
+
+ if (sta->sdata != ndi_sdata ||
+ !test_sta_flag(sta, WLAN_STA_AUTHORIZED))
+ continue;
+
+ nmi_sta = wiphy_dereference(local->hw.wiphy, sta->sta.nmi);
+ if (WARN_ON(!nmi_sta) || !nmi_sta->nan_sched)
+ continue;
+
+ if (ieee80211_nan_has_common_slots(nmi_sdata, nmi_sta->nan_sched)) {
+ netif_carrier_on(ndi_sdata->dev);
+ return;
+ }
+ }
+
+ netif_carrier_off(ndi_sdata->dev);
+}
+
+static void
+ieee80211_nan_update_peer_ndis_carrier(struct ieee80211_local *local,
+ struct sta_info *nmi_sta)
+{
+ struct sta_info *sta;
+
+ lockdep_assert_wiphy(local->hw.wiphy);
+
+ list_for_each_entry(sta, &local->sta_list, list) {
+ if (rcu_access_pointer(sta->sta.nmi) == &nmi_sta->sta)
+ ieee80211_nan_update_ndi_carrier(sta->sdata);
+ }
+}
+
int ieee80211_nan_set_peer_sched(struct ieee80211_sub_if_data *sdata,
struct cfg80211_nan_peer_sched *sched)
{
@@ -592,6 +698,8 @@ int ieee80211_nan_set_peer_sched(struct ieee80211_sub_if_data *sdata,
goto out;
}
+ ieee80211_nan_update_peer_ndis_carrier(sdata->local, sta);
+
/* Success - free old schedule */
to_free = old_sched;
ret = 0;
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index dd0d92abf60d..de85a7c68bec 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -1454,6 +1454,8 @@ static int _sta_info_move_state(struct sta_info *sta,
} else if (sta->sta_state == IEEE80211_STA_AUTHORIZED) {
ieee80211_vif_dec_num_mcast(sta->sdata);
clear_bit(WLAN_STA_AUTHORIZED, &sta->_flags);
+ if (sta->sdata->vif.type == NL80211_IFTYPE_NAN_DATA)
+ ieee80211_nan_update_ndi_carrier(sta->sdata);
/*
* If we have encryption offload, flush (station) queues
@@ -1482,6 +1484,8 @@ static int _sta_info_move_state(struct sta_info *sta,
set_bit(WLAN_STA_AUTHORIZED, &sta->_flags);
ieee80211_check_fast_xmit(sta);
ieee80211_check_fast_rx(sta);
+ if (sta->sdata->vif.type == NL80211_IFTYPE_NAN_DATA)
+ ieee80211_nan_update_ndi_carrier(sta->sdata);
}
if (sta->sdata->vif.type == NL80211_IFTYPE_AP_VLAN ||
sta->sdata->vif.type == NL80211_IFTYPE_AP)
--
2.34.1
^ permalink raw reply related
* [PATCH wireless-next 09/15] wifi: mac80211: add NAN peer schedule support
From: Miri Korenblit @ 2026-03-25 21:15 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260325211536.910411-1-miriam.rachel.korenblit@intel.com>
Peer schedules specify which channels the peer is available on and when.
Add support for configuring peer NAN schedules:
- build and store the schedule and maps
- for each channel, make sure that it fits into the capabilities, and
take the minimum between it and the local compatible nan channel.
- configure the driver
Note that the removal of a peer schedule should be done by the driver
upon NMI station removal.
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
include/net/mac80211.h | 62 +++++++++-
net/mac80211/cfg.c | 13 +++
net/mac80211/driver-ops.h | 21 ++++
net/mac80211/ieee80211_i.h | 3 +
net/mac80211/nan.c | 226 ++++++++++++++++++++++++++++++++++++-
net/mac80211/sta_info.c | 4 +
net/mac80211/trace.h | 31 +++++
net/mac80211/util.c | 8 ++
8 files changed, 365 insertions(+), 3 deletions(-)
diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 8656e2cc2b75..783757f14b61 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -877,8 +877,11 @@ struct ieee80211_bss_conf {
* is irrelevant for NAN, still store it for convenience - some functions
* require it as an argument.
* @needed_rx_chains: number of RX chains needed for this NAN channel
- * @chanctx_conf: chanctx_conf assigned to this NAN channel. Will be %NULL
- * if the channel is ULWed.
+ * @chanctx_conf: chanctx_conf assigned to this NAN channel.
+ * If a local channel is being ULWed (because we needed this chanctx for
+ * something else), the local NAN channel that used this chanctx,
+ * will have this pointer set to %NULL.
+ * A peer NAN channel should never have this pointer set to %NULL.
* @channel_entry: the Channel Entry blob as defined in Wi-Fi Aware
* (TM) 4.0 specification Table 100 (Channel Entry format for the NAN
* Availability attribute).
@@ -890,6 +893,49 @@ struct ieee80211_nan_channel {
u8 channel_entry[6];
};
+/**
+ * struct ieee80211_nan_peer_map - NAN peer schedule map
+ *
+ * This stores a single map from a peer's schedule. Each peer can have
+ * multiple maps.
+ *
+ * @map_id: the map ID from the peer schedule, %CFG80211_NAN_INVALID_MAP_ID
+ * if unused
+ * @slots: mapping of time slots to channel configurations in the schedule's
+ * channels array
+ */
+struct ieee80211_nan_peer_map {
+ u8 map_id;
+ struct ieee80211_nan_channel *slots[CFG80211_NAN_SCHED_NUM_TIME_SLOTS];
+};
+
+/**
+ * struct ieee80211_nan_peer_sched - NAN peer schedule
+ *
+ * This stores the complete schedule from a peer. Contains peer-level
+ * parameters and an array of schedule maps.
+ *
+ * @seq_id: the sequence ID from the peer schedule
+ * @committed_dw: committed DW as published by the peer
+ * @max_chan_switch: maximum channel switch time in microseconds
+ * @init_ulw: initial ULWs as published by the peer (copied)
+ * @ulw_size: number of bytes in @init_ulw
+ * @maps: array of peer schedule maps. Invalid slots have map_id set to
+ * %CFG80211_NAN_INVALID_MAP_ID.
+ * @n_channels: number of valid channel entries in @channels
+ * @channels: flexible array of negotiated peer channels for this schedule
+ */
+struct ieee80211_nan_peer_sched {
+ u8 seq_id;
+ u16 committed_dw;
+ u16 max_chan_switch;
+ const u8 *init_ulw;
+ u16 ulw_size;
+ struct ieee80211_nan_peer_map maps[CFG80211_NAN_MAX_PEER_MAPS];
+ u8 n_channels;
+ struct ieee80211_nan_channel channels[] __counted_by(n_channels);
+};
+
/**
* enum mac80211_tx_info_flags - flags to describe transmission information/status
*
@@ -2625,6 +2671,7 @@ struct ieee80211_link_sta {
* @spp_amsdu: indicates whether the STA uses SPP A-MSDU or not.
* @epp_peer: indicates that the peer is an EPP peer.
* @nmi: For NDI stations, pointer to the NMI station of the peer.
+ * @nan_sched: NAN peer schedule for this station. Valid only for NMI stations.
*/
struct ieee80211_sta {
u8 addr[ETH_ALEN] __aligned(2);
@@ -2655,6 +2702,9 @@ struct ieee80211_sta {
struct ieee80211_sta __rcu *nmi;
+ /* should only be accessed with the wiphy mutex held */
+ struct ieee80211_nan_peer_sched *nan_sched;
+
/* must be last */
u8 drv_priv[] __aligned(sizeof(void *));
};
@@ -4556,6 +4606,12 @@ struct ieee80211_prep_tx_info {
* @del_nan_func: Remove a NAN function. The driver must call
* ieee80211_nan_func_terminated() with
* NL80211_NAN_FUNC_TERM_REASON_USER_REQUEST reason code upon removal.
+ * @nan_peer_sched_changed: Notifies the driver that the peer NAN schedule
+ * has changed. The new schedule is available via sta->nan_sched.
+ * Note that the channel_entry blob might not match the actual chandef
+ * since the bandwidth of the chandef is the minimum of the local and peer
+ * bandwidth. It is the driver responsibility to remove the peer schedule
+ * when the NMI station is removed.
* @can_aggregate_in_amsdu: Called in order to determine if HW supports
* aggregating two specific frames in the same A-MSDU. The relation
* between the skbs should be symmetric and transitive. Note that while
@@ -4961,6 +5017,8 @@ struct ieee80211_ops {
void (*del_nan_func)(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
u8 instance_id);
+ int (*nan_peer_sched_changed)(struct ieee80211_hw *hw,
+ struct ieee80211_sta *sta);
bool (*can_aggregate_in_amsdu)(struct ieee80211_hw *hw,
struct sk_buff *head,
struct sk_buff *skb);
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index a1089e3964bd..493f7b5cfd6f 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -5689,6 +5689,18 @@ ieee80211_set_local_nan_sched(struct wiphy *wiphy,
return ieee80211_nan_set_local_sched(sdata, sched);
}
+static int
+ieee80211_set_peer_nan_sched(struct wiphy *wiphy,
+ struct wireless_dev *wdev,
+ struct cfg80211_nan_peer_sched *sched)
+{
+ struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev);
+
+ lockdep_assert_wiphy(sdata->local->hw.wiphy);
+
+ return ieee80211_nan_set_peer_sched(sdata, sched);
+}
+
const struct cfg80211_ops mac80211_config_ops = {
.add_virtual_intf = ieee80211_add_iface,
.del_virtual_intf = ieee80211_del_iface,
@@ -5806,4 +5818,5 @@ const struct cfg80211_ops mac80211_config_ops = {
.assoc_ml_reconf = ieee80211_assoc_ml_reconf,
.set_epcs = ieee80211_set_epcs,
.nan_set_local_sched = ieee80211_set_local_nan_sched,
+ .nan_set_peer_sched = ieee80211_set_peer_nan_sched,
};
diff --git a/net/mac80211/driver-ops.h b/net/mac80211/driver-ops.h
index 51bf3c7822a7..f1c0b87fddd5 100644
--- a/net/mac80211/driver-ops.h
+++ b/net/mac80211/driver-ops.h
@@ -1793,4 +1793,25 @@ static inline int drv_set_eml_op_mode(struct ieee80211_sub_if_data *sdata,
return ret;
}
+static inline int
+drv_nan_peer_sched_changed(struct ieee80211_local *local,
+ struct ieee80211_sub_if_data *sdata,
+ struct sta_info *sta)
+{
+ int ret;
+
+ might_sleep();
+ lockdep_assert_wiphy(local->hw.wiphy);
+ check_sdata_in_driver(sdata);
+
+ if (!local->ops->nan_peer_sched_changed)
+ return -EOPNOTSUPP;
+
+ trace_drv_nan_peer_sched_changed(local, sdata, &sta->sta);
+ ret = local->ops->nan_peer_sched_changed(&local->hw, &sta->sta);
+ trace_drv_return_int(local, ret);
+
+ return ret;
+}
+
#endif /* __MAC80211_DRIVER_OPS */
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index e3a051beba6a..23bf00472915 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -2042,6 +2042,9 @@ int ieee80211_mesh_finish_csa(struct ieee80211_sub_if_data *sdata,
/* NAN code */
int ieee80211_nan_set_local_sched(struct ieee80211_sub_if_data *sdata,
struct cfg80211_nan_local_sched *sched);
+int ieee80211_nan_set_peer_sched(struct ieee80211_sub_if_data *sdata,
+ struct cfg80211_nan_peer_sched *sched);
+void ieee80211_nan_free_peer_sched(struct ieee80211_nan_peer_sched *sched);
/* scan/BSS handling */
void ieee80211_scan_work(struct wiphy *wiphy, struct wiphy_work *work);
diff --git a/net/mac80211/nan.c b/net/mac80211/nan.c
index 2fa55e9a9dab..5e1f9bb7c49d 100644
--- a/net/mac80211/nan.c
+++ b/net/mac80211/nan.c
@@ -1,12 +1,13 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* NAN mode implementation
- * Copyright(c) 2025 Intel Corporation
+ * Copyright(c) 2025-2026 Intel Corporation
*/
#include <net/mac80211.h>
#include "ieee80211_i.h"
#include "driver-ops.h"
+#include "sta_info.h"
static void
ieee80211_nan_init_channel(struct ieee80211_nan_channel *nan_channel,
@@ -96,6 +97,82 @@ ieee80211_nan_use_chanctx(struct ieee80211_sub_if_data *sdata,
return 0;
}
+static void
+ieee80211_nan_update_peer_channels(struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_chanctx_conf *removed_conf)
+{
+ struct ieee80211_local *local = sdata->local;
+ struct sta_info *sta;
+
+ lockdep_assert_wiphy(local->hw.wiphy);
+
+ list_for_each_entry(sta, &local->sta_list, list) {
+ struct ieee80211_nan_peer_sched *peer_sched;
+ int write_idx = 0;
+ bool updated = false;
+
+ if (sta->sdata != sdata)
+ continue;
+
+ peer_sched = sta->sta.nan_sched;
+ if (!peer_sched)
+ continue;
+
+ /* NULL out map slots for channels being removed */
+ for (int i = 0; i < peer_sched->n_channels; i++) {
+ if (peer_sched->channels[i].chanctx_conf != removed_conf)
+ continue;
+
+ for (int m = 0; m < CFG80211_NAN_MAX_PEER_MAPS; m++) {
+ struct ieee80211_nan_peer_map *map =
+ &peer_sched->maps[m];
+
+ if (map->map_id == CFG80211_NAN_INVALID_MAP_ID)
+ continue;
+
+ for (int s = 0; s < ARRAY_SIZE(map->slots); s++)
+ if (map->slots[s] == &peer_sched->channels[i])
+ map->slots[s] = NULL;
+ }
+ }
+
+ /* Compact channels array, removing those with removed_conf */
+ for (int i = 0; i < peer_sched->n_channels; i++) {
+ if (peer_sched->channels[i].chanctx_conf == removed_conf) {
+ updated = true;
+ continue;
+ }
+
+ if (write_idx != i) {
+ /* Update map pointers before moving */
+ for (int m = 0; m < CFG80211_NAN_MAX_PEER_MAPS; m++) {
+ struct ieee80211_nan_peer_map *map =
+ &peer_sched->maps[m];
+
+ if (map->map_id == CFG80211_NAN_INVALID_MAP_ID)
+ continue;
+
+ for (int s = 0; s < ARRAY_SIZE(map->slots); s++)
+ if (map->slots[s] == &peer_sched->channels[i])
+ map->slots[s] = &peer_sched->channels[write_idx];
+ }
+
+ peer_sched->channels[write_idx] = peer_sched->channels[i];
+ }
+ write_idx++;
+ }
+
+ /* Clear any remaining entries at the end */
+ for (int i = write_idx; i < peer_sched->n_channels; i++)
+ memset(&peer_sched->channels[i], 0, sizeof(peer_sched->channels[i]));
+
+ peer_sched->n_channels = write_idx;
+
+ if (updated)
+ drv_nan_peer_sched_changed(local, sdata, sta);
+ }
+}
+
static void
ieee80211_nan_remove_channel(struct ieee80211_sub_if_data *sdata,
struct ieee80211_nan_channel *nan_channel)
@@ -118,6 +195,10 @@ ieee80211_nan_remove_channel(struct ieee80211_sub_if_data *sdata,
conf = nan_channel->chanctx_conf;
+ /* If any peer nan schedule uses this chanctx, update them */
+ if (conf)
+ ieee80211_nan_update_peer_channels(sdata, conf);
+
memset(nan_channel, 0, sizeof(*nan_channel));
/* Update the driver before (possibly) releasing the channel context */
@@ -376,3 +457,146 @@ void ieee80211_nan_sched_update_done(struct ieee80211_vif *vif)
GFP_KERNEL);
}
EXPORT_SYMBOL(ieee80211_nan_sched_update_done);
+
+void ieee80211_nan_free_peer_sched(struct ieee80211_nan_peer_sched *sched)
+{
+ if (!sched)
+ return;
+
+ kfree(sched->init_ulw);
+ kfree(sched);
+}
+
+static int
+ieee80211_nan_init_peer_channel(struct ieee80211_sub_if_data *sdata,
+ const struct sta_info *sta,
+ const struct cfg80211_nan_channel *cfg_chan,
+ struct ieee80211_nan_channel *new_chan)
+{
+ struct ieee80211_nan_sched_cfg *sched_cfg = &sdata->vif.cfg.nan_sched;
+
+ /* Find compatible local channel */
+ for (int j = 0; j < ARRAY_SIZE(sched_cfg->channels); j++) {
+ struct ieee80211_nan_channel *local_chan =
+ &sched_cfg->channels[j];
+ const struct cfg80211_chan_def *compat;
+
+ if (!local_chan->chanreq.oper.chan)
+ continue;
+
+ compat = cfg80211_chandef_compatible(&local_chan->chanreq.oper,
+ &cfg_chan->chandef);
+ if (!compat)
+ continue;
+
+ /* compat is the wider chandef, and we want the narrower one */
+ new_chan->chanreq.oper = compat == &local_chan->chanreq.oper ?
+ cfg_chan->chandef : local_chan->chanreq.oper;
+ new_chan->needed_rx_chains = min(local_chan->needed_rx_chains,
+ cfg_chan->rx_nss);
+ new_chan->chanctx_conf = local_chan->chanctx_conf;
+
+ break;
+ }
+
+ /*
+ * nl80211 already validated that each peer channel is compatible
+ * with at least one local channel, so this should never happen.
+ */
+ if (WARN_ON(!new_chan->chanreq.oper.chan))
+ return -EINVAL;
+
+ memcpy(new_chan->channel_entry, cfg_chan->channel_entry,
+ sizeof(new_chan->channel_entry));
+
+ return 0;
+}
+
+static void
+ieee80211_nan_init_peer_map(struct ieee80211_nan_peer_sched *peer_sched,
+ const struct cfg80211_nan_peer_map *cfg_map,
+ struct ieee80211_nan_peer_map *new_map)
+{
+ new_map->map_id = cfg_map->map_id;
+
+ if (new_map->map_id == CFG80211_NAN_INVALID_MAP_ID)
+ return;
+
+ /* Set up the slots array */
+ for (int slot = 0; slot < ARRAY_SIZE(new_map->slots); slot++) {
+ u8 chan_idx = cfg_map->schedule[slot];
+
+ if (chan_idx < peer_sched->n_channels)
+ new_map->slots[slot] = &peer_sched->channels[chan_idx];
+ }
+}
+
+int ieee80211_nan_set_peer_sched(struct ieee80211_sub_if_data *sdata,
+ struct cfg80211_nan_peer_sched *sched)
+{
+ struct ieee80211_nan_peer_sched *new_sched, *old_sched, *to_free;
+ struct sta_info *sta;
+ int ret;
+
+ lockdep_assert_wiphy(sdata->local->hw.wiphy);
+
+ if (!sdata->u.nan.started)
+ return -EINVAL;
+
+ sta = sta_info_get(sdata, sched->peer_addr);
+ if (!sta)
+ return -ENOENT;
+
+ new_sched = kzalloc(struct_size(new_sched, channels, sched->n_channels),
+ GFP_KERNEL);
+ if (!new_sched)
+ return -ENOMEM;
+
+ to_free = new_sched;
+
+ new_sched->seq_id = sched->seq_id;
+ new_sched->committed_dw = sched->committed_dw;
+ new_sched->max_chan_switch = sched->max_chan_switch;
+ new_sched->n_channels = sched->n_channels;
+
+ if (sched->ulw_size && sched->init_ulw) {
+ new_sched->init_ulw = kmemdup(sched->init_ulw, sched->ulw_size,
+ GFP_KERNEL);
+ if (!new_sched->init_ulw) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ new_sched->ulw_size = sched->ulw_size;
+ }
+
+ for (int i = 0; i < sched->n_channels; i++) {
+ ret = ieee80211_nan_init_peer_channel(sdata, sta,
+ &sched->nan_channels[i],
+ &new_sched->channels[i]);
+ if (ret)
+ goto out;
+ }
+
+ for (int m = 0; m < ARRAY_SIZE(sched->maps); m++)
+ ieee80211_nan_init_peer_map(new_sched, &sched->maps[m],
+ &new_sched->maps[m]);
+
+ /* Install the new schedule before calling the driver */
+ old_sched = sta->sta.nan_sched;
+ sta->sta.nan_sched = new_sched;
+
+ ret = drv_nan_peer_sched_changed(sdata->local, sdata, sta);
+ if (ret) {
+ /* Revert to old schedule */
+ sta->sta.nan_sched = old_sched;
+ goto out;
+ }
+
+ /* Success - free old schedule */
+ to_free = old_sched;
+ ret = 0;
+
+out:
+ ieee80211_nan_free_peer_sched(to_free);
+ return ret;
+}
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index 017d91365920..dd0d92abf60d 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -1309,6 +1309,10 @@ static int __must_check __sta_info_destroy_part1(struct sta_info *sta)
continue;
sta_info_destroy_addr(sta_iter->sdata, sta_iter->addr);
}
+
+ /* Free and clear the local peer schedule */
+ ieee80211_nan_free_peer_sched(sta->sta.nan_sched);
+ sta->sta.nan_sched = NULL;
}
/*
diff --git a/net/mac80211/trace.h b/net/mac80211/trace.h
index e5968d754f8b..71cf88039bd4 100644
--- a/net/mac80211/trace.h
+++ b/net/mac80211/trace.h
@@ -3366,6 +3366,37 @@ TRACE_EVENT(drv_set_eml_op_mode,
)
);
+TRACE_EVENT(drv_nan_peer_sched_changed,
+ TP_PROTO(struct ieee80211_local *local,
+ struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_sta *sta),
+
+ TP_ARGS(local, sdata, sta),
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ VIF_ENTRY
+ STA_ENTRY
+ __array(u8, map_ids, CFG80211_NAN_MAX_PEER_MAPS)
+ ),
+
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ VIF_ASSIGN;
+ STA_ASSIGN;
+ for (int i = 0; i < CFG80211_NAN_MAX_PEER_MAPS; i++)
+ __entry->map_ids[i] = sta->nan_sched ?
+ sta->nan_sched->maps[i].map_id :
+ 0xff;
+ ),
+
+ TP_printk(
+ LOCAL_PR_FMT VIF_PR_FMT STA_PR_FMT
+ " map_ids=[%u, %u]",
+ LOCAL_PR_ARG, VIF_PR_ARG, STA_PR_ARG,
+ __entry->map_ids[0], __entry->map_ids[1]
+ )
+);
+
#endif /* !__MAC80211_DRIVER_TRACE || TRACE_HEADER_MULTI_READ */
#undef TRACE_INCLUDE_PATH
diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index a352f73a7ec4..b093bc203c81 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -1817,6 +1817,14 @@ static int ieee80211_reconfig_nan(struct ieee80211_sub_if_data *sdata)
if (WARN_ON(res))
return res;
}
+
+ /* Add peer schedules for NMI stations that have them */
+ if (!sta->sta.nan_sched)
+ continue;
+
+ res = drv_nan_peer_sched_changed(local, sdata, sta);
+ if (WARN_ON(res))
+ return res;
}
/* Add NDI stations (stations on NAN_DATA interfaces) */
--
2.34.1
^ permalink raw reply related
* [PATCH wireless-next 08/15] wifi: mac80211: support NAN stations
From: Miri Korenblit @ 2026-03-25 21:15 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260325211536.910411-1-miriam.rachel.korenblit@intel.com>
Add support for both NMI and NDI stations.
The NDI station will be linked to the NMI station of the NAN peer for
which the NDI station is added.
A peer can choose to reuse its NMI address as the NDI address.
Since different keys might be in use for NAN management and for data
frames, we will have 2 different stations, even if they'll have the same
address.
Even though there are no links in NAN, sta->deflink will still be used
to store the one set of capabilities and SMPS mode.
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
include/net/mac80211.h | 13 ++++-
net/mac80211/cfg.c | 103 ++++++++++++++++++++++++++++++++++++----
net/mac80211/he.c | 7 ++-
net/mac80211/ht.c | 19 ++++++--
net/mac80211/iface.c | 10 ++--
net/mac80211/sta_info.c | 21 +++++++-
net/mac80211/sta_info.h | 3 +-
net/mac80211/util.c | 41 ++++++++++++++++
net/mac80211/vht.c | 16 ++++++-
9 files changed, 209 insertions(+), 24 deletions(-)
diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 24685f106757..8656e2cc2b75 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -2534,11 +2534,15 @@ struct ieee80211_sta_aggregates {
* @uhr_cap: UHR capabilities of this STA
* @s1g_cap: S1G capabilities of this STA
* @agg: per-link data for multi-link aggregation
- * @bandwidth: current bandwidth the station can receive with
+ * @bandwidth: current bandwidth the station can receive with.
+ * This is the minimum between the peer's capabilities and our own
+ * operating channel width; Invalid for NAN since that is operating on
+ * multiple channels.
* @rx_nss: in HT/VHT, the maximum number of spatial streams the
* station can receive at the moment, changed by operating mode
* notifications and capabilities. The value is only valid after
- * the station moves to associated state.
+ * the station moves to associated state. Invalid for NAN since it
+ * operates on multiple configurations of rx_nss.
* @txpwr: the station tx power configuration
*
*/
@@ -2620,6 +2624,7 @@ struct ieee80211_link_sta {
* @valid_links: bitmap of valid links, or 0 for non-MLO
* @spp_amsdu: indicates whether the STA uses SPP A-MSDU or not.
* @epp_peer: indicates that the peer is an EPP peer.
+ * @nmi: For NDI stations, pointer to the NMI station of the peer.
*/
struct ieee80211_sta {
u8 addr[ETH_ALEN] __aligned(2);
@@ -2648,6 +2653,8 @@ struct ieee80211_sta {
struct ieee80211_link_sta deflink;
struct ieee80211_link_sta __rcu *link[IEEE80211_MLD_MAX_NUM_LINKS];
+ struct ieee80211_sta __rcu *nmi;
+
/* must be last */
u8 drv_priv[] __aligned(sizeof(void *));
};
@@ -2881,6 +2888,8 @@ struct ieee80211_txq {
* station has a unique address, i.e. each station entry can be identified
* by just its MAC address; this prevents, for example, the same station
* from connecting to two virtual AP interfaces at the same time.
+ * Note that this doesn't apply for NAN, in which the peer's NMI address
+ * can be equal to its NDI address.
*
* @IEEE80211_HW_SUPPORTS_REORDERING_BUFFER: Hardware (or driver) manages the
* reordering buffer internally, guaranteeing mac80211 receives frames in
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index 701b111db7df..a1089e3964bd 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -2077,7 +2077,7 @@ static int sta_link_apply_parameters(struct ieee80211_local *local,
enum sta_link_apply_mode mode,
struct link_station_parameters *params)
{
- struct ieee80211_supported_band *sband;
+ struct ieee80211_supported_band *sband = NULL;
struct ieee80211_sub_if_data *sdata = sta->sdata;
u32 link_id = params->link_id < 0 ? 0 : params->link_id;
struct ieee80211_link_data *link =
@@ -2085,6 +2085,9 @@ static int sta_link_apply_parameters(struct ieee80211_local *local,
struct link_sta_info *link_sta =
rcu_dereference_protected(sta->link[link_id],
lockdep_is_held(&local->hw.wiphy->mtx));
+ const struct ieee80211_sta_ht_cap *own_ht_cap;
+ const struct ieee80211_sta_vht_cap *own_vht_cap;
+ const struct ieee80211_sta_he_cap *own_he_cap;
bool changes = params->link_mac ||
params->txpwr_set ||
params->supported_rates_len ||
@@ -2114,10 +2117,27 @@ static int sta_link_apply_parameters(struct ieee80211_local *local,
if (!link || !link_sta)
return -EINVAL;
- sband = ieee80211_get_link_sband(link);
- if (!sband)
+ /*
+ * We should not have any changes in NDI station, its capabilities are
+ * copied from the NMI sta
+ */
+ if (WARN_ON(sdata->vif.type == NL80211_IFTYPE_NAN_DATA))
return -EINVAL;
+ if (sdata->vif.type == NL80211_IFTYPE_NAN) {
+ own_ht_cap = &local->hw.wiphy->nan_capa.phy.ht;
+ own_vht_cap = &local->hw.wiphy->nan_capa.phy.vht;
+ own_he_cap = &local->hw.wiphy->nan_capa.phy.he;
+ } else {
+ sband = ieee80211_get_link_sband(link);
+ if (!sband)
+ return -EINVAL;
+
+ own_ht_cap = &sband->ht_cap;
+ own_vht_cap = &sband->vht_cap;
+ own_he_cap = ieee80211_get_he_iftype_cap_vif(sband, &sdata->vif);
+ }
+
if (params->link_mac) {
if (mode == STA_LINK_MODE_NEW) {
memcpy(link_sta->addr, params->link_mac, ETH_ALEN);
@@ -2139,6 +2159,27 @@ static int sta_link_apply_parameters(struct ieee80211_local *local,
return ret;
}
+ if (sdata->vif.type == NL80211_IFTYPE_NAN) {
+ static const u8 all_ofdm_rates[] = {
+ 0x0c, 0x12, 0x18, 0x24, 0x30, 0x48, 0x60, 0x6c
+ };
+
+ /* Set the same supported_rates for all bands */
+ for (int i = 0; i < NUM_NL80211_BANDS; i++) {
+ struct ieee80211_supported_band *tmp =
+ sdata->local->hw.wiphy->bands[i];
+
+ if ((i != NL80211_BAND_2GHZ && i != NL80211_BAND_5GHZ) ||
+ !tmp)
+ continue;
+
+ if (!ieee80211_parse_bitrates(tmp, all_ofdm_rates,
+ sizeof(all_ofdm_rates),
+ &link_sta->pub->supp_rates[i]))
+ return -EINVAL;
+ }
+ }
+
if (params->supported_rates &&
params->supported_rates_len &&
!ieee80211_parse_bitrates(sband, params->supported_rates,
@@ -2147,22 +2188,24 @@ static int sta_link_apply_parameters(struct ieee80211_local *local,
return -EINVAL;
if (params->ht_capa)
- ieee80211_ht_cap_ie_to_sta_ht_cap(sdata, &sband->ht_cap,
+ ieee80211_ht_cap_ie_to_sta_ht_cap(sdata, own_ht_cap,
params->ht_capa, link_sta);
/* VHT can override some HT caps such as the A-MSDU max length */
if (params->vht_capa)
ieee80211_vht_cap_ie_to_sta_vht_cap(sdata, sband,
- &sband->vht_cap,
+ own_vht_cap,
params->vht_capa, NULL,
link_sta);
if (params->he_capa)
- ieee80211_he_cap_ie_to_sta_he_cap(sdata, sband,
- (void *)params->he_capa,
- params->he_capa_len,
- (void *)params->he_6ghz_capa,
- link_sta);
+ _ieee80211_he_cap_ie_to_sta_he_cap(sdata,
+ own_he_cap,
+ (void *)params->he_capa,
+ params->he_capa_len,
+ (sband && sband->band == NL80211_BAND_6GHZ) ?
+ (void *)params->he_6ghz_capa : NULL,
+ link_sta);
if (params->he_capa && params->eht_capa)
ieee80211_eht_cap_ie_to_sta_eht_cap(sdata, sband,
@@ -2349,6 +2392,32 @@ static int sta_apply_parameters(struct ieee80211_local *local,
if (params->airtime_weight)
sta->airtime_weight = params->airtime_weight;
+ if (params->nmi_mac) {
+ struct ieee80211_sub_if_data *nmi =
+ rcu_dereference_wiphy(local->hw.wiphy,
+ sdata->u.nan_data.nmi);
+ struct sta_info *nmi_sta;
+
+ if (WARN_ON(!nmi))
+ return -EINVAL;
+
+ nmi_sta = sta_info_get(nmi, params->nmi_mac);
+ if (!nmi_sta)
+ return -ENOENT;
+ rcu_assign_pointer(sta->sta.nmi, &nmi_sta->sta);
+
+ /* For NAN_DATA stations, copy capabilities from the NMI station */
+ if (!nmi_sta->deflink.pub->ht_cap.ht_supported)
+ return -EINVAL;
+
+ sta->deflink.pub->ht_cap = nmi_sta->deflink.pub->ht_cap;
+ sta->deflink.pub->vht_cap = nmi_sta->deflink.pub->vht_cap;
+ sta->deflink.pub->he_cap = nmi_sta->deflink.pub->he_cap;
+ memcpy(&sta->deflink.pub->supp_rates,
+ &nmi_sta->deflink.pub->supp_rates,
+ sizeof(sta->deflink.pub->supp_rates));
+ }
+
/* set the STA state after all sta info from usermode has been set */
if (test_sta_flag(sta, WLAN_STA_TDLS_PEER) ||
set & BIT(NL80211_STA_FLAG_ASSOCIATED)) {
@@ -2494,6 +2563,12 @@ static int ieee80211_change_station(struct wiphy *wiphy,
else
statype = CFG80211_STA_AP_CLIENT_UNASSOC;
break;
+ case NL80211_IFTYPE_NAN:
+ statype = CFG80211_STA_NAN_MGMT;
+ break;
+ case NL80211_IFTYPE_NAN_DATA:
+ statype = CFG80211_STA_NAN_DATA;
+ break;
default:
return -EOPNOTSUPP;
}
@@ -2532,6 +2607,14 @@ static int ieee80211_change_station(struct wiphy *wiphy,
}
}
+ /* NAN capabilties should not change */
+ if (statype == CFG80211_STA_NAN_DATA &&
+ sta->deflink.pub->ht_cap.ht_supported &&
+ (params->link_sta_params.ht_capa ||
+ params->link_sta_params.vht_capa ||
+ params->link_sta_params.he_capa))
+ return -EINVAL;
+
err = sta_apply_parameters(local, sta, params);
if (err)
return err;
diff --git a/net/mac80211/he.c b/net/mac80211/he.c
index 93e0342cff4f..a3e16a5bec22 100644
--- a/net/mac80211/he.c
+++ b/net/mac80211/he.c
@@ -127,6 +127,10 @@ _ieee80211_he_cap_ie_to_sta_he_cap(struct ieee80211_sub_if_data *sdata,
if (!he_cap_ie || !own_he_cap_ptr || !own_he_cap_ptr->has_he)
return;
+ /* NDI station are using the capabilities from the NMI station */
+ if (WARN_ON_ONCE(sdata->vif.type == NL80211_IFTYPE_NAN_DATA))
+ return;
+
own_he_cap = *own_he_cap_ptr;
/* Make sure size is OK */
@@ -156,7 +160,8 @@ _ieee80211_he_cap_ie_to_sta_he_cap(struct ieee80211_sub_if_data *sdata,
he_cap->has_he = true;
link_sta->cur_max_bandwidth = ieee80211_sta_cap_rx_bw(link_sta);
- link_sta->pub->bandwidth = ieee80211_sta_cur_vht_bw(link_sta);
+ if (sdata->vif.type != NL80211_IFTYPE_NAN)
+ link_sta->pub->bandwidth = ieee80211_sta_cur_vht_bw(link_sta);
if (he_6ghz_capa)
ieee80211_update_from_he_6ghz_capa(he_6ghz_capa, link_sta);
diff --git a/net/mac80211/ht.c b/net/mac80211/ht.c
index 410e2354f33a..97719298e038 100644
--- a/net/mac80211/ht.c
+++ b/net/mac80211/ht.c
@@ -154,6 +154,10 @@ bool ieee80211_ht_cap_ie_to_sta_ht_cap(struct ieee80211_sub_if_data *sdata,
if (!ht_cap_ie || !own_cap_ptr->ht_supported)
goto apply;
+ /* NDI station are using the capabilities from the NMI station */
+ if (WARN_ON_ONCE(sdata->vif.type == NL80211_IFTYPE_NAN_DATA))
+ return 0;
+
ht_cap.ht_supported = true;
own_cap = *own_cap_ptr;
@@ -254,10 +258,17 @@ bool ieee80211_ht_cap_ie_to_sta_ht_cap(struct ieee80211_sub_if_data *sdata,
rcu_read_lock();
link_conf = rcu_dereference(sdata->vif.link_conf[link_sta->link_id]);
- if (WARN_ON(!link_conf))
+ if (WARN_ON(!link_conf)) {
width = NL80211_CHAN_WIDTH_20_NOHT;
- else
+ } else if (sdata->vif.type == NL80211_IFTYPE_NAN ||
+ sdata->vif.type == NL80211_IFTYPE_NAN_DATA) {
+ /* In NAN, link_sta->bandwidth is invalid since NAN operates on
+ * multiple channels. Just take the maximum.
+ */
+ width = NL80211_CHAN_WIDTH_320;
+ } else {
width = link_conf->chanreq.oper.width;
+ }
switch (width) {
default:
@@ -285,7 +296,9 @@ bool ieee80211_ht_cap_ie_to_sta_ht_cap(struct ieee80211_sub_if_data *sdata,
IEEE80211_STA_RX_BW_40 : IEEE80211_STA_RX_BW_20;
if (sta->sdata->vif.type == NL80211_IFTYPE_AP ||
- sta->sdata->vif.type == NL80211_IFTYPE_AP_VLAN) {
+ sta->sdata->vif.type == NL80211_IFTYPE_AP_VLAN ||
+ sta->sdata->vif.type == NL80211_IFTYPE_NAN ||
+ sta->sdata->vif.type == NL80211_IFTYPE_NAN_DATA) {
enum ieee80211_smps_mode smps_mode;
switch ((ht_cap.cap & IEEE80211_HT_CAP_SM_PS)
diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c
index 507c5e016ec8..f1ab85ff326d 100644
--- a/net/mac80211/iface.c
+++ b/net/mac80211/iface.c
@@ -535,12 +535,14 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata, bool going_do
* (because if we remove a STA after ops->remove_interface()
* the driver will have removed the vif info already!)
*
- * For AP_VLANs stations may exist since there's nothing else that
- * would have removed them, but in other modes there shouldn't
- * be any stations.
+ * For AP_VLANs, NAN and NAN_DATA stations may exist since there's
+ * nothing else that would have removed them, but in other modes there
+ * shouldn't be any stations.
*/
flushed = sta_info_flush(sdata, -1);
- WARN_ON_ONCE(sdata->vif.type != NL80211_IFTYPE_AP_VLAN && flushed > 0);
+ WARN_ON_ONCE(sdata->vif.type != NL80211_IFTYPE_AP_VLAN &&
+ sdata->vif.type != NL80211_IFTYPE_NAN &&
+ sdata->vif.type != NL80211_IFTYPE_NAN_DATA && flushed > 0);
/* don't count this interface for allmulti while it is down */
if (sdata->flags & IEEE80211_SDATA_ALLMULTI)
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index 4259e9c13ed7..017d91365920 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -795,6 +795,7 @@ struct sta_info *sta_info_alloc_with_link(struct ieee80211_sub_if_data *sdata,
static int sta_info_insert_check(struct sta_info *sta)
{
struct ieee80211_sub_if_data *sdata = sta->sdata;
+ struct ieee80211_sta *same_addr_sta;
lockdep_assert_wiphy(sdata->local->hw.wiphy);
@@ -810,13 +811,18 @@ static int sta_info_insert_check(struct sta_info *sta)
!is_valid_ether_addr(sta->sta.addr)))
return -EINVAL;
+ if (!ieee80211_hw_check(&sdata->local->hw, NEEDS_UNIQUE_STA_ADDR))
+ return 0;
+
/* The RCU read lock is required by rhashtable due to
* asynchronous resize/rehash. We also require the mutex
* for correctness.
*/
rcu_read_lock();
- if (ieee80211_hw_check(&sdata->local->hw, NEEDS_UNIQUE_STA_ADDR) &&
- ieee80211_find_sta_by_ifaddr(&sdata->local->hw, sta->addr, NULL)) {
+ same_addr_sta = ieee80211_find_sta_by_ifaddr(&sdata->local->hw,
+ sta->addr, NULL);
+ /* For NAN, a peer can re-use */
+ if (same_addr_sta && same_addr_sta != rcu_access_pointer(sta->sta.nmi)) {
rcu_read_unlock();
return -ENOTUNIQ;
}
@@ -1294,6 +1300,17 @@ static int __must_check __sta_info_destroy_part1(struct sta_info *sta)
lockdep_assert_wiphy(local->hw.wiphy);
+ if (sdata->vif.type == NL80211_IFTYPE_NAN) {
+ struct sta_info *sta_iter, *tmp;
+
+ /* Remove all NDI stations associated with this NMI STA */
+ list_for_each_entry_safe(sta_iter, tmp, &local->sta_list, list) {
+ if (rcu_access_pointer(sta_iter->sta.nmi) != &sta->sta)
+ continue;
+ sta_info_destroy_addr(sta_iter->sdata, sta_iter->addr);
+ }
+ }
+
/*
* Before removing the station from the driver and
* rate control, it might still start new aggregation
diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h
index 58ccbea7f6f6..3e5d003bd31f 100644
--- a/net/mac80211/sta_info.h
+++ b/net/mac80211/sta_info.h
@@ -505,7 +505,8 @@ struct ieee80211_fragment_cache {
* @status_stats.ack_signal_filled: last ACK signal validity
* @status_stats.avg_ack_signal: average ACK signal
* @cur_max_bandwidth: maximum bandwidth to use for TX to the station,
- * taken from HT/VHT capabilities or VHT operating mode notification
+ * taken from HT/VHT capabilities or VHT operating mode notification.
+ * Invalid for NAN since that is operating on multiple bands.
* @rx_omi_bw_rx: RX OMI bandwidth restriction to apply for RX
* @rx_omi_bw_tx: RX OMI bandwidth restriction to apply for TX
* @rx_omi_bw_staging: RX OMI bandwidth restriction to apply later
diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index 925a09246ad9..a352f73a7ec4 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -1782,6 +1782,7 @@ static int ieee80211_reconfig_nan(struct ieee80211_sub_if_data *sdata)
{
struct ieee80211_local *local = sdata->local;
struct ieee80211_sub_if_data *ndi_sdata;
+ struct sta_info *sta;
int res;
res = drv_start_nan(local, sdata, &sdata->u.nan.conf);
@@ -1802,6 +1803,42 @@ static int ieee80211_reconfig_nan(struct ieee80211_sub_if_data *sdata)
}
}
+ /* Add NMI stations (stations on the NAN interface) */
+ list_for_each_entry(sta, &local->sta_list, list) {
+ enum ieee80211_sta_state state;
+
+ if (!sta->uploaded || sta->sdata != sdata)
+ continue;
+
+ for (state = IEEE80211_STA_NOTEXIST; state < sta->sta_state;
+ state++) {
+ res = drv_sta_state(local, sdata, sta, state,
+ state + 1);
+ if (WARN_ON(res))
+ return res;
+ }
+ }
+
+ /* Add NDI stations (stations on NAN_DATA interfaces) */
+ list_for_each_entry(sta, &local->sta_list, list) {
+ enum ieee80211_sta_state state;
+
+ if (!sta->uploaded ||
+ sta->sdata->vif.type != NL80211_IFTYPE_NAN_DATA)
+ continue;
+
+ if (WARN_ON(!sta->sta.nmi))
+ continue;
+
+ for (state = IEEE80211_STA_NOTEXIST; state < sta->sta_state;
+ state++) {
+ res = drv_sta_state(local, sta->sdata, sta, state,
+ state + 1);
+ if (WARN_ON(res))
+ return res;
+ }
+ }
+
return 0;
}
@@ -2060,6 +2097,10 @@ int ieee80211_reconfig(struct ieee80211_local *local)
case NL80211_IFTYPE_AP_VLAN:
case NL80211_IFTYPE_MONITOR:
break;
+ case NL80211_IFTYPE_NAN:
+ case NL80211_IFTYPE_NAN_DATA:
+ /* NAN stations are handled later */
+ break;
case NL80211_IFTYPE_ADHOC:
if (sdata->vif.cfg.ibss_joined)
WARN_ON(drv_join_ibss(local, sdata));
diff --git a/net/mac80211/vht.c b/net/mac80211/vht.c
index a6570781740a..f3bb5a561a38 100644
--- a/net/mac80211/vht.c
+++ b/net/mac80211/vht.c
@@ -133,6 +133,10 @@ ieee80211_vht_cap_ie_to_sta_vht_cap(struct ieee80211_sub_if_data *sdata,
if (!vht_cap_ie || !own_vht_cap->vht_supported)
return;
+ /* NDI station are using the capabilities from the NMI station */
+ if (WARN_ON_ONCE(sdata->vif.type == NL80211_IFTYPE_NAN_DATA))
+ return;
+
if (sband) {
/* Allow VHT if at least one channel on the sband supports 80 MHz */
bool have_80mhz = false;
@@ -320,7 +324,8 @@ ieee80211_vht_cap_ie_to_sta_vht_cap(struct ieee80211_sub_if_data *sdata,
IEEE80211_STA_RX_BW_160;
}
- link_sta->pub->bandwidth = ieee80211_sta_cur_vht_bw(link_sta);
+ if (sdata->vif.type != NL80211_IFTYPE_NAN)
+ link_sta->pub->bandwidth = ieee80211_sta_cur_vht_bw(link_sta);
/*
* Work around the Cisco 9115 FW 17.3 bug by taking the min of
@@ -373,6 +378,10 @@ __ieee80211_sta_cap_rx_bw(struct link_sta_info *link_sta,
} else {
struct ieee80211_bss_conf *link_conf;
+ if (WARN_ON_ONCE(sdata->vif.type == NL80211_IFTYPE_NAN_DATA ||
+ sdata->vif.type == NL80211_IFTYPE_NAN))
+ return IEEE80211_STA_RX_BW_20;
+
rcu_read_lock();
link_conf = rcu_dereference(sdata->vif.link_conf[link_id]);
band = link_conf->chanreq.oper.chan->band;
@@ -518,6 +527,11 @@ _ieee80211_sta_cur_vht_bw(struct link_sta_info *link_sta,
} else {
struct ieee80211_bss_conf *link_conf;
+ /* NAN operates on multiple channels so a chandef must be given */
+ if (WARN_ON_ONCE(sta->sdata->vif.type == NL80211_IFTYPE_NAN ||
+ sta->sdata->vif.type == NL80211_IFTYPE_NAN_DATA))
+ return IEEE80211_STA_RX_BW_20;
+
rcu_read_lock();
link_conf = rcu_dereference(sta->sdata->vif.link_conf[link_sta->link_id]);
if (WARN_ON_ONCE(!link_conf)) {
--
2.34.1
^ permalink raw reply related
* [PATCH wireless-next 07/15] wifi: mac80211: handle reconfig for NAN DATA interfaces
From: Miri Korenblit @ 2026-03-25 21:15 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260325211536.910411-1-miriam.rachel.korenblit@intel.com>
Make sure these interfaces are added to the driver only after the NAN
one was, and after NAN operation was started.
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/util.c | 19 +++++++++++++++++--
1 file changed, 17 insertions(+), 2 deletions(-)
diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index 19ac778b704d..925a09246ad9 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -1780,10 +1780,11 @@ ieee80211_reconfig_nan_offload_de(struct ieee80211_sub_if_data *sdata)
static int ieee80211_reconfig_nan(struct ieee80211_sub_if_data *sdata)
{
+ struct ieee80211_local *local = sdata->local;
+ struct ieee80211_sub_if_data *ndi_sdata;
int res;
- res = drv_start_nan(sdata->local, sdata,
- &sdata->u.nan.conf);
+ res = drv_start_nan(local, sdata, &sdata->u.nan.conf);
if (WARN_ON(res))
return res;
@@ -1792,6 +1793,15 @@ static int ieee80211_reconfig_nan(struct ieee80211_sub_if_data *sdata)
drv_vif_cfg_changed(sdata->local, sdata, BSS_CHANGED_NAN_LOCAL_SCHED);
+ /* Now we can add all the NDIs to the driver */
+ list_for_each_entry(ndi_sdata, &local->interfaces, list) {
+ if (ndi_sdata->vif.type == NL80211_IFTYPE_NAN_DATA) {
+ res = drv_add_interface(local, ndi_sdata);
+ if (WARN_ON(res))
+ return res;
+ }
+ }
+
return 0;
}
@@ -1945,6 +1955,9 @@ int ieee80211_reconfig(struct ieee80211_local *local)
if (sdata->vif.type == NL80211_IFTYPE_MONITOR &&
!ieee80211_hw_check(&local->hw, NO_VIRTUAL_MONITOR))
continue;
+ /* These vifs can't be added before NAN was started */
+ if (sdata->vif.type == NL80211_IFTYPE_NAN_DATA)
+ continue;
if (sdata->vif.type != NL80211_IFTYPE_AP_VLAN &&
ieee80211_sdata_running(sdata)) {
res = drv_add_interface(local, sdata);
@@ -1962,6 +1975,8 @@ int ieee80211_reconfig(struct ieee80211_local *local)
if (sdata->vif.type == NL80211_IFTYPE_MONITOR &&
!ieee80211_hw_check(&local->hw, NO_VIRTUAL_MONITOR))
continue;
+ if (sdata->vif.type == NL80211_IFTYPE_NAN_DATA)
+ continue;
if (sdata->vif.type != NL80211_IFTYPE_AP_VLAN &&
ieee80211_sdata_running(sdata))
drv_remove_interface(local, sdata);
--
2.34.1
^ permalink raw reply related
* [PATCH wireless-next 06/15] wifi: mac80211: support open and close for NAN_DATA interfaces
From: Miri Korenblit @ 2026-03-25 21:15 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260325211536.910411-1-miriam.rachel.korenblit@intel.com>
Support opening and closing a NAN_DATA interface.
Track the NAN (NMI) interface, for convenience.
Allow opening an NAN_DATA interface only if the NAN interface is running
(NAN has started).
When closing the NAN interface, make sure all NAN_DATA interfaces are
closed first, and warn if this is not the case.
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/ieee80211_i.h | 11 +++++++++++
net/mac80211/iface.c | 33 ++++++++++++++++++++++++++++++---
2 files changed, 41 insertions(+), 3 deletions(-)
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 92ea8de8a6db..e3a051beba6a 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -1006,6 +1006,16 @@ struct ieee80211_if_nan {
DECLARE_BITMAP(removed_channels, IEEE80211_NAN_MAX_CHANNELS);
};
+/**
+ * struct ieee80211_if_nan_data - NAN data path state
+ *
+ * @nmi: pointer to the NAN management interface sdata. Used for data path,
+ * hence RCU.
+ */
+struct ieee80211_if_nan_data {
+ struct ieee80211_sub_if_data __rcu *nmi;
+};
+
struct ieee80211_link_data_managed {
u8 bssid[ETH_ALEN] __aligned(2);
@@ -1204,6 +1214,7 @@ struct ieee80211_sub_if_data {
struct ieee80211_if_ocb ocb;
struct ieee80211_if_mntr mntr;
struct ieee80211_if_nan nan;
+ struct ieee80211_if_nan_data nan_data;
} u;
struct ieee80211_link_data deflink;
diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c
index 0f3e49cdbb39..507c5e016ec8 100644
--- a/net/mac80211/iface.c
+++ b/net/mac80211/iface.c
@@ -361,6 +361,17 @@ static int ieee80211_check_concurrent_iface(struct ieee80211_sub_if_data *sdata,
nsdata->vif.type == NL80211_IFTYPE_OCB))
return -EBUSY;
+ /*
+ * A NAN DATA interface is correlated to the NAN
+ * (management) one
+ */
+ if (iftype == NL80211_IFTYPE_NAN_DATA &&
+ nsdata->vif.type == NL80211_IFTYPE_NAN) {
+ if (!nsdata->u.nan.started)
+ return -EINVAL;
+ rcu_assign_pointer(sdata->u.nan_data.nmi, nsdata);
+ }
+
/*
* Allow only a single IBSS interface to be up at any
* time. This is restricted because beacon distribution
@@ -475,6 +486,7 @@ static int ieee80211_open(struct net_device *dev)
static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata, bool going_down)
{
struct ieee80211_local *local = sdata->local;
+ struct ieee80211_sub_if_data *iter;
unsigned long flags;
struct sk_buff_head freeq;
struct sk_buff *skb, *tmp;
@@ -621,6 +633,12 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata, bool going_do
}
break;
case NL80211_IFTYPE_NAN:
+ /* Check if any open NAN_DATA interfaces */
+ list_for_each_entry(iter, &local->interfaces, list) {
+ WARN_ON(iter->vif.type == NL80211_IFTYPE_NAN_DATA &&
+ ieee80211_sdata_running(iter));
+ }
+
/* clean all the functions */
if (!(local->hw.wiphy->nan_capa.flags &
WIPHY_NAN_FLAGS_USERSPACE_DE)) {
@@ -636,6 +654,9 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata, bool going_do
spin_unlock_bh(&sdata->u.nan.de.func_lock);
}
break;
+ case NL80211_IFTYPE_NAN_DATA:
+ RCU_INIT_POINTER(sdata->u.nan_data.nmi, NULL);
+ fallthrough;
default:
wiphy_work_cancel(sdata->local->hw.wiphy, &sdata->work);
/*
@@ -1384,9 +1405,12 @@ int ieee80211_do_open(struct wireless_dev *wdev, bool coming_up)
case NL80211_IFTYPE_P2P_DEVICE:
case NL80211_IFTYPE_OCB:
case NL80211_IFTYPE_NAN:
- case NL80211_IFTYPE_NAN_DATA:
/* no special treatment */
break;
+ case NL80211_IFTYPE_NAN_DATA:
+ if (WARN_ON(!rcu_access_pointer(sdata->u.nan_data.nmi)))
+ return -ENOLINK;
+ break;
case NL80211_IFTYPE_UNSPECIFIED:
case NUM_NL80211_IFTYPES:
case NL80211_IFTYPE_P2P_CLIENT:
@@ -1404,8 +1428,8 @@ int ieee80211_do_open(struct wireless_dev *wdev, bool coming_up)
res = drv_start(local);
if (res) {
/*
- * no need to worry about AP_VLAN cleanup since in that
- * case we can't have open_count == 0
+ * no need to worry about AP_VLAN/NAN_DATA cleanup since
+ * in that case we can't have open_count == 0
*/
return res;
}
@@ -1524,6 +1548,7 @@ int ieee80211_do_open(struct wireless_dev *wdev, bool coming_up)
case NL80211_IFTYPE_AP:
case NL80211_IFTYPE_MESH_POINT:
case NL80211_IFTYPE_OCB:
+ case NL80211_IFTYPE_NAN_DATA:
netif_carrier_off(dev);
break;
case NL80211_IFTYPE_P2P_DEVICE:
@@ -1570,6 +1595,8 @@ int ieee80211_do_open(struct wireless_dev *wdev, bool coming_up)
err_stop:
if (!local->open_count)
drv_stop(local, false);
+ if (sdata->vif.type == NL80211_IFTYPE_NAN_DATA)
+ RCU_INIT_POINTER(sdata->u.nan_data.nmi, NULL);
if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
list_del(&sdata->u.vlan.list);
/* Might not be initialized yet, but it is harmless */
--
2.34.1
^ permalink raw reply related
* [PATCH wireless-next 05/15] wifi: mac80211: add NAN local schedule support
From: Miri Korenblit @ 2026-03-25 21:15 UTC (permalink / raw)
To: linux-wireless; +Cc: Avraham Stern, Johannes Berg
In-Reply-To: <20260325211536.910411-1-miriam.rachel.korenblit@intel.com>
A NAN local schedule consist of a list of NAN channels, and an array
that maps time slots to the channel it is scheduled to (or NULL to indicate
unscheduled).
A NAN channel is the configuration of a channel which is used for NAN
operations. It is a new type of chanctx user (before, the only user is a
link). A NAN channel may not have a chanctx assigned if it is ULWed out.
A NAN channel may or may not be scheduled (for example, user space
may want to prepare the resources before the actual schedule is
configured).
Add management of the NAN local schedule.
Since we introduce a new chanctx user, also adjust the different
for_each_chanctx_user_* macros to visit also the NAN channels and take
those into account.
Co-developed-by: Avraham Stern <avraham.stern@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
include/net/mac80211.h | 66 +++++++
net/mac80211/Makefile | 2 +-
net/mac80211/cfg.c | 13 ++
net/mac80211/chan.c | 132 ++++++++++---
net/mac80211/ieee80211_i.h | 20 +-
net/mac80211/iface.c | 8 +
net/mac80211/nan.c | 378 +++++++++++++++++++++++++++++++++++++
net/mac80211/util.c | 28 ++-
8 files changed, 609 insertions(+), 38 deletions(-)
create mode 100644 net/mac80211/nan.c
diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index ff862d54eb77..24685f106757 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -365,6 +365,7 @@ struct ieee80211_vif_chanctx_switch {
* @BSS_CHANGED_MLD_VALID_LINKS: MLD valid links status changed.
* @BSS_CHANGED_MLD_TTLM: negotiated TID to link mapping was changed
* @BSS_CHANGED_TPE: transmit power envelope changed
+ * @BSS_CHANGED_NAN_LOCAL_SCHED: NAN local schedule changed (NAN mode only)
*/
enum ieee80211_bss_change {
BSS_CHANGED_ASSOC = 1<<0,
@@ -402,6 +403,7 @@ enum ieee80211_bss_change {
BSS_CHANGED_MLD_VALID_LINKS = BIT_ULL(33),
BSS_CHANGED_MLD_TTLM = BIT_ULL(34),
BSS_CHANGED_TPE = BIT_ULL(35),
+ BSS_CHANGED_NAN_LOCAL_SCHED = BIT_ULL(36),
/* when adding here, make sure to change ieee80211_reconfig */
};
@@ -866,6 +868,28 @@ struct ieee80211_bss_conf {
u8 s1g_long_beacon_period;
};
+#define IEEE80211_NAN_MAX_CHANNELS 3
+
+/**
+ * struct ieee80211_nan_channel - NAN channel information
+ *
+ * @chanreq: channel request for this NAN channel. Even though this chanreq::ap
+ * is irrelevant for NAN, still store it for convenience - some functions
+ * require it as an argument.
+ * @needed_rx_chains: number of RX chains needed for this NAN channel
+ * @chanctx_conf: chanctx_conf assigned to this NAN channel. Will be %NULL
+ * if the channel is ULWed.
+ * @channel_entry: the Channel Entry blob as defined in Wi-Fi Aware
+ * (TM) 4.0 specification Table 100 (Channel Entry format for the NAN
+ * Availability attribute).
+ */
+struct ieee80211_nan_channel {
+ struct ieee80211_chan_req chanreq;
+ u8 needed_rx_chains;
+ struct ieee80211_chanctx_conf *chanctx_conf;
+ u8 channel_entry[6];
+};
+
/**
* enum mac80211_tx_info_flags - flags to describe transmission information/status
*
@@ -1917,6 +1941,8 @@ enum ieee80211_offload_flags {
IEEE80211_OFFLOAD_DECAP_ENABLED = BIT(2),
};
+#define IEEE80211_NAN_AVAIL_BLOB_MAX_LEN 54
+
/**
* struct ieee80211_eml_params - EHT Operating mode notification parameters
*
@@ -1942,6 +1968,32 @@ struct ieee80211_eml_params {
u8 emlmr_mcs_map_bw[9];
};
+/**
+ * struct ieee80211_nan_sched_cfg - NAN schedule configuration
+ * @channels: array of NAN channels. A channel entry is in use if
+ * channels[i].chanreq.oper.chan is not NULL.
+ * @schedule: NAN local schedule - mapping of each 16TU time slot to
+ * the NAN channel on which the radio will operate. NULL if unscheduled.
+ * @avail_blob: NAN Availability attribute blob.
+ * @avail_blob_len: length of the @avail_blob in bytes.
+ * @deferred: indicates that the driver should notify peers before applying the
+ * new NAN schedule, and apply the new schedule the second NAN Slot
+ * boundary after it notified the peers, as defined in Wi-Fi Aware (TM) 4.0
+ * specification, section 5.2.2.
+ * The driver must call ieee80211_nan_sched_update_done() after the
+ * schedule has been applied.
+ * If a HW restart happened while a deferred schedule update was pending,
+ * mac80211 will reconfigure the deferred schedule (and wait for the driver
+ * to notify that the schedule has been applied).
+ */
+struct ieee80211_nan_sched_cfg {
+ struct ieee80211_nan_channel channels[IEEE80211_NAN_MAX_CHANNELS];
+ struct ieee80211_nan_channel *schedule[CFG80211_NAN_SCHED_NUM_TIME_SLOTS];
+ u8 avail_blob[IEEE80211_NAN_AVAIL_BLOB_MAX_LEN];
+ u16 avail_blob_len;
+ bool deferred;
+};
+
/**
* struct ieee80211_vif_cfg - interface configuration
* @assoc: association status
@@ -1970,6 +2022,7 @@ struct ieee80211_eml_params {
* your driver/device needs to do.
* @ap_addr: AP MLD address, or BSSID for non-MLO connections
* (station mode only)
+ * @nan_sched: NAN schedule parameters. &struct ieee80211_nan_sched_cfg
*/
struct ieee80211_vif_cfg {
/* association related data */
@@ -1988,6 +2041,8 @@ struct ieee80211_vif_cfg {
bool s1g;
bool idle;
u8 ap_addr[ETH_ALEN] __aligned(2);
+ /* Protected by the wiphy mutex */
+ struct ieee80211_nan_sched_cfg nan_sched;
};
#define IEEE80211_TTLM_NUM_TIDS 8
@@ -7754,6 +7809,17 @@ void ieee80211_nan_func_match(struct ieee80211_vif *vif,
struct cfg80211_nan_match_params *match,
gfp_t gfp);
+/**
+ * ieee80211_nan_sched_update_done - notify that NAN schedule update is done
+ *
+ * This function is called by the driver to notify mac80211 that the NAN
+ * schedule update has been applied.
+ * Must be called with wiphy mutex held. May sleep.
+ *
+ * @vif: &struct ieee80211_vif pointer from the add_interface callback.
+ */
+void ieee80211_nan_sched_update_done(struct ieee80211_vif *vif);
+
/**
* ieee80211_calc_rx_airtime - calculate estimated transmission airtime for RX.
*
diff --git a/net/mac80211/Makefile b/net/mac80211/Makefile
index b0e392eb7753..abf46c951299 100644
--- a/net/mac80211/Makefile
+++ b/net/mac80211/Makefile
@@ -36,7 +36,7 @@ mac80211-y := \
tdls.o \
ocb.o \
airtime.o \
- eht.o uhr.o
+ eht.o uhr.o nan.o
mac80211-$(CONFIG_MAC80211_LEDS) += led.o
mac80211-$(CONFIG_MAC80211_DEBUGFS) += \
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index e851668f4ef3..701b111db7df 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -5594,6 +5594,18 @@ ieee80211_set_epcs(struct wiphy *wiphy, struct net_device *dev, bool enable)
return ieee80211_mgd_set_epcs(sdata, enable);
}
+static int
+ieee80211_set_local_nan_sched(struct wiphy *wiphy,
+ struct wireless_dev *wdev,
+ struct cfg80211_nan_local_sched *sched)
+{
+ struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev);
+
+ lockdep_assert_wiphy(wiphy);
+
+ return ieee80211_nan_set_local_sched(sdata, sched);
+}
+
const struct cfg80211_ops mac80211_config_ops = {
.add_virtual_intf = ieee80211_add_iface,
.del_virtual_intf = ieee80211_del_iface,
@@ -5710,4 +5722,5 @@ const struct cfg80211_ops mac80211_config_ops = {
.get_radio_mask = ieee80211_get_radio_mask,
.assoc_ml_reconf = ieee80211_assoc_ml_reconf,
.set_epcs = ieee80211_set_epcs,
+ .nan_set_local_sched = ieee80211_set_local_nan_sched,
};
diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
index 1e4bfcd25697..eca3fed1edbe 100644
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -16,6 +16,8 @@ struct ieee80211_chanctx_user_iter {
struct ieee80211_chan_req *chanreq;
struct ieee80211_sub_if_data *sdata;
struct ieee80211_link_data *link;
+ struct ieee80211_nan_channel *nan_channel;
+ int nan_channel_next_idx;
enum nl80211_iftype iftype;
bool reserved, radar_required, done;
enum {
@@ -31,20 +33,38 @@ enum ieee80211_chanctx_iter_type {
CHANCTX_ITER_ASSIGNED,
};
-static void ieee80211_chanctx_user_iter_next(struct ieee80211_local *local,
- struct ieee80211_chanctx *ctx,
- struct ieee80211_chanctx_user_iter *iter,
- enum ieee80211_chanctx_iter_type type,
- bool start)
+static bool
+ieee80211_chanctx_user_iter_next_nan_channel(struct ieee80211_chanctx *ctx,
+ struct ieee80211_chanctx_user_iter *iter)
{
- lockdep_assert_wiphy(local->hw.wiphy);
+ /* Start from the next index after current position */
+ for (int i = iter->nan_channel_next_idx;
+ i < ARRAY_SIZE(iter->sdata->vif.cfg.nan_sched.channels); i++) {
+ struct ieee80211_nan_channel *nan_channel =
+ &iter->sdata->vif.cfg.nan_sched.channels[i];
- if (start) {
- memset(iter, 0, sizeof(*iter));
- goto next_interface;
+ if (!nan_channel->chanreq.oper.chan)
+ continue;
+
+ if (nan_channel->chanctx_conf != &ctx->conf)
+ continue;
+
+ iter->nan_channel = nan_channel;
+ iter->nan_channel_next_idx = i + 1;
+ iter->chanreq = &nan_channel->chanreq;
+ iter->link = NULL;
+ iter->reserved = false;
+ iter->radar_required = false;
+ return true;
}
+ return false;
+}
-next_link:
+static bool
+ieee80211_chanctx_user_iter_next_link(struct ieee80211_chanctx *ctx,
+ struct ieee80211_chanctx_user_iter *iter,
+ enum ieee80211_chanctx_iter_type type)
+{
for (int link_id = iter->link ? iter->link->link_id : 0;
link_id < ARRAY_SIZE(iter->sdata->link);
link_id++) {
@@ -64,7 +84,7 @@ static void ieee80211_chanctx_user_iter_next(struct ieee80211_local *local,
iter->reserved = false;
iter->radar_required = link->radar_required;
iter->chanreq = &link->conf->chanreq;
- return;
+ return true;
}
fallthrough;
case CHANCTX_ITER_POS_RESERVED:
@@ -77,7 +97,7 @@ static void ieee80211_chanctx_user_iter_next(struct ieee80211_local *local,
link->reserved_radar_required;
iter->chanreq = &link->reserved;
- return;
+ return true;
}
fallthrough;
case CHANCTX_ITER_POS_DONE:
@@ -85,6 +105,33 @@ static void ieee80211_chanctx_user_iter_next(struct ieee80211_local *local,
continue;
}
}
+ return false;
+}
+
+static void
+ieee80211_chanctx_user_iter_next(struct ieee80211_local *local,
+ struct ieee80211_chanctx *ctx,
+ struct ieee80211_chanctx_user_iter *iter,
+ enum ieee80211_chanctx_iter_type type,
+ bool start)
+{
+ bool found;
+
+ lockdep_assert_wiphy(local->hw.wiphy);
+
+ if (start) {
+ memset(iter, 0, sizeof(*iter));
+ goto next_interface;
+ }
+
+next_user:
+ if (iter->iftype == NL80211_IFTYPE_NAN)
+ found = ieee80211_chanctx_user_iter_next_nan_channel(ctx, iter);
+ else
+ found = ieee80211_chanctx_user_iter_next_link(ctx, iter, type);
+
+ if (found)
+ return;
next_interface:
/* next (or first) interface */
@@ -97,10 +144,18 @@ static void ieee80211_chanctx_user_iter_next(struct ieee80211_local *local,
if (iter->sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
continue;
+ /* NAN channels don't reserve channel context */
+ if (iter->sdata->vif.type == NL80211_IFTYPE_NAN &&
+ type == CHANCTX_ITER_RESERVED)
+ continue;
+
+ iter->nan_channel = NULL;
iter->link = NULL;
- iter->per_link = CHANCTX_ITER_POS_ASSIGNED;
iter->iftype = iter->sdata->vif.type;
- goto next_link;
+ iter->chanreq = NULL;
+ iter->per_link = CHANCTX_ITER_POS_ASSIGNED;
+ iter->nan_channel_next_idx = 0;
+ goto next_user;
}
iter->done = true;
@@ -133,8 +188,8 @@ static void ieee80211_chanctx_user_iter_next(struct ieee80211_local *local,
CHANCTX_ITER_ALL, \
false))
-static int ieee80211_chanctx_num_assigned(struct ieee80211_local *local,
- struct ieee80211_chanctx *ctx)
+int ieee80211_chanctx_num_assigned(struct ieee80211_local *local,
+ struct ieee80211_chanctx *ctx)
{
struct ieee80211_chanctx_user_iter iter;
int num = 0;
@@ -321,7 +376,7 @@ ieee80211_chanctx_non_reserved_chandef(struct ieee80211_local *local,
lockdep_assert_wiphy(local->hw.wiphy);
for_each_chanctx_user_assigned(local, ctx, &iter) {
- if (iter.link->reserved_chanctx)
+ if (iter.link && iter.link->reserved_chanctx)
continue;
comp_def = ieee80211_chanreq_compatible(iter.chanreq,
@@ -480,7 +535,6 @@ ieee80211_get_width_of_link(struct ieee80211_link_data *link)
case NL80211_IFTYPE_AP_VLAN:
return ieee80211_get_max_required_bw(link);
case NL80211_IFTYPE_P2P_DEVICE:
- case NL80211_IFTYPE_NAN:
break;
case NL80211_IFTYPE_MONITOR:
WARN_ON_ONCE(!ieee80211_hw_check(&local->hw,
@@ -495,6 +549,7 @@ ieee80211_get_width_of_link(struct ieee80211_link_data *link)
case NUM_NL80211_IFTYPES:
case NL80211_IFTYPE_P2P_CLIENT:
case NL80211_IFTYPE_P2P_GO:
+ case NL80211_IFTYPE_NAN:
case NL80211_IFTYPE_NAN_DATA:
WARN_ON_ONCE(1);
break;
@@ -504,6 +559,18 @@ ieee80211_get_width_of_link(struct ieee80211_link_data *link)
return NL80211_CHAN_WIDTH_20_NOHT;
}
+static enum nl80211_chan_width
+ieee80211_get_width_of_chanctx_user(struct ieee80211_chanctx_user_iter *iter)
+{
+ if (iter->link)
+ return ieee80211_get_width_of_link(iter->link);
+
+ if (WARN_ON_ONCE(!iter->nan_channel || iter->reserved))
+ return NL80211_CHAN_WIDTH_20_NOHT;
+
+ return iter->nan_channel->chanreq.oper.width;
+}
+
static enum nl80211_chan_width
ieee80211_get_chanctx_max_required_bw(struct ieee80211_local *local,
struct ieee80211_chanctx *ctx,
@@ -521,7 +588,7 @@ ieee80211_get_chanctx_max_required_bw(struct ieee80211_local *local,
/* When this is true we only care about the reserving links */
if (check_reserved) {
for_each_chanctx_user_reserved(local, ctx, &iter) {
- width = ieee80211_get_width_of_link(iter.link);
+ width = ieee80211_get_width_of_chanctx_user(&iter);
max_bw = max(max_bw, width);
}
goto check_monitor;
@@ -529,7 +596,7 @@ ieee80211_get_chanctx_max_required_bw(struct ieee80211_local *local,
/* Consider all assigned links */
for_each_chanctx_user_assigned(local, ctx, &iter) {
- width = ieee80211_get_width_of_link(iter.link);
+ width = ieee80211_get_width_of_chanctx_user(&iter);
max_bw = max(max_bw, width);
}
@@ -941,7 +1008,10 @@ ieee80211_new_chanctx(struct ieee80211_local *local,
kfree(ctx);
return ERR_PTR(err);
}
- /* We ignored a driver error, see _ieee80211_set_active_links */
+ /*
+ * We ignored a driver error, see _ieee80211_set_active_links and/or
+ * ieee80211_nan_set_local_sched
+ */
WARN_ON_ONCE(err && !local->in_reconfig);
list_add_rcu(&ctx->list, &local->chanctx_list);
@@ -962,9 +1032,9 @@ static void ieee80211_del_chanctx(struct ieee80211_local *local,
ieee80211_remove_wbrf(local, &ctx->conf.def);
}
-static void ieee80211_free_chanctx(struct ieee80211_local *local,
- struct ieee80211_chanctx *ctx,
- bool skip_idle_recalc)
+void ieee80211_free_chanctx(struct ieee80211_local *local,
+ struct ieee80211_chanctx *ctx,
+ bool skip_idle_recalc)
{
lockdep_assert_wiphy(local->hw.wiphy);
@@ -1159,6 +1229,7 @@ void ieee80211_recalc_smps_chanctx(struct ieee80211_local *local,
case NL80211_IFTYPE_ADHOC:
case NL80211_IFTYPE_MESH_POINT:
case NL80211_IFTYPE_OCB:
+ case NL80211_IFTYPE_NAN:
break;
default:
continue;
@@ -1169,6 +1240,15 @@ void ieee80211_recalc_smps_chanctx(struct ieee80211_local *local,
break;
}
+ if (iter.nan_channel) {
+ rx_chains_dynamic = rx_chains_static =
+ iter.nan_channel->needed_rx_chains;
+ break;
+ }
+
+ if (!iter.link)
+ continue;
+
switch (iter.link->smps_mode) {
default:
WARN_ONCE(1, "Invalid SMPS mode %d\n",
@@ -1777,7 +1857,7 @@ static int ieee80211_vif_use_reserved_switch(struct ieee80211_local *local)
for_each_chanctx_user_assigned(local, ctx->replace_ctx, &iter) {
n_assigned++;
- if (iter.link->reserved_chanctx) {
+ if (iter.link && iter.link->reserved_chanctx) {
n_reserved++;
if (iter.link->reserved_ready)
n_ready++;
@@ -2033,7 +2113,7 @@ void __ieee80211_link_release_channel(struct ieee80211_link_data *link,
ieee80211_vif_use_reserved_switch(local);
}
-static struct ieee80211_chanctx *
+struct ieee80211_chanctx *
ieee80211_find_or_create_chanctx(struct ieee80211_sub_if_data *sdata,
const struct ieee80211_chan_req *chanreq,
enum ieee80211_chanctx_mode mode,
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 8d5f9a725fdf..92ea8de8a6db 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -990,6 +990,8 @@ struct ieee80211_if_mntr {
* @de: Discovery Engine state (only valid if !WIPHY_NAN_FLAGS_USERSPACE_DE)
* @de.func_lock: lock for @de.function_inst_ids
* @de.function_inst_ids: a bitmap of available instance_id's
+ * @removed_channels: bitmap of channels that should be removed from the NAN
+ * schedule once the deferred schedule update is completed.
*/
struct ieee80211_if_nan {
struct cfg80211_nan_conf conf;
@@ -1000,6 +1002,8 @@ struct ieee80211_if_nan {
spinlock_t func_lock;
struct idr function_inst_ids;
} de;
+
+ DECLARE_BITMAP(removed_channels, IEEE80211_NAN_MAX_CHANNELS);
};
struct ieee80211_link_data_managed {
@@ -2024,6 +2028,10 @@ int ieee80211_mesh_csa_beacon(struct ieee80211_sub_if_data *sdata,
int ieee80211_mesh_finish_csa(struct ieee80211_sub_if_data *sdata,
u64 *changed);
+/* NAN code */
+int ieee80211_nan_set_local_sched(struct ieee80211_sub_if_data *sdata,
+ struct cfg80211_nan_local_sched *sched);
+
/* scan/BSS handling */
void ieee80211_scan_work(struct wiphy *wiphy, struct wiphy_work *work);
int ieee80211_request_ibss_scan(struct ieee80211_sub_if_data *sdata,
@@ -2812,7 +2820,17 @@ int ieee80211_max_num_channels(struct ieee80211_local *local, int radio_idx);
u32 ieee80211_get_radio_mask(struct wiphy *wiphy, struct net_device *dev);
void ieee80211_recalc_chanctx_chantype(struct ieee80211_local *local,
struct ieee80211_chanctx *ctx);
-
+struct ieee80211_chanctx *
+ieee80211_find_or_create_chanctx(struct ieee80211_sub_if_data *sdata,
+ const struct ieee80211_chan_req *chanreq,
+ enum ieee80211_chanctx_mode mode,
+ bool assign_on_failure,
+ bool *reused_ctx);
+void ieee80211_free_chanctx(struct ieee80211_local *local,
+ struct ieee80211_chanctx *ctx,
+ bool skip_idle_recalc);
+int ieee80211_chanctx_num_assigned(struct ieee80211_local *local,
+ struct ieee80211_chanctx *ctx);
/* TDLS */
int ieee80211_tdls_mgmt(struct wiphy *wiphy, struct net_device *dev,
const u8 *peer, int link_id,
diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c
index f0a5a675c5a5..0f3e49cdbb39 100644
--- a/net/mac80211/iface.c
+++ b/net/mac80211/iface.c
@@ -886,6 +886,14 @@ static void ieee80211_teardown_sdata(struct ieee80211_sub_if_data *sdata)
ieee80211_vif_clear_links(sdata);
ieee80211_link_stop(&sdata->deflink);
+
+ if (sdata->vif.type == NL80211_IFTYPE_NAN) {
+ struct ieee80211_nan_sched_cfg *nan_sched =
+ &sdata->vif.cfg.nan_sched;
+
+ for (int i = 0; i < ARRAY_SIZE(nan_sched->channels); i++)
+ WARN_ON(nan_sched->channels[i].chanreq.oper.chan);
+ }
}
static void ieee80211_uninit(struct net_device *dev)
diff --git a/net/mac80211/nan.c b/net/mac80211/nan.c
new file mode 100644
index 000000000000..2fa55e9a9dab
--- /dev/null
+++ b/net/mac80211/nan.c
@@ -0,0 +1,378 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * NAN mode implementation
+ * Copyright(c) 2025 Intel Corporation
+ */
+#include <net/mac80211.h>
+
+#include "ieee80211_i.h"
+#include "driver-ops.h"
+
+static void
+ieee80211_nan_init_channel(struct ieee80211_nan_channel *nan_channel,
+ struct cfg80211_nan_channel *cfg_nan_channel)
+{
+ memset(nan_channel, 0, sizeof(*nan_channel));
+
+ nan_channel->chanreq.oper = cfg_nan_channel->chandef;
+ memcpy(nan_channel->channel_entry, cfg_nan_channel->channel_entry,
+ sizeof(nan_channel->channel_entry));
+ nan_channel->needed_rx_chains = cfg_nan_channel->rx_nss;
+}
+
+static void
+ieee80211_nan_update_channel(struct ieee80211_local *local,
+ struct ieee80211_nan_channel *nan_channel,
+ struct cfg80211_nan_channel *cfg_nan_channel,
+ bool deferred)
+{
+ struct ieee80211_chanctx_conf *conf;
+ bool reducing_nss;
+
+ if (WARN_ON(!cfg80211_chandef_identical(&nan_channel->chanreq.oper,
+ &cfg_nan_channel->chandef)))
+ return;
+
+ if (WARN_ON(memcmp(nan_channel->channel_entry,
+ cfg_nan_channel->channel_entry,
+ sizeof(nan_channel->channel_entry))))
+ return;
+
+ if (nan_channel->needed_rx_chains == cfg_nan_channel->rx_nss)
+ return;
+
+ reducing_nss = nan_channel->needed_rx_chains > cfg_nan_channel->rx_nss;
+ nan_channel->needed_rx_chains = cfg_nan_channel->rx_nss;
+
+ conf = nan_channel->chanctx_conf;
+
+ /*
+ * If we are adding NSSs, we need to be ready before notifying the peer,
+ * if we are reducing NSSs, we need to wait until the peer is notified.
+ */
+ if (!conf || (deferred && reducing_nss))
+ return;
+
+ ieee80211_recalc_smps_chanctx(local, container_of(conf,
+ struct ieee80211_chanctx,
+ conf));
+}
+
+static int
+ieee80211_nan_use_chanctx(struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_nan_channel *nan_channel,
+ bool assign_on_failure)
+{
+ struct ieee80211_chanctx *ctx;
+ bool reused_ctx;
+
+ if (!nan_channel->chanreq.oper.chan)
+ return -EINVAL;
+
+ if (ieee80211_check_combinations(sdata, &nan_channel->chanreq.oper,
+ IEEE80211_CHANCTX_SHARED, 0, -1))
+ return -EBUSY;
+
+ ctx = ieee80211_find_or_create_chanctx(sdata, &nan_channel->chanreq,
+ IEEE80211_CHANCTX_SHARED,
+ assign_on_failure,
+ &reused_ctx);
+ if (IS_ERR(ctx))
+ return PTR_ERR(ctx);
+
+ nan_channel->chanctx_conf = &ctx->conf;
+
+ /*
+ * In case an existing channel context is being used, we marked it as
+ * will_be_used, now that it is assigned - clear this indication
+ */
+ if (reused_ctx) {
+ WARN_ON(!ctx->will_be_used);
+ ctx->will_be_used = false;
+ }
+ ieee80211_recalc_chanctx_min_def(sdata->local, ctx);
+ ieee80211_recalc_smps_chanctx(sdata->local, ctx);
+
+ return 0;
+}
+
+static void
+ieee80211_nan_remove_channel(struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_nan_channel *nan_channel)
+{
+ struct ieee80211_chanctx_conf *conf;
+ struct ieee80211_chanctx *ctx;
+ struct ieee80211_nan_sched_cfg *sched_cfg = &sdata->vif.cfg.nan_sched;
+
+ if (WARN_ON(!nan_channel))
+ return;
+
+ lockdep_assert_wiphy(sdata->local->hw.wiphy);
+
+ if (!nan_channel->chanreq.oper.chan)
+ return;
+
+ for (int slot = 0; slot < ARRAY_SIZE(sched_cfg->schedule); slot++)
+ if (sched_cfg->schedule[slot] == nan_channel)
+ sched_cfg->schedule[slot] = NULL;
+
+ conf = nan_channel->chanctx_conf;
+
+ memset(nan_channel, 0, sizeof(*nan_channel));
+
+ /* Update the driver before (possibly) releasing the channel context */
+ drv_vif_cfg_changed(sdata->local, sdata, BSS_CHANGED_NAN_LOCAL_SCHED);
+
+ /* Channel might not have a chanctx if it was ULWed */
+ if (!conf)
+ return;
+
+ ctx = container_of(conf, struct ieee80211_chanctx, conf);
+
+ if (ieee80211_chanctx_num_assigned(sdata->local, ctx) > 0) {
+ ieee80211_recalc_chanctx_chantype(sdata->local, ctx);
+ ieee80211_recalc_smps_chanctx(sdata->local, ctx);
+ ieee80211_recalc_chanctx_min_def(sdata->local, ctx);
+ }
+
+ if (ieee80211_chanctx_refcount(sdata->local, ctx) == 0)
+ ieee80211_free_chanctx(sdata->local, ctx, false);
+}
+
+static struct ieee80211_nan_channel *
+ieee80211_nan_find_free_channel(struct ieee80211_nan_sched_cfg *sched_cfg)
+{
+ for (int i = 0; i < ARRAY_SIZE(sched_cfg->channels); i++) {
+ if (!sched_cfg->channels[i].chanreq.oper.chan)
+ return &sched_cfg->channels[i];
+ }
+
+ return NULL;
+}
+
+int ieee80211_nan_set_local_sched(struct ieee80211_sub_if_data *sdata,
+ struct cfg80211_nan_local_sched *sched)
+{
+ struct ieee80211_nan_channel *sched_idx_to_chan[IEEE80211_NAN_MAX_CHANNELS] = {};
+ struct ieee80211_nan_sched_cfg *sched_cfg = &sdata->vif.cfg.nan_sched;
+ struct ieee80211_nan_sched_cfg backup_sched;
+ int ret;
+
+ if (sched->n_channels > IEEE80211_NAN_MAX_CHANNELS)
+ return -EOPNOTSUPP;
+
+ if (sched->nan_avail_blob_len > IEEE80211_NAN_AVAIL_BLOB_MAX_LEN)
+ return -EINVAL;
+
+ /*
+ * If a deferred schedule update is pending completion, new updates are
+ * not allowed. Only allow to configure an empty schedule so NAN can be
+ * stopped in the middle of a deferred update. This is fine because
+ * empty schedule means the local NAN device will not be available for
+ * peers anymore so there is no need to update peers about a new
+ * schedule.
+ */
+ if (WARN_ON(sched_cfg->deferred && sched->n_channels))
+ return -EBUSY;
+
+ bitmap_zero(sdata->u.nan.removed_channels, IEEE80211_NAN_MAX_CHANNELS);
+
+ memcpy(backup_sched.schedule, sched_cfg->schedule,
+ sizeof(backup_sched.schedule));
+ memcpy(backup_sched.channels, sched_cfg->channels,
+ sizeof(backup_sched.channels));
+ memcpy(backup_sched.avail_blob, sched_cfg->avail_blob,
+ sizeof(backup_sched.avail_blob));
+ backup_sched.avail_blob_len = sched_cfg->avail_blob_len;
+
+ memcpy(sched_cfg->avail_blob, sched->nan_avail_blob,
+ sched->nan_avail_blob_len);
+ sched_cfg->avail_blob_len = sched->nan_avail_blob_len;
+
+ /*
+ * Remove channels that are no longer in the new schedule to free up
+ * resources before adding new channels. For deferred schedule, channels
+ * will be removed when the schedule is applied.
+ * Create a mapping from sched index to sched_cfg channel
+ */
+ for (int i = 0; i < ARRAY_SIZE(sched_cfg->channels); i++) {
+ bool still_needed = false;
+
+ if (!sched_cfg->channels[i].chanreq.oper.chan)
+ continue;
+
+ for (int j = 0; j < sched->n_channels; j++) {
+ if (cfg80211_chandef_identical(&sched_cfg->channels[i].chanreq.oper,
+ &sched->nan_channels[j].chandef)) {
+ sched_idx_to_chan[j] =
+ &sched_cfg->channels[i];
+ still_needed = true;
+ break;
+ }
+ }
+
+ if (!still_needed) {
+ __set_bit(i, sdata->u.nan.removed_channels);
+ if (!sched->deferred)
+ ieee80211_nan_remove_channel(sdata,
+ &sched_cfg->channels[i]);
+ }
+ }
+
+ for (int i = 0; i < sched->n_channels; i++) {
+ struct ieee80211_nan_channel *chan = sched_idx_to_chan[i];
+
+ if (chan) {
+ ieee80211_nan_update_channel(sdata->local, chan,
+ &sched->nan_channels[i],
+ sched->deferred);
+ } else {
+ chan = ieee80211_nan_find_free_channel(sched_cfg);
+ if (WARN_ON(!chan)) {
+ ret = -EINVAL;
+ goto err;
+ }
+
+ sched_idx_to_chan[i] = chan;
+ ieee80211_nan_init_channel(chan,
+ &sched->nan_channels[i]);
+
+ ret = ieee80211_nan_use_chanctx(sdata, chan, false);
+ if (ret) {
+ memset(chan, 0, sizeof(*chan));
+ goto err;
+ }
+ }
+ }
+
+ for (int s = 0; s < ARRAY_SIZE(sched_cfg->schedule); s++) {
+ if (sched->schedule[s] < ARRAY_SIZE(sched_idx_to_chan))
+ sched_cfg->schedule[s] =
+ sched_idx_to_chan[sched->schedule[s]];
+ else
+ sched_cfg->schedule[s] = NULL;
+ }
+
+ sched_cfg->deferred = sched->deferred;
+
+ drv_vif_cfg_changed(sdata->local, sdata, BSS_CHANGED_NAN_LOCAL_SCHED);
+
+ /*
+ * For deferred update, don't update NDI carriers yet as the new
+ * schedule is not yet applied so common slots don't change. The NDI
+ * carrier will be updated once the driver notifies the new schedule is
+ * applied.
+ */
+ if (sched_cfg->deferred)
+ return 0;
+
+ bitmap_zero(sdata->u.nan.removed_channels, IEEE80211_NAN_MAX_CHANNELS);
+
+ return 0;
+err:
+ /* Remove newly added channels */
+ for (int i = 0; i < ARRAY_SIZE(sched_cfg->channels); i++) {
+ struct cfg80211_chan_def *chan_def =
+ &sched_cfg->channels[i].chanreq.oper;
+
+ if (!chan_def->chan)
+ continue;
+
+ if (!cfg80211_chandef_identical(&backup_sched.channels[i].chanreq.oper,
+ chan_def))
+ ieee80211_nan_remove_channel(sdata,
+ &sched_cfg->channels[i]);
+ }
+
+ /* Re-add all backed up channels */
+ for (int i = 0; i < ARRAY_SIZE(backup_sched.channels); i++) {
+ struct ieee80211_nan_channel *chan = &sched_cfg->channels[i];
+
+ *chan = backup_sched.channels[i];
+
+ /*
+ * For deferred update, no channels were removed and the channel
+ * context didn't change, so nothing else to do.
+ */
+ if (!chan->chanctx_conf || sched->deferred)
+ continue;
+
+ if (test_bit(i, sdata->u.nan.removed_channels)) {
+ /* Clear the stale chanctx pointer */
+ chan->chanctx_conf = NULL;
+ /*
+ * We removed the newly added channels so we don't lack
+ * resources. So the only reason that this would fail
+ * is a FW error which we ignore. Therefore, this
+ * should never fail.
+ */
+ WARN_ON(ieee80211_nan_use_chanctx(sdata, chan, true));
+ } else {
+ struct ieee80211_chanctx_conf *conf = chan->chanctx_conf;
+
+ /* FIXME: detect no-op? */
+ /* Channel was not removed but may have been updated */
+ ieee80211_recalc_smps_chanctx(sdata->local,
+ container_of(conf,
+ struct ieee80211_chanctx,
+ conf));
+ }
+ }
+
+ memcpy(sched_cfg->schedule, backup_sched.schedule,
+ sizeof(backup_sched.schedule));
+ memcpy(sched_cfg->avail_blob, backup_sched.avail_blob,
+ sizeof(backup_sched.avail_blob));
+ sched_cfg->avail_blob_len = backup_sched.avail_blob_len;
+ sched_cfg->deferred = false;
+ bitmap_zero(sdata->u.nan.removed_channels, IEEE80211_NAN_MAX_CHANNELS);
+
+ drv_vif_cfg_changed(sdata->local, sdata, BSS_CHANGED_NAN_LOCAL_SCHED);
+ return ret;
+}
+
+void ieee80211_nan_sched_update_done(struct ieee80211_vif *vif)
+{
+ struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
+ struct ieee80211_nan_sched_cfg *sched_cfg = &vif->cfg.nan_sched;
+ unsigned int i;
+
+ lockdep_assert_wiphy(sdata->local->hw.wiphy);
+
+ if (WARN_ON(!sched_cfg->deferred))
+ return;
+
+ /*
+ * Clear the deferred flag before removing channels. Removing channels
+ * will trigger another schedule update to the driver, and there is no
+ * need for this update to be deferred since removed channels are not
+ * part of the schedule anymore, so no need to notify peers about
+ * removing them.
+ */
+ sched_cfg->deferred = false;
+
+ for (i = 0; i < ARRAY_SIZE(sched_cfg->channels); i++) {
+ struct ieee80211_nan_channel *chan = &sched_cfg->channels[i];
+ struct ieee80211_chanctx_conf *conf = chan->chanctx_conf;
+
+ if (!chan->chanreq.oper.chan)
+ continue;
+
+ if (test_bit(i, sdata->u.nan.removed_channels))
+ ieee80211_nan_remove_channel(sdata, chan);
+ else if (conf)
+ /*
+ * We might have called this already for some channels,
+ * but this knows to handle a no-op.
+ */
+ ieee80211_recalc_smps_chanctx(sdata->local,
+ container_of(conf,
+ struct ieee80211_chanctx,
+ conf));
+ }
+
+ bitmap_zero(sdata->u.nan.removed_channels, IEEE80211_NAN_MAX_CHANNELS);
+ cfg80211_nan_sched_update_done(ieee80211_vif_to_wdev(vif), true,
+ GFP_KERNEL);
+}
+EXPORT_SYMBOL(ieee80211_nan_sched_update_done);
diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index 36795529ff82..19ac778b704d 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -1744,20 +1744,12 @@ static void ieee80211_reconfig_stations(struct ieee80211_sub_if_data *sdata)
}
}
-static int ieee80211_reconfig_nan(struct ieee80211_sub_if_data *sdata)
+static int
+ieee80211_reconfig_nan_offload_de(struct ieee80211_sub_if_data *sdata)
{
struct cfg80211_nan_func *func, **funcs;
int res, id, i = 0;
- res = drv_start_nan(sdata->local, sdata,
- &sdata->u.nan.conf);
- if (WARN_ON(res))
- return res;
-
- if (sdata->local->hw.wiphy->nan_capa.flags &
- WIPHY_NAN_FLAGS_USERSPACE_DE)
- return 0;
-
funcs = kzalloc_objs(*funcs, sdata->local->hw.max_nan_de_entries + 1);
if (!funcs)
return -ENOMEM;
@@ -1783,6 +1775,22 @@ static int ieee80211_reconfig_nan(struct ieee80211_sub_if_data *sdata)
}
kfree(funcs);
+ return res;
+}
+
+static int ieee80211_reconfig_nan(struct ieee80211_sub_if_data *sdata)
+{
+ int res;
+
+ res = drv_start_nan(sdata->local, sdata,
+ &sdata->u.nan.conf);
+ if (WARN_ON(res))
+ return res;
+
+ if (!(sdata->local->hw.wiphy->nan_capa.flags & WIPHY_NAN_FLAGS_USERSPACE_DE))
+ return ieee80211_reconfig_nan_offload_de(sdata);
+
+ drv_vif_cfg_changed(sdata->local, sdata, BSS_CHANGED_NAN_LOCAL_SCHED);
return 0;
}
--
2.34.1
^ permalink raw reply related
* [PATCH wireless-next 04/15] wifi: mac80211: run NAN DE code only when appropriate
From: Miri Korenblit @ 2026-03-25 21:15 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260325211536.910411-1-miriam.rachel.korenblit@intel.com>
NAN DE (Discovery Engine) may be handled in the device or in user space.
When handled in user space, all the NAN func management code should not
run. Moreover, devices with user space DE should not provide the
add/del_nan_func callbaks. For such devices, ieee80211_reconfig_nan will
always fail.
Make it clear what parts of ieee80211_if_nan are relevant to DE
management, and touch those only when DE is offloaded.
Add a check that makes sure that a driver doesn't register with
add_del/nan_func callbacks if DE is in user space.
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/cfg.c | 52 ++++++++++++++++++++++++--------------
net/mac80211/ieee80211_i.h | 13 ++++++----
net/mac80211/iface.c | 25 +++++++++++-------
net/mac80211/main.c | 4 ++-
net/mac80211/util.c | 10 +++++---
5 files changed, 67 insertions(+), 37 deletions(-)
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index 13132215afb4..e851668f4ef3 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -502,12 +502,15 @@ static int ieee80211_add_nan_func(struct wiphy *wiphy,
if (!ieee80211_sdata_running(sdata))
return -ENETDOWN;
- spin_lock_bh(&sdata->u.nan.func_lock);
+ if (WARN_ON(wiphy->nan_capa.flags & WIPHY_NAN_FLAGS_USERSPACE_DE))
+ return -EOPNOTSUPP;
+
+ spin_lock_bh(&sdata->u.nan.de.func_lock);
- ret = idr_alloc(&sdata->u.nan.function_inst_ids,
+ ret = idr_alloc(&sdata->u.nan.de.function_inst_ids,
nan_func, 1, sdata->local->hw.max_nan_de_entries + 1,
GFP_ATOMIC);
- spin_unlock_bh(&sdata->u.nan.func_lock);
+ spin_unlock_bh(&sdata->u.nan.de.func_lock);
if (ret < 0)
return ret;
@@ -518,10 +521,10 @@ static int ieee80211_add_nan_func(struct wiphy *wiphy,
ret = drv_add_nan_func(sdata->local, sdata, nan_func);
if (ret) {
- spin_lock_bh(&sdata->u.nan.func_lock);
- idr_remove(&sdata->u.nan.function_inst_ids,
+ spin_lock_bh(&sdata->u.nan.de.func_lock);
+ idr_remove(&sdata->u.nan.de.function_inst_ids,
nan_func->instance_id);
- spin_unlock_bh(&sdata->u.nan.func_lock);
+ spin_unlock_bh(&sdata->u.nan.de.func_lock);
}
return ret;
@@ -534,9 +537,9 @@ ieee80211_find_nan_func_by_cookie(struct ieee80211_sub_if_data *sdata,
struct cfg80211_nan_func *func;
int id;
- lockdep_assert_held(&sdata->u.nan.func_lock);
+ lockdep_assert_held(&sdata->u.nan.de.func_lock);
- idr_for_each_entry(&sdata->u.nan.function_inst_ids, func, id) {
+ idr_for_each_entry(&sdata->u.nan.de.function_inst_ids, func, id) {
if (func->cookie == cookie)
return func;
}
@@ -555,13 +558,16 @@ static void ieee80211_del_nan_func(struct wiphy *wiphy,
!ieee80211_sdata_running(sdata))
return;
- spin_lock_bh(&sdata->u.nan.func_lock);
+ if (WARN_ON(wiphy->nan_capa.flags & WIPHY_NAN_FLAGS_USERSPACE_DE))
+ return;
+
+ spin_lock_bh(&sdata->u.nan.de.func_lock);
func = ieee80211_find_nan_func_by_cookie(sdata, cookie);
if (func)
instance_id = func->instance_id;
- spin_unlock_bh(&sdata->u.nan.func_lock);
+ spin_unlock_bh(&sdata->u.nan.de.func_lock);
if (instance_id)
drv_del_nan_func(sdata->local, sdata, instance_id);
@@ -4888,18 +4894,22 @@ void ieee80211_nan_func_terminated(struct ieee80211_vif *vif,
if (WARN_ON(vif->type != NL80211_IFTYPE_NAN))
return;
- spin_lock_bh(&sdata->u.nan.func_lock);
+ if (WARN_ON(sdata->local->hw.wiphy->nan_capa.flags &
+ WIPHY_NAN_FLAGS_USERSPACE_DE))
+ return;
+
+ spin_lock_bh(&sdata->u.nan.de.func_lock);
- func = idr_find(&sdata->u.nan.function_inst_ids, inst_id);
+ func = idr_find(&sdata->u.nan.de.function_inst_ids, inst_id);
if (WARN_ON(!func)) {
- spin_unlock_bh(&sdata->u.nan.func_lock);
+ spin_unlock_bh(&sdata->u.nan.de.func_lock);
return;
}
cookie = func->cookie;
- idr_remove(&sdata->u.nan.function_inst_ids, inst_id);
+ idr_remove(&sdata->u.nan.de.function_inst_ids, inst_id);
- spin_unlock_bh(&sdata->u.nan.func_lock);
+ spin_unlock_bh(&sdata->u.nan.de.func_lock);
cfg80211_free_nan_func(func);
@@ -4918,16 +4928,20 @@ void ieee80211_nan_func_match(struct ieee80211_vif *vif,
if (WARN_ON(vif->type != NL80211_IFTYPE_NAN))
return;
- spin_lock_bh(&sdata->u.nan.func_lock);
+ if (WARN_ON(sdata->local->hw.wiphy->nan_capa.flags &
+ WIPHY_NAN_FLAGS_USERSPACE_DE))
+ return;
+
+ spin_lock_bh(&sdata->u.nan.de.func_lock);
- func = idr_find(&sdata->u.nan.function_inst_ids, match->inst_id);
+ func = idr_find(&sdata->u.nan.de.function_inst_ids, match->inst_id);
if (WARN_ON(!func)) {
- spin_unlock_bh(&sdata->u.nan.func_lock);
+ spin_unlock_bh(&sdata->u.nan.de.func_lock);
return;
}
match->cookie = func->cookie;
- spin_unlock_bh(&sdata->u.nan.func_lock);
+ spin_unlock_bh(&sdata->u.nan.de.func_lock);
cfg80211_nan_match(ieee80211_vif_to_wdev(vif), match, gfp);
}
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 53d783769642..8d5f9a725fdf 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -987,16 +987,19 @@ struct ieee80211_if_mntr {
*
* @conf: current NAN configuration
* @started: true iff NAN is started
- * @func_lock: lock for @func_inst_ids
- * @function_inst_ids: a bitmap of available instance_id's
+ * @de: Discovery Engine state (only valid if !WIPHY_NAN_FLAGS_USERSPACE_DE)
+ * @de.func_lock: lock for @de.function_inst_ids
+ * @de.function_inst_ids: a bitmap of available instance_id's
*/
struct ieee80211_if_nan {
struct cfg80211_nan_conf conf;
bool started;
- /* protects function_inst_ids */
- spinlock_t func_lock;
- struct idr function_inst_ids;
+ struct {
+ /* protects function_inst_ids */
+ spinlock_t func_lock;
+ struct idr function_inst_ids;
+ } de;
};
struct ieee80211_link_data_managed {
diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c
index 7518dcbcdf1c..f0a5a675c5a5 100644
--- a/net/mac80211/iface.c
+++ b/net/mac80211/iface.c
@@ -622,15 +622,19 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata, bool going_do
break;
case NL80211_IFTYPE_NAN:
/* clean all the functions */
- spin_lock_bh(&sdata->u.nan.func_lock);
+ if (!(local->hw.wiphy->nan_capa.flags &
+ WIPHY_NAN_FLAGS_USERSPACE_DE)) {
+ spin_lock_bh(&sdata->u.nan.de.func_lock);
+
+ idr_for_each_entry(&sdata->u.nan.de.function_inst_ids,
+ func, i) {
+ idr_remove(&sdata->u.nan.de.function_inst_ids, i);
+ cfg80211_free_nan_func(func);
+ }
+ idr_destroy(&sdata->u.nan.de.function_inst_ids);
- idr_for_each_entry(&sdata->u.nan.function_inst_ids, func, i) {
- idr_remove(&sdata->u.nan.function_inst_ids, i);
- cfg80211_free_nan_func(func);
+ spin_unlock_bh(&sdata->u.nan.de.func_lock);
}
- idr_destroy(&sdata->u.nan.function_inst_ids);
-
- spin_unlock_bh(&sdata->u.nan.func_lock);
break;
default:
wiphy_work_cancel(sdata->local->hw.wiphy, &sdata->work);
@@ -1942,8 +1946,11 @@ static void ieee80211_setup_sdata(struct ieee80211_sub_if_data *sdata,
MONITOR_FLAG_OTHER_BSS;
break;
case NL80211_IFTYPE_NAN:
- idr_init(&sdata->u.nan.function_inst_ids);
- spin_lock_init(&sdata->u.nan.func_lock);
+ if (!(sdata->local->hw.wiphy->nan_capa.flags &
+ WIPHY_NAN_FLAGS_USERSPACE_DE)) {
+ idr_init(&sdata->u.nan.de.function_inst_ids);
+ spin_lock_init(&sdata->u.nan.de.func_lock);
+ }
sdata->vif.bss_conf.bssid = sdata->vif.addr;
break;
case NL80211_IFTYPE_AP_VLAN:
diff --git a/net/mac80211/main.c b/net/mac80211/main.c
index d1bb6353908d..f47dd58770ad 100644
--- a/net/mac80211/main.c
+++ b/net/mac80211/main.c
@@ -1157,7 +1157,9 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)
if (WARN_ON(local->hw.wiphy->interface_modes &
BIT(NL80211_IFTYPE_NAN) &&
- (!local->ops->start_nan || !local->ops->stop_nan)))
+ ((!local->ops->start_nan || !local->ops->stop_nan) ||
+ (local->hw.wiphy->nan_capa.flags & WIPHY_NAN_FLAGS_USERSPACE_DE &&
+ (local->ops->add_nan_func || local->ops->del_nan_func)))))
return -EINVAL;
if (hw->wiphy->flags & WIPHY_FLAG_SUPPORTS_MLO) {
diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index 38b0c42c4c13..36795529ff82 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -1754,6 +1754,10 @@ static int ieee80211_reconfig_nan(struct ieee80211_sub_if_data *sdata)
if (WARN_ON(res))
return res;
+ if (sdata->local->hw.wiphy->nan_capa.flags &
+ WIPHY_NAN_FLAGS_USERSPACE_DE)
+ return 0;
+
funcs = kzalloc_objs(*funcs, sdata->local->hw.max_nan_de_entries + 1);
if (!funcs)
return -ENOMEM;
@@ -1762,12 +1766,12 @@ static int ieee80211_reconfig_nan(struct ieee80211_sub_if_data *sdata)
* This is a little bit ugly. We need to call a potentially sleeping
* callback for each NAN function, so we can't hold the spinlock.
*/
- spin_lock_bh(&sdata->u.nan.func_lock);
+ spin_lock_bh(&sdata->u.nan.de.func_lock);
- idr_for_each_entry(&sdata->u.nan.function_inst_ids, func, id)
+ idr_for_each_entry(&sdata->u.nan.de.function_inst_ids, func, id)
funcs[i++] = func;
- spin_unlock_bh(&sdata->u.nan.func_lock);
+ spin_unlock_bh(&sdata->u.nan.de.func_lock);
for (i = 0; funcs[i]; i++) {
res = drv_add_nan_func(sdata->local, sdata, funcs[i]);
--
2.34.1
^ permalink raw reply related
* [PATCH wireless-next 03/15] wifi: mac80211: export ieee80211_calculate_rx_timestamp
From: Miri Korenblit @ 2026-03-25 21:15 UTC (permalink / raw)
To: linux-wireless; +Cc: Benjamin Berg, Johannes Berg
In-Reply-To: <20260325211536.910411-1-miriam.rachel.korenblit@intel.com>
From: Benjamin Berg <benjamin.berg@intel.com>
The function is quite useful when handling beacon timestamps. Export it
so that it can be used by mac80211_hwsim and others.
Signed-off-by: Benjamin Berg <benjamin.berg@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
include/net/mac80211.h | 18 ++++++++++++++++++
net/mac80211/ibss.c | 2 +-
net/mac80211/ieee80211_i.h | 4 ----
net/mac80211/mesh_sync.c | 2 +-
net/mac80211/rx.c | 2 +-
net/mac80211/scan.c | 2 +-
net/mac80211/util.c | 18 +++---------------
7 files changed, 25 insertions(+), 23 deletions(-)
diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 2959736efdf3..ff862d54eb77 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -7393,6 +7393,24 @@ void ieee80211_disable_rssi_reports(struct ieee80211_vif *vif);
*/
int ieee80211_ave_rssi(struct ieee80211_vif *vif, int link_id);
+/**
+ * ieee80211_calculate_rx_timestamp - calculate timestamp in frame
+ * @hw: pointer as obtained from ieee80211_alloc_hw()
+ * @status: RX status
+ * @mpdu_len: total MPDU length (including FCS)
+ * @mpdu_offset: offset into MPDU to calculate timestamp at
+ *
+ * This function calculates the RX timestamp at the given MPDU offset, taking
+ * into account what the RX timestamp was. An offset of 0 will just normalize
+ * the timestamp to TSF at beginning of MPDU reception.
+ *
+ * Returns: the calculated timestamp
+ */
+u64 ieee80211_calculate_rx_timestamp(struct ieee80211_hw *hw,
+ struct ieee80211_rx_status *status,
+ unsigned int mpdu_len,
+ unsigned int mpdu_offset);
+
/**
* ieee80211_report_wowlan_wakeup - report WoWLAN wakeup
* @vif: virtual interface
diff --git a/net/mac80211/ibss.c b/net/mac80211/ibss.c
index 1e1ab25d9d8d..97292ff51475 100644
--- a/net/mac80211/ibss.c
+++ b/net/mac80211/ibss.c
@@ -1127,7 +1127,7 @@ static void ieee80211_rx_bss_info(struct ieee80211_sub_if_data *sdata,
if (ieee80211_have_rx_timestamp(rx_status)) {
/* time when timestamp field was received */
rx_timestamp =
- ieee80211_calculate_rx_timestamp(local, rx_status,
+ ieee80211_calculate_rx_timestamp(&local->hw, rx_status,
len + FCS_LEN, 24);
} else {
/*
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index bacb49ad2817..53d783769642 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -1922,10 +1922,6 @@ ieee80211_vif_get_num_mcast_if(struct ieee80211_sub_if_data *sdata)
return -1;
}
-u64 ieee80211_calculate_rx_timestamp(struct ieee80211_local *local,
- struct ieee80211_rx_status *status,
- unsigned int mpdu_len,
- unsigned int mpdu_offset);
int ieee80211_hw_config(struct ieee80211_local *local, int radio_idx,
u32 changed);
int ieee80211_hw_conf_chan(struct ieee80211_local *local);
diff --git a/net/mac80211/mesh_sync.c b/net/mac80211/mesh_sync.c
index 3a66b4cefca7..24a68eef7db8 100644
--- a/net/mac80211/mesh_sync.c
+++ b/net/mac80211/mesh_sync.c
@@ -103,7 +103,7 @@ mesh_sync_offset_rx_bcn_presp(struct ieee80211_sub_if_data *sdata, u16 stype,
* section.
*/
if (ieee80211_have_rx_timestamp(rx_status))
- t_r = ieee80211_calculate_rx_timestamp(local, rx_status,
+ t_r = ieee80211_calculate_rx_timestamp(&local->hw, rx_status,
len + FCS_LEN, 24);
else
t_r = drv_get_tsf(local, sdata);
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index d9a654ef082d..dbdd67c181d8 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -404,7 +404,7 @@ ieee80211_add_rx_radiotap_header(struct ieee80211_local *local,
while ((pos - (u8 *)rthdr) & 7)
*pos++ = 0;
put_unaligned_le64(
- ieee80211_calculate_rx_timestamp(local, status,
+ ieee80211_calculate_rx_timestamp(&local->hw, status,
mpdulen, 0),
pos);
rthdr->it_present |= cpu_to_le32(BIT(IEEE80211_RADIOTAP_TSFT));
diff --git a/net/mac80211/scan.c b/net/mac80211/scan.c
index 4823c8d45639..eeff230bd909 100644
--- a/net/mac80211/scan.c
+++ b/net/mac80211/scan.c
@@ -216,7 +216,7 @@ ieee80211_bss_info_update(struct ieee80211_local *local,
if (link_conf) {
bss_meta.parent_tsf =
- ieee80211_calculate_rx_timestamp(local,
+ ieee80211_calculate_rx_timestamp(&local->hw,
rx_status,
len + FCS_LEN,
24);
diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index 72e73f4f79c5..38b0c42c4c13 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -3423,20 +3423,7 @@ u8 ieee80211_mcs_to_chains(const struct ieee80211_mcs_info *mcs)
return 1;
}
-/**
- * ieee80211_calculate_rx_timestamp - calculate timestamp in frame
- * @local: mac80211 hw info struct
- * @status: RX status
- * @mpdu_len: total MPDU length (including FCS)
- * @mpdu_offset: offset into MPDU to calculate timestamp at
- *
- * This function calculates the RX timestamp at the given MPDU offset, taking
- * into account what the RX timestamp was. An offset of 0 will just normalize
- * the timestamp to TSF at beginning of MPDU reception.
- *
- * Returns: the calculated timestamp
- */
-u64 ieee80211_calculate_rx_timestamp(struct ieee80211_local *local,
+u64 ieee80211_calculate_rx_timestamp(struct ieee80211_hw *hw,
struct ieee80211_rx_status *status,
unsigned int mpdu_len,
unsigned int mpdu_offset)
@@ -3555,7 +3542,7 @@ u64 ieee80211_calculate_rx_timestamp(struct ieee80211_local *local,
case RX_ENC_LEGACY: {
struct ieee80211_supported_band *sband;
- sband = local->hw.wiphy->bands[status->band];
+ sband = hw->wiphy->bands[status->band];
ri.legacy = sband->bitrates[status->rate_idx].bitrate;
if (mactime_plcp_start) {
@@ -3587,6 +3574,7 @@ u64 ieee80211_calculate_rx_timestamp(struct ieee80211_local *local,
return ts;
}
+EXPORT_SYMBOL_GPL(ieee80211_calculate_rx_timestamp);
/* Cancel CAC for the interfaces under the specified @local. If @ctx is
* also provided, only the interfaces using that ctx will be canceled.
--
2.34.1
^ permalink raw reply related
* [PATCH wireless-next 02/15] wifi: ieee80211: add more NAN definitions
From: Miri Korenblit @ 2026-03-25 21:15 UTC (permalink / raw)
To: linux-wireless; +Cc: Benjamin Berg, Johannes Berg
In-Reply-To: <20260325211536.910411-1-miriam.rachel.korenblit@intel.com>
From: Benjamin Berg <benjamin.berg@intel.com>
These will be needed to implement NAN synchronization in mac80211_hwsim.
Signed-off-by: Benjamin Berg <benjamin.berg@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
include/linux/ieee80211-nan.h | 37 +++++++++++++++++++++++++++++++++++
include/linux/ieee80211.h | 1 +
2 files changed, 38 insertions(+)
diff --git a/include/linux/ieee80211-nan.h b/include/linux/ieee80211-nan.h
index ebf28ea651f9..455033955e54 100644
--- a/include/linux/ieee80211-nan.h
+++ b/include/linux/ieee80211-nan.h
@@ -37,4 +37,41 @@
#define NAN_DEV_CAPA_NDPE_SUPPORTED 0x08
#define NAN_DEV_CAPA_S3_SUPPORTED 0x10
+/* NAN attributes, as defined in Wi-Fi Aware (TM) specification 4.0 Table 42 */
+#define NAN_ATTR_MASTER_INDICATION 0x00
+#define NAN_ATTR_CLUSTER_INFO 0x01
+
+struct ieee80211_nan_attr {
+ u8 attr;
+ __le16 length;
+ u8 data[];
+} __packed;
+
+struct ieee80211_nan_master_indication {
+ u8 master_pref;
+ u8 random_factor;
+} __packed;
+
+struct ieee80211_nan_anchor_master_info {
+ union {
+ __le64 master_rank;
+ struct {
+ u8 master_addr[ETH_ALEN];
+ u8 random_factor;
+ u8 master_pref;
+ } __packed;
+ } __packed;
+ u8 hop_count;
+ __le32 ambtt;
+} __packed;
+
+#define for_each_nan_attr(_attr, _data, _datalen) \
+ for (_attr = (const struct ieee80211_nan_attr *)(_data); \
+ (const u8 *)(_data) + (_datalen) - (const u8 *)_attr >= \
+ (int)sizeof(*_attr) && \
+ (const u8 *)(_data) + (_datalen) - (const u8 *)_attr >= \
+ (int)sizeof(*_attr) + le16_to_cpu(_attr->length); \
+ _attr = (const struct ieee80211_nan_attr *) \
+ (_attr->data + le16_to_cpu(_attr->length)))
+
#endif /* LINUX_IEEE80211_NAN_H */
diff --git a/include/linux/ieee80211.h b/include/linux/ieee80211.h
index b5d649db123f..ffa8f9f77efe 100644
--- a/include/linux/ieee80211.h
+++ b/include/linux/ieee80211.h
@@ -2240,6 +2240,7 @@ struct ieee80211_multiple_bssid_configuration {
#define WLAN_OUI_WFA 0x506f9a
#define WLAN_OUI_TYPE_WFA_P2P 9
+#define WLAN_OUI_TYPE_WFA_NAN 0x13
#define WLAN_OUI_TYPE_WFA_DPP 0x1A
#define WLAN_OUI_MICROSOFT 0x0050f2
#define WLAN_OUI_TYPE_MICROSOFT_WPA 1
--
2.34.1
^ permalink raw reply related
* [PATCH wireless-next 01/15] wifi: mac80211: add a TXQ for management frames on NAN devices
From: Miri Korenblit @ 2026-03-25 21:15 UTC (permalink / raw)
To: linux-wireless; +Cc: Benjamin Berg, Johannes Berg
In-Reply-To: <20260325211536.910411-1-miriam.rachel.korenblit@intel.com>
From: Benjamin Berg <benjamin.berg@intel.com>
Currently there is no TXQ for non-data frames. Add a new txq_mgmt for
this purpose and create one of these on NAN devices. On NAN devices,
these frames may only be transmitted during the discovery window and it
is therefore helpful to schedule them using a queue.
Signed-off-by: Benjamin Berg <benjamin.berg@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
include/net/mac80211.h | 2 ++
net/mac80211/iface.c | 28 ++++++++++++++++++++++++++--
net/mac80211/tx.c | 20 ++++++++++++++++----
net/mac80211/util.c | 34 +++++++++++++++++++++++-----------
4 files changed, 67 insertions(+), 17 deletions(-)
diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 9cc482191ab9..2959736efdf3 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -2074,6 +2074,7 @@ enum ieee80211_neg_ttlm_res {
* @drv_priv: data area for driver use, will always be aligned to
* sizeof(void \*).
* @txq: the multicast data TX queue
+ * @txq_mgmt: the mgmt frame TX queue, currently only exists for NAN devices
* @offload_flags: 802.3 -> 802.11 enapsulation offload flags, see
* &enum ieee80211_offload_flags.
*/
@@ -2092,6 +2093,7 @@ struct ieee80211_vif {
u8 hw_queue[IEEE80211_NUM_ACS];
struct ieee80211_txq *txq;
+ struct ieee80211_txq *txq_mgmt;
netdev_features_t netdev_features;
u32 driver_flags;
diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c
index 125897717a4c..7518dcbcdf1c 100644
--- a/net/mac80211/iface.c
+++ b/net/mac80211/iface.c
@@ -682,6 +682,10 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata, bool going_do
if (sdata->vif.txq)
ieee80211_txq_purge(sdata->local, to_txq_info(sdata->vif.txq));
+ if (sdata->vif.txq_mgmt)
+ ieee80211_txq_purge(sdata->local,
+ to_txq_info(sdata->vif.txq_mgmt));
+
sdata->bss = NULL;
if (local->open_count == 0)
@@ -2223,10 +2227,16 @@ int ieee80211_if_add(struct ieee80211_local *local, const char *name,
lockdep_assert_wiphy(local->hw.wiphy);
if (type == NL80211_IFTYPE_P2P_DEVICE || type == NL80211_IFTYPE_NAN) {
+ int size = ALIGN(sizeof(*sdata) + local->hw.vif_data_size,
+ sizeof(void *));
struct wireless_dev *wdev;
+ int txq_size = 0;
+
+ if (type == NL80211_IFTYPE_NAN)
+ txq_size = sizeof(struct txq_info) +
+ local->hw.txq_data_size;
- sdata = kzalloc(sizeof(*sdata) + local->hw.vif_data_size,
- GFP_KERNEL);
+ sdata = kzalloc(size + txq_size, GFP_KERNEL);
if (!sdata)
return -ENOMEM;
wdev = &sdata->wdev;
@@ -2236,6 +2246,16 @@ int ieee80211_if_add(struct ieee80211_local *local, const char *name,
ieee80211_assign_perm_addr(local, wdev->address, type);
memcpy(sdata->vif.addr, wdev->address, ETH_ALEN);
ether_addr_copy(sdata->vif.bss_conf.addr, sdata->vif.addr);
+
+ /*
+ * Add a management TXQ for NAN devices which includes frames
+ * that will only be transmitted during discovery windows (DWs)
+ */
+ if (type == NL80211_IFTYPE_NAN) {
+ txqi = (struct txq_info *)((unsigned long)sdata + size);
+ ieee80211_txq_init(sdata, NULL, txqi,
+ IEEE80211_NUM_TIDS);
+ }
} else {
int size = ALIGN(sizeof(*sdata) + local->hw.vif_data_size,
sizeof(void *));
@@ -2386,6 +2406,10 @@ void ieee80211_if_remove(struct ieee80211_sub_if_data *sdata)
if (sdata->vif.txq)
ieee80211_txq_purge(sdata->local, to_txq_info(sdata->vif.txq));
+ if (sdata->vif.txq_mgmt)
+ ieee80211_txq_purge(sdata->local,
+ to_txq_info(sdata->vif.txq_mgmt));
+
synchronize_rcu();
cfg80211_unregister_wdev(&sdata->wdev);
diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c
index 3844c7fbb8a8..730c208c3bdf 100644
--- a/net/mac80211/tx.c
+++ b/net/mac80211/tx.c
@@ -1313,13 +1313,19 @@ static struct txq_info *ieee80211_get_txq(struct ieee80211_local *local,
unlikely(!ieee80211_is_data_present(hdr->frame_control))) {
if ((!ieee80211_is_mgmt(hdr->frame_control) ||
ieee80211_is_bufferable_mmpdu(skb) ||
- vif->type == NL80211_IFTYPE_STATION) &&
+ vif->type == NL80211_IFTYPE_STATION ||
+ vif->type == NL80211_IFTYPE_NAN ||
+ vif->type == NL80211_IFTYPE_NAN_DATA) &&
sta && sta->uploaded) {
/*
* This will be NULL if the driver didn't set the
* opt-in hardware flag.
*/
txq = sta->sta.txq[IEEE80211_NUM_TIDS];
+ } else if ((!ieee80211_is_mgmt(hdr->frame_control) ||
+ ieee80211_is_bufferable_mmpdu(skb)) &&
+ !sta) {
+ txq = vif->txq_mgmt;
}
} else if (sta) {
u8 tid = skb->priority & IEEE80211_QOS_CTL_TID_MASK;
@@ -1512,9 +1518,15 @@ void ieee80211_txq_init(struct ieee80211_sub_if_data *sdata,
txqi->txq.vif = &sdata->vif;
if (!sta) {
- sdata->vif.txq = &txqi->txq;
- txqi->txq.tid = 0;
- txqi->txq.ac = IEEE80211_AC_BE;
+ txqi->txq.tid = tid;
+
+ if (tid == IEEE80211_NUM_TIDS) {
+ sdata->vif.txq_mgmt = &txqi->txq;
+ txqi->txq.ac = IEEE80211_AC_VO;
+ } else {
+ sdata->vif.txq = &txqi->txq;
+ txqi->txq.ac = IEEE80211_AC_BE;
+ }
return;
}
diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index 8987a4504520..72e73f4f79c5 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -325,7 +325,7 @@ static void __ieee80211_wake_txqs(struct ieee80211_sub_if_data *sdata, int ac)
struct ieee80211_vif *vif = &sdata->vif;
struct fq *fq = &local->fq;
struct ps_data *ps = NULL;
- struct txq_info *txqi;
+ struct txq_info *txqi = NULL;
struct sta_info *sta;
int i;
@@ -344,37 +344,49 @@ static void __ieee80211_wake_txqs(struct ieee80211_sub_if_data *sdata, int ac)
for (i = 0; i < ARRAY_SIZE(sta->sta.txq); i++) {
struct ieee80211_txq *txq = sta->sta.txq[i];
+ struct txq_info *sta_txqi;
if (!txq)
continue;
- txqi = to_txq_info(txq);
+ sta_txqi = to_txq_info(txq);
if (ac != txq->ac)
continue;
if (!test_and_clear_bit(IEEE80211_TXQ_DIRTY,
- &txqi->flags))
+ &sta_txqi->flags))
continue;
spin_unlock(&fq->lock);
- drv_wake_tx_queue(local, txqi);
+ drv_wake_tx_queue(local, sta_txqi);
spin_lock(&fq->lock);
}
}
- if (!vif->txq)
- goto out;
+ if (vif->txq) {
+ txqi = to_txq_info(vif->txq);
- txqi = to_txq_info(vif->txq);
+ /* txq and txq_mgmt are mutually exclusive */
+ WARN_ON_ONCE(vif->txq_mgmt);
- if (!test_and_clear_bit(IEEE80211_TXQ_DIRTY, &txqi->flags) ||
- (ps && atomic_read(&ps->num_sta_ps)) || ac != vif->txq->ac)
- goto out;
+ if (!test_and_clear_bit(IEEE80211_TXQ_DIRTY, &txqi->flags) ||
+ (ps && atomic_read(&ps->num_sta_ps)) ||
+ ac != vif->txq->ac)
+ txqi = NULL;
+ } else if (vif->txq_mgmt) {
+ txqi = to_txq_info(vif->txq_mgmt);
+
+ if (!test_and_clear_bit(IEEE80211_TXQ_DIRTY, &txqi->flags) ||
+ ac != vif->txq_mgmt->ac)
+ txqi = NULL;
+ }
spin_unlock(&fq->lock);
- drv_wake_tx_queue(local, txqi);
+ if (txqi)
+ drv_wake_tx_queue(local, txqi);
+
local_bh_enable();
return;
out:
--
2.34.1
^ permalink raw reply related
* [PATCH wireless-next 00/15] wifi: mac80211: add NAN support
From: Miri Korenblit @ 2026-03-25 21:15 UTC (permalink / raw)
To: linux-wireless
Hi,
This is the first series of patches for adding support for NAN in
mac80211.
It contains (mainly):
- support NAN local and peer schedules
- basic support for NAN DATA interface
- support NAN stations
- support configuring keys on NAN interfcase
- support Tx/Rx over NAN DATA
Thanks,
Miri
---
Avraham Stern (1):
wifi: mac80211: allow add_key on NAN interfaces
Benjamin Berg (3):
wifi: mac80211: add a TXQ for management frames on NAN devices
wifi: ieee80211: add more NAN definitions
wifi: mac80211: export ieee80211_calculate_rx_timestamp
Miri Korenblit (11):
wifi: mac80211: run NAN DE code only when appropriate
wifi: mac80211: add NAN local schedule support
wifi: mac80211: support open and close for NAN_DATA interfaces
wifi: mac80211: handle reconfig for NAN DATA interfaces
wifi: mac80211: support NAN stations
wifi: mac80211: add NAN peer schedule support
wifi: mac80211: update NAN data path state on schedule changes
wifi: mac80211: add support for TX over NAN_DATA interfaces
wifi: mac80211: Accept frames on NAN DATA interfaces
wifi: mac80211: allow block ack agreements in NAN Data
wifi: mac80211: report and drop spurious NAN Data frames
include/linux/ieee80211-nan.h | 37 ++
include/linux/ieee80211.h | 1 +
include/net/mac80211.h | 157 +++++++-
net/mac80211/Makefile | 2 +-
net/mac80211/agg-tx.c | 3 +-
net/mac80211/cfg.c | 195 ++++++++--
net/mac80211/chan.c | 132 +++++--
net/mac80211/driver-ops.h | 21 +
net/mac80211/he.c | 7 +-
net/mac80211/ht.c | 19 +-
net/mac80211/ibss.c | 2 +-
net/mac80211/ieee80211_i.h | 52 ++-
net/mac80211/iface.c | 104 ++++-
net/mac80211/main.c | 4 +-
net/mac80211/mesh_sync.c | 2 +-
net/mac80211/nan.c | 710 ++++++++++++++++++++++++++++++++++
net/mac80211/rx.c | 61 ++-
net/mac80211/scan.c | 2 +-
net/mac80211/sta_info.c | 29 +-
net/mac80211/sta_info.h | 3 +-
net/mac80211/trace.h | 31 ++
net/mac80211/tx.c | 52 ++-
net/mac80211/util.c | 146 +++++--
net/mac80211/vht.c | 16 +-
24 files changed, 1641 insertions(+), 147 deletions(-)
create mode 100644 net/mac80211/nan.c
--
2.34.1
^ permalink raw reply
* Re: [BUG] wifi: rtw88: Hard system freeze on RTL8821CE when power_save is enabled (LPS/ASPM conflict)
From: LB F @ 2026-03-25 20:38 UTC (permalink / raw)
To: Ping-Ke Shih; +Cc: linux-wireless@vger.kernel.org, linux-kernel@vger.kernel.org
In-Reply-To: <ba9790526e4e42c386642a05fcbc2f34@realtek.com>
Subject: Cross-platform analysis: RTL8821CE ASPM/LPS instability
affects multiple OEM platforms beyond HP P3S95EA#ACB
Hi Ping-Ke,
First of all, thank you very much for your work on the rtw88 driver
and for the time you spent helping us resolve the issues on our HP
laptop. Both patches -- the v2 DMI quirk (ASPM + LPS Deep) and the
v2 RX rate validation (rx.c) -- have been tested and verified stable
on our system across two kernel updates (6.19.9-1 and 6.19.9-2),
sustained network load (~1.9 GB), and multiple suspend/resume cycles.
The system is now completely free of freezes, h2c errors, and
mac80211 warnings. Your patches genuinely solved every issue we had.
While working through this, I noticed that many other users across
different hardware platforms appear to be experiencing the same
problems that your patches resolved for us. I decided to collect
and organize these observations in case they might be useful to you.
Please note that this is an amateur analysis, not a professional
one -- I am just a user trying to help. It is entirely possible
that I have missed nuances or made incorrect assumptions. My only
goal is to share what I found, in case it provides useful data
points or sparks ideas for broader improvements. If any of this
is not relevant or not useful, please feel free to disregard it.
1. KERNEL BUGZILLA: OPEN RTL8821CE REPORTS
==========================================
I reviewed all open RTL8821CE bugs in kernel.org Bugzilla. Three
of the six show symptoms that directly match the root causes
addressed by your patches (ASPM deadlock and LPS Deep h2c failures).
--- Directly correlated with ASPM/LPS patches ---
Bug 215131 - System freeze (ASPM L1 deadlock)
Title: "Realtek 8821CE makes the system freeze after 9e2fd29864c5
(rtw88: add napi support)"
Reporter: Kai-Heng Feng (Canonical)
Kernel: 5.15+
Symptoms: Hard freeze preceded by "pci bus timeout, check dma status"
warnings. RX tag mismatch in rtw_pci_dma_check().
Workaround confirmed by reporter: rtw88_pci.disable_aspm=1
Reporter note: "disable_aspm=1 is not a viable workaround because
it increases power consumption significantly"
Status: OPEN since 2021-11-24.
Link: https://bugzilla.kernel.org/show_bug.cgi?id=215131
Relevance: Identical root cause to Bug 221195. The reporter's
confirmed workaround (disable_aspm=1) is exactly what
the DMI quirk implements.
Bug 219830 - h2c/LPS failures + BT crackling
Title: "rtw88_8821ce (RTL8821CE) slow performance and error
messages in dmesg"
Reporter: Bmax Y14 laptop, Fedora 41, kernel 6.13.5
Symptoms: - "failed to send h2c command" (periodic)
- "firmware failed to leave lps state" (periodic)
- Lower signal strength vs Windows
- Bluetooth crackling during audio playback
Cross-ref: https://github.com/lwfinger/rtw88/issues/306
Status: OPEN since 2025-03-02.
Link: https://bugzilla.kernel.org/show_bug.cgi?id=219830
Relevance: The h2c/lps errors are the same messages we observed
before the DMI quirk disabled LPS Deep Mode. The BT
crackling may correlate with the invalid RX rate
condition addressed by your rx.c validation patch.
Bug 218697 - TX queue flush timeout during scan
Title: "rtw88_8821ce timed out to flush queue 2"
Reporter: Arch Linux, kernel 6.8.4 / 6.8.5
Symptoms: - "timed out to flush queue 2" every ~30 seconds
- "failed to get tx report from firmware"
- Stack trace: ieee80211_scan_work -> rtw_ops_flush ->
rtw_mac_flush_queues timeout
Status: OPEN since 2024-04-08.
Link: https://bugzilla.kernel.org/show_bug.cgi?id=218697
Relevance: The flush timeout occurs when the firmware cannot
respond to TX queue operations -- consistent with
firmware being stuck in LPS Deep during scan.
--- Potentially related (no confirmed workaround data) ---
Bug 217491 - "timed out to flush queue 1" regression (kernel 6.3)
Manjaro user. Floods of "timed out to flush queue 1/2".
Similar pattern to Bug 218697.
Link: https://bugzilla.kernel.org/show_bug.cgi?id=217491
Bug 217781 - Random sudden dropouts
Arch user. Random disconnections during streaming/transfers.
Reproduced on Ubuntu and Fedora (kernels 5.15 to 6.4).
Link: https://bugzilla.kernel.org/show_bug.cgi?id=217781
Bug 216685 - Low wireless speed
Reduced throughput vs expected 802.11ac performance.
Link: https://bugzilla.kernel.org/show_bug.cgi?id=216685
2. SYMPTOM-TO-PATCH MAPPING
=============================
dmesg signature Patch that resolves it
-------------------------- ----------------------
Hard system freeze pci.c DMI quirk (disable ASPM)
"pci bus timeout, check dma" pci.c DMI quirk (disable ASPM)
"firmware failed to leave lps" pci.c DMI quirk (disable LPS Deep)
"failed to send h2c command" pci.c DMI quirk (disable LPS Deep)
"timed out to flush queue N" pci.c DMI quirk (disable LPS Deep) [1]
"failed to get tx report" pci.c DMI quirk (disable LPS Deep) [1]
VHT NSS=0 mac80211 WARNING rx.c rate validation (v2)
Confirmed in bugs: 215131, 219830, 218697, 221195.
[1] Inferred: flush timeout occurs when firmware cannot exit LPS
to process TX queue operations.
3. AFFECTED HARDWARE
=====================
Current DMI quirk coverage: HP P3S95EA#ACB only.
Platforms confirmed affected in Bugzilla:
Bug 221195: HP Notebook 81F0 (P3S95EA#ACB)
Bug 215131: unknown (Canonical upstream testing)
Bug 219830: Bmax Y14
Bug 218697: unknown (Arch Linux user)
Platforms reported in community forums as requiring
disable_aspm=Y and/or disable_lps_deep=Y for stable RTL8821CE:
- HP 17-by4063CL
- Lenovo IdeaPad S145-15AST, IdeaPad 3, IdeaPad 330S
- ASUS VivoBook X series
- Acer Aspire 3/5 series
All use PCI Device ID 10ec:c821 with different Subsystem IDs.
4. LPS_DEEP_MODE_LCLK IN THE rtw88 TREE
=========================================
I verified in the source which chips have the same
lps_deep_mode_supported flag:
Chip file lps_deep_mode_supported PCIe variant
--------- ----------------------- ------------
rtw8821c.c BIT(LPS_DEEP_MODE_LCLK) rtw8821ce ✓
rtw8822c.c BIT(LPS_DEEP_MODE_LCLK) | PG rtw8822ce ✓
rtw8822b.c BIT(LPS_DEEP_MODE_LCLK) rtw8822be ✓
rtw8814a.c BIT(LPS_DEEP_MODE_LCLK) rtw8814ae ✓
rtw8723d.c 0 rtw8723de ✗
rtw8703b.c 0 (SDIO) -
rtw8821a.c 0 (legacy) -
Source references:
rtw8821c.c:2002 rtw8822c.c:5365 rtw8822b.c:2545
rtw8814a.c:2211 rtw8723d.c:2144
RTL8822CE community reports (Manjaro, Arch forums) confirm the
same disable_aspm=Y + disable_lps_deep=Y workaround is effective,
consistent with rtw8822c.c having LCLK enabled.
5. COMMUNITY WORKAROUND REFERENCES
====================================
The following are concrete examples of forums and wikis where the
same modprobe workarounds are actively recommended:
Arch Wiki - RTW88 section:
https://wiki.archlinux.org/title/Network_configuration/Wireless
(section "RTW88" and "rtl8821ce" under Troubleshooting/Realtek)
Recommends rtw88-dkms-git and documents the rtw88_8821ce issues.
Arch Wiki - RTW89 section (same page):
Documents the identical ASPM pattern for the newer RTW89 driver:
options rtw89_pci disable_aspm_l1=y disable_aspm_l1ss=y
options rtw89_core disable_ps_mode=y
This suggests the ASPM/LPS interaction is a systemic Realtek
design issue, not specific to rtw88 or the 8821CE chip.
Arch Linux Forum - RTL8821CE thread:
https://bbs.archlinux.org/viewtopic.php?id=273440
Referenced by the Arch Wiki as the primary rtl8821ce discussion.
lwfinger/rtw88 GitHub:
https://github.com/lwfinger/rtw88/issues/306
Directly cross-referenced by Bug 219830. Reporter reports h2c
errors and investigated antenna hardware (no fault found).
lwfinger/rtw89 GitHub:
https://github.com/lwfinger/rtw89/issues/275#issuecomment-1784155449
Same ASPM-disable pattern documented for the newer RTW89 driver
on HP and Lenovo laptops.
6. OBSERVATIONS
================
a) Three open Bugzilla reporters (215131, 219830, 218697) show
symptoms identical to those resolved by your patches but have
no upstream fix available since they are not the HP P3S95EA#ACB.
b) Bug 215131 reporter (Kai-Heng Feng, Canonical) explicitly
confirmed disable_aspm=1 as a workaround and called it
"not viable" due to power cost. A DMI quirk for their
platform would give them a proper fix.
c) The Arch Wiki documents the same ASPM-disable pattern for
both RTW88 and RTW89 drivers, suggesting the underlying
hardware/firmware limitation is shared across generations.
d) Asking Bugzilla reporters to provide their DMI data
(dmidecode -t 1,2) could allow extending the quirk table
with minimal effort and zero risk to unaffected platforms.
e) The rx.c rate validation patch is chip-agnostic and requires
no platform-specific consideration.
7. REFERENCES
==============
Kernel Bugzilla:
https://bugzilla.kernel.org/show_bug.cgi?id=215131
https://bugzilla.kernel.org/show_bug.cgi?id=219830
https://bugzilla.kernel.org/show_bug.cgi?id=218697
https://bugzilla.kernel.org/show_bug.cgi?id=217491
https://bugzilla.kernel.org/show_bug.cgi?id=217781
https://bugzilla.kernel.org/show_bug.cgi?id=216685
GitHub:
https://github.com/lwfinger/rtw88/issues/306
https://github.com/lwfinger/rtw89/issues/275
Arch Wiki:
https://wiki.archlinux.org/title/Network_configuration/Wireless
Arch Linux Forum:
https://bbs.archlinux.org/viewtopic.php?id=273440
Source code (lps_deep_mode_supported):
drivers/net/wireless/realtek/rtw88/rtw8821c.c:2002
drivers/net/wireless/realtek/rtw88/rtw8822c.c:5365
drivers/net/wireless/realtek/rtw88/rtw8822b.c:2545
drivers/net/wireless/realtek/rtw88/rtw8814a.c:2211
drivers/net/wireless/realtek/rtw88/rtw8723d.c:2144
Best regards,
Oleksandr Havrylov <goainwo@gmail.com>
^ permalink raw reply
* [PATCH v4 wireless-next] wifi: mac80211: ignore reserved bits in reconfiguration status
From: Miri Korenblit @ 2026-03-25 19:57 UTC (permalink / raw)
To: linux-wireless; +Cc: Benjamin Berg, Ilan Peer
From: Benjamin Berg <benjamin.berg@intel.com>
The Link ID Info field in the Reconfiguration Status Duple subfield of
the Reconfiguration Response frame only uses the lower four bits for the
link ID. The upper bits are reserved and should therefore be ignored.
Signed-off-by: Benjamin Berg <benjamin.berg@intel.com>
Reviewed-by: Ilan Peer <ilan.peer@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
include/linux/ieee80211.h | 7 +++++++
net/mac80211/mlme.c | 14 ++++++++------
2 files changed, 15 insertions(+), 6 deletions(-)
--
v2: added structs and macros, as required.
v3: fixed Fixes tag
v4: added the requested structures again...
diff --git a/include/linux/ieee80211.h b/include/linux/ieee80211.h
index 52db36120314..b5d649db123f 100644
--- a/include/linux/ieee80211.h
+++ b/include/linux/ieee80211.h
@@ -1194,6 +1194,13 @@ struct ieee80211_mgmt {
#define IEEE80211_MIN_ACTION_SIZE(type) offsetofend(struct ieee80211_mgmt, u.action.type)
+/* Link Reconfiguration Status Duple field */
+struct ieee80211_ml_reconf_status {
+ u8 info;
+ __le16 status;
+} __packed;
+
+#define IEEE80211_ML_RECONF_LINK_ID_MASK 0xf
/* Management MIC information element (IEEE 802.11w) for CMAC */
struct ieee80211_mmie {
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index 173a60360a45..7fc5616cb244 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -10459,8 +10459,8 @@ void ieee80211_process_ml_reconf_resp(struct ieee80211_sub_if_data *sdata,
pos = mgmt->u.action.ml_reconf_resp.variable;
len -= offsetofend(typeof(*mgmt), u.action.ml_reconf_resp);
- /* each status duple is 3 octets */
- if (len < mgmt->u.action.ml_reconf_resp.count * 3) {
+ if (len < mgmt->u.action.ml_reconf_resp.count *
+ sizeof(struct ieee80211_ml_reconf_status)) {
sdata_info(sdata,
"mlo: reconf: unexpected len=%zu, count=%u\n",
len, mgmt->u.action.ml_reconf_resp.count);
@@ -10469,9 +10469,11 @@ void ieee80211_process_ml_reconf_resp(struct ieee80211_sub_if_data *sdata,
link_mask = sta_changed_links;
for (i = 0; i < mgmt->u.action.ml_reconf_resp.count; i++) {
- u16 status = get_unaligned_le16(pos + 1);
+ struct ieee80211_ml_reconf_status *reconf_status = (void *)pos;
+ u16 status = le16_to_cpu(reconf_status->status);
- link_id = *pos;
+ link_id = u8_get_bits(reconf_status->info,
+ IEEE80211_ML_RECONF_LINK_ID_MASK);
if (!(link_mask & BIT(link_id))) {
sdata_info(sdata,
@@ -10506,8 +10508,8 @@ void ieee80211_process_ml_reconf_resp(struct ieee80211_sub_if_data *sdata,
sdata->u.mgd.reconf.added_links &= ~BIT(link_id);
}
- pos += 3;
- len -= 3;
+ pos += sizeof(*reconf_status);
+ len -= sizeof(*reconf_status);
}
if (link_mask) {
--
2.34.1
^ permalink raw reply related
* Re: [PULL linux-firmware] ath10k, ath11k and ath12k firmware ath-20260325
From: Dmitry Baryshkov @ 2026-03-25 19:39 UTC (permalink / raw)
To: Jeff Johnson
Cc: linux-firmware, linux-wireless, ath10k, ath11k, ath12k, jjohnson
In-Reply-To: <32af006e-265f-4239-9b5f-be3d52996e10@oss.qualcomm.com>
On Wed, Mar 25, 2026 at 11:00:04AM -0700, Jeff Johnson wrote:
> Hi,
> Here's a new pull request for ath10k, ath11k and ath12k.
>
> No updates for ath10k or ath12k
>
> For ath11k:
> Update QCA6698AQ and WCN6855 firmware to address a WoW page fault issue.
>
> Please let me know if there are any problems.
Thanks, merged and pushed out:
https://gitlab.com/kernel-firmware/linux-firmware/-/merge_requests/974
>
> Thanks,
> /jeff
>
--
With best wishes
Dmitry
^ permalink raw reply
* RE: wifi: rtw89: rtw8922ae: HWSI bus lockup during RF recalibration on AP bandwidth change
From: Jeffrey Wälti @ 2026-03-25 19:25 UTC (permalink / raw)
To: Ping-Ke Shih; +Cc: linux-wireless@vger.kernel.org
In-Reply-To: <8bf447cc627746cca6eb30ae283bbbe6@realtek.com>
<pkshih@realtek.com> wrote:
> Jeffrey Wälti <jeffrey@waelti.dev> wrote:
> > Hi,
> >
> > I am experiencing a reproducible HWSI bus lockup on the RTL8922AE
> > (rtw89_8922ae) triggered by the connected AP changing its advertised
> > bandwidth in a beacon. During the lockup, the radio is unresponsive and the only
> > fix I could find is reconnecting to the network.
> >
> > The issue occurs on every boot within seconds of association, and also during
> > normal operation whenever the AP re-advertises its
> > channel width.
> >
> > I have tested with both the in-tree driver on kernel 6.19 and the
> > latest out-of-tree driver from morrownr/rtw89 (git HEAD). Both
> > reproduce the issue identically.
>
> Please try to disable power save and ASPM by
> 1) iw wlan0 set power_save off
> 2) reference and install https://github.com/lwfinger/rtw89/blob/main/70-rtw89.conf
> and then cold reboot.
>
> >
> > User-visible impact
> > -------------------
> >
> > During the HWSI busy window, all network traffic basically stops. Existing TCP
> > connections stall and time-sensitive applications (VoIP, video calls) break. The
> > Wi-Fi/BT coexistence is also disrupted, causing paired Bluetooth devices to
> > disconnect.
> >
> > The issue reproduces on every association and also during
> > runtime when the AP periodically re-advertises its bandwidth (sometimes every
> > few minutes), making connectivity unreliable.
> >
> > Boot-to-bug dmesg (trimmed to relevant entries)
> > ------------------------------------------------
> >
> > [ 17.659262] rtw89_8922ae 0000:03:00.0: loaded firmware
> > rtw89/rtw8922a_fw-4.bin
> > [ 17.659440] rtw89_8922ae 0000:03:00.0: enabling device (0000 -> 0003)
> > [ 17.666964] rtw89_8922ae 0000:03:00.0: Firmware version 0.35.80.3 (8ef4f0cf),
> > cmd version 1, type 1
> > [ 17.666968] rtw89_8922ae 0000:03:00.0: Firmware version 0.35.80.3 (8ef4f0cf),
> > cmd version 1, type 3
> > [ 17.685115] rtw89_8922ae 0000:03:00.0: chip rfe_type is 1
> > [ 17.685886] input: HD-Audio Generic Mic as
> > /devices/pci0000:00/0000:00:08.1/0000:04:00.6/sound/card1/input24
> > [ 17.685913] input: HD-Audio Generic Headphone as
> > /devices/pci0000:00/0000:00:08.1/0000:04:00.6/sound/card1/input25
> > [ 17.687499] rtw89_8922ae 0000:03:00.0: Firmware version 0.1.0.0 (7b393818),
> > cmd version 0, type 64
> > [ 17.687504] rtw89_8922ae 0000:03:00.0: Firmware element BB version: 00 49 00
> > 00
> > [ 17.687511] rtw89_8922ae 0000:03:00.0: Firmware element radio A version: 00
> > 33 00 00
> > [ 17.687516] rtw89_8922ae 0000:03:00.0: Firmware element NCTL version: 00 0f
> > 00 00
> > [ 17.687536] rtw89_8922ae 0000:03:00.0: Firmware element TXPWR version: 00 46
> > 00 00
> > [ 17.687537] rtw89_8922ae 0000:03:00.0: Firmware element TXPWR version: 00 46
> > 00 00
> > [ 17.687538] rtw89_8922ae 0000:03:00.0: Firmware element TXPWR version: 00 46
> > 00 00
> > [ 17.687546] rtw89_8922ae 0000:03:00.0: Firmware element PWR_TRK version: 00
> > 33 00 00
> > [ 17.687550] rtw89_8922ae 0000:03:00.0: Firmware element REGD version: 00 49
> > 00 08
> > [ 17.691873] rtw89_8922ae 0000:03:00.0: rfkill hardware state changed to
> > enable
> > [ 18.108033] systemd-journald[808]: Received client request to flush runtime
> > journal.
> > [ 18.367229] input: keyd virtual keyboard as /devices/virtual/input/input26
> > [ 18.383013] Bluetooth: BNEP (Ethernet Emulation) ver 1.3
> > [ 18.383017] Bluetooth: BNEP filters: protocol multicast
> > [ 18.383021] Bluetooth: BNEP socket layer initialized
> > [ 18.410929] input: keyd virtual pointer as /devices/virtual/input/input27
> > [ 18.464298] Bluetooth: hci0: RTL: fw version 0x41c0c905
> > [ 18.647322] Bluetooth: hci0: AOSP extensions version v1.00
> > [ 18.647546] Bluetooth: MGMT ver 1.23
> > [ 18.655814] NET: Registered PF_ALG protocol family
> > [ 21.985205] wlan0: authenticate with 7c:10:c9:b5:b4:4c (local
> > address=7c:fa:80:c3:5b:f9)
> > [ 21.985210] wlan0: send auth to 7c:10:c9:b5:b4:4c (try 1/3)
> > [ 22.001200] wlan0: authenticate with 7c:10:c9:b5:b4:4c (local
> > address=7c:fa:80:c3:5b:f9)
> > [ 22.001210] wlan0: send auth to 7c:10:c9:b5:b4:4c (try 1/3)
> > [ 22.002893] wlan0: authenticated
> > [ 22.003792] wlan0: associate with 7c:10:c9:b5:b4:4c (try 1/3)
> > [ 22.005327] wlan0: RX AssocResp from 7c:10:c9:b5:b4:4c (capab=0x1011
> > status=0 aid=17)
> > [ 22.111182] wlan0: associated
> > [ 22.111255] wlan0: Ignore NSS change to invalid 4 in VHT opmode notif from
> > 7c:10:c9:b5:b4:4c
> > [ 22.111263] wlan0: Limiting TX power to 23 (23 - 0) dBm as advertised by
> > 7c:10:c9:b5:b4:4c
> > [ 32.623170] hid-sensor-hub 0020:1022:0001.0004: hidraw3: SENSOR HUB HID
> > v0.00 Device [hid-amdsfh 1022:0001] on pcie_mp2_amd
> > [ 33.076564] wlan0: AP 7c:10:c9:b5:b4:4c changed bandwidth in beacon, new used
> > config is 5220.000 MHz, width 5 (5250.000/0 MHz)
> > [ 33.090085] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
> > [ 33.102460] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI
> > is busy
> > [ 33.114775] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
> > [ 33.127371] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI
> > is busy
> > [ 33.141826] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
> > [ 33.153783] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
> > [ 33.165901] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
> > [ 33.178402] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI
> > is busy
> > [ 33.191675] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI
> > is busy
> > [ 33.205185] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
> > [ 33.217544] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI
> > is busy
> > [ 33.229788] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
> > [ 33.242802] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI
> > is busy
> > [ 33.257200] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
> > [ 33.269858] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI
> > is busy
> > [ 33.282153] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
> > [ 33.295625] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI
> > is busy
> > [ 33.307822] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
> > [ 33.320258] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI
> > is busy
> > [ 33.332693] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
> > [ 33.345004] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI
> > is busy
> > [ 33.360051] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
> > [ 33.373084] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
> > [ 33.385703] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
> > [ 33.397827] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI
> > is busy
> > [ 33.411372] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI
> > is busy
> > [ 33.426744] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
> > [ 33.438969] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI
> > is busy
> > [ 33.451407] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
> > [ 33.464456] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI
> > is busy
> > [ 33.477296] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
> > [ 33.489589] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI
> > is busy
> > [ 33.502064] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
> > [ 33.514235] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI
> > is busy
> > [ 33.514249] wlan0: Ignore NSS change to invalid 4 in VHT opmode notif from
> > 7c:10:c9:b5:b4:4c
> > [ 34.491697] rfkill: input handler disabled
> > [ 34.778186] Bluetooth: RFCOMM TTY layer initialized
> > [ 34.778198] Bluetooth: RFCOMM socket layer initialized
> > [ 34.778201] Bluetooth: RFCOMM ver 1.11
> > [ 39.723603] rfkill: input handler enabled
> > [ 40.871391] rfkill: input handler disabled
> > [ 41.010414] nvme nvme0: using unchecked data buffer
> > [ 43.272141] warning: `ThreadPoolForeg' uses wireless extensions which will
> > stop working for Wi-Fi 7 hardware; use nl80211
> >
> > Environment
> > -----------
> >
> > Linux version 6.19.9-1-cachyos (linux-cachyos@cachyos) (clang version 22.1.1,
> > LLD 22.1.1) #1 SMP PREEMPT_DYNAMIC Thu, 19 Mar 2026 20:13:27 +0000
> >
> > 03:00.0 Network controller [0280]: Realtek Semiconductor Co., Ltd. RTL8922AE
> > 802.11be PCIe Wireless Network Adapter [10ec:8922] (rev 01)
> > Subsystem: Lenovo Device [17aa:4922]
> > Control: I/O+ Mem+ BusMaster+ SpecCycle- MemWINV- VGASnoop- ParErr-
> > Stepping- SERR- FastB2B- DisINTx+
> > Status: Cap+ 66MHz- UDF- FastB2B- ParErr- DEVSEL=fast >TAbort- <TAbort-
> > <MAbort- >SERR- <PERR- INTx-
> > Latency: 0
> > Interrupt: pin A routed to IRQ 112
> > IOMMU group: 15
> > Region 0: I/O ports at 2000 [size=256]
> > Region 2: Memory at 80b00000 (64-bit, non-prefetchable) [size=1M]
> > Capabilities: <access denied>
> > Kernel driver in use: rtw89_8922ae
> > Kernel modules: rtw89_8922ae
> >
> > rtw89_8922ae 12288 0
> > rtw89_8922a 77824 1 rtw89_8922ae
> > rtw89_pci 131072 1 rtw89_8922ae
> > rtw89_core 1236992 2 rtw89_8922a,rtw89_pci
> > mac80211 1806336 2 rtw89_core,rtw89_pci
> > cfg80211 1523712 3 rtw89_core,rtw89_8922a,mac80211
> > rfkill 45056 9 rtw89_core,bluetooth,ideapad_laptop,cfg80211
> >
> > Hardware: Lenovo Yoga 7 2-in-1 14AKP10 (machine type 83JR)
> > Chip: RTL8922AE (PCI ID 10ec:8922)
> > Firmware: rtw89/rtw8922a_fw-4.bin, version 0.35.80.3 (8ef4f0cf)
> > RFE type: 1
> >
> > I am happy to provide additional debugging information, test patches, or collect
> > further traces if needed.
>
> Please help to test the latest kernel 7.0-rc with additional patch [1].
>
> [1] https://lore.kernel.org/linux-wireless/20260310080146.31113-4-pkshih@realtek.com/
>
> Ping-Ke
>
>
Thank you for coming back to me so quickly, I just encountered the same thing with kernel 7.0-rc5.
[26055.113514] wlan0: authenticate with 7c:10:c9:b5:b4:4c (local address=7c:fa:80:c3:5b:f9)
[26055.113528] wlan0: send auth to 7c:10:c9:b5:b4:4c (try 1/3)
[26055.133089] wlan0: send auth to 7c:10:c9:b5:b4:4c (try 2/3)
[26055.150902] wlan0: authenticate with 7c:10:c9:b5:b4:4c (local address=7c:fa:80:c3:5b:f9)
[26055.150914] wlan0: send auth to 7c:10:c9:b5:b4:4c (try 1/3)
[26055.153246] wlan0: authenticated
[26055.153721] wlan0: associate with 7c:10:c9:b5:b4:4c (try 1/3)
[26055.155584] wlan0: RX AssocResp from 7c:10:c9:b5:b4:4c (capab=0x1011 status=0 aid=27)
[26055.271023] wlan0: associated
[26055.271237] wlan0: Ignore NSS change to invalid 4 in VHT opmode notif from 7c:10:c9:b5:b4:4c
[26055.271248] wlan0: Limiting TX power to 23 (23 - 0) dBm as advertised by 7c:10:c9:b5:b4:4c
[26065.984663] wlan0: AP 7c:10:c9:b5:b4:4c changed bandwidth in beacon, new used config is 5220.000 MHz, width 5 (5250.000/0 MHz)
[26065.999102] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
[26066.012897] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI is busy
[26066.026604] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
[26066.040218] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI is busy
[26066.055075] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
[26066.066562] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
[26066.078117] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
[26066.090357] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI is busy
[26066.102961] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI is busy
[26066.115725] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
[26066.127339] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI is busy
[26066.139004] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
[26066.150442] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI is busy
[26066.161916] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
[26066.173963] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI is busy
[26066.185870] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
[26066.197456] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI is busy
[26066.209340] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
[26066.221632] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI is busy
[26066.233635] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
[26066.245872] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI is busy
[26066.259756] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
[26066.271685] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
[26066.283509] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
[26066.295651] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI is busy
[26066.308285] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI is busy
[26066.320675] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
[26066.332706] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI is busy
[26066.344874] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
[26066.356848] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI is busy
[26066.369061] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
[26066.381196] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI is busy
[26066.393249] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
[26066.405227] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI is busy
[26066.405240] wlan0: Ignore NSS change to invalid 4 in VHT opmode notif from 7c:10:c9:b5:b4:4c
[26078.276959] wlan0: authenticate with 7c:10:c9:b5:b4:48 (local address=7c:fa:80:c3:5b:f9)
[26078.276970] wlan0: send auth to 7c:10:c9:b5:b4:48 (try 1/3)
[26078.295560] wlan0: authenticate with 7c:10:c9:b5:b4:48 (local address=7c:fa:80:c3:5b:f9)
[26078.295573] wlan0: send auth to 7c:10:c9:b5:b4:48 (try 1/3)
[26078.305323] wlan0: authenticated
[26078.307561] wlan0: associate with 7c:10:c9:b5:b4:48 (try 1/3)
[26078.313095] wlan0: RX AssocResp from 7c:10:c9:b5:b4:48 (capab=0x1411 status=0 aid=33)
[26078.424096] wlan0: associated
[26165.453429] ideapad_acpi VPC2004:00: unexpected charge_types: both [Fast] and [Long_Life] are enabled
[26226.049306] wlan0: disconnect from AP 7c:10:c9:b5:b4:48 for new auth to 7c:10:c9:b5:b4:4c
[26226.283858] wlan0: authenticate with 7c:10:c9:b5:b4:4c (local address=7c:fa:80:c3:5b:f9)
[26226.283870] wlan0: send auth to 7c:10:c9:b5:b4:4c (try 1/3)
[26226.284759] wlan0: authenticated
[26226.286909] wlan0: associate with 7c:10:c9:b5:b4:4c (try 1/3)
[26226.289664] wlan0: RX ReassocResp from 7c:10:c9:b5:b4:4c (capab=0x1011 status=0 aid=13)
[26226.400500] wlan0: associated
[26226.400560] wlan0: Ignore NSS change to invalid 4 in VHT opmode notif from 7c:10:c9:b5:b4:4c
[26226.400566] wlan0: Limiting TX power to 23 (23 - 0) dBm as advertised by 7c:10:c9:b5:b4:4c
[26237.305703] wlan0: AP 7c:10:c9:b5:b4:4c changed bandwidth in beacon, new used config is 5220.000 MHz, width 5 (5250.000/0 MHz)
[26237.323124] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
[26237.338894] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI is busy
[26237.352942] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
[26237.366488] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI is busy
[26237.380974] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
[26237.396258] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
[26237.410103] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
[26237.423962] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI is busy
[26237.439875] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI is busy
[26237.454635] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
[26237.468377] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI is busy
[26237.482203] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
[26237.496234] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI is busy
[26237.510190] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
[26237.524248] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI is busy
[26237.536842] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
[26237.550513] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI is busy
[26237.564329] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
[26237.578157] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI is busy
[26237.591944] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
[26237.605710] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI is busy
[26237.621850] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
[26237.635990] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
[26237.650297] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
[26237.664291] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI is busy
[26237.679167] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI is busy
[26237.694107] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
[26237.708096] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI is busy
[26237.722032] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
[26237.735945] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI is busy
[26237.749689] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
[26237.763441] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI is busy
[26237.777281] rtw89_8922ae 0000:03:00.0: poll HWSI is busy
[26237.791136] rtw89_8922ae 0000:03:00.0: [rtw89_phy_write_full_rf_v2_a] HWSI is busy
[26237.791148] wlan0: Ignore NSS change to invalid 4 in VHT opmode notif from 7c:10:c9:b5:b4:4c
[26323.825259] wlan0: deauthenticating from 7c:10:c9:b5:b4:4c by local choice (Reason: 3=DEAUTH_LEAVING)
[26324.373581] wlan0: authenticate with 7c:10:c9:b5:b4:4c (local address=7c:fa:80:c3:5b:f9)
[26324.373593] wlan0: send auth to 7c:10:c9:b5:b4:4c (try 1/3)
[26324.391589] wlan0: authenticate with 7c:10:c9:b5:b4:4c (local address=7c:fa:80:c3:5b:f9)
[26324.391624] wlan0: send auth to 7c:10:c9:b5:b4:4c (try 1/3)
[26324.393858] wlan0: authenticated
[26324.394270] wlan0: associate with 7c:10:c9:b5:b4:4c (try 1/3)
[26324.395784] wlan0: RX AssocResp from 7c:10:c9:b5:b4:4c (capab=0x1011 status=0 aid=20)
[26324.503689] wlan0: associated
[26324.503778] wlan0: Limiting TX power to 23 (23 - 0) dBm as advertised by 7c:10:c9:b5:b4:4c
Jeffrey Wälti
^ permalink raw reply
* [PULL linux-firmware] ath10k, ath11k and ath12k firmware ath-20260325
From: Jeff Johnson @ 2026-03-25 18:00 UTC (permalink / raw)
To: linux-firmware; +Cc: linux-wireless, ath10k, ath11k, ath12k, jjohnson
The following changes since commit 51d2775ba036b0dfbfe44c7ba0a1350ddaefc058:
Merge branch 'qat_4xxx' into 'main' (2026-03-23 21:17:08 +0000)
are available in the Git repository at:
git://git.kernel.org/pub/scm/linux/kernel/git/ath/linux-firmware.git ath-20260325
for you to fetch changes up to b56c03b621391558627e3aafe5a8dc32d3997680:
ath11k: WCN6855 hw2.0@nfa765: update to WLAN.HSP.1.1-04866.5-QCAHSPSWPL_V1_V2_SILICONZ_IOE-1 (2026-03-25 10:53:18 -0700)
----------------------------------------------------------------
Hi,
Here's a new pull request for ath10k, ath11k and ath12k.
No updates for ath10k or ath12k
For ath11k:
Update QCA6698AQ and WCN6855 firmware to address a WoW page fault issue.
Please let me know if there are any problems.
Thanks,
/jeff
----------------------------------------------------------------
Jeff Johnson (2):
ath11k: QCA6698AQ hw2.1: update to WLAN.HSP.1.1-04866.5-QCAHSPSWPL_V1_V2_SILICONZ_IOE-1
ath11k: WCN6855 hw2.0@nfa765: update to WLAN.HSP.1.1-04866.5-QCAHSPSWPL_V1_V2_SILICONZ_IOE-1
WHENCE | 4 ++--
ath11k/QCA6698AQ/hw2.1/amss.bin | Bin 5083136 -> 5079040 bytes
ath11k/WCN6855/hw2.0/nfa765/amss.bin | Bin 5005312 -> 5079040 bytes
ath11k/WCN6855/hw2.0/nfa765/m3.bin | Bin 266684 -> 266684 bytes
4 files changed, 2 insertions(+), 2 deletions(-)
^ permalink raw reply
* [PATCH v4 0/3] ath10k: Introduce a devicetree quirk to skip host cap QMI requests
From: David Heidelberg via B4 Relay @ 2026-03-25 17:57 UTC (permalink / raw)
To: Johannes Berg, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jeff Johnson, Bjorn Andersson, Konrad Dybcio, Paul Sajna
Cc: Amit Pundir, linux-wireless, devicetree, ath10k, linux-kernel,
linux-arm-msm, phone-devel, David Heidelberg
This quirk is used so far used on:
- LG G7 ThinQ
- Xiaomi Poco F1
I'm resending it after ~ 4 years since initial send due to Snapdragon
845 being one of best supported platform for mobile phones running
Linux, so it would be shame to not have shiny support.
Original thread:
https://lore.kernel.org/all/b796bfee-b753-479a-a8d6-ba1fe3ee6222@ixit.cz/
I tried the embedding the information inside the firmware, but the
information is required *before* loading the firmware itself.
Firmware quirk thread:
https://lore.kernel.org/linux-wireless/20251111-xiaomi-beryllium-firmware-v1-0-836b9c51ad86@ixit.cz/
Until merged, available also at:
https://codeberg.org/sdm845/linux/commits/branch/b4/skip-host-cam-qmi-req
Signed-off-by: David Heidelberg <david@ixit.cz>
---
Changes in v4:
- Added my own missing SoB. (Dmitry)
- Improve the commit message. (Dmitry)
- Link to v3: https://lore.kernel.org/r/20260325-skip-host-cam-qmi-req-v3-0-b163cf7b3c81@ixit.cz
Changes in v3:
- Rebased on recent linux-next (next-20260325).
- Improved motivation and description. (Dmitry)
- Link to v2: https://lore.kernel.org/r/20251110-skip-host-cam-qmi-req-v2-0-0daf485a987a@ixit.cz
---
Amit Pundir (3):
dt-bindings: wireless: ath10k: Add quirk to skip host cap QMI requests
ath10k: Add device-tree quirk to skip host cap QMI requests
arm64: dts: qcom: sdm845-xiaomi-beryllium: Enable ath10k host-cap skip quirk
.../devicetree/bindings/net/wireless/qcom,ath10k.yaml | 6 ++++++
.../arm64/boot/dts/qcom/sdm845-xiaomi-beryllium-common.dtsi | 1 +
drivers/net/wireless/ath/ath10k/qmi.c | 13 ++++++++++---
drivers/net/wireless/ath/ath10k/snoc.c | 3 +++
drivers/net/wireless/ath/ath10k/snoc.h | 1 +
5 files changed, 21 insertions(+), 3 deletions(-)
---
base-commit: 85964cdcad0fac9a0eb7b87a0f9d88cc074b854c
change-id: 20251110-skip-host-cam-qmi-req-e155628ebc39
Best regards,
--
David Heidelberg <david@ixit.cz>
^ permalink raw reply
* [PATCH v4 3/3] arm64: dts: qcom: sdm845-xiaomi-beryllium: Enable ath10k host-cap skip quirk
From: David Heidelberg via B4 Relay @ 2026-03-25 17:57 UTC (permalink / raw)
To: Johannes Berg, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jeff Johnson, Bjorn Andersson, Konrad Dybcio, Paul Sajna
Cc: Amit Pundir, linux-wireless, devicetree, ath10k, linux-kernel,
linux-arm-msm, phone-devel, David Heidelberg
In-Reply-To: <20260325-skip-host-cam-qmi-req-v4-0-bc08538487aa@ixit.cz>
From: Amit Pundir <amit.pundir@linaro.org>
The Wi-Fi firmware used on Xiaomi Poco F1 (beryllium) phone doesn't
support the host-capability QMI request, so add a quirk to skip it on
this device.
Signed-off-by: Amit Pundir <amit.pundir@linaro.org>
Signed-off-by: David Heidelberg <david@ixit.cz>
---
arch/arm64/boot/dts/qcom/sdm845-xiaomi-beryllium-common.dtsi | 1 +
1 file changed, 1 insertion(+)
diff --git a/arch/arm64/boot/dts/qcom/sdm845-xiaomi-beryllium-common.dtsi b/arch/arm64/boot/dts/qcom/sdm845-xiaomi-beryllium-common.dtsi
index 1298485c42142..950bbcc3bf91f 100644
--- a/arch/arm64/boot/dts/qcom/sdm845-xiaomi-beryllium-common.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm845-xiaomi-beryllium-common.dtsi
@@ -661,5 +661,6 @@ &wifi {
vdd-3.3-ch1-supply = <&vreg_l23a_3p3>;
qcom,calibration-variant = "xiaomi_beryllium";
+ qcom,snoc-host-cap-skip-quirk;
};
--
2.53.0
^ permalink raw reply related
* [PATCH v4 2/3] ath10k: Add device-tree quirk to skip host cap QMI requests
From: David Heidelberg via B4 Relay @ 2026-03-25 17:57 UTC (permalink / raw)
To: Johannes Berg, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jeff Johnson, Bjorn Andersson, Konrad Dybcio, Paul Sajna
Cc: Amit Pundir, linux-wireless, devicetree, ath10k, linux-kernel,
linux-arm-msm, phone-devel, David Heidelberg
In-Reply-To: <20260325-skip-host-cam-qmi-req-v4-0-bc08538487aa@ixit.cz>
From: Amit Pundir <amit.pundir@linaro.org>
Some firmware versions do not support the host capability QMI request.
Since this request occurs before firmware-N.bin and board-M.bin are
loaded, the quirk cannot be expressed in the firmware itself.
The root cause is unclear, but there appears to be a generation of
firmware that lacks host capability support.
Without this quirk, ath10k_qmi_host_cap_send_sync() returns
QMI_ERR_MALFORMED_MSG_V01 before loading the firmware. This error is not
fatal - Wi-Fi services still come up successfully if the request is simply
skipped.
Add a device-tree quirk to skip the host capability QMI request on devices
whose firmware does not support it.
For example, firmware build
"QC_IMAGE_VERSION_STRING=WLAN.HL.2.0.c3-00257-QCAHLSWMTPLZ-1"
on Xiaomi Poco F1 phone requires this quirk.
Suggested-by: Bjorn Andersson <andersson@kernel.org>
Signed-off-by: Amit Pundir <amit.pundir@linaro.org>
Signed-off-by: David Heidelberg <david@ixit.cz>
---
drivers/net/wireless/ath/ath10k/qmi.c | 13 ++++++++++---
drivers/net/wireless/ath/ath10k/snoc.c | 3 +++
drivers/net/wireless/ath/ath10k/snoc.h | 1 +
3 files changed, 14 insertions(+), 3 deletions(-)
diff --git a/drivers/net/wireless/ath/ath10k/qmi.c b/drivers/net/wireless/ath/ath10k/qmi.c
index eebd78e7ff6bc..e7f90fd9e9b83 100644
--- a/drivers/net/wireless/ath/ath10k/qmi.c
+++ b/drivers/net/wireless/ath/ath10k/qmi.c
@@ -808,6 +808,7 @@ ath10k_qmi_ind_register_send_sync_msg(struct ath10k_qmi *qmi)
static void ath10k_qmi_event_server_arrive(struct ath10k_qmi *qmi)
{
struct ath10k *ar = qmi->ar;
+ struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar);
int ret;
ret = ath10k_qmi_ind_register_send_sync_msg(qmi);
@@ -819,9 +820,15 @@ static void ath10k_qmi_event_server_arrive(struct ath10k_qmi *qmi)
return;
}
- ret = ath10k_qmi_host_cap_send_sync(qmi);
- if (ret)
- return;
+ /*
+ * Skip the host capability request for the firmware versions which
+ * do not support this feature.
+ */
+ if (!test_bit(ATH10K_SNOC_FLAG_SKIP_HOST_CAP_QUIRK, &ar_snoc->flags)) {
+ ret = ath10k_qmi_host_cap_send_sync(qmi);
+ if (ret)
+ return;
+ }
ret = ath10k_qmi_msa_mem_info_send_sync_msg(qmi);
if (ret)
diff --git a/drivers/net/wireless/ath/ath10k/snoc.c b/drivers/net/wireless/ath/ath10k/snoc.c
index f72f236fb9eb3..3106502275781 100644
--- a/drivers/net/wireless/ath/ath10k/snoc.c
+++ b/drivers/net/wireless/ath/ath10k/snoc.c
@@ -1362,6 +1362,9 @@ static void ath10k_snoc_quirks_init(struct ath10k *ar)
if (of_property_read_bool(dev->of_node, "qcom,snoc-host-cap-8bit-quirk"))
set_bit(ATH10K_SNOC_FLAG_8BIT_HOST_CAP_QUIRK, &ar_snoc->flags);
+
+ if (of_property_read_bool(dev->of_node, "qcom,snoc-host-cap-skip-quirk"))
+ set_bit(ATH10K_SNOC_FLAG_SKIP_HOST_CAP_QUIRK, &ar_snoc->flags);
}
int ath10k_snoc_fw_indication(struct ath10k *ar, u64 type)
diff --git a/drivers/net/wireless/ath/ath10k/snoc.h b/drivers/net/wireless/ath/ath10k/snoc.h
index 1ecae34687c21..46574fd8f84ee 100644
--- a/drivers/net/wireless/ath/ath10k/snoc.h
+++ b/drivers/net/wireless/ath/ath10k/snoc.h
@@ -51,6 +51,7 @@ enum ath10k_snoc_flags {
ATH10K_SNOC_FLAG_MODEM_STOPPED,
ATH10K_SNOC_FLAG_RECOVERY,
ATH10K_SNOC_FLAG_8BIT_HOST_CAP_QUIRK,
+ ATH10K_SNOC_FLAG_SKIP_HOST_CAP_QUIRK,
};
struct clk_bulk_data;
--
2.53.0
^ permalink raw reply related
* [PATCH v4 1/3] dt-bindings: wireless: ath10k: Add quirk to skip host cap QMI requests
From: David Heidelberg via B4 Relay @ 2026-03-25 17:57 UTC (permalink / raw)
To: Johannes Berg, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jeff Johnson, Bjorn Andersson, Konrad Dybcio, Paul Sajna
Cc: Amit Pundir, linux-wireless, devicetree, ath10k, linux-kernel,
linux-arm-msm, phone-devel, David Heidelberg
In-Reply-To: <20260325-skip-host-cam-qmi-req-v4-0-bc08538487aa@ixit.cz>
From: Amit Pundir <amit.pundir@linaro.org>
Some firmware versions do not support the host-capability QMI request.
Since this request occurs before firmware and board files are loaded,
the quirk cannot be expressed in the firmware itself and must be described
in the device tree.
Signed-off-by: Amit Pundir <amit.pundir@linaro.org>
Signed-off-by: David Heidelberg <david@ixit.cz>
---
Documentation/devicetree/bindings/net/wireless/qcom,ath10k.yaml | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/Documentation/devicetree/bindings/net/wireless/qcom,ath10k.yaml b/Documentation/devicetree/bindings/net/wireless/qcom,ath10k.yaml
index f2440d39b7ebc..5120b3589ab57 100644
--- a/Documentation/devicetree/bindings/net/wireless/qcom,ath10k.yaml
+++ b/Documentation/devicetree/bindings/net/wireless/qcom,ath10k.yaml
@@ -171,6 +171,12 @@ properties:
Quirk specifying that the firmware expects the 8bit version
of the host capability QMI request
+ qcom,snoc-host-cap-skip-quirk:
+ type: boolean
+ description:
+ Quirk specifying that the firmware wants to skip the host
+ capability QMI request
+
qcom,xo-cal-data:
$ref: /schemas/types.yaml#/definitions/uint32
description:
--
2.53.0
^ permalink raw reply related
* Re: [PATCH v3 2/3] ath10k: Introduce a device-tree quirk to skip host cap QMI requests
From: David Heidelberg @ 2026-03-25 17:39 UTC (permalink / raw)
To: Dmitry Baryshkov
Cc: Johannes Berg, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jeff Johnson, Bjorn Andersson, Konrad Dybcio, Paul Sajna,
Amit Pundir, linux-wireless, devicetree, ath10k, linux-kernel,
linux-arm-msm, phone-devel
In-Reply-To: <d6pv62kc5zyqite7krm65vbtlqnsc3v53rlrtilchyk5c7uad2@iu4yaw2ksr65>
On 25/03/2026 18:15, Dmitry Baryshkov wrote:
> On Wed, Mar 25, 2026 at 05:41:13PM +0100, David Heidelberg via B4 Relay wrote:
>> From: Amit Pundir <amit.pundir@linaro.org>
>>
>> There are firmware versions which do not support host capability QMI
>> request. We suspect either the host cap is not implemented or there may
>> be firmware specific issues, but apparently there seem to be a generation
>> of firmware that has this particular behavior.
>
> It needs to be explicit that this happens _before_ firmware-N.bin and
> board-M.bin loading. As such, you can't add a quirk to the firmware.bin
> (a standard way to handle firmware issues).
Ok, let me send with updated desc :)
>
>> For example, firmware build on Xiaomi Poco F1 (sdm845) phone:
>> "QC_IMAGE_VERSION_STRING=WLAN.HL.2.0.c3-00257-QCAHLSWMTPLZ-1"
>>
>> If we do not skip the host cap QMI request on Poco F1, then we get a
>> QMI_ERR_MALFORMED_MSG_V01 error message before loading the firmware in the
>> ath10k_qmi_host_cap_send_sync(). This error message is not fatal to the
>> firmware nor to the ath10k driver and we can still bring up the WiFi
>> services successfully if we just ignore it.
>>
>> Hence introducing this device-tree quirk to skip host capability
>> QMI request for the devices with firmware versions which do not support
>> this feature.
>>
>> Suggested-by: Bjorn Andersson <andersson@kernel.org>
>> Signed-off-by: Amit Pundir <amit.pundir@linaro.org>
>
> You are sending the patch, but it misses your SoB.
Oh, sorry bout that.
checkpatch.pl could spot this kind of an issue :P
David>
>> ---
>> drivers/net/wireless/ath/ath10k/qmi.c | 13 ++++++++++---
>> drivers/net/wireless/ath/ath10k/snoc.c | 3 +++
>> drivers/net/wireless/ath/ath10k/snoc.h | 1 +
>> 3 files changed, 14 insertions(+), 3 deletions(-)
>
--
David Heidelberg
^ permalink raw reply
* Re: [PATCH net-next v3 03/13] net: introduce ndo_set_rx_mode_async and dev_rx_mode_work
From: Stanislav Fomichev @ 2026-03-25 17:34 UTC (permalink / raw)
To: Jakub Kicinski, Stanislav Fomichev, netdev, davem, edumazet,
pabeni, horms, corbet, skhan, andrew+netdev, michael.chan,
pavan.chebbi, anthony.l.nguyen, przemyslaw.kitszel, saeedm,
tariqt, mbloch, alexanderduyck, kernel-team, johannes, sd,
jianbol, dtatulea, mohsin.bashr, jacob.e.keller, willemb,
skhawaja, bestswngs, aleksandr.loktionov, kees, linux-doc,
linux-kernel, intel-wired-lan, linux-rdma, linux-wireless,
linux-kselftest, leon
In-Reply-To: <acP59NM6HZhV9oAe@mini-arch>
On 03/25, Stanislav Fomichev wrote:
> On 03/24, Jakub Kicinski wrote:
> > On Tue, 24 Mar 2026 15:49:27 -0700 Stanislav Fomichev wrote:
> > > > > Not sure why cancel+release, maybe you're thinking about the unregister
> > > > > path? This is rtnl_unlock -> netdev_run_todo -> __rtnl_unlock + some
> > > > > extras.
> > > > >
> > > > > And the flush is here to plumb the addresses to the real devices
> > > > > before we return to the callers. Mostly because of the following
> > > > > things we have in the tests:
> > > > >
> > > > > # TEST: team cleanup mode lacp [FAIL]
> > > > > # macvlan unicast address not found on a slave
> > > > >
> > > > > Can you explain a bit more on the suggestion?
> > > >
> > > > Oh, I thought it's here for unregister! Feels like it'd be cleaner to
> > > > add the flush in dev_*c_add() and friends? How hard would it be to
> > > > identify the callers in atomic context?
> > >
> > > Not sure we can do it in dev_xc_add because it runs under rtnl :-(
> > > I currently do flush in netdev_run_todo because that's the place that
> > > doesn't hold rtnl. Otherwise flush will get stuck because the work
> > > handler grabs it...
> >
> > I was thinking of something a'la linkwatch. We can "steal" / "flush"
> > the pending work inline. I guess linkwatch is a major source of races
> > over the years...
> >
> > Does the macvlan + team problem still happens with the current
> > implementation minus the flush? We are only flushing once so only
> > pushing the addresses thru one layer of async callbacks.
>
> Yes, it does happen consistently when I remove the flush. It also
> happens with my internal v4, so I need to look again at what's going on.
> Not sure whether it's my internal regression or I was just sloppy/lucky
> (since you're correct in pointing out that we flush only once).
Hmm, the test does 'team -d' in the background. That's why it works for
bonding, but not the teaming. I'll update the test to a bunch of
'ip' commands instead of starting a daemon..
> Before I went down the workqueue route, I had a simple
> net_todo_list-like approach: `list_add_tail` on enqueue and
> `while(!list_empty) run_work()` on rtnl_unlock. This had a nice properly of
> tracking re-submissions (by checking whether the device's list_head is
> linked into the list or not) and it was relatively easy to do the
> recursive flush. Let me try get back to this approach and see whether
> it solves the flush? Not sure what wq buys us at this point.
Will still look into that, maybe something similar to the linkwatch as
you mentioned.
^ permalink raw reply
* Re: [PATCH] wifi: virt_wifi: remove SET_NETDEV_DEV to avoid use-after-free
From: Alexander Popov @ 2026-03-25 17:24 UTC (permalink / raw)
To: Andrew Lunn
Cc: Jakub Kicinski, David Miller, Eric Dumazet, Paolo Abeni,
Simon Horman, Maxime Chevallier, Michal Kubecek, Gal Pressman,
Kory Maincent, Oleksij Rempel, Ido Schimmel, Heiner Kallweit,
Greg KH, Johannes Berg, James Guan, Kees Cook, Paul Moses,
linux-wireless, netdev, linux-kernel, security, notify
In-Reply-To: <fa3c91c3-9ceb-4fb2-9250-cc239fb0c1b6@lunn.ch>
On 3/25/26 15:34, Andrew Lunn wrote:
> On Wed, Mar 25, 2026 at 01:46:02AM +0300, Alexander Popov wrote:
>> Currently we execute `SET_NETDEV_DEV(dev, &priv->lowerdev->dev)` for
>> the virt_wifi net devices. However, unregistering a virt_wifi device in
>> netdev_run_todo() can happen together with the device referenced by
>> SET_NETDEV_DEV().
>>
>> It can result in use-after-free during the ethtool operations performed
>> on a virt_wifi device that is currently being unregistered. Such a net
>> device can have the `dev.parent` field pointing to the freed memory,
>> but ethnl_ops_begin() calls `pm_runtime_get_sync(dev->dev.parent)`.
>>
>> Let's remove SET_NETDEV_DEV for virt_wifi to avoid bugs like this:
>
> Did you have a look at all user of SET_NETDEV_DEV() to see if there
> are other examples of the same bug?
>
> What i found was:
>
> https://elixir.bootlin.com/linux/v6.19.9/source/drivers/net/ethernet/mellanox/mlx4/en_netdev.c#L3180
>
> Does this have the same problem?
Andrew, I can't say about this particular net device. Looks like it refers to a
specific ethernet adapter.
How can we distinguish security-relevant bugs similar to this use-after-free:
an unprivileged user must be able to create a given net device via user namespaces.
As I mentioned, applying this fix in ethtool could help against them:
https://lore.kernel.org/all/20260322075917.254874-1-alex.popov@linux.com/T/#u
Best regards,
Alexander
^ permalink raw reply
* [PATCH v2] wifi: mt76: mt7996: disable UNI_BSS_INFO_PROTECT_INFO for mt7996
From: Ryder Lee @ 2026-03-25 17:17 UTC (permalink / raw)
To: Felix Fietkau; +Cc: linux-mediatek, linux-wireless, Shayne Chen, Ryder Lee
The current MT7996 firmware causes TX failure and need further
investigation, so it is temporarily disabled.
MT7992 and MT7990 are working normally.
Signed-off-by: Ryder Lee <ryder.lee@mediatek.com>
---
v2 - add a comment to describe why this check is required.
---
drivers/net/wireless/mediatek/mt76/mt7996/mcu.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
index 16420375112d..ae068392b9dd 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
@@ -1281,6 +1281,10 @@ int mt7996_mcu_set_protection(struct mt7996_phy *phy, struct mt7996_vif_link *li
PROT_NONGF_STA = BIT(7),
};
+ /* The current firmware causes TX failure. Need further investigation */
+ if (is_mt7996(&dev->mt76))
+ return 0;
+
skb = __mt7996_mcu_alloc_bss_req(&dev->mt76, &link->mt76,
MT7996_BSS_UPDATE_MAX_SIZE);
if (IS_ERR(skb))
--
2.45.2
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox