Johannes Berg says:

====================
Another set of updates:
 - various small fixes for ath10k/ath12k/mwifiex/rsi
 - cfg80211 fix for HE bitrate overflow
 - mac80211 fixes
   - S1G beacon handling in scan
   - skb tailroom handling for HW encryption
   - CSA fix for multi-link
   - handling of disabled links during association

* tag 'wireless-2026-11-22' of https://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless:
  wifi: cfg80211: ignore link disabled flag from userspace
  wifi: mac80211: apply advertised TTLM from association response
  wifi: mac80211: parse all TTLM entries
  wifi: mac80211: don't increment crypto_tx_tailroom_needed_cnt twice
  wifi: mac80211: don't perform DA check on S1G beacon
  wifi: ath12k: Fix wrong P2P device link id issue
  wifi: ath12k: fix dead lock while flushing management frames
  wifi: ath12k: Fix scan state stuck in ABORTING after cancel_remain_on_channel
  wifi: ath12k: cancel scan only on active scan vdev
  wifi: mwifiex: Fix a loop in mwifiex_update_ampdu_rxwinsize()
  wifi: mac80211: correctly check if CSA is active
  wifi: cfg80211: Fix bitrate calculation overflow for HE rates
  wifi: rsi: Fix memory corruption due to not set vif driver data size
  wifi: ath12k: don't force radio frequency check in freq_to_idx()
  wifi: ath12k: fix dma_free_coherent() pointer
  wifi: ath10k: fix dma_free_coherent() pointer
====================

Link: https://patch.msgid.link/20260122110248.15450-3-johannes@sipsolutions.net
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski
2026-01-22 07:54:30 -08:00
15 changed files with 171 additions and 150 deletions

View File

@@ -1727,8 +1727,8 @@ static void _ath10k_ce_free_pipe(struct ath10k *ar, int ce_id)
(ce_state->src_ring->nentries *
sizeof(struct ce_desc) +
CE_DESC_RING_ALIGN),
ce_state->src_ring->base_addr_owner_space,
ce_state->src_ring->base_addr_ce_space);
ce_state->src_ring->base_addr_owner_space_unaligned,
ce_state->src_ring->base_addr_ce_space_unaligned);
kfree(ce_state->src_ring);
}
@@ -1737,8 +1737,8 @@ static void _ath10k_ce_free_pipe(struct ath10k *ar, int ce_id)
(ce_state->dest_ring->nentries *
sizeof(struct ce_desc) +
CE_DESC_RING_ALIGN),
ce_state->dest_ring->base_addr_owner_space,
ce_state->dest_ring->base_addr_ce_space);
ce_state->dest_ring->base_addr_owner_space_unaligned,
ce_state->dest_ring->base_addr_ce_space_unaligned);
kfree(ce_state->dest_ring);
}
@@ -1758,8 +1758,8 @@ static void _ath10k_ce_free_pipe_64(struct ath10k *ar, int ce_id)
(ce_state->src_ring->nentries *
sizeof(struct ce_desc_64) +
CE_DESC_RING_ALIGN),
ce_state->src_ring->base_addr_owner_space,
ce_state->src_ring->base_addr_ce_space);
ce_state->src_ring->base_addr_owner_space_unaligned,
ce_state->src_ring->base_addr_ce_space_unaligned);
kfree(ce_state->src_ring);
}
@@ -1768,8 +1768,8 @@ static void _ath10k_ce_free_pipe_64(struct ath10k *ar, int ce_id)
(ce_state->dest_ring->nentries *
sizeof(struct ce_desc_64) +
CE_DESC_RING_ALIGN),
ce_state->dest_ring->base_addr_owner_space,
ce_state->dest_ring->base_addr_ce_space);
ce_state->dest_ring->base_addr_owner_space_unaligned,
ce_state->dest_ring->base_addr_ce_space_unaligned);
kfree(ce_state->dest_ring);
}

View File

@@ -984,8 +984,8 @@ void ath12k_ce_free_pipes(struct ath12k_base *ab)
dma_free_coherent(ab->dev,
pipe->src_ring->nentries * desc_sz +
CE_DESC_RING_ALIGN,
pipe->src_ring->base_addr_owner_space,
pipe->src_ring->base_addr_ce_space);
pipe->src_ring->base_addr_owner_space_unaligned,
pipe->src_ring->base_addr_ce_space_unaligned);
kfree(pipe->src_ring);
pipe->src_ring = NULL;
}
@@ -995,8 +995,8 @@ void ath12k_ce_free_pipes(struct ath12k_base *ab)
dma_free_coherent(ab->dev,
pipe->dest_ring->nentries * desc_sz +
CE_DESC_RING_ALIGN,
pipe->dest_ring->base_addr_owner_space,
pipe->dest_ring->base_addr_ce_space);
pipe->dest_ring->base_addr_owner_space_unaligned,
pipe->dest_ring->base_addr_ce_space_unaligned);
kfree(pipe->dest_ring);
pipe->dest_ring = NULL;
}
@@ -1007,8 +1007,8 @@ void ath12k_ce_free_pipes(struct ath12k_base *ab)
dma_free_coherent(ab->dev,
pipe->status_ring->nentries * desc_sz +
CE_DESC_RING_ALIGN,
pipe->status_ring->base_addr_owner_space,
pipe->status_ring->base_addr_ce_space);
pipe->status_ring->base_addr_owner_space_unaligned,
pipe->status_ring->base_addr_ce_space_unaligned);
kfree(pipe->status_ring);
pipe->status_ring = NULL;
}

View File

@@ -5495,7 +5495,8 @@ static void ath12k_mac_op_cancel_hw_scan(struct ieee80211_hw *hw,
for_each_set_bit(link_id, &links_map, ATH12K_NUM_MAX_LINKS) {
arvif = wiphy_dereference(hw->wiphy, ahvif->link[link_id]);
if (!arvif || arvif->is_started)
if (!arvif || !arvif->is_created ||
arvif->ar->scan.arvif != arvif)
continue;
ar = arvif->ar;
@@ -9172,6 +9173,9 @@ static void ath12k_mac_op_tx(struct ieee80211_hw *hw,
return;
}
} else {
if (vif->type == NL80211_IFTYPE_P2P_DEVICE)
link_id = ATH12K_FIRST_SCAN_LINK;
else
link_id = 0;
}
@@ -12142,6 +12146,9 @@ static void ath12k_mac_op_flush(struct ieee80211_hw *hw, struct ieee80211_vif *v
if (drop)
return;
for_each_ar(ah, ar, i)
wiphy_work_flush(hw->wiphy, &ar->wmi_mgmt_tx_work);
/* vif can be NULL when flush() is considered for hw */
if (!vif) {
for_each_ar(ah, ar, i)
@@ -12149,9 +12156,6 @@ static void ath12k_mac_op_flush(struct ieee80211_hw *hw, struct ieee80211_vif *v
return;
}
for_each_ar(ah, ar, i)
wiphy_work_flush(hw->wiphy, &ar->wmi_mgmt_tx_work);
ahvif = ath12k_vif_to_ahvif(vif);
links = ahvif->links_map;
for_each_set_bit(link_id, &links, IEEE80211_MLD_MAX_NUM_LINKS) {
@@ -13343,7 +13347,7 @@ static int ath12k_mac_op_cancel_remain_on_channel(struct ieee80211_hw *hw,
ath12k_scan_abort(ar);
cancel_delayed_work_sync(&ar->scan.timeout);
wiphy_work_cancel(hw->wiphy, &ar->scan.vdev_clean_wk);
wiphy_work_flush(hw->wiphy, &ar->scan.vdev_clean_wk);
return 0;
}

View File

@@ -6575,17 +6575,10 @@ static int freq_to_idx(struct ath12k *ar, int freq)
if (!sband)
continue;
for (ch = 0; ch < sband->n_channels; ch++, idx++) {
if (sband->channels[ch].center_freq <
KHZ_TO_MHZ(ar->freq_range.start_freq) ||
sband->channels[ch].center_freq >
KHZ_TO_MHZ(ar->freq_range.end_freq))
continue;
for (ch = 0; ch < sband->n_channels; ch++, idx++)
if (sband->channels[ch].center_freq == freq)
goto exit;
}
}
exit:
return idx;

View File

@@ -825,7 +825,7 @@ void mwifiex_update_rxreor_flags(struct mwifiex_adapter *adapter, u8 flags)
static void mwifiex_update_ampdu_rxwinsize(struct mwifiex_adapter *adapter,
bool coex_flag)
{
u8 i;
u8 i, j;
u32 rx_win_size;
struct mwifiex_private *priv;
@@ -863,8 +863,8 @@ static void mwifiex_update_ampdu_rxwinsize(struct mwifiex_adapter *adapter,
if (rx_win_size != priv->add_ba_param.rx_win_size) {
if (!priv->media_connected)
continue;
for (i = 0; i < MAX_NUM_TID; i++)
mwifiex_11n_delba(priv, i);
for (j = 0; j < MAX_NUM_TID; j++)
mwifiex_11n_delba(priv, j);
}
}
}

View File

@@ -2035,6 +2035,7 @@ int rsi_mac80211_attach(struct rsi_common *common)
hw->queues = MAX_HW_QUEUES;
hw->extra_tx_headroom = RSI_NEEDED_HEADROOM;
hw->vif_data_size = sizeof(struct vif_priv);
hw->max_rates = 1;
hw->max_rate_tries = MAX_RETRIES;

View File

@@ -3221,8 +3221,6 @@ struct cfg80211_auth_request {
* if this is %NULL for a link, that link is not requested
* @elems: extra elements for the per-STA profile for this link
* @elems_len: length of the elements
* @disabled: If set this link should be included during association etc. but it
* should not be used until enabled by the AP MLD.
* @error: per-link error code, must be <= 0. If there is an error, then the
* operation as a whole must fail.
*/
@@ -3230,7 +3228,6 @@ struct cfg80211_assoc_link {
struct cfg80211_bss *bss;
const u8 *elems;
size_t elems_len;
bool disabled;
int error;
};

View File

@@ -2880,8 +2880,9 @@ enum nl80211_commands {
* index. If the userspace includes more RNR elements than number of
* MBSSID elements then these will be added in every EMA beacon.
*
* @NL80211_ATTR_MLO_LINK_DISABLED: Flag attribute indicating that the link is
* disabled.
* @NL80211_ATTR_MLO_LINK_DISABLED: Unused. It was used to indicate that a link
* is disabled during association. However, the AP will send the
* information by including a TTLM in the association response.
*
* @NL80211_ATTR_BSS_DUMP_INCLUDE_USE_DATA: Include BSS usage data, i.e.
* include BSSes that can only be used in restricted scenarios and/or

View File

@@ -451,8 +451,6 @@ struct ieee80211_mgd_assoc_data {
struct ieee80211_conn_settings conn;
u16 status;
bool disabled;
} link[IEEE80211_MLD_MAX_NUM_LINKS];
u8 ap_addr[ETH_ALEN] __aligned(2);

View File

@@ -350,6 +350,8 @@ static int ieee80211_check_concurrent_iface(struct ieee80211_sub_if_data *sdata,
/* we hold the RTNL here so can safely walk the list */
list_for_each_entry(nsdata, &local->interfaces, list) {
if (nsdata != sdata && ieee80211_sdata_running(nsdata)) {
struct ieee80211_link_data *link;
/*
* Only OCB and monitor mode may coexist
*/
@@ -376,8 +378,10 @@ static int ieee80211_check_concurrent_iface(struct ieee80211_sub_if_data *sdata,
* will not add another interface while any channel
* switch is active.
*/
if (nsdata->vif.bss_conf.csa_active)
for_each_link_data(nsdata, link) {
if (link->conf->csa_active)
return -EBUSY;
}
/*
* The remaining checks are only performed for interfaces

View File

@@ -987,6 +987,7 @@ void ieee80211_reenable_keys(struct ieee80211_sub_if_data *sdata)
if (ieee80211_sdata_running(sdata)) {
list_for_each_entry(key, &sdata->key_list, list) {
if (!(key->flags & KEY_FLAG_TAINTED))
increment_tailroom_need_count(sdata);
ieee80211_key_enable_hw_accel(key);
}

View File

@@ -6161,6 +6161,98 @@ static bool ieee80211_get_dtim(const struct cfg80211_bss_ies *ies,
return true;
}
static u16 ieee80211_get_ttlm(u8 bm_size, u8 *data)
{
if (bm_size == 1)
return *data;
return get_unaligned_le16(data);
}
static int
ieee80211_parse_adv_t2l(struct ieee80211_sub_if_data *sdata,
const struct ieee80211_ttlm_elem *ttlm,
struct ieee80211_adv_ttlm_info *ttlm_info)
{
/* The element size was already validated in
* ieee80211_tid_to_link_map_size_ok()
*/
u8 control, link_map_presence, map_size, tid;
u8 *pos;
memset(ttlm_info, 0, sizeof(*ttlm_info));
pos = (void *)ttlm->optional;
control = ttlm->control;
if ((control & IEEE80211_TTLM_CONTROL_DIRECTION) !=
IEEE80211_TTLM_DIRECTION_BOTH) {
sdata_info(sdata, "Invalid advertised T2L map direction\n");
return -EINVAL;
}
link_map_presence = *pos;
pos++;
if (control & IEEE80211_TTLM_CONTROL_SWITCH_TIME_PRESENT) {
ttlm_info->switch_time = get_unaligned_le16(pos);
/* Since ttlm_info->switch_time == 0 means no switch time, bump
* it by 1.
*/
if (!ttlm_info->switch_time)
ttlm_info->switch_time = 1;
pos += 2;
}
if (control & IEEE80211_TTLM_CONTROL_EXPECTED_DUR_PRESENT) {
ttlm_info->duration = pos[0] | pos[1] << 8 | pos[2] << 16;
pos += 3;
}
if (control & IEEE80211_TTLM_CONTROL_DEF_LINK_MAP) {
ttlm_info->map = 0xffff;
return 0;
}
if (control & IEEE80211_TTLM_CONTROL_LINK_MAP_SIZE)
map_size = 1;
else
map_size = 2;
/* According to Draft P802.11be_D3.0 clause 35.3.7.1.7, an AP MLD shall
* not advertise a TID-to-link mapping that does not map all TIDs to the
* same link set, reject frame if not all links have mapping
*/
if (link_map_presence != 0xff) {
sdata_info(sdata,
"Invalid advertised T2L mapping presence indicator\n");
return -EINVAL;
}
ttlm_info->map = ieee80211_get_ttlm(map_size, pos);
if (!ttlm_info->map) {
sdata_info(sdata,
"Invalid advertised T2L map for TID 0\n");
return -EINVAL;
}
pos += map_size;
for (tid = 1; tid < 8; tid++) {
u16 map = ieee80211_get_ttlm(map_size, pos);
if (map != ttlm_info->map) {
sdata_info(sdata, "Invalid advertised T2L map for tid %d\n",
tid);
return -EINVAL;
}
pos += map_size;
}
return 0;
}
static bool ieee80211_assoc_success(struct ieee80211_sub_if_data *sdata,
struct ieee80211_mgmt *mgmt,
struct ieee802_11_elems *elems,
@@ -6192,8 +6284,6 @@ static bool ieee80211_assoc_success(struct ieee80211_sub_if_data *sdata,
continue;
valid_links |= BIT(link_id);
if (assoc_data->link[link_id].disabled)
dormant_links |= BIT(link_id);
if (link_id != assoc_data->assoc_link_id) {
err = ieee80211_sta_allocate_link(sta, link_id);
@@ -6202,6 +6292,33 @@ static bool ieee80211_assoc_success(struct ieee80211_sub_if_data *sdata,
}
}
/*
* We do not support setting a negotiated TTLM during
* association. As such, we can assume that if there is a TTLM,
* then it is the currently active advertised TTLM.
* In that case, there must be exactly one TTLM that does not
* have a switch time set. This mapping should also leave us
* with at least one usable link.
*/
if (elems->ttlm_num > 1) {
sdata_info(sdata,
"More than one advertised TTLM in association response\n");
goto out_err;
} else if (elems->ttlm_num == 1) {
if (ieee80211_parse_adv_t2l(sdata, elems->ttlm[0],
&sdata->u.mgd.ttlm_info) ||
sdata->u.mgd.ttlm_info.switch_time != 0 ||
!(valid_links & sdata->u.mgd.ttlm_info.map)) {
sdata_info(sdata,
"Invalid advertised TTLM in association response\n");
goto out_err;
}
sdata->u.mgd.ttlm_info.active = true;
dormant_links =
valid_links & ~sdata->u.mgd.ttlm_info.map;
}
ieee80211_vif_set_links(sdata, valid_links, dormant_links);
}
@@ -6992,95 +7109,6 @@ static void ieee80211_tid_to_link_map_work(struct wiphy *wiphy,
sdata->u.mgd.ttlm_info.switch_time = 0;
}
static u16 ieee80211_get_ttlm(u8 bm_size, u8 *data)
{
if (bm_size == 1)
return *data;
else
return get_unaligned_le16(data);
}
static int
ieee80211_parse_adv_t2l(struct ieee80211_sub_if_data *sdata,
const struct ieee80211_ttlm_elem *ttlm,
struct ieee80211_adv_ttlm_info *ttlm_info)
{
/* The element size was already validated in
* ieee80211_tid_to_link_map_size_ok()
*/
u8 control, link_map_presence, map_size, tid;
u8 *pos;
memset(ttlm_info, 0, sizeof(*ttlm_info));
pos = (void *)ttlm->optional;
control = ttlm->control;
if ((control & IEEE80211_TTLM_CONTROL_DEF_LINK_MAP) ||
!(control & IEEE80211_TTLM_CONTROL_SWITCH_TIME_PRESENT))
return 0;
if ((control & IEEE80211_TTLM_CONTROL_DIRECTION) !=
IEEE80211_TTLM_DIRECTION_BOTH) {
sdata_info(sdata, "Invalid advertised T2L map direction\n");
return -EINVAL;
}
link_map_presence = *pos;
pos++;
ttlm_info->switch_time = get_unaligned_le16(pos);
/* Since ttlm_info->switch_time == 0 means no switch time, bump it
* by 1.
*/
if (!ttlm_info->switch_time)
ttlm_info->switch_time = 1;
pos += 2;
if (control & IEEE80211_TTLM_CONTROL_EXPECTED_DUR_PRESENT) {
ttlm_info->duration = pos[0] | pos[1] << 8 | pos[2] << 16;
pos += 3;
}
if (control & IEEE80211_TTLM_CONTROL_LINK_MAP_SIZE)
map_size = 1;
else
map_size = 2;
/* According to Draft P802.11be_D3.0 clause 35.3.7.1.7, an AP MLD shall
* not advertise a TID-to-link mapping that does not map all TIDs to the
* same link set, reject frame if not all links have mapping
*/
if (link_map_presence != 0xff) {
sdata_info(sdata,
"Invalid advertised T2L mapping presence indicator\n");
return -EINVAL;
}
ttlm_info->map = ieee80211_get_ttlm(map_size, pos);
if (!ttlm_info->map) {
sdata_info(sdata,
"Invalid advertised T2L map for TID 0\n");
return -EINVAL;
}
pos += map_size;
for (tid = 1; tid < 8; tid++) {
u16 map = ieee80211_get_ttlm(map_size, pos);
if (map != ttlm_info->map) {
sdata_info(sdata, "Invalid advertised T2L map for tid %d\n",
tid);
return -EINVAL;
}
pos += map_size;
}
return 0;
}
static void ieee80211_process_adv_ttlm(struct ieee80211_sub_if_data *sdata,
struct ieee802_11_elems *elems,
u64 beacon_ts)
@@ -9737,7 +9765,6 @@ int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata,
req, true, i,
&assoc_data->link[i].conn);
assoc_data->link[i].bss = link_cbss;
assoc_data->link[i].disabled = req->links[i].disabled;
if (!bss->uapsd_supported)
uapsd_supported = false;
@@ -10719,8 +10746,6 @@ int ieee80211_mgd_assoc_ml_reconf(struct ieee80211_sub_if_data *sdata,
&data->link[link_id].conn);
data->link[link_id].bss = link_cbss;
data->link[link_id].disabled =
req->add_links[link_id].disabled;
data->link[link_id].elems =
(u8 *)req->add_links[link_id].elems;
data->link[link_id].elems_len =

View File

@@ -347,8 +347,13 @@ void ieee80211_scan_rx(struct ieee80211_local *local, struct sk_buff *skb)
mgmt->da))
return;
} else {
/* Beacons are expected only with broadcast address */
if (!is_broadcast_ether_addr(mgmt->da))
/*
* Non-S1G beacons are expected only with broadcast address.
* S1G beacons only carry the SA so no DA check is required
* nor possible.
*/
if (!ieee80211_is_s1g_beacon(mgmt->frame_control) &&
!is_broadcast_ether_addr(mgmt->da))
return;
}

View File

@@ -12241,9 +12241,6 @@ static int nl80211_process_links(struct cfg80211_registered_device *rdev,
return -EINVAL;
}
}
links[link_id].disabled =
nla_get_flag(attrs[NL80211_ATTR_MLO_LINK_DISABLED]);
}
return 0;
@@ -12423,13 +12420,6 @@ static int nl80211_associate(struct sk_buff *skb, struct genl_info *info)
goto free;
}
if (req.links[req.link_id].disabled) {
GENL_SET_ERR_MSG(info,
"cannot have assoc link disabled");
err = -EINVAL;
goto free;
}
if (info->attrs[NL80211_ATTR_ASSOC_MLD_EXT_CAPA_OPS])
req.ext_mld_capa_ops =
nla_get_u16(info->attrs[NL80211_ATTR_ASSOC_MLD_EXT_CAPA_OPS]);

View File

@@ -1561,12 +1561,14 @@ static u32 cfg80211_calculate_bitrate_he(struct rate_info *rate)
tmp = result;
tmp *= SCALE;
do_div(tmp, mcs_divisors[rate->mcs]);
result = tmp;
/* and take NSS, DCM into account */
result = (result * rate->nss) / 8;
tmp *= rate->nss;
do_div(tmp, 8);
if (rate->he_dcm)
result /= 2;
do_div(tmp, 2);
result = tmp;
return result / 10000;
}