wifi: cfg80211: add NL80211_CMD_NAN_SET_NON_EVAC_CHANNELS

Add a new NL80211_CMD_NAN_SET_NON_EVAC_CHANNELS command that lets user
space mark a set of NAN channels as non-evacuable. The command carries
the channels as nested NL80211_ATTR_NAN_CHANNEL attributes (chandef
only) and replaces the entire set of non-evacuable channels; channels
not included in the list become evacuable again.

All provided channels must already exist in the current local schedule,
otherwise -ENOENT is returned. The command is rejected with -EBUSY
while a schedule update is pending.

This prevents the kernel from evacuating channels that carry NDC or
immutable schedules, so evacuating these channels would break existing
NDP connections.

Fixes: 154b0296c0 ("wifi: nl80211: Add a notification to notify NAN channel evacuation")
Signed-off-by: Andrei Otcheretianski <andrei.otcheretianski@intel.com>
Reviewed-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Link: https://patch.msgid.link/20260921215936.2efc32011e73.Ice8839058e114f6f9783079807df446388187dda@changeid
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
This commit is contained in:
Andrei Otcheretianski 2026-09-21 22:00:22 +03:00 committed by Johannes Berg
parent 96040137f2
commit 025ef5f4f6
7 changed files with 205 additions and 0 deletions

View File

@ -4264,6 +4264,21 @@ struct cfg80211_nan_local_sched {
struct cfg80211_nan_channel nan_channels[] __counted_by(n_channels);
};
/**
* struct cfg80211_nan_non_evac_channels - NAN non-evacuable channels
*
* This struct defines the set of NAN local schedule channels that must not
* be evacuated for concurrent operations.
*
* @n_channels: number of channel definitions in %chandefs.
* @chandefs: array of channel definitions that must not be evacuated. Each
* must match a channel of the current local schedule.
*/
struct cfg80211_nan_non_evac_channels {
u8 n_channels;
struct cfg80211_chan_def chandefs[] __counted_by(n_channels);
};
/**
* struct cfg80211_nan_peer_map - NAN peer schedule map
*
@ -5212,6 +5227,12 @@ struct mgmt_frame_regs {
* schedule, the full new schedule is provided - partial updates are not
* supported, and the new schedule completely replaces the previous one.
*
* @nan_set_non_evac_channels: set the list of local schedule channels that
* must not be evacuated for concurrent operations. The provided list
* replaces the previous set; channels of the current schedule that are
* not included become evacuable again. All provided channels are
* guaranteed by cfg80211 to belong to the current local schedule.
*
* @set_multicast_to_unicast: configure multicast to unicast conversion for BSS
*
* @get_txq_stats: Get TXQ stats for interface or phy. If wdev is %NULL, this
@ -5601,6 +5622,9 @@ struct cfg80211_ops {
int (*nan_set_peer_sched)(struct wiphy *wiphy,
struct wireless_dev *wdev,
struct cfg80211_nan_peer_sched *sched);
int (*nan_set_non_evac_channels)(struct wiphy *wiphy,
struct wireless_dev *wdev,
struct cfg80211_nan_non_evac_channels *channels);
int (*set_multicast_to_unicast)(struct wiphy *wiphy,
struct net_device *dev,
const bool enabled);

View File

@ -1383,6 +1383,13 @@
* 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.
* Setting a new schedule is always allowed and is never treated as an
* evacuation, even if it removes channels that were previously marked as
* non-evacuable with %NL80211_CMD_NAN_SET_NON_EVAC_CHANNELS. The
* non-evacuable marking is a per-channel property of the schedule:
* channels that remain in the new schedule keep their marking, channels
* that are removed simply lose it, and newly added channels are
* evacuable by default.
* @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)
@ -1427,6 +1434,28 @@
* @NL80211_CMD_STOP_PD: Stop the PD operation, identified by
* its %NL80211_ATTR_WDEV interface.
*
* @NL80211_CMD_NAN_SET_NON_EVAC_CHANNELS: Set the list of NAN local schedule
* channels that must not be evacuated. NAN must be operational
* (%NL80211_CMD_START_NAN was executed) and a local schedule must have
* been set (%NL80211_CMD_NAN_SET_LOCAL_SCHED). The command carries zero
* or more nested %NL80211_ATTR_NAN_CHANNEL attributes, each identifying a
* channel (by its channel definition) of the current local schedule that
* must not be evacuated for concurrent operations. The provided list
* replaces the previous set of non-evacuable channels; channels of the
* current schedule that are not included become evacuable again. All
* provided channels must exist in the current local schedule, otherwise
* the command fails. This is used to protect channels carrying NDC or
* immutable schedules, whose evacuation would break existing NDP
* connections.
* The non-evacuable marking is a per-channel property of the current
* local schedule and only affects evacuation for concurrent operations;
* it does not prevent the schedule itself from being changed. Removing a
* channel from the schedule with %NL80211_CMD_NAN_SET_LOCAL_SCHED is a
* user-initiated change, not an evacuation, and is allowed even for a
* non-evacuable channel. Across a schedule update, channels that remain
* keep their non-evacuable marking, removed channels lose it, and newly
* added channels are evacuable by default; issue this command again to
* change the non-evacuable set.
* @NL80211_CMD_MAX: highest used command number
* @__NL80211_CMD_AFTER_LAST: internal use
*/
@ -1705,6 +1734,8 @@ enum nl80211_commands {
NL80211_CMD_START_PD,
NL80211_CMD_STOP_PD,
NL80211_CMD_NAN_SET_NON_EVAC_CHANNELS,
/* add new commands above here */
/* used to define NL80211_CMD_MAX below */

View File

@ -348,6 +348,41 @@ int cfg80211_nan_set_local_schedule(struct cfg80211_registered_device *rdev,
return 0;
}
int cfg80211_nan_set_non_evac_channels(struct cfg80211_registered_device *rdev,
struct wireless_dev *wdev,
struct cfg80211_nan_non_evac_channels *channels)
{
lockdep_assert_held(&rdev->wiphy.mtx);
if (wdev->iftype != NL80211_IFTYPE_NAN || !wdev_running(wdev))
return -EINVAL;
/*
* Don't allow updating the non-evacuable channels while a deferred
* schedule update is pending, as the set of channels may still change.
*/
if (wdev->u.nan.sched_update_pending)
return -EBUSY;
/* All provided channels must belong to the current local schedule. */
for (int i = 0; i < channels->n_channels; i++) {
bool found = false;
for (int j = 0; j < wdev->u.nan.n_channels; j++) {
if (cfg80211_chandef_identical(&wdev->u.nan.chandefs[j],
&channels->chandefs[i])) {
found = true;
break;
}
}
if (!found)
return -ENOENT;
}
return rdev_nan_set_non_evac_channels(rdev, wdev, channels);
}
void cfg80211_stop_pd(struct cfg80211_registered_device *rdev,
struct wireless_dev *wdev)
{

View File

@ -578,6 +578,10 @@ int cfg80211_nan_set_local_schedule(struct cfg80211_registered_device *rdev,
struct wireless_dev *wdev,
struct cfg80211_nan_local_sched *sched);
int cfg80211_nan_set_non_evac_channels(struct cfg80211_registered_device *rdev,
struct wireless_dev *wdev,
struct cfg80211_nan_non_evac_channels *channels);
struct cfg80211_internal_bss *
cfg80211_bss_update(struct cfg80211_registered_device *rdev,
struct cfg80211_internal_bss *tmp,

View File

@ -17845,6 +17845,69 @@ static int nl80211_nan_set_local_sched(struct sk_buff *skb,
return cfg80211_nan_set_local_schedule(rdev, wdev, sched);
}
static int
nl80211_parse_non_evac_channel(struct cfg80211_registered_device *rdev,
struct nlattr *channel, struct genl_info *info,
struct cfg80211_chan_def *chandef)
{
struct nlattr **channel_parsed __free(kfree) =
kcalloc(NL80211_ATTR_MAX + 1, sizeof(*channel_parsed),
GFP_KERNEL);
int ret;
if (!channel_parsed)
return -ENOMEM;
ret = nla_parse_nested(channel_parsed, NL80211_ATTR_MAX, channel, NULL,
info->extack);
if (ret)
return ret;
return nl80211_parse_chandef(rdev, info->extack, channel_parsed,
chandef, false);
}
static int nl80211_nan_set_non_evac_channels(struct sk_buff *skb,
struct genl_info *info)
{
struct cfg80211_registered_device *rdev = info->user_ptr[0];
struct wireless_dev *wdev = info->user_ptr[1];
int rem, i = 0, n_channels = 0;
struct nlattr *channel;
if (wdev->iftype != NL80211_IFTYPE_NAN)
return -EOPNOTSUPP;
if (!wdev_running(wdev))
return -ENOTCONN;
/* Count how many channel attributes we got */
nlmsg_for_each_attr_type(channel, NL80211_ATTR_NAN_CHANNEL,
info->nlhdr, GENL_HDRLEN, rem)
n_channels++;
struct cfg80211_nan_non_evac_channels *channels __free(kfree) =
kzalloc(struct_size(channels, chandefs, n_channels),
GFP_KERNEL);
if (!channels)
return -ENOMEM;
channels->n_channels = n_channels;
nlmsg_for_each_attr_type(channel, NL80211_ATTR_NAN_CHANNEL,
info->nlhdr, GENL_HDRLEN, rem) {
int ret;
ret = nl80211_parse_non_evac_channel(rdev, channel, info,
&channels->chandefs[i]);
if (ret)
return ret;
i++;
}
return cfg80211_nan_set_non_evac_channels(rdev, wdev, channels);
}
static int nl80211_get_protocol_features(struct sk_buff *skb,
struct genl_info *info)
{
@ -20724,6 +20787,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_NON_EVAC_CHANNELS,
.doit = nl80211_nan_set_non_evac_channels,
.flags = GENL_ADMIN_PERM,
.internal_flags = IFLAGS(NL80211_FLAG_NEED_WDEV_UP),
},
};
static struct genl_family nl80211_fam __ro_after_init = {

View File

@ -1097,6 +1097,23 @@ rdev_nan_set_peer_sched(struct cfg80211_registered_device *rdev,
return ret;
}
static inline int
rdev_nan_set_non_evac_channels(struct cfg80211_registered_device *rdev,
struct wireless_dev *wdev,
struct cfg80211_nan_non_evac_channels *channels)
{
int ret;
trace_rdev_nan_set_non_evac_channels(&rdev->wiphy, wdev, channels);
if (rdev->ops->nan_set_non_evac_channels)
ret = rdev->ops->nan_set_non_evac_channels(&rdev->wiphy, wdev,
channels);
else
ret = -EOPNOTSUPP;
trace_rdev_return_int(&rdev->wiphy, ret);
return ret;
}
static inline int rdev_start_pd(struct cfg80211_registered_device *rdev,
struct wireless_dev *wdev)
{

View File

@ -2486,6 +2486,31 @@ TRACE_EVENT(rdev_nan_set_peer_sched,
)
);
TRACE_EVENT(rdev_nan_set_non_evac_channels,
TP_PROTO(struct wiphy *wiphy, struct wireless_dev *wdev,
struct cfg80211_nan_non_evac_channels *channels),
TP_ARGS(wiphy, wdev, channels),
TP_STRUCT__entry(
WIPHY_ENTRY
WDEV_ENTRY
__field(u8, n_channels)
__dynamic_array(u32, freqs, channels->n_channels)
),
TP_fast_assign(
u32 *freqs = __get_dynamic_array(freqs);
WIPHY_ASSIGN;
WDEV_ASSIGN;
__entry->n_channels = channels->n_channels;
for (int i = 0; i < channels->n_channels; i++)
freqs[i] = channels->chandefs[i].chan->center_freq;
),
TP_printk(WIPHY_PR_FMT ", " WDEV_PR_FMT ", n_channels: %u, freqs: %s",
WIPHY_PR_ARG, WDEV_PR_ARG, __entry->n_channels,
__print_array(__get_dynamic_array(freqs),
__entry->n_channels, sizeof(u32)))
);
TRACE_EVENT(rdev_set_mac_acl,
TP_PROTO(struct wiphy *wiphy, struct net_device *netdev,
struct cfg80211_acl_data *params),