[PATCH v2 ath-next] wifi: ath12k: Add EHT capabilities to scan probe frame

Gaole Zhang gaole.zhang at oss.qualcomm.com
Fri Oct 9 05:47:39 PDT 2026


On 10/5/2026 2:50 AM, Jeff Johnson wrote:
> On 10/1/2026 1:39 AM, Gaole Zhang wrote:
>> ath12k hardware scan Probe Request frames do not include the EHT
>> Capabilities element. Without this element, an AP cannot identify the
>> STA as EHT-capable during active scanning and may omit EHT-related
>> information from its Probe Response. This can result in incomplete scan
>> information and prevent userspace from correctly determining the AP's
>> EHT capabilities.
>>
>> For firmware to include the EHT Capabilities element in scan Probe
>> Request frames, two conditions are required:
>>
>>    - The scan channel must have WMI_CHAN_INFO_ALLOW_EHT set, which allows
>>      firmware to add the EHT Capabilities element to Probe Request frames.
>>    - The EHT Capabilities element must be configured on the vdev through
>>      WMI_VDEV_SET_IE_CMDID, so firmware can use it when building Probe
>>      Request frames.
>>
>> Set WMI_CHAN_INFO_ALLOW_EHT for channels when 11be is supported by
>> firmware and not disabled by ACPI. Also configure the EHT Capabilities
>> element before each hardware scan when the element is present in the
>> mac80211 scan IE buffer.
>>
>> Firmware retains the configured element in the vdev context until it is
>> replaced or the vdev is deleted. However, ath12k scans may either reuse
>> an already started vdev or create a temporary scan vdev that is cleaned
>> up after scan completion. Sending WMI_VDEV_SET_IE_CMDID for each scan
>> request ensures correct behavior in both cases.
>>
>> Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c7-00108-QCAHMTSWPL_V1.0_V2.0_SILICONZ_UPSTREAM-3
>>
>> Signed-off-by: Gaole Zhang <gaole.zhang at oss.qualcomm.com>
>> ---
>> v2:
>>   - Clarify the firmware requirements for adding the EHT Capabilities
>>     element to scan Probe Request frames.
>>   - Explain why the EHT Capabilities element is configured before each
>>     hardware scan, covering both reused vdevs and temporary scan vdevs.
>>   - No code changes.
>>
>>   drivers/net/wireless/ath/ath12k/core.h |  6 ++++
>>   drivers/net/wireless/ath/ath12k/mac.c  | 29 +++++++++++++--
>>   drivers/net/wireless/ath/ath12k/reg.c  |  4 +++
>>   drivers/net/wireless/ath/ath12k/wmi.c  | 50 ++++++++++++++++++++++++++
>>   drivers/net/wireless/ath/ath12k/wmi.h  | 23 +++++++++++-
>>   5 files changed, 109 insertions(+), 3 deletions(-)
>>
>> diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h
>> index a98fc6e0699d..e7373a523dc3 100644
>> --- a/drivers/net/wireless/ath/ath12k/core.h
>> +++ b/drivers/net/wireless/ath/ath12k/core.h
>> @@ -1394,6 +1394,12 @@ static inline struct ath12k *ath12k_ah_to_ar(struct ath12k_hw *ah, u8 radio_idx)
>>   	return &ah->radio[radio_idx];
>>   }
>>   
>> +static inline bool ath12k_is_11be_enabled(struct ath12k_base *ab)
>> +{
>> +	return test_bit(WMI_TLV_SERVICE_11BE, ab->wmi_ab.svc_map) &&
>> +	       !ath12k_acpi_get_disable_11be(ab);
>> +}
>> +
>>   static inline struct ath12k_hw *ath12k_ar_to_ah(struct ath12k *ar)
>>   {
>>   	return ar->ah;
>> diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c
>> index d4116ba0da08..f353af63c5eb 100644
>> --- a/drivers/net/wireless/ath/ath12k/mac.c
>> +++ b/drivers/net/wireless/ath/ath12k/mac.c
>> @@ -5602,6 +5602,25 @@ ath12k_mac_find_link_id_by_ar(struct ath12k_vif *ahvif, struct ath12k *ar)
>>   	return ATH12K_FIRST_SCAN_LINK;
>>   }
>>   
>> +static int ath12k_mac_set_scan_eht_cap_ie(struct ath12k *ar,
>> +					  struct ath12k_link_vif *arvif,
>> +					  const u8 *ies, size_t ies_len)
>> +{
>> +	const struct element *eht_cap;
>> +
>> +	lockdep_assert_wiphy(ath12k_ar_to_hw(ar)->wiphy);
>> +
>> +	eht_cap = cfg80211_find_ext_elem(WLAN_EID_EXT_EHT_CAPABILITY, ies,
>> +					 ies_len);
>> +	if (!eht_cap || eht_cap->datalen <= 1)
>> +		return 0;
>> +
>> +	return ath12k_wmi_vdev_set_ie(ar, arvif->vdev_id,
>> +				      WLAN_EID_EXTENSION,
>> +				      eht_cap->data, eht_cap->datalen,
>> +				      WMI_SET_VDEV_IE_BAND_ALL);
>> +}
>> +
>>   static int ath12k_mac_initiate_hw_scan(struct ieee80211_hw *hw,
>>   				       struct ieee80211_vif *vif,
>>   				       struct ieee80211_scan_request *hw_req,
>> @@ -5718,6 +5737,13 @@ static int ath12k_mac_initiate_hw_scan(struct ieee80211_hw *hw,
>>   			goto exit;
>>   		}
>>   		arg->extraie.len = req->ie_len;
>> +		ret = ath12k_mac_set_scan_eht_cap_ie(ar, arvif, req->ie,
>> +						     req->ie_len);
>> +		if (ret) {
>> +			ath12k_dbg(ar->ab, ATH12K_DBG_MAC,
>> +				   "failed to set eht cap ie, ret %d\n", ret);
>> +			goto exit;
> do we really want to bypass scanning in this case?
> would it be better to still scan, just without added capability?
done on v3 patch
>
>> +		}
>>   	}
>>   
>>   	if (req->n_ssids) {
>> @@ -8983,8 +9009,7 @@ static void ath12k_mac_copy_eht_cap(struct ath12k *ar,
>>   
>>   	memset(eht_cap, 0, sizeof(struct ieee80211_sta_eht_cap));
>>   
>> -	if (!(test_bit(WMI_TLV_SERVICE_11BE, ar->ab->wmi_ab.svc_map)) ||
>> -	    ath12k_acpi_get_disable_11be(ar->ab))
>> +	if (!ath12k_is_11be_enabled(ar->ab))
>>   		return;
>>   
>>   	eht_cap->has_eht = true;
>> diff --git a/drivers/net/wireless/ath/ath12k/reg.c b/drivers/net/wireless/ath/ath12k/reg.c
>> index 89abf2e87ad1..0aba1e74a3e9 100644
>> --- a/drivers/net/wireless/ath/ath12k/reg.c
>> +++ b/drivers/net/wireless/ath/ath12k/reg.c
>> @@ -140,6 +140,7 @@ int ath12k_reg_update_chan_list(struct ath12k *ar, bool wait)
>>   	enum nl80211_band band;
>>   	int num_channels = 0;
>>   	int i, ret = 0;
>> +	bool has_eht;
>>   
>>   	if (ar->ah->state == ATH12K_HW_STATE_RESTARTING)
>>   		return 0;
>> @@ -180,6 +181,8 @@ int ath12k_reg_update_chan_list(struct ath12k *ar, bool wait)
>>   
>>   	ch = arg->channel;
>>   
>> +	has_eht = ath12k_is_11be_enabled(ar->ab);
>> +
>>   	for (band = 0; band < NUM_NL80211_BANDS; band++) {
>>   		if (!(ar->mac.sbands[band].channels && bands[band]))
>>   			continue;
>> @@ -201,6 +204,7 @@ int ath12k_reg_update_chan_list(struct ath12k *ar, bool wait)
>>   			ch->allow_ht = true;
>>   			ch->allow_vht = true;
>>   			ch->allow_he = true;
>> +			ch->allow_eht = has_eht;
>>   
>>   			ch->dfs_set =
>>   				!!(channel->flags & IEEE80211_CHAN_RADAR);
>> diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c
>> index a63bbda0219c..5fe7af731a4e 100644
>> --- a/drivers/net/wireless/ath/ath12k/wmi.c
>> +++ b/drivers/net/wireless/ath/ath12k/wmi.c
>> @@ -2005,6 +2005,54 @@ int ath12k_wmi_p2p_go_bcn_ie(struct ath12k *ar, u32 vdev_id,
>>   	return ret;
>>   }
>>   
>> +int ath12k_wmi_vdev_set_ie(struct ath12k *ar, u32 vdev_id, u32 ie_id,
>> +			   const u8 *ie, size_t ie_len, u32 band)
>> +{
>> +	struct ath12k_wmi_pdev *wmi = ar->wmi;
>> +	struct wmi_vdev_set_ie_cmd *cmd;
>> +	struct sk_buff *skb;
>> +	struct wmi_tlv *tlv;
>> +	size_t aligned_len;
>> +	int ret, len;
>> +	void *ptr;
>> +
>> +	aligned_len = roundup(ie_len, sizeof(u32));
>> +	len = sizeof(*cmd) + TLV_HDR_SIZE + aligned_len;
>> +
>> +	skb = ath12k_wmi_alloc_skb(wmi->wmi_ab, len);
>> +	if (!skb)
>> +		return -ENOMEM;
>> +
>> +	ptr = skb->data;
>> +	cmd = ptr;
>> +	cmd->tlv_header = ath12k_wmi_tlv_cmd_hdr(WMI_TAG_VDEV_SET_IE_CMD,
>> +						 sizeof(*cmd));
>> +	cmd->vdev_id = cpu_to_le32(vdev_id);
>> +	cmd->ie_id = cpu_to_le32(ie_id);
>> +	cmd->ie_len = cpu_to_le32(ie_len);
>> +	cmd->ie_source = cpu_to_le32(WMI_SET_VDEV_IE_SOURCE_HOST);
>> +	cmd->band = cpu_to_le32(band);
>> +
>> +	ath12k_dbg(ar->ab, ATH12K_DBG_WMI,
>> +		   "WMI set ie vdev_id %u ie_id %u ie_len %zu band %u\n",
>> +		   vdev_id, ie_id, ie_len, band);
>> +
>> +	ptr += sizeof(*cmd);
>> +	tlv = ptr;
>> +	tlv->header = ath12k_wmi_tlv_hdr(WMI_TAG_ARRAY_BYTE, aligned_len);
>> +	memcpy(tlv->value, ie, ie_len);
>> +
>> +	ret = ath12k_wmi_cmd_send(wmi, skb, WMI_VDEV_SET_IE_CMDID);
>> +	if (ret) {
>> +		ath12k_warn(ar->ab,
>> +			    "failed to send WMI_VDEV_SET_IE_CMDID for vdev %u ie %u: %d\n",
>> +			    vdev_id, ie_id, ret);
>> +		dev_kfree_skb(skb);
>> +	}
>> +
>> +	return ret;
>> +}
>> +
>>   int ath12k_wmi_bcn_tmpl(struct ath12k_link_vif *arvif,
>>   			struct ieee80211_mutable_offsets *offs,
>>   			struct sk_buff *bcn,
>> @@ -2946,6 +2994,8 @@ int ath12k_wmi_send_scan_chan_list_cmd(struct ath12k *ar,
>>   				chan_info->info |= cpu_to_le32(WMI_CHAN_INFO_ALLOW_VHT);
>>   			else if (channel_arg->allow_ht)
>>   				chan_info->info |= cpu_to_le32(WMI_CHAN_INFO_ALLOW_HT);
>> +			if (channel_arg->allow_eht)
>> +				chan_info->info |= cpu_to_le32(WMI_CHAN_INFO_ALLOW_EHT);
>>   			if (channel_arg->half_rate)
>>   				chan_info->info |= cpu_to_le32(WMI_CHAN_INFO_HALF_RATE);
>>   			if (channel_arg->quarter_rate)
>> diff --git a/drivers/net/wireless/ath/ath12k/wmi.h b/drivers/net/wireless/ath/ath12k/wmi.h
>> index b508aa759bd8..e51b3bdab77b 100644
>> --- a/drivers/net/wireless/ath/ath12k/wmi.h
>> +++ b/drivers/net/wireless/ath/ath12k/wmi.h
>> @@ -3075,7 +3075,8 @@ struct ath12k_wmi_channel_arg {
>>   	    allow_vht:1,
>>   	    allow_he:1,
>>   	    set_agile:1,
>> -	    psc_channel:1;
>> +	    psc_channel:1,
>> +	    allow_eht:1;
> add after allow_he to keep in a logical order since this isn't ABI?
done on v3 patch
>
>>   	u32 phy_mode;
>>   	u32 cfreq1;
>>   	u32 cfreq2;
>> @@ -3694,6 +3695,7 @@ struct ath12k_wmi_scan_cancel_arg {
>>   #define WMI_CHAN_INFO_DFS_FREQ2		BIT(16)
>>   #define WMI_CHAN_INFO_ALLOW_HE		BIT(17)
>>   #define WMI_CHAN_INFO_PSC		BIT(18)
>> +#define WMI_CHAN_INFO_ALLOW_EHT		BIT(21)
>>   
>>   #define WMI_CHAN_REG_INFO1_MIN_PWR	GENMASK(7, 0)
>>   #define WMI_CHAN_REG_INFO1_MAX_PWR	GENMASK(15, 8)
>> @@ -3845,6 +3847,23 @@ struct wmi_p2p_go_set_beacon_ie_cmd {
>>   	__le32 ie_buf_len;
>>   } __packed;
>>   
>> +#define WMI_SET_VDEV_IE_SOURCE_HOST	0
>> +
>> +enum wmi_set_vdev_ie_band {
>> +	WMI_SET_VDEV_IE_BAND_ALL,
>> +	WMI_SET_VDEV_IE_BAND_2_4GHZ,
>> +	WMI_SET_VDEV_IE_BAND_5GHZ,
>> +};
>> +
>> +struct wmi_vdev_set_ie_cmd {
>> +	__le32 tlv_header;
>> +	__le32 vdev_id;
>> +	__le32 ie_id;
>> +	__le32 ie_len;
>> +	__le32 ie_source;
>> +	__le32 band;
>> +} __packed;
>> +
>>   struct wmi_vdev_install_key_cmd {
>>   	__le32 tlv_header;
>>   	__le32 vdev_id;
>> @@ -6583,6 +6602,8 @@ int ath12k_wmi_pdev_resume(struct ath12k *ar, u32 pdev_id);
>>   
>>   int ath12k_wmi_send_peer_assoc_cmd(struct ath12k *ar,
>>   				   struct ath12k_wmi_peer_assoc_arg *arg);
>> +int ath12k_wmi_vdev_set_ie(struct ath12k *ar, u32 vdev_id, u32 ie_id,
>> +			   const u8 *ie, size_t ie_len, u32 band);
>>   int ath12k_wmi_vdev_install_key(struct ath12k *ar,
>>   				struct wmi_vdev_install_key_arg *arg);
>>   int ath12k_wmi_pdev_bss_chan_info_request(struct ath12k *ar,
>>
>> base-commit: c3bace8584ca707e34ba837f65c2e9d566af4c2c



More information about the ath12k mailing list