* [PATCH net-next v2 05/13] fbnic: convert to ndo_set_rx_mode_async
From: Stanislav Fomichev @ 2026-03-18 15:02 UTC (permalink / raw)
To: netdev
Cc: davem, edumazet, kuba, 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, sdf, mohsin.bashr,
jacob.e.keller, willemb, skhawaja, bestswngs, linux-doc,
linux-kernel, intel-wired-lan, linux-rdma, linux-wireless,
linux-kselftest, leon
In-Reply-To: <20260318150305.123900-1-sdf@fomichev.me>
Convert fbnic from ndo_set_rx_mode to ndo_set_rx_mode_async. The
driver's __fbnic_set_rx_mode() now takes explicit uc/mc list
parameters and uses __hw_addr_sync_dev() on the snapshots instead
of __dev_uc_sync/__dev_mc_sync on the netdev directly.
Update callers in fbnic_up, fbnic_fw_config_after_crash,
fbnic_bmc_rpc_check and fbnic_set_mac to pass the real address
lists calling __fbnic_set_rx_mode outside the async work path.
Cc: Alexander Duyck <alexanderduyck@fb.com>
Cc: kernel-team@meta.com
Signed-off-by: Stanislav Fomichev <sdf@fomichev.me>
---
.../net/ethernet/meta/fbnic/fbnic_netdev.c | 20 ++++++++++++-------
.../net/ethernet/meta/fbnic/fbnic_netdev.h | 4 +++-
drivers/net/ethernet/meta/fbnic/fbnic_pci.c | 4 ++--
drivers/net/ethernet/meta/fbnic/fbnic_rpc.c | 2 +-
4 files changed, 19 insertions(+), 11 deletions(-)
diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_netdev.c b/drivers/net/ethernet/meta/fbnic/fbnic_netdev.c
index b4b396ca9bce..c406a3b56b37 100644
--- a/drivers/net/ethernet/meta/fbnic/fbnic_netdev.c
+++ b/drivers/net/ethernet/meta/fbnic/fbnic_netdev.c
@@ -183,7 +183,9 @@ static int fbnic_mc_unsync(struct net_device *netdev, const unsigned char *addr)
return ret;
}
-void __fbnic_set_rx_mode(struct fbnic_dev *fbd)
+void __fbnic_set_rx_mode(struct fbnic_dev *fbd,
+ struct netdev_hw_addr_list *uc,
+ struct netdev_hw_addr_list *mc)
{
bool uc_promisc = false, mc_promisc = false;
struct net_device *netdev = fbd->netdev;
@@ -213,10 +215,10 @@ void __fbnic_set_rx_mode(struct fbnic_dev *fbd)
}
/* Synchronize unicast and multicast address lists */
- err = __dev_uc_sync(netdev, fbnic_uc_sync, fbnic_uc_unsync);
+ err = __hw_addr_sync_dev(uc, netdev, fbnic_uc_sync, fbnic_uc_unsync);
if (err == -ENOSPC)
uc_promisc = true;
- err = __dev_mc_sync(netdev, fbnic_mc_sync, fbnic_mc_unsync);
+ err = __hw_addr_sync_dev(mc, netdev, fbnic_mc_sync, fbnic_mc_unsync);
if (err == -ENOSPC)
mc_promisc = true;
@@ -238,18 +240,21 @@ void __fbnic_set_rx_mode(struct fbnic_dev *fbd)
fbnic_write_tce_tcam(fbd);
}
-static void fbnic_set_rx_mode(struct net_device *netdev)
+static void fbnic_set_rx_mode(struct net_device *netdev,
+ struct netdev_hw_addr_list *uc,
+ struct netdev_hw_addr_list *mc)
{
struct fbnic_net *fbn = netdev_priv(netdev);
struct fbnic_dev *fbd = fbn->fbd;
/* No need to update the hardware if we are not running */
if (netif_running(netdev))
- __fbnic_set_rx_mode(fbd);
+ __fbnic_set_rx_mode(fbd, uc, mc);
}
static int fbnic_set_mac(struct net_device *netdev, void *p)
{
+ struct fbnic_net *fbn = netdev_priv(netdev);
struct sockaddr *addr = p;
if (!is_valid_ether_addr(addr->sa_data))
@@ -257,7 +262,8 @@ static int fbnic_set_mac(struct net_device *netdev, void *p)
eth_hw_addr_set(netdev, addr->sa_data);
- fbnic_set_rx_mode(netdev);
+ if (netif_running(netdev))
+ __fbnic_set_rx_mode(fbn->fbd, &netdev->uc, &netdev->mc);
return 0;
}
@@ -551,7 +557,7 @@ static const struct net_device_ops fbnic_netdev_ops = {
.ndo_features_check = fbnic_features_check,
.ndo_set_mac_address = fbnic_set_mac,
.ndo_change_mtu = fbnic_change_mtu,
- .ndo_set_rx_mode = fbnic_set_rx_mode,
+ .ndo_set_rx_mode_async = fbnic_set_rx_mode,
.ndo_get_stats64 = fbnic_get_stats64,
.ndo_bpf = fbnic_bpf,
.ndo_hwtstamp_get = fbnic_hwtstamp_get,
diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_netdev.h b/drivers/net/ethernet/meta/fbnic/fbnic_netdev.h
index 9129a658f8fa..eded20b0e9e4 100644
--- a/drivers/net/ethernet/meta/fbnic/fbnic_netdev.h
+++ b/drivers/net/ethernet/meta/fbnic/fbnic_netdev.h
@@ -97,7 +97,9 @@ void fbnic_time_init(struct fbnic_net *fbn);
int fbnic_time_start(struct fbnic_net *fbn);
void fbnic_time_stop(struct fbnic_net *fbn);
-void __fbnic_set_rx_mode(struct fbnic_dev *fbd);
+void __fbnic_set_rx_mode(struct fbnic_dev *fbd,
+ struct netdev_hw_addr_list *uc,
+ struct netdev_hw_addr_list *mc);
void fbnic_clear_rx_mode(struct fbnic_dev *fbd);
void fbnic_phylink_get_pauseparam(struct net_device *netdev,
diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_pci.c b/drivers/net/ethernet/meta/fbnic/fbnic_pci.c
index e3aebbe3656d..6b139cf54256 100644
--- a/drivers/net/ethernet/meta/fbnic/fbnic_pci.c
+++ b/drivers/net/ethernet/meta/fbnic/fbnic_pci.c
@@ -135,7 +135,7 @@ void fbnic_up(struct fbnic_net *fbn)
fbnic_rss_reinit_hw(fbn->fbd, fbn);
- __fbnic_set_rx_mode(fbn->fbd);
+ __fbnic_set_rx_mode(fbn->fbd, &fbn->netdev->uc, &fbn->netdev->mc);
/* Enable Tx/Rx processing */
fbnic_napi_enable(fbn);
@@ -180,7 +180,7 @@ static int fbnic_fw_config_after_crash(struct fbnic_dev *fbd)
}
fbnic_rpc_reset_valid_entries(fbd);
- __fbnic_set_rx_mode(fbd);
+ __fbnic_set_rx_mode(fbd, &fbd->netdev->uc, &fbd->netdev->mc);
return 0;
}
diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_rpc.c b/drivers/net/ethernet/meta/fbnic/fbnic_rpc.c
index 42a186db43ea..fe95b6f69646 100644
--- a/drivers/net/ethernet/meta/fbnic/fbnic_rpc.c
+++ b/drivers/net/ethernet/meta/fbnic/fbnic_rpc.c
@@ -244,7 +244,7 @@ void fbnic_bmc_rpc_check(struct fbnic_dev *fbd)
if (fbd->fw_cap.need_bmc_tcam_reinit) {
fbnic_bmc_rpc_init(fbd);
- __fbnic_set_rx_mode(fbd);
+ __fbnic_set_rx_mode(fbd, &fbd->netdev->uc, &fbd->netdev->mc);
fbd->fw_cap.need_bmc_tcam_reinit = false;
}
--
2.53.0
^ permalink raw reply related
* [PATCH net-next v2 04/13] net: move promiscuity handling into dev_rx_mode_work
From: Stanislav Fomichev @ 2026-03-18 15:02 UTC (permalink / raw)
To: netdev
Cc: davem, edumazet, kuba, 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, sdf, mohsin.bashr,
jacob.e.keller, willemb, skhawaja, bestswngs, linux-doc,
linux-kernel, intel-wired-lan, linux-rdma, linux-wireless,
linux-kselftest, leon
In-Reply-To: <20260318150305.123900-1-sdf@fomichev.me>
Move unicast promiscuity tracking into dev_rx_mode_work so it runs
under netdev_ops_lock instead of under the addr_lock spinlock. This
is required because __dev_set_promiscuity calls dev_change_rx_flags
and __dev_notify_flags, both of which may need to sleep.
Change ASSERT_RTNL() to netdev_ops_assert_locked() in
__dev_set_promiscuity, netif_set_allmulti and __dev_change_flags
since these are now called from the work queue under the ops lock.
Signed-off-by: Stanislav Fomichev <sdf@fomichev.me>
---
Documentation/networking/netdevices.rst | 4 ++
net/core/dev.c | 79 +++++++++++++++++--------
2 files changed, 57 insertions(+), 26 deletions(-)
diff --git a/Documentation/networking/netdevices.rst b/Documentation/networking/netdevices.rst
index dc83d78d3b27..5cdaa1a3dcc8 100644
--- a/Documentation/networking/netdevices.rst
+++ b/Documentation/networking/netdevices.rst
@@ -298,6 +298,10 @@ struct net_device synchronization rules
Notes: Sleepable version of ndo_set_rx_mode. Receives snapshots
of the unicast and multicast address lists.
+ndo_change_rx_flags:
+ Synchronization: rtnl_lock() semaphore. In addition, netdev instance
+ lock if the driver implements queue management or shaper API.
+
ndo_setup_tc:
``TC_SETUP_BLOCK`` and ``TC_SETUP_FT`` are running under NFT locks
(i.e. no ``rtnl_lock`` and no device instance lock). The rest of
diff --git a/net/core/dev.c b/net/core/dev.c
index 77fdbe836754..d50d6dc6ac1f 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -9574,7 +9574,7 @@ static int __dev_set_promiscuity(struct net_device *dev, int inc, bool notify)
kuid_t uid;
kgid_t gid;
- ASSERT_RTNL();
+ netdev_ops_assert_locked(dev);
promiscuity = dev->promiscuity + inc;
if (promiscuity == 0) {
@@ -9610,16 +9610,8 @@ static int __dev_set_promiscuity(struct net_device *dev, int inc, bool notify)
dev_change_rx_flags(dev, IFF_PROMISC);
}
- if (notify) {
- /* The ops lock is only required to ensure consistent locking
- * for `NETDEV_CHANGE` notifiers. This function is sometimes
- * called without the lock, even for devices that are ops
- * locked, such as in `dev_uc_sync_multiple` when using
- * bonding or teaming.
- */
- netdev_ops_assert_locked(dev);
+ if (notify)
__dev_notify_flags(dev, old_flags, IFF_PROMISC, 0, NULL);
- }
return 0;
}
@@ -9641,7 +9633,7 @@ int netif_set_allmulti(struct net_device *dev, int inc, bool notify)
unsigned int old_flags = dev->flags, old_gflags = dev->gflags;
unsigned int allmulti, flags;
- ASSERT_RTNL();
+ netdev_ops_assert_locked(dev);
allmulti = dev->allmulti + inc;
if (allmulti == 0) {
@@ -9671,12 +9663,36 @@ int netif_set_allmulti(struct net_device *dev, int inc, bool notify)
return 0;
}
+/**
+ * dev_uc_promisc_update() - evaluate whether uc_promisc should be toggled.
+ * @dev: device
+ *
+ * Must be called under netif_addr_lock_bh.
+ * Return: +1 to enter promisc, -1 to leave, 0 for no change.
+ */
+static int dev_uc_promisc_update(struct net_device *dev)
+{
+ if (dev->priv_flags & IFF_UNICAST_FLT)
+ return 0;
+
+ if (!netdev_uc_empty(dev) && !dev->uc_promisc) {
+ dev->uc_promisc = true;
+ return 1;
+ }
+ if (netdev_uc_empty(dev) && dev->uc_promisc) {
+ dev->uc_promisc = false;
+ return -1;
+ }
+ return 0;
+}
+
static void dev_rx_mode_work(struct work_struct *work)
{
struct net_device *dev = container_of(work, struct net_device,
rx_mode_work);
struct netdev_hw_addr_list uc_snap, mc_snap, uc_ref, mc_ref;
const struct net_device_ops *ops = dev->netdev_ops;
+ int promisc_inc;
int err;
__hw_addr_init(&uc_snap);
@@ -9704,15 +9720,28 @@ static void dev_rx_mode_work(struct work_struct *work)
if (!err)
err = __hw_addr_list_snapshot(&mc_ref, &dev->mc,
dev->addr_len);
- netif_addr_unlock_bh(dev);
if (err) {
__hw_addr_flush(&uc_snap);
__hw_addr_flush(&uc_ref);
__hw_addr_flush(&mc_snap);
+ netif_addr_unlock_bh(dev);
goto out;
}
+ promisc_inc = dev_uc_promisc_update(dev);
+
+ netif_addr_unlock_bh(dev);
+ } else {
+ netif_addr_lock_bh(dev);
+ promisc_inc = dev_uc_promisc_update(dev);
+ netif_addr_unlock_bh(dev);
+ }
+
+ if (promisc_inc)
+ __dev_set_promiscuity(dev, promisc_inc, false);
+
+ if (ops->ndo_set_rx_mode_async) {
ops->ndo_set_rx_mode_async(dev, &uc_snap, &mc_snap);
netif_addr_lock_bh(dev);
@@ -9721,6 +9750,10 @@ static void dev_rx_mode_work(struct work_struct *work)
__hw_addr_list_reconcile(&dev->mc, &mc_snap,
&mc_ref, dev->addr_len);
netif_addr_unlock_bh(dev);
+ } else if (ops->ndo_set_rx_mode) {
+ netif_addr_lock_bh(dev);
+ ops->ndo_set_rx_mode(dev);
+ netif_addr_unlock_bh(dev);
}
out:
@@ -9739,28 +9772,22 @@ static void dev_rx_mode_work(struct work_struct *work)
void __dev_set_rx_mode(struct net_device *dev)
{
const struct net_device_ops *ops = dev->netdev_ops;
+ int promisc_inc;
/* dev_open will call this function so the list will stay sane. */
if (!netif_up_and_present(dev))
return;
- if (ops->ndo_set_rx_mode_async) {
+ if (ops->ndo_set_rx_mode_async || ops->ndo_change_rx_flags) {
queue_work(rx_mode_wq, &dev->rx_mode_work);
return;
}
- if (!(dev->priv_flags & IFF_UNICAST_FLT)) {
- /* Unicast addresses changes may only happen under the rtnl,
- * therefore calling __dev_set_promiscuity here is safe.
- */
- if (!netdev_uc_empty(dev) && !dev->uc_promisc) {
- __dev_set_promiscuity(dev, 1, false);
- dev->uc_promisc = true;
- } else if (netdev_uc_empty(dev) && dev->uc_promisc) {
- __dev_set_promiscuity(dev, -1, false);
- dev->uc_promisc = false;
- }
- }
+ /* Legacy path for non-ops locked HW devices. */
+
+ promisc_inc = dev_uc_promisc_update(dev);
+ if (promisc_inc)
+ __dev_set_promiscuity(dev, promisc_inc, false);
if (ops->ndo_set_rx_mode)
ops->ndo_set_rx_mode(dev);
@@ -9810,7 +9837,7 @@ int __dev_change_flags(struct net_device *dev, unsigned int flags,
unsigned int old_flags = dev->flags;
int ret;
- ASSERT_RTNL();
+ netdev_ops_assert_locked(dev);
/*
* Set the flags on our device.
--
2.53.0
^ permalink raw reply related
* [PATCH net-next v2 03/13] net: introduce ndo_set_rx_mode_async and dev_rx_mode_work
From: Stanislav Fomichev @ 2026-03-18 15:02 UTC (permalink / raw)
To: netdev
Cc: davem, edumazet, kuba, 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, sdf, mohsin.bashr,
jacob.e.keller, willemb, skhawaja, bestswngs, linux-doc,
linux-kernel, intel-wired-lan, linux-rdma, linux-wireless,
linux-kselftest, leon
In-Reply-To: <20260318150305.123900-1-sdf@fomichev.me>
Add ndo_set_rx_mode_async callback that drivers can implement instead
of the legacy ndo_set_rx_mode. The legacy callback runs under the
netif_addr_lock spinlock with BHs disabled, preventing drivers from
sleeping. The async variant runs from a work queue with rtnl_lock and
netdev_lock_ops held, in fully sleepable context.
When __dev_set_rx_mode() sees ndo_set_rx_mode_async, it schedules
dev_rx_mode_work instead of calling the driver inline. The work
function takes two snapshots of each address list (uc/mc) under
the addr_lock, then drops the lock and calls the driver with the
work copies. After the driver returns, it reconciles the snapshots
back to the real lists under the lock.
Signed-off-by: Stanislav Fomichev <sdf@fomichev.me>
---
Documentation/networking/netdevices.rst | 8 +++
include/linux/netdevice.h | 20 ++++++
net/core/dev.c | 94 +++++++++++++++++++++++--
3 files changed, 115 insertions(+), 7 deletions(-)
diff --git a/Documentation/networking/netdevices.rst b/Documentation/networking/netdevices.rst
index 35704d115312..dc83d78d3b27 100644
--- a/Documentation/networking/netdevices.rst
+++ b/Documentation/networking/netdevices.rst
@@ -289,6 +289,14 @@ struct net_device synchronization rules
ndo_set_rx_mode:
Synchronization: netif_addr_lock spinlock.
Context: BHs disabled
+ Notes: Deprecated in favor of sleepable ndo_set_rx_mode_async.
+
+ndo_set_rx_mode_async:
+ Synchronization: rtnl_lock() semaphore. In addition, netdev instance
+ lock if the driver implements queue management or shaper API.
+ Context: process (from a work queue)
+ Notes: Sleepable version of ndo_set_rx_mode. Receives snapshots
+ of the unicast and multicast address lists.
ndo_setup_tc:
``TC_SETUP_BLOCK`` and ``TC_SETUP_FT`` are running under NFT locks
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 469b7cdb3237..7ede1f56bd70 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -1117,6 +1117,16 @@ struct netdev_net_notifier {
* This function is called device changes address list filtering.
* If driver handles unicast address filtering, it should set
* IFF_UNICAST_FLT in its priv_flags.
+ * Cannot sleep, called with netif_addr_lock_bh held.
+ * Deprecated in favor of sleepable ndo_set_rx_mode_async.
+ *
+ * void (*ndo_set_rx_mode_async)(struct net_device *dev,
+ * struct netdev_hw_addr_list *uc,
+ * struct netdev_hw_addr_list *mc);
+ * Sleepable version of ndo_set_rx_mode. Called from a work queue
+ * with rtnl_lock and netdev_lock_ops(dev) held. The uc/mc parameters
+ * are snapshots of the address lists - iterate with
+ * netdev_hw_addr_list_for_each(ha, uc).
*
* int (*ndo_set_mac_address)(struct net_device *dev, void *addr);
* This function is called when the Media Access Control address
@@ -1437,6 +1447,9 @@ struct net_device_ops {
void (*ndo_change_rx_flags)(struct net_device *dev,
int flags);
void (*ndo_set_rx_mode)(struct net_device *dev);
+ void (*ndo_set_rx_mode_async)(struct net_device *dev,
+ struct netdev_hw_addr_list *uc,
+ struct netdev_hw_addr_list *mc);
int (*ndo_set_mac_address)(struct net_device *dev,
void *addr);
int (*ndo_validate_addr)(struct net_device *dev);
@@ -1903,6 +1916,7 @@ enum netdev_reg_state {
* has been enabled due to the need to listen to
* additional unicast addresses in a device that
* does not implement ndo_set_rx_mode()
+ * @rx_mode_work: Work queue entry for ndo_set_rx_mode_async()
* @uc: unicast mac addresses
* @mc: multicast mac addresses
* @dev_addrs: list of device hw addresses
@@ -2293,6 +2307,7 @@ struct net_device {
unsigned int promiscuity;
unsigned int allmulti;
bool uc_promisc;
+ struct work_struct rx_mode_work;
#ifdef CONFIG_LOCKDEP
unsigned char nested_level;
#endif
@@ -4661,6 +4676,11 @@ static inline bool netif_device_present(const struct net_device *dev)
return test_bit(__LINK_STATE_PRESENT, &dev->state);
}
+static inline bool netif_up_and_present(const struct net_device *dev)
+{
+ return (dev->flags & IFF_UP) && netif_device_present(dev);
+}
+
void netif_device_detach(struct net_device *dev);
void netif_device_attach(struct net_device *dev);
diff --git a/net/core/dev.c b/net/core/dev.c
index 200d44883fc1..77fdbe836754 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -2381,6 +2381,8 @@ static void netstamp_clear(struct work_struct *work)
static DECLARE_WORK(netstamp_work, netstamp_clear);
#endif
+static struct workqueue_struct *rx_mode_wq;
+
void net_enable_timestamp(void)
{
#ifdef CONFIG_JUMP_LABEL
@@ -9669,22 +9671,83 @@ int netif_set_allmulti(struct net_device *dev, int inc, bool notify)
return 0;
}
-/*
- * Upload unicast and multicast address lists to device and
- * configure RX filtering. When the device doesn't support unicast
- * filtering it is put in promiscuous mode while unicast addresses
- * are present.
+static void dev_rx_mode_work(struct work_struct *work)
+{
+ struct net_device *dev = container_of(work, struct net_device,
+ rx_mode_work);
+ struct netdev_hw_addr_list uc_snap, mc_snap, uc_ref, mc_ref;
+ const struct net_device_ops *ops = dev->netdev_ops;
+ int err;
+
+ __hw_addr_init(&uc_snap);
+ __hw_addr_init(&mc_snap);
+ __hw_addr_init(&uc_ref);
+ __hw_addr_init(&mc_ref);
+
+ rtnl_lock();
+ netdev_lock_ops(dev);
+
+ if (!netif_up_and_present(dev))
+ goto out;
+
+ if (ops->ndo_set_rx_mode_async) {
+ netif_addr_lock_bh(dev);
+
+ err = __hw_addr_list_snapshot(&uc_snap, &dev->uc,
+ dev->addr_len);
+ if (!err)
+ err = __hw_addr_list_snapshot(&uc_ref, &dev->uc,
+ dev->addr_len);
+ if (!err)
+ err = __hw_addr_list_snapshot(&mc_snap, &dev->mc,
+ dev->addr_len);
+ if (!err)
+ err = __hw_addr_list_snapshot(&mc_ref, &dev->mc,
+ dev->addr_len);
+ netif_addr_unlock_bh(dev);
+
+ if (err) {
+ __hw_addr_flush(&uc_snap);
+ __hw_addr_flush(&uc_ref);
+ __hw_addr_flush(&mc_snap);
+ goto out;
+ }
+
+ ops->ndo_set_rx_mode_async(dev, &uc_snap, &mc_snap);
+
+ netif_addr_lock_bh(dev);
+ __hw_addr_list_reconcile(&dev->uc, &uc_snap,
+ &uc_ref, dev->addr_len);
+ __hw_addr_list_reconcile(&dev->mc, &mc_snap,
+ &mc_ref, dev->addr_len);
+ netif_addr_unlock_bh(dev);
+ }
+
+out:
+ netdev_unlock_ops(dev);
+ rtnl_unlock();
+}
+
+/**
+ * __dev_set_rx_mode() - upload unicast and multicast address lists to device
+ * and configure RX filtering.
+ * @dev: device
+ *
+ * When the device doesn't support unicast filtering it is put in promiscuous
+ * mode while unicast addresses are present.
*/
void __dev_set_rx_mode(struct net_device *dev)
{
const struct net_device_ops *ops = dev->netdev_ops;
/* dev_open will call this function so the list will stay sane. */
- if (!(dev->flags&IFF_UP))
+ if (!netif_up_and_present(dev))
return;
- if (!netif_device_present(dev))
+ if (ops->ndo_set_rx_mode_async) {
+ queue_work(rx_mode_wq, &dev->rx_mode_work);
return;
+ }
if (!(dev->priv_flags & IFF_UNICAST_FLT)) {
/* Unicast addresses changes may only happen under the rtnl,
@@ -11708,6 +11771,16 @@ void netdev_run_todo(void)
__rtnl_unlock();
+ /* Make sure all pending rx_mode work completes before returning.
+ *
+ * rx_mode_wq may be NULL during early boot:
+ * core_initcall(netlink_proto_init) vs subsys_initcall(net_dev_init).
+ *
+ * Check current_work() to avoid flushing from the wq.
+ */
+ if (rx_mode_wq && !current_work())
+ flush_workqueue(rx_mode_wq);
+
/* Wait for rcu callbacks to finish before next phase */
if (!list_empty(&list))
rcu_barrier();
@@ -12099,6 +12172,7 @@ struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,
#endif
mutex_init(&dev->lock);
+ INIT_WORK(&dev->rx_mode_work, dev_rx_mode_work);
dev->priv_flags = IFF_XMIT_DST_RELEASE | IFF_XMIT_DST_RELEASE_PERM;
setup(dev);
@@ -12203,6 +12277,8 @@ void free_netdev(struct net_device *dev)
kfree(rcu_dereference_protected(dev->ingress_queue, 1));
+ cancel_work_sync(&dev->rx_mode_work);
+
/* Flush device addresses */
dev_addr_flush(dev);
@@ -13296,6 +13372,10 @@ static int __init net_dev_init(void)
if (register_pernet_device(&default_device_ops))
goto out;
+ rx_mode_wq = alloc_ordered_workqueue("rx_mode_wq", 0);
+ if (!rx_mode_wq)
+ goto out;
+
open_softirq(NET_TX_SOFTIRQ, net_tx_action);
open_softirq(NET_RX_SOFTIRQ, net_rx_action);
--
2.53.0
^ permalink raw reply related
* [PATCH net-next v2 02/13] wifi: cfg80211: use __rtnl_unlock in nl80211_pre_doit
From: Stanislav Fomichev @ 2026-03-18 15:02 UTC (permalink / raw)
To: netdev
Cc: davem, edumazet, kuba, 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, sdf, mohsin.bashr,
jacob.e.keller, willemb, skhawaja, bestswngs, linux-doc,
linux-kernel, intel-wired-lan, linux-rdma, linux-wireless,
linux-kselftest, leon
In-Reply-To: <20260318150305.123900-1-sdf@fomichev.me>
nl80211_pre_doit acquires rtnl_lock and then wiphy_lock, releasing
rtnl while keeping wiphy_lock held until post_doit. With the
introduction of rx_mode_wq and its flush in netdev_run_todo, calling
rtnl_unlock here creates a circular lock dependency:
Chain exists of:
(wq_completion)rx_mode_wq --> rtnl_mutex --> &rdev->wiphy.mtx
Possible unsafe locking scenario:
CPU0 CPU1
---- ----
lock(&rdev->wiphy.mtx);
lock(rtnl_mutex);
lock(&rdev->wiphy.mtx);
lock((wq_completion)rx_mode_wq);
Switch to __rtnl_unlock to skip netdev_run_todo in nl80211_pre_doit.
This seems safe because we run before the op.
Link: http://lore.kernel.org/netdev/69b5ad67.a00a0220.3b25d1.001a.GAE@google.com
Signed-off-by: Stanislav Fomichev <sdf@fomichev.me>
---
net/wireless/nl80211.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 2225f5d0b124..ce5f25d4c87e 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -18192,7 +18192,7 @@ static int nl80211_pre_doit(const struct genl_split_ops *ops,
__release(&rdev->wiphy.mtx);
}
if (!(internal_flags & NL80211_FLAG_NEED_RTNL))
- rtnl_unlock();
+ __rtnl_unlock();
return 0;
out_unlock:
--
2.53.0
^ permalink raw reply related
* [PATCH net-next v2 01/13] net: add address list snapshot and reconciliation infrastructure
From: Stanislav Fomichev @ 2026-03-18 15:02 UTC (permalink / raw)
To: netdev
Cc: davem, edumazet, kuba, 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, sdf, mohsin.bashr,
jacob.e.keller, willemb, skhawaja, bestswngs, linux-doc,
linux-kernel, intel-wired-lan, linux-rdma, linux-wireless,
linux-kselftest, leon
In-Reply-To: <20260318150305.123900-1-sdf@fomichev.me>
Introduce __hw_addr_list_snapshot() and __hw_addr_list_reconcile()
for use by the upcoming ndo_set_rx_mode_async callback.
The async rx_mode path needs to snapshot the device's unicast and
multicast address lists under the addr_lock, hand those snapshots
to the driver (which may sleep), and then propagate any sync_cnt
changes back to the real lists. Two identical snapshots are taken:
a work copy for the driver to pass to __hw_addr_sync_dev() and a
reference copy to compute deltas against.
__hw_addr_list_reconcile() walks the reference snapshot comparing
each entry against the work snapshot to determine what the driver
synced or unsynced. It then applies those deltas to the real list,
handling concurrent modifications:
- If the real entry was concurrently removed but the driver synced
it to hardware (delta > 0), re-insert a stale entry so the next
work run properly unsyncs it from hardware.
- If the entry still exists, apply the delta normally. An entry
whose refcount drops to zero is removed.
Signed-off-by: Stanislav Fomichev <sdf@fomichev.me>
---
include/linux/netdevice.h | 6 +
net/core/dev.h | 1 +
net/core/dev_addr_lists.c | 110 ++++++++++-
net/core/dev_addr_lists_test.c | 321 ++++++++++++++++++++++++++++++++-
4 files changed, 435 insertions(+), 3 deletions(-)
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index ae269a2e7f4d..469b7cdb3237 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -4985,6 +4985,12 @@ void __hw_addr_unsync_dev(struct netdev_hw_addr_list *list,
int (*unsync)(struct net_device *,
const unsigned char *));
void __hw_addr_init(struct netdev_hw_addr_list *list);
+int __hw_addr_list_snapshot(struct netdev_hw_addr_list *snap,
+ const struct netdev_hw_addr_list *list,
+ int addr_len);
+void __hw_addr_list_reconcile(struct netdev_hw_addr_list *real_list,
+ struct netdev_hw_addr_list *work,
+ struct netdev_hw_addr_list *ref, int addr_len);
/* Functions used for device addresses handling */
void dev_addr_mod(struct net_device *dev, unsigned int offset,
diff --git a/net/core/dev.h b/net/core/dev.h
index 781619e76b3e..acc925b7b337 100644
--- a/net/core/dev.h
+++ b/net/core/dev.h
@@ -69,6 +69,7 @@ void linkwatch_run_queue(void);
void dev_addr_flush(struct net_device *dev);
int dev_addr_init(struct net_device *dev);
void dev_addr_check(struct net_device *dev);
+void __hw_addr_flush(struct netdev_hw_addr_list *list);
#if IS_ENABLED(CONFIG_NET_SHAPER)
void net_shaper_flush_netdev(struct net_device *dev);
diff --git a/net/core/dev_addr_lists.c b/net/core/dev_addr_lists.c
index 76c91f224886..754f5ea4c3db 100644
--- a/net/core/dev_addr_lists.c
+++ b/net/core/dev_addr_lists.c
@@ -481,7 +481,7 @@ void __hw_addr_unsync_dev(struct netdev_hw_addr_list *list,
}
EXPORT_SYMBOL(__hw_addr_unsync_dev);
-static void __hw_addr_flush(struct netdev_hw_addr_list *list)
+void __hw_addr_flush(struct netdev_hw_addr_list *list)
{
struct netdev_hw_addr *ha, *tmp;
@@ -501,6 +501,114 @@ void __hw_addr_init(struct netdev_hw_addr_list *list)
}
EXPORT_SYMBOL(__hw_addr_init);
+/**
+ * __hw_addr_list_snapshot - create a snapshot copy of an address list
+ * @snap: destination snapshot list (needs to be __hw_addr_init-initialized)
+ * @list: source address list to snapshot
+ * @addr_len: length of addresses
+ *
+ * Creates a copy of @list with individually allocated entries suitable
+ * for use with __hw_addr_sync_dev() and other list manipulation helpers.
+ * Each entry is allocated with GFP_ATOMIC; must be called under a spinlock.
+ *
+ * Return: 0 on success, -errno on failure.
+ */
+int __hw_addr_list_snapshot(struct netdev_hw_addr_list *snap,
+ const struct netdev_hw_addr_list *list,
+ int addr_len)
+{
+ struct netdev_hw_addr *ha, *entry;
+
+ list_for_each_entry(ha, &list->list, list) {
+ entry = __hw_addr_create(ha->addr, addr_len, ha->type,
+ false, false);
+ if (!entry) {
+ __hw_addr_flush(snap);
+ return -ENOMEM;
+ }
+ entry->sync_cnt = ha->sync_cnt;
+ entry->refcount = ha->refcount;
+
+ list_add_tail(&entry->list, &snap->list);
+ __hw_addr_insert(snap, entry, addr_len);
+ snap->count++;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(__hw_addr_list_snapshot);
+
+/**
+ * __hw_addr_list_reconcile - sync snapshot changes back and free snapshots
+ * @real_list: the real address list to update
+ * @work: the working snapshot (modified by driver via __hw_addr_sync_dev)
+ * @ref: the reference snapshot (untouched copy of original state)
+ * @addr_len: length of addresses
+ *
+ * Walks the reference snapshot and compares each entry against the work
+ * snapshot to compute sync_cnt deltas. Applies those deltas to @real_list.
+ * Frees both snapshots when done.
+ * Caller must hold netif_addr_lock_bh.
+ */
+void __hw_addr_list_reconcile(struct netdev_hw_addr_list *real_list,
+ struct netdev_hw_addr_list *work,
+ struct netdev_hw_addr_list *ref, int addr_len)
+{
+ struct netdev_hw_addr *ref_ha, *work_ha, *real_ha;
+ int delta;
+
+ list_for_each_entry(ref_ha, &ref->list, list) {
+ work_ha = __hw_addr_lookup(work, ref_ha->addr, addr_len,
+ ref_ha->type);
+ if (work_ha)
+ delta = work_ha->sync_cnt - ref_ha->sync_cnt;
+ else
+ delta = -1;
+
+ if (delta == 0)
+ continue;
+
+ real_ha = __hw_addr_lookup(real_list, ref_ha->addr, addr_len,
+ ref_ha->type);
+ if (!real_ha) {
+ /* The real entry was concurrently removed. If the
+ * driver synced this addr to hardware (delta > 0),
+ * re-insert it as a stale entry so the next work
+ * run unsyncs it from hardware.
+ */
+ if (delta > 0) {
+ real_ha = __hw_addr_create(ref_ha->addr,
+ addr_len,
+ ref_ha->type, false,
+ false);
+ if (real_ha) {
+ real_ha->sync_cnt = 1;
+ real_ha->refcount = 1;
+ list_add_tail_rcu(&real_ha->list,
+ &real_list->list);
+ __hw_addr_insert(real_list, real_ha,
+ addr_len);
+ real_list->count++;
+ }
+ }
+ continue;
+ }
+
+ real_ha->sync_cnt += delta;
+ real_ha->refcount += delta;
+ if (!real_ha->refcount) {
+ rb_erase(&real_ha->node, &real_list->tree);
+ list_del_rcu(&real_ha->list);
+ kfree_rcu(real_ha, rcu_head);
+ real_list->count--;
+ }
+ }
+
+ __hw_addr_flush(work);
+ __hw_addr_flush(ref);
+}
+EXPORT_SYMBOL(__hw_addr_list_reconcile);
+
/*
* Device addresses handling functions
*/
diff --git a/net/core/dev_addr_lists_test.c b/net/core/dev_addr_lists_test.c
index 8e1dba825e94..c62ce06fc4d5 100644
--- a/net/core/dev_addr_lists_test.c
+++ b/net/core/dev_addr_lists_test.c
@@ -8,16 +8,24 @@
static const struct net_device_ops dummy_netdev_ops = {
};
+#define ADDR_A 1
+#define ADDR_B 2
+#define ADDR_C 3
+
struct dev_addr_test_priv {
u32 addr_seen;
+ u32 addr_synced;
+ u32 addr_unsynced;
};
static int dev_addr_test_sync(struct net_device *netdev, const unsigned char *a)
{
struct dev_addr_test_priv *datp = netdev_priv(netdev);
- if (a[0] < 31 && !memchr_inv(a, a[0], ETH_ALEN))
+ if (a[0] < 31 && !memchr_inv(a, a[0], ETH_ALEN)) {
datp->addr_seen |= 1 << a[0];
+ datp->addr_synced |= 1 << a[0];
+ }
return 0;
}
@@ -26,11 +34,22 @@ static int dev_addr_test_unsync(struct net_device *netdev,
{
struct dev_addr_test_priv *datp = netdev_priv(netdev);
- if (a[0] < 31 && !memchr_inv(a, a[0], ETH_ALEN))
+ if (a[0] < 31 && !memchr_inv(a, a[0], ETH_ALEN)) {
datp->addr_seen &= ~(1 << a[0]);
+ datp->addr_unsynced |= 1 << a[0];
+ }
return 0;
}
+static void dev_addr_test_reset(struct net_device *netdev)
+{
+ struct dev_addr_test_priv *datp = netdev_priv(netdev);
+
+ datp->addr_seen = 0;
+ datp->addr_synced = 0;
+ datp->addr_unsynced = 0;
+}
+
static int dev_addr_test_init(struct kunit *test)
{
struct dev_addr_test_priv *datp;
@@ -225,6 +244,300 @@ static void dev_addr_test_add_excl(struct kunit *test)
rtnl_unlock();
}
+/* Snapshot test: basic sync with no concurrent modifications.
+ * Add one address, snapshot, driver syncs it, reconcile propagates
+ * sync_cnt delta back to real list.
+ */
+static void dev_addr_test_snapshot_sync(struct kunit *test)
+{
+ struct net_device *netdev = test->priv;
+ struct netdev_hw_addr_list snap, ref;
+ struct dev_addr_test_priv *datp;
+ struct netdev_hw_addr *ha;
+ u8 addr[ETH_ALEN];
+
+ datp = netdev_priv(netdev);
+
+ rtnl_lock();
+
+ memset(addr, ADDR_A, sizeof(addr));
+ KUNIT_EXPECT_EQ(test, 0, dev_uc_add(netdev, addr));
+
+ /* Snapshot: ADDR_A has sync_cnt=0, refcount=1 (new) */
+ netif_addr_lock_bh(netdev);
+ __hw_addr_init(&snap);
+ __hw_addr_init(&ref);
+ KUNIT_ASSERT_EQ(test, 0,
+ __hw_addr_list_snapshot(&snap, &netdev->uc, ETH_ALEN));
+ KUNIT_ASSERT_EQ(test, 0,
+ __hw_addr_list_snapshot(&ref, &netdev->uc, ETH_ALEN));
+ netif_addr_unlock_bh(netdev);
+
+ /* Driver syncs ADDR_A to hardware */
+ dev_addr_test_reset(netdev);
+ __hw_addr_sync_dev(&snap, netdev, dev_addr_test_sync,
+ dev_addr_test_unsync);
+ KUNIT_EXPECT_EQ(test, 1 << ADDR_A, datp->addr_synced);
+ KUNIT_EXPECT_EQ(test, 0, datp->addr_unsynced);
+
+ /* Reconcile: delta=+1 applied to real entry */
+ netif_addr_lock_bh(netdev);
+ __hw_addr_list_reconcile(&netdev->uc, &snap, &ref, ETH_ALEN);
+ netif_addr_unlock_bh(netdev);
+
+ /* Real entry should now reflect the sync: sync_cnt=1, refcount=2 */
+ KUNIT_EXPECT_EQ(test, 1, netdev->uc.count);
+ ha = list_first_entry(&netdev->uc.list, struct netdev_hw_addr, list);
+ KUNIT_EXPECT_MEMEQ(test, ha->addr, addr, ETH_ALEN);
+ KUNIT_EXPECT_EQ(test, 1, ha->sync_cnt);
+ KUNIT_EXPECT_EQ(test, 2, ha->refcount);
+
+ /* Second work run: already synced, nothing to do */
+ dev_addr_test_reset(netdev);
+ __hw_addr_sync_dev(&netdev->uc, netdev, dev_addr_test_sync,
+ dev_addr_test_unsync);
+ KUNIT_EXPECT_EQ(test, 0, datp->addr_synced);
+ KUNIT_EXPECT_EQ(test, 0, datp->addr_unsynced);
+ KUNIT_EXPECT_EQ(test, 1, netdev->uc.count);
+
+ rtnl_unlock();
+}
+
+/* Snapshot test: ADDR_A synced to hardware, then concurrently removed
+ * from the real list before reconcile runs. Reconcile re-inserts ADDR_A as
+ * a stale entry so the next work run unsyncs it from hardware.
+ */
+static void dev_addr_test_snapshot_remove_during_sync(struct kunit *test)
+{
+ struct net_device *netdev = test->priv;
+ struct netdev_hw_addr_list snap, ref;
+ struct dev_addr_test_priv *datp;
+ struct netdev_hw_addr *ha;
+ u8 addr[ETH_ALEN];
+
+ datp = netdev_priv(netdev);
+
+ rtnl_lock();
+
+ memset(addr, ADDR_A, sizeof(addr));
+ KUNIT_EXPECT_EQ(test, 0, dev_uc_add(netdev, addr));
+
+ /* Snapshot: ADDR_A is new (sync_cnt=0, refcount=1) */
+ netif_addr_lock_bh(netdev);
+ __hw_addr_init(&snap);
+ __hw_addr_init(&ref);
+ KUNIT_ASSERT_EQ(test, 0,
+ __hw_addr_list_snapshot(&snap, &netdev->uc, ETH_ALEN));
+ KUNIT_ASSERT_EQ(test, 0,
+ __hw_addr_list_snapshot(&ref, &netdev->uc, ETH_ALEN));
+ netif_addr_unlock_bh(netdev);
+
+ /* Driver syncs ADDR_A to hardware */
+ dev_addr_test_reset(netdev);
+ __hw_addr_sync_dev(&snap, netdev, dev_addr_test_sync,
+ dev_addr_test_unsync);
+ KUNIT_EXPECT_EQ(test, 1 << ADDR_A, datp->addr_synced);
+ KUNIT_EXPECT_EQ(test, 0, datp->addr_unsynced);
+
+ /* Concurrent removal: user deletes ADDR_A while driver was working */
+ memset(addr, ADDR_A, sizeof(addr));
+ KUNIT_EXPECT_EQ(test, 0, dev_uc_del(netdev, addr));
+ KUNIT_EXPECT_EQ(test, 0, netdev->uc.count);
+
+ /* Reconcile: ADDR_A gone from real list but driver synced it,
+ * so it gets re-inserted as stale (sync_cnt=1, refcount=1).
+ */
+ netif_addr_lock_bh(netdev);
+ __hw_addr_list_reconcile(&netdev->uc, &snap, &ref, ETH_ALEN);
+ netif_addr_unlock_bh(netdev);
+
+ KUNIT_EXPECT_EQ(test, 1, netdev->uc.count);
+ ha = list_first_entry(&netdev->uc.list, struct netdev_hw_addr, list);
+ KUNIT_EXPECT_MEMEQ(test, ha->addr, addr, ETH_ALEN);
+ KUNIT_EXPECT_EQ(test, 1, ha->sync_cnt);
+ KUNIT_EXPECT_EQ(test, 1, ha->refcount);
+
+ /* Second work run: stale entry gets unsynced from HW and removed */
+ dev_addr_test_reset(netdev);
+ __hw_addr_sync_dev(&netdev->uc, netdev, dev_addr_test_sync,
+ dev_addr_test_unsync);
+ KUNIT_EXPECT_EQ(test, 0, datp->addr_synced);
+ KUNIT_EXPECT_EQ(test, 1 << ADDR_A, datp->addr_unsynced);
+ KUNIT_EXPECT_EQ(test, 0, netdev->uc.count);
+
+ rtnl_unlock();
+}
+
+/* Snapshot test: ADDR_A was stale (unsynced from hardware by driver),
+ * but concurrently re-added by the user. The re-add bumps refcount of
+ * the existing stale entry. Reconcile applies delta=-1, leaving ADDR_A
+ * as a fresh entry (sync_cnt=0, refcount=1) for the next work run.
+ */
+static void dev_addr_test_snapshot_readd_during_unsync(struct kunit *test)
+{
+ struct net_device *netdev = test->priv;
+ struct netdev_hw_addr_list snap, ref;
+ struct dev_addr_test_priv *datp;
+ struct netdev_hw_addr *ha;
+ u8 addr[ETH_ALEN];
+
+ datp = netdev_priv(netdev);
+
+ rtnl_lock();
+
+ memset(addr, ADDR_A, sizeof(addr));
+ KUNIT_EXPECT_EQ(test, 0, dev_uc_add(netdev, addr));
+
+ /* Sync ADDR_A to hardware: sync_cnt=1, refcount=2 */
+ dev_addr_test_reset(netdev);
+ __hw_addr_sync_dev(&netdev->uc, netdev, dev_addr_test_sync,
+ dev_addr_test_unsync);
+ KUNIT_EXPECT_EQ(test, 1 << ADDR_A, datp->addr_synced);
+ KUNIT_EXPECT_EQ(test, 0, datp->addr_unsynced);
+
+ /* User removes ADDR_A: refcount=1, sync_cnt=1 -> stale */
+ KUNIT_EXPECT_EQ(test, 0, dev_uc_del(netdev, addr));
+
+ /* Snapshot: ADDR_A is stale (sync_cnt=1, refcount=1) */
+ netif_addr_lock_bh(netdev);
+ __hw_addr_init(&snap);
+ __hw_addr_init(&ref);
+ KUNIT_ASSERT_EQ(test, 0,
+ __hw_addr_list_snapshot(&snap, &netdev->uc, ETH_ALEN));
+ KUNIT_ASSERT_EQ(test, 0,
+ __hw_addr_list_snapshot(&ref, &netdev->uc, ETH_ALEN));
+ netif_addr_unlock_bh(netdev);
+
+ /* Driver unsyncs stale ADDR_A from hardware */
+ dev_addr_test_reset(netdev);
+ __hw_addr_sync_dev(&snap, netdev, dev_addr_test_sync,
+ dev_addr_test_unsync);
+ KUNIT_EXPECT_EQ(test, 0, datp->addr_synced);
+ KUNIT_EXPECT_EQ(test, 1 << ADDR_A, datp->addr_unsynced);
+
+ /* Concurrent: user re-adds ADDR_A. dev_uc_add finds the existing
+ * stale entry and bumps refcount from 1 -> 2. sync_cnt stays 1.
+ */
+ KUNIT_EXPECT_EQ(test, 0, dev_uc_add(netdev, addr));
+ KUNIT_EXPECT_EQ(test, 1, netdev->uc.count);
+
+ /* Reconcile: ref sync_cnt=1 matches real sync_cnt=1, delta=-1
+ * applied. Result: sync_cnt=0, refcount=1 (fresh).
+ */
+ netif_addr_lock_bh(netdev);
+ __hw_addr_list_reconcile(&netdev->uc, &snap, &ref, ETH_ALEN);
+ netif_addr_unlock_bh(netdev);
+
+ /* Entry survives as fresh: needs re-sync to HW */
+ KUNIT_EXPECT_EQ(test, 1, netdev->uc.count);
+ ha = list_first_entry(&netdev->uc.list, struct netdev_hw_addr, list);
+ KUNIT_EXPECT_MEMEQ(test, ha->addr, addr, ETH_ALEN);
+ KUNIT_EXPECT_EQ(test, 0, ha->sync_cnt);
+ KUNIT_EXPECT_EQ(test, 1, ha->refcount);
+
+ /* Second work run: fresh entry gets synced to HW */
+ dev_addr_test_reset(netdev);
+ __hw_addr_sync_dev(&netdev->uc, netdev, dev_addr_test_sync,
+ dev_addr_test_unsync);
+ KUNIT_EXPECT_EQ(test, 1 << ADDR_A, datp->addr_synced);
+ KUNIT_EXPECT_EQ(test, 0, datp->addr_unsynced);
+
+ rtnl_unlock();
+}
+
+/* Snapshot test: ADDR_A is new (synced by driver), and independent ADDR_B
+ * is concurrently removed from the real list. A's sync delta propagates
+ * normally; B's absence doesn't interfere.
+ */
+static void dev_addr_test_snapshot_add_and_remove(struct kunit *test)
+{
+ struct net_device *netdev = test->priv;
+ struct netdev_hw_addr_list snap, ref;
+ struct dev_addr_test_priv *datp;
+ struct netdev_hw_addr *ha;
+ u8 addr[ETH_ALEN];
+
+ datp = netdev_priv(netdev);
+
+ rtnl_lock();
+
+ /* Add ADDR_A and ADDR_B (will be synced then removed) */
+ memset(addr, ADDR_A, sizeof(addr));
+ KUNIT_EXPECT_EQ(test, 0, dev_uc_add(netdev, addr));
+ memset(addr, ADDR_B, sizeof(addr));
+ KUNIT_EXPECT_EQ(test, 0, dev_uc_add(netdev, addr));
+
+ /* Sync both to hardware: sync_cnt=1, refcount=2 */
+ __hw_addr_sync_dev(&netdev->uc, netdev, dev_addr_test_sync,
+ dev_addr_test_unsync);
+
+ /* Add ADDR_C (new, will be synced by snapshot) */
+ memset(addr, ADDR_C, sizeof(addr));
+ KUNIT_EXPECT_EQ(test, 0, dev_uc_add(netdev, addr));
+
+ /* Snapshot: A,B synced (sync_cnt=1,refcount=2); C new (0,1) */
+ netif_addr_lock_bh(netdev);
+ __hw_addr_init(&snap);
+ __hw_addr_init(&ref);
+ KUNIT_ASSERT_EQ(test, 0,
+ __hw_addr_list_snapshot(&snap, &netdev->uc, ETH_ALEN));
+ KUNIT_ASSERT_EQ(test, 0,
+ __hw_addr_list_snapshot(&ref, &netdev->uc, ETH_ALEN));
+ netif_addr_unlock_bh(netdev);
+
+ /* Driver syncs snapshot: ADDR_C is new -> synced; A,B already synced */
+ dev_addr_test_reset(netdev);
+ __hw_addr_sync_dev(&snap, netdev, dev_addr_test_sync,
+ dev_addr_test_unsync);
+ KUNIT_EXPECT_EQ(test, 1 << ADDR_C, datp->addr_synced);
+ KUNIT_EXPECT_EQ(test, 0, datp->addr_unsynced);
+
+ /* Concurrent: user removes addr B while driver was working */
+ memset(addr, ADDR_B, sizeof(addr));
+ KUNIT_EXPECT_EQ(test, 0, dev_uc_del(netdev, addr));
+
+ /* Reconcile: ADDR_C's delta=+1 applied to real list.
+ * ADDR_B's delta=0 (unchanged in snapshot),
+ * so nothing to apply to ADDR_B.
+ */
+ netif_addr_lock_bh(netdev);
+ __hw_addr_list_reconcile(&netdev->uc, &snap, &ref, ETH_ALEN);
+ netif_addr_unlock_bh(netdev);
+
+ /* ADDR_A: unchanged (sync_cnt=1, refcount=2)
+ * ADDR_B: refcount went from 2->1 via dev_uc_del (still present, stale)
+ * ADDR_C: sync propagated (sync_cnt=1, refcount=2)
+ */
+ KUNIT_EXPECT_EQ(test, 3, netdev->uc.count);
+ netdev_hw_addr_list_for_each(ha, &netdev->uc) {
+ u8 id = ha->addr[0];
+
+ if (!memchr_inv(ha->addr, id, ETH_ALEN)) {
+ if (id == ADDR_A) {
+ KUNIT_EXPECT_EQ(test, 1, ha->sync_cnt);
+ KUNIT_EXPECT_EQ(test, 2, ha->refcount);
+ } else if (id == ADDR_B) {
+ /* B: still present but now stale */
+ KUNIT_EXPECT_EQ(test, 1, ha->sync_cnt);
+ KUNIT_EXPECT_EQ(test, 1, ha->refcount);
+ } else if (id == ADDR_C) {
+ KUNIT_EXPECT_EQ(test, 1, ha->sync_cnt);
+ KUNIT_EXPECT_EQ(test, 2, ha->refcount);
+ }
+ }
+ }
+
+ /* Second work run: ADDR_B is stale, gets unsynced and removed */
+ dev_addr_test_reset(netdev);
+ __hw_addr_sync_dev(&netdev->uc, netdev, dev_addr_test_sync,
+ dev_addr_test_unsync);
+ KUNIT_EXPECT_EQ(test, 0, datp->addr_synced);
+ KUNIT_EXPECT_EQ(test, 1 << ADDR_B, datp->addr_unsynced);
+ KUNIT_EXPECT_EQ(test, 2, netdev->uc.count);
+
+ rtnl_unlock();
+}
+
static struct kunit_case dev_addr_test_cases[] = {
KUNIT_CASE(dev_addr_test_basic),
KUNIT_CASE(dev_addr_test_sync_one),
@@ -232,6 +545,10 @@ static struct kunit_case dev_addr_test_cases[] = {
KUNIT_CASE(dev_addr_test_del_main),
KUNIT_CASE(dev_addr_test_add_set),
KUNIT_CASE(dev_addr_test_add_excl),
+ KUNIT_CASE(dev_addr_test_snapshot_sync),
+ KUNIT_CASE(dev_addr_test_snapshot_remove_during_sync),
+ KUNIT_CASE(dev_addr_test_snapshot_readd_during_unsync),
+ KUNIT_CASE(dev_addr_test_snapshot_add_and_remove),
{}
};
--
2.53.0
^ permalink raw reply related
* [PATCH net-next v2 00/13] net: sleepable ndo_set_rx_mode
From: Stanislav Fomichev @ 2026-03-18 15:02 UTC (permalink / raw)
To: netdev
Cc: davem, edumazet, kuba, 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, sdf, mohsin.bashr,
jacob.e.keller, willemb, skhawaja, bestswngs, linux-doc,
linux-kernel, intel-wired-lan, linux-rdma, linux-wireless,
linux-kselftest, leon
This series adds a new ndo_set_rx_mode_async callback that enables
drivers to handle address list updates in a sleepable context. The
current ndo_set_rx_mode is called under the netif_addr_lock spinlock
with BHs disabled, which prevents drivers from sleeping. This is
problematic for ops-locked drivers that need to sleep.
The approach:
1. Add snapshot/reconcile infrastructure for address lists
2. Introduce dev_rx_mode_work that takes snapshots under the lock,
drops the lock, calls the driver, then reconciles changes back
3. Move promiscuity handling into the scheduled work as well
4. Convert existing ops-locked drivers to ndo_set_rx_mode_async
5. Add a warning for ops-locked drivers still using ndo_set_rx_mode
6. Add a selftest exercising the team+bridge+macvlan topology that
triggers the addr_lock -> ops_lock ordering issue
v2:
- wifi: cfg80211: use __rtnl_unlock in nl80211_pre_doit (syzbot)
- simplify mlx5e_sync_netdev_addr for !uc (Cosmin)
- switch to snapshot in bnxt_cfg_rx_mode (Michael)
- add team to net/config (Jakub)
Stanislav Fomichev (13):
net: add address list snapshot and reconciliation infrastructure
wifi: cfg80211: use __rtnl_unlock in nl80211_pre_doit
net: introduce ndo_set_rx_mode_async and dev_rx_mode_work
net: move promiscuity handling into dev_rx_mode_work
fbnic: convert to ndo_set_rx_mode_async
mlx5: convert to ndo_set_rx_mode_async
bnxt: convert to ndo_set_rx_mode_async
bnxt: use snapshot in bnxt_cfg_rx_mode
iavf: convert to ndo_set_rx_mode_async
netdevsim: convert to ndo_set_rx_mode_async
dummy: convert to ndo_set_rx_mode_async
net: warn ops-locked drivers still using ndo_set_rx_mode
selftests: net: add team_bridge_macvlan rx_mode test
Documentation/networking/netdevices.rst | 12 +
drivers/net/dummy.c | 6 +-
drivers/net/ethernet/broadcom/bnxt/bnxt.c | 53 +--
drivers/net/ethernet/intel/iavf/iavf_main.c | 14 +-
.../net/ethernet/mellanox/mlx5/core/en/fs.h | 5 +-
.../net/ethernet/mellanox/mlx5/core/en_fs.c | 30 +-
.../net/ethernet/mellanox/mlx5/core/en_main.c | 16 +-
.../net/ethernet/meta/fbnic/fbnic_netdev.c | 20 +-
.../net/ethernet/meta/fbnic/fbnic_netdev.h | 4 +-
drivers/net/ethernet/meta/fbnic/fbnic_pci.c | 4 +-
drivers/net/ethernet/meta/fbnic/fbnic_rpc.c | 2 +-
drivers/net/netdevsim/netdev.c | 8 +-
include/linux/netdevice.h | 26 ++
net/core/dev.c | 175 ++++++++--
net/core/dev.h | 1 +
net/core/dev_addr_lists.c | 110 +++++-
net/core/dev_addr_lists_test.c | 321 +++++++++++++++++-
net/wireless/nl80211.c | 2 +-
tools/testing/selftests/net/config | 1 +
tools/testing/selftests/net/rtnetlink.sh | 44 +++
20 files changed, 761 insertions(+), 93 deletions(-)
--
2.53.0
^ permalink raw reply
* Re: (subset) [PATCH v1 0/8] Group QMI service IDs into the QMI header
From: Bjorn Andersson @ 2026-03-18 13:50 UTC (permalink / raw)
To: konradybcio, Daniel Lezcano
Cc: linux-kernel, Alex Elder, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Jeff Johnson,
Mathieu Poirier, Srinivas Kandagatla, Jaroslav Kysela,
Takashi Iwai, Kees Cook, Greg Kroah-Hartman, Arnd Bergmann,
Mark Brown, Wesley Cheng, netdev, linux-wireless, ath10k, ath11k,
ath12k, linux-arm-msm, linux-remoteproc, linux-sound
In-Reply-To: <20260309230346.3584252-1-daniel.lezcano@oss.qualcomm.com>
On Tue, 10 Mar 2026 00:03:29 +0100, Daniel Lezcano wrote:
> The different subsystems implementing the QMI service protocol are
> using their own definition of the service id. It is not a problem but
> it results on having those duplicated with different names but the
> same value and without consistency in their name.
>
> It makes more sense to unify their names and move the definitions in
> the QMI header file providing a consistent way to represent the
> supported protocols. Consequently the different drivers will use them
> instead of their own definition of the service id.
>
> [...]
Applied, thanks!
[8/8] samples: qmi: Use the unified QMI service ID instead of defining it locally
commit: 8baf6b3b7695849581a91bdaf66af2be68ef32ed
Best regards,
--
Bjorn Andersson <andersson@kernel.org>
^ permalink raw reply
* Re: [PATCH v4] wifi: iwlwifi: pcie: optimize MSI-X interrupt affinity
From: kernel test robot @ 2026-03-18 13:04 UTC (permalink / raw)
To: Adrián García Casado, Miri Korenblit
Cc: oe-kbuild-all, linux-wireless, linux-kernel, Miguel Ojeda,
Adrián García Casado
In-Reply-To: <20260317193252.13763-1-adriangarciacasado42@gmail.com>
Hi Adrián,
kernel test robot noticed the following build warnings:
[auto build test WARNING on wireless-next/main]
[also build test WARNING on wireless/main rust/rust-next linus/master v7.0-rc4 next-20260317]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Adri-n-Garc-a-Casado/wifi-iwlwifi-pcie-optimize-MSI-X-interrupt-affinity/20260318-081834
base: https://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless-next.git main
patch link: https://lore.kernel.org/r/20260317193252.13763-1-adriangarciacasado42%40gmail.com
patch subject: [PATCH v4] wifi: iwlwifi: pcie: optimize MSI-X interrupt affinity
config: x86_64-rhel-9.4-kunit (https://download.01.org/0day-ci/archive/20260318/202603182147.ECKLrJRf-lkp@intel.com/config)
compiler: gcc-14 (Debian 14.2.0-19) 14.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260318/202603182147.ECKLrJRf-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202603182147.ECKLrJRf-lkp@intel.com/
All warnings (new ones prefixed by >>):
drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans.c: In function 'iwl_pcie_irq_set_affinity':
>> drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans.c:1675:37: warning: unused variable 'offset' [-Wunused-variable]
1675 | int iter_rx_q, i, ret, cpu, offset, last_cpu;
| ^~~~~~
vim +/offset +1675 drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans.c
1670
1671 static void iwl_pcie_irq_set_affinity(struct iwl_trans *trans,
1672 struct iwl_trans_info *info)
1673 {
1674 #if defined(CONFIG_SMP)
> 1675 int iter_rx_q, i, ret, cpu, offset, last_cpu;
1676 struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
1677
1678 i = trans_pcie->shared_vec_mask & IWL_SHARED_IRQ_FIRST_RSS ? 0 : 1;
1679 iter_rx_q = info->num_rxqs - 1 + i;
1680 last_cpu = -1;
1681 for (; i < iter_rx_q ; i++) {
1682 /*
1683 * Balanced distribution: skip CPU0 for high-rate RSS queues
1684 * to avoid contention with system housekeeping.
1685 */
1686 cpu = cpumask_next(last_cpu, cpu_online_mask);
1687 if (cpu >= nr_cpu_ids)
1688 cpu = cpumask_first(cpu_online_mask);
1689
1690 if (cpu == 0 && num_online_cpus() > 1) {
1691 cpu = cpumask_next(0, cpu_online_mask);
1692 if (cpu >= nr_cpu_ids)
1693 cpu = cpumask_first(cpu_online_mask);
1694 }
1695 last_cpu = cpu;
1696
1697 cpumask_set_cpu(cpu, &trans_pcie->affinity_mask[i]);
1698 ret = irq_set_affinity_hint(trans_pcie->msix_entries[i].vector,
1699 &trans_pcie->affinity_mask[i]);
1700 if (ret)
1701 IWL_ERR(trans_pcie->trans,
1702 "Failed to set affinity mask for IRQ %d\n",
1703 trans_pcie->msix_entries[i].vector);
1704 }
1705 #endif
1706 }
1707
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* [PATCH v5 wireless-next 12/12] wifi: cfg80211: allow protected action frame TX for NAN
From: Miri Korenblit @ 2026-03-18 12:39 UTC (permalink / raw)
To: linux-wireless; +Cc: Avraham Stern, Johannes Berg
In-Reply-To: <20260318123926.206536-1-miriam.rachel.korenblit@intel.com>
From: Avraham Stern <avraham.stern@intel.com>
Allow transmitting protected dual of public action frames on
NAN device and NAN data interfaces, since NAN action frames
may be protected and can be sent on both.
Signed-off-by: Avraham Stern <avraham.stern@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
net/wireless/mlme.c | 9 +++++++--
net/wireless/nl80211.c | 2 ++
2 files changed, 9 insertions(+), 2 deletions(-)
diff --git a/net/wireless/mlme.c b/net/wireless/mlme.c
index e817ee297df0..bd72317c4964 100644
--- a/net/wireless/mlme.c
+++ b/net/wireless/mlme.c
@@ -4,7 +4,7 @@
*
* Copyright (c) 2009, Jouni Malinen <j@w1.fi>
* Copyright (c) 2015 Intel Deutschland GmbH
- * Copyright (C) 2019-2020, 2022-2025 Intel Corporation
+ * Copyright (C) 2019-2020, 2022-2026 Intel Corporation
*/
#include <linux/kernel.h>
@@ -933,12 +933,17 @@ int cfg80211_mlme_mgmt_tx(struct cfg80211_registered_device *rdev,
* cfg80211 doesn't track the stations
*/
break;
+ case NL80211_IFTYPE_NAN:
+ case NL80211_IFTYPE_NAN_DATA:
+ if (mgmt->u.action.category !=
+ WLAN_CATEGORY_PROTECTED_DUAL_OF_ACTION)
+ err = -EOPNOTSUPP;
+ break;
case NL80211_IFTYPE_P2P_DEVICE:
/*
* fall through, P2P device only supports
* public action frames
*/
- case NL80211_IFTYPE_NAN:
default:
err = -EOPNOTSUPP;
break;
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 0c4d6e8b45b2..9e7f02d1c7ef 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -14207,6 +14207,7 @@ static int nl80211_register_mgmt(struct sk_buff *skb, struct genl_info *info)
case NL80211_IFTYPE_P2P_DEVICE:
break;
case NL80211_IFTYPE_NAN:
+ case NL80211_IFTYPE_NAN_DATA:
if (!wiphy_ext_feature_isset(wdev->wiphy,
NL80211_EXT_FEATURE_SECURE_NAN) &&
!(wdev->wiphy->nan_capa.flags &
@@ -14270,6 +14271,7 @@ static int nl80211_tx_mgmt(struct sk_buff *skb, struct genl_info *info)
case NL80211_IFTYPE_P2P_GO:
break;
case NL80211_IFTYPE_NAN:
+ case NL80211_IFTYPE_NAN_DATA:
if (!wiphy_ext_feature_isset(wdev->wiphy,
NL80211_EXT_FEATURE_SECURE_NAN) &&
!(wdev->wiphy->nan_capa.flags &
--
2.34.1
^ permalink raw reply related
* [PATCH v5 wireless-next 11/12] wifi: ieee80211: Add some missing NAN definitions
From: Miri Korenblit @ 2026-03-18 12:39 UTC (permalink / raw)
To: linux-wireless; +Cc: Ilan Peer
In-Reply-To: <20260318123926.206536-1-miriam.rachel.korenblit@intel.com>
From: Ilan Peer <ilan.peer@intel.com>
Add some missing NAN Device capabilities definitions.
Signed-off-by: Ilan Peer <ilan.peer@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
include/linux/ieee80211-nan.h | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/include/linux/ieee80211-nan.h b/include/linux/ieee80211-nan.h
index d07959bf8a90..ebf28ea651f9 100644
--- a/include/linux/ieee80211-nan.h
+++ b/include/linux/ieee80211-nan.h
@@ -9,7 +9,7 @@
* Copyright (c) 2006, Michael Wu <flamingice@sourmilk.net>
* Copyright (c) 2013 - 2014 Intel Mobile Communications GmbH
* Copyright (c) 2016 - 2017 Intel Deutschland GmbH
- * Copyright (c) 2018 - 2025 Intel Corporation
+ * Copyright (c) 2018 - 2026 Intel Corporation
*/
#ifndef LINUX_IEEE80211_NAN_H
@@ -23,6 +23,11 @@
#define NAN_OP_MODE_160MHZ 0x04
#define NAN_OP_MODE_PNDL_SUPPRTED 0x08
+#define NAN_DEV_CAPA_NUM_TX_ANT_POS 0
+#define NAN_DEV_CAPA_NUM_TX_ANT_MASK 0x0f
+#define NAN_DEV_CAPA_NUM_RX_ANT_POS 4
+#define NAN_DEV_CAPA_NUM_RX_ANT_MASK 0xf0
+
/* NAN Device capabilities, as defined in Wi-Fi Aware (TM) specification
* Table 79
*/
--
2.34.1
^ permalink raw reply related
* [PATCH v5 wireless-next 10/12] wifi: nl80211: Add a notification to notify NAN channel evacuation
From: Miri Korenblit @ 2026-03-18 12:39 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260318123926.206536-1-miriam.rachel.korenblit@intel.com>
If all available channel resources are used for NAN channels, and one of
them is shared with another interface, and that interface needs to move
to a different channel (for example STA interface that needs to do a
channel or a link switch), then the driver can evacuate one of the NAN
channels (i.e. detach it from its channel resource and announce to the
peers that this channel is ULWed). In that case, the driver needs to
notify user space about the channel evacuation, so the user space can
adjust the local schedule accordingly.
Add a notification to let userspace know about it.
Reviewed-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Link: https://patch.msgid.link/20260219114327.d5bebfd5ff73.Iaaf5ef17e1ab7a38c19d60558e68fcf517e2b400@changeid
---
include/net/cfg80211.h | 19 ++++++++++++++
include/uapi/linux/nl80211.h | 27 +++++++++++++++-----
net/wireless/nl80211.c | 48 ++++++++++++++++++++++++++++++++++++
net/wireless/trace.h | 18 ++++++++++++++
4 files changed, 106 insertions(+), 6 deletions(-)
diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index ee173f69c417..9d3639ff9c28 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -10588,6 +10588,25 @@ void cfg80211_nan_cluster_joined(struct wireless_dev *wdev,
void cfg80211_nan_ulw_update(struct wireless_dev *wdev,
const u8 *ulw, size_t ulw_len, gfp_t gfp);
+/**
+ * cfg80211_nan_channel_evac - Notify user space about NAN channel evacuation
+ * @wdev: Pointer to the wireless device structure
+ * @chandef: Pointer to the channel definition of the NAN channel that was
+ * evacuated
+ * @gfp: Memory allocation flags
+ *
+ * This function is used by drivers to notify user space when a NAN
+ * channel has been evacuated (i.e. ULWed) due to channel resource conflicts
+ * with other interfaces.
+ * This can happen when another interface sharing the channel resource with NAN
+ * needs to move to a different channel (e.g. due to channel switch or link
+ * switch). User space may reconfigure the local schedule to exclude the
+ * evacuated channel.
+ */
+void cfg80211_nan_channel_evac(struct wireless_dev *wdev,
+ const struct cfg80211_chan_def *chandef,
+ gfp_t gfp);
+
#ifdef CONFIG_CFG80211_DEBUGFS
/**
* wiphy_locked_debugfs_read - do a locked read in debugfs
diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h
index 947ec7079484..3d55bf4be36f 100644
--- a/include/uapi/linux/nl80211.h
+++ b/include/uapi/linux/nl80211.h
@@ -1406,6 +1406,15 @@
* with the updated ULW blob of the device. User space can use this blob
* to attach to frames sent to peers. This notification contains
* %NL80211_ATTR_NAN_ULW with the ULW blob.
+ * @NL80211_CMD_NAN_CHANNEL_EVAC: Notification to indicate that a NAN
+ * channel has been evacuated due to resource conflicts with other
+ * interfaces. This can happen when another interface sharing the channel
+ * resource with NAN needs to move to a different channel (e.g., channel
+ * switch or link switch on a BSS interface).
+ * The notification contains %NL80211_ATTR_NAN_CHANNEL attribute
+ * identifying the evacuated channel.
+ * User space may reconfigure the local schedule in response to this
+ * notification.
* @NL80211_CMD_MAX: highest used command number
* @__NL80211_CMD_AFTER_LAST: internal use
*/
@@ -1678,6 +1687,9 @@ enum nl80211_commands {
NL80211_CMD_NAN_SET_PEER_SCHED,
NL80211_CMD_NAN_ULW_UPDATE,
+
+ NL80211_CMD_NAN_CHANNEL_EVAC,
+
/* add new commands above here */
/* used to define NL80211_CMD_MAX below */
@@ -3040,20 +3052,23 @@ enum nl80211_commands {
* Currently only supported in mac80211 drivers.
* @NL80211_ATTR_NAN_CHANNEL: This is a nested attribute. There can be multiple
* attributes of this type, each one represents a channel definition and
- * consists of top-level attributes like %NL80211_ATTR_WIPHY_FREQ. Must
- * contain %NL80211_ATTR_NAN_CHANNEL_ENTRY and
- * %NL80211_ATTR_NAN_RX_NSS.
- * This attribute is used with %NL80211_CMD_NAN_SET_LOCAL_SCHED to specify
+ * consists of top-level attributes like %NL80211_ATTR_WIPHY_FREQ.
+ * When used with %NL80211_CMD_NAN_SET_LOCAL_SCHED, it specifies
* the channel definitions on which the radio needs to operate during
* specific time slots. All of the channel definitions should be mutually
- * incompatible.
- * This is also used with %NL80211_CMD_NAN_SET_PEER_SCHED to configure the
+ * incompatible. With this command, %NL80211_ATTR_NAN_CHANNEL_ENTRY and
+ * %NL80211_ATTR_NAN_RX_NSS are mandatory.
+ * When used with %NL80211_CMD_NAN_SET_PEER_SCHED, it configures the
* peer NAN channels. In that case, the channel definitions can be
* compatible to each other, or even identical just with different RX NSS.
+ * With this command, %NL80211_ATTR_NAN_CHANNEL_ENTRY and
+ * %NL80211_ATTR_NAN_RX_NSS are mandatory.
* The number of channels should fit the current configuration of channels
* and the possible interface combinations.
* If an existing NAN channel is changed but the chandef isn't, the
* channel entry must also remain unchanged.
+ * When used with %NL80211_CMD_NAN_CHANNEL_EVAC, this identifies the
+ * channels that were evacuated.
* @NL80211_ATTR_NAN_CHANNEL_ENTRY: a byte array of 6 bytes. contains the
* Channel Entry as defined in Wi-Fi Aware (TM) 4.0 specification Table
* 100 (Channel Entry format for the NAN Availability attribute).
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index bf9df11553ef..0c4d6e8b45b2 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -22936,6 +22936,54 @@ void cfg80211_nan_ulw_update(struct wireless_dev *wdev,
}
EXPORT_SYMBOL(cfg80211_nan_ulw_update);
+void cfg80211_nan_channel_evac(struct wireless_dev *wdev,
+ const struct cfg80211_chan_def *chandef,
+ gfp_t gfp)
+{
+ struct wiphy *wiphy = wdev->wiphy;
+ struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
+ struct sk_buff *msg;
+ struct nlattr *chan_attr;
+ void *hdr;
+
+ trace_cfg80211_nan_channel_evac(wiphy, wdev, chandef);
+
+ if (!wdev->owner_nlportid)
+ return;
+
+ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, gfp);
+ if (!msg)
+ return;
+
+ hdr = nl80211hdr_put(msg, 0, 0, 0, NL80211_CMD_NAN_CHANNEL_EVAC);
+ if (!hdr)
+ goto nla_put_failure;
+
+ if (nla_put_u32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx) ||
+ nla_put_u64_64bit(msg, NL80211_ATTR_WDEV, wdev_id(wdev),
+ NL80211_ATTR_PAD))
+ goto nla_put_failure;
+
+ chan_attr = nla_nest_start(msg, NL80211_ATTR_NAN_CHANNEL);
+ if (!chan_attr)
+ goto nla_put_failure;
+
+ if (nl80211_send_chandef(msg, chandef))
+ goto nla_put_failure;
+
+ nla_nest_end(msg, chan_attr);
+
+ genlmsg_end(msg, hdr);
+
+ genlmsg_unicast(wiphy_net(wiphy), msg, wdev->owner_nlportid);
+
+ return;
+
+ nla_put_failure:
+ nlmsg_free(msg);
+}
+EXPORT_SYMBOL(cfg80211_nan_channel_evac);
+
/* initialisation/exit functions */
int __init nl80211_init(void)
diff --git a/net/wireless/trace.h b/net/wireless/trace.h
index 061bb84f1a48..eb5bedf9c92a 100644
--- a/net/wireless/trace.h
+++ b/net/wireless/trace.h
@@ -4363,6 +4363,24 @@ TRACE_EVENT(cfg80211_nan_ulw_update,
__print_array(__get_dynamic_array(ulw),
__get_dynamic_array_len(ulw), 1))
);
+
+TRACE_EVENT(cfg80211_nan_channel_evac,
+ TP_PROTO(struct wiphy *wiphy, struct wireless_dev *wdev,
+ const struct cfg80211_chan_def *chandef),
+ TP_ARGS(wiphy, wdev, chandef),
+ TP_STRUCT__entry(
+ WDEV_ENTRY
+ WIPHY_ENTRY
+ CHAN_DEF_ENTRY
+ ),
+ TP_fast_assign(
+ WDEV_ASSIGN;
+ WIPHY_ASSIGN;
+ CHAN_DEF_ASSIGN(chandef);
+ ),
+ TP_printk(WDEV_PR_FMT ", " WIPHY_PR_FMT ", " CHAN_DEF_PR_FMT,
+ WDEV_PR_ARG, WIPHY_PR_ARG, CHAN_DEF_PR_ARG)
+);
#endif /* !__RDEV_OPS_TRACE || TRACE_HEADER_MULTI_READ */
#undef TRACE_INCLUDE_PATH
--
2.34.1
^ permalink raw reply related
* [PATCH v5 wireless-next 09/12] wifi: nl80211: add NL80211_CMD_NAN_ULW_UPDATE notification
From: Miri Korenblit @ 2026-03-18 12:39 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260318123926.206536-1-miriam.rachel.korenblit@intel.com>
Add a new notification command that allows drivers to notify user space
when the device's ULW (Unaligned Schedule) blob has been updated. This
enables user space to attach the updated ULW blob to frames sent to NAN
peers.
Reviewed-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Link: https://patch.msgid.link/20260219114327.32b715af4ebb.Ibdb6e33941afd94abf77245245f87e4338d729d3@changeid
---
include/net/cfg80211.h | 14 ++++++++++++
include/uapi/linux/nl80211.h | 5 +++++
net/wireless/nl80211.c | 43 ++++++++++++++++++++++++++++++++++++
net/wireless/trace.h | 21 ++++++++++++++++++
4 files changed, 83 insertions(+)
diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index 0d19f34ea7ac..ee173f69c417 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -10574,6 +10574,20 @@ void cfg80211_nan_cluster_joined(struct wireless_dev *wdev,
const u8 *cluster_id, bool new_cluster,
gfp_t gfp);
+/**
+ * cfg80211_nan_ulw_update - Notify user space about ULW update
+ * @wdev: Pointer to the wireless device structure
+ * @ulw: Pointer to the ULW blob data
+ * @ulw_len: Length of the ULW blob in bytes
+ * @gfp: Memory allocation flags
+ *
+ * This function is used by drivers to notify user space when the device's
+ * ULW (Unaligned Schedule) blob has been updated. User space can use this
+ * blob to attach to frames sent to peers.
+ */
+void cfg80211_nan_ulw_update(struct wireless_dev *wdev,
+ const u8 *ulw, size_t ulw_len, gfp_t gfp);
+
#ifdef CONFIG_CFG80211_DEBUGFS
/**
* wiphy_locked_debugfs_read - do a locked read in debugfs
diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h
index cf6f1f6b9e36..947ec7079484 100644
--- a/include/uapi/linux/nl80211.h
+++ b/include/uapi/linux/nl80211.h
@@ -1402,6 +1402,10 @@
* completely replace the previous one.
* The peer schedule is automatically removed when the NMI station is
* removed.
+ * @NL80211_CMD_NAN_ULW_UPDATE: Notification from the driver to user space
+ * with the updated ULW blob of the device. User space can use this blob
+ * to attach to frames sent to peers. This notification contains
+ * %NL80211_ATTR_NAN_ULW with the ULW blob.
* @NL80211_CMD_MAX: highest used command number
* @__NL80211_CMD_AFTER_LAST: internal use
*/
@@ -1673,6 +1677,7 @@ enum nl80211_commands {
NL80211_CMD_NAN_SET_PEER_SCHED,
+ NL80211_CMD_NAN_ULW_UPDATE,
/* add new commands above here */
/* used to define NL80211_CMD_MAX below */
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index e162b0a3c2a6..bf9df11553ef 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -22893,6 +22893,49 @@ void cfg80211_nan_cluster_joined(struct wireless_dev *wdev,
}
EXPORT_SYMBOL(cfg80211_nan_cluster_joined);
+void cfg80211_nan_ulw_update(struct wireless_dev *wdev,
+ const u8 *ulw, size_t ulw_len, gfp_t gfp)
+{
+ struct wiphy *wiphy = wdev->wiphy;
+ struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
+ struct sk_buff *msg;
+ void *hdr;
+
+ trace_cfg80211_nan_ulw_update(wiphy, wdev, ulw, ulw_len);
+
+ if (!wdev->owner_nlportid)
+ return;
+
+ /* 32 for the wiphy idx, 64 for the wdev id, 100 for padding */
+ msg = nlmsg_new(nla_total_size(sizeof(u32)) +
+ nla_total_size(ulw_len) +
+ nla_total_size(sizeof(u64)) + 100,
+ gfp);
+ if (!msg)
+ return;
+
+ hdr = nl80211hdr_put(msg, 0, 0, 0, NL80211_CMD_NAN_ULW_UPDATE);
+ if (!hdr)
+ goto nla_put_failure;
+
+ if (nla_put_u32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx) ||
+ nla_put_u64_64bit(msg, NL80211_ATTR_WDEV, wdev_id(wdev),
+ NL80211_ATTR_PAD) ||
+ (ulw && ulw_len &&
+ nla_put(msg, NL80211_ATTR_NAN_ULW, ulw_len, ulw)))
+ goto nla_put_failure;
+
+ genlmsg_end(msg, hdr);
+
+ genlmsg_unicast(wiphy_net(wiphy), msg, wdev->owner_nlportid);
+
+ return;
+
+ nla_put_failure:
+ nlmsg_free(msg);
+}
+EXPORT_SYMBOL(cfg80211_nan_ulw_update);
+
/* initialisation/exit functions */
int __init nl80211_init(void)
diff --git a/net/wireless/trace.h b/net/wireless/trace.h
index df639d97cc0c..061bb84f1a48 100644
--- a/net/wireless/trace.h
+++ b/net/wireless/trace.h
@@ -4342,6 +4342,27 @@ TRACE_EVENT(cfg80211_nan_sched_update_done,
TP_printk(WIPHY_PR_FMT ", " WDEV_PR_FMT " success=%d",
WIPHY_PR_ARG, WDEV_PR_ARG, __entry->success)
);
+
+TRACE_EVENT(cfg80211_nan_ulw_update,
+ TP_PROTO(struct wiphy *wiphy, struct wireless_dev *wdev,
+ const u8 *ulw, size_t ulw_len),
+ TP_ARGS(wiphy, wdev, ulw, ulw_len),
+ TP_STRUCT__entry(
+ WIPHY_ENTRY
+ WDEV_ENTRY
+ __dynamic_array(u8, ulw, ulw_len)
+ ),
+ TP_fast_assign(
+ WIPHY_ASSIGN;
+ WDEV_ASSIGN;
+ if (ulw && ulw_len)
+ memcpy(__get_dynamic_array(ulw), ulw, ulw_len);
+ ),
+ TP_printk(WIPHY_PR_FMT ", " WDEV_PR_FMT " ulw: %s",
+ WIPHY_PR_ARG, WDEV_PR_ARG,
+ __print_array(__get_dynamic_array(ulw),
+ __get_dynamic_array_len(ulw), 1))
+);
#endif /* !__RDEV_OPS_TRACE || TRACE_HEADER_MULTI_READ */
#undef TRACE_INCLUDE_PATH
--
2.34.1
^ permalink raw reply related
* [PATCH v5 wireless-next 08/12] wifi: nl80211: allow reporting spurious NAN Data frames
From: Miri Korenblit @ 2026-03-18 12:39 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260318123926.206536-1-miriam.rachel.korenblit@intel.com>
Currently we have this ability for AP and GO. But it is now needed also for
NAN_DATA mode - as per Wi-Fi Aware (TM) 4.0 specification 6.2.5:
"If a NAN Device receives a unicast NAN Data frame destined for it, but
with A1 address and A2 address that are not assigned to the NDP, it shall
discard the frame, and should send a Data Path Termination NAF to the
frame transmitter"
To allow this, change NL80211_CMD_UNEXPECTED_FRAME to support also
NAN_DATA, so drivers can report such cases and the user space can act
accordingly.
Reviewed-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Link: https://patch.msgid.link/20260108102921.5cf9f1351655.I47c98ce37843730b8b9eb8bd8e9ef62ed6c17613@changeid
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Link: https://patch.msgid.link/20260219094725.3846371-6-miriam.rachel.korenblit@intel.com
---
include/net/cfg80211.h | 13 +++++++------
include/uapi/linux/nl80211.h | 5 +++--
net/wireless/mlme.c | 4 ++--
net/wireless/nl80211.c | 12 +++++++-----
4 files changed, 19 insertions(+), 15 deletions(-)
diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index 48ca5d3aa201..0d19f34ea7ac 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -6938,8 +6938,8 @@ enum ieee80211_ap_reg_power {
* the P2P Device.
* @ps: powersave mode is enabled
* @ps_timeout: dynamic powersave timeout
- * @ap_unexpected_nlportid: (private) netlink port ID of application
- * registered for unexpected class 3 frames (AP mode)
+ * @unexpected_nlportid: (private) netlink port ID of application
+ * registered for unexpected frames (AP mode or NAN_DATA mode)
* @conn: (private) cfg80211 software SME connection state machine data
* @connect_keys: (private) keys to set after connection is established
* @conn_bss_type: connecting/connected BSS type
@@ -7001,7 +7001,7 @@ struct wireless_dev {
bool ps;
int ps_timeout;
- u32 ap_unexpected_nlportid;
+ u32 unexpected_nlportid;
u32 owner_nlportid;
bool nl_owner_dead;
@@ -9572,9 +9572,10 @@ void cfg80211_pmksa_candidate_notify(struct net_device *dev, int index,
* @addr: the transmitter address
* @gfp: context flags
*
- * This function is used in AP mode (only!) to inform userspace that
- * a spurious class 3 frame was received, to be able to deauth the
- * sender.
+ * This function is used in AP mode to inform userspace that a spurious
+ * class 3 frame was received, to be able to deauth the sender.
+ * It is also used in NAN_DATA mode to report frames from unknown peers
+ * (A2 not assigned to any active NDP), per Wi-Fi Aware (TM) 4.0 specification 6.2.5.
* Return: %true if the frame was passed to userspace (or this failed
* for a reason other than not having a subscription.)
*/
diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h
index e7f31a34eee4..cf6f1f6b9e36 100644
--- a/include/uapi/linux/nl80211.h
+++ b/include/uapi/linux/nl80211.h
@@ -906,8 +906,9 @@
* @NL80211_CMD_UNEXPECTED_FRAME: Used by an application controlling an AP
* (or GO) interface (i.e. hostapd) to ask for unexpected frames to
* implement sending deauth to stations that send unexpected class 3
- * frames. Also used as the event sent by the kernel when such a frame
- * is received.
+ * frames. For NAN_DATA interfaces, this is used to report frames from
+ * unknown peers (A2 not assigned to any active NDP).
+ * Also used as the event sent by the kernel when such a frame is received.
* For the event, the %NL80211_ATTR_MAC attribute carries the TA and
* other attributes like the interface index are present.
* If used as the command it must have an interface index and you can
diff --git a/net/wireless/mlme.c b/net/wireless/mlme.c
index 5cd86253a62e..e817ee297df0 100644
--- a/net/wireless/mlme.c
+++ b/net/wireless/mlme.c
@@ -782,8 +782,8 @@ void cfg80211_mlme_unregister_socket(struct wireless_dev *wdev, u32 nlportid)
rdev_crit_proto_stop(rdev, wdev);
}
- if (nlportid == wdev->ap_unexpected_nlportid)
- wdev->ap_unexpected_nlportid = 0;
+ if (nlportid == wdev->unexpected_nlportid)
+ wdev->unexpected_nlportid = 0;
}
void cfg80211_mlme_purge_registrations(struct wireless_dev *wdev)
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 012fef1d6275..e162b0a3c2a6 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -15777,13 +15777,14 @@ static int nl80211_register_unexpected_frame(struct sk_buff *skb,
struct wireless_dev *wdev = dev->ieee80211_ptr;
if (wdev->iftype != NL80211_IFTYPE_AP &&
- wdev->iftype != NL80211_IFTYPE_P2P_GO)
+ wdev->iftype != NL80211_IFTYPE_P2P_GO &&
+ wdev->iftype != NL80211_IFTYPE_NAN_DATA)
return -EINVAL;
- if (wdev->ap_unexpected_nlportid)
+ if (wdev->unexpected_nlportid)
return -EBUSY;
- wdev->ap_unexpected_nlportid = info->snd_portid;
+ wdev->unexpected_nlportid = info->snd_portid;
return 0;
}
@@ -21281,7 +21282,7 @@ static bool __nl80211_unexpected_frame(struct net_device *dev, u8 cmd,
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
struct sk_buff *msg;
void *hdr;
- u32 nlportid = READ_ONCE(wdev->ap_unexpected_nlportid);
+ u32 nlportid = READ_ONCE(wdev->unexpected_nlportid);
if (!nlportid)
return false;
@@ -21321,7 +21322,8 @@ bool cfg80211_rx_spurious_frame(struct net_device *dev, const u8 *addr,
trace_cfg80211_rx_spurious_frame(dev, addr, link_id);
if (WARN_ON(wdev->iftype != NL80211_IFTYPE_AP &&
- wdev->iftype != NL80211_IFTYPE_P2P_GO)) {
+ wdev->iftype != NL80211_IFTYPE_P2P_GO &&
+ wdev->iftype != NL80211_IFTYPE_NAN_DATA)) {
trace_cfg80211_return_bool(false);
return false;
}
--
2.34.1
^ permalink raw reply related
* [PATCH v5 wireless-next 07/12] wifi: cfg80211: allow ToDS=0/FromDS=0 data frames on NAN data interfaces
From: Miri Korenblit @ 2026-03-18 12:39 UTC (permalink / raw)
To: linux-wireless; +Cc: Daniel Gabay
In-Reply-To: <20260318123926.206536-1-miriam.rachel.korenblit@intel.com>
From: Daniel Gabay <daniel.gabay@intel.com>
According to Wi-Fi Aware (TM) specification Table 3, data frame should
have 0 in the FromDS/ToDS fields. Don't drop received frames with 0
FromDS/ToDS if they are received on NAN_DATA interface.
While at it, fix a double indent.
Signed-off-by: Daniel Gabay <daniel.gabay@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Link: https://patch.msgid.link/20260108102921.de5f318a790a.Id34dd69552920b579e6881ffd38fa692a491b601@changeid
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Link: https://patch.msgid.link/20260219094725.3846371-5-miriam.rachel.korenblit@intel.com
---
net/wireless/util.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/net/wireless/util.c b/net/wireless/util.c
index 8dda571585cf..6769ef55ee59 100644
--- a/net/wireless/util.c
+++ b/net/wireless/util.c
@@ -625,8 +625,9 @@ int ieee80211_data_to_8023_exthdr(struct sk_buff *skb, struct ethhdr *ehdr,
case cpu_to_le16(0):
if (iftype != NL80211_IFTYPE_ADHOC &&
iftype != NL80211_IFTYPE_STATION &&
- iftype != NL80211_IFTYPE_OCB)
- return -1;
+ iftype != NL80211_IFTYPE_OCB &&
+ iftype != NL80211_IFTYPE_NAN_DATA)
+ return -1;
break;
}
--
2.34.1
^ permalink raw reply related
* [PATCH v5 wireless-next 06/12] wifi: nl80211: define an API for configuring the NAN peer's schedule
From: Miri Korenblit @ 2026-03-18 12:39 UTC (permalink / raw)
To: linux-wireless
In-Reply-To: <20260318123926.206536-1-miriam.rachel.korenblit@intel.com>
Add an NL80211 command to configure the NAN schedule of a NAN peer.
Such a schedule contains a list of NAN channels, and a mapping from each
time slots to the corresponding channel (or unscheduled).
Also contains more information about the schedule, such as sequence ID
and map ID.
Not all of the restrictions are validated in this patch. In particular,
comparison of two maps of the same peer requires storing/retrieving each
map of each peer, only for validation.
Therefore, it is the responsibilty of the driver to check that.
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Link: https://patch.msgid.link/20260219114327.5b13fa5af4f6.If0e214ff5b52c9666e985fefa3f7be0ad14d93fb@changeid
---
include/net/cfg80211.h | 58 +++++
include/uapi/linux/nl80211.h | 82 +++++++-
net/wireless/nl80211.c | 395 ++++++++++++++++++++++++++++++++---
net/wireless/rdev-ops.h | 16 ++
net/wireless/trace.h | 28 +++
5 files changed, 546 insertions(+), 33 deletions(-)
diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index 654d71f60e8c..48ca5d3aa201 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -4175,6 +4175,54 @@ struct cfg80211_nan_local_sched {
struct cfg80211_nan_channel nan_channels[] __counted_by(n_channels);
};
+/**
+ * struct cfg80211_nan_peer_map - NAN peer schedule map
+ *
+ * This struct defines a single NAN peer schedule map
+ *
+ * @map_id: map ID of this schedule map
+ * @schedule: a mapping of time slots to chandef indexes in the schedule's
+ * @nan_channels. Each slot lasts 16TUs. An unscheduled slot will be
+ * set to %NL80211_NAN_SCHED_NOT_AVAIL_SLOT.
+ */
+struct cfg80211_nan_peer_map {
+ u8 map_id;
+ u8 schedule[CFG80211_NAN_SCHED_NUM_TIME_SLOTS];
+};
+
+#define CFG80211_NAN_MAX_PEER_MAPS 2
+#define CFG80211_NAN_INVALID_MAP_ID 0xff
+
+/**
+ * struct cfg80211_nan_peer_sched - NAN peer schedule
+ *
+ * This struct defines NAN peer schedule parameters for a peer.
+ *
+ * @peer_addr: MAC address of the peer (NMI address)
+ * @seq_id: sequence ID of the peer schedule.
+ * @committed_dw: committed DW as published by the peer.
+ * See %NL80211_ATTR_NAN_COMMITTED_DW
+ * @max_chan_switch: maximum channel switch time in microseconds as published
+ * by the peer. See %NL80211_ATTR_NAN_MAX_CHAN_SWITCH_TIME.
+ * @init_ulw: initial ULWs as published by the peer.
+ * @ulw_size: number of bytes in @init_ulw.
+ * @n_channels: number of channel definitions in @nan_channels.
+ * @nan_channels: array of NAN channel definitions for this schedule.
+ * @maps: array of peer schedule maps. Unused entries have
+ * map_id = %CFG80211_NAN_INVALID_MAP_ID.
+ */
+struct cfg80211_nan_peer_sched {
+ const u8 *peer_addr;
+ u8 seq_id;
+ u16 committed_dw;
+ u16 max_chan_switch;
+ const u8 *init_ulw;
+ u16 ulw_size;
+ u8 n_channels;
+ struct cfg80211_nan_channel *nan_channels;
+ struct cfg80211_nan_peer_map maps[CFG80211_NAN_MAX_PEER_MAPS];
+};
+
/**
* enum cfg80211_nan_conf_changes - indicates changed fields in NAN
* configuration
@@ -4961,6 +5009,13 @@ struct mgmt_frame_regs {
* radio should operate on. If the chandef of a NAN channel is not
* changed, the channel entry must also remain unchanged. It is the
* driver's responsibility to verify this.
+ * @nan_set_peer_sched: configure the peer schedule for NAN. The schedule
+ * consists of an array of %cfg80211_nan_channel and the schedule itself,
+ * in which each entry maps each time slot to a channel on which the
+ * radio should operate on. In addition, it contains more peer's schedule
+ * information such as committed DW, etc. When updating an existing peer
+ * schedule, the full new schedule is provided - partial updates are not
+ * supported, and the new schedule completely replaces the previous one.
*
* @set_multicast_to_unicast: configure multicast to unicast conversion for BSS
*
@@ -5341,6 +5396,9 @@ struct cfg80211_ops {
int (*nan_set_local_sched)(struct wiphy *wiphy,
struct wireless_dev *wdev,
struct cfg80211_nan_local_sched *sched);
+ int (*nan_set_peer_sched)(struct wiphy *wiphy,
+ struct wireless_dev *wdev,
+ struct cfg80211_nan_peer_sched *sched);
int (*set_multicast_to_unicast)(struct wiphy *wiphy,
struct net_device *dev,
const bool enabled);
diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h
index 1897b9a35be8..e7f31a34eee4 100644
--- a/include/uapi/linux/nl80211.h
+++ b/include/uapi/linux/nl80211.h
@@ -1381,6 +1381,26 @@
* %NL80211_CMD_NAN_SET_LOCAL_SCHED and %NL80211_ATTR_NAN_SCHED_DEFERRED)
* has been completed. The presence of %NL80211_ATTR_NAN_SCHED_UPDATE_SUCCESS
* indicates that the update was successful.
+ * @NL80211_CMD_NAN_SET_PEER_SCHED: Set the peer NAN schedule. NAN
+ * must be operational (%NL80211_CMD_START_NAN was executed).
+ * Required attributes: %NL80211_ATTR_MAC (peer NMI address) and
+ * %NL80211_ATTR_NAN_COMMITTED_DW.
+ * Optionally, the full schedule can be provided by including all of:
+ * %NL80211_ATTR_NAN_SEQ_ID, %NL80211_ATTR_NAN_CHANNEL (one or more), and
+ * %NL80211_ATTR_NAN_PEER_MAPS (see &enum nl80211_nan_peer_map_attrs).
+ * If any of these three optional attributes is provided, all three must
+ * be provided.
+ * Each peer channel must be compatible with at least one local channel
+ * set by %NL80211_CMD_SET_LOCAL_NAN_SCHED. Different maps must not
+ * contain compatible channels.
+ * For single-radio devices (n_radio <= 1), different maps must not
+ * schedule the same time slot, as the device cannot operate on multiple
+ * channels simultaneously.
+ * When updating an existing peer schedule, the full new schedule must be
+ * provided - partial updates are not supported. The new schedule will
+ * completely replace the previous one.
+ * The peer schedule is automatically removed when the NMI station is
+ * removed.
* @NL80211_CMD_MAX: highest used command number
* @__NL80211_CMD_AFTER_LAST: internal use
*/
@@ -1650,6 +1670,8 @@ enum nl80211_commands {
NL80211_CMD_NAN_SCHED_UPDATE_DONE,
+ NL80211_CMD_NAN_SET_PEER_SCHED,
+
/* add new commands above here */
/* used to define NL80211_CMD_MAX below */
@@ -3018,8 +3040,12 @@ enum nl80211_commands {
* This attribute is used with %NL80211_CMD_NAN_SET_LOCAL_SCHED to specify
* the channel definitions on which the radio needs to operate during
* specific time slots. All of the channel definitions should be mutually
- * incompatible. The number of channels should fit the current
- * configuration of channels and the possible interface combinations.
+ * incompatible.
+ * This is also used with %NL80211_CMD_NAN_SET_PEER_SCHED to configure the
+ * peer NAN channels. In that case, the channel definitions can be
+ * compatible to each other, or even identical just with different RX NSS.
+ * The number of channels should fit the current configuration of channels
+ * and the possible interface combinations.
* If an existing NAN channel is changed but the chandef isn't, the
* channel entry must also remain unchanged.
* @NL80211_ATTR_NAN_CHANNEL_ENTRY: a byte array of 6 bytes. contains the
@@ -3027,7 +3053,7 @@ enum nl80211_commands {
* 100 (Channel Entry format for the NAN Availability attribute).
* @NL80211_ATTR_NAN_RX_NSS: (u8) RX NSS used for a NAN channel. This is
* used with %NL80211_ATTR_NAN_CHANNEL when configuring NAN channels with
- * %NL80211_CMD_NAN_SET_LOCAL_SCHED.
+ * %NL80211_CMD_NAN_SET_LOCAL_SCHED or %NL80211_CMD_NAN_SET_PEER_SCHED.
* @NL80211_ATTR_NAN_TIME_SLOTS: an array of u8 values and 32 cells. each value
* maps a time slot to the chandef on which the radio should operate on in
* that time. %NL80211_NAN_SCHED_NOT_AVAIL_SLOT indicates unscheduled.
@@ -3061,6 +3087,24 @@ enum nl80211_commands {
* @NL80211_ATTR_NAN_NMI_MAC: The address of the NMI station to which this NDI
* station belongs. Used with %NL80211_CMD_NEW_STATION when adding an NDI
* station.
+ * @NL80211_ATTR_NAN_ULW: (Binary) The initial ULW(s) as published by the
+ * peer, as defined in the Wi-Fi Aware (TM) 4.0 specification Table 109
+ * (Unaligned Schedule attribute format). Used to configure the device
+ * with the initial ULW(s) of a peer, before the device starts tracking it.
+ * @NL80211_ATTR_NAN_COMMITTED_DW: (u16) The committed DW as published by the
+ * peer, as defined in the Wi-Fi Aware (TM) 4.0 specification Table 80
+ * (Committed DW Information field format).
+ * @NL80211_ATTR_NAN_SEQ_ID: (u8) The sequence ID of the peer schedule that
+ * %NL80211_CMD_NAN_SET_PEER_SCHED defines. The device follows the
+ * sequence ID in the frames to identify newer schedules. Once a schedule
+ * with a higher sequence ID is received, the device may stop communicating
+ * with that peer until a new peer schedule with a matching sequence ID is
+ * received.
+ * @NL80211_ATTR_NAN_MAX_CHAN_SWITCH_TIME: (u16) The maximum channel switch
+ * time, in microseconds.
+ * @NL80211_ATTR_NAN_PEER_MAPS: Nested array of peer schedule maps.
+ * Used with %NL80211_CMD_NAN_SET_PEER_SCHED. Contains up to 2 entries,
+ * each containing nested attributes from &enum nl80211_nan_peer_map_attrs.
*
* @NL80211_ATTR_INCUMBENT_SIGNAL_INTERFERENCE_BITMAP: u32 attribute specifying
* the signal interference bitmap detected on the operating bandwidth for
@@ -3662,6 +3706,12 @@ enum nl80211_attrs {
NL80211_ATTR_NAN_NMI_MAC,
+ NL80211_ATTR_NAN_ULW,
+ NL80211_ATTR_NAN_COMMITTED_DW,
+ NL80211_ATTR_NAN_SEQ_ID,
+ NL80211_ATTR_NAN_MAX_CHAN_SWITCH_TIME,
+ NL80211_ATTR_NAN_PEER_MAPS,
+
/* add attributes here, update the policy in nl80211.c */
__NL80211_ATTR_AFTER_LAST,
@@ -8701,6 +8751,32 @@ enum nl80211_nan_capabilities {
NL80211_NAN_CAPABILITIES_MAX = __NL80211_NAN_CAPABILITIES_LAST - 1,
};
+/**
+ * enum nl80211_nan_peer_map_attrs - NAN peer schedule map attributes
+ *
+ * Nested attributes used within %NL80211_ATTR_NAN_PEER_MAPS to define
+ * individual peer schedule maps.
+ *
+ * @__NL80211_NAN_PEER_MAP_ATTR_INVALID: Invalid
+ * @NL80211_NAN_PEER_MAP_ATTR_MAP_ID: (u8) The map ID for this schedule map.
+ * @NL80211_NAN_PEER_MAP_ATTR_TIME_SLOTS: An array of u8 values with 32 cells.
+ * Each value maps a time slot to a channel index within the schedule's
+ * channel list (%NL80211_ATTR_NAN_CHANNEL attributes).
+ * %NL80211_NAN_SCHED_NOT_AVAIL_SLOT indicates unscheduled.
+ * @__NL80211_NAN_PEER_MAP_ATTR_LAST: Internal
+ * @NL80211_NAN_PEER_MAP_ATTR_MAX: Highest peer map attribute
+ */
+enum nl80211_nan_peer_map_attrs {
+ __NL80211_NAN_PEER_MAP_ATTR_INVALID,
+
+ NL80211_NAN_PEER_MAP_ATTR_MAP_ID,
+ NL80211_NAN_PEER_MAP_ATTR_TIME_SLOTS,
+
+ /* keep last */
+ __NL80211_NAN_PEER_MAP_ATTR_LAST,
+ NL80211_NAN_PEER_MAP_ATTR_MAX = __NL80211_NAN_PEER_MAP_ATTR_LAST - 1,
+};
+
#define NL80211_NAN_SCHED_NOT_AVAIL_SLOT 0xff
#endif /* __LINUX_NL80211_H */
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index b1e2303f0ef3..012fef1d6275 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -367,6 +367,63 @@ static int validate_nan_avail_blob(const struct nlattr *attr,
return 0;
}
+static int validate_nan_ulw(const struct nlattr *attr,
+ struct netlink_ext_ack *extack)
+{
+ const u8 *data = nla_data(attr);
+ unsigned int len = nla_len(attr);
+ unsigned int pos = 0;
+
+ while (pos < len) {
+ u16 attr_len;
+
+ /* Need at least: Attr ID (1) + Length (2) */
+ if (pos + 3 > len) {
+ NL_SET_ERR_MSG_FMT(extack,
+ "ULW: Incomplete header (need 3 bytes, have %u)",
+ len - pos);
+ return -EINVAL;
+ }
+
+ if (data[pos] != 0x17) {
+ NL_SET_ERR_MSG_FMT(extack,
+ "ULW: Invalid Attribute ID 0x%02x (expected 0x17)",
+ data[pos]);
+ return -EINVAL;
+ }
+ pos++;
+
+ /* Length is in little-endian format */
+ attr_len = get_unaligned_le16(&data[pos]);
+ pos += 2;
+
+ /*
+ * Check if length is one of the valid values: 16 (no
+ * channel/band entry included), 18 (band entry included),
+ * 21 (channel entry included without Auxiliary channel bitmap),
+ * or 23 (channel entry included with Auxiliary channel bitmap).
+ */
+ if (attr_len != 16 && attr_len != 18 && attr_len != 21 &&
+ attr_len != 23) {
+ NL_SET_ERR_MSG_FMT(extack,
+ "ULW: Invalid length %u (must be 16, 18, 21, or 23)",
+ attr_len);
+ return -EINVAL;
+ }
+
+ if (pos + attr_len > len) {
+ NL_SET_ERR_MSG_FMT(extack,
+ "ULW: Length field (%u) exceeds remaining data (%u)",
+ attr_len, len - pos);
+ return -EINVAL;
+ }
+
+ pos += attr_len;
+ }
+
+ return 0;
+}
+
static int validate_uhr_capa(const struct nlattr *attr,
struct netlink_ext_ack *extack)
{
@@ -589,6 +646,13 @@ nl80211_nan_band_conf_policy[NL80211_NAN_BAND_CONF_ATTR_MAX + 1] = {
[NL80211_NAN_BAND_CONF_DISABLE_SCAN] = { .type = NLA_FLAG },
};
+static const struct nla_policy
+nl80211_nan_peer_map_policy[NL80211_NAN_PEER_MAP_ATTR_MAX + 1] = {
+ [NL80211_NAN_PEER_MAP_ATTR_MAP_ID] = NLA_POLICY_MAX(NLA_U8, 15),
+ [NL80211_NAN_PEER_MAP_ATTR_TIME_SLOTS] =
+ NLA_POLICY_EXACT_LEN(CFG80211_NAN_SCHED_NUM_TIME_SLOTS),
+};
+
static const struct nla_policy
nl80211_nan_conf_policy[NL80211_NAN_CONF_ATTR_MAX + 1] = {
[NL80211_NAN_CONF_CLUSTER_ID] =
@@ -1005,6 +1069,13 @@ static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = {
NLA_POLICY_VALIDATE_FN(NLA_BINARY, validate_nan_avail_blob),
[NL80211_ATTR_NAN_SCHED_DEFERRED] = { .type = NLA_FLAG },
[NL80211_ATTR_NAN_NMI_MAC] = NLA_POLICY_ETH_ADDR,
+ [NL80211_ATTR_NAN_ULW] =
+ NLA_POLICY_VALIDATE_FN(NLA_BINARY, validate_nan_ulw),
+ [NL80211_ATTR_NAN_COMMITTED_DW] = { .type = NLA_U16 },
+ [NL80211_ATTR_NAN_SEQ_ID] = { .type = NLA_U8 },
+ [NL80211_ATTR_NAN_MAX_CHAN_SWITCH_TIME] = { .type = NLA_U16 },
+ [NL80211_ATTR_NAN_PEER_MAPS] =
+ NLA_POLICY_NESTED_ARRAY(nl80211_nan_peer_map_policy),
};
/* policy for the key attributes */
@@ -16659,8 +16730,8 @@ EXPORT_SYMBOL(cfg80211_nan_sched_update_done);
static int nl80211_parse_nan_channel(struct cfg80211_registered_device *rdev,
struct nlattr *channel,
struct genl_info *info,
- struct cfg80211_nan_local_sched *sched,
- u8 index)
+ struct cfg80211_nan_channel *nan_channels,
+ u8 index, bool local)
{
struct nlattr **channel_parsed __free(kfree) = NULL;
struct cfg80211_chan_def chandef;
@@ -16695,40 +16766,304 @@ static int nl80211_parse_nan_channel(struct cfg80211_registered_device *rdev,
return -EINVAL;
}
- for (int i = 0; i < index; i++) {
- if (cfg80211_chandef_compatible(&sched->nan_channels[i].chandef,
- &chandef)) {
- NL_SET_ERR_MSG_ATTR(info->extack, channel,
- "Channels in NAN schedule must be mutually incompatible");
- return -EINVAL;
+ if (local) {
+ for (int i = 0; i < index; i++) {
+ if (cfg80211_chandef_compatible(&nan_channels[i].chandef,
+ &chandef)) {
+ NL_SET_ERR_MSG_ATTR(info->extack, channel,
+ "Channels in NAN schedule must be mutually incompatible");
+ return -EINVAL;
+ }
}
}
- if (!channel_parsed[NL80211_ATTR_NAN_CHANNEL_ENTRY])
+ if (!channel_parsed[NL80211_ATTR_NAN_CHANNEL_ENTRY]) {
+ NL_SET_ERR_MSG(info->extack,
+ "Missing NAN channel entry attribute");
return -EINVAL;
+ }
- sched->nan_channels[index].channel_entry =
+ nan_channels[index].channel_entry =
nla_data(channel_parsed[NL80211_ATTR_NAN_CHANNEL_ENTRY]);
- if (!channel_parsed[NL80211_ATTR_NAN_RX_NSS])
+ if (!channel_parsed[NL80211_ATTR_NAN_RX_NSS]) {
+ NL_SET_ERR_MSG(info->extack,
+ "Missing NAN RX NSS attribute");
return -EINVAL;
+ }
- sched->nan_channels[index].rx_nss =
+ nan_channels[index].rx_nss =
nla_get_u8(channel_parsed[NL80211_ATTR_NAN_RX_NSS]);
n_rx_nss = u8_get_bits(rdev->wiphy.nan_capa.n_antennas, 0x03);
- if (sched->nan_channels[index].rx_nss > n_rx_nss ||
- !sched->nan_channels[index].rx_nss) {
+ if ((local && nan_channels[index].rx_nss > n_rx_nss) ||
+ !nan_channels[index].rx_nss) {
NL_SET_ERR_MSG_ATTR(info->extack, channel,
"Invalid RX NSS in NAN channel definition");
return -EINVAL;
}
- sched->nan_channels[index].chandef = chandef;
+ nan_channels[index].chandef = chandef;
+
+ return 0;
+}
+
+static int
+nl80211_parse_nan_schedule(struct genl_info *info, struct nlattr *slots_attr,
+ u8 schedule[CFG80211_NAN_SCHED_NUM_TIME_SLOTS],
+ u8 n_channels)
+{
+ if (WARN_ON(nla_len(slots_attr) != CFG80211_NAN_SCHED_NUM_TIME_SLOTS))
+ return -EINVAL;
+
+ memcpy(schedule, nla_data(slots_attr), nla_len(slots_attr));
+
+ for (int slot = 0; slot < CFG80211_NAN_SCHED_NUM_TIME_SLOTS; slot++) {
+ if (schedule[slot] != NL80211_NAN_SCHED_NOT_AVAIL_SLOT &&
+ schedule[slot] >= n_channels) {
+ NL_SET_ERR_MSG_FMT(info->extack,
+ "Invalid time slot: slot %d refers to channel index %d, n_channels=%d",
+ slot, schedule[slot], n_channels);
+ return -EINVAL;
+ }
+ }
return 0;
}
+static int
+nl80211_parse_nan_peer_map(struct genl_info *info, struct nlattr *map_attr,
+ struct cfg80211_nan_peer_map *map, u8 n_channels)
+{
+ struct nlattr *tb[NL80211_NAN_PEER_MAP_ATTR_MAX + 1];
+ int ret;
+
+ ret = nla_parse_nested(tb, NL80211_NAN_PEER_MAP_ATTR_MAX, map_attr,
+ nl80211_nan_peer_map_policy, info->extack);
+ if (ret)
+ return ret;
+
+ if (!tb[NL80211_NAN_PEER_MAP_ATTR_MAP_ID] ||
+ !tb[NL80211_NAN_PEER_MAP_ATTR_TIME_SLOTS]) {
+ NL_SET_ERR_MSG(info->extack,
+ "Missing required peer map attributes");
+ return -EINVAL;
+ }
+
+ map->map_id = nla_get_u8(tb[NL80211_NAN_PEER_MAP_ATTR_MAP_ID]);
+
+ /* Parse schedule */
+ return nl80211_parse_nan_schedule(info,
+ tb[NL80211_NAN_PEER_MAP_ATTR_TIME_SLOTS],
+ map->schedule, n_channels);
+}
+
+static int nl80211_nan_validate_map_pair(struct wiphy *wiphy,
+ struct genl_info *info,
+ const struct cfg80211_nan_peer_map *map1,
+ const struct cfg80211_nan_peer_map *map2,
+ struct cfg80211_nan_channel *nan_channels)
+{
+ /* Check for duplicate map_id */
+ if (map1->map_id == map2->map_id) {
+ NL_SET_ERR_MSG_FMT(info->extack, "Duplicate map_id %u",
+ map1->map_id);
+ return -EINVAL;
+ }
+
+ /* Check for compatible channels between maps */
+ for (int i = 0; i < ARRAY_SIZE(map1->schedule); i++) {
+ if (map1->schedule[i] == NL80211_NAN_SCHED_NOT_AVAIL_SLOT)
+ continue;
+
+ for (int j = 0; j < ARRAY_SIZE(map2->schedule); j++) {
+ u8 ch1 = map1->schedule[i];
+ u8 ch2 = map2->schedule[j];
+
+ if (ch2 == NL80211_NAN_SCHED_NOT_AVAIL_SLOT)
+ continue;
+
+ if (cfg80211_chandef_compatible(&nan_channels[ch1].chandef,
+ &nan_channels[ch2].chandef)) {
+ NL_SET_ERR_MSG_FMT(info->extack,
+ "Maps %u and %u have compatible channels %d and %d",
+ map1->map_id, map2->map_id,
+ ch1, ch2);
+ return -EINVAL;
+ }
+ }
+ }
+
+ /*
+ * Check for conflicting time slots between maps.
+ * Only check for single-radio devices (n_radio <= 1) which cannot
+ * operate on multiple channels simultaneously.
+ */
+ if (wiphy->n_radio > 1)
+ return 0;
+
+ for (int i = 0; i < ARRAY_SIZE(map1->schedule); i++) {
+ if (map1->schedule[i] != NL80211_NAN_SCHED_NOT_AVAIL_SLOT &&
+ map2->schedule[i] != NL80211_NAN_SCHED_NOT_AVAIL_SLOT) {
+ NL_SET_ERR_MSG_FMT(info->extack,
+ "Maps %u and %u both schedule slot %d",
+ map1->map_id, map2->map_id, i);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int nl80211_nan_set_peer_sched(struct sk_buff *skb,
+ struct genl_info *info)
+{
+ struct cfg80211_registered_device *rdev = info->user_ptr[0];
+ struct cfg80211_nan_channel *nan_channels __free(kfree) = NULL;
+ struct cfg80211_nan_peer_sched sched = {};
+ struct wireless_dev *wdev = info->user_ptr[1];
+ struct nlattr *map_attr, *channel;
+ int ret, n_maps = 0, n_channels = 0, i = 0, rem;
+
+ if (wdev->iftype != NL80211_IFTYPE_NAN)
+ return -EOPNOTSUPP;
+
+ if (!info->attrs[NL80211_ATTR_MAC] ||
+ !info->attrs[NL80211_ATTR_NAN_COMMITTED_DW]) {
+ NL_SET_ERR_MSG(info->extack,
+ "Required NAN peer schedule attributes are missing");
+ return -EINVAL;
+ }
+
+ /* First count how many channel attributes we got */
+ nlmsg_for_each_attr_type(channel, NL80211_ATTR_NAN_CHANNEL,
+ info->nlhdr, GENL_HDRLEN, rem)
+ n_channels++;
+
+ if (!((info->attrs[NL80211_ATTR_NAN_SEQ_ID] &&
+ info->attrs[NL80211_ATTR_NAN_PEER_MAPS] && n_channels) ||
+ ((!info->attrs[NL80211_ATTR_NAN_SEQ_ID] &&
+ !info->attrs[NL80211_ATTR_NAN_PEER_MAPS] && !n_channels)))) {
+ NL_SET_ERR_MSG(info->extack,
+ "Either provide all of: seq id, channels and maps, or none");
+ return -EINVAL;
+ }
+
+ /*
+ * Limit the number of peer channels to:
+ * local_channels * 4 (possible BWs) * 2 (possible NSS values)
+ */
+ if (n_channels && n_channels > wdev->u.nan.n_channels * 4 * 2) {
+ NL_SET_ERR_MSG_FMT(info->extack,
+ "Too many peer channels: %d (max %d)",
+ n_channels,
+ wdev->u.nan.n_channels * 4 * 2);
+ return -EINVAL;
+ }
+
+ if (n_channels) {
+ nan_channels = kcalloc(n_channels, sizeof(*nan_channels),
+ GFP_KERNEL);
+ if (!nan_channels)
+ return -ENOMEM;
+ }
+
+ /* Parse peer channels */
+ nlmsg_for_each_attr_type(channel, NL80211_ATTR_NAN_CHANNEL,
+ info->nlhdr, GENL_HDRLEN, rem) {
+ bool compatible = false;
+
+ ret = nl80211_parse_nan_channel(rdev, channel, info,
+ nan_channels, i, false);
+ if (ret)
+ return ret;
+
+ /* Verify channel is compatible with at least one local channel */
+ for (int j = 0; j < wdev->u.nan.n_channels; j++) {
+ if (cfg80211_chandef_compatible(&nan_channels[i].chandef,
+ &wdev->u.nan.chandefs[j])) {
+ compatible = true;
+ break;
+ }
+ }
+ if (!compatible) {
+ NL_SET_ERR_MSG_FMT(info->extack,
+ "Channel %d not compatible with any local channel",
+ i);
+ return -EINVAL;
+ }
+ i++;
+ }
+
+ sched.n_channels = n_channels;
+ sched.nan_channels = nan_channels;
+ sched.peer_addr = nla_data(info->attrs[NL80211_ATTR_MAC]);
+ sched.seq_id = nla_get_u8_default(info->attrs[NL80211_ATTR_NAN_SEQ_ID], 0);
+ sched.committed_dw = nla_get_u16(info->attrs[NL80211_ATTR_NAN_COMMITTED_DW]);
+ sched.max_chan_switch =
+ nla_get_u16_default(info->attrs[NL80211_ATTR_NAN_MAX_CHAN_SWITCH_TIME], 0);
+
+ if (info->attrs[NL80211_ATTR_NAN_ULW]) {
+ sched.ulw_size = nla_len(info->attrs[NL80211_ATTR_NAN_ULW]);
+ sched.init_ulw = nla_data(info->attrs[NL80211_ATTR_NAN_ULW]);
+ }
+
+ /* Initialize all maps as invalid */
+ for (int j = 0; j < ARRAY_SIZE(sched.maps); j++)
+ sched.maps[j].map_id = CFG80211_NAN_INVALID_MAP_ID;
+
+ if (info->attrs[NL80211_ATTR_NAN_PEER_MAPS]) {
+ /* Parse each map */
+ nla_for_each_nested(map_attr, info->attrs[NL80211_ATTR_NAN_PEER_MAPS],
+ rem) {
+ if (n_maps >= ARRAY_SIZE(sched.maps)) {
+ NL_SET_ERR_MSG(info->extack, "Too many peer maps");
+ return -EINVAL;
+ }
+
+ ret = nl80211_parse_nan_peer_map(info, map_attr,
+ &sched.maps[n_maps],
+ n_channels);
+ if (ret)
+ return ret;
+
+ /* Validate against previous maps */
+ for (int j = 0; j < n_maps; j++) {
+ ret = nl80211_nan_validate_map_pair(&rdev->wiphy, info,
+ &sched.maps[j],
+ &sched.maps[n_maps],
+ nan_channels);
+ if (ret)
+ return ret;
+ }
+
+ n_maps++;
+ }
+ }
+
+ /* Verify each channel is scheduled at least once */
+ for (int ch = 0; ch < n_channels; ch++) {
+ bool scheduled = false;
+
+ for (int m = 0; m < n_maps && !scheduled; m++) {
+ for (int s = 0; s < ARRAY_SIZE(sched.maps[m].schedule); s++) {
+ if (sched.maps[m].schedule[s] == ch) {
+ scheduled = true;
+ break;
+ }
+ }
+ }
+ if (!scheduled) {
+ NL_SET_ERR_MSG_FMT(info->extack,
+ "Channel %d is not scheduled in any map",
+ ch);
+ return -EINVAL;
+ }
+ }
+
+ return rdev_nan_set_peer_sched(rdev, wdev, &sched);
+}
+
static bool nl80211_nan_is_sched_empty(struct cfg80211_nan_local_sched *sched)
{
if (!sched->n_channels)
@@ -16748,7 +17083,7 @@ static int nl80211_nan_set_local_sched(struct sk_buff *skb,
struct cfg80211_registered_device *rdev = info->user_ptr[0];
struct cfg80211_nan_local_sched *sched __free(kfree) = NULL;
struct wireless_dev *wdev = info->user_ptr[1];
- int rem, i = 0, n_channels = 0;
+ int rem, i = 0, n_channels = 0, ret;
struct nlattr *channel;
bool sched_empty;
@@ -16775,26 +17110,20 @@ static int nl80211_nan_set_local_sched(struct sk_buff *skb,
nlmsg_for_each_attr_type(channel, NL80211_ATTR_NAN_CHANNEL,
info->nlhdr, GENL_HDRLEN, rem) {
- int ret = nl80211_parse_nan_channel(rdev, channel, info, sched,
- i);
+ ret = nl80211_parse_nan_channel(rdev, channel, info,
+ sched->nan_channels, i, true);
if (ret)
return ret;
i++;
}
- memcpy(sched->schedule,
- nla_data(info->attrs[NL80211_ATTR_NAN_TIME_SLOTS]),
- nla_len(info->attrs[NL80211_ATTR_NAN_TIME_SLOTS]));
-
- for (int slot = 0; slot < ARRAY_SIZE(sched->schedule); slot++) {
- if (sched->schedule[slot] != NL80211_NAN_SCHED_NOT_AVAIL_SLOT &&
- sched->schedule[slot] >= sched->n_channels) {
- NL_SET_ERR_MSG(info->extack,
- "Invalid time slot in NAN schedule");
- return -EINVAL;
- }
- }
+ /* Parse and validate schedule */
+ ret = nl80211_parse_nan_schedule(info,
+ info->attrs[NL80211_ATTR_NAN_TIME_SLOTS],
+ sched->schedule, sched->n_channels);
+ if (ret)
+ return ret;
sched_empty = nl80211_nan_is_sched_empty(sched);
@@ -19646,6 +19975,12 @@ static const struct genl_small_ops nl80211_small_ops[] = {
.flags = GENL_ADMIN_PERM,
.internal_flags = IFLAGS(NL80211_FLAG_NEED_WDEV_UP),
},
+ {
+ .cmd = NL80211_CMD_NAN_SET_PEER_SCHED,
+ .doit = nl80211_nan_set_peer_sched,
+ .flags = GENL_ADMIN_PERM,
+ .internal_flags = IFLAGS(NL80211_FLAG_NEED_WDEV_UP),
+ },
};
static struct genl_family nl80211_fam __ro_after_init = {
diff --git a/net/wireless/rdev-ops.h b/net/wireless/rdev-ops.h
index b886dedb25c6..bba239a068f6 100644
--- a/net/wireless/rdev-ops.h
+++ b/net/wireless/rdev-ops.h
@@ -1076,6 +1076,22 @@ rdev_nan_set_local_sched(struct cfg80211_registered_device *rdev,
return ret;
}
+static inline int
+rdev_nan_set_peer_sched(struct cfg80211_registered_device *rdev,
+ struct wireless_dev *wdev,
+ struct cfg80211_nan_peer_sched *sched)
+{
+ int ret;
+
+ trace_rdev_nan_set_peer_sched(&rdev->wiphy, wdev, sched);
+ if (rdev->ops->nan_set_peer_sched)
+ ret = rdev->ops->nan_set_peer_sched(&rdev->wiphy, wdev, sched);
+ else
+ ret = -EOPNOTSUPP;
+ trace_rdev_return_int(&rdev->wiphy, ret);
+ return ret;
+}
+
static inline int rdev_set_mac_acl(struct cfg80211_registered_device *rdev,
struct net_device *dev,
struct cfg80211_acl_data *params)
diff --git a/net/wireless/trace.h b/net/wireless/trace.h
index d32b83439363..df639d97cc0c 100644
--- a/net/wireless/trace.h
+++ b/net/wireless/trace.h
@@ -2431,6 +2431,34 @@ TRACE_EVENT(rdev_nan_set_local_sched,
CFG80211_NAN_SCHED_NUM_TIME_SLOTS, 1))
);
+TRACE_EVENT(rdev_nan_set_peer_sched,
+ TP_PROTO(struct wiphy *wiphy, struct wireless_dev *wdev,
+ struct cfg80211_nan_peer_sched *sched),
+ TP_ARGS(wiphy, wdev, sched),
+ TP_STRUCT__entry(
+ WIPHY_ENTRY
+ WDEV_ENTRY
+ __array(u8, peer_addr, ETH_ALEN)
+ __field(u8, seq_id)
+ __field(u16, committed_dw)
+ __field(u16, max_chan_switch)
+ ),
+ TP_fast_assign(
+ WIPHY_ASSIGN;
+ WDEV_ASSIGN;
+ memcpy(__entry->peer_addr, sched->peer_addr, ETH_ALEN);
+ __entry->seq_id = sched->seq_id;
+ __entry->committed_dw = sched->committed_dw;
+ __entry->max_chan_switch = sched->max_chan_switch;
+ ),
+ TP_printk(WIPHY_PR_FMT ", " WDEV_PR_FMT
+ ", peer: %pM, seq_id: %u, committed_dw: 0x%x, max_chan_switch: %u",
+ WIPHY_PR_ARG, WDEV_PR_ARG, __entry->peer_addr,
+ __entry->seq_id, __entry->committed_dw,
+ __entry->max_chan_switch
+ )
+);
+
TRACE_EVENT(rdev_set_mac_acl,
TP_PROTO(struct wiphy *wiphy, struct net_device *netdev,
struct cfg80211_acl_data *params),
--
2.34.1
^ permalink raw reply related
* [PATCH v5 wireless-next 05/12] wifi: nl80211: add support for NAN stations
From: Miri Korenblit @ 2026-03-18 12:39 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260318123926.206536-1-miriam.rachel.korenblit@intel.com>
There are 2 types of logical links with a NAN peer:
- management (NMI), which is used for Tx/Rx of NAN management frames.
- data (NDI), which is used for Tx/Rx of data frames, or non-NAN
management frames.
The NMI station has two roles:
- representation of the NAN peer - for example, the peer's schedule
and the HT, VHT, HE capabilities - belong to the NMI station, and not to
the NDI ones.
- Tx/Rx of NAN management frames to/from the peer.
The NDI station is used for Tx/Rx data frames of a specific NDP that was
established with the NAN peer.
Note that a peer can choose to reuse its NMI address as the NDI address.
In that case, it is expected that two stations will be added even though
they will have the same address.
- An NDI station can only be added after the corresponding NMI station
was configured with capabilities.
- All the NDI stations will be removed before the NDI interface is brought
down.
- All NMI stations will be removed before NAN is stopped.
- Before NMI sta removal, all corresponding NDI stations will be removed
Add support for adding, removing, and changing NMI and NDI stations.
Reviewed-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Link: https://patch.msgid.link/20260219114327.d280936ee832.I6d859eee759bb5824a9ffd2984410faf879ba00e@changeid
---
include/net/cfg80211.h | 56 ++++++++++++++++
include/uapi/linux/nl80211.h | 8 ++-
net/wireless/nl80211.c | 120 ++++++++++++++++++++++++++++-------
3 files changed, 161 insertions(+), 23 deletions(-)
diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index 60cd0fbe9a46..654d71f60e8c 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -1831,6 +1831,7 @@ struct cfg80211_ttlm_params {
* @eml_cap: EML capabilities of this station
* @link_sta_params: link related params.
* @epp_peer: EPP peer indication
+ * @nmi_mac: MAC address of the NMI station of the NAN peer
*/
struct station_parameters {
struct net_device *vlan;
@@ -1858,6 +1859,7 @@ struct station_parameters {
u16 eml_cap;
struct link_station_parameters link_sta_params;
bool epp_peer;
+ const u8 *nmi_mac;
};
/**
@@ -1897,6 +1899,8 @@ struct station_del_parameters {
* entry that is operating, has been marked authorized by userspace)
* @CFG80211_STA_MESH_PEER_KERNEL: peer on mesh interface (kernel managed)
* @CFG80211_STA_MESH_PEER_USER: peer on mesh interface (user managed)
+ * @CFG80211_STA_NAN_MGMT: NAN management interface station
+ * @CFG80211_STA_NAN_DATA: NAN data path station
*/
enum cfg80211_station_type {
CFG80211_STA_AP_CLIENT,
@@ -1908,6 +1912,8 @@ enum cfg80211_station_type {
CFG80211_STA_TDLS_PEER_ACTIVE,
CFG80211_STA_MESH_PEER_KERNEL,
CFG80211_STA_MESH_PEER_USER,
+ CFG80211_STA_NAN_MGMT,
+ CFG80211_STA_NAN_DATA,
};
/**
@@ -3999,6 +4005,56 @@ struct cfg80211_qos_map {
*
* The local schedule specifies which channels the device is available on and
* when. Must be cancelled before NAN is stopped.
+ *
+ * NAN Stations
+ * ~~~~~~~~~~~~
+ *
+ * There are two types of stations corresponding to the two interface types:
+ *
+ * - NMI station: Represents the NAN peer. Peer-specific data such as the peer's
+ * schedule and the HT, VHT and HE capabilities belongs to the NMI station.
+ * Also used for Tx/Rx of NAN management frames to/from the peer.
+ * Added on the %NL80211_IFTYPE_NAN interface.
+ *
+ * - NDI station: Used for Tx/Rx of data frames (and non-NAN management frames)
+ * for a specific NDP established with the NAN peer. Added on the
+ * %NL80211_IFTYPE_NAN_DATA interface.
+ *
+ * A peer may reuse its NMI address as the NDI address. In that case, two
+ * separate stations should be added even though they share the same MAC
+ * address.
+ *
+ * HT, VHT and HE capabilities should not changes after it was set. It is the
+ * driver's responsibility to check that.
+ *
+ * An NDI station can only be added if the corresponding NMI station has already
+ * been configured with HT (and possibly VHT and HE) capabilities. It is the
+ * driver's responsibility to check that.
+ *
+ * All NDI stations must be removed before corresponding NMI station is removed.
+ * Therefore, removing a NMI station implies that the associated NDI station(s)
+ * (if any) will be removed first.
+ *
+ * NAN Dependencies
+ * ~~~~~~~~~~~~~~~~
+ *
+ * The following diagram shows the dependencies between NAN components.
+ * An arrow from A to B means A must be started/added before B, and B must be
+ * stopped/removed before A:
+ *
+ * +-------------+
+ * | NMI iface |---(local schedule)
+ * +------+------+
+ * / \
+ * v v
+ * +-----------+ +-------------+
+ * | NDI iface | | NMI sta |---(peer schedule)
+ * +-----+-----+ +------+------+
+ * \ /
+ * v v
+ * +----------+
+ * | NDI sta |
+ * +----------+
*/
/**
diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h
index c94e957a3467..1897b9a35be8 100644
--- a/include/uapi/linux/nl80211.h
+++ b/include/uapi/linux/nl80211.h
@@ -2677,7 +2677,8 @@ enum nl80211_commands {
* a flow is assigned on each round of the DRR scheduler.
* @NL80211_ATTR_HE_CAPABILITY: HE Capability information element (from
* association request when used with NL80211_CMD_NEW_STATION). Can be set
- * only if %NL80211_STA_FLAG_WME is set.
+ * only if %NL80211_STA_FLAG_WME is set (except for NAN, which uses WME
+ * anyway).
*
* @NL80211_ATTR_FTM_RESPONDER: nested attribute which user-space can include
* in %NL80211_CMD_START_AP or %NL80211_CMD_SET_BEACON for fine timing
@@ -3057,6 +3058,9 @@ enum nl80211_commands {
* %NL80211_CMD_NAN_SCHED_UPDATE_DONE to indicate that the deferred
* schedule update completed successfully. If this flag is not present,
* the update failed.
+ * @NL80211_ATTR_NAN_NMI_MAC: The address of the NMI station to which this NDI
+ * station belongs. Used with %NL80211_CMD_NEW_STATION when adding an NDI
+ * station.
*
* @NL80211_ATTR_INCUMBENT_SIGNAL_INTERFERENCE_BITMAP: u32 attribute specifying
* the signal interference bitmap detected on the operating bandwidth for
@@ -3656,6 +3660,8 @@ enum nl80211_attrs {
NL80211_ATTR_NAN_SCHED_DEFERRED,
NL80211_ATTR_NAN_SCHED_UPDATE_SUCCESS,
+ NL80211_ATTR_NAN_NMI_MAC,
+
/* add attributes here, update the policy in nl80211.c */
__NL80211_ATTR_AFTER_LAST,
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 07ec0621d4de..b1e2303f0ef3 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -1004,6 +1004,7 @@ static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = {
[NL80211_ATTR_NAN_AVAIL_BLOB] =
NLA_POLICY_VALIDATE_FN(NLA_BINARY, validate_nan_avail_blob),
[NL80211_ATTR_NAN_SCHED_DEFERRED] = { .type = NLA_FLAG },
+ [NL80211_ATTR_NAN_NMI_MAC] = NLA_POLICY_ETH_ADDR,
};
/* policy for the key attributes */
@@ -7233,6 +7234,26 @@ static int parse_station_flags(struct genl_info *info,
if ((params->sta_flags_mask |
params->sta_flags_set) & BIT(__NL80211_STA_FLAG_INVALID))
return -EINVAL;
+
+ if ((iftype == NL80211_IFTYPE_NAN ||
+ iftype == NL80211_IFTYPE_NAN_DATA) &&
+ params->sta_flags_mask &
+ ~(BIT(NL80211_STA_FLAG_AUTHENTICATED) |
+ BIT(NL80211_STA_FLAG_ASSOCIATED) |
+ BIT(NL80211_STA_FLAG_AUTHORIZED) |
+ BIT(NL80211_STA_FLAG_MFP)))
+ return -EINVAL;
+
+ /* WME is always used in NAN */
+ if (iftype == NL80211_IFTYPE_NAN_DATA) {
+ /* but don't let userspace control it */
+ if (params->sta_flags_mask & BIT(NL80211_STA_FLAG_WME))
+ return -EINVAL;
+
+ params->sta_flags_mask |= BIT(NL80211_STA_FLAG_WME);
+ params->sta_flags_set |= BIT(NL80211_STA_FLAG_WME);
+ }
+
return 0;
}
@@ -8115,7 +8136,7 @@ static int nl80211_dump_station(struct sk_buff *skb,
/* nl80211_prepare_wdev_dump acquired it in the successful case */
__acquire(&rdev->wiphy.mtx);
- if (!wdev->netdev) {
+ if (!wdev->netdev && wdev->iftype != NL80211_IFTYPE_NAN) {
err = -EINVAL;
goto out_err;
}
@@ -8302,10 +8323,12 @@ int cfg80211_check_station_change(struct wiphy *wiphy,
return -EINVAL;
if (params->link_sta_params.supported_rates)
return -EINVAL;
- if (params->ext_capab || params->link_sta_params.ht_capa ||
- params->link_sta_params.vht_capa ||
- params->link_sta_params.he_capa ||
- params->link_sta_params.eht_capa ||
+ if (statype != CFG80211_STA_NAN_MGMT &&
+ (params->link_sta_params.ht_capa ||
+ params->link_sta_params.vht_capa ||
+ params->link_sta_params.he_capa))
+ return -EINVAL;
+ if (params->ext_capab || params->link_sta_params.eht_capa ||
params->link_sta_params.uhr_capa)
return -EINVAL;
if (params->sta_flags_mask & BIT(NL80211_STA_FLAG_SPP_AMSDU))
@@ -8377,6 +8400,19 @@ int cfg80211_check_station_change(struct wiphy *wiphy,
params->plink_action != NL80211_PLINK_ACTION_BLOCK)
return -EINVAL;
break;
+ case CFG80211_STA_NAN_MGMT:
+ if (params->sta_flags_mask &
+ ~(BIT(NL80211_STA_FLAG_AUTHORIZED) |
+ BIT(NL80211_STA_FLAG_MFP)))
+ return -EINVAL;
+ break;
+ case CFG80211_STA_NAN_DATA:
+ if (params->sta_flags_mask &
+ ~(BIT(NL80211_STA_FLAG_AUTHORIZED) |
+ BIT(NL80211_STA_FLAG_MFP) |
+ BIT(NL80211_STA_FLAG_WME)))
+ return -EINVAL;
+ break;
}
/*
@@ -8591,7 +8627,8 @@ static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info)
memset(¶ms, 0, sizeof(params));
- if (!dev)
+ if (!dev && wdev->iftype != NL80211_IFTYPE_NAN &&
+ wdev->iftype != NL80211_IFTYPE_NAN_DATA)
return -EINVAL;
if (!rdev->ops->change_station)
@@ -8734,6 +8771,8 @@ static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info)
case NL80211_IFTYPE_STATION:
case NL80211_IFTYPE_ADHOC:
case NL80211_IFTYPE_MESH_POINT:
+ case NL80211_IFTYPE_NAN:
+ case NL80211_IFTYPE_NAN_DATA:
break;
default:
err = -EOPNOTSUPP;
@@ -8762,7 +8801,7 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info)
memset(¶ms, 0, sizeof(params));
- if (!dev)
+ if (!dev && wdev->iftype != NL80211_IFTYPE_NAN)
return -EINVAL;
if (!rdev->ops->add_station)
@@ -8771,15 +8810,31 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info)
if (!info->attrs[NL80211_ATTR_MAC])
return -EINVAL;
- if (!info->attrs[NL80211_ATTR_STA_LISTEN_INTERVAL])
- return -EINVAL;
+ if (wdev->iftype == NL80211_IFTYPE_NAN ||
+ wdev->iftype == NL80211_IFTYPE_NAN_DATA) {
+ if (info->attrs[NL80211_ATTR_STA_SUPPORTED_RATES])
+ return -EINVAL;
+ if (wdev->iftype == NL80211_IFTYPE_NAN_DATA) {
+ if (!info->attrs[NL80211_ATTR_NAN_NMI_MAC])
+ return -EINVAL;
- if (!info->attrs[NL80211_ATTR_STA_SUPPORTED_RATES])
- return -EINVAL;
+ /* Only NMI stations receive the HT/VHT/HE capabilities */
+ if (info->attrs[NL80211_ATTR_HT_CAPABILITY] ||
+ info->attrs[NL80211_ATTR_VHT_CAPABILITY] ||
+ info->attrs[NL80211_ATTR_HE_CAPABILITY])
+ return -EINVAL;
+ }
+ } else {
+ if (!info->attrs[NL80211_ATTR_STA_LISTEN_INTERVAL])
+ return -EINVAL;
- if (!info->attrs[NL80211_ATTR_STA_AID] &&
- !info->attrs[NL80211_ATTR_PEER_AID])
- return -EINVAL;
+ if (!info->attrs[NL80211_ATTR_STA_SUPPORTED_RATES])
+ return -EINVAL;
+
+ if (!info->attrs[NL80211_ATTR_STA_AID] &&
+ !info->attrs[NL80211_ATTR_PEER_AID])
+ return -EINVAL;
+ }
params.link_sta_params.link_id =
nl80211_link_id_or_invalid(info->attrs);
@@ -8795,12 +8850,16 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info)
mac_addr = nla_data(info->attrs[NL80211_ATTR_MAC]);
}
- params.link_sta_params.supported_rates =
- nla_data(info->attrs[NL80211_ATTR_STA_SUPPORTED_RATES]);
- params.link_sta_params.supported_rates_len =
- nla_len(info->attrs[NL80211_ATTR_STA_SUPPORTED_RATES]);
- params.listen_interval =
- nla_get_u16(info->attrs[NL80211_ATTR_STA_LISTEN_INTERVAL]);
+ if (info->attrs[NL80211_ATTR_STA_SUPPORTED_RATES]) {
+ params.link_sta_params.supported_rates =
+ nla_data(info->attrs[NL80211_ATTR_STA_SUPPORTED_RATES]);
+ params.link_sta_params.supported_rates_len =
+ nla_len(info->attrs[NL80211_ATTR_STA_SUPPORTED_RATES]);
+ }
+
+ if (info->attrs[NL80211_ATTR_STA_LISTEN_INTERVAL])
+ params.listen_interval =
+ nla_get_u16(info->attrs[NL80211_ATTR_STA_LISTEN_INTERVAL]);
if (info->attrs[NL80211_ATTR_VLAN_ID])
params.vlan_id = nla_get_u16(info->attrs[NL80211_ATTR_VLAN_ID]);
@@ -8819,7 +8878,7 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info)
if (info->attrs[NL80211_ATTR_PEER_AID])
params.aid = nla_get_u16(info->attrs[NL80211_ATTR_PEER_AID]);
- else
+ else if (info->attrs[NL80211_ATTR_STA_AID])
params.aid = nla_get_u16(info->attrs[NL80211_ATTR_STA_AID]);
if (info->attrs[NL80211_ATTR_STA_CAPABILITY]) {
@@ -8940,6 +8999,16 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info)
return -EINVAL;
}
+ if (wdev->iftype == NL80211_IFTYPE_NAN ||
+ wdev->iftype == NL80211_IFTYPE_NAN_DATA) {
+ if (params.sta_modify_mask & STATION_PARAM_APPLY_UAPSD)
+ return -EINVAL;
+ /* NAN NMI station must be added in associated or authorized state */
+ if (!(params.sta_flags_set & (BIT(NL80211_STA_FLAG_ASSOCIATED) |
+ BIT(NL80211_STA_FLAG_AUTHENTICATED))))
+ return -EINVAL;
+ }
+
/* Ensure that HT/VHT capabilities are not set for 6 GHz HE STA */
if (params.link_sta_params.he_6ghz_capa &&
(params.link_sta_params.ht_capa || params.link_sta_params.vht_capa))
@@ -9032,6 +9101,11 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info)
*/
params.sta_flags_mask &= ~BIT(NL80211_STA_FLAG_AUTHORIZED);
break;
+ case NL80211_IFTYPE_NAN:
+ break;
+ case NL80211_IFTYPE_NAN_DATA:
+ params.nmi_mac = nla_data(info->attrs[NL80211_ATTR_NAN_NMI_MAC]);
+ break;
default:
return -EOPNOTSUPP;
}
@@ -9073,7 +9147,7 @@ static int nl80211_del_station(struct sk_buff *skb, struct genl_info *info)
memset(¶ms, 0, sizeof(params));
- if (!dev)
+ if (!dev && wdev->iftype != NL80211_IFTYPE_NAN)
return -EINVAL;
if (info->attrs[NL80211_ATTR_MAC])
@@ -9084,6 +9158,8 @@ static int nl80211_del_station(struct sk_buff *skb, struct genl_info *info)
case NL80211_IFTYPE_AP_VLAN:
case NL80211_IFTYPE_MESH_POINT:
case NL80211_IFTYPE_P2P_GO:
+ case NL80211_IFTYPE_NAN:
+ case NL80211_IFTYPE_NAN_DATA:
/* always accept these */
break;
case NL80211_IFTYPE_ADHOC:
--
2.34.1
^ permalink raw reply related
* [PATCH v5 wireless-next 04/12] wifi: cfg80211: separately store HT, VHT and HE capabilities for NAN
From: Miri Korenblit @ 2026-03-18 12:39 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260318123926.206536-1-miriam.rachel.korenblit@intel.com>
In NAN, unlike in other modes, there is only one set of (HT, VHT, HE)
capabilities that is used for all channels (and bands) used in the NAN
data path.
This set of capabilities will have to be a special one, for example - have
the minimum of (HT-for-5 GHz, HT-for-2.4 GHz), careful handling of the
bits that have a different meaning for each band, etc.
While we could use the exiting sband/iftype capabilities, and require
identical capabilities for all bands (makes no sense since this means
that we will have VHT capabilities in the 2.4 GHz slot),
or require that only one of the sbands will be set,
or have logic to extract the minimum and handle the conflicting bits -
it seems simpler to add a dedicated set of capabilities which is special
for NAN, and is band agnostic, to be populated by the driver.
That way we also let the driver decide how it wants to handle the
conflicting bits.
Add this special set of these capabilities to wiphy:nan_capabilities, to be
populated by the driver.
Send it to user space.
Reviewed-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Link: https://patch.msgid.link/20260219114327.4b6f3e4a81b4.I45422adc0df3ad4101d857a92e83f0de5cf241e1@changeid
---
include/net/cfg80211.h | 11 ++++++
include/uapi/linux/nl80211.h | 43 ++++++++++++++++++++++++
net/wireless/core.c | 4 +++
net/wireless/nl80211.c | 65 ++++++++++++++++++++++++++++++++++++
4 files changed, 123 insertions(+)
diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index 1797ece50295..60cd0fbe9a46 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -5913,6 +5913,12 @@ enum wiphy_nan_flags {
* @max_channel_switch_time: maximum channel switch time in milliseconds.
* @dev_capabilities: NAN device capabilities as defined in Wi-Fi Aware (TM)
* specification Table 79 (Capabilities field).
+ * @phy: Band-agnostic capabilities for NAN data interfaces. Since NAN
+ * operates on multiple channels simultaneously, these capabilities apply
+ * across all bands. Valid only if NL80211_IFTYPE_NAN_DATA is supported.
+ * @phy.ht: HT capabilities (mandatory for NAN data)
+ * @phy.vht: VHT capabilities (optional)
+ * @phy.he: HE capabilities (optional)
*/
struct wiphy_nan_capa {
u32 flags;
@@ -5920,6 +5926,11 @@ struct wiphy_nan_capa {
u8 n_antennas;
u16 max_channel_switch_time;
u8 dev_capabilities;
+ struct {
+ struct ieee80211_sta_ht_cap ht;
+ struct ieee80211_sta_vht_cap vht;
+ struct ieee80211_sta_he_cap he;
+ } phy;
};
#define CFG80211_HW_TIMESTAMP_ALL_PEERS 0xffff
diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h
index 3984c176f9e7..c94e957a3467 100644
--- a/include/uapi/linux/nl80211.h
+++ b/include/uapi/linux/nl80211.h
@@ -4462,6 +4462,46 @@ enum nl80211_band_attr {
#define NL80211_BAND_ATTR_HT_CAPA NL80211_BAND_ATTR_HT_CAPA
+/**
+ * enum nl80211_nan_phy_cap_attr - NAN PHY capabilities attributes
+ * @__NL80211_NAN_PHY_CAP_ATTR_INVALID: attribute number 0 is reserved
+ * @NL80211_NAN_PHY_CAP_ATTR_HT_MCS_SET: 16-byte attribute containing HT MCS set
+ * @NL80211_NAN_PHY_CAP_ATTR_HT_CAPA: HT capabilities (u16)
+ * @NL80211_NAN_PHY_CAP_ATTR_HT_AMPDU_FACTOR: HT A-MPDU factor (u8)
+ * @NL80211_NAN_PHY_CAP_ATTR_HT_AMPDU_DENSITY: HT A-MPDU density (u8)
+ * @NL80211_NAN_PHY_CAP_ATTR_VHT_MCS_SET: 8-byte attribute containing VHT MCS set
+ * @NL80211_NAN_PHY_CAP_ATTR_VHT_CAPA: VHT capabilities (u32)
+ * @NL80211_NAN_PHY_CAP_ATTR_HE_MAC: HE MAC capabilities
+ * @NL80211_NAN_PHY_CAP_ATTR_HE_PHY: HE PHY capabilities
+ * @NL80211_NAN_PHY_CAP_ATTR_HE_MCS_SET: HE supported NSS/MCS combinations
+ * @NL80211_NAN_PHY_CAP_ATTR_HE_PPE: HE PPE thresholds
+ * @NL80211_NAN_PHY_CAP_ATTR_MAX: highest NAN PHY cap attribute number
+ * @__NL80211_NAN_PHY_CAP_ATTR_AFTER_LAST: internal use
+ */
+enum nl80211_nan_phy_cap_attr {
+ __NL80211_NAN_PHY_CAP_ATTR_INVALID,
+
+ /* HT capabilities */
+ NL80211_NAN_PHY_CAP_ATTR_HT_MCS_SET,
+ NL80211_NAN_PHY_CAP_ATTR_HT_CAPA,
+ NL80211_NAN_PHY_CAP_ATTR_HT_AMPDU_FACTOR,
+ NL80211_NAN_PHY_CAP_ATTR_HT_AMPDU_DENSITY,
+
+ /* VHT capabilities */
+ NL80211_NAN_PHY_CAP_ATTR_VHT_MCS_SET,
+ NL80211_NAN_PHY_CAP_ATTR_VHT_CAPA,
+
+ /* HE capabilities */
+ NL80211_NAN_PHY_CAP_ATTR_HE_MAC,
+ NL80211_NAN_PHY_CAP_ATTR_HE_PHY,
+ NL80211_NAN_PHY_CAP_ATTR_HE_MCS_SET,
+ NL80211_NAN_PHY_CAP_ATTR_HE_PPE,
+
+ /* keep last */
+ __NL80211_NAN_PHY_CAP_ATTR_AFTER_LAST,
+ NL80211_NAN_PHY_CAP_ATTR_MAX = __NL80211_NAN_PHY_CAP_ATTR_AFTER_LAST - 1
+};
+
/**
* enum nl80211_wmm_rule - regulatory wmm rule
*
@@ -8635,6 +8675,8 @@ enum nl80211_s1g_short_beacon_attrs {
* @NL80211_NAN_CAPA_CAPABILITIES: u8 attribute containing the
* capabilities of the device as defined in Wi-Fi Aware (TM)
* specification Table 79 (Capabilities field).
+ * @NL80211_NAN_CAPA_PHY: nested attribute containing band-agnostic
+ * capabilities for NAN data path. See &enum nl80211_nan_phy_cap_attr.
* @__NL80211_NAN_CAPABILITIES_LAST: Internal
* @NL80211_NAN_CAPABILITIES_MAX: Highest NAN capability attribute.
*/
@@ -8647,6 +8689,7 @@ enum nl80211_nan_capabilities {
NL80211_NAN_CAPA_NUM_ANTENNAS,
NL80211_NAN_CAPA_MAX_CHANNEL_SWITCH_TIME,
NL80211_NAN_CAPA_CAPABILITIES,
+ NL80211_NAN_CAPA_PHY,
/* keep last */
__NL80211_NAN_CAPABILITIES_LAST,
NL80211_NAN_CAPABILITIES_MAX = __NL80211_NAN_CAPABILITIES_LAST - 1,
diff --git a/net/wireless/core.c b/net/wireless/core.c
index 200b97f912eb..6783e0672dcb 100644
--- a/net/wireless/core.c
+++ b/net/wireless/core.c
@@ -835,6 +835,10 @@ int wiphy_register(struct wiphy *wiphy)
!(wiphy->nan_supported_bands & BIT(NL80211_BAND_2GHZ)))))
return -EINVAL;
+ if (WARN_ON((wiphy->interface_modes & BIT(NL80211_IFTYPE_NAN_DATA)) &&
+ !wiphy->nan_capa.phy.ht.ht_supported))
+ return -EINVAL;
+
if (WARN_ON(wiphy->interface_modes & BIT(NL80211_IFTYPE_WDS)))
return -EINVAL;
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index b3c68d281c3f..07ec0621d4de 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -2721,6 +2721,68 @@ static int nl80211_put_radios(struct wiphy *wiphy, struct sk_buff *msg)
return -ENOBUFS;
}
+static int nl80211_put_nan_phy_cap(struct wiphy *wiphy, struct sk_buff *msg)
+{
+ struct nlattr *nl_phy_cap;
+ const struct ieee80211_sta_ht_cap *ht_cap;
+ const struct ieee80211_sta_vht_cap *vht_cap;
+ const struct ieee80211_sta_he_cap *he_cap;
+
+ if (!cfg80211_iftype_allowed(wiphy, NL80211_IFTYPE_NAN_DATA, false, 0))
+ return 0;
+
+ ht_cap = &wiphy->nan_capa.phy.ht;
+ vht_cap = &wiphy->nan_capa.phy.vht;
+ he_cap = &wiphy->nan_capa.phy.he;
+
+ /* HT is mandatory */
+ if (WARN_ON(!ht_cap->ht_supported))
+ return 0;
+
+ nl_phy_cap = nla_nest_start_noflag(msg, NL80211_NAN_CAPA_PHY);
+ if (!nl_phy_cap)
+ return -ENOBUFS;
+
+ if (nla_put(msg, NL80211_NAN_PHY_CAP_ATTR_HT_MCS_SET,
+ sizeof(ht_cap->mcs), &ht_cap->mcs) ||
+ nla_put_u16(msg, NL80211_NAN_PHY_CAP_ATTR_HT_CAPA, ht_cap->cap) ||
+ nla_put_u8(msg, NL80211_NAN_PHY_CAP_ATTR_HT_AMPDU_FACTOR,
+ ht_cap->ampdu_factor) ||
+ nla_put_u8(msg, NL80211_NAN_PHY_CAP_ATTR_HT_AMPDU_DENSITY,
+ ht_cap->ampdu_density))
+ goto fail;
+
+ if (vht_cap->vht_supported) {
+ if (nla_put(msg, NL80211_NAN_PHY_CAP_ATTR_VHT_MCS_SET,
+ sizeof(vht_cap->vht_mcs), &vht_cap->vht_mcs) ||
+ nla_put_u32(msg, NL80211_NAN_PHY_CAP_ATTR_VHT_CAPA,
+ vht_cap->cap))
+ goto fail;
+ }
+
+ if (he_cap->has_he) {
+ if (nla_put(msg, NL80211_NAN_PHY_CAP_ATTR_HE_MAC,
+ sizeof(he_cap->he_cap_elem.mac_cap_info),
+ he_cap->he_cap_elem.mac_cap_info) ||
+ nla_put(msg, NL80211_NAN_PHY_CAP_ATTR_HE_PHY,
+ sizeof(he_cap->he_cap_elem.phy_cap_info),
+ he_cap->he_cap_elem.phy_cap_info) ||
+ nla_put(msg, NL80211_NAN_PHY_CAP_ATTR_HE_MCS_SET,
+ sizeof(he_cap->he_mcs_nss_supp),
+ &he_cap->he_mcs_nss_supp) ||
+ nla_put(msg, NL80211_NAN_PHY_CAP_ATTR_HE_PPE,
+ sizeof(he_cap->ppe_thres), he_cap->ppe_thres))
+ goto fail;
+ }
+
+ nla_nest_end(msg, nl_phy_cap);
+ return 0;
+
+fail:
+ nla_nest_cancel(msg, nl_phy_cap);
+ return -ENOBUFS;
+}
+
static int nl80211_put_nan_capa(struct wiphy *wiphy, struct sk_buff *msg)
{
struct nlattr *nan_caps;
@@ -2747,6 +2809,9 @@ static int nl80211_put_nan_capa(struct wiphy *wiphy, struct sk_buff *msg)
wiphy->nan_capa.dev_capabilities))
goto fail;
+ if (nl80211_put_nan_phy_cap(wiphy, msg))
+ goto fail;
+
nla_nest_end(msg, nan_caps);
return 0;
--
2.34.1
^ permalink raw reply related
* [PATCH v5 wireless-next 03/12] wifi: cfg80211: add support for NAN data interface
From: Miri Korenblit @ 2026-03-18 12:39 UTC (permalink / raw)
To: linux-wireless
In-Reply-To: <20260318123926.206536-1-miriam.rachel.korenblit@intel.com>
This new interface type represents a NAN data interface (NDI).
It is used for data communication with NAN peers.
Note that the existing NL80211_IFTYPE_NAN interface, which is the NAN
Management Interface (NMI), is used for management communication.
An NDI interface is started when a new NAN data path is about to
be established, and is stopped after the NAN data path is terminated.
- An NDI interface can only be started if the NMI is running, and NAN is
started.
- Before the NMI is stopped, the NDI interfaces will be stopped.
Add the new interface type, handle add/remove operations for it,
and makes sure of the conditions above.
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Link: https://patch.msgid.link/20260219114327.0d681335c2e2.I92973483e927820ae2297853c141842fdb262747@changeid
---
include/net/cfg80211.h | 21 +++++++++++
include/uapi/linux/nl80211.h | 4 ++
net/mac80211/cfg.c | 1 +
net/mac80211/chan.c | 2 +
net/mac80211/iface.c | 3 ++
net/mac80211/rx.c | 2 +
net/mac80211/util.c | 1 +
net/wireless/chan.c | 2 +
net/wireless/core.c | 72 +++++++++++++++++++++++++++++++-----
net/wireless/core.h | 6 +++
net/wireless/nl80211.c | 14 ++++++-
net/wireless/reg.c | 12 ++++--
net/wireless/sysfs.c | 27 +++++++-------
net/wireless/util.c | 21 +++++++++--
14 files changed, 159 insertions(+), 29 deletions(-)
diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index 539dcf65c188..1797ece50295 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -3980,6 +3980,27 @@ struct cfg80211_qos_map {
struct cfg80211_dscp_range up[8];
};
+/**
+ * DOC: Neighbor Awareness Networking (NAN)
+ *
+ * NAN uses two interface types:
+ *
+ * - %NL80211_IFTYPE_NAN: a non-netdev interface. This has two roles: (1) holds
+ * the configuration of all NAN activities (DE parameters, synchronisation
+ * parameters, local schedule, etc.), and (2) uses as the NAN Management
+ * Interface (NMI), which is used for NAN management communication.
+ *
+ * - %NL80211_IFTYPE_NAN_DATA: The NAN Data Interface (NDI), used for data
+ * communication with NAN peers.
+ *
+ * An NDI interface can only be started (IFF_UP) if the NMI one is running and
+ * NAN is started. Before NAN is stopped, all associated NDI interfaces
+ * must be stopped first.
+ *
+ * The local schedule specifies which channels the device is available on and
+ * when. Must be cancelled before NAN is stopped.
+ */
+
/**
* struct cfg80211_nan_band_config - NAN band specific configuration
*
diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h
index 484094667abc..3984c176f9e7 100644
--- a/include/uapi/linux/nl80211.h
+++ b/include/uapi/linux/nl80211.h
@@ -3749,6 +3749,9 @@ enum nl80211_attrs {
* @NL80211_IFTYPE_OCB: Outside Context of a BSS
* This mode corresponds to the MIB variable dot11OCBActivated=true
* @NL80211_IFTYPE_NAN: NAN device interface type (not a netdev)
+ * @NL80211_IFTYPE_NAN_DATA: NAN data interface type (netdev); NAN data
+ * interfaces can only be brought up (IFF_UP) when a NAN interface
+ * already exists and NAN has been started (using %NL80211_CMD_START_NAN).
* @NL80211_IFTYPE_MAX: highest interface type number currently defined
* @NUM_NL80211_IFTYPES: number of defined interface types
*
@@ -3770,6 +3773,7 @@ enum nl80211_iftype {
NL80211_IFTYPE_P2P_DEVICE,
NL80211_IFTYPE_OCB,
NL80211_IFTYPE_NAN,
+ NL80211_IFTYPE_NAN_DATA,
/* keep last */
NUM_NL80211_IFTYPES,
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index ee64ac8e0f61..1fbcc773c3ed 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -718,6 +718,7 @@ static int ieee80211_add_key(struct wiphy *wiphy, struct wireless_dev *wdev,
case NL80211_IFTYPE_P2P_CLIENT:
case NL80211_IFTYPE_P2P_GO:
case NL80211_IFTYPE_OCB:
+ case NL80211_IFTYPE_NAN_DATA:
/* shouldn't happen */
WARN_ON_ONCE(1);
break;
diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
index 2f0c93f3ace6..95ebfad15512 100644
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -509,6 +509,7 @@ ieee80211_get_chanctx_max_required_bw(struct ieee80211_local *local,
case NUM_NL80211_IFTYPES:
case NL80211_IFTYPE_P2P_CLIENT:
case NL80211_IFTYPE_P2P_GO:
+ case NL80211_IFTYPE_NAN_DATA:
WARN_ON_ONCE(1);
}
@@ -1436,6 +1437,7 @@ ieee80211_link_chanctx_reservation_complete(struct ieee80211_link_data *link)
case NL80211_IFTYPE_P2P_GO:
case NL80211_IFTYPE_P2P_DEVICE:
case NL80211_IFTYPE_NAN:
+ case NL80211_IFTYPE_NAN_DATA:
case NUM_NL80211_IFTYPES:
WARN_ON(1);
break;
diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c
index 40ce0bb72726..02f464eb2cde 100644
--- a/net/mac80211/iface.c
+++ b/net/mac80211/iface.c
@@ -1369,6 +1369,7 @@ 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_UNSPECIFIED:
@@ -1940,6 +1941,8 @@ static void ieee80211_setup_sdata(struct ieee80211_sub_if_data *sdata,
case NL80211_IFTYPE_P2P_DEVICE:
sdata->vif.bss_conf.bssid = sdata->vif.addr;
break;
+ case NL80211_IFTYPE_NAN_DATA:
+ break;
case NL80211_IFTYPE_UNSPECIFIED:
case NL80211_IFTYPE_WDS:
case NUM_NL80211_IFTYPES:
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index 19c33f7a8193..d9a654ef082d 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -4607,6 +4607,8 @@ static bool ieee80211_accept_frame(struct ieee80211_rx_data *rx)
(ieee80211_is_public_action(hdr, skb->len) ||
(ieee80211_is_auth(hdr->frame_control) &&
ether_addr_equal(sdata->vif.addr, hdr->addr1)));
+ case NL80211_IFTYPE_NAN_DATA:
+ return false;
default:
break;
}
diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index 55054de62508..8987a4504520 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -2118,6 +2118,7 @@ int ieee80211_reconfig(struct ieee80211_local *local)
return res;
}
break;
+ case NL80211_IFTYPE_NAN_DATA:
case NL80211_IFTYPE_AP_VLAN:
case NL80211_IFTYPE_MONITOR:
case NL80211_IFTYPE_P2P_DEVICE:
diff --git a/net/wireless/chan.c b/net/wireless/chan.c
index fa0764ede9c5..d4eff81a9027 100644
--- a/net/wireless/chan.c
+++ b/net/wireless/chan.c
@@ -816,6 +816,7 @@ int cfg80211_chandef_dfs_required(struct wiphy *wiphy,
case NL80211_IFTYPE_MONITOR:
case NL80211_IFTYPE_AP_VLAN:
case NL80211_IFTYPE_P2P_DEVICE:
+ case NL80211_IFTYPE_NAN_DATA:
break;
case NL80211_IFTYPE_WDS:
case NL80211_IFTYPE_UNSPECIFIED:
@@ -939,6 +940,7 @@ bool cfg80211_beaconing_iface_active(struct wireless_dev *wdev)
case NL80211_IFTYPE_P2P_DEVICE:
/* Can NAN type be considered as beaconing interface? */
case NL80211_IFTYPE_NAN:
+ case NL80211_IFTYPE_NAN_DATA:
break;
case NL80211_IFTYPE_UNSPECIFIED:
case NL80211_IFTYPE_WDS:
diff --git a/net/wireless/core.c b/net/wireless/core.c
index 54c89b0db352..200b97f912eb 100644
--- a/net/wireless/core.c
+++ b/net/wireless/core.c
@@ -329,16 +329,21 @@ void cfg80211_shutdown_all_interfaces(struct wiphy *wiphy)
ASSERT_RTNL();
+ /*
+ * Some netdev interfaces need to be closed before some non-netdev
+ * ones, i.e. NAN_DATA interfaces need to be closed before the NAN
+ * interface
+ */
list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
if (wdev->netdev) {
dev_close(wdev->netdev);
continue;
}
+ }
- /* otherwise, check iftype */
-
- guard(wiphy)(wiphy);
+ guard(wiphy)(wiphy);
+ list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
switch (wdev->iftype) {
case NL80211_IFTYPE_P2P_DEVICE:
cfg80211_stop_p2p_device(rdev, wdev);
@@ -396,6 +401,8 @@ void cfg80211_destroy_ifaces(struct cfg80211_registered_device *rdev)
list_for_each_entry_safe(wdev, tmp, &rdev->wiphy.wdev_list, list) {
if (wdev->nl_owner_dead) {
+ cfg80211_close_dependents(rdev, wdev);
+
if (wdev->netdev)
dev_close(wdev->netdev);
@@ -406,6 +413,21 @@ void cfg80211_destroy_ifaces(struct cfg80211_registered_device *rdev)
}
}
+void cfg80211_close_dependents(struct cfg80211_registered_device *rdev,
+ struct wireless_dev *wdev)
+{
+ ASSERT_RTNL();
+
+ if (wdev->iftype != NL80211_IFTYPE_NAN)
+ return;
+
+ /* Close all NAN DATA interfaces */
+ list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
+ if (wdev->iftype == NL80211_IFTYPE_NAN_DATA)
+ dev_close(wdev->netdev);
+ }
+}
+
static void cfg80211_destroy_iface_wk(struct work_struct *work)
{
struct cfg80211_registered_device *rdev;
@@ -1419,9 +1441,8 @@ void cfg80211_update_iface_num(struct cfg80211_registered_device *rdev,
rdev->num_running_monitor_ifaces += num;
}
-void cfg80211_leave(struct cfg80211_registered_device *rdev,
- struct wireless_dev *wdev,
- int link_id)
+void cfg80211_leave_locked(struct cfg80211_registered_device *rdev,
+ struct wireless_dev *wdev, int link_id)
{
struct net_device *dev = wdev->netdev;
struct cfg80211_sched_scan_request *pos, *tmp;
@@ -1472,6 +1493,7 @@ void cfg80211_leave(struct cfg80211_registered_device *rdev,
break;
case NL80211_IFTYPE_AP_VLAN:
case NL80211_IFTYPE_MONITOR:
+ case NL80211_IFTYPE_NAN_DATA:
/* nothing to do */
break;
case NL80211_IFTYPE_UNSPECIFIED:
@@ -1482,6 +1504,19 @@ void cfg80211_leave(struct cfg80211_registered_device *rdev,
}
}
+void cfg80211_leave(struct cfg80211_registered_device *rdev,
+ struct wireless_dev *wdev, int link_id)
+{
+ ASSERT_RTNL();
+
+ /* NAN_DATA interfaces must be closed before stopping NAN */
+ cfg80211_close_dependents(rdev, wdev);
+
+ guard(wiphy)(&rdev->wiphy);
+
+ cfg80211_leave_locked(rdev, wdev, link_id);
+}
+
void cfg80211_stop_link(struct wiphy *wiphy, struct wireless_dev *wdev,
int link_id, gfp_t gfp)
{
@@ -1497,6 +1532,9 @@ void cfg80211_stop_link(struct wiphy *wiphy, struct wireless_dev *wdev,
trace_cfg80211_stop_link(wiphy, wdev, link_id);
+ if (wdev->iftype == NL80211_IFTYPE_NAN)
+ return;
+
ev = kzalloc_obj(*ev, gfp);
if (!ev)
return;
@@ -1647,10 +1685,9 @@ static int cfg80211_netdev_notifier_call(struct notifier_block *nb,
}
break;
case NETDEV_GOING_DOWN:
- scoped_guard(wiphy, &rdev->wiphy) {
- cfg80211_leave(rdev, wdev, -1);
+ cfg80211_leave(rdev, wdev, -1);
+ scoped_guard(wiphy, &rdev->wiphy)
cfg80211_remove_links(wdev);
- }
/* since we just did cfg80211_leave() nothing to do there */
cancel_work_sync(&wdev->disconnect_wk);
cancel_work_sync(&wdev->pmsr_free_wk);
@@ -1731,6 +1768,23 @@ static int cfg80211_netdev_notifier_call(struct notifier_block *nb,
if (rfkill_blocked(rdev->wiphy.rfkill))
return notifier_from_errno(-ERFKILL);
+
+ /* NAN_DATA interfaces require a running NAN interface */
+ if (wdev->iftype == NL80211_IFTYPE_NAN_DATA) {
+ struct wireless_dev *iter;
+ bool nan_started = false;
+
+ list_for_each_entry(iter, &rdev->wiphy.wdev_list, list) {
+ if (iter->iftype == NL80211_IFTYPE_NAN &&
+ wdev_running(iter)) {
+ nan_started = true;
+ break;
+ }
+ }
+
+ if (!nan_started)
+ return notifier_from_errno(-ENOLINK);
+ }
break;
default:
return NOTIFY_DONE;
diff --git a/net/wireless/core.h b/net/wireless/core.h
index c7ae1f8a9bd8..ae2d56d3ad90 100644
--- a/net/wireless/core.h
+++ b/net/wireless/core.h
@@ -318,6 +318,9 @@ void cfg80211_cqm_rssi_notify_work(struct wiphy *wiphy,
void cfg80211_destroy_ifaces(struct cfg80211_registered_device *rdev);
+void cfg80211_close_dependents(struct cfg80211_registered_device *rdev,
+ struct wireless_dev *wdev);
+
/* free object */
void cfg80211_dev_free(struct cfg80211_registered_device *rdev);
@@ -541,6 +544,9 @@ int cfg80211_validate_beacon_int(struct cfg80211_registered_device *rdev,
void cfg80211_update_iface_num(struct cfg80211_registered_device *rdev,
enum nl80211_iftype iftype, int num);
+void cfg80211_leave_locked(struct cfg80211_registered_device *rdev,
+ struct wireless_dev *wdev, int link_id);
+
void cfg80211_leave(struct cfg80211_registered_device *rdev,
struct wireless_dev *wdev,
int link_id);
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index e70141cccb44..b3c68d281c3f 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -1764,6 +1764,7 @@ static int nl80211_key_allowed(struct wireless_dev *wdev)
return 0;
return -ENOLINK;
case NL80211_IFTYPE_NAN:
+ case NL80211_IFTYPE_NAN_DATA:
if (wiphy_ext_feature_isset(wdev->wiphy,
NL80211_EXT_FEATURE_SECURE_NAN))
return 0;
@@ -4921,6 +4922,8 @@ static int nl80211_del_interface(struct sk_buff *skb, struct genl_info *info)
else
dev_close(wdev->netdev);
+ cfg80211_close_dependents(rdev, wdev);
+
mutex_lock(&rdev->wiphy.mtx);
return cfg80211_remove_virtual_intf(rdev, wdev);
@@ -15964,6 +15967,10 @@ static int nl80211_stop_nan(struct sk_buff *skb, struct genl_info *info)
if (wdev->iftype != NL80211_IFTYPE_NAN)
return -EOPNOTSUPP;
+ cfg80211_close_dependents(rdev, wdev);
+
+ guard(wiphy)(&rdev->wiphy);
+
cfg80211_stop_nan(rdev, wdev);
return 0;
@@ -18356,7 +18363,11 @@ nl80211_epcs_cfg(struct sk_buff *skb, struct genl_info *info)
NL80211_FLAG_NEED_RTNL) \
SELECTOR(__sel, WIPHY_CLEAR, \
NL80211_FLAG_NEED_WIPHY | \
- NL80211_FLAG_CLEAR_SKB)
+ NL80211_FLAG_CLEAR_SKB) \
+ SELECTOR(__sel, WDEV_UP_RTNL_NOMTX, \
+ NL80211_FLAG_NEED_WDEV_UP | \
+ NL80211_FLAG_NO_WIPHY_MTX | \
+ NL80211_FLAG_NEED_RTNL)
enum nl80211_internal_flags_selector {
#define SELECTOR(_, name, value) NL80211_IFL_SEL_##name,
@@ -19193,6 +19204,7 @@ static const struct genl_small_ops nl80211_small_ops[] = {
.doit = nl80211_stop_nan,
.flags = GENL_ADMIN_PERM,
.internal_flags = IFLAGS(NL80211_FLAG_NEED_WDEV_UP |
+ NL80211_FLAG_NO_WIPHY_MTX |
NL80211_FLAG_NEED_RTNL),
},
{
diff --git a/net/wireless/reg.c b/net/wireless/reg.c
index 4b5450aec72e..5db2121c0b57 100644
--- a/net/wireless/reg.c
+++ b/net/wireless/reg.c
@@ -2409,6 +2409,9 @@ static bool reg_wdev_chan_valid(struct wiphy *wiphy, struct wireless_dev *wdev)
continue;
chandef = wdev->u.ocb.chandef;
break;
+ case NL80211_IFTYPE_NAN_DATA:
+ /* NAN channels are checked in NL80211_IFTYPE_NAN interface */
+ break;
default:
/* others not implemented for now */
WARN_ON_ONCE(1);
@@ -2445,11 +2448,14 @@ static void reg_leave_invalid_chans(struct wiphy *wiphy)
struct wireless_dev *wdev;
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
- guard(wiphy)(wiphy);
+ list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
+ bool valid;
- list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list)
- if (!reg_wdev_chan_valid(wiphy, wdev))
+ scoped_guard(wiphy, wiphy)
+ valid = reg_wdev_chan_valid(wiphy, wdev);
+ if (!valid)
cfg80211_leave(rdev, wdev, -1);
+ }
}
static void reg_check_chans_work(struct work_struct *work)
diff --git a/net/wireless/sysfs.c b/net/wireless/sysfs.c
index 3385a27468f7..d45ddc457c30 100644
--- a/net/wireless/sysfs.c
+++ b/net/wireless/sysfs.c
@@ -102,25 +102,26 @@ static int wiphy_suspend(struct device *dev)
if (!rdev->wiphy.registered)
goto out_unlock_rtnl;
- wiphy_lock(&rdev->wiphy);
if (rdev->wiphy.wowlan_config) {
- cfg80211_process_wiphy_works(rdev, NULL);
- if (rdev->ops->suspend)
- ret = rdev_suspend(rdev, rdev->wiphy.wowlan_config);
- if (ret <= 0)
- goto out_unlock_wiphy;
+ scoped_guard(wiphy, &rdev->wiphy) {
+ cfg80211_process_wiphy_works(rdev, NULL);
+ if (rdev->ops->suspend)
+ ret = rdev_suspend(rdev,
+ rdev->wiphy.wowlan_config);
+ if (ret <= 0)
+ goto out_unlock_rtnl;
+ }
}
/* Driver refused to configure wowlan (ret = 1) or no wowlan */
cfg80211_leave_all(rdev);
- cfg80211_process_rdev_events(rdev);
- cfg80211_process_wiphy_works(rdev, NULL);
- if (rdev->ops->suspend)
- ret = rdev_suspend(rdev, NULL);
-
-out_unlock_wiphy:
- wiphy_unlock(&rdev->wiphy);
+ scoped_guard(wiphy, &rdev->wiphy) {
+ cfg80211_process_rdev_events(rdev);
+ cfg80211_process_wiphy_works(rdev, NULL);
+ if (rdev->ops->suspend)
+ ret = rdev_suspend(rdev, NULL);
+ }
out_unlock_rtnl:
if (ret == 0)
rdev->suspended = true;
diff --git a/net/wireless/util.c b/net/wireless/util.c
index 0a0cea018fc5..8dda571585cf 100644
--- a/net/wireless/util.c
+++ b/net/wireless/util.c
@@ -1144,8 +1144,15 @@ void cfg80211_process_wdev_events(struct wireless_dev *wdev)
ev->ij.channel);
break;
case EVENT_STOPPED:
- cfg80211_leave(wiphy_to_rdev(wdev->wiphy), wdev,
- ev->link_id);
+ /*
+ * for NAN interfaces cfg80211_leave must be called but
+ * locking here doesn't allow this.
+ */
+ if (WARN_ON(wdev->iftype == NL80211_IFTYPE_NAN))
+ break;
+
+ cfg80211_leave_locked(wiphy_to_rdev(wdev->wiphy), wdev,
+ ev->link_id);
break;
case EVENT_PORT_AUTHORIZED:
__cfg80211_port_authorized(wdev, ev->pa.peer_addr,
@@ -1184,6 +1191,13 @@ int cfg80211_change_iface(struct cfg80211_registered_device *rdev,
if (otype == NL80211_IFTYPE_AP_VLAN)
return -EOPNOTSUPP;
+ /*
+ * for NAN interfaces cfg80211_leave must be called for leaving,
+ * but locking here doesn't allow this.
+ */
+ if (otype == NL80211_IFTYPE_NAN)
+ return -EOPNOTSUPP;
+
/* cannot change into P2P device or NAN */
if (ntype == NL80211_IFTYPE_P2P_DEVICE ||
ntype == NL80211_IFTYPE_NAN)
@@ -1204,7 +1218,7 @@ int cfg80211_change_iface(struct cfg80211_registered_device *rdev,
dev->ieee80211_ptr->use_4addr = false;
rdev_set_qos_map(rdev, dev, NULL);
- cfg80211_leave(rdev, dev->ieee80211_ptr, -1);
+ cfg80211_leave_locked(rdev, dev->ieee80211_ptr, -1);
cfg80211_process_rdev_events(rdev);
cfg80211_mlme_purge_registrations(dev->ieee80211_ptr);
@@ -1232,6 +1246,7 @@ int cfg80211_change_iface(struct cfg80211_registered_device *rdev,
case NL80211_IFTYPE_OCB:
case NL80211_IFTYPE_P2P_CLIENT:
case NL80211_IFTYPE_ADHOC:
+ case NL80211_IFTYPE_NAN_DATA:
dev->priv_flags |= IFF_DONT_BRIDGE;
break;
case NL80211_IFTYPE_P2P_GO:
--
2.34.1
^ permalink raw reply related
* [PATCH v5 wireless-next 02/12] wifi: cfg80211: make sure NAN chandefs are valid
From: Miri Korenblit @ 2026-03-18 12:39 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
In-Reply-To: <20260318123926.206536-1-miriam.rachel.korenblit@intel.com>
Until now there was not handling for NAN in reg_wdev_chan_valid.
Now as this wdev might use chandefs, check the validity of those.
Reviewed-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Link: https://patch.msgid.link/20260108102921.51b42ffc9a42.Iacb030fc17027afb55707ca1d6dc146631d55767@changeid
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Link: https://patch.msgid.link/20260219094725.3846371-4-miriam.rachel.korenblit@intel.com
---
net/wireless/reg.c | 15 ++++++++++++---
1 file changed, 12 insertions(+), 3 deletions(-)
diff --git a/net/wireless/reg.c b/net/wireless/reg.c
index 20bba7e491c5..4b5450aec72e 100644
--- a/net/wireless/reg.c
+++ b/net/wireless/reg.c
@@ -2348,6 +2348,18 @@ static bool reg_wdev_chan_valid(struct wiphy *wiphy, struct wireless_dev *wdev)
if (!wdev->netdev || !netif_running(wdev->netdev))
return true;
+ /* NAN doesn't have links, handle it separately */
+ if (iftype == NL80211_IFTYPE_NAN) {
+ for (int i = 0; i < wdev->u.nan.n_channels; i++) {
+ ret = cfg80211_reg_can_beacon(wiphy,
+ &wdev->u.nan.chandefs[i],
+ NL80211_IFTYPE_NAN);
+ if (!ret)
+ return false;
+ }
+ return true;
+ }
+
for (link = 0; link < ARRAY_SIZE(wdev->links); link++) {
struct ieee80211_channel *chan;
@@ -2397,9 +2409,6 @@ static bool reg_wdev_chan_valid(struct wiphy *wiphy, struct wireless_dev *wdev)
continue;
chandef = wdev->u.ocb.chandef;
break;
- case NL80211_IFTYPE_NAN:
- /* we have no info, but NAN is also pretty universal */
- continue;
default:
/* others not implemented for now */
WARN_ON_ONCE(1);
--
2.34.1
^ permalink raw reply related
* [PATCH v5 wireless-next 01/12] wifi: cfg80211: Add an API to configure local NAN schedule
From: Miri Korenblit @ 2026-03-18 12:39 UTC (permalink / raw)
To: linux-wireless
In-Reply-To: <20260318123926.206536-1-miriam.rachel.korenblit@intel.com>
Add an nl80211 API to allow user space to configure the local NAN
schedule.
The local schedule consists of a list of channel definitions and a schedule
map, in which each element covers a time slot and indicates on what
channel the device should be in that time slot.
Channels can be added to schedule even without being scheduled, for
reservation purposes.
A schedule can be configured either immedietally or be deferred, in case
there are already connected peers.
When the deferred flag is set, the command is a request from the device
to perform an announced schedule update: send the updated NAN
Availability - as set in this command - to the peers, and do the
actual switch to the new schedule on the right time (i.e. at the end of
the slot after the slot in which the update was sent to the peers).
In addition, a notification will be sent to indicate a deferred update
completion.
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Link: https://patch.msgid.link/20260219114327.ecca178a2de0.Ic977ab08b4ed5cf9b849e55d3a59b01ad3fbd08e@changeid
---
include/net/cfg80211.h | 73 +++++++++-
include/uapi/linux/nl80211.h | 76 ++++++++++
net/wireless/core.c | 54 ++++++-
net/wireless/core.h | 4 +
net/wireless/nl80211.c | 266 +++++++++++++++++++++++++++++++++++
net/wireless/rdev-ops.h | 16 +++
net/wireless/trace.h | 38 +++++
7 files changed, 525 insertions(+), 2 deletions(-)
diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index 8cd870ece351..539dcf65c188 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -4050,6 +4050,54 @@ struct cfg80211_nan_conf {
u16 vendor_elems_len;
};
+#define CFG80211_NAN_SCHED_NUM_TIME_SLOTS 32
+
+/**
+ * struct cfg80211_nan_channel - NAN channel configuration
+ *
+ * This struct defines a NAN channel configuration
+ *
+ * @chandef: the channel definition
+ * @channel_entry: pointer to the Channel Entry blob as defined in Wi-Fi Aware
+ * (TM) 4.0 specification Table 100 (Channel Entry format for the NAN
+ * Availability attribute).
+ * @rx_nss: number of spatial streams supported on this channel
+ */
+struct cfg80211_nan_channel {
+ struct cfg80211_chan_def chandef;
+ const u8 *channel_entry;
+ u8 rx_nss;
+};
+
+/**
+ * struct cfg80211_nan_local_sched - NAN local schedule
+ *
+ * This struct defines NAN local schedule parameters
+ *
+ * @schedule: a mapping of time slots to chandef indexes in %nan_channels.
+ * An unscheduled slot will be set to %NL80211_NAN_SCHED_NOT_AVAIL_SLOT.
+ * @n_channels: number of channel definitions in %nan_channels.
+ * @nan_avail_blob: pointer to NAN Availability attribute blob.
+ * See %NL80211_ATTR_NAN_AVAIL_BLOB for more details.
+ * @nan_avail_blob_len: length of the @nan_avail_blob in bytes.
+ * @deferred: if true, the command containing this schedule configuration is a
+ * request from the device to perform an announced schedule update. This
+ * means that it needs to send the updated NAN availability to the peers,
+ * and do the actual switch on the right time (i.e. at the end of the slot
+ * after the slot in which the updated NAN Availability was sent).
+ * See %NL80211_ATTR_NAN_SCHED_DEFERRED for more details.
+ * If false, the schedule is applied immediately.
+ * @nan_channels: array of NAN channel definitions that can be scheduled.
+ */
+struct cfg80211_nan_local_sched {
+ u8 schedule[CFG80211_NAN_SCHED_NUM_TIME_SLOTS];
+ u8 n_channels;
+ const u8 *nan_avail_blob;
+ u16 nan_avail_blob_len;
+ bool deferred;
+ struct cfg80211_nan_channel nan_channels[] __counted_by(n_channels);
+};
+
/**
* enum cfg80211_nan_conf_changes - indicates changed fields in NAN
* configuration
@@ -4830,6 +4878,12 @@ struct mgmt_frame_regs {
* @nan_change_conf: changes NAN configuration. The changed parameters must
* be specified in @changes (using &enum cfg80211_nan_conf_changes);
* All other parameters must be ignored.
+ * @nan_set_local_sched: configure the local schedule for NAN. The schedule
+ * consists of an array of %cfg80211_nan_channel and the schedule itself,
+ * in which each entry maps each time slot to the channel on which the
+ * radio should operate on. If the chandef of a NAN channel is not
+ * changed, the channel entry must also remain unchanged. It is the
+ * driver's responsibility to verify this.
*
* @set_multicast_to_unicast: configure multicast to unicast conversion for BSS
*
@@ -5207,7 +5261,9 @@ struct cfg80211_ops {
struct wireless_dev *wdev,
struct cfg80211_nan_conf *conf,
u32 changes);
-
+ int (*nan_set_local_sched)(struct wiphy *wiphy,
+ struct wireless_dev *wdev,
+ struct cfg80211_nan_local_sched *sched);
int (*set_multicast_to_unicast)(struct wiphy *wiphy,
struct net_device *dev,
const bool enabled);
@@ -6859,6 +6915,9 @@ struct wireless_dev {
} ocb;
struct {
u8 cluster_id[ETH_ALEN] __aligned(2);
+ u8 n_channels;
+ struct cfg80211_chan_def *chandefs;
+ bool sched_update_pending;
} nan;
} u;
@@ -10016,6 +10075,18 @@ void cfg80211_nan_func_terminated(struct wireless_dev *wdev,
enum nl80211_nan_func_term_reason reason,
u64 cookie, gfp_t gfp);
+/**
+ * cfg80211_nan_sched_update_done - notify deferred schedule update completion
+ * @wdev: the wireless device reporting the event
+ * @success: whether or not the schedule update was successful
+ * @gfp: allocation flags
+ *
+ * This function notifies user space that a deferred local NAN schedule update
+ * (requested with %NL80211_ATTR_NAN_SCHED_DEFERRED) has been completed.
+ */
+void cfg80211_nan_sched_update_done(struct wireless_dev *wdev, bool success,
+ gfp_t gfp);
+
/* ethtool helper */
void cfg80211_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info);
diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h
index 67d764023988..484094667abc 100644
--- a/include/uapi/linux/nl80211.h
+++ b/include/uapi/linux/nl80211.h
@@ -1367,6 +1367,20 @@
* %NL80211_ATTR_INCUMBENT_SIGNAL_INTERFERENCE_BITMAP. The current channel
* definition is also sent.
*
+ * @NL80211_CMD_NAN_SET_LOCAL_SCHED: Set the local NAN schedule. NAN must be
+ * operational (%NL80211_CMD_START_NAN was executed). Must contain
+ * %NL80211_ATTR_NAN_TIME_SLOTS and %NL80211_ATTR_NAN_AVAIL_BLOB, but
+ * %NL80211_ATTR_NAN_CHANNEL is optional (for example in case of a channel
+ * removal, that channel won't be provided).
+ * If %NL80211_ATTR_NAN_SCHED_DEFERRED is set, the command is a request
+ * from the device to perform an announced schedule update. See
+ * %NL80211_ATTR_NAN_SCHED_DEFERRED for more details.
+ * If not set, the schedule should be applied immediately.
+ * @NL80211_CMD_NAN_SCHED_UPDATE_DONE: Event sent to user space to notify that
+ * a deferred local NAN schedule update (requested with
+ * %NL80211_CMD_NAN_SET_LOCAL_SCHED and %NL80211_ATTR_NAN_SCHED_DEFERRED)
+ * has been completed. The presence of %NL80211_ATTR_NAN_SCHED_UPDATE_SUCCESS
+ * indicates that the update was successful.
* @NL80211_CMD_MAX: highest used command number
* @__NL80211_CMD_AFTER_LAST: internal use
*/
@@ -1632,6 +1646,10 @@ enum nl80211_commands {
NL80211_CMD_INCUMBENT_SIGNAL_DETECT,
+ NL80211_CMD_NAN_SET_LOCAL_SCHED,
+
+ NL80211_CMD_NAN_SCHED_UPDATE_DONE,
+
/* add new commands above here */
/* used to define NL80211_CMD_MAX below */
@@ -2991,6 +3009,54 @@ enum nl80211_commands {
* @NL80211_ATTR_DISABLE_UHR: Force UHR capable interfaces to disable
* this feature during association. This is a flag attribute.
* Currently only supported in mac80211 drivers.
+ * @NL80211_ATTR_NAN_CHANNEL: This is a nested attribute. There can be multiple
+ * attributes of this type, each one represents a channel definition and
+ * consists of top-level attributes like %NL80211_ATTR_WIPHY_FREQ. Must
+ * contain %NL80211_ATTR_NAN_CHANNEL_ENTRY and
+ * %NL80211_ATTR_NAN_RX_NSS.
+ * This attribute is used with %NL80211_CMD_NAN_SET_LOCAL_SCHED to specify
+ * the channel definitions on which the radio needs to operate during
+ * specific time slots. All of the channel definitions should be mutually
+ * incompatible. The number of channels should fit the current
+ * configuration of channels and the possible interface combinations.
+ * If an existing NAN channel is changed but the chandef isn't, the
+ * channel entry must also remain unchanged.
+ * @NL80211_ATTR_NAN_CHANNEL_ENTRY: a byte array of 6 bytes. contains the
+ * Channel Entry as defined in Wi-Fi Aware (TM) 4.0 specification Table
+ * 100 (Channel Entry format for the NAN Availability attribute).
+ * @NL80211_ATTR_NAN_RX_NSS: (u8) RX NSS used for a NAN channel. This is
+ * used with %NL80211_ATTR_NAN_CHANNEL when configuring NAN channels with
+ * %NL80211_CMD_NAN_SET_LOCAL_SCHED.
+ * @NL80211_ATTR_NAN_TIME_SLOTS: an array of u8 values and 32 cells. each value
+ * maps a time slot to the chandef on which the radio should operate on in
+ * that time. %NL80211_NAN_SCHED_NOT_AVAIL_SLOT indicates unscheduled.
+ * The chandef is represented using its index, where the index is the
+ * sequential number of the %NL80211_ATTR_NAN_CHANNEL attribute within all
+ * the attributes of this type.
+ * Each slots spans over 16TUs, hence the entire schedule spans over
+ * 512TUs. Other slot durations and periods are currently not supported.
+ * @NL80211_ATTR_NAN_AVAIL_BLOB: (Binary) The NAN Availability attribute blob,
+ * including the attribute header, as defined in Wi-Fi Aware (TM) 4.0
+ * specification Table 93 (NAN Availability attribute format). Required with
+ * %NL80211_CMD_NAN_SET_LOCAL_SCHED to provide the raw NAN Availability
+ * attribute. Used by the device to publish Schedule Update NAFs.
+ * @NL80211_ATTR_NAN_SCHED_DEFERRED: Flag attribute used with
+ * %NL80211_CMD_NAN_SET_LOCAL_SCHED. When present, the command is a
+ * request from the device to perform an announced schedule update. This
+ * means that it needs to send the updated NAN availability to the peers,
+ * and do the actual switch on the right time (i.e. at the end of the slot
+ * after the slot in which the updated NAN Availability was sent). Since
+ * the slots management is done in the device, the update to the peers
+ * needs to be sent by the device, so it knows the actual switch time.
+ * If the flag is not set, the schedule should be applied immediately.
+ * When this flag is set, the total number of NAN channels from both the
+ * old and new schedules must not exceed the allowed number of local NAN
+ * channels, because with deferred scheduling the old channels cannot be
+ * removed before adding the new ones to free up space.
+ * @NL80211_ATTR_NAN_SCHED_UPDATE_SUCCESS: flag attribute used with
+ * %NL80211_CMD_NAN_SCHED_UPDATE_DONE to indicate that the deferred
+ * schedule update completed successfully. If this flag is not present,
+ * the update failed.
*
* @NL80211_ATTR_INCUMBENT_SIGNAL_INTERFERENCE_BITMAP: u32 attribute specifying
* the signal interference bitmap detected on the operating bandwidth for
@@ -3582,6 +3648,14 @@ enum nl80211_attrs {
NL80211_ATTR_UHR_OPERATION,
+ NL80211_ATTR_NAN_CHANNEL,
+ NL80211_ATTR_NAN_CHANNEL_ENTRY,
+ NL80211_ATTR_NAN_TIME_SLOTS,
+ NL80211_ATTR_NAN_RX_NSS,
+ NL80211_ATTR_NAN_AVAIL_BLOB,
+ NL80211_ATTR_NAN_SCHED_DEFERRED,
+ NL80211_ATTR_NAN_SCHED_UPDATE_SUCCESS,
+
/* add attributes here, update the policy in nl80211.c */
__NL80211_ATTR_AFTER_LAST,
@@ -8574,4 +8648,6 @@ enum nl80211_nan_capabilities {
NL80211_NAN_CAPABILITIES_MAX = __NL80211_NAN_CAPABILITIES_LAST - 1,
};
+#define NL80211_NAN_SCHED_NOT_AVAIL_SLOT 0xff
+
#endif /* __LINUX_NL80211_H */
diff --git a/net/wireless/core.c b/net/wireless/core.c
index 23afc250bc10..54c89b0db352 100644
--- a/net/wireless/core.c
+++ b/net/wireless/core.c
@@ -5,7 +5,7 @@
* Copyright 2006-2010 Johannes Berg <johannes@sipsolutions.net>
* Copyright 2013-2014 Intel Mobile Communications GmbH
* Copyright 2015-2017 Intel Deutschland GmbH
- * Copyright (C) 2018-2025 Intel Corporation
+ * Copyright (C) 2018-2026 Intel Corporation
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
@@ -254,6 +254,8 @@ void cfg80211_stop_p2p_device(struct cfg80211_registered_device *rdev,
void cfg80211_stop_nan(struct cfg80211_registered_device *rdev,
struct wireless_dev *wdev)
{
+ struct cfg80211_nan_local_sched empty_sched = {};
+
lockdep_assert_held(&rdev->wiphy.mtx);
if (WARN_ON(wdev->iftype != NL80211_IFTYPE_NAN))
@@ -262,6 +264,15 @@ void cfg80211_stop_nan(struct cfg80211_registered_device *rdev,
if (!wdev_running(wdev))
return;
+ /*
+ * If there is a scheduled update pending, mark it as canceled, so the
+ * empty schedule will be accepted
+ */
+ wdev->u.nan.sched_update_pending = false;
+
+ /* Unschedule all */
+ cfg80211_nan_set_local_schedule(rdev, wdev, &empty_sched);
+
rdev_stop_nan(rdev, wdev);
wdev->is_running = false;
@@ -270,6 +281,47 @@ void cfg80211_stop_nan(struct cfg80211_registered_device *rdev,
rdev->opencount--;
}
+int cfg80211_nan_set_local_schedule(struct cfg80211_registered_device *rdev,
+ struct wireless_dev *wdev,
+ struct cfg80211_nan_local_sched *sched)
+{
+ int ret;
+
+ lockdep_assert_held(&rdev->wiphy.mtx);
+
+ if (wdev->iftype != NL80211_IFTYPE_NAN || !wdev_running(wdev))
+ return -EINVAL;
+
+ if (wdev->u.nan.sched_update_pending)
+ return -EBUSY;
+
+ ret = rdev_nan_set_local_sched(rdev, wdev, sched);
+ if (ret)
+ return ret;
+
+ wdev->u.nan.sched_update_pending = sched->deferred;
+
+ kfree(wdev->u.nan.chandefs);
+ wdev->u.nan.chandefs = NULL;
+ wdev->u.nan.n_channels = 0;
+
+ if (!sched->n_channels)
+ return 0;
+
+ wdev->u.nan.chandefs = kcalloc(sched->n_channels,
+ sizeof(*wdev->u.nan.chandefs),
+ GFP_KERNEL);
+ if (!wdev->u.nan.chandefs)
+ return -ENOMEM;
+
+ for (int i = 0; i < sched->n_channels; i++)
+ wdev->u.nan.chandefs[i] = sched->nan_channels[i].chandef;
+
+ wdev->u.nan.n_channels = sched->n_channels;
+
+ return 0;
+}
+
void cfg80211_shutdown_all_interfaces(struct wiphy *wiphy)
{
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
diff --git a/net/wireless/core.h b/net/wireless/core.h
index 6cace846d7a3..c7ae1f8a9bd8 100644
--- a/net/wireless/core.h
+++ b/net/wireless/core.h
@@ -551,6 +551,10 @@ void cfg80211_stop_p2p_device(struct cfg80211_registered_device *rdev,
void cfg80211_stop_nan(struct cfg80211_registered_device *rdev,
struct wireless_dev *wdev);
+int cfg80211_nan_set_local_schedule(struct cfg80211_registered_device *rdev,
+ struct wireless_dev *wdev,
+ struct cfg80211_nan_local_sched *sched);
+
struct cfg80211_internal_bss *
cfg80211_bss_update(struct cfg80211_registered_device *rdev,
struct cfg80211_internal_bss *tmp,
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index d2ef13ab1a20..e70141cccb44 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -333,6 +333,40 @@ static int validate_nan_cluster_id(const struct nlattr *attr,
return 0;
}
+static int validate_nan_avail_blob(const struct nlattr *attr,
+ struct netlink_ext_ack *extack)
+{
+ const u8 *data = nla_data(attr);
+ unsigned int len = nla_len(attr);
+ u16 attr_len;
+
+ /* Need at least: Attr ID (1) + Length (2) */
+ if (len < 3) {
+ NL_SET_ERR_MSG_FMT(extack,
+ "NAN Availability: Too short (need at least 3 bytes, have %u)",
+ len);
+ return -EINVAL;
+ }
+
+ if (data[0] != 0x12) {
+ NL_SET_ERR_MSG_FMT(extack,
+ "NAN Availability: Invalid Attribute ID 0x%02x (expected 0x12)",
+ data[0]);
+ return -EINVAL;
+ }
+
+ attr_len = get_unaligned_le16(&data[1]);
+
+ if (attr_len != len - 3) {
+ NL_SET_ERR_MSG_FMT(extack,
+ "NAN Availability: Length field (%u) doesn't match data length (%u)",
+ attr_len, len - 3);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
static int validate_uhr_capa(const struct nlattr *attr,
struct netlink_ext_ack *extack)
{
@@ -962,6 +996,14 @@ static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = {
[NL80211_ATTR_DISABLE_UHR] = { .type = NLA_FLAG },
[NL80211_ATTR_UHR_OPERATION] =
NLA_POLICY_VALIDATE_FN(NLA_BINARY, validate_uhr_operation),
+ [NL80211_ATTR_NAN_CHANNEL] = NLA_POLICY_NESTED(nl80211_policy),
+ [NL80211_ATTR_NAN_CHANNEL_ENTRY] = NLA_POLICY_EXACT_LEN(6),
+ [NL80211_ATTR_NAN_RX_NSS] = { .type = NLA_U8 },
+ [NL80211_ATTR_NAN_TIME_SLOTS] =
+ NLA_POLICY_EXACT_LEN(CFG80211_NAN_SCHED_NUM_TIME_SLOTS),
+ [NL80211_ATTR_NAN_AVAIL_BLOB] =
+ NLA_POLICY_VALIDATE_FN(NLA_BINARY, validate_nan_avail_blob),
+ [NL80211_ATTR_NAN_SCHED_DEFERRED] = { .type = NLA_FLAG },
};
/* policy for the key attributes */
@@ -16421,6 +16463,224 @@ void cfg80211_nan_func_terminated(struct wireless_dev *wdev,
}
EXPORT_SYMBOL(cfg80211_nan_func_terminated);
+void cfg80211_nan_sched_update_done(struct wireless_dev *wdev, bool success,
+ gfp_t gfp)
+{
+ struct wiphy *wiphy = wdev->wiphy;
+ struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
+ struct sk_buff *msg;
+ void *hdr;
+
+ trace_cfg80211_nan_sched_update_done(wiphy, wdev, success);
+
+ /* Can happen if we stopped NAN */
+ if (!wdev->u.nan.sched_update_pending)
+ return;
+
+ wdev->u.nan.sched_update_pending = false;
+
+ if (!wdev->owner_nlportid)
+ return;
+
+ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, gfp);
+ if (!msg)
+ return;
+
+ hdr = nl80211hdr_put(msg, 0, 0, 0, NL80211_CMD_NAN_SCHED_UPDATE_DONE);
+ if (!hdr)
+ goto nla_put_failure;
+
+ if (nla_put_u32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx) ||
+ nla_put_u64_64bit(msg, NL80211_ATTR_WDEV, wdev_id(wdev),
+ NL80211_ATTR_PAD) ||
+ (success &&
+ nla_put_flag(msg, NL80211_ATTR_NAN_SCHED_UPDATE_SUCCESS)))
+ goto nla_put_failure;
+
+ genlmsg_end(msg, hdr);
+
+ genlmsg_unicast(wiphy_net(wiphy), msg, wdev->owner_nlportid);
+
+ return;
+
+nla_put_failure:
+ nlmsg_free(msg);
+}
+EXPORT_SYMBOL(cfg80211_nan_sched_update_done);
+
+static int nl80211_parse_nan_channel(struct cfg80211_registered_device *rdev,
+ struct nlattr *channel,
+ struct genl_info *info,
+ struct cfg80211_nan_local_sched *sched,
+ u8 index)
+{
+ struct nlattr **channel_parsed __free(kfree) = NULL;
+ struct cfg80211_chan_def chandef;
+ u8 n_rx_nss;
+ int ret;
+
+ channel_parsed = kcalloc(NL80211_ATTR_MAX + 1, sizeof(*channel_parsed),
+ GFP_KERNEL);
+ if (!channel_parsed)
+ return -ENOMEM;
+
+ ret = nla_parse_nested(channel_parsed, NL80211_ATTR_MAX, channel, NULL,
+ info->extack);
+ if (ret)
+ return ret;
+
+ ret = nl80211_parse_chandef(rdev, info->extack, channel_parsed,
+ &chandef);
+ if (ret)
+ return ret;
+
+ if (chandef.chan->band == NL80211_BAND_6GHZ) {
+ NL_SET_ERR_MSG(info->extack,
+ "6 GHz band is not supported");
+ return -EOPNOTSUPP;
+ }
+
+ if (!cfg80211_reg_can_beacon(&rdev->wiphy, &chandef,
+ NL80211_IFTYPE_NAN)) {
+ NL_SET_ERR_MSG_ATTR(info->extack, channel,
+ "Channel in NAN schedule is not allowed for NAN operation");
+ return -EINVAL;
+ }
+
+ for (int i = 0; i < index; i++) {
+ if (cfg80211_chandef_compatible(&sched->nan_channels[i].chandef,
+ &chandef)) {
+ NL_SET_ERR_MSG_ATTR(info->extack, channel,
+ "Channels in NAN schedule must be mutually incompatible");
+ return -EINVAL;
+ }
+ }
+
+ if (!channel_parsed[NL80211_ATTR_NAN_CHANNEL_ENTRY])
+ return -EINVAL;
+
+ sched->nan_channels[index].channel_entry =
+ nla_data(channel_parsed[NL80211_ATTR_NAN_CHANNEL_ENTRY]);
+
+ if (!channel_parsed[NL80211_ATTR_NAN_RX_NSS])
+ return -EINVAL;
+
+ sched->nan_channels[index].rx_nss =
+ nla_get_u8(channel_parsed[NL80211_ATTR_NAN_RX_NSS]);
+
+ n_rx_nss = u8_get_bits(rdev->wiphy.nan_capa.n_antennas, 0x03);
+ if (sched->nan_channels[index].rx_nss > n_rx_nss ||
+ !sched->nan_channels[index].rx_nss) {
+ NL_SET_ERR_MSG_ATTR(info->extack, channel,
+ "Invalid RX NSS in NAN channel definition");
+ return -EINVAL;
+ }
+
+ sched->nan_channels[index].chandef = chandef;
+
+ return 0;
+}
+
+static bool nl80211_nan_is_sched_empty(struct cfg80211_nan_local_sched *sched)
+{
+ if (!sched->n_channels)
+ return true;
+
+ for (int i = 0; i < ARRAY_SIZE(sched->schedule); i++) {
+ if (sched->schedule[i] != NL80211_NAN_SCHED_NOT_AVAIL_SLOT)
+ return false;
+ }
+
+ return true;
+}
+
+static int nl80211_nan_set_local_sched(struct sk_buff *skb,
+ struct genl_info *info)
+{
+ struct cfg80211_registered_device *rdev = info->user_ptr[0];
+ struct cfg80211_nan_local_sched *sched __free(kfree) = NULL;
+ struct wireless_dev *wdev = info->user_ptr[1];
+ int rem, i = 0, n_channels = 0;
+ struct nlattr *channel;
+ bool sched_empty;
+
+ if (wdev->iftype != NL80211_IFTYPE_NAN)
+ return -EOPNOTSUPP;
+
+ if (!wdev_running(wdev))
+ return -ENOTCONN;
+
+ if (!info->attrs[NL80211_ATTR_NAN_TIME_SLOTS])
+ return -EINVAL;
+
+ /* First count how many channel attributes we got */
+ nlmsg_for_each_attr_type(channel, NL80211_ATTR_NAN_CHANNEL,
+ info->nlhdr, GENL_HDRLEN, rem)
+ n_channels++;
+
+ sched = kzalloc(struct_size(sched, nan_channels, n_channels),
+ GFP_KERNEL);
+ if (!sched)
+ return -ENOMEM;
+
+ sched->n_channels = n_channels;
+
+ nlmsg_for_each_attr_type(channel, NL80211_ATTR_NAN_CHANNEL,
+ info->nlhdr, GENL_HDRLEN, rem) {
+ int ret = nl80211_parse_nan_channel(rdev, channel, info, sched,
+ i);
+
+ if (ret)
+ return ret;
+ i++;
+ }
+
+ memcpy(sched->schedule,
+ nla_data(info->attrs[NL80211_ATTR_NAN_TIME_SLOTS]),
+ nla_len(info->attrs[NL80211_ATTR_NAN_TIME_SLOTS]));
+
+ for (int slot = 0; slot < ARRAY_SIZE(sched->schedule); slot++) {
+ if (sched->schedule[slot] != NL80211_NAN_SCHED_NOT_AVAIL_SLOT &&
+ sched->schedule[slot] >= sched->n_channels) {
+ NL_SET_ERR_MSG(info->extack,
+ "Invalid time slot in NAN schedule");
+ return -EINVAL;
+ }
+ }
+
+ sched_empty = nl80211_nan_is_sched_empty(sched);
+
+ sched->deferred =
+ nla_get_flag(info->attrs[NL80211_ATTR_NAN_SCHED_DEFERRED]);
+
+ if (sched_empty) {
+ if (sched->deferred) {
+ NL_SET_ERR_MSG(info->extack,
+ "Schedule cannot be deferred if all time slots are unavailable");
+ return -EINVAL;
+ }
+
+ if (info->attrs[NL80211_ATTR_NAN_AVAIL_BLOB]) {
+ NL_SET_ERR_MSG(info->extack,
+ "NAN Availability blob must be empty if all time slots are unavailable");
+ return -EINVAL;
+ }
+ } else {
+ if (!info->attrs[NL80211_ATTR_NAN_AVAIL_BLOB]) {
+ NL_SET_ERR_MSG(info->extack,
+ "NAN Availability blob attribute is required");
+ return -EINVAL;
+ }
+
+ sched->nan_avail_blob =
+ nla_data(info->attrs[NL80211_ATTR_NAN_AVAIL_BLOB]);
+ sched->nan_avail_blob_len =
+ nla_len(info->attrs[NL80211_ATTR_NAN_AVAIL_BLOB]);
+ }
+
+ return cfg80211_nan_set_local_schedule(rdev, wdev, sched);
+}
+
static int nl80211_get_protocol_features(struct sk_buff *skb,
struct genl_info *info)
{
@@ -19227,6 +19487,12 @@ static const struct genl_small_ops nl80211_small_ops[] = {
.flags = GENL_UNS_ADMIN_PERM,
.internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV_UP),
},
+ {
+ .cmd = NL80211_CMD_NAN_SET_LOCAL_SCHED,
+ .doit = nl80211_nan_set_local_sched,
+ .flags = GENL_ADMIN_PERM,
+ .internal_flags = IFLAGS(NL80211_FLAG_NEED_WDEV_UP),
+ },
};
static struct genl_family nl80211_fam __ro_after_init = {
diff --git a/net/wireless/rdev-ops.h b/net/wireless/rdev-ops.h
index 2bad8b60b7c9..b886dedb25c6 100644
--- a/net/wireless/rdev-ops.h
+++ b/net/wireless/rdev-ops.h
@@ -1060,6 +1060,22 @@ rdev_nan_change_conf(struct cfg80211_registered_device *rdev,
return ret;
}
+static inline int
+rdev_nan_set_local_sched(struct cfg80211_registered_device *rdev,
+ struct wireless_dev *wdev,
+ struct cfg80211_nan_local_sched *sched)
+{
+ int ret;
+
+ trace_rdev_nan_set_local_sched(&rdev->wiphy, wdev, sched);
+ if (rdev->ops->nan_set_local_sched)
+ ret = rdev->ops->nan_set_local_sched(&rdev->wiphy, wdev, sched);
+ else
+ ret = -EOPNOTSUPP;
+ trace_rdev_return_int(&rdev->wiphy, ret);
+ return ret;
+}
+
static inline int rdev_set_mac_acl(struct cfg80211_registered_device *rdev,
struct net_device *dev,
struct cfg80211_acl_data *params)
diff --git a/net/wireless/trace.h b/net/wireless/trace.h
index af23f4fca90a..d32b83439363 100644
--- a/net/wireless/trace.h
+++ b/net/wireless/trace.h
@@ -2410,6 +2410,27 @@ TRACE_EVENT(rdev_del_nan_func,
WIPHY_PR_ARG, WDEV_PR_ARG, __entry->cookie)
);
+TRACE_EVENT(rdev_nan_set_local_sched,
+ TP_PROTO(struct wiphy *wiphy, struct wireless_dev *wdev,
+ struct cfg80211_nan_local_sched *sched),
+ TP_ARGS(wiphy, wdev, sched),
+ TP_STRUCT__entry(
+ WIPHY_ENTRY
+ WDEV_ENTRY
+ __array(u8, schedule, CFG80211_NAN_SCHED_NUM_TIME_SLOTS)
+ ),
+ TP_fast_assign(
+ WIPHY_ASSIGN;
+ WDEV_ASSIGN;
+ memcpy(__entry->schedule, sched->schedule,
+ CFG80211_NAN_SCHED_NUM_TIME_SLOTS);
+ ),
+ TP_printk(WIPHY_PR_FMT ", " WDEV_PR_FMT ", schedule: %s",
+ WIPHY_PR_ARG, WDEV_PR_ARG,
+ __print_array(__entry->schedule,
+ CFG80211_NAN_SCHED_NUM_TIME_SLOTS, 1))
+);
+
TRACE_EVENT(rdev_set_mac_acl,
TP_PROTO(struct wiphy *wiphy, struct net_device *netdev,
struct cfg80211_acl_data *params),
@@ -4276,6 +4297,23 @@ TRACE_EVENT(cfg80211_incumbent_signal_notify,
TP_printk(WIPHY_PR_FMT ", " CHAN_DEF_PR_FMT ", signal_interference_bitmap=0x%x",
WIPHY_PR_ARG, CHAN_DEF_PR_ARG, __entry->signal_interference_bitmap)
);
+
+TRACE_EVENT(cfg80211_nan_sched_update_done,
+ TP_PROTO(struct wiphy *wiphy, struct wireless_dev *wdev, bool success),
+ TP_ARGS(wiphy, wdev, success),
+ TP_STRUCT__entry(
+ WIPHY_ENTRY
+ WDEV_ENTRY
+ __field(bool, success)
+ ),
+ TP_fast_assign(
+ WIPHY_ASSIGN;
+ WDEV_ASSIGN;
+ __entry->success = success;
+ ),
+ TP_printk(WIPHY_PR_FMT ", " WDEV_PR_FMT " success=%d",
+ WIPHY_PR_ARG, WDEV_PR_ARG, __entry->success)
+);
#endif /* !__RDEV_OPS_TRACE || TRACE_HEADER_MULTI_READ */
#undef TRACE_INCLUDE_PATH
--
2.34.1
^ permalink raw reply related
* [PATCH v5 wireless-next 00/12] wifi: cfg80211/nl80211: Add NAN Data Path support
From: Miri Korenblit @ 2026-03-18 12:39 UTC (permalink / raw)
To: linux-wireless
This series adds support for Neighbor Awareness Networking (NAN) Data
Path in cfg80211/nl80211. This includes support for:
- Local and peer NAN schedule configuration
- A new netdev interface type for NAN data communication
- NAN management and data stations
- TX/RX support
Miri
---
Avraham Stern (1):
wifi: cfg80211: allow protected action frame TX for NAN
Daniel Gabay (1):
wifi: cfg80211: allow ToDS=0/FromDS=0 data frames on NAN data
interfaces
Ilan Peer (1):
wifi: ieee80211: Add some missing NAN definitions
Miri Korenblit (9):
wifi: cfg80211: Add an API to configure local NAN schedule
wifi: cfg80211: make sure NAN chandefs are valid
wifi: cfg80211: add support for NAN data interface
wifi: cfg80211: separately store HT, VHT and HE capabilities for NAN
wifi: nl80211: add support for NAN stations
wifi: nl80211: define an API for configuring the NAN peer's schedule
wifi: nl80211: allow reporting spurious NAN Data frames
wifi: nl80211: add NL80211_CMD_NAN_ULW_UPDATE notification
wifi: nl80211: Add a notification to notify NAN channel evacuation
include/linux/ieee80211-nan.h | 7 +-
include/net/cfg80211.h | 265 +++++++++-
include/uapi/linux/nl80211.h | 232 ++++++++-
net/mac80211/cfg.c | 1 +
net/mac80211/chan.c | 2 +
net/mac80211/iface.c | 3 +
net/mac80211/rx.c | 2 +
net/mac80211/util.c | 1 +
net/wireless/chan.c | 2 +
net/wireless/core.c | 130 ++++-
net/wireless/core.h | 10 +
net/wireless/mlme.c | 13 +-
net/wireless/nl80211.c | 905 ++++++++++++++++++++++++++++++++--
net/wireless/rdev-ops.h | 32 ++
net/wireless/reg.c | 27 +-
net/wireless/sysfs.c | 27 +-
net/wireless/trace.h | 105 ++++
net/wireless/util.c | 26 +-
18 files changed, 1713 insertions(+), 77 deletions(-)
---
v2: add commit "wifi: cfg80211: remove unneeded call to cfg80211_leave"
to the series. It was sent separately but should really be part of this
series, otherwise it contains a deadlock
V3: added more attributes to the local schedule API, added 2 more APIs
(ULW and channel evacuation notification), and fixed the documentation.
v4: Fixed not closing NAN DATA interface before the NAN one in case the
socket is closed
v5: Allow local schedule setting with no blob if the schedule is empty,
i.e. in teardown case
--
2.34.1
^ permalink raw reply
* Re: [PATCH] wireless-regdb: Add regulatory info for CEPT countries FO, GI, IM, SM and VA listed by WiFi Alliance
From: Chen-Yu Tsai @ 2026-03-18 8:33 UTC (permalink / raw)
To: Ping-Ke Shih; +Cc: linux-wireless, wireless-regdb
In-Reply-To: <20260318064834.103731-1-pkshih@gmail.com>
On Wed, Mar 18, 2026 at 2:48 PM Ping-Ke Shih <pkshih@gmail.com> wrote:
>
> From: Ping-Ke Shih <pkshih@realtek.com>
>
> In commit 5a8ced5ad313 ("wireless-regdb: Update regulatory info for CEPT
> countries for 6GHz listed by WiFi Alliance"), the following are skipped as
> they do not have corresponding entries in the database yet.
>
> - Faroe Islands (FO)
> - Gibraltar (GI)
> - Isle of Man (IM)
> - San Marino (SM)
> - Holy See (Vatican City State) (VA)
Confirmed the list. Also checked against another source [1].
[1] https://help.ui.com/hc/en-us/articles/8691786444567-Regions-Supporting-6-GHz
> Look up the CEPT decisions [2], and add entries along with decisions [3],
> [4] and [5] for 2/5/6 GHz regulations.
>
> The 2 GHz band by ECC Decision (11)05 [3], which the adoption of ERC
> Recommendation 70-03 [6], the regulations and the frequency bands to be
> used for Short Range Devices (SRDs) are included in specific annexes to
The 2 GHz band is now governed by ERC Recommendation 70-03 [6] with the
withdrawal of ERC Decision ERC/DEC/(01)07 by ECC Decision (11)05 [3].
> this Recommendation. By ERC Recommendation 70-03 annex 3 (WIDEBAND DATA
> TRANSMISSION SYSTEMS):
>
> * 2400-2483.5 MHz
> - 100 mW
Looks correct.
ERC Recommendation 70-03 annex 3 also includes the 60GHz range (57-71 GHz).
Can you check that? We would need some other source of information like
the WiFi Alliance page to verify that the countries have adopted the
decision. I don't think the CEPT decisions are directly binding.
The end of the document also has a table of the implementation status
of some bands in some countries. It is obvious the table is not complete.
> The 5 GHz band by ECC Decision (04)08 [4]:
>
> * 5150-5250 MHz
> - 200 mW
> - Indoor use
> * 5250-5350 MHz
> - 200 mW (due to TPC required, -3dBm, 100 mW is adopted)
> - DFS, TPC
> - Indoor use
> * 5470-5725 MHz
> - 1 W (due to TPC required, -3dBm, 500 mW is adopted)
> - DFS, TPC
> - Indoor and outdoor use
>
> The 6 GHz band by ECC Decision (20)01 [5]:
>
> * LPI devices (adopted)
> - 5945-6425 MHz
> - Restricted to indoor use only
> - Maximum mean e.i.r.p.: 23 dBm
> - Maximum mean e.i.r.p. density: 10 dBm/MHz
> * VLP devices
> - 5945-6425 MHz
> - Indoors and outdoors
> - Maximum mean e.i.r.p.: 14 dBm
> - Maximum mean e.i.r.p. density: 1 dBm/MHz
Looks correct.
> [1] https://www.wi-fi.org/regulations-enabling-6-ghz-wi-fi
> [2] https://docdb.cept.org/document/category/ECC_Decisions?status=ACTIVE
> [3] https://docdb.cept.org/download/1535
> [4] https://docdb.cept.org/download/4501
> [5] https://docdb.cept.org/download/4685
> [6] https://docdb.cept.org/download/3700
This doesn't seem to be the latest version. The online version is here:
https://docdb.cept.org/document/845
and shows the latest revision was published on Feb 13, 2026.
> Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
> ---
> db.txt | 35 +++++++++++++++++++++++++++++++++++
> 1 file changed, 35 insertions(+)
>
> diff --git a/db.txt b/db.txt
> index d54ef78e0da8..e2afb145ede5 100644
> --- a/db.txt
> +++ b/db.txt
> @@ -731,6 +731,13 @@ country FM: DFS-FCC
> (5490 - 5730 @ 160), (24), DFS
> (5735 - 5835 @ 80), (30)
>
> +country FO: DFS-ETSI
> + (2402 - 2483.5 @ 40), (100 mW)
This should start at 2400 for all the newly added countries.
> + (5150 - 5250 @ 80), (200 mW), NO-OUTDOOR, AUTO-BW
> + (5250 - 5350 @ 80), (100 mW), DFS, NO-OUTDOOR, AUTO-BW
> + (5470 - 5725 @ 160), (500 mW), DFS
> + (5945 - 6425 @ 320), (23), NO-OUTDOOR
> +
> # FR as part of EU/CEPT accepted decisions 2005/513/EC (5GHz RLAN, EN 301 893)
> # and 2006/771/EC (amended by 2008/432/EC, Short-Range Devices, EN 300 440)
> # EU decision 2005/513/EC: https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:02005D0513-20070213
> @@ -801,6 +808,13 @@ country GH: DFS-FCC
> (5490 - 5730 @ 160), (24), DFS
> (5735 - 5835 @ 80), (30)
>
> +country GI: DFS-ETSI
> + (2402 - 2483.5 @ 40), (100 mW)
> + (5150 - 5250 @ 80), (200 mW), NO-OUTDOOR, AUTO-BW
> + (5250 - 5350 @ 80), (100 mW), DFS, NO-OUTDOOR, AUTO-BW
> + (5470 - 5725 @ 160), (500 mW), DFS
> + (5945 - 6425 @ 320), (23), NO-OUTDOOR
> +
> country GL: DFS-ETSI
> (2402 - 2482 @ 40), (20)
> (5170 - 5250 @ 80), (20), AUTO-BW, wmmrule=ETSI
> @@ -976,6 +990,13 @@ country IL: DFS-ETSI
> (5725 - 5875 @ 80), (25 mW), AUTO-BW
> (5945 - 6425 @ 320), (200 mW), NO-OUTDOOR
>
> +country IM: DFS-ETSI
> + (2402 - 2483.5 @ 40), (100 mW)
> + (5150 - 5250 @ 80), (200 mW), NO-OUTDOOR, AUTO-BW
> + (5250 - 5350 @ 80), (100 mW), DFS, NO-OUTDOOR, AUTO-BW
> + (5470 - 5725 @ 160), (500 mW), DFS
> + (5945 - 6425 @ 320), (23), NO-OUTDOOR
> +
> # Source:
> # https://dot.gov.in/spectrummanagement/delicensing-24-24835-ghz-band-gsr-45-e-5150-5350-ghz-gsr-46-e-and-5725-5875-ghz
> # https://dot.gov.in/spectrummanagement/license-exemption-5-ghz-gsr-1048e-dated-22102018
> @@ -1855,6 +1876,13 @@ country SK: DFS-ETSI
> # 60 GHz band channels 1-4 (ETSI EN 302 567)
> (57000 - 66000 @ 2160), (40)
>
> +country SM: DFS-ETSI
> + (2402 - 2483.5 @ 40), (100 mW)
> + (5150 - 5250 @ 80), (200 mW), NO-OUTDOOR, AUTO-BW
> + (5250 - 5350 @ 80), (100 mW), DFS, NO-OUTDOOR, AUTO-BW
> + (5470 - 5725 @ 160), (500 mW), DFS
> + (5945 - 6425 @ 320), (23), NO-OUTDOOR
> +
> # Source:
> # Regulation N° 2004-005 ART/DG/DRC/D.Rég
> country SN: DFS-FCC
> @@ -2072,6 +2100,13 @@ country UZ: DFS-ETSI
> (5170 - 5250 @ 80), (20), AUTO-BW
> (5250 - 5330 @ 80), (20), DFS, AUTO-BW
>
> +country VA: DFS-ETSI
> + (2402 - 2483.5 @ 40), (100 mW)
> + (5150 - 5250 @ 80), (200 mW), NO-OUTDOOR, AUTO-BW
> + (5250 - 5350 @ 80), (100 mW), DFS, NO-OUTDOOR, AUTO-BW
> + (5470 - 5725 @ 160), (500 mW), DFS
> + (5945 - 6425 @ 320), (23), NO-OUTDOOR
> +
> # Source:
> # http://www.ntrc.vc/regulations/Jun_2006_Spectrum_Managment_Regulations.pdf
> country VC: DFS-ETSI
> --
> 2.25.1
>
^ permalink raw reply
* [PATCH v3] wifi: rsi_91x_usb: do not pause rfkill polling when stopping mac80211
From: Ville Nummela @ 2026-03-18 8:19 UTC (permalink / raw)
To: linux-wireless; +Cc: Ville Nummela
Removing rsi_91x USB adapter could cause rtnetlink to lock up.
When rsi_mac80211_stop is called, wiphy_lock is locked. Call to
wiphy_rfkill_stop_polling would wait until the work queue has
finished, but because the work queue waits for wiphy_lock, that
would never happen.
Moving the call to rsi_disconnect avoids the lock up.
Signed-off-by: Ville Nummela <ville.nummela@kempower.com>
---
Changes since v2:
- Fix whitespaces
drivers/net/wireless/rsi/rsi_91x_mac80211.c | 17 ++++++++++++++++-
drivers/net/wireless/rsi/rsi_91x_usb.c | 2 ++
drivers/net/wireless/rsi/rsi_common.h | 1 +
3 files changed, 19 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wireless/rsi/rsi_91x_mac80211.c b/drivers/net/wireless/rsi/rsi_91x_mac80211.c
index c7ae8031436a..3faf2235728b 100644
--- a/drivers/net/wireless/rsi/rsi_91x_mac80211.c
+++ b/drivers/net/wireless/rsi/rsi_91x_mac80211.c
@@ -325,6 +325,22 @@ void rsi_mac80211_detach(struct rsi_hw *adapter)
}
EXPORT_SYMBOL_GPL(rsi_mac80211_detach);
+/**
+ * rsi_mac80211_rfkill_exit() - This function is used to stop rfkill polling
+ * when the device is removed.
+ * @adapter: Pointer to the adapter structure.
+ *
+ * Return: None.
+ */
+void rsi_mac80211_rfkill_exit(struct rsi_hw *adapter)
+{
+ struct ieee80211_hw *hw = adapter->hw;
+
+ if (hw)
+ wiphy_rfkill_stop_polling(hw->wiphy);
+}
+EXPORT_SYMBOL_GPL(rsi_mac80211_rfkill_exit);
+
/**
* rsi_indicate_tx_status() - This function indicates the transmit status.
* @adapter: Pointer to the adapter structure.
@@ -422,7 +438,6 @@ static void rsi_mac80211_stop(struct ieee80211_hw *hw, bool suspend)
rsi_dbg(ERR_ZONE, "===> Interface DOWN <===\n");
mutex_lock(&common->mutex);
common->iface_down = true;
- wiphy_rfkill_stop_polling(hw->wiphy);
/* Block all rx frames */
rsi_send_rx_filter_frame(common, 0xffff);
diff --git a/drivers/net/wireless/rsi/rsi_91x_usb.c b/drivers/net/wireless/rsi/rsi_91x_usb.c
index d83204701e27..8765cac6f875 100644
--- a/drivers/net/wireless/rsi/rsi_91x_usb.c
+++ b/drivers/net/wireless/rsi/rsi_91x_usb.c
@@ -877,6 +877,8 @@ static void rsi_disconnect(struct usb_interface *pfunction)
if (!adapter)
return;
+ rsi_mac80211_rfkill_exit(adapter);
+
rsi_mac80211_detach(adapter);
if (IS_ENABLED(CONFIG_RSI_COEX) && adapter->priv->coex_mode > 1 &&
diff --git a/drivers/net/wireless/rsi/rsi_common.h b/drivers/net/wireless/rsi/rsi_common.h
index 7aa5124575cf..591602beeec6 100644
--- a/drivers/net/wireless/rsi/rsi_common.h
+++ b/drivers/net/wireless/rsi/rsi_common.h
@@ -79,6 +79,7 @@ static inline int rsi_kill_thread(struct rsi_thread *handle)
}
void rsi_mac80211_detach(struct rsi_hw *hw);
+void rsi_mac80211_rfkill_exit(struct rsi_hw *hw);
u16 rsi_get_connected_channel(struct ieee80211_vif *vif);
struct rsi_hw *rsi_91x_init(u16 oper_mode);
void rsi_91x_deinit(struct rsi_hw *adapter);
--
2.34.1
^ permalink raw reply related
* Re: [PATCH v2] wifi: rsi_91x_usb: do not pause rfkill polling when stopping mac80211
From: Johannes Berg @ 2026-03-18 8:06 UTC (permalink / raw)
To: Ville Nummela, linux-wireless
In-Reply-To: <20260318061018.Horde.a06VxaHRNwVUkzqm2u-EwFA@www.nummela.org>
On Wed, 2026-03-18 at 06:10 +0000, Ville Nummela wrote:
>
> @@ -422,7 +438,6 @@ static void rsi_mac80211_stop(struct ieee80211_hw
> *hw, bool suspend)
> rsi_dbg(ERR_ZONE, "===> Interface DOWN <===\n");
> mutex_lock(&common->mutex);
> common->iface_down = true;
> - wiphy_rfkill_stop_polling(hw->wiphy);
This patch is severely whitespace damaged and cannot be applied.
johannes
^ permalink raw reply
* Re: [syzbot] [wireless?] WARNING in cfg80211_chandef_create
From: Johannes Berg @ 2026-03-18 8:00 UTC (permalink / raw)
To: Lachlan Hodges, syzbot
Cc: linux-kernel, linux-wireless, netdev, syzkaller-bugs
In-Reply-To: <tffrshs5h76cfj3k6lelrx6lmm7qtc7hww7r554xtnhfnsw2ip@dqktqrq2k47q>
On Wed, 2026-03-18 at 11:53 +1100, Lachlan Hodges wrote:
> > HEAD commit: b84a0ebe421c Add linux-next specific files for 20260313
>
> I think this didn't have your fix already?
Indeed, looks like that tree got cut between your and my commits, even
though they less than eight hours apart :)
johannes
^ permalink raw reply
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