[PATCH v2 ath-next] wifi: ath12k: add support for BSS color change

Baochen Qiang baochen.qiang at oss.qualcomm.com
Thu Oct 16 23:38:15 PDT 2025



On 10/17/2025 2:01 PM, Wei Zhang wrote:
> Add support for handling BSS color collision events reported by firmware.
> 
> There are two scenarios where a BSS color collision may be detected:
> 1. The AP's MAC detects the collision directly, and firmware reports a
>    BSS color collision event to the host.
> 2. A STA associated with the AP detects the collision. The notification
>    frame from the peer is routed directly to the AP firmware, which handles
>    it and sends the BSS color collision event to the host.
> 
> Add logic to parse and handle such events, and pass the data
> up to mac80211.
> 
> Unlike CSA, firmware does not provide an offload mechanism for BSS color
> change. Instead, the color change process is triggered via beacon offload
> TX completion events sent by firmware.
> 
> BSS color feature is enabled depending on service flag advertised by
> firmware, based on which color change functionality is invoked.
> 
> This change builds upon the following ath11k patch.
> commit 886433a98425 ("ath11k: add support for BSS color change")
> 
> Tested-on: WCN7850 hw2.0 PCI WLAN.IOE_HMT.1.1-00011-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1
> 
> Signed-off-by: Wei Zhang <wei.zhang at oss.qualcomm.com>
> ---
> Changes in v2:
> - Drop "This patch" from commit message
> ---
>  drivers/net/wireless/ath/ath12k/core.h |  1 +
>  drivers/net/wireless/ath/ath12k/mac.c  | 58 +++++++++++++++++++-
>  drivers/net/wireless/ath/ath12k/wmi.c  | 73 +++++++++++++++++++++++++-
>  drivers/net/wireless/ath/ath12k/wmi.h  | 24 +++++++++
>  4 files changed, 154 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h
> index 02a32b9f3ac2..41da0efaa854 100644
> --- a/drivers/net/wireless/ath/ath12k/core.h
> +++ b/drivers/net/wireless/ath/ath12k/core.h
> @@ -356,6 +356,7 @@ struct ath12k_link_vif {
>  	bool pairwise_key_done;
>  	u16 num_stations;
>  	bool is_csa_in_progress;
> +	struct wiphy_work bcn_tx_work;
>  };
>  
>  struct ath12k_vif {
> diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c
> index 86b79deacf03..eb9b9a322870 100644
> --- a/drivers/net/wireless/ath/ath12k/mac.c
> +++ b/drivers/net/wireless/ath/ath12k/mac.c
> @@ -3834,6 +3834,38 @@ static void ath12k_recalculate_mgmt_rate(struct ath12k *ar,
>  		ath12k_warn(ar->ab, "failed to set beacon tx rate %d\n", ret);
>  }
>  
> +static void ath12k_mac_bcn_tx_event(struct ath12k_link_vif *arvif)
> +{
> +	struct ieee80211_vif *vif = ath12k_ahvif_to_vif(arvif->ahvif);
> +	struct ieee80211_bss_conf *link_conf;
> +
> +	link_conf = ath12k_mac_get_link_bss_conf(arvif);
> +	if (!link_conf) {
> +		ath12k_warn(arvif->ar->ab, "failed to get link conf for vdev %u\n",
> +			    arvif->vdev_id);
> +		return;
> +	}
> +
> +	if (link_conf->color_change_active) {
> +		if (ieee80211_beacon_cntdwn_is_complete(vif, arvif->link_id)) {
> +			ieee80211_color_change_finish(vif, arvif->link_id);
> +			return;
> +		}
> +
> +		ieee80211_beacon_update_cntdwn(vif, arvif->link_id);
> +		ath12k_mac_setup_bcn_tmpl(arvif);
> +	}
> +}
> +
> +static void ath12k_mac_bcn_tx_work(struct wiphy *wiphy, struct wiphy_work *work)
> +{
> +	struct ath12k_link_vif *arvif = container_of(work, struct ath12k_link_vif,
> +						     bcn_tx_work);
> +
> +	lockdep_assert_wiphy(wiphy);
> +	ath12k_mac_bcn_tx_event(arvif);
> +}
> +
>  static void ath12k_mac_init_arvif(struct ath12k_vif *ahvif,
>  				  struct ath12k_link_vif *arvif, int link_id)
>  {
> @@ -3863,6 +3895,7 @@ static void ath12k_mac_init_arvif(struct ath12k_vif *ahvif,
>  	INIT_LIST_HEAD(&arvif->list);
>  	INIT_DELAYED_WORK(&arvif->connection_loss_work,
>  			  ath12k_mac_vif_sta_connection_loss_work);
> +	wiphy_work_init(&arvif->bcn_tx_work, ath12k_mac_bcn_tx_work);
>  
>  	arvif->num_stations = 0;
>  
> @@ -3900,6 +3933,7 @@ static void ath12k_mac_remove_link_interface(struct ieee80211_hw *hw,
>  	lockdep_assert_wiphy(ah->hw->wiphy);
>  
>  	cancel_delayed_work_sync(&arvif->connection_loss_work);
> +	wiphy_work_cancel(ath12k_ar_to_hw(ar)->wiphy, &arvif->bcn_tx_work);
>  
>  	ath12k_dbg(ar->ab, ATH12K_DBG_MAC, "mac remove link interface (vdev %d link id %d)",
>  		   arvif->vdev_id, arvif->link_id);
> @@ -4547,8 +4581,25 @@ static void ath12k_mac_bss_info_changed(struct ath12k *ar,
>  							    ATH12K_BSS_COLOR_AP_PERIODS,
>  							    info->he_bss_color.enabled);
>  			if (ret)
> -				ath12k_warn(ar->ab, "failed to set bss color collision on vdev %i: %d\n",
> +				ath12k_warn(ar->ab, "failed to set bss color collision on vdev %u: %d\n",
>  					    arvif->vdev_id,  ret);
> +
> +			param_id = WMI_VDEV_PARAM_BSS_COLOR;
> +			if (info->he_bss_color.enabled)
> +				param_value = info->he_bss_color.color <<
> +					      IEEE80211_HE_OPERATION_BSS_COLOR_OFFSET;
> +			else
> +				param_value = IEEE80211_HE_OPERATION_BSS_COLOR_DISABLED;
> +
> +			ret = ath12k_wmi_vdev_set_param_cmd(ar, arvif->vdev_id,
> +							    param_id,
> +							    param_value);
> +			if (ret)
> +				ath12k_warn(ar->ab, "failed to set bss color param on vdev %u: %d\n",
> +					    arvif->vdev_id,  ret);
> +			else
> +				ath12k_dbg(ar->ab, ATH12K_DBG_MAC, "bss color param 0x%x set on vdev %u\n",
> +					   param_value, arvif->vdev_id);
>  		} else if (vif->type == NL80211_IFTYPE_STATION) {
>  			ret = ath12k_wmi_send_bss_color_change_enable_cmd(ar,
>  									  arvif->vdev_id,
> @@ -13972,6 +14023,11 @@ static int ath12k_mac_hw_register(struct ath12k_hw *ah)
>  	wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_CQM_RSSI_LIST);
>  	wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_STA_TX_PWR);
>  	wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_ACK_SIGNAL_SUPPORT);
> +	if (test_bit(WMI_TLV_SERVICE_BSS_COLOR_OFFLOAD,
> +		     ab->wmi_ab.svc_map)) {
> +		wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_BSS_COLOR);
> +		ieee80211_hw_set(hw, DETECTS_COLOR_COLLISION);
> +	}
>  
>  	wiphy->cipher_suites = cipher_suites;
>  	wiphy->n_cipher_suites = ARRAY_SIZE(cipher_suites);
> diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c
> index e76275bd6916..5075d86df36f 100644
> --- a/drivers/net/wireless/ath/ath12k/wmi.c
> +++ b/drivers/net/wireless/ath/ath12k/wmi.c
> @@ -14,6 +14,7 @@
>  #include <linux/uuid.h>
>  #include <linux/time.h>
>  #include <linux/of.h>
> +#include <linux/cleanup.h>
>  #include "core.h"
>  #include "debugfs.h"
>  #include "debug.h"
> @@ -190,6 +191,8 @@ static const struct ath12k_wmi_tlv_policy ath12k_wmi_tlv_policies[] = {
>  		.min_len = sizeof(struct wmi_11d_new_cc_event) },
>  	[WMI_TAG_PER_CHAIN_RSSI_STATS] = {
>  		.min_len = sizeof(struct wmi_per_chain_rssi_stat_params) },
> +	[WMI_TAG_OBSS_COLOR_COLLISION_EVT] = {
> +		.min_len = sizeof(struct wmi_obss_color_collision_event) },
>  };
>  
>  __le32 ath12k_wmi_tlv_hdr(u32 cmd, u32 len)
> @@ -3850,6 +3853,58 @@ int ath12k_wmi_fils_discovery(struct ath12k *ar, u32 vdev_id, u32 interval,
>  	return ret;
>  }
>  
> +static void
> +ath12k_wmi_obss_color_collision_event(struct ath12k_base *ab, struct sk_buff *skb)
> +{
> +	const struct wmi_obss_color_collision_event *ev;
> +	struct ath12k_link_vif *arvif;
> +	u32 vdev_id, evt_type;
> +	u64 bitmap;
> +
> +	const void **tb __free(kfree) = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
> +	if (IS_ERR(tb)) {
> +		ath12k_warn(ab, "failed to parse OBSS color collision tlv %ld\n",
> +			    PTR_ERR(tb));
> +		return;
> +	}
> +
> +	ev = tb[WMI_TAG_OBSS_COLOR_COLLISION_EVT];
> +	if (!ev) {
> +		ath12k_warn(ab, "failed to fetch OBSS color collision event\n");
> +		return;
> +	}
> +
> +	vdev_id = le32_to_cpu(ev->vdev_id);
> +	evt_type = le32_to_cpu(ev->evt_type);
> +	bitmap = le64_to_cpu(ev->obss_color_bitmap);
> +
> +	guard(rcu)();
> +
> +	arvif = ath12k_mac_get_arvif_by_vdev_id(ab, vdev_id);
> +	if (!arvif) {
> +		ath12k_warn(ab, "no arvif found for vdev %u in OBSS color collision event\n",
> +			    vdev_id);
> +		return;
> +	}
> +
> +	switch (evt_type) {
> +	case WMI_BSS_COLOR_COLLISION_DETECTION:
> +		ieee80211_obss_color_collision_notify(arvif->ahvif->vif,
> +						      bitmap,
> +						      arvif->link_id);
> +		ath12k_dbg(ab, ATH12K_DBG_WMI,
> +			   "obss color collision detected vdev %u event %d bitmap %016llx\n",
> +			   vdev_id, evt_type, bitmap);
> +		break;
> +	case WMI_BSS_COLOR_COLLISION_DISABLE:
> +	case WMI_BSS_COLOR_FREE_SLOT_TIMER_EXPIRY:
> +	case WMI_BSS_COLOR_FREE_SLOT_AVAILABLE:
> +		break;
> +	default:
> +		ath12k_warn(ab, "unknown OBSS color collision event type %d\n", evt_type);
> +	}
> +}
> +
>  static void
>  ath12k_fill_band_to_mac_param(struct ath12k_base  *soc,
>  			      struct ath12k_wmi_pdev_band_arg *arg)
> @@ -7014,12 +7069,26 @@ static void ath12k_vdev_start_resp_event(struct ath12k_base *ab, struct sk_buff
>  
>  static void ath12k_bcn_tx_status_event(struct ath12k_base *ab, struct sk_buff *skb)
>  {
> +	struct ath12k_link_vif *arvif;
> +	struct ath12k *ar;
>  	u32 vdev_id, tx_status;
>  
>  	if (ath12k_pull_bcn_tx_status_ev(ab, skb, &vdev_id, &tx_status) != 0) {
>  		ath12k_warn(ab, "failed to extract bcn tx status");
>  		return;
>  	}
> +
> +	guard(rcu)();
> +
> +	arvif = ath12k_mac_get_arvif_by_vdev_id(ab, vdev_id);
> +	if (!arvif) {
> +		ath12k_warn(ab, "invalid vdev %u in bcn tx status\n",
> +			    vdev_id);
> +		return;
> +	}
> +
> +	ar = arvif->ar;
> +	wiphy_work_queue(ath12k_ar_to_hw(ar)->wiphy, &arvif->bcn_tx_work);
>  }
>  
>  static void ath12k_vdev_stopped_event(struct ath12k_base *ab, struct sk_buff *skb)
> @@ -9877,6 +9946,9 @@ static void ath12k_wmi_op_rx(struct ath12k_base *ab, struct sk_buff *skb)
>  	case WMI_PDEV_RSSI_DBM_CONVERSION_PARAMS_INFO_EVENTID:
>  		ath12k_wmi_rssi_dbm_conversion_params_info_event(ab, skb);
>  		break;
> +	case WMI_OBSS_COLOR_COLLISION_DETECTION_EVENTID:
> +		ath12k_wmi_obss_color_collision_event(ab, skb);
> +		break;
>  	/* add Unsupported events (rare) here */
>  	case WMI_TBTTOFFSET_EXT_UPDATE_EVENTID:
>  	case WMI_PEER_OPER_MODE_CHANGE_EVENTID:
> @@ -9887,7 +9959,6 @@ static void ath12k_wmi_op_rx(struct ath12k_base *ab, struct sk_buff *skb)
>  	/* add Unsupported events (frequent) here */
>  	case WMI_PDEV_GET_HALPHY_CAL_STATUS_EVENTID:
>  	case WMI_MGMT_RX_FW_CONSUMED_EVENTID:
> -	case WMI_OBSS_COLOR_COLLISION_DETECTION_EVENTID:
>  		/* debug might flood hence silently ignore (no-op) */
>  		break;
>  	case WMI_PDEV_UTF_EVENTID:
> diff --git a/drivers/net/wireless/ath/ath12k/wmi.h b/drivers/net/wireless/ath/ath12k/wmi.h
> index 64bd968989c8..911ef9d52817 100644
> --- a/drivers/net/wireless/ath/ath12k/wmi.h
> +++ b/drivers/net/wireless/ath/ath12k/wmi.h
> @@ -4928,6 +4928,24 @@ struct wmi_obss_spatial_reuse_params_cmd {
>  #define ATH12K_BSS_COLOR_STA_PERIODS				10000
>  #define ATH12K_BSS_COLOR_AP_PERIODS				5000
>  
> +/**
> + * enum wmi_bss_color_collision - Event types for BSS color collision handling
> + * @WMI_BSS_COLOR_COLLISION_DISABLE: Indicates that BSS color collision detection
> + *                                   is disabled.
> + * @WMI_BSS_COLOR_COLLISION_DETECTION: Event triggered when a BSS color collision
> + *                                     is detected.
> + * @WMI_BSS_COLOR_FREE_SLOT_TIMER_EXPIRY: Event indicating that the timer for waiting
> + *                                        on a free BSS color slot has expired.
> + * @WMI_BSS_COLOR_FREE_SLOT_AVAILABLE: Event indicating that a free BSS color slot
> + *                                     has become available.
> + */
> +enum wmi_bss_color_collision {
> +	WMI_BSS_COLOR_COLLISION_DISABLE = 0,
> +	WMI_BSS_COLOR_COLLISION_DETECTION,
> +	WMI_BSS_COLOR_FREE_SLOT_TIMER_EXPIRY,
> +	WMI_BSS_COLOR_FREE_SLOT_AVAILABLE,
> +};
> +
>  struct wmi_obss_color_collision_cfg_params_cmd {
>  	__le32 tlv_header;
>  	__le32 vdev_id;
> @@ -4945,6 +4963,12 @@ struct wmi_bss_color_change_enable_params_cmd {
>  	__le32 enable;
>  } __packed;
>  
> +struct wmi_obss_color_collision_event {
> +	__le32 vdev_id;
> +	__le32 evt_type;
> +	__le64 obss_color_bitmap;
> +} __packed;
> +
>  #define ATH12K_IPV4_TH_SEED_SIZE 5
>  #define ATH12K_IPV6_TH_SEED_SIZE 11
>  
> 
> base-commit: d5ce93f136fbee2b7afbe221f34ca881036f8de3

Reviewed-by: Baochen Qiang <baochen.qiang at oss.qualcomm.com>




More information about the ath12k mailing list