[PATCH ath-next] wifi: ath12k: provide MAC address list for single-radio wiphys
Baochen Qiang
baochen.qiang at oss.qualcomm.com
Tue Sep 8 00:18:20 PDT 2026
On 9/2/2026 4:22 PM, Vitor Soares wrote:
> From: Vitor Soares <vitor.soares at toradex.com>
>
> ath12k supports creating vdevs with addresses provided by mac80211.
> Userspace can already make concurrent interfaces work by explicitly
> assigning different locally administered addresses.
>
> However, ath12k does not advertise an address list, so when a second
> interface is created without an explicit address, mac80211 has nothing
> to pick from and falls back to the permanent address. If the first
> interface is already running with that address, bringing up the second
> one fails with -ENOTUNIQ.
>
> As in ath11k, provide a list of usable addresses so
> ieee80211_assign_perm_addr() can hand each new interface an unused one.
> Keep the first entry as the radio's own MAC address. Generate the
> remaining entries as locally administered variants by changing only the
> upper nibble of the first octet, which naturally limits the pool to 16
> unique addresses.
>
We (Qualcomm) ever internally proposed a similar solution, but it turns out to break chips
like QCN9274 etc. I have forwarded this patch to internal developers, will come back once
get a conclusion.
> Handle only single-radio wiphys. Wiphys with more than one radio behave
> exactly as before.
>
> Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c7-00108-QCAHMTSWPL_V1.0_V2.0_SILICONZ_UPSTREAM-3
>
> Signed-off-by: Vitor Soares <vitor.soares at toradex.com>
> ---
> Note that a device which is not MLO capable registers one wiphy per
> radio, so each radio of a multi-radio device also gets a pool, seeded
> from that radio's own MAC. I only have single-radio WCN7850 hardware,
> so that case is untested.
> ---
> drivers/net/wireless/ath/ath12k/mac.c | 48 +++++++++++++++++++++++++++
> 1 file changed, 48 insertions(+)
>
> diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c
> index 9a775602775d..bee6701f1029 100644
> --- a/drivers/net/wireless/ath/ath12k/mac.c
> +++ b/drivers/net/wireless/ath/ath12k/mac.c
> @@ -14685,6 +14685,50 @@ static void ath12k_mac_cleanup_unregister(struct ath12k *ar)
> kfree(ar->mac.sbands[NL80211_BAND_6GHZ].channels);
> }
>
> +static void ath12k_mac_cleanup_mac_address_list(struct ath12k_hw *ah)
> +{
> + kfree(ah->hw->wiphy->addresses);
> +}
> +
> +static void ath12k_mac_setup_mac_address_list(struct ath12k_hw *ah,
> + const u8 *mac_addr)
> +{
> + struct ath12k *ar = ath12k_ah_to_ar(ah, 0);
> + struct mac_address *addresses;
> + u16 n_addresses;
> + int i;
> +
> + /* The pool is derived from one base address, so it can describe only a
> + * single-radio wiphy: with more radios each radio has its own base
> + * address, possibly from a different ath12k_base.
> + */
> + if (ah->num_radio != 1)
> + return;
> +
> + /* Only the upper nibble of the first octet is varied, so at most 16
> + * addresses can be derived from one base.
> + */
> + n_addresses = min_t(u16, TARGET_NUM_VDEVS(ar->ab), 16);
> + if (n_addresses <= 1)
> + return;
> +
> + addresses = kzalloc_objs(*addresses, n_addresses);
> + if (!addresses)
> + return;
> +
> + ether_addr_copy(addresses[0].addr, mac_addr);
> + for (i = 1; i < n_addresses; i++) {
> + ether_addr_copy(addresses[i].addr, mac_addr);
> + /* set Local Administered Address bit */
> + addresses[i].addr[0] |= 0x2;
> +
> + addresses[i].addr[0] += i << 4;
> + }
> +
> + ah->hw->wiphy->addresses = addresses;
> + ah->hw->wiphy->n_addresses = n_addresses;
> +}
> +
> static void ath12k_mac_hw_unregister(struct ath12k_hw *ah)
> {
> struct ieee80211_hw *hw = ah->hw;
> @@ -14704,6 +14748,7 @@ static void ath12k_mac_hw_unregister(struct ath12k_hw *ah)
> ath12k_mac_cleanup_unregister(ar);
>
> ath12k_mac_cleanup_iface_combinations(ah);
> + ath12k_mac_cleanup_mac_address_list(ah);
>
> SET_IEEE80211_DEV(hw, NULL);
> }
> @@ -14820,6 +14865,7 @@ static int ath12k_mac_hw_register(struct ath12k_hw *ah)
> wiphy->available_antennas_tx = antennas_tx;
>
> SET_IEEE80211_PERM_ADDR(hw, mac_addr);
> + ath12k_mac_setup_mac_address_list(ah, mac_addr);
> SET_IEEE80211_DEV(hw, ab->dev);
>
> ret = ath12k_mac_setup_iface_combinations(ah);
> @@ -15041,6 +15087,8 @@ static int ath12k_mac_hw_register(struct ath12k_hw *ah)
> ath12k_mac_cleanup_unregister(ar);
> }
>
> + ath12k_mac_cleanup_mac_address_list(ah);
> +
> SET_IEEE80211_DEV(hw, NULL);
>
> return ret;
More information about the ath12k
mailing list