Some new content already, notably:

- mac80211: major rework of station bandwidth handling,
              fixing issues with lower capability than AP
  - general: cleanups for EMLSR spec issues (drafts differed)
  - ath9k: GPIO interface improvements
  - ath12k: replace dynamic memory allocation in WMI RX path
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEpeA8sTs3M8SN2hR410qiO8sPaAAFAmnzRDMACgkQ10qiO8sP
 aADD4A//QO8xam/2bs/xUs2jg/1UT3m2i9RWfJT7FNi4J1eCOsSbvc8ioqieNkUy
 OA+TQnyeKziQAGB9k5R9LLbMvpRD3Z1LgSYwJdh+wjPFCiHWpr3pTtUh8q+/6vQ9
 z/2ksPnrV4xlRSPmqq1zbELSb1nJgJ8CeEZiOD4DQUqN+bsALid9BflK+0LmzbTl
 BgB0nuYUy49daIQe1yqg19qiHSStie/vwWzdpRU19rs27CSxxgGBeipa18a18dU8
 TgG0x90YdNw2DibyeRi3cuD4VK3vUfGOBAJ59p8I+Ib8qODTVK9+m+DYGEXA62Zo
 ky4KUu6EnBHTpDAftgXNqWVs/Yvv2ioNoVu1aIqoeAdCUF4IsOeViAjuntxdmFUo
 g0eqpFF550xvvDqQIC1iAX/SL7MC8xKJocY7jqIy7aseVOwxVSuzbZ4lSp3QjS+6
 ljtiVIWbwv9llOVhv5VIUWRloK8Ostycu2iQEAOLi5Hq8RDEk17aC4yu08ecDXGF
 PpECs2gWCNkAJORSwh9vks/msJVuGqCrO946lCMGdMeL/9GJyzHDIdgEFRfd8tkB
 dRQlY+gn9viyC+WVeVoIXxCWUKHFpmQvUUz3nF3NdxPBL1kljcpEJUO1cdkUmw6o
 HkJuJ1gOlAqgI6n6XImIY4/17GPeNmOey3rz7njXzkn/FS/pjNs=
 =cEhV
 -----END PGP SIGNATURE-----

Merge tag 'wireless-next-2026-04-30' of https://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless-next

Johannes Berg says:

====================
Some new content already, notably:
 - mac80211: major rework of station bandwidth handling,
             fixing issues with lower capability than AP
 - general: cleanups for EMLSR spec issues (drafts differed)
 - ath9k: GPIO interface improvements
 - ath12k: replace dynamic memory allocation in WMI RX path

* tag 'wireless-next-2026-04-30' of https://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless-next: (39 commits)
  wifi: brcmsmac: phy_lcn: Remove dead code in wlc_lcnphy_radio_2064_channel_tune_4313()
  wifi: mac80211: always allow transmitting null-data on TXQs
  wifi: mac80211: use kstrtobool_from_user() in debugfs callbacks
  wifi: cfg80211: validate cipher suite for NAN Data keys
  wifi: nl80211: check link is beaconing for color change
  wifi: mac80211: clarify an 802.11 VHT spec reference
  wifi: mac80211: fix per-station PHY capability bandwidth
  wifi: mac80211: clarify per-STA bandwidth handling
  wifi: nl80211: always validate AP operation/PHY regulatory
  wifi: cfg80211: provide HT/VHT operation for AP beacon
  wifi: nl80211: reject too short HT/VHT/HE/EHT capability/operation
  wifi: cfg80211: move AP HT/VHT/... operation to beacon info
  wifi: nl80211: reject beacons with bad HE operation
  wifi: cfg80211: remove HE/SAE H2E required fields
  wifi: mac80211: remove ieee80211_sta_cur_vht_bw()
  wifi: mac80211: clean up ieee80211_sta_cap_rx_bw()
  wifi: mac80211: clean up initial STA NSS/bandwidth handling
  wifi: mac80211: clean up STA NSS handling
  wifi: mac80211: simplify ieee80211_sta_rx_bw_to_chan_width()
  wifi: nl80211: document channel opmode change channel width
  ...
====================

Link: https://patch.msgid.link/20260430120304.249081-3-johannes@sipsolutions.net
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2026-04-30 17:10:20 -07:00
commit 6855a52318
45 changed files with 1097 additions and 859 deletions

View File

@ -11,6 +11,7 @@
#include <linux/device.h>
#include <linux/gpio/driver.h>
#include <linux/gpio/generic.h>
#include <linux/gpio/machine.h> /* For WLAN GPIOs */
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/mod_devicetable.h>
@ -214,6 +215,56 @@ static const struct of_device_id ath79_gpio_of_match[] = {
};
MODULE_DEVICE_TABLE(of, ath79_gpio_of_match);
#if IS_ENABLED(CONFIG_ATH9K_AHB)
/*
* This registers all of the ath79k GPIOs as descriptors to be picked
* directly from the ATH79K wifi driver if the two are jitted together
* in the same SoC.
*/
#define ATH79K_WIFI_DESCS 32
static int ath79_gpio_register_wifi_descriptors(struct device *dev,
const char *label)
{
struct gpiod_lookup_table *lookup;
int i;
/* Create a gpiod lookup using gpiochip-local offsets + 1 for NULL */
lookup = devm_kzalloc(dev,
struct_size(lookup, table, ATH79K_WIFI_DESCS + 1),
GFP_KERNEL);
if (!lookup)
return -ENOMEM;
/*
* Ugly system-wide lookup for the NULL device: we know this
* is already NULL but explicitly assign it here for people to
* know what is going on. (Yes this is an ugly legacy hack, live
* with it.)
*/
lookup->dev_id = NULL;
for (i = 0; i < ATH79K_WIFI_DESCS; i++) {
lookup->table[i] =
/*
* Set the HW offset on the chip and the lookup
* index to the same value, so looking up index 0
* will get HW offset 0, index 1 HW offset 1 etc.
*/
GPIO_LOOKUP_IDX(label, i, "ath9k", i, GPIO_ACTIVE_HIGH);
}
gpiod_add_lookup_table(lookup);
return 0;
}
#else
static int ath79_gpio_register_wifi_descriptors(struct device *dev,
const char *label)
{
return 0;
}
#endif
static int ath79_gpio_probe(struct platform_device *pdev)
{
struct gpio_generic_chip_config config;
@ -276,7 +327,11 @@ static int ath79_gpio_probe(struct platform_device *pdev)
girq->handler = handle_simple_irq;
}
return devm_gpiochip_add_data(dev, &ctrl->chip.gc, ctrl);
err = devm_gpiochip_add_data(dev, &ctrl->chip.gc, ctrl);
if (err)
return err;
return ath79_gpio_register_wifi_descriptors(dev, ctrl->chip.gc.label);
}
static struct platform_driver ath79_gpio_driver = {

View File

@ -583,31 +583,36 @@ static int ath12k_ahb_config_ext_irq(struct ath12k_base *ab)
netif_napi_add(irq_grp->napi_ndev, &irq_grp->napi,
ath12k_ahb_ext_grp_napi_poll);
for (j = 0; j < ATH12K_EXT_IRQ_NUM_MAX; j++) {
/* For TX ring, ensure that the ring mask and the
* tcl_to_wbm_rbm_map point to the same ring number.
*/
for (j = 0; j < DP_TCL_NUM_RING_MAX; j++) {
if (ring_mask->tx[i] &
BIT(ab->hal.tcl_to_wbm_rbm_map[j].wbm_ring_num)) {
BIT(ab->hal.tcl_to_wbm_rbm_map[j].wbm_ring_num) &&
num_irq < ATH12K_EXT_IRQ_NUM_MAX) {
irq_grp->irqs[num_irq++] =
wbm2host_tx_completions_ring1 - j;
}
}
if (ring_mask->rx[i] & BIT(j)) {
for (j = 0; j < ATH12K_EXT_IRQ_NUM_MAX; j++) {
if (ring_mask->rx[i] & BIT(j) &&
num_irq < ATH12K_EXT_IRQ_NUM_MAX) {
irq_grp->irqs[num_irq++] =
reo2host_destination_ring1 - j;
}
if (ring_mask->rx_err[i] & BIT(j))
if (ring_mask->rx_err[i] & BIT(j) &&
num_irq < ATH12K_EXT_IRQ_NUM_MAX)
irq_grp->irqs[num_irq++] = reo2host_exception;
if (ring_mask->rx_wbm_rel[i] & BIT(j))
if (ring_mask->rx_wbm_rel[i] & BIT(j) &&
num_irq < ATH12K_EXT_IRQ_NUM_MAX)
irq_grp->irqs[num_irq++] = wbm2host_rx_release;
if (ring_mask->reo_status[i] & BIT(j))
if (ring_mask->reo_status[i] & BIT(j) &&
num_irq < ATH12K_EXT_IRQ_NUM_MAX)
irq_grp->irqs[num_irq++] = reo2host_status;
if (ring_mask->rx_mon_dest[i] & BIT(j))
if (ring_mask->rx_mon_dest[i] & BIT(j) &&
num_irq < ATH12K_EXT_IRQ_NUM_MAX)
irq_grp->irqs[num_irq++] =
rxdma2host_monitor_destination_mac1;
}

View File

@ -2256,6 +2256,7 @@ void ath12k_core_deinit(struct ath12k_base *ab)
void ath12k_core_free(struct ath12k_base *ab)
{
timer_delete_sync(&ab->rx_replenish_retry);
ath12k_wmi_free();
destroy_workqueue(ab->workqueue_aux);
destroy_workqueue(ab->workqueue);
kfree(ab);
@ -2280,6 +2281,9 @@ struct ath12k_base *ath12k_core_alloc(struct device *dev, size_t priv_size,
if (!ab->workqueue_aux)
goto err_free_wq;
if (ath12k_wmi_alloc() < 0)
goto err_free_wq_aux;
mutex_init(&ab->core_lock);
spin_lock_init(&ab->base_lock);
init_completion(&ab->reset_complete);
@ -2314,6 +2318,8 @@ struct ath12k_base *ath12k_core_alloc(struct device *dev, size_t priv_size,
return ab;
err_free_wq_aux:
destroy_workqueue(ab->workqueue_aux);
err_free_wq:
destroy_workqueue(ab->workqueue);
err_sc_free:

View File

@ -146,7 +146,7 @@ static int ath12k_htt_tlv_ppdu_stats_parse(struct ath12k_base *ab,
}
int ath12k_dp_htt_tlv_iter(struct ath12k_base *ab, const void *ptr, size_t len,
int (*iter)(struct ath12k_base *ar, u16 tag, u16 len,
int (*iter)(struct ath12k_base *ab, u16 tag, u16 len,
const void *ptr, void *data),
void *data)
{

View File

@ -1523,7 +1523,7 @@ int ath12k_dp_tx_htt_srng_setup(struct ath12k_base *ab, u32 ring_id,
void ath12k_dp_htt_htc_t2h_msg_handler(struct ath12k_base *ab,
struct sk_buff *skb);
int ath12k_dp_htt_tlv_iter(struct ath12k_base *ab, const void *ptr, size_t len,
int (*iter)(struct ath12k_base *ar, u16 tag, u16 len,
int (*iter)(struct ath12k_base *ab, u16 tag, u16 len,
const void *ptr, void *data),
void *data);
int ath12k_dp_tx_htt_h2t_ver_req_msg(struct ath12k_base *ab);

View File

@ -1,7 +1,7 @@
/* SPDX-License-Identifier: BSD-3-Clause-Clear */
/*
* Copyright (c) 2018-2021 The Linux Foundation. All rights reserved.
* Copyright (c) 2021-2022 Qualcomm Innovation Center, Inc. All rights reserved.
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/
#ifndef ATH12K_HTC_H
@ -301,7 +301,7 @@ struct ath12k_htc {
u8 wmi_ep_count;
};
int ath12k_htc_init(struct ath12k_base *ar);
int ath12k_htc_init(struct ath12k_base *ab);
int ath12k_htc_wait_target(struct ath12k_htc *htc);
int ath12k_htc_start(struct ath12k_htc *htc);
int ath12k_htc_connect_service(struct ath12k_htc *htc,
@ -309,8 +309,8 @@ int ath12k_htc_connect_service(struct ath12k_htc *htc,
struct ath12k_htc_svc_conn_resp *conn_resp);
int ath12k_htc_send(struct ath12k_htc *htc, enum ath12k_htc_ep_id eid,
struct sk_buff *packet);
struct sk_buff *ath12k_htc_alloc_skb(struct ath12k_base *ar, int size);
void ath12k_htc_rx_completion_handler(struct ath12k_base *ar,
struct sk_buff *ath12k_htc_alloc_skb(struct ath12k_base *ab, int size);
void ath12k_htc_rx_completion_handler(struct ath12k_base *ab,
struct sk_buff *skb);
#endif

View File

@ -559,7 +559,6 @@ struct hal_eht_sig_ofdma_cmn_eb1 {
#define HAL_RX_EHT_SIG_OFDMA_EB2_RU_ALLOC_2_4 GENMASK_ULL(35, 27)
#define HAL_RX_EHT_SIG_OFDMA_EB2_RU_ALLOC_2_5 GENMASK_ULL(44, 36)
#define HAL_RX_EHT_SIG_OFDMA_EB2_RU_ALLOC_2_6 GENMASK_ULL(53, 45)
#define HAL_RX_EHT_SIG_OFDMA_EB2_MCS GNEMASK_ULL(57, 54)
struct hal_eht_sig_ofdma_cmn_eb2 {
__le64 info0;

View File

@ -15,6 +15,8 @@
#include <linux/time.h>
#include <linux/of.h>
#include <linux/cleanup.h>
#include <linux/percpu.h>
#include <linux/refcount.h>
#include "core.h"
#include "debugfs.h"
#include "debug.h"
@ -134,6 +136,10 @@ struct wmi_pdev_set_obss_bitmap_arg {
const char *label;
};
static DEFINE_MUTEX(ath12k_wmi_mutex);
static refcount_t ath12k_wmi_refcount;
static void __percpu *ath12k_wmi_tb;
static const struct ath12k_wmi_tlv_policy ath12k_wmi_tlv_policies[] = {
[WMI_TAG_ARRAY_BYTE] = { .min_len = 0 },
[WMI_TAG_ARRAY_UINT32] = { .min_len = 0 },
@ -289,29 +295,19 @@ static int ath12k_wmi_tlv_iter_parse(struct ath12k_base *ab, u16 tag, u16 len,
return 0;
}
static int ath12k_wmi_tlv_parse(struct ath12k_base *ar, const void **tb,
const void *ptr, size_t len)
{
return ath12k_wmi_tlv_iter(ar, ptr, len, ath12k_wmi_tlv_iter_parse,
(void *)tb);
}
static const void **
ath12k_wmi_tlv_parse_alloc(struct ath12k_base *ab,
struct sk_buff *skb, gfp_t gfp)
ath12k_wmi_tlv_parse(struct ath12k_base *ab, struct sk_buff *skb)
{
const void **tb;
int ret;
tb = kzalloc_objs(*tb, WMI_TAG_MAX, gfp);
if (!tb)
return ERR_PTR(-ENOMEM);
tb = this_cpu_ptr(ath12k_wmi_tb);
memset(tb, 0, WMI_TAG_MAX * sizeof(*tb));
ret = ath12k_wmi_tlv_parse(ab, tb, skb->data, skb->len);
if (ret) {
kfree(tb);
ret = ath12k_wmi_tlv_iter(ab, skb->data, skb->len,
ath12k_wmi_tlv_iter_parse, (void *)tb);
if (ret)
return ERR_PTR(ret);
}
return tb;
}
@ -3911,9 +3907,10 @@ ath12k_wmi_obss_color_collision_event(struct ath12k_base *ab, struct sk_buff *sk
const struct wmi_obss_color_collision_event *ev;
struct ath12k_link_vif *arvif;
u32 vdev_id, evt_type;
const void **tb;
u64 bitmap;
const void **tb __free(kfree) = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ath12k_warn(ab, "failed to parse OBSS color collision tlv %ld\n",
PTR_ERR(tb));
@ -5714,7 +5711,7 @@ static int ath12k_pull_vdev_start_resp_tlv(struct ath12k_base *ab, struct sk_buf
const struct wmi_vdev_start_resp_event *ev;
int ret;
tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
@ -5724,13 +5721,11 @@ static int ath12k_pull_vdev_start_resp_tlv(struct ath12k_base *ab, struct sk_buf
ev = tb[WMI_TAG_VDEV_START_RESPONSE_EVENT];
if (!ev) {
ath12k_warn(ab, "failed to fetch vdev start resp ev");
kfree(tb);
return -EPROTO;
}
*vdev_rsp = *ev;
kfree(tb);
return 0;
}
@ -5809,7 +5804,7 @@ static int ath12k_pull_reg_chan_list_ext_update_ev(struct ath12k_base *ab,
ath12k_dbg(ab, ATH12K_DBG_WMI, "processing regulatory ext channel list\n");
tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
@ -5819,7 +5814,6 @@ static int ath12k_pull_reg_chan_list_ext_update_ev(struct ath12k_base *ab,
ev = tb[WMI_TAG_REG_CHAN_LIST_CC_EXT_EVENT];
if (!ev) {
ath12k_warn(ab, "failed to fetch reg chan list ext update ev\n");
kfree(tb);
return -EPROTO;
}
@ -5849,7 +5843,6 @@ static int ath12k_pull_reg_chan_list_ext_update_ev(struct ath12k_base *ab,
if (num_2g_reg_rules > MAX_REG_RULES || num_5g_reg_rules > MAX_REG_RULES) {
ath12k_warn(ab, "Num reg rules for 2G/5G exceeds max limit (num_2g_reg_rules: %d num_5g_reg_rules: %d max_rules: %d)\n",
num_2g_reg_rules, num_5g_reg_rules, MAX_REG_RULES);
kfree(tb);
return -EINVAL;
}
@ -5859,7 +5852,6 @@ static int ath12k_pull_reg_chan_list_ext_update_ev(struct ath12k_base *ab,
if (num_6g_reg_rules_ap[i] > MAX_6GHZ_REG_RULES) {
ath12k_warn(ab, "Num 6G reg rules for AP mode(%d) exceeds max limit (num_6g_reg_rules_ap: %d, max_rules: %d)\n",
i, num_6g_reg_rules_ap[i], MAX_6GHZ_REG_RULES);
kfree(tb);
return -EINVAL;
}
@ -5884,14 +5876,12 @@ static int ath12k_pull_reg_chan_list_ext_update_ev(struct ath12k_base *ab,
num_6g_reg_rules_cl[WMI_REG_VLP_AP][i] > MAX_6GHZ_REG_RULES) {
ath12k_warn(ab, "Num 6g client reg rules exceeds max limit, for client(type: %d)\n",
i);
kfree(tb);
return -EINVAL;
}
}
if (!total_reg_rules) {
ath12k_warn(ab, "No reg rules available\n");
kfree(tb);
return -EINVAL;
}
@ -5993,7 +5983,6 @@ static int ath12k_pull_reg_chan_list_ext_update_ev(struct ath12k_base *ab,
ext_wmi_reg_rule);
if (!reg_info->reg_rules_2g_ptr) {
kfree(tb);
ath12k_warn(ab, "Unable to Allocate memory for 2g rules\n");
return -ENOMEM;
}
@ -6027,7 +6016,6 @@ static int ath12k_pull_reg_chan_list_ext_update_ev(struct ath12k_base *ab,
ext_wmi_reg_rule);
if (!reg_info->reg_rules_5g_ptr) {
kfree(tb);
ath12k_warn(ab, "Unable to Allocate memory for 5g rules\n");
return -ENOMEM;
}
@ -6046,7 +6034,6 @@ static int ath12k_pull_reg_chan_list_ext_update_ev(struct ath12k_base *ab,
ext_wmi_reg_rule);
if (!reg_info->reg_rules_6g_ap_ptr[i]) {
kfree(tb);
ath12k_warn(ab, "Unable to Allocate memory for 6g ap rules\n");
return -ENOMEM;
}
@ -6061,7 +6048,6 @@ static int ath12k_pull_reg_chan_list_ext_update_ev(struct ath12k_base *ab,
ext_wmi_reg_rule);
if (!reg_info->reg_rules_6g_client_ptr[j][i]) {
kfree(tb);
ath12k_warn(ab, "Unable to Allocate memory for 6g client rules\n");
return -ENOMEM;
}
@ -6096,7 +6082,6 @@ static int ath12k_pull_reg_chan_list_ext_update_ev(struct ath12k_base *ab,
ath12k_dbg(ab, ATH12K_DBG_WMI, "processed regulatory ext channel list\n");
kfree(tb);
return 0;
}
@ -6107,7 +6092,7 @@ static int ath12k_pull_peer_del_resp_ev(struct ath12k_base *ab, struct sk_buff *
const struct wmi_peer_delete_resp_event *ev;
int ret;
tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
@ -6117,7 +6102,6 @@ static int ath12k_pull_peer_del_resp_ev(struct ath12k_base *ab, struct sk_buff *
ev = tb[WMI_TAG_PEER_DELETE_RESP_EVENT];
if (!ev) {
ath12k_warn(ab, "failed to fetch peer delete resp ev");
kfree(tb);
return -EPROTO;
}
@ -6127,7 +6111,6 @@ static int ath12k_pull_peer_del_resp_ev(struct ath12k_base *ab, struct sk_buff *
ether_addr_copy(peer_del_resp->peer_macaddr.addr,
ev->peer_macaddr.addr);
kfree(tb);
return 0;
}
@ -6139,7 +6122,7 @@ static int ath12k_pull_vdev_del_resp_ev(struct ath12k_base *ab,
const struct wmi_vdev_delete_resp_event *ev;
int ret;
tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
@ -6149,13 +6132,11 @@ static int ath12k_pull_vdev_del_resp_ev(struct ath12k_base *ab,
ev = tb[WMI_TAG_VDEV_DELETE_RESP_EVENT];
if (!ev) {
ath12k_warn(ab, "failed to fetch vdev delete resp ev");
kfree(tb);
return -EPROTO;
}
*vdev_id = le32_to_cpu(ev->vdev_id);
kfree(tb);
return 0;
}
@ -6167,7 +6148,7 @@ static int ath12k_pull_bcn_tx_status_ev(struct ath12k_base *ab,
const struct wmi_bcn_tx_status_event *ev;
int ret;
tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
@ -6177,14 +6158,12 @@ static int ath12k_pull_bcn_tx_status_ev(struct ath12k_base *ab,
ev = tb[WMI_TAG_OFFLOAD_BCN_TX_STATUS_EVENT];
if (!ev) {
ath12k_warn(ab, "failed to fetch bcn tx status ev");
kfree(tb);
return -EPROTO;
}
*vdev_id = le32_to_cpu(ev->vdev_id);
*tx_status = le32_to_cpu(ev->tx_status);
kfree(tb);
return 0;
}
@ -6195,7 +6174,7 @@ static int ath12k_pull_vdev_stopped_param_tlv(struct ath12k_base *ab, struct sk_
const struct wmi_vdev_stopped_event *ev;
int ret;
tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
@ -6205,13 +6184,11 @@ static int ath12k_pull_vdev_stopped_param_tlv(struct ath12k_base *ab, struct sk_
ev = tb[WMI_TAG_VDEV_STOPPED_EVENT];
if (!ev) {
ath12k_warn(ab, "failed to fetch vdev stop ev");
kfree(tb);
return -EPROTO;
}
*vdev_id = le32_to_cpu(ev->vdev_id);
kfree(tb);
return 0;
}
@ -6350,7 +6327,7 @@ static int ath12k_pull_mgmt_tx_compl_param_tlv(struct ath12k_base *ab,
const struct wmi_mgmt_tx_compl_event *ev;
int ret;
tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
@ -6360,7 +6337,6 @@ static int ath12k_pull_mgmt_tx_compl_param_tlv(struct ath12k_base *ab,
ev = tb[WMI_TAG_MGMT_TX_COMPL_EVENT];
if (!ev) {
ath12k_warn(ab, "failed to fetch mgmt tx compl ev");
kfree(tb);
return -EPROTO;
}
@ -6370,7 +6346,6 @@ static int ath12k_pull_mgmt_tx_compl_param_tlv(struct ath12k_base *ab,
param->ppdu_id = ev->ppdu_id;
param->ack_rssi = ev->ack_rssi;
kfree(tb);
return 0;
}
@ -6533,7 +6508,7 @@ static int ath12k_pull_scan_ev(struct ath12k_base *ab, struct sk_buff *skb,
const struct wmi_scan_event *ev;
int ret;
tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
@ -6543,7 +6518,6 @@ static int ath12k_pull_scan_ev(struct ath12k_base *ab, struct sk_buff *skb,
ev = tb[WMI_TAG_SCAN_EVENT];
if (!ev) {
ath12k_warn(ab, "failed to fetch scan ev");
kfree(tb);
return -EPROTO;
}
@ -6555,7 +6529,6 @@ static int ath12k_pull_scan_ev(struct ath12k_base *ab, struct sk_buff *skb,
scan_evt_param->vdev_id = ev->vdev_id;
scan_evt_param->tsf_timestamp = ev->tsf_timestamp;
kfree(tb);
return 0;
}
@ -6566,7 +6539,7 @@ static int ath12k_pull_peer_sta_kickout_ev(struct ath12k_base *ab, struct sk_buf
const struct wmi_peer_sta_kickout_event *ev;
int ret;
tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
@ -6576,7 +6549,6 @@ static int ath12k_pull_peer_sta_kickout_ev(struct ath12k_base *ab, struct sk_buf
ev = tb[WMI_TAG_PEER_STA_KICKOUT_EVENT];
if (!ev) {
ath12k_warn(ab, "failed to fetch peer sta kickout ev");
kfree(tb);
return -EPROTO;
}
@ -6584,7 +6556,6 @@ static int ath12k_pull_peer_sta_kickout_ev(struct ath12k_base *ab, struct sk_buf
arg->reason = le32_to_cpu(ev->reason);
arg->rssi = le32_to_cpu(ev->rssi);
kfree(tb);
return 0;
}
@ -6595,7 +6566,7 @@ static int ath12k_pull_roam_ev(struct ath12k_base *ab, struct sk_buff *skb,
const struct wmi_roam_event *ev;
int ret;
tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
@ -6605,7 +6576,6 @@ static int ath12k_pull_roam_ev(struct ath12k_base *ab, struct sk_buff *skb,
ev = tb[WMI_TAG_ROAM_EVENT];
if (!ev) {
ath12k_warn(ab, "failed to fetch roam ev");
kfree(tb);
return -EPROTO;
}
@ -6613,7 +6583,6 @@ static int ath12k_pull_roam_ev(struct ath12k_base *ab, struct sk_buff *skb,
roam_ev->reason = ev->reason;
roam_ev->rssi = ev->rssi;
kfree(tb);
return 0;
}
@ -6647,7 +6616,7 @@ static int ath12k_pull_chan_info_ev(struct ath12k_base *ab, struct sk_buff *skb,
const struct wmi_chan_info_event *ev;
int ret;
tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
@ -6657,7 +6626,6 @@ static int ath12k_pull_chan_info_ev(struct ath12k_base *ab, struct sk_buff *skb,
ev = tb[WMI_TAG_CHAN_INFO_EVENT];
if (!ev) {
ath12k_warn(ab, "failed to fetch chan info ev");
kfree(tb);
return -EPROTO;
}
@ -6674,7 +6642,6 @@ static int ath12k_pull_chan_info_ev(struct ath12k_base *ab, struct sk_buff *skb,
ch_info_ev->mac_clk_mhz = ev->mac_clk_mhz;
ch_info_ev->vdev_id = ev->vdev_id;
kfree(tb);
return 0;
}
@ -6686,7 +6653,7 @@ ath12k_pull_pdev_bss_chan_info_ev(struct ath12k_base *ab, struct sk_buff *skb,
const struct wmi_pdev_bss_chan_info_event *ev;
int ret;
tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
@ -6696,7 +6663,6 @@ ath12k_pull_pdev_bss_chan_info_ev(struct ath12k_base *ab, struct sk_buff *skb,
ev = tb[WMI_TAG_PDEV_BSS_CHAN_INFO_EVENT];
if (!ev) {
ath12k_warn(ab, "failed to fetch pdev bss chan info ev");
kfree(tb);
return -EPROTO;
}
@ -6714,7 +6680,6 @@ ath12k_pull_pdev_bss_chan_info_ev(struct ath12k_base *ab, struct sk_buff *skb,
bss_ch_info_ev->rx_bss_cycle_count_low = ev->rx_bss_cycle_count_low;
bss_ch_info_ev->rx_bss_cycle_count_high = ev->rx_bss_cycle_count_high;
kfree(tb);
return 0;
}
@ -6726,7 +6691,7 @@ ath12k_pull_vdev_install_key_compl_ev(struct ath12k_base *ab, struct sk_buff *sk
const struct wmi_vdev_install_key_compl_event *ev;
int ret;
tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
@ -6736,7 +6701,6 @@ ath12k_pull_vdev_install_key_compl_ev(struct ath12k_base *ab, struct sk_buff *sk
ev = tb[WMI_TAG_VDEV_INSTALL_KEY_COMPLETE_EVENT];
if (!ev) {
ath12k_warn(ab, "failed to fetch vdev install key compl ev");
kfree(tb);
return -EPROTO;
}
@ -6746,7 +6710,6 @@ ath12k_pull_vdev_install_key_compl_ev(struct ath12k_base *ab, struct sk_buff *sk
arg->key_flags = le32_to_cpu(ev->key_flags);
arg->status = le32_to_cpu(ev->status);
kfree(tb);
return 0;
}
@ -6757,7 +6720,7 @@ static int ath12k_pull_peer_assoc_conf_ev(struct ath12k_base *ab, struct sk_buff
const struct wmi_peer_assoc_conf_event *ev;
int ret;
tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
@ -6767,14 +6730,12 @@ static int ath12k_pull_peer_assoc_conf_ev(struct ath12k_base *ab, struct sk_buff
ev = tb[WMI_TAG_PEER_ASSOC_CONF_EVENT];
if (!ev) {
ath12k_warn(ab, "failed to fetch peer assoc conf ev");
kfree(tb);
return -EPROTO;
}
peer_assoc_conf->vdev_id = le32_to_cpu(ev->vdev_id);
peer_assoc_conf->macaddr = ev->peer_macaddr.addr;
kfree(tb);
return 0;
}
@ -6792,7 +6753,7 @@ static int ath12k_reg_11d_new_cc_event(struct ath12k_base *ab, struct sk_buff *s
const void **tb;
int ret, i;
tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
@ -6801,7 +6762,6 @@ static int ath12k_reg_11d_new_cc_event(struct ath12k_base *ab, struct sk_buff *s
ev = tb[WMI_TAG_11D_NEW_COUNTRY_EVENT];
if (!ev) {
kfree(tb);
ath12k_warn(ab, "failed to fetch 11d new cc ev");
return -EPROTO;
}
@ -6814,8 +6774,6 @@ static int ath12k_reg_11d_new_cc_event(struct ath12k_base *ab, struct sk_buff *s
ab->new_alpha2[0],
ab->new_alpha2[1]);
kfree(tb);
for (i = 0; i < ab->num_radios; i++) {
pdev = &ab->pdevs[i];
ar = pdev->ar;
@ -8557,7 +8515,7 @@ static void ath12k_pdev_ctl_failsafe_check_event(struct ath12k_base *ab,
const struct wmi_pdev_ctl_failsafe_chk_event *ev;
int ret;
tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
@ -8567,7 +8525,6 @@ static void ath12k_pdev_ctl_failsafe_check_event(struct ath12k_base *ab,
ev = tb[WMI_TAG_PDEV_CTL_FAILSAFE_CHECK_EVENT];
if (!ev) {
ath12k_warn(ab, "failed to fetch pdev ctl failsafe check ev");
kfree(tb);
return;
}
@ -8581,8 +8538,6 @@ static void ath12k_pdev_ctl_failsafe_check_event(struct ath12k_base *ab,
if (ev->ctl_failsafe_status != 0)
ath12k_warn(ab, "pdev ctl failsafe failure status %d",
ev->ctl_failsafe_status);
kfree(tb);
}
static void
@ -8654,7 +8609,7 @@ ath12k_wmi_pdev_csa_switch_count_status_event(struct ath12k_base *ab,
const u32 *vdev_ids;
int ret;
tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
@ -8666,7 +8621,6 @@ ath12k_wmi_pdev_csa_switch_count_status_event(struct ath12k_base *ab,
if (!ev || !vdev_ids) {
ath12k_warn(ab, "failed to fetch pdev csa switch count ev");
kfree(tb);
return;
}
@ -8676,8 +8630,6 @@ ath12k_wmi_pdev_csa_switch_count_status_event(struct ath12k_base *ab,
ev->num_vdevs);
ath12k_wmi_process_csa_switch_count_event(ab, ev, vdev_ids);
kfree(tb);
}
static void
@ -8689,7 +8641,7 @@ ath12k_wmi_pdev_dfs_radar_detected_event(struct ath12k_base *ab, struct sk_buff
struct ath12k *ar;
int ret;
tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
@ -8700,7 +8652,6 @@ ath12k_wmi_pdev_dfs_radar_detected_event(struct ath12k_base *ab, struct sk_buff
if (!ev) {
ath12k_warn(ab, "failed to fetch pdev dfs radar detected ev");
kfree(tb);
return;
}
@ -8739,8 +8690,6 @@ ath12k_wmi_pdev_dfs_radar_detected_event(struct ath12k_base *ab, struct sk_buff
exit:
rcu_read_unlock();
kfree(tb);
}
static void ath12k_tm_wmi_event_segmented(struct ath12k_base *ab, u32 cmd_id,
@ -8751,7 +8700,7 @@ static void ath12k_tm_wmi_event_segmented(struct ath12k_base *ab, u32 cmd_id,
int ret;
u16 length;
tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
@ -8762,14 +8711,11 @@ static void ath12k_tm_wmi_event_segmented(struct ath12k_base *ab, u32 cmd_id,
ev = tb[WMI_TAG_ARRAY_BYTE];
if (!ev) {
ath12k_warn(ab, "failed to fetch ftm msg\n");
kfree(tb);
return;
}
length = skb->len - TLV_HDR_SIZE;
ath12k_tm_process_event(ab, cmd_id, ev, length);
kfree(tb);
tb = NULL;
}
static void
@ -8782,7 +8728,7 @@ ath12k_wmi_pdev_temperature_event(struct ath12k_base *ab,
int temp;
u32 pdev_id;
tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ath12k_warn(ab, "failed to parse tlv: %ld\n", PTR_ERR(tb));
return;
@ -8791,15 +8737,12 @@ ath12k_wmi_pdev_temperature_event(struct ath12k_base *ab,
ev = tb[WMI_TAG_PDEV_TEMPERATURE_EVENT];
if (!ev) {
ath12k_warn(ab, "failed to fetch pdev temp ev\n");
kfree(tb);
return;
}
temp = a_sle32_to_cpu(ev->temp);
pdev_id = le32_to_cpu(ev->pdev_id);
kfree(tb);
ath12k_dbg(ab, ATH12K_DBG_WMI,
"pdev temperature ev temp %d pdev_id %u\n",
temp, pdev_id);
@ -8826,7 +8769,7 @@ static void ath12k_fils_discovery_event(struct ath12k_base *ab,
const struct wmi_fils_discovery_event *ev;
int ret;
tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab,
@ -8838,15 +8781,12 @@ static void ath12k_fils_discovery_event(struct ath12k_base *ab,
ev = tb[WMI_TAG_HOST_SWFDA_EVENT];
if (!ev) {
ath12k_warn(ab, "failed to fetch FILS discovery event\n");
kfree(tb);
return;
}
ath12k_warn(ab,
"FILS discovery frame expected from host for vdev_id: %u, transmission scheduled at %u, next TBTT: %u\n",
ev->vdev_id, ev->fils_tt, ev->tbtt);
kfree(tb);
}
static void ath12k_probe_resp_tx_status_event(struct ath12k_base *ab,
@ -8856,7 +8796,7 @@ static void ath12k_probe_resp_tx_status_event(struct ath12k_base *ab,
const struct wmi_probe_resp_tx_status_event *ev;
int ret;
tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab,
@ -8869,7 +8809,6 @@ static void ath12k_probe_resp_tx_status_event(struct ath12k_base *ab,
if (!ev) {
ath12k_warn(ab,
"failed to fetch probe response transmission status event");
kfree(tb);
return;
}
@ -8877,8 +8816,6 @@ static void ath12k_probe_resp_tx_status_event(struct ath12k_base *ab,
ath12k_warn(ab,
"Probe response transmission failed for vdev_id %u, status %u\n",
ev->vdev_id, ev->tx_status);
kfree(tb);
}
static int ath12k_wmi_p2p_noa_event(struct ath12k_base *ab,
@ -8890,7 +8827,7 @@ static int ath12k_wmi_p2p_noa_event(struct ath12k_base *ab,
struct ath12k *ar;
int ret, vdev_id;
tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse P2P NoA TLV: %d\n", ret);
@ -8900,10 +8837,8 @@ static int ath12k_wmi_p2p_noa_event(struct ath12k_base *ab,
ev = tb[WMI_TAG_P2P_NOA_EVENT];
noa = tb[WMI_TAG_P2P_NOA_INFO];
if (!ev || !noa) {
ret = -EPROTO;
goto out;
}
if (!ev || !noa)
return -EPROTO;
vdev_id = __le32_to_cpu(ev->vdev_id);
@ -8926,8 +8861,6 @@ static int ath12k_wmi_p2p_noa_event(struct ath12k_base *ab,
unlock:
rcu_read_unlock();
out:
kfree(tb);
return ret;
}
@ -8938,7 +8871,7 @@ static void ath12k_rfkill_state_change_event(struct ath12k_base *ab,
const void **tb;
int ret;
tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
@ -8946,10 +8879,8 @@ static void ath12k_rfkill_state_change_event(struct ath12k_base *ab,
}
ev = tb[WMI_TAG_RFKILL_EVENT];
if (!ev) {
kfree(tb);
if (!ev)
return;
}
ath12k_dbg(ab, ATH12K_DBG_MAC,
"wmi tlv rfkill state change gpio %d type %d radio_state %d\n",
@ -8962,7 +8893,6 @@ static void ath12k_rfkill_state_change_event(struct ath12k_base *ab,
spin_unlock_bh(&ab->base_lock);
queue_work(ab->workqueue, &ab->rfkill_work);
kfree(tb);
}
static void
@ -8978,7 +8908,7 @@ static void ath12k_wmi_twt_enable_event(struct ath12k_base *ab,
const struct wmi_twt_enable_event *ev;
int ret;
tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse wmi twt enable status event tlv: %d\n",
@ -8989,15 +8919,12 @@ static void ath12k_wmi_twt_enable_event(struct ath12k_base *ab,
ev = tb[WMI_TAG_TWT_ENABLE_COMPLETE_EVENT];
if (!ev) {
ath12k_warn(ab, "failed to fetch twt enable wmi event\n");
goto exit;
return;
}
ath12k_dbg(ab, ATH12K_DBG_MAC, "wmi twt enable event pdev id %u status %u\n",
le32_to_cpu(ev->pdev_id),
le32_to_cpu(ev->status));
exit:
kfree(tb);
}
static void ath12k_wmi_twt_disable_event(struct ath12k_base *ab,
@ -9007,7 +8934,7 @@ static void ath12k_wmi_twt_disable_event(struct ath12k_base *ab,
const struct wmi_twt_disable_event *ev;
int ret;
tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse wmi twt disable status event tlv: %d\n",
@ -9018,15 +8945,12 @@ static void ath12k_wmi_twt_disable_event(struct ath12k_base *ab,
ev = tb[WMI_TAG_TWT_DISABLE_COMPLETE_EVENT];
if (!ev) {
ath12k_warn(ab, "failed to fetch twt disable wmi event\n");
goto exit;
return;
}
ath12k_dbg(ab, ATH12K_DBG_MAC, "wmi twt disable event pdev id %d status %u\n",
le32_to_cpu(ev->pdev_id),
le32_to_cpu(ev->status));
exit:
kfree(tb);
}
static int ath12k_wmi_wow_wakeup_host_parse(struct ath12k_base *ab,
@ -9099,7 +9023,7 @@ static void ath12k_wmi_gtk_offload_status_event(struct ath12k_base *ab,
const void **tb;
int ret;
tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
@ -9109,7 +9033,6 @@ static void ath12k_wmi_gtk_offload_status_event(struct ath12k_base *ab,
ev = tb[WMI_TAG_GTK_OFFLOAD_STATUS_EVENT];
if (!ev) {
ath12k_warn(ab, "failed to fetch gtk offload status ev");
kfree(tb);
return;
}
@ -9119,7 +9042,6 @@ static void ath12k_wmi_gtk_offload_status_event(struct ath12k_base *ab,
rcu_read_unlock();
ath12k_warn(ab, "failed to get arvif for vdev_id:%d\n",
le32_to_cpu(ev->vdev_id));
kfree(tb);
return;
}
@ -9135,8 +9057,6 @@ static void ath12k_wmi_gtk_offload_status_event(struct ath12k_base *ab,
(void *)&replay_ctr_be, GFP_ATOMIC);
rcu_read_unlock();
kfree(tb);
}
static void ath12k_wmi_event_mlo_setup_complete(struct ath12k_base *ab,
@ -9148,7 +9068,7 @@ static void ath12k_wmi_event_mlo_setup_complete(struct ath12k_base *ab,
const void **tb;
int ret, i;
tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse mlo setup complete event tlv: %d\n",
@ -9159,7 +9079,6 @@ static void ath12k_wmi_event_mlo_setup_complete(struct ath12k_base *ab,
ev = tb[WMI_TAG_MLO_SETUP_COMPLETE_EVENT];
if (!ev) {
ath12k_warn(ab, "failed to fetch mlo setup complete event\n");
kfree(tb);
return;
}
@ -9178,14 +9097,11 @@ static void ath12k_wmi_event_mlo_setup_complete(struct ath12k_base *ab,
if (!ar) {
ath12k_warn(ab, "invalid pdev_id %d status %u in setup complete event\n",
ev->pdev_id, ev->status);
goto out;
return;
}
ar->mlo_setup_status = le32_to_cpu(ev->status);
complete(&ar->mlo_setup_done);
out:
kfree(tb);
}
static void ath12k_wmi_event_teardown_complete(struct ath12k_base *ab,
@ -9195,7 +9111,7 @@ static void ath12k_wmi_event_teardown_complete(struct ath12k_base *ab,
const void **tb;
int ret;
tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse teardown complete event tlv: %d\n", ret);
@ -9203,13 +9119,8 @@ static void ath12k_wmi_event_teardown_complete(struct ath12k_base *ab,
}
ev = tb[WMI_TAG_MLO_TEARDOWN_COMPLETE];
if (!ev) {
if (!ev)
ath12k_warn(ab, "failed to fetch teardown complete event\n");
kfree(tb);
return;
}
kfree(tb);
}
#ifdef CONFIG_ATH12K_DEBUGFS
@ -11239,3 +11150,31 @@ int ath12k_wmi_send_mlo_link_set_active_cmd(struct ath12k_base *ab,
dev_kfree_skb(skb);
return ret;
}
int ath12k_wmi_alloc(void)
{
guard(mutex)(&ath12k_wmi_mutex);
if (!ath12k_wmi_tb) {
ath12k_wmi_tb = __alloc_percpu(WMI_TAG_MAX * sizeof(void *),
__alignof__(void *));
if (!ath12k_wmi_tb)
return -ENOMEM;
refcount_set(&ath12k_wmi_refcount, 1);
} else {
refcount_inc(&ath12k_wmi_refcount);
}
return 0;
}
void ath12k_wmi_free(void)
{
guard(mutex)(&ath12k_wmi_mutex);
if (refcount_dec_and_test(&ath12k_wmi_refcount)) {
free_percpu(ath12k_wmi_tb);
ath12k_wmi_tb = NULL;
}
}

View File

@ -6576,4 +6576,7 @@ int ath12k_wmi_send_vdev_set_tpc_power(struct ath12k *ar,
struct ath12k_reg_tpc_power_info *param);
int ath12k_wmi_send_mlo_link_set_active_cmd(struct ath12k_base *ab,
struct wmi_mlo_link_set_active_arg *param);
int ath12k_wmi_alloc(void);
void ath12k_wmi_free(void);
#endif

View File

@ -21,7 +21,7 @@
#include <linux/time.h>
#include <linux/bitops.h>
#include <linux/etherdevice.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/unaligned.h>
#include "hw.h"
@ -2719,19 +2719,28 @@ static void ath9k_hw_gpio_cfg_output_mux(struct ath_hw *ah, u32 gpio, u32 type)
static void ath9k_hw_gpio_cfg_soc(struct ath_hw *ah, u32 gpio, bool out,
const char *label)
{
enum gpiod_flags flags = out ? GPIOD_OUT_LOW : GPIOD_IN;
struct gpio_desc *gpiod;
int err;
if (ah->caps.gpio_requested & BIT(gpio))
if (ah->gpiods[gpio])
return;
err = devm_gpio_request_one(ah->dev, gpio, out ? GPIOF_OUT_INIT_LOW : GPIOF_IN, label);
/*
* Obtains a system specific GPIO descriptor from another GPIO controller.
* Ideally this should come from the device tree, this is a legacy code
* path.
*/
gpiod = gpiod_get_index(NULL, "ath9k", gpio, flags);
err = PTR_ERR_OR_ZERO(gpiod);
if (err) {
ath_err(ath9k_hw_common(ah), "request GPIO%d failed:%d\n",
gpio, err);
return;
}
ah->caps.gpio_requested |= BIT(gpio);
gpiod_set_consumer_name(gpiod, label);
ah->gpiods[gpio] = gpiod;
}
static void ath9k_hw_gpio_cfg_wmac(struct ath_hw *ah, u32 gpio, bool out,
@ -2791,10 +2800,12 @@ void ath9k_hw_gpio_free(struct ath_hw *ah, u32 gpio)
if (!AR_SREV_SOC(ah))
return;
WARN_ON(gpio >= ah->caps.num_gpio_pins);
if (ah->gpiods[gpio]) {
gpiod_put(ah->gpiods[gpio]);
ah->gpiods[gpio] = NULL;
}
if (ah->caps.gpio_requested & BIT(gpio))
ah->caps.gpio_requested &= ~BIT(gpio);
WARN_ON(gpio >= ah->caps.num_gpio_pins);
}
EXPORT_SYMBOL(ath9k_hw_gpio_free);
@ -2822,8 +2833,8 @@ u32 ath9k_hw_gpio_get(struct ath_hw *ah, u32 gpio)
val = REG_READ(ah, AR_GPIO_IN(ah)) & BIT(gpio);
else
val = MS_REG_READ(AR, gpio);
} else if (BIT(gpio) & ah->caps.gpio_requested) {
val = gpio_get_value(gpio) & BIT(gpio);
} else if (ah->gpiods[gpio]) {
val = gpiod_get_value(ah->gpiods[gpio]);
} else {
WARN_ON(1);
}
@ -2846,8 +2857,8 @@ void ath9k_hw_set_gpio(struct ath_hw *ah, u32 gpio, u32 val)
AR7010_GPIO_OUT : AR_GPIO_IN_OUT(ah);
REG_RMW(ah, out_addr, val << gpio, BIT(gpio));
} else if (BIT(gpio) & ah->caps.gpio_requested) {
gpio_set_value(gpio, val);
} else if (ah->gpiods[gpio]) {
gpiod_set_value(ah->gpiods[gpio], val);
} else {
WARN_ON(1);
}

View File

@ -19,6 +19,7 @@
#include <linux/if_ether.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/io.h>
#include <linux/firmware.h>
@ -302,7 +303,6 @@ struct ath9k_hw_capabilities {
u8 max_rxchains;
u8 num_gpio_pins;
u32 gpio_mask;
u32 gpio_requested;
u8 rx_hp_qdepth;
u8 rx_lp_qdepth;
u8 rx_status_len;
@ -783,6 +783,7 @@ struct ath_hw {
struct ath9k_hw_capabilities caps;
struct ath9k_channel channels[ATH9K_NUM_CHANNELS];
struct ath9k_channel *curchan;
struct gpio_desc *gpiods[32];
union {
struct ar5416_eeprom_def def;

View File

@ -1607,7 +1607,6 @@ wlc_lcnphy_radio_2064_channel_tune_4313(struct brcms_phy *pi, u8 channel)
{
uint i;
const struct chan_info_2064_lcnphy *ci;
u8 rfpll_doubler = 0;
u8 pll_pwrup, pll_pwrup_ovr;
s32 qFcal;
u8 d15, d16, f16, e44, e45;
@ -1618,18 +1617,12 @@ wlc_lcnphy_radio_2064_channel_tune_4313(struct brcms_phy *pi, u8 channel)
u16 g30, d28;
ci = &chan_info_2064_lcnphy[0];
rfpll_doubler = 1;
mod_radio_reg(pi, RADIO_2064_REG09D, 0x4, 0x1 << 2);
write_radio_reg(pi, RADIO_2064_REG09E, 0xf);
if (!rfpll_doubler) {
loop_bw = PLL_2064_LOOP_BW;
d30 = PLL_2064_D30;
} else {
loop_bw = PLL_2064_LOOP_BW_DOUBLER;
d30 = PLL_2064_D30_DOUBLER;
}
loop_bw = PLL_2064_LOOP_BW_DOUBLER;
d30 = PLL_2064_D30_DOUBLER;
if (CHSPEC_IS2G(pi->radio_chanspec)) {
for (i = 0; i < ARRAY_SIZE(chan_info_2064_lcnphy); i++)
@ -1669,7 +1662,7 @@ wlc_lcnphy_radio_2064_channel_tune_4313(struct brcms_phy *pi, u8 channel)
e44 = 0;
e45 = 0;
fpfd = rfpll_doubler ? (pi->xtalfreq << 1) : (pi->xtalfreq);
fpfd = pi->xtalfreq << 1;
if (pi->xtalfreq > 26000000)
e44 = 1;
if (pi->xtalfreq > 52000000)

View File

@ -257,7 +257,7 @@ static void iwl_mld_fill_mac_cmd_sta(struct iwl_mld *mld,
IEEE80211_EML_CAP_TRANSITION_TIMEOUT);
cmd->client.esr_transition_timeout =
min_t(u16, IEEE80211_EML_CAP_TRANSITION_TIMEOUT_128TU,
min_t(u16, IEEE80211_EML_CAP_TRANSITION_TIMEOUT_64TU,
esr_transition_timeout);
cmd->client.medium_sync_delay =
cpu_to_le16(vif->cfg.eml_med_sync_delay);

View File

@ -114,10 +114,10 @@ static const u8 ext_capa_base[IWL_MLD_STA_EXT_CAPA_SIZE] = {
};
#define IWL_MLD_EMLSR_CAPA (IEEE80211_EML_CAP_EMLSR_SUPP | \
IEEE80211_EML_CAP_EMLSR_PADDING_DELAY_32US << \
__bf_shf(IEEE80211_EML_CAP_EMLSR_PADDING_DELAY) | \
IEEE80211_EML_CAP_EML_PADDING_DELAY_32US << \
__bf_shf(IEEE80211_EML_CAP_EML_PADDING_DELAY) | \
IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY_64US << \
__bf_shf(IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY))
__bf_shf(IEEE80211_EML_CAP_EML_TRANSITION_DELAY))
#define IWL_MLD_CAPA_OPS (FIELD_PREP_CONST( \
IEEE80211_MLD_CAP_OP_TID_TO_LINK_MAP_NEG_SUPP, \
IEEE80211_MLD_CAP_OP_TID_TO_LINK_MAP_NEG_SUPP_SAME) | \

View File

@ -263,10 +263,10 @@ static const u8 tm_if_types_ext_capa_sta[] = {
*/
#define IWL_MVM_EMLSR_CAPA (IEEE80211_EML_CAP_EMLSR_SUPP | \
IEEE80211_EML_CAP_EMLSR_PADDING_DELAY_32US << \
__bf_shf(IEEE80211_EML_CAP_EMLSR_PADDING_DELAY) | \
IEEE80211_EML_CAP_EML_PADDING_DELAY_32US << \
__bf_shf(IEEE80211_EML_CAP_EML_PADDING_DELAY) | \
IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY_64US << \
__bf_shf(IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY))
__bf_shf(IEEE80211_EML_CAP_EML_TRANSITION_DELAY))
#define IWL_MVM_MLD_CAPA_OPS (FIELD_PREP_CONST( \
IEEE80211_MLD_CAP_OP_TID_TO_LINK_MAP_NEG_SUPP, \
IEEE80211_MLD_CAP_OP_TID_TO_LINK_MAP_NEG_SUPP_SAME) | \

View File

@ -1880,8 +1880,8 @@ mt7925_mcu_sta_eht_mld_tlv(struct sk_buff *skb,
eml_cap = (vif->cfg.eml_cap & (IEEE80211_EML_CAP_EMLSR_SUPP |
IEEE80211_EML_CAP_TRANSITION_TIMEOUT)) |
(ext_capa->eml_capabilities & (IEEE80211_EML_CAP_EMLSR_PADDING_DELAY |
IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY));
(ext_capa->eml_capabilities & (IEEE80211_EML_CAP_EML_PADDING_DELAY |
IEEE80211_EML_CAP_EML_TRANSITION_DELAY));
if (eml_cap & IEEE80211_EML_CAP_EMLSR_SUPP) {
eht_mld->eml_cap[0] = u16_get_bits(eml_cap, GENMASK(7, 0));

View File

@ -261,8 +261,8 @@ int qtnf_cmd_send_start_ap(struct qtnf_vif *vif,
cmd->p2p_ctwindow = s->p2p_ctwindow;
cmd->p2p_opp_ps = s->p2p_opp_ps;
cmd->pbss = s->pbss;
cmd->ht_required = s->ht_required;
cmd->vht_required = s->vht_required;
cmd->ht_required = s->beacon.ht_required;
cmd->vht_required = s->beacon.vht_required;
cmd->twt_responder = s->twt_responder;
if (s->he_obss_pd.enable) {
cmd->sr_params.sr_control |= QLINK_SR_SRG_INFORMATION_PRESENT;

View File

@ -5050,7 +5050,7 @@ int rtw89_fw_h2c_join_info(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rtwv
le32_encode_bits(0, RTW89_H2C_JOININFO_W1_EMLSR_CAB) |
le32_encode_bits(0, RTW89_H2C_JOININFO_W1_NSTR_EN) |
le32_encode_bits(init_ps, RTW89_H2C_JOININFO_W1_INIT_PWR_STATE) |
le32_encode_bits(IEEE80211_EML_CAP_EMLSR_PADDING_DELAY_256US,
le32_encode_bits(IEEE80211_EML_CAP_EML_PADDING_DELAY_256US,
RTW89_H2C_JOININFO_W1_EMLSR_PADDING) |
le32_encode_bits(IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY_256US,
RTW89_H2C_JOININFO_W1_EMLSR_TRANS_DELAY) |

View File

@ -485,26 +485,28 @@ struct ieee80211_multi_link_elem {
#define IEEE80211_MED_SYNC_DELAY_DEFAULT 0x10ac
#define IEEE80211_EML_CAP_EMLSR_SUPP 0x0001
#define IEEE80211_EML_CAP_EMLSR_PADDING_DELAY 0x000e
#define IEEE80211_EML_CAP_EMLSR_PADDING_DELAY_0US 0
#define IEEE80211_EML_CAP_EMLSR_PADDING_DELAY_32US 1
#define IEEE80211_EML_CAP_EMLSR_PADDING_DELAY_64US 2
#define IEEE80211_EML_CAP_EMLSR_PADDING_DELAY_128US 3
#define IEEE80211_EML_CAP_EMLSR_PADDING_DELAY_256US 4
#define IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY 0x0070
#define IEEE80211_EML_CAP_EML_PADDING_DELAY 0x000e
/* Described Tables 9-417i & 9-417k in 802.11be-2024, which have the same values */
#define IEEE80211_EML_CAP_EML_PADDING_DELAY_0US 0
#define IEEE80211_EML_CAP_EML_PADDING_DELAY_32US 1
#define IEEE80211_EML_CAP_EML_PADDING_DELAY_64US 2
#define IEEE80211_EML_CAP_EML_PADDING_DELAY_128US 3
#define IEEE80211_EML_CAP_EML_PADDING_DELAY_256US 4
#define IEEE80211_EML_CAP_EML_TRANSITION_DELAY 0x0070
/* Described in Table 9-417j in 802.11be-2024 */
#define IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY_0US 0
#define IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY_16US 1
#define IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY_32US 2
#define IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY_64US 3
#define IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY_128US 4
#define IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY_256US 5
/* Described in Table 9-417l in 802.11be-2024 */
#define IEEE80211_EML_CAP_EMLMR_TRANSITION_DELAY_0US 0
#define IEEE80211_EML_CAP_EMLMR_TRANSITION_DELAY_32US 1
#define IEEE80211_EML_CAP_EMLMR_TRANSITION_DELAY_64US 2
#define IEEE80211_EML_CAP_EMLMR_TRANSITION_DELAY_128US 3
#define IEEE80211_EML_CAP_EMLMR_TRANSITION_DELAY_256US 4
#define IEEE80211_EML_CAP_EMLMR_SUPPORT 0x0080
#define IEEE80211_EML_CAP_EMLMR_DELAY 0x0700
#define IEEE80211_EML_CAP_EMLMR_DELAY_0US 0
#define IEEE80211_EML_CAP_EMLMR_DELAY_32US 1
#define IEEE80211_EML_CAP_EMLMR_DELAY_64US 2
#define IEEE80211_EML_CAP_EMLMR_DELAY_128US 3
#define IEEE80211_EML_CAP_EMLMR_DELAY_256US 4
#define IEEE80211_EML_CAP_TRANSITION_TIMEOUT 0x7800
#define IEEE80211_EML_CAP_TRANSITION_TIMEOUT_0 0
#define IEEE80211_EML_CAP_TRANSITION_TIMEOUT_128US 1
@ -517,7 +519,6 @@ struct ieee80211_multi_link_elem {
#define IEEE80211_EML_CAP_TRANSITION_TIMEOUT_16TU 8
#define IEEE80211_EML_CAP_TRANSITION_TIMEOUT_32TU 9
#define IEEE80211_EML_CAP_TRANSITION_TIMEOUT_64TU 10
#define IEEE80211_EML_CAP_TRANSITION_TIMEOUT_128TU 11
#define IEEE80211_MLD_CAP_OP_MAX_SIMUL_LINKS 0x000f
#define IEEE80211_MLD_CAP_OP_SRS_SUPPORT 0x0010
@ -1121,14 +1122,20 @@ static inline bool ieee80211_tid_to_link_map_size_ok(const u8 *data, size_t len)
static inline u32 ieee80211_emlsr_pad_delay_in_us(u16 eml_cap)
{
u32 emlsr_supp =
u16_get_bits(eml_cap, IEEE80211_EML_CAP_EMLSR_SUPP);
if (!emlsr_supp)
return 0;
/* IEEE Std 802.11be-2024 Table 9-417i—Encoding of the EMLSR
* Padding Delay subfield.
*/
u32 pad_delay = u16_get_bits(eml_cap,
IEEE80211_EML_CAP_EMLSR_PADDING_DELAY);
IEEE80211_EML_CAP_EML_PADDING_DELAY);
if (!pad_delay ||
pad_delay > IEEE80211_EML_CAP_EMLSR_PADDING_DELAY_256US)
pad_delay > IEEE80211_EML_CAP_EML_PADDING_DELAY_256US)
return 0;
return 32 * (1 << (pad_delay - 1));
@ -1145,12 +1152,18 @@ static inline u32 ieee80211_emlsr_pad_delay_in_us(u16 eml_cap)
static inline u32 ieee80211_emlsr_trans_delay_in_us(u16 eml_cap)
{
u32 emlsr_supp =
u16_get_bits(eml_cap, IEEE80211_EML_CAP_EMLSR_SUPP);
if (!emlsr_supp)
return 0;
/* IEEE Std 802.11be-2024 Table 9-417j—Encoding of the EMLSR
* Transition Delay subfield.
*/
u32 trans_delay =
u16_get_bits(eml_cap,
IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY);
IEEE80211_EML_CAP_EML_TRANSITION_DELAY);
/* invalid values also just use 0 */
if (!trans_delay ||
@ -1161,11 +1174,73 @@ static inline u32 ieee80211_emlsr_trans_delay_in_us(u16 eml_cap)
}
/**
* ieee80211_eml_trans_timeout_in_us - Fetch the EMLSR Transition
* ieee80211_emlmr_pad_delay_in_us - Fetch the EMLMR Padding delay
* in microseconds
* @eml_cap: EML capabilities field value from common info field of
* the Multi-link element
* Return: the EMLMR Padding delay (in microseconds) encoded in the
* EML Capabilities field
*/
static inline u32 ieee80211_emlmr_pad_delay_in_us(u16 eml_cap)
{
u32 emlmr_supp =
u16_get_bits(eml_cap, IEEE80211_EML_CAP_EMLMR_SUPPORT);
if (!emlmr_supp)
return 0;
/* IEEE Std 802.11be-2024 Table 9-417k—Encoding of the EMLMR
* Padding Delay subfield.
*/
u32 pad_delay = u16_get_bits(eml_cap,
IEEE80211_EML_CAP_EML_PADDING_DELAY);
if (!pad_delay ||
pad_delay > IEEE80211_EML_CAP_EML_PADDING_DELAY_256US)
return 0;
return 32 * (1 << (pad_delay - 1));
}
/**
* ieee80211_emlmr_trans_delay_in_us - Fetch the EMLMR Transition
* delay in microseconds
* @eml_cap: EML capabilities field value from common info field of
* the Multi-link element
* Return: the EMLMR Transition delay (in microseconds) encoded in the
* EML Capabilities field
*/
static inline u32 ieee80211_emlmr_trans_delay_in_us(u16 eml_cap)
{
u32 emlmr_supp =
u16_get_bits(eml_cap, IEEE80211_EML_CAP_EMLMR_SUPPORT);
if (!emlmr_supp)
return 0;
/* IEEE Std 802.11be-2024 Table 9-417l—Encoding of the EMLMR
* Transition Delay subfield.
*/
u32 trans_delay =
u16_get_bits(eml_cap,
IEEE80211_EML_CAP_EML_TRANSITION_DELAY);
/* invalid values also just use 0 */
if (!trans_delay ||
trans_delay > IEEE80211_EML_CAP_EMLMR_TRANSITION_DELAY_256US)
return 0;
return 32 * (1 << (trans_delay - 1));
}
/**
* ieee80211_eml_trans_timeout_in_us - Fetch the EML Transition
* timeout value in microseconds
* @eml_cap: EML capabilities field value from common info field of
* the Multi-link element
* Return: the EMLSR Transition timeout (in microseconds) encoded in
* Return: the EML Transition timeout (in microseconds) encoded in
* the EML Capabilities field
*/
@ -1178,7 +1253,7 @@ static inline u32 ieee80211_eml_trans_timeout_in_us(u16 eml_cap)
IEEE80211_EML_CAP_TRANSITION_TIMEOUT);
/* invalid values also just use 0 */
if (!timeout || timeout > IEEE80211_EML_CAP_TRANSITION_TIMEOUT_128TU)
if (!timeout || timeout > IEEE80211_EML_CAP_TRANSITION_TIMEOUT_64TU)
return 0;
return 128 * (1 << (timeout - 1));

View File

@ -1396,6 +1396,13 @@ struct cfg80211_rnr_elems {
* @he_bss_color: BSS Color settings
* @he_bss_color_valid: indicates whether bss color
* attribute is present in beacon data or not.
* @ht_required: stations must support HT
* @vht_required: stations must support VHT
* @ht_oper: HT operation element (or %NULL if HT isn't enabled)
* @vht_oper: VHT operation element (or %NULL if VHT isn't enabled)
* @he_oper: HE operation IE (or %NULL if HE isn't enabled)
* @eht_oper: EHT operation IE (or %NULL if EHT isn't enabled)
* @uhr_oper: UHR operation (or %NULL if UHR isn't enabled)
*/
struct cfg80211_beacon_data {
unsigned int link_id;
@ -1420,6 +1427,13 @@ struct cfg80211_beacon_data {
size_t civicloc_len;
struct cfg80211_he_bss_color he_bss_color;
bool he_bss_color_valid;
bool ht_required, vht_required;
const struct ieee80211_ht_operation *ht_oper;
const struct ieee80211_vht_operation *vht_oper;
const struct ieee80211_he_operation *he_oper;
const struct ieee80211_eht_operation *eht_oper;
const struct ieee80211_uhr_operation *uhr_oper;
};
struct mac_address {
@ -1524,16 +1538,9 @@ struct cfg80211_s1g_short_beacon {
* @vht_cap: VHT capabilities (or %NULL if VHT isn't enabled)
* @he_cap: HE capabilities (or %NULL if HE isn't enabled)
* @eht_cap: EHT capabilities (or %NULL if EHT isn't enabled)
* @eht_oper: EHT operation IE (or %NULL if EHT isn't enabled)
* @uhr_oper: UHR operation (or %NULL if UHR isn't enabled)
* @ht_required: stations must support HT
* @vht_required: stations must support VHT
* @twt_responder: Enable Target Wait Time
* @he_required: stations must support HE
* @sae_h2e_required: stations must support direct H2E technique in SAE
* @flags: flags, as defined in &enum nl80211_ap_settings_flags
* @he_obss_pd: OBSS Packet Detection settings
* @he_oper: HE operation IE (or %NULL if HE isn't enabled)
* @fils_discovery: FILS discovery transmission parameters
* @unsol_bcast_probe_resp: Unsolicited broadcast probe response parameters
* @mbssid_config: AP settings for multiple bssid
@ -1562,11 +1569,7 @@ struct cfg80211_ap_settings {
const struct ieee80211_ht_cap *ht_cap;
const struct ieee80211_vht_cap *vht_cap;
const struct ieee80211_he_cap_elem *he_cap;
const struct ieee80211_he_operation *he_oper;
const struct ieee80211_eht_cap_elem *eht_cap;
const struct ieee80211_eht_operation *eht_oper;
const struct ieee80211_uhr_operation *uhr_oper;
bool ht_required, vht_required, he_required, sae_h2e_required;
bool twt_responder;
u32 flags;
struct ieee80211_he_obss_pd he_obss_pd;

View File

@ -1747,18 +1747,18 @@ struct ieee80211_rx_status {
u8 he_ru:3;
u8 he_gi:2;
u8 he_dcm:1;
};
} __packed;
struct {
u8 ru:4;
u8 gi:2;
} eht;
} __packed eht;
struct {
u8 ru:4;
u8 gi:2;
u8 elr:1;
u8 im:1;
} uhr;
};
} __packed uhr;
} __packed;
u8 rate_idx;
u8 nss;
u8 rx_flags;
@ -1771,6 +1771,8 @@ struct ieee80211_rx_status {
u8 link_valid:1, link_id:4;
};
static_assert(sizeof(struct ieee80211_rx_status) <= sizeof_field(struct sk_buff, cb));
static inline u32
ieee80211_rx_status_to_khz(struct ieee80211_rx_status *rx_status)
{

View File

@ -1204,10 +1204,12 @@
* user space through the connect result as the user space would have
* initiated the connection through the connect request.
*
* @NL80211_CMD_STA_OPMODE_CHANGED: An event that notify station's
* ht opmode or vht opmode changes using any of %NL80211_ATTR_SMPS_MODE,
* %NL80211_ATTR_CHANNEL_WIDTH,%NL80211_ATTR_NSS attributes with its
* address(specified in %NL80211_ATTR_MAC).
* @NL80211_CMD_STA_OPMODE_CHANGED: An event that notifies that a station's
* HT opmode or VHT opmode changed using any of %NL80211_ATTR_SMPS_MODE,
* %NL80211_ATTR_CHANNEL_WIDTH, %NL80211_ATTR_NSS attributes with its
* address (specified in %NL80211_ATTR_MAC).
* Note that 80+80 and 160 MHz might not be differentiated, i.e. may
* report %NL80211_CHAN_WIDTH_160 instead of %NL80211_CHAN_WIDTH_80P80.
*
* @NL80211_CMD_GET_FTM_RESPONDER_STATS: Retrieve FTM responder statistics, in
* the %NL80211_ATTR_FTM_RESPONDER_STATS attribute.

View File

@ -1312,6 +1312,120 @@ ieee80211_copy_rnr_beacon(u8 *pos, struct cfg80211_rnr_elems *dst,
return offset;
}
static enum ieee80211_sta_rx_bandwidth
ieee80211_calc_ap_he_and_lower(struct cfg80211_beacon_data *params)
{
const struct ieee80211_vht_operation *vht_oper = params->vht_oper;
int ccfs0, ccfs1;
if (params->he_oper) {
const struct ieee80211_he_6ghz_oper *he_6ghz_oper;
if (params->he_oper->he_oper_params &
cpu_to_le32(IEEE80211_HE_OPERATION_VHT_OPER_INFO))
vht_oper = (void *)params->he_oper->optional;
he_6ghz_oper = ieee80211_he_6ghz_oper(params->he_oper);
if (he_6ghz_oper) {
switch (u8_get_bits(he_6ghz_oper->control,
IEEE80211_HE_6GHZ_OPER_CTRL_CHANWIDTH)) {
case IEEE80211_HE_6GHZ_OPER_CTRL_CHANWIDTH_20MHZ:
return IEEE80211_STA_RX_BW_20;
case IEEE80211_HE_6GHZ_OPER_CTRL_CHANWIDTH_40MHZ:
return IEEE80211_STA_RX_BW_40;
case IEEE80211_HE_6GHZ_OPER_CTRL_CHANWIDTH_80MHZ:
return IEEE80211_STA_RX_BW_80;
case IEEE80211_HE_6GHZ_OPER_CTRL_CHANWIDTH_160MHZ:
return IEEE80211_STA_RX_BW_160;
}
}
}
if (vht_oper) {
switch (vht_oper->chan_width) {
case IEEE80211_VHT_CHANWIDTH_USE_HT:
/* check for HT (or fall down to 20) below */
break;
case IEEE80211_VHT_CHANWIDTH_160MHZ:
case IEEE80211_VHT_CHANWIDTH_80P80MHZ:
/* deprecated encodings */
return IEEE80211_STA_RX_BW_160;
case IEEE80211_VHT_CHANWIDTH_80MHZ:
/*
* See IEEE 802.11-2020 Table 9-352-BSS bandwidth
* when the VHT Operation Information field Channel
* Width subfield is 1
*
* (IEEE80211_VHT_CHANWIDTH_80MHZ == 1)
*/
ccfs0 = vht_oper->center_freq_seg0_idx;
ccfs1 = vht_oper->center_freq_seg1_idx;
if (!ccfs0)
return IEEE80211_STA_RX_BW_80;
if (ccfs1 && abs(ccfs1 - ccfs0) == 8)
return IEEE80211_STA_RX_BW_160;
/* 80+80 - RX BW doesn't cover that / uses 160 */
if (ccfs1 && abs(ccfs1 - ccfs0) > 16)
return IEEE80211_STA_RX_BW_160;
fallthrough;
default:
/* reserved encoding - assume 80 */
return IEEE80211_STA_RX_BW_80;
}
}
if (params->ht_oper) {
switch (u8_get_bits(params->ht_oper->ht_param,
IEEE80211_HT_PARAM_CHA_SEC_OFFSET)) {
case IEEE80211_HT_PARAM_CHA_SEC_NONE:
default: /* invalid values */
return IEEE80211_STA_RX_BW_20;
case IEEE80211_HT_PARAM_CHA_SEC_ABOVE:
case IEEE80211_HT_PARAM_CHA_SEC_BELOW:
return IEEE80211_STA_RX_BW_40;
}
}
/* nothing found, must be 20 MHz */
return IEEE80211_STA_RX_BW_20;
}
static void ieee80211_update_ap_bandwidth(struct ieee80211_link_data *link,
struct cfg80211_beacon_data *params)
{
struct ieee80211_local *local = link->sdata->local;
struct ieee80211_chanctx_conf *chanctx_conf;
struct ieee80211_chanctx *chanctx;
/*
* Updating the beacon might, without even changing the channel, cause
* the usable bandwidth for some stations to be changed, for example
* if the beacon configuration is EHT with 160 MHz, HE could change
* between 20, 40, 80 and 160 MHz, and HE (or lower) clients need to
* be handled accordingly.
* Calculate the HE and lower bandwidth and apply that to all stations.
*
* In the future, this also needs to calculate EHT bandwidth and apply
* it to all stations not using UHR DBE, since the chandef would then
* include DBE.
*/
if (link->conf->chanreq.oper.chan->band == NL80211_BAND_S1GHZ)
return;
link->bss_bw.he_and_lower = ieee80211_calc_ap_he_and_lower(params);
chanctx_conf = sdata_dereference(link->conf->chanctx_conf, link->sdata);
chanctx = container_of(chanctx_conf, struct ieee80211_chanctx, conf);
/*
* Note: this relies on ieee80211_recalc_chanctx_min_def() having
* the side effect of updating all stations, if they changed; that
* was normally for when the chandef changed but is used here too.
*/
ieee80211_recalc_chanctx_min_def(local, chanctx);
}
static int
ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata,
struct ieee80211_link_data *link,
@ -1450,6 +1564,8 @@ ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata,
if (old)
kfree_rcu(old, rcu_head);
ieee80211_update_ap_bandwidth(link, params);
*changed |= _changed;
return 0;
}
@ -1541,13 +1657,13 @@ static int ieee80211_start_ap(struct wiphy *wiphy, struct net_device *dev,
cpu_to_le32(IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE);
}
if (params->he_cap && params->he_oper) {
if (params->he_cap && params->beacon.he_oper) {
link_conf->he_support = true;
link_conf->htc_trig_based_pkt_ext =
le32_get_bits(params->he_oper->he_oper_params,
le32_get_bits(params->beacon.he_oper->he_oper_params,
IEEE80211_HE_OPERATION_DFLT_PE_DURATION_MASK);
link_conf->frame_time_rts_th =
le32_get_bits(params->he_oper->he_oper_params,
le32_get_bits(params->beacon.he_oper->he_oper_params,
IEEE80211_HE_OPERATION_RTS_THRESHOLD_MASK);
changed |= BSS_CHANGED_HE_OBSS_PD;
@ -1596,7 +1712,7 @@ static int ieee80211_start_ap(struct wiphy *wiphy, struct net_device *dev,
IEEE80211_EHT_PHY_CAP7_NON_OFDMA_UL_MU_MIMO_160MHZ |
IEEE80211_EHT_PHY_CAP7_NON_OFDMA_UL_MU_MIMO_320MHZ);
link_conf->eht_disable_mcs15 =
u8_get_bits(params->eht_oper->params,
u8_get_bits(params->beacon.eht_oper->params,
IEEE80211_EHT_OPER_MCS15_DISABLE);
} else {
link_conf->eht_su_beamformer = false;
@ -1604,7 +1720,7 @@ static int ieee80211_start_ap(struct wiphy *wiphy, struct net_device *dev,
link_conf->eht_mu_beamformer = false;
}
if (params->uhr_oper) {
if (params->beacon.uhr_oper) {
if (!link_conf->eht_support)
return -EOPNOTSUPP;
@ -2221,7 +2337,15 @@ static int sta_link_apply_parameters(struct ieee80211_local *local,
ieee80211_s1g_cap_to_sta_s1g_cap(sdata, params->s1g_capa,
link_sta);
ieee80211_sta_init_nss(link_sta);
switch (sdata->vif.type) {
case NL80211_IFTYPE_NAN:
case NL80211_IFTYPE_NAN_DATA:
/* not applicable - they don't use NSS/BW as capability */
break;
default:
ieee80211_sta_init_nss_bw_capa(link_sta, &link->conf->chanreq.oper);
break;
}
if (params->opmode_notif_used) {
enum nl80211_chan_width width = link->conf->chanreq.oper.width;

View File

@ -438,11 +438,12 @@ ieee80211_find_reservation_chanctx(struct ieee80211_local *local,
return NULL;
}
static enum nl80211_chan_width ieee80211_get_sta_bw(struct sta_info *sta,
unsigned int link_id)
static enum nl80211_chan_width
ieee80211_get_sta_bw(struct sta_info *sta, struct ieee80211_link_data *link)
{
enum ieee80211_sta_rx_bandwidth width;
struct link_sta_info *link_sta;
int link_id = link->link_id;
link_sta = wiphy_dereference(sta->local->hw.wiphy, sta->link[link_id]);
@ -454,45 +455,28 @@ static enum nl80211_chan_width ieee80211_get_sta_bw(struct sta_info *sta,
* We assume that TX/RX might be asymmetric (so e.g. VHT operating
* mode notification changes what a STA wants to receive, but not
* necessarily what it will transmit to us), and therefore use the
* capabilities here. Calling it RX bandwidth capability is a bit
* wrong though, since capabilities are in fact symmetric.
* "from station" bandwidth here.
*/
width = ieee80211_sta_cap_rx_bw(link_sta);
width = ieee80211_sta_current_bw(link_sta, &link->conf->chanreq.oper,
IEEE80211_STA_BW_RX_FROM_STA);
switch (width) {
case IEEE80211_STA_RX_BW_20:
if (link_sta->pub->ht_cap.ht_supported)
return NL80211_CHAN_WIDTH_20;
else
return NL80211_CHAN_WIDTH_20_NOHT;
case IEEE80211_STA_RX_BW_40:
return NL80211_CHAN_WIDTH_40;
case IEEE80211_STA_RX_BW_80:
return NL80211_CHAN_WIDTH_80;
case IEEE80211_STA_RX_BW_160:
/*
* This applied for both 160 and 80+80. since we use
* the returned value to consider degradation of
* ctx->conf.min_def, we have to make sure to take
* the bigger one (NL80211_CHAN_WIDTH_160).
* Otherwise we might try degrading even when not
* needed, as the max required sta_bw returned (80+80)
* might be smaller than the configured bw (160).
*/
return NL80211_CHAN_WIDTH_160;
case IEEE80211_STA_RX_BW_320:
return NL80211_CHAN_WIDTH_320;
default:
WARN_ON(1);
return NL80211_CHAN_WIDTH_20;
}
if (width == IEEE80211_STA_RX_BW_20 &&
!link_sta->pub->ht_cap.ht_supported &&
!link_sta->pub->he_cap.has_he)
return NL80211_CHAN_WIDTH_20_NOHT;
/*
* This returns 160 for both 160 and 80+80. Since we use
* the returned value to consider narrowing for
* ctx->conf.min_def, that's correct and necessary.
*/
return ieee80211_sta_rx_bw_to_chan_width(width);
}
static enum nl80211_chan_width
ieee80211_get_max_required_bw(struct ieee80211_link_data *link)
{
struct ieee80211_sub_if_data *sdata = link->sdata;
unsigned int link_id = link->link_id;
enum nl80211_chan_width max_bw = NL80211_CHAN_WIDTH_20_NOHT;
struct sta_info *sta;
@ -503,7 +487,7 @@ ieee80211_get_max_required_bw(struct ieee80211_link_data *link)
!(sta->sdata->bss && sta->sdata->bss == sdata->bss))
continue;
max_bw = max(max_bw, ieee80211_get_sta_bw(sta, link_id));
max_bw = max(max_bw, ieee80211_get_sta_bw(sta, link));
}
return max_bw;
@ -709,8 +693,9 @@ static void ieee80211_chan_bw_change(struct ieee80211_local *local,
else
new_chandef = &link_conf->chanreq.oper;
new_sta_bw = _ieee80211_sta_cur_vht_bw(link_sta,
new_chandef);
new_sta_bw = ieee80211_sta_current_bw(link_sta,
new_chandef,
IEEE80211_STA_BW_TX_TO_STA);
/* nothing change */
if (new_sta_bw == link_sta->pub->bandwidth)
@ -733,6 +718,9 @@ static void ieee80211_chan_bw_change(struct ieee80211_local *local,
* recalc the min required chan width of the channel context, which is
* the max of min required widths of all the interfaces bound to this
* channel context.
*
* Note: ieee80211_update_ap_bandwidth() relies on this iterating all
* affected stations, even if min_def didn't change.
*/
static void
_ieee80211_recalc_chanctx_min_def(struct ieee80211_local *local,
@ -743,13 +731,11 @@ _ieee80211_recalc_chanctx_min_def(struct ieee80211_local *local,
u32 changed = __ieee80211_recalc_chanctx_min_def(local, ctx, rsvd_for,
check_reserved);
if (!changed)
return;
/* check is BW narrowed */
ieee80211_chan_bw_change(local, ctx, false, true);
drv_change_chanctx(local, ctx, changed);
if (changed)
drv_change_chanctx(local, ctx, changed);
/* check is BW wider */
ieee80211_chan_bw_change(local, ctx, false, false);

View File

@ -320,26 +320,17 @@ static ssize_t aql_enable_read(struct file *file, char __user *user_buf,
static ssize_t aql_enable_write(struct file *file, const char __user *user_buf,
size_t count, loff_t *ppos)
{
char buf[3];
size_t len;
bool val;
int ret;
if (count > sizeof(buf))
return -EINVAL;
ret = kstrtobool_from_user(user_buf, count, &val);
if (unlikely(ret))
return ret;
if (copy_from_user(buf, user_buf, count))
return -EFAULT;
buf[sizeof(buf) - 1] = '\0';
len = strlen(buf);
if (len > 0 && buf[len - 1] == '\n')
buf[len - 1] = 0;
if (buf[0] == '0' && buf[1] == '\0')
static_branch_enable(&aql_disable);
else if (buf[0] == '1' && buf[1] == '\0')
if (val)
static_branch_disable(&aql_disable);
else
return -EINVAL;
static_branch_enable(&aql_disable);
return count;
}
@ -371,26 +362,14 @@ static ssize_t force_tx_status_write(struct file *file,
loff_t *ppos)
{
struct ieee80211_local *local = file->private_data;
char buf[3];
bool val;
int ret;
if (count >= sizeof(buf))
return -EINVAL;
if (copy_from_user(buf, user_buf, count))
return -EFAULT;
if (count && buf[count - 1] == '\n')
buf[count - 1] = '\0';
else
buf[count] = '\0';
if (buf[0] == '0' && buf[1] == '\0')
local->force_tx_status = 0;
else if (buf[0] == '1' && buf[1] == '\0')
local->force_tx_status = 1;
else
return -EINVAL;
ret = kstrtobool_from_user(user_buf, count, &val);
if (unlikely(ret))
return ret;
local->force_tx_status = val;
return count;
}

View File

@ -2,7 +2,7 @@
/*
* EHT handling
*
* Copyright(c) 2021-2025 Intel Corporation
* Copyright(c) 2021-2026 Intel Corporation
*/
#include "driver-ops.h"
@ -74,9 +74,6 @@ ieee80211_eht_cap_ie_to_sta_eht_cap(struct ieee80211_sub_if_data *sdata,
eht_cap->has_eht = true;
link_sta->cur_max_bandwidth = ieee80211_sta_cap_rx_bw(link_sta);
link_sta->pub->bandwidth = ieee80211_sta_cur_vht_bw(link_sta);
/*
* The MPDU length bits are reserved on all but 2.4 GHz and get set via
* VHT (5 GHz) or HE (6 GHz) capabilities.
@ -204,7 +201,7 @@ void ieee80211_rx_eml_op_mode_notif(struct ieee80211_sub_if_data *sdata,
pad_delay = u8_get_bits(ptr[2],
IEEE80211_EML_EMLSR_PAD_DELAY);
if (pad_delay >
IEEE80211_EML_CAP_EMLSR_PADDING_DELAY_256US)
IEEE80211_EML_CAP_EML_PADDING_DELAY_256US)
return;
trans_delay = u8_get_bits(ptr[2],
@ -217,11 +214,11 @@ void ieee80211_rx_eml_op_mode_notif(struct ieee80211_sub_if_data *sdata,
sta->sta.eml_cap =
u8_replace_bits(sta->sta.eml_cap,
pad_delay,
IEEE80211_EML_CAP_EMLSR_PADDING_DELAY);
IEEE80211_EML_CAP_EML_PADDING_DELAY);
sta->sta.eml_cap =
u8_replace_bits(sta->sta.eml_cap,
trans_delay,
IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY);
IEEE80211_EML_CAP_EML_TRANSITION_DELAY);
}
}

View File

@ -3,7 +3,7 @@
* HE handling
*
* Copyright(c) 2017 Intel Deutschland GmbH
* Copyright(c) 2019-2025 Intel Corporation
* Copyright(c) 2019-2026 Intel Corporation
*/
#include "ieee80211_i.h"
@ -159,10 +159,6 @@ _ieee80211_he_cap_ie_to_sta_he_cap(struct ieee80211_sub_if_data *sdata,
he_cap->has_he = true;
link_sta->cur_max_bandwidth = ieee80211_sta_cap_rx_bw(link_sta);
if (sdata->vif.type != NL80211_IFTYPE_NAN)
link_sta->pub->bandwidth = ieee80211_sta_cur_vht_bw(link_sta);
if (he_6ghz_capa)
ieee80211_update_from_he_6ghz_capa(he_6ghz_capa, link_sta);
@ -277,7 +273,8 @@ static void ieee80211_link_sta_rc_update_omi(struct ieee80211_link_data *link,
band = link->conf->chanreq.oper.chan->band;
sband = sdata->local->hw.wiphy->bands[band];
new_bw = ieee80211_sta_cur_vht_bw(link_sta);
new_bw = ieee80211_sta_current_bw(link_sta, &link->conf->chanreq.oper,
IEEE80211_STA_BW_TX_TO_STA);
if (link_sta->pub->bandwidth == new_bw)
return;

View File

@ -140,14 +140,11 @@ bool ieee80211_ht_cap_ie_to_sta_ht_cap(struct ieee80211_sub_if_data *sdata,
const struct ieee80211_ht_cap *ht_cap_ie,
struct link_sta_info *link_sta)
{
struct ieee80211_bss_conf *link_conf;
struct sta_info *sta = link_sta->sta;
struct ieee80211_sta_ht_cap ht_cap, own_cap;
u8 ampdu_info, tx_mcs_set_cap;
int i, max_tx_streams;
bool changed;
enum ieee80211_sta_rx_bandwidth bw;
enum nl80211_chan_width width;
memset(&ht_cap, 0, sizeof(ht_cap));
@ -256,45 +253,6 @@ bool ieee80211_ht_cap_ie_to_sta_ht_cap(struct ieee80211_sub_if_data *sdata,
memcpy(&link_sta->pub->ht_cap, &ht_cap, sizeof(ht_cap));
rcu_read_lock();
link_conf = rcu_dereference(sdata->vif.link_conf[link_sta->link_id]);
if (WARN_ON(!link_conf)) {
width = NL80211_CHAN_WIDTH_20_NOHT;
} else if (sdata->vif.type == NL80211_IFTYPE_NAN ||
sdata->vif.type == NL80211_IFTYPE_NAN_DATA) {
/* In NAN, link_sta->bandwidth is invalid since NAN operates on
* multiple channels. Just take the maximum.
*/
width = NL80211_CHAN_WIDTH_320;
} else {
width = link_conf->chanreq.oper.width;
}
switch (width) {
default:
WARN_ON_ONCE(1);
fallthrough;
case NL80211_CHAN_WIDTH_20_NOHT:
case NL80211_CHAN_WIDTH_20:
bw = IEEE80211_STA_RX_BW_20;
break;
case NL80211_CHAN_WIDTH_40:
case NL80211_CHAN_WIDTH_80:
case NL80211_CHAN_WIDTH_80P80:
case NL80211_CHAN_WIDTH_160:
case NL80211_CHAN_WIDTH_320:
bw = ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40 ?
IEEE80211_STA_RX_BW_40 : IEEE80211_STA_RX_BW_20;
break;
}
rcu_read_unlock();
link_sta->pub->bandwidth = bw;
link_sta->cur_max_bandwidth =
ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40 ?
IEEE80211_STA_RX_BW_40 : IEEE80211_STA_RX_BW_20;
if (sta->sdata->vif.type == NL80211_IFTYPE_AP ||
sta->sdata->vif.type == NL80211_IFTYPE_AP_VLAN ||
sta->sdata->vif.type == NL80211_IFTYPE_NAN ||
@ -629,25 +587,30 @@ void ieee80211_ht_handle_chanwidth_notif(struct ieee80211_local *local,
enum ieee80211_sta_rx_bandwidth max_bw, new_bw;
struct ieee80211_supported_band *sband;
struct sta_opmode_info sta_opmode = {};
struct ieee80211_link_data *link;
lockdep_assert_wiphy(local->hw.wiphy);
link = sdata_dereference(sdata->link[link_sta->link_id], sdata);
if (WARN_ON(!link))
return;
if (chanwidth == IEEE80211_HT_CHANWIDTH_20MHZ)
max_bw = IEEE80211_STA_RX_BW_20;
else
max_bw = ieee80211_sta_cap_rx_bw(link_sta);
max_bw = IEEE80211_STA_RX_BW_MAX;
/* set cur_max_bandwidth and recalc sta bw */
link_sta->cur_max_bandwidth = max_bw;
new_bw = ieee80211_sta_cur_vht_bw(link_sta);
/* set op_mode_bw and recalc sta bw */
link_sta->op_mode_bw = max_bw;
new_bw = ieee80211_sta_current_bw(link_sta, &link->conf->chanreq.oper,
IEEE80211_STA_BW_TX_TO_STA);
if (link_sta->pub->bandwidth == new_bw)
return;
link_sta->pub->bandwidth = new_bw;
sband = local->hw.wiphy->bands[band];
sta_opmode.bw =
ieee80211_sta_rx_bw_to_chan_width(link_sta);
sta_opmode.bw = ieee80211_sta_rx_bw_to_chan_width(new_bw);
sta_opmode.changed = STA_OPMODE_MAX_BW_CHANGED;
rate_control_rate_update(local, sband, link_sta,

View File

@ -553,6 +553,9 @@ static struct sta_info *ieee80211_ibss_finish_sta(struct sta_info *sta)
memcpy(addr, sta->sta.addr, ETH_ALEN);
ieee80211_sta_init_nss_bw_capa(&sta->deflink,
&sdata->deflink.conf->chanreq.oper);
ibss_dbg(sdata, "Adding new IBSS station %pM\n", addr);
sta_info_pre_move_state(sta, IEEE80211_STA_AUTH);

View File

@ -1133,6 +1133,10 @@ struct ieee80211_link_data {
struct ieee80211_bss_conf *conf;
struct {
enum ieee80211_sta_rx_bandwidth he_and_lower;
} bss_bw;
#ifdef CONFIG_MAC80211_DEBUGFS
struct dentry *debugfs_dir;
#endif
@ -2299,25 +2303,6 @@ ieee80211_vht_cap_ie_to_sta_vht_cap(struct ieee80211_sub_if_data *sdata,
const struct ieee80211_vht_cap *vht_cap_ie,
const struct ieee80211_vht_cap *vht_cap_ie2,
struct link_sta_info *link_sta);
enum ieee80211_sta_rx_bandwidth
_ieee80211_sta_cap_rx_bw(struct link_sta_info *link_sta,
struct cfg80211_chan_def *chandef);
static inline enum ieee80211_sta_rx_bandwidth
ieee80211_sta_cap_rx_bw(struct link_sta_info *link_sta)
{
return _ieee80211_sta_cap_rx_bw(link_sta, NULL);
}
enum ieee80211_sta_rx_bandwidth
_ieee80211_sta_cur_vht_bw(struct link_sta_info *link_sta,
struct cfg80211_chan_def *chandef);
static inline enum ieee80211_sta_rx_bandwidth
ieee80211_sta_cur_vht_bw(struct link_sta_info *link_sta)
{
return _ieee80211_sta_cur_vht_bw(link_sta, NULL);
}
void ieee80211_sta_init_nss(struct link_sta_info *link_sta);
enum nl80211_chan_width
ieee80211_sta_cap_chan_bw(struct link_sta_info *link_sta);
void ieee80211_process_mu_groups(struct ieee80211_sub_if_data *sdata,
struct ieee80211_link_data *link,
struct ieee80211_mgmt *mgmt);
@ -2331,8 +2316,6 @@ void ieee80211_apply_vhtcap_overrides(struct ieee80211_sub_if_data *sdata,
struct ieee80211_sta_vht_cap *vht_cap);
void ieee80211_get_vht_mask_from_cap(__le16 vht_cap,
u16 vht_mask[NL80211_VHT_NSS_MAX]);
enum nl80211_chan_width
ieee80211_sta_rx_bw_to_chan_width(struct link_sta_info *sta);
/* HE */
void
@ -2718,6 +2701,9 @@ void ieee80211_add_s1g_capab_ie(struct ieee80211_sub_if_data *sdata,
void ieee80211_add_aid_request_ie(struct ieee80211_sub_if_data *sdata,
struct sk_buff *skb);
enum nl80211_chan_width
ieee80211_sta_rx_bw_to_chan_width(enum ieee80211_sta_rx_bandwidth bw);
/* element building in SKBs */
int ieee80211_put_srates_elem(struct sk_buff *skb,
const struct ieee80211_supported_band *sband,

View File

@ -470,6 +470,9 @@ static void mesh_sta_info_init(struct ieee80211_sub_if_data *sdata,
elems->eht_cap, elems->eht_cap_len,
&sta->deflink);
ieee80211_sta_init_nss_bw_capa(&sta->deflink,
&sdata->deflink.conf->chanreq.oper);
if (bw != sta->sta.deflink.bandwidth)
changed |= IEEE80211_RC_BW_CHANGED;

View File

@ -2571,8 +2571,9 @@ static void ieee80211_csa_switch_work(struct wiphy *wiphy,
return;
link_sta->pub->bandwidth =
_ieee80211_sta_cur_vht_bw(link_sta,
&link->csa.chanreq.oper);
ieee80211_sta_current_bw(link_sta,
&link->csa.chanreq.oper,
IEEE80211_STA_BW_TX_TO_STA);
return;
}
@ -5748,20 +5749,13 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link,
* next beacon and update then.
*/
/*
* If an operating mode notification IE is present, override the
* NSS calculation (that would be done in rate_control_rate_init())
* and use the # of streams from that element.
*/
if (elems->opmode_notif &&
!(*elems->opmode_notif & IEEE80211_OPMODE_NOTIF_RX_NSS_TYPE_BF)) {
u8 nss;
ieee80211_sta_init_nss_bw_capa(link_sta, &bss_conf->chanreq.oper);
nss = *elems->opmode_notif & IEEE80211_OPMODE_NOTIF_RX_NSS_MASK;
nss >>= IEEE80211_OPMODE_NOTIF_RX_NSS_SHIFT;
nss += 1;
link_sta->pub->rx_nss = nss;
}
/* If an operating mode notification element is present, use it. */
if (elems->opmode_notif)
__ieee80211_vht_handle_opmode(sdata, link_sta,
*elems->opmode_notif,
sband->band);
/*
* Always handle WMM once after association regardless
@ -10617,7 +10611,6 @@ void ieee80211_process_ml_reconf_resp(struct ieee80211_sub_if_data *sdata,
if (add_links_data->link[link_id].status != WLAN_STATUS_SUCCESS)
goto disconnect;
ieee80211_sta_init_nss(link_sta);
if (ieee80211_sta_activate_link(sta, link_id))
goto disconnect;

View File

@ -4,7 +4,7 @@
*
* Copyright: (c) 2014 Czech Technical University in Prague
* (c) 2014 Volkswagen Group Research
* Copyright (C) 2022 - 2024 Intel Corporation
* Copyright (C) 2022 - 2024, 2026 Intel Corporation
* Author: Rostislav Lisovy <rostislav.lisovy@fel.cvut.cz>
* Funded by: Volkswagen Group Research
*/
@ -92,6 +92,9 @@ static struct sta_info *ieee80211_ocb_finish_sta(struct sta_info *sta)
memcpy(addr, sta->sta.addr, ETH_ALEN);
ieee80211_sta_init_nss_bw_capa(&sta->deflink,
&sdata->deflink.conf->chanreq.oper);
ocb_dbg(sdata, "Adding new IBSS station %pM (dev=%s)\n",
addr, sdata->name);

View File

@ -4,7 +4,7 @@
* Copyright 2005-2006, Devicescape Software, Inc.
* Copyright (c) 2006 Jiri Benc <jbenc@suse.cz>
* Copyright 2017 Intel Deutschland GmbH
* Copyright (C) 2019, 2022-2025 Intel Corporation
* Copyright (C) 2019, 2022-2026 Intel Corporation
*/
#include <linux/kernel.h>
@ -38,8 +38,6 @@ void rate_control_rate_init(struct link_sta_info *link_sta)
struct ieee80211_supported_band *sband;
struct ieee80211_chanctx_conf *chanctx_conf;
ieee80211_sta_init_nss(link_sta);
if (!ref)
return;

View File

@ -572,11 +572,13 @@ static int sta_info_alloc_link(struct ieee80211_local *local,
link_info->rx_omi_bw_tx = IEEE80211_STA_RX_BW_MAX;
link_info->rx_omi_bw_staging = IEEE80211_STA_RX_BW_MAX;
link_info->op_mode_bw = IEEE80211_STA_RX_BW_MAX;
/*
* Cause (a) warning(s) if IEEE80211_STA_RX_BW_MAX != 320
* or if new values are added to the enum.
*/
switch (link_info->cur_max_bandwidth) {
switch (link_info->op_mode_bw) {
case IEEE80211_STA_RX_BW_20:
case IEEE80211_STA_RX_BW_40:
case IEEE80211_STA_RX_BW_80:
@ -3424,6 +3426,104 @@ void ieee80211_sta_remove_link(struct sta_info *sta, unsigned int link_id)
sta_remove_link(sta, link_id, true);
}
static u8 ieee80211_sta_nss_capability(struct link_sta_info *link_sta)
{
u8 ht_rx_nss = 0, vht_rx_nss = 0, he_rx_nss = 0, eht_rx_nss = 0, rx_nss;
bool support_160;
if (link_sta->pub->eht_cap.has_eht) {
int i;
const u8 *rx_nss_mcs = (void *)&link_sta->pub->eht_cap.eht_mcs_nss_supp;
/* get the max nss for EHT over all possible bandwidths and mcs */
for (i = 0; i < sizeof(struct ieee80211_eht_mcs_nss_supp); i++)
eht_rx_nss = max_t(u8, eht_rx_nss,
u8_get_bits(rx_nss_mcs[i],
IEEE80211_EHT_MCS_NSS_RX));
}
if (link_sta->pub->he_cap.has_he) {
int i;
u8 rx_mcs_80 = 0, rx_mcs_160 = 0;
const struct ieee80211_sta_he_cap *he_cap = &link_sta->pub->he_cap;
u16 mcs_160_map =
le16_to_cpu(he_cap->he_mcs_nss_supp.rx_mcs_160);
u16 mcs_80_map = le16_to_cpu(he_cap->he_mcs_nss_supp.rx_mcs_80);
for (i = 7; i >= 0; i--) {
u8 mcs_160 = (mcs_160_map >> (2 * i)) & 3;
if (mcs_160 != IEEE80211_HE_MCS_NOT_SUPPORTED) {
rx_mcs_160 = i + 1;
break;
}
}
for (i = 7; i >= 0; i--) {
u8 mcs_80 = (mcs_80_map >> (2 * i)) & 3;
if (mcs_80 != IEEE80211_HE_MCS_NOT_SUPPORTED) {
rx_mcs_80 = i + 1;
break;
}
}
support_160 = he_cap->he_cap_elem.phy_cap_info[0] &
IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G;
if (support_160)
he_rx_nss = min(rx_mcs_80, rx_mcs_160);
else
he_rx_nss = rx_mcs_80;
}
if (link_sta->pub->ht_cap.ht_supported) {
if (link_sta->pub->ht_cap.mcs.rx_mask[0])
ht_rx_nss++;
if (link_sta->pub->ht_cap.mcs.rx_mask[1])
ht_rx_nss++;
if (link_sta->pub->ht_cap.mcs.rx_mask[2])
ht_rx_nss++;
if (link_sta->pub->ht_cap.mcs.rx_mask[3])
ht_rx_nss++;
/* FIXME: consider rx_highest? */
}
if (link_sta->pub->vht_cap.vht_supported) {
int i;
u16 rx_mcs_map;
rx_mcs_map = le16_to_cpu(link_sta->pub->vht_cap.vht_mcs.rx_mcs_map);
for (i = 7; i >= 0; i--) {
u8 mcs = (rx_mcs_map >> (2 * i)) & 3;
if (mcs != IEEE80211_VHT_MCS_NOT_SUPPORTED) {
vht_rx_nss = i + 1;
break;
}
}
/* FIXME: consider rx_highest? */
}
rx_nss = max(vht_rx_nss, ht_rx_nss);
rx_nss = max(he_rx_nss, rx_nss);
rx_nss = max(eht_rx_nss, rx_nss);
rx_nss = max_t(u8, 1, rx_nss);
return rx_nss;
}
void ieee80211_sta_init_nss_bw_capa(struct link_sta_info *link_sta,
struct cfg80211_chan_def *chandef)
{
link_sta->capa_nss = ieee80211_sta_nss_capability(link_sta);
link_sta->pub->rx_nss = link_sta->capa_nss;
link_sta->pub->bandwidth =
ieee80211_sta_current_bw(link_sta, chandef,
IEEE80211_STA_BW_TX_TO_STA);
}
void ieee80211_sta_set_max_amsdu_subframes(struct sta_info *sta,
const u8 *ext_capab,
unsigned int ext_capab_len)
@ -3456,3 +3556,195 @@ bool lockdep_sta_mutex_held(struct ieee80211_sta *pubsta)
}
EXPORT_SYMBOL(lockdep_sta_mutex_held);
#endif
/**
* ieee80211_sta_bw_capability - get STA's bandwidth capability
* @link_sta: the (link) STA to get the capability for
* @band: the band to get the capability on
*
* Return: the maximum bandwidth supported by the STA
*/
static enum ieee80211_sta_rx_bandwidth
ieee80211_sta_bw_capability(struct link_sta_info *link_sta,
enum nl80211_band band)
{
struct ieee80211_sta_vht_cap *vht_cap = &link_sta->pub->vht_cap;
struct ieee80211_sta_he_cap *he_cap = &link_sta->pub->he_cap;
struct ieee80211_sta_eht_cap *eht_cap = &link_sta->pub->eht_cap;
u32 cap_width;
if (he_cap->has_he) {
u8 info;
if (eht_cap->has_eht && band == NL80211_BAND_6GHZ) {
info = eht_cap->eht_cap_elem.phy_cap_info[0];
if (info & IEEE80211_EHT_PHY_CAP0_320MHZ_IN_6GHZ)
return IEEE80211_STA_RX_BW_320;
}
info = he_cap->he_cap_elem.phy_cap_info[0];
if (band == NL80211_BAND_2GHZ) {
if (info & IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G)
return IEEE80211_STA_RX_BW_40;
return IEEE80211_STA_RX_BW_20;
}
if (info & IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G ||
info & IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_80PLUS80_MHZ_IN_5G)
return IEEE80211_STA_RX_BW_160;
if (info & IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G)
return IEEE80211_STA_RX_BW_80;
return IEEE80211_STA_RX_BW_20;
}
if (!vht_cap->vht_supported)
return link_sta->pub->ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40 ?
IEEE80211_STA_RX_BW_40 :
IEEE80211_STA_RX_BW_20;
cap_width = vht_cap->cap & IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK;
if (cap_width == IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160MHZ ||
cap_width == IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ)
return IEEE80211_STA_RX_BW_160;
/*
* If this is non-zero, then it does support 160 MHz after all,
* in one form or the other. We don't distinguish here (or even
* above) between 160 and 80+80 yet.
*/
if (vht_cap->cap & IEEE80211_VHT_CAP_EXT_NSS_BW_MASK)
return IEEE80211_STA_RX_BW_160;
return IEEE80211_STA_RX_BW_80;
}
/**
* ieee80211_sta_usable_bw - get STA's usable bandwidth capability
* @link_sta: the (link) STA to get the capability for
* @band: the band to get the capability on
*
* If the STA is on an AP interface, take into account the AP's
* bandwidth corresponding to this station's PHY capability
*
* Return: the maximum bandwidth supported by the STA on the
* connection to the interface it's connected to
*/
static enum ieee80211_sta_rx_bandwidth
ieee80211_sta_usable_bw(struct link_sta_info *link_sta,
enum nl80211_band band)
{
struct ieee80211_sub_if_data *sdata = link_sta->sta->sdata;
enum ieee80211_sta_rx_bandwidth bw;
struct ieee80211_link_data *link;
bw = ieee80211_sta_bw_capability(link_sta, band);
if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN) {
sdata = get_bss_sdata(sdata);
/* for a STA to exist on VLAN, it must have AP */
if (WARN_ON(!sdata))
return IEEE80211_STA_RX_BW_20;
}
if (sdata->vif.type != NL80211_IFTYPE_AP)
return bw;
/* for a link STA to exist, vif must have the link */
link = sdata_dereference(sdata->link[link_sta->link_id], sdata);
if (WARN_ON(!link))
return IEEE80211_STA_RX_BW_20;
if (link_sta->pub->eht_cap.has_eht)
return bw;
return min(bw, link->bss_bw.he_and_lower);
}
static enum ieee80211_sta_rx_bandwidth
ieee80211_sta_current_bw_rx_from_sta(struct link_sta_info *link_sta,
struct cfg80211_chan_def *chandef)
{
/*
* Take RX OMI into account. The value "rx_omi_bw_rx" is what
* we've indicated to the STA we can currently receive.
*
* This is needed since the RX OMI is done by us to save power,
* requiring changing both our TX (rate control) and RX (chanctx),
* which in turn needs to be done in the right order (stop TX
* at a higher bandwidth first while reducing bandwidth, and
* change the chanctx only after the peer accepts, etc.)
*/
return min(ieee80211_sta_usable_bw(link_sta, chandef->chan->band),
link_sta->rx_omi_bw_rx);
}
static enum ieee80211_sta_rx_bandwidth
ieee80211_sta_current_bw_tx_to_sta(struct link_sta_info *link_sta,
struct cfg80211_chan_def *chandef)
{
struct sta_info *sta = link_sta->sta;
enum nl80211_chan_width bss_width;
enum ieee80211_sta_rx_bandwidth bw;
enum nl80211_band band;
bss_width = chandef->width;
band = chandef->chan->band;
bw = ieee80211_sta_usable_bw(link_sta, band);
bw = min(bw, link_sta->op_mode_bw);
/* also limit to RX OMI bandwidth we TX to the STA */
bw = min(bw, link_sta->rx_omi_bw_tx);
/* Don't consider AP's bandwidth for TDLS peers, section 11.23.1 of
* IEEE80211-2016 specification makes higher bandwidth operation
* possible on the TDLS link if the peers have wider bandwidth
* capability.
*
* However, in this case, and only if the TDLS peer is authorized,
* limit to the tdls_chandef so that the configuration here isn't
* wider than what's actually requested on the channel context.
*/
if (test_sta_flag(sta, WLAN_STA_TDLS_PEER) &&
test_sta_flag(sta, WLAN_STA_TDLS_WIDER_BW) &&
test_sta_flag(sta, WLAN_STA_AUTHORIZED) &&
sta->tdls_chandef.chan)
bw = min(bw, ieee80211_chan_width_to_rx_bw(sta->tdls_chandef.width));
else
bw = min(bw, ieee80211_chan_width_to_rx_bw(bss_width));
return bw;
}
/**
* ieee80211_sta_current_bw - get STA's current usable bandwidth
* @link_sta: the (link) STA to get the bandwidth for
* @chandef: the chandef for the channel the STA is on
* @direction: the direction (to or from STA)
*
* Return: the maximum bandwidth that the station can/may
* (currently) use in the given direction
*/
enum ieee80211_sta_rx_bandwidth
ieee80211_sta_current_bw(struct link_sta_info *link_sta,
struct cfg80211_chan_def *chandef,
enum ieee80211_sta_bw_direction direction)
{
if (WARN_ON(!chandef))
return IEEE80211_STA_RX_BW_20;
switch (direction) {
case IEEE80211_STA_BW_RX_FROM_STA:
return ieee80211_sta_current_bw_rx_from_sta(link_sta, chandef);
case IEEE80211_STA_BW_TX_TO_STA:
return ieee80211_sta_current_bw_tx_to_sta(link_sta, chandef);
}
/* unreachable */
return IEEE80211_STA_RX_BW_20;
}

View File

@ -504,7 +504,7 @@ struct ieee80211_fragment_cache {
* @status_stats.last_ack_signal: last ACK signal
* @status_stats.ack_signal_filled: last ACK signal validity
* @status_stats.avg_ack_signal: average ACK signal
* @cur_max_bandwidth: maximum bandwidth to use for TX to the station,
* @op_mode_bw: dynamic bandwidth limit to transmit to the STA,
* taken from HT/VHT capabilities or VHT operating mode notification.
* Invalid for NAN since that is operating on multiple bands.
* @rx_omi_bw_rx: RX OMI bandwidth restriction to apply for RX
@ -558,7 +558,7 @@ struct link_sta_info {
u64 msdu[IEEE80211_NUM_TIDS + 1];
} tx_stats;
enum ieee80211_sta_rx_bandwidth cur_max_bandwidth;
enum ieee80211_sta_rx_bandwidth op_mode_bw;
enum ieee80211_sta_rx_bandwidth rx_omi_bw_rx,
rx_omi_bw_tx,
rx_omi_bw_staging;
@ -997,12 +997,24 @@ void ieee80211_sta_ps_deliver_uapsd(struct sta_info *sta);
unsigned long ieee80211_sta_last_active(struct sta_info *sta, int link_id);
void ieee80211_sta_init_nss_bw_capa(struct link_sta_info *link_sta,
struct cfg80211_chan_def *chandef);
void ieee80211_sta_set_max_amsdu_subframes(struct sta_info *sta,
const u8 *ext_capab,
unsigned int ext_capab_len);
void __ieee80211_sta_recalc_aggregates(struct sta_info *sta, u16 active_links);
enum ieee80211_sta_bw_direction {
IEEE80211_STA_BW_RX_FROM_STA,
IEEE80211_STA_BW_TX_TO_STA,
};
enum ieee80211_sta_rx_bandwidth
ieee80211_sta_current_bw(struct link_sta_info *link_sta,
struct cfg80211_chan_def *chandef,
enum ieee80211_sta_bw_direction direction);
enum sta_stats_type {
STA_STATS_RATE_TYPE_INVALID = 0,
STA_STATS_RATE_TYPE_LEGACY,

View File

@ -311,17 +311,21 @@ ieee80211_tdls_chandef_vht_upgrade(struct ieee80211_sub_if_data *sdata,
/* IEEE802.11ac-2013 Table E-4 */
static const u16 centers_80mhz[] = { 5210, 5290, 5530, 5610, 5690, 5775 };
struct cfg80211_chan_def uc = sta->tdls_chandef;
enum nl80211_chan_width max_width =
ieee80211_sta_cap_chan_bw(&sta->deflink);
enum nl80211_chan_width max_width;
int i;
/* only support upgrading non-narrow channels up to 80Mhz */
if (max_width == NL80211_CHAN_WIDTH_5 ||
max_width == NL80211_CHAN_WIDTH_10)
return;
if (max_width > NL80211_CHAN_WIDTH_80)
switch (ieee80211_sta_current_bw(&sta->deflink, &uc,
IEEE80211_STA_BW_RX_FROM_STA)) {
case IEEE80211_STA_RX_BW_20:
max_width = NL80211_CHAN_WIDTH_20;
break;
case IEEE80211_STA_RX_BW_40:
max_width = NL80211_CHAN_WIDTH_40;
break;
default: /* 80 or higher, only support upgrade to 80 */
max_width = NL80211_CHAN_WIDTH_80;
break;
}
if (uc.width >= max_width)
return;
@ -1334,7 +1338,9 @@ static void iee80211_tdls_recalc_chanctx(struct ieee80211_sub_if_data *sdata,
enum ieee80211_sta_rx_bandwidth bw;
bw = ieee80211_chan_width_to_rx_bw(conf->def.width);
bw = min(bw, ieee80211_sta_cap_rx_bw(&sta->deflink));
bw = min(bw, ieee80211_sta_current_bw(&sta->deflink,
&conf->def,
IEEE80211_STA_BW_RX_FROM_STA));
if (bw != sta->sta.deflink.bandwidth) {
sta->sta.deflink.bandwidth = bw;
rate_control_rate_update(local, sband,

View File

@ -3916,7 +3916,7 @@ struct sk_buff *ieee80211_tx_dequeue(struct ieee80211_hw *hw,
* injected frames or EAPOL frames from the local station.
*/
if (unlikely(!(info->flags & IEEE80211_TX_CTL_INJECTED) &&
ieee80211_is_data(hdr->frame_control) &&
ieee80211_is_data_present(hdr->frame_control) &&
!ieee80211_vif_is_mesh(&tx.sdata->vif) &&
tx.sdata->vif.type != NL80211_IFTYPE_OCB &&
!is_multicast_ether_addr(hdr->addr1) &&

View File

@ -3149,7 +3149,9 @@ bool ieee80211_chandef_vht_oper(struct ieee80211_hw *hw, u32 vht_cap_info,
ext_nss_bw_supp = 0;
/*
* Cf. IEEE 802.11 Table 9-250
* Cf. IEEE 802.11-2020 Table 9-272 - Setting of the Supported Channel
* Width Set subfield and Extended NSS BW Support subfield at a STA
* transmitting the VHT Capabilities Information field
*
* We really just consider that because it's inefficient to connect
* at a higher bandwidth than we'll actually be able to use.
@ -3840,6 +3842,25 @@ void ieee80211_chandef_downgrade(struct cfg80211_chan_def *c,
WARN_ON_ONCE(!cfg80211_chandef_valid(c));
}
enum nl80211_chan_width
ieee80211_sta_rx_bw_to_chan_width(enum ieee80211_sta_rx_bandwidth bw)
{
switch (bw) {
case IEEE80211_STA_RX_BW_20:
return NL80211_CHAN_WIDTH_20;
case IEEE80211_STA_RX_BW_40:
return NL80211_CHAN_WIDTH_40;
case IEEE80211_STA_RX_BW_80:
return NL80211_CHAN_WIDTH_80;
case IEEE80211_STA_RX_BW_160:
return NL80211_CHAN_WIDTH_160;
case IEEE80211_STA_RX_BW_320:
return NL80211_CHAN_WIDTH_320;
default:
return NL80211_CHAN_WIDTH_20;
}
}
int ieee80211_send_action_csa(struct ieee80211_sub_if_data *sdata,
struct cfg80211_csa_settings *csa_settings)
{

View File

@ -301,32 +301,6 @@ ieee80211_vht_cap_ie_to_sta_vht_cap(struct ieee80211_sub_if_data *sdata,
return;
}
/* finally set up the bandwidth */
switch (vht_cap->cap & IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK) {
case IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160MHZ:
case IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ:
link_sta->cur_max_bandwidth = IEEE80211_STA_RX_BW_160;
break;
default:
link_sta->cur_max_bandwidth = IEEE80211_STA_RX_BW_80;
if (!(vht_cap->vht_mcs.tx_highest &
cpu_to_le16(IEEE80211_VHT_EXT_NSS_BW_CAPABLE)))
break;
/*
* If this is non-zero, then it does support 160 MHz after all,
* in one form or the other. We don't distinguish here (or even
* above) between 160 and 80+80 yet.
*/
if (cap_info & IEEE80211_VHT_CAP_EXT_NSS_BW_MASK)
link_sta->cur_max_bandwidth =
IEEE80211_STA_RX_BW_160;
}
if (sdata->vif.type != NL80211_IFTYPE_NAN)
link_sta->pub->bandwidth = ieee80211_sta_cur_vht_bw(link_sta);
/*
* Work around the Cisco 9115 FW 17.3 bug by taking the min of
* both reported MPDU lengths.
@ -357,319 +331,20 @@ ieee80211_vht_cap_ie_to_sta_vht_cap(struct ieee80211_sub_if_data *sdata,
ieee80211_sta_recalc_aggregates(&link_sta->sta->sta);
}
/* FIXME: move this to some better location - parses HE/EHT now */
static enum ieee80211_sta_rx_bandwidth
__ieee80211_sta_cap_rx_bw(struct link_sta_info *link_sta,
struct cfg80211_chan_def *chandef)
{
unsigned int link_id = link_sta->link_id;
struct ieee80211_sub_if_data *sdata = link_sta->sta->sdata;
struct ieee80211_sta_vht_cap *vht_cap = &link_sta->pub->vht_cap;
struct ieee80211_sta_he_cap *he_cap = &link_sta->pub->he_cap;
struct ieee80211_sta_eht_cap *eht_cap = &link_sta->pub->eht_cap;
u32 cap_width;
if (he_cap->has_he) {
enum nl80211_band band;
u8 info;
if (chandef) {
band = chandef->chan->band;
} else {
struct ieee80211_bss_conf *link_conf;
if (WARN_ON_ONCE(sdata->vif.type == NL80211_IFTYPE_NAN_DATA ||
sdata->vif.type == NL80211_IFTYPE_NAN))
return IEEE80211_STA_RX_BW_20;
rcu_read_lock();
link_conf = rcu_dereference(sdata->vif.link_conf[link_id]);
band = link_conf->chanreq.oper.chan->band;
rcu_read_unlock();
}
if (eht_cap->has_eht && band == NL80211_BAND_6GHZ) {
info = eht_cap->eht_cap_elem.phy_cap_info[0];
if (info & IEEE80211_EHT_PHY_CAP0_320MHZ_IN_6GHZ)
return IEEE80211_STA_RX_BW_320;
}
info = he_cap->he_cap_elem.phy_cap_info[0];
if (band == NL80211_BAND_2GHZ) {
if (info & IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G)
return IEEE80211_STA_RX_BW_40;
return IEEE80211_STA_RX_BW_20;
}
if (info & IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G ||
info & IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_80PLUS80_MHZ_IN_5G)
return IEEE80211_STA_RX_BW_160;
if (info & IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G)
return IEEE80211_STA_RX_BW_80;
return IEEE80211_STA_RX_BW_20;
}
if (!vht_cap->vht_supported)
return link_sta->pub->ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40 ?
IEEE80211_STA_RX_BW_40 :
IEEE80211_STA_RX_BW_20;
cap_width = vht_cap->cap & IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK;
if (cap_width == IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160MHZ ||
cap_width == IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ)
return IEEE80211_STA_RX_BW_160;
/*
* If this is non-zero, then it does support 160 MHz after all,
* in one form or the other. We don't distinguish here (or even
* above) between 160 and 80+80 yet.
*/
if (vht_cap->cap & IEEE80211_VHT_CAP_EXT_NSS_BW_MASK)
return IEEE80211_STA_RX_BW_160;
return IEEE80211_STA_RX_BW_80;
}
enum ieee80211_sta_rx_bandwidth
_ieee80211_sta_cap_rx_bw(struct link_sta_info *link_sta,
struct cfg80211_chan_def *chandef)
{
/*
* With RX OMI, also pretend that the STA's capability changed.
* Of course this isn't really true, it didn't change, only our
* RX capability was changed by notifying RX OMI to the STA.
* The purpose, however, is to save power, and that requires
* changing also transmissions to the AP and the chanctx. The
* transmissions depend on link_sta->bandwidth which is set in
* _ieee80211_sta_cur_vht_bw() below, but the chanctx depends
* on the result of this function which is also called by
* _ieee80211_sta_cur_vht_bw(), so we need to do that here as
* well. This is sufficient for the steady state, but during
* the transition we already need to change TX/RX separately,
* so _ieee80211_sta_cur_vht_bw() below applies the _tx one.
*/
return min(__ieee80211_sta_cap_rx_bw(link_sta, chandef),
link_sta->rx_omi_bw_rx);
}
enum nl80211_chan_width
ieee80211_sta_cap_chan_bw(struct link_sta_info *link_sta)
{
struct ieee80211_sta_vht_cap *vht_cap = &link_sta->pub->vht_cap;
u32 cap_width;
if (!vht_cap->vht_supported) {
if (!link_sta->pub->ht_cap.ht_supported)
return NL80211_CHAN_WIDTH_20_NOHT;
return link_sta->pub->ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40 ?
NL80211_CHAN_WIDTH_40 : NL80211_CHAN_WIDTH_20;
}
cap_width = vht_cap->cap & IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK;
if (cap_width == IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160MHZ)
return NL80211_CHAN_WIDTH_160;
else if (cap_width == IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ)
return NL80211_CHAN_WIDTH_80P80;
return NL80211_CHAN_WIDTH_80;
}
enum nl80211_chan_width
ieee80211_sta_rx_bw_to_chan_width(struct link_sta_info *link_sta)
{
enum ieee80211_sta_rx_bandwidth cur_bw =
link_sta->pub->bandwidth;
struct ieee80211_sta_vht_cap *vht_cap =
&link_sta->pub->vht_cap;
u32 cap_width;
switch (cur_bw) {
case IEEE80211_STA_RX_BW_20:
if (!link_sta->pub->ht_cap.ht_supported)
return NL80211_CHAN_WIDTH_20_NOHT;
else
return NL80211_CHAN_WIDTH_20;
case IEEE80211_STA_RX_BW_40:
return NL80211_CHAN_WIDTH_40;
case IEEE80211_STA_RX_BW_80:
return NL80211_CHAN_WIDTH_80;
case IEEE80211_STA_RX_BW_160:
cap_width =
vht_cap->cap & IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK;
if (cap_width == IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160MHZ)
return NL80211_CHAN_WIDTH_160;
return NL80211_CHAN_WIDTH_80P80;
default:
return NL80211_CHAN_WIDTH_20;
}
}
/* FIXME: rename/move - this deals with everything not just VHT */
enum ieee80211_sta_rx_bandwidth
_ieee80211_sta_cur_vht_bw(struct link_sta_info *link_sta,
struct cfg80211_chan_def *chandef)
{
struct sta_info *sta = link_sta->sta;
enum nl80211_chan_width bss_width;
enum ieee80211_sta_rx_bandwidth bw;
if (chandef) {
bss_width = chandef->width;
} else {
struct ieee80211_bss_conf *link_conf;
/* NAN operates on multiple channels so a chandef must be given */
if (WARN_ON_ONCE(sta->sdata->vif.type == NL80211_IFTYPE_NAN ||
sta->sdata->vif.type == NL80211_IFTYPE_NAN_DATA))
return IEEE80211_STA_RX_BW_20;
rcu_read_lock();
link_conf = rcu_dereference(sta->sdata->vif.link_conf[link_sta->link_id]);
if (WARN_ON_ONCE(!link_conf)) {
rcu_read_unlock();
return IEEE80211_STA_RX_BW_20;
}
bss_width = link_conf->chanreq.oper.width;
rcu_read_unlock();
}
/* intentionally do not take rx_bw_omi_rx into account */
bw = __ieee80211_sta_cap_rx_bw(link_sta, chandef);
bw = min(bw, link_sta->cur_max_bandwidth);
/* but do apply rx_omi_bw_tx */
bw = min(bw, link_sta->rx_omi_bw_tx);
/* Don't consider AP's bandwidth for TDLS peers, section 11.23.1 of
* IEEE80211-2016 specification makes higher bandwidth operation
* possible on the TDLS link if the peers have wider bandwidth
* capability.
*
* However, in this case, and only if the TDLS peer is authorized,
* limit to the tdls_chandef so that the configuration here isn't
* wider than what's actually requested on the channel context.
*/
if (test_sta_flag(sta, WLAN_STA_TDLS_PEER) &&
test_sta_flag(sta, WLAN_STA_TDLS_WIDER_BW) &&
test_sta_flag(sta, WLAN_STA_AUTHORIZED) &&
sta->tdls_chandef.chan)
bw = min(bw, ieee80211_chan_width_to_rx_bw(sta->tdls_chandef.width));
else
bw = min(bw, ieee80211_chan_width_to_rx_bw(bss_width));
return bw;
}
void ieee80211_sta_init_nss(struct link_sta_info *link_sta)
{
u8 ht_rx_nss = 0, vht_rx_nss = 0, he_rx_nss = 0, eht_rx_nss = 0, rx_nss;
bool support_160;
if (link_sta->pub->eht_cap.has_eht) {
int i;
const u8 *rx_nss_mcs = (void *)&link_sta->pub->eht_cap.eht_mcs_nss_supp;
/* get the max nss for EHT over all possible bandwidths and mcs */
for (i = 0; i < sizeof(struct ieee80211_eht_mcs_nss_supp); i++)
eht_rx_nss = max_t(u8, eht_rx_nss,
u8_get_bits(rx_nss_mcs[i],
IEEE80211_EHT_MCS_NSS_RX));
}
if (link_sta->pub->he_cap.has_he) {
int i;
u8 rx_mcs_80 = 0, rx_mcs_160 = 0;
const struct ieee80211_sta_he_cap *he_cap = &link_sta->pub->he_cap;
u16 mcs_160_map =
le16_to_cpu(he_cap->he_mcs_nss_supp.rx_mcs_160);
u16 mcs_80_map = le16_to_cpu(he_cap->he_mcs_nss_supp.rx_mcs_80);
for (i = 7; i >= 0; i--) {
u8 mcs_160 = (mcs_160_map >> (2 * i)) & 3;
if (mcs_160 != IEEE80211_HE_MCS_NOT_SUPPORTED) {
rx_mcs_160 = i + 1;
break;
}
}
for (i = 7; i >= 0; i--) {
u8 mcs_80 = (mcs_80_map >> (2 * i)) & 3;
if (mcs_80 != IEEE80211_HE_MCS_NOT_SUPPORTED) {
rx_mcs_80 = i + 1;
break;
}
}
support_160 = he_cap->he_cap_elem.phy_cap_info[0] &
IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G;
if (support_160)
he_rx_nss = min(rx_mcs_80, rx_mcs_160);
else
he_rx_nss = rx_mcs_80;
}
if (link_sta->pub->ht_cap.ht_supported) {
if (link_sta->pub->ht_cap.mcs.rx_mask[0])
ht_rx_nss++;
if (link_sta->pub->ht_cap.mcs.rx_mask[1])
ht_rx_nss++;
if (link_sta->pub->ht_cap.mcs.rx_mask[2])
ht_rx_nss++;
if (link_sta->pub->ht_cap.mcs.rx_mask[3])
ht_rx_nss++;
/* FIXME: consider rx_highest? */
}
if (link_sta->pub->vht_cap.vht_supported) {
int i;
u16 rx_mcs_map;
rx_mcs_map = le16_to_cpu(link_sta->pub->vht_cap.vht_mcs.rx_mcs_map);
for (i = 7; i >= 0; i--) {
u8 mcs = (rx_mcs_map >> (2 * i)) & 3;
if (mcs != IEEE80211_VHT_MCS_NOT_SUPPORTED) {
vht_rx_nss = i + 1;
break;
}
}
/* FIXME: consider rx_highest? */
}
rx_nss = max(vht_rx_nss, ht_rx_nss);
rx_nss = max(he_rx_nss, rx_nss);
rx_nss = max(eht_rx_nss, rx_nss);
rx_nss = max_t(u8, 1, rx_nss);
link_sta->capa_nss = rx_nss;
/* that shouldn't be set yet, but we can handle it anyway */
if (link_sta->op_mode_nss)
link_sta->pub->rx_nss =
min_t(u8, rx_nss, link_sta->op_mode_nss);
else
link_sta->pub->rx_nss = rx_nss;
}
u32 __ieee80211_vht_handle_opmode(struct ieee80211_sub_if_data *sdata,
struct link_sta_info *link_sta,
u8 opmode, enum nl80211_band band)
{
enum ieee80211_sta_rx_bandwidth new_bw;
struct sta_opmode_info sta_opmode = {};
struct ieee80211_link_data *link;
u32 changed = 0;
u8 nss;
link = sdata_dereference(sdata->link[link_sta->link_id], sdata);
if (WARN_ON(!link))
return 0;
/* ignore - no support for BF yet */
if (opmode & IEEE80211_OPMODE_NOTIF_RX_NSS_TYPE_BF)
return 0;
@ -698,28 +373,29 @@ u32 __ieee80211_vht_handle_opmode(struct ieee80211_sub_if_data *sdata,
switch (opmode & IEEE80211_OPMODE_NOTIF_CHANWIDTH_MASK) {
case IEEE80211_OPMODE_NOTIF_CHANWIDTH_20MHZ:
/* ignore IEEE80211_OPMODE_NOTIF_BW_160_80P80 must not be set */
link_sta->cur_max_bandwidth = IEEE80211_STA_RX_BW_20;
link_sta->op_mode_bw = IEEE80211_STA_RX_BW_20;
break;
case IEEE80211_OPMODE_NOTIF_CHANWIDTH_40MHZ:
/* ignore IEEE80211_OPMODE_NOTIF_BW_160_80P80 must not be set */
link_sta->cur_max_bandwidth = IEEE80211_STA_RX_BW_40;
link_sta->op_mode_bw = IEEE80211_STA_RX_BW_40;
break;
case IEEE80211_OPMODE_NOTIF_CHANWIDTH_80MHZ:
if (opmode & IEEE80211_OPMODE_NOTIF_BW_160_80P80)
link_sta->cur_max_bandwidth = IEEE80211_STA_RX_BW_160;
link_sta->op_mode_bw = IEEE80211_STA_RX_BW_160;
else
link_sta->cur_max_bandwidth = IEEE80211_STA_RX_BW_80;
link_sta->op_mode_bw = IEEE80211_STA_RX_BW_80;
break;
case IEEE80211_OPMODE_NOTIF_CHANWIDTH_160MHZ:
/* legacy only, no longer used by newer spec */
link_sta->cur_max_bandwidth = IEEE80211_STA_RX_BW_160;
link_sta->op_mode_bw = IEEE80211_STA_RX_BW_160;
break;
}
new_bw = ieee80211_sta_cur_vht_bw(link_sta);
new_bw = ieee80211_sta_current_bw(link_sta, &link->conf->chanreq.oper,
IEEE80211_STA_BW_TX_TO_STA);
if (new_bw != link_sta->pub->bandwidth) {
link_sta->pub->bandwidth = new_bw;
sta_opmode.bw = ieee80211_sta_rx_bw_to_chan_width(link_sta);
sta_opmode.bw = ieee80211_sta_rx_bw_to_chan_width(new_bw);
changed |= IEEE80211_RC_BW_CHANGED;
sta_opmode.changed |= STA_OPMODE_MAX_BW_CHANGED;
}

View File

@ -804,6 +804,24 @@ static int wiphy_verify_combinations(struct wiphy *wiphy)
return ret;
}
static bool wiphy_cipher_suites_valid(const struct wiphy *wiphy)
{
int i, j;
if (wiphy->n_cipher_suites && !wiphy->cipher_suites)
return false;
for (i = 0; i < wiphy->n_cipher_suites; i++) {
for (j = 0; j < i; j++) {
if (wiphy->cipher_suites[i] ==
wiphy->cipher_suites[j])
return false;
}
}
return true;
}
int wiphy_register(struct wiphy *wiphy)
{
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
@ -940,6 +958,9 @@ int wiphy_register(struct wiphy *wiphy)
if (res)
return res;
if (!wiphy_cipher_suites_valid(wiphy))
return -EINVAL;
/* sanity check supported bands/channels */
for (band = 0; band < NUM_NL80211_BANDS; band++) {
const struct ieee80211_sband_iftype_data *iftd;

View File

@ -3,7 +3,7 @@
* Wireless configuration interface internals.
*
* Copyright 2006-2010 Johannes Berg <johannes@sipsolutions.net>
* Copyright (C) 2018-2025 Intel Corporation
* Copyright (C) 2018-2026 Intel Corporation
*/
#ifndef __NET_WIRELESS_CORE_H
#define __NET_WIRELESS_CORE_H
@ -446,6 +446,7 @@ bool cfg80211_supported_cipher_suite(struct wiphy *wiphy, u32 cipher);
bool cfg80211_valid_key_idx(struct cfg80211_registered_device *rdev,
int key_idx, bool pairwise);
int cfg80211_validate_key_settings(struct cfg80211_registered_device *rdev,
struct wireless_dev *wdev,
struct key_params *params, int key_idx,
bool pairwise, const u8 *mac_addr);
void __cfg80211_scan_done(struct wiphy *wiphy, struct wiphy_work *wk);

View File

@ -1733,6 +1733,7 @@ static int nl80211_parse_key(struct genl_info *info, struct key_parse *k)
static struct cfg80211_cached_keys *
nl80211_parse_connkeys(struct cfg80211_registered_device *rdev,
struct wireless_dev *wdev,
struct genl_info *info, bool *no_ht)
{
struct nlattr *keys = info->attrs[NL80211_ATTR_KEYS];
@ -1782,7 +1783,7 @@ nl80211_parse_connkeys(struct cfg80211_registered_device *rdev,
goto error;
} else if (parse.defmgmt)
goto error;
err = cfg80211_validate_key_settings(rdev, &parse.p,
err = cfg80211_validate_key_settings(rdev, wdev, &parse.p,
parse.idx, false, NULL);
if (err)
goto error;
@ -5100,7 +5101,7 @@ static int nl80211_validate_key_link_id(struct genl_info *info,
if (wdev->valid_links) {
if (link_id == -1) {
GENL_SET_ERR_MSG(info,
"link ID must for MLO group key");
"link ID must be set for MLO group key");
return -EINVAL;
}
if (!(wdev->valid_links & BIT(link_id))) {
@ -5407,7 +5408,7 @@ static int nl80211_new_key(struct sk_buff *skb, struct genl_info *info)
if (!rdev->ops->add_key)
return -EOPNOTSUPP;
if (cfg80211_validate_key_settings(rdev, &key.p, key.idx,
if (cfg80211_validate_key_settings(rdev, wdev, &key.p, key.idx,
key.type == NL80211_KEYTYPE_PAIRWISE,
mac_addr)) {
GENL_SET_ERR_MSG(info, "key setting validation failed");
@ -6394,9 +6395,104 @@ static int nl80211_parse_he_bss_color(struct nlattr *attrs,
return 0;
}
static void nl80211_check_ap_rate_selectors(struct cfg80211_beacon_data *bcn,
const struct element *rates)
{
int i;
if (!rates)
return;
for (i = 0; i < rates->datalen; i++) {
if (rates->data[i] == BSS_MEMBERSHIP_SELECTOR_HT_PHY)
bcn->ht_required = true;
if (rates->data[i] == BSS_MEMBERSHIP_SELECTOR_VHT_PHY)
bcn->vht_required = true;
}
}
/*
* Since the nl80211 API didn't include, from the beginning, attributes about
* HT/VHT/... operation, we parse them out of the elements and check for
* validity for use by drivers/mac80211.
*/
static int nl80211_calculate_ap_operation(struct nlattr *attrs[],
struct cfg80211_beacon_data *bcn,
struct netlink_ext_ack *extack)
{
size_t ies_len = bcn->tail_len;
const u8 *ies = bcn->tail;
const struct element *rates;
const struct element *op;
rates = cfg80211_find_elem(WLAN_EID_SUPP_RATES, ies, ies_len);
nl80211_check_ap_rate_selectors(bcn, rates);
rates = cfg80211_find_elem(WLAN_EID_EXT_SUPP_RATES, ies, ies_len);
nl80211_check_ap_rate_selectors(bcn, rates);
op = cfg80211_find_ext_elem(WLAN_EID_EXT_HE_OPERATION, ies, ies_len);
if (op) {
if (op->datalen < sizeof(*bcn->he_oper) + 1) {
NL_SET_ERR_MSG(extack, "bad HE operation in beacon");
return -EINVAL;
}
bcn->he_oper = (void *)(op->data + 1);
/* takes extension ID into account */
if (op->datalen < ieee80211_he_oper_size((void *)bcn->he_oper)) {
NL_SET_ERR_MSG(extack, "bad HE operation in beacon");
return -EINVAL;
}
}
op = cfg80211_find_elem(WLAN_EID_HT_OPERATION, ies, ies_len);
if (op) {
if (op->datalen < sizeof(*bcn->ht_oper)) {
NL_SET_ERR_MSG(extack, "bad HT operation in beacon");
return -EINVAL;
}
bcn->ht_oper = (void *)op->data;
}
op = cfg80211_find_elem(WLAN_EID_VHT_OPERATION, ies, ies_len);
if (op) {
if (op->datalen < sizeof(*bcn->vht_oper)) {
NL_SET_ERR_MSG(extack, "bad VHT operation in beacon");
return -EINVAL;
}
bcn->vht_oper = (void *)op->data;
}
op = cfg80211_find_ext_elem(WLAN_EID_EXT_EHT_OPERATION, ies, ies_len);
if (op) {
if (!ieee80211_eht_oper_size_ok(op->data + 1,
op->datalen - 1)) {
NL_SET_ERR_MSG(extack, "bad EHT operation in beacon");
return -EINVAL;
}
bcn->eht_oper = (void *)(op->data + 1);
}
op = cfg80211_find_ext_elem(WLAN_EID_EXT_UHR_OPER, ies, ies_len);
if (op) {
/* need full UHR operation separately */
if (!attrs[NL80211_ATTR_UHR_OPERATION]) {
NL_SET_ERR_MSG(extack, "missing UHR operation");
return -EINVAL;
}
bcn->uhr_oper = nla_data(attrs[NL80211_ATTR_UHR_OPERATION]);
} else if (attrs[NL80211_ATTR_UHR_OPERATION]) {
NL_SET_ERR_MSG(extack, "unexpected UHR operation");
return -EINVAL;
}
return 0;
}
static int nl80211_parse_beacon(struct cfg80211_registered_device *rdev,
struct nlattr *attrs[],
struct cfg80211_beacon_data *bcn,
struct ieee80211_channel *chan,
struct netlink_ext_ack *extack)
{
bool haveinfo = false;
@ -6511,6 +6607,19 @@ static int nl80211_parse_beacon(struct cfg80211_registered_device *rdev,
}
}
err = nl80211_calculate_ap_operation(attrs, bcn, extack);
if (err)
return err;
if (bcn->he_oper && (chan->flags & IEEE80211_CHAN_NO_HE))
return -EOPNOTSUPP;
if (bcn->eht_oper && (chan->flags & IEEE80211_CHAN_NO_EHT))
return -EOPNOTSUPP;
if (bcn->uhr_oper && (chan->flags & IEEE80211_CHAN_NO_UHR))
return -EOPNOTSUPP;
return 0;
}
@ -6628,75 +6737,54 @@ nl80211_parse_unsol_bcast_probe_resp(struct cfg80211_registered_device *rdev,
return 0;
}
static void nl80211_check_ap_rate_selectors(struct cfg80211_ap_settings *params,
const struct element *rates)
{
int i;
if (!rates)
return;
for (i = 0; i < rates->datalen; i++) {
if (rates->data[i] == BSS_MEMBERSHIP_SELECTOR_HT_PHY)
params->ht_required = true;
if (rates->data[i] == BSS_MEMBERSHIP_SELECTOR_VHT_PHY)
params->vht_required = true;
if (rates->data[i] == BSS_MEMBERSHIP_SELECTOR_HE_PHY)
params->he_required = true;
if (rates->data[i] == BSS_MEMBERSHIP_SELECTOR_SAE_H2E)
params->sae_h2e_required = true;
}
}
/*
* Since the nl80211 API didn't include, from the beginning, attributes about
* HT/VHT requirements/capabilities, we parse them out of the IEs for the
* benefit of drivers that rebuild IEs in the firmware.
* HT/VHT/... capabilities, we parse them out of the elements and check for
* validity for use by drivers/mac80211.
*/
static int nl80211_calculate_ap_params(struct cfg80211_ap_settings *params)
static int nl80211_calculate_ap_capabilities(struct genl_info *info,
struct cfg80211_ap_settings *params)
{
const struct cfg80211_beacon_data *bcn = &params->beacon;
size_t ies_len = bcn->tail_len;
const u8 *ies = bcn->tail;
const struct element *rates;
size_t ies_len = params->beacon.tail_len;
const u8 *ies = params->beacon.tail;
const struct element *cap;
rates = cfg80211_find_elem(WLAN_EID_SUPP_RATES, ies, ies_len);
nl80211_check_ap_rate_selectors(params, rates);
rates = cfg80211_find_elem(WLAN_EID_EXT_SUPP_RATES, ies, ies_len);
nl80211_check_ap_rate_selectors(params, rates);
cap = cfg80211_find_elem(WLAN_EID_HT_CAPABILITY, ies, ies_len);
if (cap && cap->datalen >= sizeof(*params->ht_cap))
if (cap) {
if (cap->datalen < sizeof(*params->ht_cap)) {
GENL_SET_ERR_MSG(info, "bad HT capability in beacon");
return -EINVAL;
}
params->ht_cap = (void *)cap->data;
}
cap = cfg80211_find_elem(WLAN_EID_VHT_CAPABILITY, ies, ies_len);
if (cap && cap->datalen >= sizeof(*params->vht_cap))
if (cap) {
if (cap->datalen < sizeof(*params->vht_cap)) {
GENL_SET_ERR_MSG(info, "bad VHT capability in beacon");
return -EINVAL;
}
params->vht_cap = (void *)cap->data;
}
cap = cfg80211_find_ext_elem(WLAN_EID_EXT_HE_CAPABILITY, ies, ies_len);
if (cap && cap->datalen >= sizeof(*params->he_cap) + 1)
if (cap) {
if (cap->datalen < sizeof(*params->he_cap) + 1) {
GENL_SET_ERR_MSG(info, "bad HE capability in beacon");
return -EINVAL;
}
params->he_cap = (void *)(cap->data + 1);
cap = cfg80211_find_ext_elem(WLAN_EID_EXT_HE_OPERATION, ies, ies_len);
if (cap && cap->datalen >= sizeof(*params->he_oper) + 1)
params->he_oper = (void *)(cap->data + 1);
}
cap = cfg80211_find_ext_elem(WLAN_EID_EXT_EHT_CAPABILITY, ies, ies_len);
if (cap) {
if (!cap->datalen)
return -EINVAL;
params->eht_cap = (void *)(cap->data + 1);
if (!ieee80211_eht_capa_size_ok((const u8 *)params->he_cap,
(const u8 *)params->eht_cap,
cap->datalen - 1, true))
return -EINVAL;
}
cap = cfg80211_find_ext_elem(WLAN_EID_EXT_EHT_OPERATION, ies, ies_len);
if (cap) {
if (!cap->datalen)
return -EINVAL;
params->eht_oper = (void *)(cap->data + 1);
if (!ieee80211_eht_oper_size_ok((const u8 *)params->eht_oper,
cap->datalen - 1))
cap->datalen - 1, true)) {
GENL_SET_ERR_MSG(info, "bad EHT capability in beacon");
return -EINVAL;
}
}
return 0;
@ -6826,24 +6914,6 @@ static void nl80211_send_ap_started(struct wireless_dev *wdev,
nlmsg_free(msg);
}
static int nl80211_validate_ap_phy_operation(struct cfg80211_ap_settings *params)
{
struct ieee80211_channel *channel = params->chandef.chan;
if ((params->he_cap || params->he_oper) &&
(channel->flags & IEEE80211_CHAN_NO_HE))
return -EOPNOTSUPP;
if ((params->eht_cap || params->eht_oper) &&
(channel->flags & IEEE80211_CHAN_NO_EHT))
return -EOPNOTSUPP;
if (params->uhr_oper && (channel->flags & IEEE80211_CHAN_NO_UHR))
return -EOPNOTSUPP;
return 0;
}
static int
nl80211_parse_s1g_short_beacon(struct cfg80211_registered_device *rdev,
struct nlattr *attrs,
@ -6916,11 +6986,6 @@ static int nl80211_start_ap(struct sk_buff *skb, struct genl_info *info)
if (!params)
return -ENOMEM;
err = nl80211_parse_beacon(rdev, info->attrs, &params->beacon,
info->extack);
if (err)
goto out;
params->beacon_interval =
nla_get_u32(info->attrs[NL80211_ATTR_BEACON_INTERVAL]);
params->dtim_period =
@ -7037,6 +7102,11 @@ static int nl80211_start_ap(struct sk_buff *skb, struct genl_info *info)
goto out;
}
err = nl80211_parse_beacon(rdev, info->attrs, &params->beacon,
params->chandef.chan, info->extack);
if (err)
goto out;
beacon_check.iftype = wdev->iftype;
beacon_check.relax = true;
beacon_check.reg_power =
@ -7136,14 +7206,7 @@ static int nl80211_start_ap(struct sk_buff *skb, struct genl_info *info)
goto out;
}
err = nl80211_calculate_ap_params(params);
if (err)
goto out;
if (info->attrs[NL80211_ATTR_UHR_OPERATION])
params->uhr_oper = nla_data(info->attrs[NL80211_ATTR_UHR_OPERATION]);
err = nl80211_validate_ap_phy_operation(params);
err = nl80211_calculate_ap_capabilities(info, params);
if (err)
goto out;
@ -7214,6 +7277,7 @@ static int nl80211_set_beacon(struct sk_buff *skb, struct genl_info *info)
return -ENOMEM;
err = nl80211_parse_beacon(rdev, info->attrs, &params->beacon,
wdev->links[link_id].ap.chandef.chan,
info->extack);
if (err)
goto out;
@ -11818,11 +11882,16 @@ static int nl80211_channel_switch(struct sk_buff *skb, struct genl_info *info)
params.count = cs_count;
err = nl80211_parse_chandef(rdev, info->extack, info->attrs,
&params.chandef);
if (err)
goto free;
if (!need_new_beacon)
goto skip_beacons;
err = nl80211_parse_beacon(rdev, info->attrs, &params.beacon_after,
info->extack);
params.chandef.chan, info->extack);
if (err)
goto free;
@ -11839,6 +11908,7 @@ static int nl80211_channel_switch(struct sk_buff *skb, struct genl_info *info)
goto free;
err = nl80211_parse_beacon(rdev, csa_attrs, &params.beacon_csa,
wdev->links[link_id].ap.chandef.chan,
info->extack);
if (err)
goto free;
@ -11867,11 +11937,6 @@ static int nl80211_channel_switch(struct sk_buff *skb, struct genl_info *info)
goto free;
skip_beacons:
err = nl80211_parse_chandef(rdev, info->extack, info->attrs,
&params.chandef);
if (err)
goto free;
if (!cfg80211_reg_can_beacon_relax(&rdev->wiphy, &params.chandef,
wdev->iftype)) {
err = -EINVAL;
@ -13163,7 +13228,8 @@ static int nl80211_join_ibss(struct sk_buff *skb, struct genl_info *info)
if (ibss.privacy && info->attrs[NL80211_ATTR_KEYS]) {
bool no_ht = false;
connkeys = nl80211_parse_connkeys(rdev, info, &no_ht);
connkeys = nl80211_parse_connkeys(rdev, dev->ieee80211_ptr,
info, &no_ht);
if (IS_ERR(connkeys))
return PTR_ERR(connkeys);
@ -13605,7 +13671,8 @@ static int nl80211_connect(struct sk_buff *skb, struct genl_info *info)
}
if (connect.privacy && info->attrs[NL80211_ATTR_KEYS]) {
connkeys = nl80211_parse_connkeys(rdev, info, NULL);
connkeys = nl80211_parse_connkeys(rdev, dev->ieee80211_ptr,
info, NULL);
if (IS_ERR(connkeys))
return PTR_ERR(connkeys);
}
@ -18324,7 +18391,12 @@ static int nl80211_color_change(struct sk_buff *skb, struct genl_info *info)
params.count = nla_get_u8(info->attrs[NL80211_ATTR_COLOR_CHANGE_COUNT]);
params.color = nla_get_u8(info->attrs[NL80211_ATTR_COLOR_CHANGE_COLOR]);
params.link_id = nl80211_link_id(info->attrs);
if (!wdev->links[params.link_id].ap.beacon_interval)
return -EINVAL;
err = nl80211_parse_beacon(rdev, info->attrs, &params.beacon_next,
wdev->links[params.link_id].ap.chandef.chan,
info->extack);
if (err)
return err;
@ -18340,6 +18412,7 @@ static int nl80211_color_change(struct sk_buff *skb, struct genl_info *info)
goto out;
err = nl80211_parse_beacon(rdev, tb, &params.beacon_color_change,
wdev->links[params.link_id].ap.chandef.chan,
info->extack);
if (err)
goto out;
@ -18397,7 +18470,6 @@ static int nl80211_color_change(struct sk_buff *skb, struct genl_info *info)
goto out;
}
params.link_id = nl80211_link_id(info->attrs);
err = rdev_color_change(rdev, dev, &params);
out:

View File

@ -284,6 +284,7 @@ bool cfg80211_valid_key_idx(struct cfg80211_registered_device *rdev,
}
int cfg80211_validate_key_settings(struct cfg80211_registered_device *rdev,
struct wireless_dev *wdev,
struct key_params *params, int key_idx,
bool pairwise, const u8 *mac_addr)
{
@ -344,6 +345,15 @@ int cfg80211_validate_key_settings(struct cfg80211_registered_device *rdev,
break;
}
/*
* Per Wi-Fi Aware v4.0 section 7.1.2, NAN Data interfaces
* shall only use CCMP-128 or GCMP-256.
*/
if (wdev->iftype == NL80211_IFTYPE_NAN_DATA &&
params->cipher != WLAN_CIPHER_SUITE_CCMP &&
params->cipher != WLAN_CIPHER_SUITE_GCMP_256)
return -EINVAL;
switch (params->cipher) {
case WLAN_CIPHER_SUITE_WEP40:
if (params->key_len != WLAN_KEY_LEN_WEP40)

View File

@ -489,7 +489,8 @@ static int cfg80211_set_encryption(struct cfg80211_registered_device *rdev,
if (addr)
tx_key = false;
if (cfg80211_validate_key_settings(rdev, params, idx, pairwise, addr))
if (cfg80211_validate_key_settings(rdev, wdev, params, idx,
pairwise, addr))
return -EINVAL;
err = 0;