* ath12k: handling of HE and EHT capabilities
@ 2026-03-12 9:02 Alexander Wilhelm
2026-03-12 9:37 ` Johannes Berg
0 siblings, 1 reply; 7+ messages in thread
From: Alexander Wilhelm @ 2026-03-12 9:02 UTC (permalink / raw)
To: Jeff Johnson; +Cc: ath12k, linux-wireless, linux-kernel
Hello devs,
I’m currently trying to fix the HE and EHT capabilities handling on the
big‑endian platform. Unfortunately, I don’t fully understand how exactly
these capabilities are supposed to be defined.
For example, I use the `iw` tool to display the capabilities and their
descriptions. The code for that has the following function prototypes:
* void print_ht_capability(__u16 cap);
* void print_vht_info(__u32 capa, const __u8 *mcs);
* static void __print_he_capa(const __u16 *mac_cap,
const __u16 *phy_cap,
const __u16 *mcs_set, size_t mcs_len,
const __u8 *ppet, int ppet_len,
bool indent);
* static void __print_eht_capa(int band,
const __u8 *mac_cap,
const __u32 *phy_cap,
const __u8 *mcs_set, size_t mcs_len,
const __u8 *ppet, size_t ppet_len,
const __u16 *he_phy_cap,
bool indent);
For HE capabilites in 6 GHz band I couldn't find the respective function.
Then I looked into `include/net/cfg80211.h` and examined the structures
that define the capability data types.
struct ieee80211_sta_ht_cap {
u16 cap; /* use IEEE80211_HT_CAP_ */
bool ht_supported;
u8 ampdu_factor;
u8 ampdu_density;
struct ieee80211_mcs_info mcs;
};
struct ieee80211_sta_vht_cap {
bool vht_supported;
u32 cap; /* use IEEE80211_VHT_CAP_ */
struct ieee80211_vht_mcs_info vht_mcs;
};
The structs for HT and VHT use `u16` and `u32` data types for the `cap`
variable, matching what `iw` does. That part is consistent.
struct ieee80211_he_cap_elem {
u8 mac_cap_info[6];
u8 phy_cap_info[11];
} __packed;
struct ieee80211_he_6ghz_capa {
/* uses IEEE80211_HE_6GHZ_CAP_* below */
__le16 capa; }
__packed;
However, for HE the types differ from the `iw` implementation. Here, `u8`
arrays are used instead of `u16` for MAC and PHY capabilities. The 6 GHz
capabilities use `u16`, which is also different.
struct ieee80211_eht_cap_elem_fixed {
u8 mac_cap_info[2];
u8 phy_cap_info[9];
} __packed;
For EHT, `u8` arrays are also used for both MAC and PHY caps, instead of
`u32` for the PHY caps as in the `iw` implementation.
The current `ath12k` implementation always uses `u32` values, which does
not work on big‑endian platforms:
ath12k_pci 0001:01:00.0: BAR 0: assigned [mem 0xc00000000-0xc001fffff 64bit]
ath12k_pci 0001:01:00.0: MSI vectors: 1
ath12k_pci 0001:01:00.0: Hardware name: qcn9274 hw2.0
ath12k_pci 0001:01:00.0: qmi dma allocation failed (29360128 B type 1), will try later with small size
ath12k_pci 0001:01:00.0: memory type 10 not supported
ath12k_pci 0001:01:00.0: chip_id 0x0 chip_family 0xb board_id 0x1005 soc_id 0x401a2200
ath12k_pci 0001:01:00.0: fw_version 0x111300d6 fw_build_timestamp 2024-08-06 08:43 fw_build_id QC_IMAGE_VERSION_STRING=WLAN.WBE.1.1.1-00214-QCAHKSWPL_SILICONZ-1
ath12k_pci 0001:01:00.0: leaving PCI ASPM disabled to avoid MHI M2 problems
ath12k_pci 0001:01:00.0: Invalid module id 2
ath12k_pci 0001:01:00.0: failed to parse tlv -22
ath12k_pci 0001:01:00.0: ieee80211 registration failed: -22
ath12k_pci 0001:01:00.0: failed register the radio with mac80211: -22
ath12k_pci 0001:01:00.0: failed to create pdev core: -22
ath12k_pci 0001:01:00.0: qmi failed set mode request, mode: 4, err = -110
ath12k_pci 0001:01:00.0: qmi failed to send wlan mode off
I want to address and fix this issue. However, I cannot apply the “never
break the userspace” rule here, as it seems, it is already broken. Can
someone help clarify which datatypes are supposed to be used? Once that is
clear, I can fix the `ath12k` implementation.
Best regards
Alexander Wilhelm
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: ath12k: handling of HE and EHT capabilities
2026-03-12 9:02 ath12k: handling of HE and EHT capabilities Alexander Wilhelm
@ 2026-03-12 9:37 ` Johannes Berg
2026-03-12 10:53 ` Alexander Wilhelm
0 siblings, 1 reply; 7+ messages in thread
From: Johannes Berg @ 2026-03-12 9:37 UTC (permalink / raw)
To: Alexander Wilhelm, Jeff Johnson; +Cc: ath12k, linux-wireless, linux-kernel
Hi,
> For example, I use the `iw` tool to display the capabilities and their
> descriptions. The code for that has the following function prototypes:
>
> * void print_ht_capability(__u16 cap);
> * void print_vht_info(__u32 capa, const __u8 *mcs);
> * static void __print_he_capa(const __u16 *mac_cap,
> const __u16 *phy_cap,
> const __u16 *mcs_set, size_t mcs_len,
> const __u8 *ppet, int ppet_len,
> bool indent);
> * static void __print_eht_capa(int band,
> const __u8 *mac_cap,
> const __u32 *phy_cap,
> const __u8 *mcs_set, size_t mcs_len,
> const __u8 *ppet, size_t ppet_len,
> const __u16 *he_phy_cap,
> bool indent);
This is perhaps a bit unfortunate, but note that the HE and EHT __u16
and __u32 here are really little endian pointers, and the functions do
byte-order conversion.
> struct ieee80211_sta_ht_cap {
> u16 cap; /* use IEEE80211_HT_CAP_ */
> bool ht_supported;
> u8 ampdu_factor;
> u8 ampdu_density;
> struct ieee80211_mcs_info mcs;
> };
>
> struct ieee80211_sta_vht_cap {
> bool vht_supported;
> u32 cap; /* use IEEE80211_VHT_CAP_ */
> struct ieee80211_vht_mcs_info vht_mcs;
> };
>
> The structs for HT and VHT use `u16` and `u32` data types for the `cap`
> variable, matching what `iw` does. That part is consistent.
Careful. There are different structs used in different places, notably
HT/VHT and HE/EHT differ.
For HT and VHT, look at the start of nl80211_send_band_rateinfo(), which
sends themas individual attributes, defined in enum nl80211_band_attr,
and the values that are u16 (NL80211_BAND_ATTR_HT_CAPA) or u32
(NL80211_BAND_ATTR_VHT_CAPA) are in host byte order, though both are
actually documented misleadingly ("as in [V]HT information IE" is just
all around wrong.)
For HE/EHT, you have it in nl80211_send_iftype_data() since it's per
interface type, and all the individual values are just as they appear in
the spec, regardless of their size.
Note that spec is generally in little endian, but sometimes has strange
field lengths like MAC capabilities being 6 bytes in HE:
> struct ieee80211_he_cap_elem {
> u8 mac_cap_info[6];
> u8 phy_cap_info[11];
> } __packed;
>
> struct ieee80211_he_6ghz_capa {
> /* uses IEEE80211_HE_6GHZ_CAP_* below */
> __le16 capa; }
> __packed;
>
> However, for HE the types differ from the `iw` implementation. Here, `u8`
> arrays are used instead of `u16` for MAC and PHY capabilities. The 6 GHz
> capabilities use `u16`, which is also different.
That doesn't really matter, they're just a set of 6 or 11 bytes, and
e.g. the HE MAC capabilities are treated by the kernel as a set of 6
bytes, but by iw as a set of 3 __le16, which results in the same
interpretation, or at least should.
> struct ieee80211_eht_cap_elem_fixed {
> u8 mac_cap_info[2];
> u8 phy_cap_info[9];
> } __packed;
>
> For EHT, `u8` arrays are also used for both MAC and PHY caps, instead of
> `u32` for the PHY caps as in the `iw` implementation.
Same thing here.
> The current `ath12k` implementation always uses `u32` values, which does
> not work on big‑endian platforms:
Yeah, that seems problematic and not really fitting for something that's
6, 11, 2 or 9 bytes long?
> I want to address and fix this issue. However, I cannot apply the “never
> break the userspace” rule here, as it seems, it is already broken.
I don't think it's broken, why do you say so?
What's (clearly) broken is how ath12k puts the data into the HE/EHT
structs that the kernel expects, but per your dmesg:
> ath12k_pci 0001:01:00.0: ieee80211 registration failed: -22
> ath12k_pci 0001:01:00.0: failed register the radio with mac80211: -22
it seems that even mac80211 doesn't like the capabilities, so the byte
order issue already exists there.
It seems to me the issue is that ath12k_band_cap is in u32, converted,
but then memcpy()d.
johannes
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: ath12k: handling of HE and EHT capabilities
2026-03-12 9:37 ` Johannes Berg
@ 2026-03-12 10:53 ` Alexander Wilhelm
2026-03-12 11:05 ` Johannes Berg
0 siblings, 1 reply; 7+ messages in thread
From: Alexander Wilhelm @ 2026-03-12 10:53 UTC (permalink / raw)
To: Johannes Berg; +Cc: Jeff Johnson, ath12k, linux-wireless, linux-kernel
On Thu, Mar 12, 2026 at 10:37:46AM +0100, Johannes Berg wrote:
> Hi,
> > For example, I use the `iw` tool to display the capabilities and their
> > descriptions. The code for that has the following function prototypes:
> >
> > * void print_ht_capability(__u16 cap);
> > * void print_vht_info(__u32 capa, const __u8 *mcs);
> > * static void __print_he_capa(const __u16 *mac_cap,
> > const __u16 *phy_cap,
> > const __u16 *mcs_set, size_t mcs_len,
> > const __u8 *ppet, int ppet_len,
> > bool indent);
> > * static void __print_eht_capa(int band,
> > const __u8 *mac_cap,
> > const __u32 *phy_cap,
> > const __u8 *mcs_set, size_t mcs_len,
> > const __u8 *ppet, size_t ppet_len,
> > const __u16 *he_phy_cap,
> > bool indent);
>
> This is perhaps a bit unfortunate, but note that the HE and EHT __u16
> and __u32 here are really little endian pointers, and the functions do
> byte-order conversion.
I don’t see this in the function. For example, the MAC capabilities are a
`u16 *` in CPU endianness, which is simply memcpy’d from the parsed
`NL80211_BAND_IFTYPE_ATTR_HE_CAP_MAC`. Later, they are treated as `u16 *`,
as shown in the following code:
printf("%s\t\tHE MAC Capabilities (0x", pre);
for (i = 0; i < 3; i++)
printf("%04x", mac_cap[i]);
printf("):\n");
Here is the result on little‑ vs. big‑endian platforms:
Little endian:
HE MAC Capabilities (0x081a010d030f):
Big endian:
HE MAC Capabilities (0x0b00189a4010):
For the PHY capabilities, they are also a `u16 *`, but they are treated as
a `u8 *`. However, later in the description printing, `PRINT_HE_CAP` does
not take endianness into account. This prints the correct hex values, but
the interpretation/description is wrong:
Little endian:
HE PHY Capabilities: (0x1c604c89ffdb839c110c00):
HE40/HE80/5GHz
HE160/5GHz
HE160/HE80+80/5GHz
LDPC Coding in Payload
HE SU PPDU with 1x HE-LTF and 0.8us GI
STBC Tx <= 80MHz
STBC Rx <= 80MHz
Full Bandwidth UL MU-MIMO
DCM Max Constellation: 1
DCM Max Constellation Rx: 1
SU Beamformer
SU Beamformee
MU Beamformer
Beamformee STS <= 80Mhz: 7
Beamformee STS > 80Mhz: 7
Sounding Dimensions <= 80Mhz: 3
Sounding Dimensions > 80Mhz: 3
Ng = 16 SU Feedback
Ng = 16 MU Feedback
Codebook Size SU Feedback
Codebook Size MU Feedback
PPE Threshold Present
HE SU PPDU & HE PPDU 4x HE-LTF 0.8us GI
Max NC: 3
STBC Rx > 80MHz
HE ER SU PPDU 4x HE-LTF 0.8us GI
HE ER SU PPDU 1x HE-LTF 0.8us GI
TX 1024-QAM
RX 1024-QAM
Big endian:
HE PHY Capabilities: (0x1c604c89ffda839c110c00):
Punctured Preamble RX: 12
HE SU PPDU with 1x HE-LTF and 0.8us GI
Doppler Rx
Full Bandwidth UL MU-MIMO
DCM Max Constellation: 3
DCM Max NSS Tx: 1
DCM Max Constellation Rx: 3
DCM Max NSS Rx: 1
Rx HE MU PPDU from Non-AP STA
SU Beamformer
SU Beamformee
Beamformee STS <= 80Mhz: 2
Beamformee STS > 80Mhz: 4
Sounding Dimensions <= 80Mhz: 3
Ng = 16 MU Feedback
Codebook Size MU Feedback
Triggered MU Beamforming Feedback
Triggered CQI Feedback
Partial Bandwidth DL MU-MIMO
PPE Threshold Present
SRP-based SR
Max NC: 2
20MHz in 160/80+80MHz HE PPDU
80MHz in 160/80+80MHz HE PPDU
HE ER SU PPDU 1x HE-LTF 0.8us GI
DCM Max BW: 2
> > struct ieee80211_sta_ht_cap {
> > u16 cap; /* use IEEE80211_HT_CAP_ */
> > bool ht_supported;
> > u8 ampdu_factor;
> > u8 ampdu_density;
> > struct ieee80211_mcs_info mcs;
> > };
> >
> > struct ieee80211_sta_vht_cap {
> > bool vht_supported;
> > u32 cap; /* use IEEE80211_VHT_CAP_ */
> > struct ieee80211_vht_mcs_info vht_mcs;
> > };
> >
> > The structs for HT and VHT use `u16` and `u32` data types for the `cap`
> > variable, matching what `iw` does. That part is consistent.
>
> Careful. There are different structs used in different places, notably
> HT/VHT and HE/EHT differ.
>
> For HT and VHT, look at the start of nl80211_send_band_rateinfo(), which
> sends themas individual attributes, defined in enum nl80211_band_attr,
> and the values that are u16 (NL80211_BAND_ATTR_HT_CAPA) or u32
> (NL80211_BAND_ATTR_VHT_CAPA) are in host byte order, though both are
> actually documented misleadingly ("as in [V]HT information IE" is just
> all around wrong.)
>
> For HE/EHT, you have it in nl80211_send_iftype_data() since it's per
> interface type, and all the individual values are just as they appear in
> the spec, regardless of their size.
Okay, I think I understand so far. I had also initially wondered why HE/EHT
capabilities were treated separately from the HT/VHT capabilities.
> Note that spec is generally in little endian, but sometimes has strange
> field lengths like MAC capabilities being 6 bytes in HE:
>
> > struct ieee80211_he_cap_elem {
> > u8 mac_cap_info[6];
> > u8 phy_cap_info[11];
> > } __packed;
> >
> > struct ieee80211_he_6ghz_capa {
> > /* uses IEEE80211_HE_6GHZ_CAP_* below */
> > __le16 capa; }
> > __packed;
> >
> > However, for HE the types differ from the `iw` implementation. Here, `u8`
> > arrays are used instead of `u16` for MAC and PHY capabilities. The 6 GHz
> > capabilities use `u16`, which is also different.
>
> That doesn't really matter, they're just a set of 6 or 11 bytes, and
> e.g. the HE MAC capabilities are treated by the kernel as a set of 6
> bytes, but by iw as a set of 3 __le16, which results in the same
> interpretation, or at least should.
Sure. I agree with you, it makes no difference whether I use `u8[6]` or
`__le16[3]`, as long as I use `memcpy` and don’t perform any CPU‑endian
swapping at this point.
> > struct ieee80211_eht_cap_elem_fixed {
> > u8 mac_cap_info[2];
> > u8 phy_cap_info[9];
> > } __packed;
> >
> > For EHT, `u8` arrays are also used for both MAC and PHY caps, instead of
> > `u32` for the PHY caps as in the `iw` implementation.
>
> Same thing here.
>
> > The current `ath12k` implementation always uses `u32` values, which does
> > not work on big‑endian platforms:
>
> Yeah, that seems problematic and not really fitting for something that's
> 6, 11, 2 or 9 bytes long?
>
> > I want to address and fix this issue. However, I cannot apply the “never
> > break the userspace” rule here, as it seems, it is already broken.
>
> I don't think it's broken, why do you say so?
Well, if `ath12k` uses `u32` in CPU‑native order, that’s a bug, and I can’t
get `ieee80211_hw` registered. If I use `__le32` in little-endian order
instead, I end up with incorrect capabilities and mismatched descriptions
shown by the `iw` tool (but I can get the driver running). So neither
approach seems to be a 100% solution at first glance. Did I misinterpret
the rule?
> What's (clearly) broken is how ath12k puts the data into the HE/EHT
> structs that the kernel expects, but per your dmesg:
>
> > ath12k_pci 0001:01:00.0: ieee80211 registration failed: -22
> > ath12k_pci 0001:01:00.0: failed register the radio with mac80211: -22
>
> it seems that even mac80211 doesn't like the capabilities, so the byte
> order issue already exists there.
>
> It seems to me the issue is that ath12k_band_cap is in u32, converted,
> but then memcpy()d.
The `ath12k` driver uses `u32` arrays in CPU‑native order for this, so the
swap is effectively happening. Later, in `ieee80211_register_hw`, the
values are compared at the bit level, and that’s where it fails. I
understand that technically `__le32` could be used in `ath12k`, meaning no
swap, but since `u8` arrays are used in `cfg80211`, that might actually be
the better approach. I just wanted to provide a solution that is clean and
acceptable. The `iw` tool still needs to be updated accordingly.
Best regards
Alexander Wilhelm
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: ath12k: handling of HE and EHT capabilities
2026-03-12 10:53 ` Alexander Wilhelm
@ 2026-03-12 11:05 ` Johannes Berg
2026-03-12 12:10 ` Johannes Berg
0 siblings, 1 reply; 7+ messages in thread
From: Johannes Berg @ 2026-03-12 11:05 UTC (permalink / raw)
To: Alexander Wilhelm; +Cc: Jeff Johnson, ath12k, linux-wireless, linux-kernel
On Thu, 2026-03-12 at 11:53 +0100, Alexander Wilhelm wrote:
> On Thu, Mar 12, 2026 at 10:37:46AM +0100, Johannes Berg wrote:
> > Hi,
> > > For example, I use the `iw` tool to display the capabilities and their
> > > descriptions. The code for that has the following function prototypes:
> > >
> > > * void print_ht_capability(__u16 cap);
> > > * void print_vht_info(__u32 capa, const __u8 *mcs);
> > > * static void __print_he_capa(const __u16 *mac_cap,
> > > const __u16 *phy_cap,
> > > const __u16 *mcs_set, size_t mcs_len,
> > > const __u8 *ppet, int ppet_len,
> > > bool indent);
> > > * static void __print_eht_capa(int band,
> > > const __u8 *mac_cap,
> > > const __u32 *phy_cap,
> > > const __u8 *mcs_set, size_t mcs_len,
> > > const __u8 *ppet, size_t ppet_len,
> > > const __u16 *he_phy_cap,
> > > bool indent);
> >
> > This is perhaps a bit unfortunate, but note that the HE and EHT __u16
> > and __u32 here are really little endian pointers, and the functions do
> > byte-order conversion.
>
> I don’t see this in the function. For example, the MAC capabilities are a
> `u16 *` in CPU endianness, which is simply memcpy’d from the parsed
> `NL80211_BAND_IFTYPE_ATTR_HE_CAP_MAC`. Later, they are treated as `u16 *`,
> as shown in the following code:
>
> printf("%s\t\tHE MAC Capabilities (0x", pre);
> for (i = 0; i < 3; i++)
> printf("%04x", mac_cap[i]);
> printf("):\n");
>
> Here is the result on little‑ vs. big‑endian platforms:
>
> Little endian:
> HE MAC Capabilities (0x081a010d030f):
>
> Big endian:
> HE MAC Capabilities (0x0b00189a4010):
Oh, OK, so _that_ print is definitely wrong in iw. But the individual
prints are converted:
#define PRINT_HE_CAP(_var, _idx, _bit, _str) \
do { \
if (le16toh(_var[_idx]) & BIT(_bit)) \
printf("%s\t\t\t" _str "\n", pre); \
} while (0)
> For the PHY capabilities, they are also a `u16 *`, but they are treated as
> a `u8 *`. However, later in the description printing, `PRINT_HE_CAP` does
> not take endianness into account.
PRINT_HE_CAP *does* convert, there's le16toh() there. Same in
PRINT_HE_CAP_MASK.
It should convert, because it's from a u8[6] kernel API that just
carries the values as they are in the spec.
> > > I want to address and fix this issue. However, I cannot apply the “never
> > > break the userspace” rule here, as it seems, it is already broken.
> >
> > I don't think it's broken, why do you say so?
Well, I see now that I missed the
printf("%s\t\tHE MAC Capabilities (0x", pre);
for (i = 0; i < 3; i++)
printf("%04x", le16toh(mac_cap[i]));
and
printf("%s\t\tEHT MAC Capabilities (0x", pre);
for (i = 0; i < 2; i++)
printf("%02x", mac_cap[i]);
parts, those are definitely broken in iw on big endian platforms. We
should fix those in iw. The actual individual prints seem fine though.
> Well, if `ath12k` uses `u32` in CPU‑native order, that’s a bug, and I can’t
> get `ieee80211_hw` registered. If I use `__le32` in little-endian order
> instead, I end up with incorrect capabilities and mismatched descriptions
> shown by the `iw` tool (but I can get the driver running). So neither
> approach seems to be a 100% solution at first glance. Did I misinterpret
> the rule?
The *descriptions* should be fine I think? Just the first line with the
hex would be messed up.
> > What's (clearly) broken is how ath12k puts the data into the HE/EHT
> > structs that the kernel expects, but per your dmesg:
> >
> > > ath12k_pci 0001:01:00.0: ieee80211 registration failed: -22
> > > ath12k_pci 0001:01:00.0: failed register the radio with mac80211: -22
> >
> > it seems that even mac80211 doesn't like the capabilities, so the byte
> > order issue already exists there.
> >
> > It seems to me the issue is that ath12k_band_cap is in u32, converted,
> > but then memcpy()d.
>
> The `ath12k` driver uses `u32` arrays in CPU‑native order for this, so the
> swap is effectively happening.
Yeah but the swap is wrong, since HE/EHT capabilities are just byte
arrays in spec byte order in cfg80211/nl80211.
> Later, in `ieee80211_register_hw`, the
> values are compared at the bit level, and that’s where it fails. I
> understand that technically `__le32` could be used in `ath12k`, meaning no
> swap, but since `u8` arrays are used in `cfg80211`, that might actually be
> the better approach.
Sure, could use u8 in ath12k too, dunno, up to the maintainer. At least
if it was __le32 you could still memcpy() from it since no swap
happened, and wouldn't change the code structure that much.
johannes
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: ath12k: handling of HE and EHT capabilities
2026-03-12 11:05 ` Johannes Berg
@ 2026-03-12 12:10 ` Johannes Berg
2026-03-12 13:00 ` Alexander Wilhelm
0 siblings, 1 reply; 7+ messages in thread
From: Johannes Berg @ 2026-03-12 12:10 UTC (permalink / raw)
To: Alexander Wilhelm; +Cc: Jeff Johnson, ath12k, linux-wireless, linux-kernel
Wait ...
> > I don’t see this in the function. For example, the MAC capabilities are a
> > `u16 *` in CPU endianness, which is simply memcpy’d from the parsed
> > `NL80211_BAND_IFTYPE_ATTR_HE_CAP_MAC`. Later, they are treated as `u16 *`,
> > as shown in the following code:
> >
> > printf("%s\t\tHE MAC Capabilities (0x", pre);
> > for (i = 0; i < 3; i++)
> > printf("%04x", mac_cap[i]);
> > printf("):\n");
That's incorrect for sure. But iw code now actually reads
printf("%s\t\tHE MAC Capabilities (0x", pre);
for (i = 0; i < 3; i++)
printf("%04x", le16toh(mac_cap[i]));
printf("):\n");
which is correct. HE PHY capabilities are printed as
printf("%s\t\tHE PHY Capabilities: (0x", pre);
for (i = 0; i < 11; i++)
printf("%02x", ((__u8 *)phy_cap)[i + 1]);
in my version of the code, and it seems to me the +1 is incorrect either
way?
> printf("%s\t\tEHT MAC Capabilities (0x", pre);
> for (i = 0; i < 2; i++)
> printf("%02x", mac_cap[i]);
This was also correct, not incorrect as I stated, since mac_cap is u8 *,
and EHT PHY capabilities are cast to u8 * first.
Maybe your iw is just really old?
johannes
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: ath12k: handling of HE and EHT capabilities
2026-03-12 12:10 ` Johannes Berg
@ 2026-03-12 13:00 ` Alexander Wilhelm
2026-03-13 7:45 ` Alexander Wilhelm
0 siblings, 1 reply; 7+ messages in thread
From: Alexander Wilhelm @ 2026-03-12 13:00 UTC (permalink / raw)
To: Johannes Berg; +Cc: Jeff Johnson, ath12k, linux-wireless, linux-kernel
On Thu, Mar 12, 2026 at 01:10:21PM +0100, Johannes Berg wrote:
> Wait ...
>
> > > I don’t see this in the function. For example, the MAC capabilities are a
> > > `u16 *` in CPU endianness, which is simply memcpy’d from the parsed
> > > `NL80211_BAND_IFTYPE_ATTR_HE_CAP_MAC`. Later, they are treated as `u16 *`,
> > > as shown in the following code:
> > >
> > > printf("%s\t\tHE MAC Capabilities (0x", pre);
> > > for (i = 0; i < 3; i++)
> > > printf("%04x", mac_cap[i]);
> > > printf("):\n");
>
> That's incorrect for sure. But iw code now actually reads
>
> printf("%s\t\tHE MAC Capabilities (0x", pre);
> for (i = 0; i < 3; i++)
> printf("%04x", le16toh(mac_cap[i]));
> printf("):\n");
>
>
> which is correct. HE PHY capabilities are printed as
>
> printf("%s\t\tHE PHY Capabilities: (0x", pre);
> for (i = 0; i < 11; i++)
> printf("%02x", ((__u8 *)phy_cap)[i + 1]);
>
> in my version of the code, and it seems to me the +1 is incorrect either
> way?
>
> > printf("%s\t\tEHT MAC Capabilities (0x", pre);
> > for (i = 0; i < 2; i++)
> > printf("%02x", mac_cap[i]);
>
> This was also correct, not incorrect as I stated, since mac_cap is u8 *,
> and EHT PHY capabilities are cast to u8 * first.
>
> Maybe your iw is just really old?
Sorry, my fault. I'm using `OpenWrt v24.10.5` with `iw` version 6.9. The
latest master has the `le16toh` implemented. With my `ath12k` fix the PHY
capabilities and the respecitve descriptions are fine now. But I still
cannot get MAC capabilities correct. I'll analyze it further.
Best regards
Alexander wilhelm
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: ath12k: handling of HE and EHT capabilities
2026-03-12 13:00 ` Alexander Wilhelm
@ 2026-03-13 7:45 ` Alexander Wilhelm
0 siblings, 0 replies; 7+ messages in thread
From: Alexander Wilhelm @ 2026-03-13 7:45 UTC (permalink / raw)
To: Johannes Berg; +Cc: Jeff Johnson, ath12k, linux-wireless, linux-kernel
On Thu, Mar 12, 2026 at 02:00:21PM +0100, Alexander Wilhelm wrote:
> On Thu, Mar 12, 2026 at 01:10:21PM +0100, Johannes Berg wrote:
> > Wait ...
> >
> > > > I don’t see this in the function. For example, the MAC capabilities are a
> > > > `u16 *` in CPU endianness, which is simply memcpy’d from the parsed
> > > > `NL80211_BAND_IFTYPE_ATTR_HE_CAP_MAC`. Later, they are treated as `u16 *`,
> > > > as shown in the following code:
> > > >
> > > > printf("%s\t\tHE MAC Capabilities (0x", pre);
> > > > for (i = 0; i < 3; i++)
> > > > printf("%04x", mac_cap[i]);
> > > > printf("):\n");
> >
> > That's incorrect for sure. But iw code now actually reads
> >
> > printf("%s\t\tHE MAC Capabilities (0x", pre);
> > for (i = 0; i < 3; i++)
> > printf("%04x", le16toh(mac_cap[i]));
> > printf("):\n");
> >
> >
> > which is correct. HE PHY capabilities are printed as
> >
> > printf("%s\t\tHE PHY Capabilities: (0x", pre);
> > for (i = 0; i < 11; i++)
> > printf("%02x", ((__u8 *)phy_cap)[i + 1]);
> >
> > in my version of the code, and it seems to me the +1 is incorrect either
> > way?
> >
> > > printf("%s\t\tEHT MAC Capabilities (0x", pre);
> > > for (i = 0; i < 2; i++)
> > > printf("%02x", mac_cap[i]);
> >
> > This was also correct, not incorrect as I stated, since mac_cap is u8 *,
> > and EHT PHY capabilities are cast to u8 * first.
> >
> > Maybe your iw is just really old?
>
> Sorry, my fault. I'm using `OpenWrt v24.10.5` with `iw` version 6.9. The
> latest master has the `le16toh` implemented. With my `ath12k` fix the PHY
> capabilities and the respecitve descriptions are fine now. But I still
> cannot get MAC capabilities correct. I'll analyze it further.
Hi Johannes,
I finally have `ath12k` running with the correct capabilities. The latest
`iw` version also performs the byte swaps correctly, except for the HE MAC
capabilities output. There, each 2‑byte pair is swapped between big‑endian
and little‑endian platforms. I’m sending a patch to make this consistent
across all architectures. Thank you for the support.
Best regards
Alexander Wilhelm
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-03-13 7:45 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-12 9:02 ath12k: handling of HE and EHT capabilities Alexander Wilhelm
2026-03-12 9:37 ` Johannes Berg
2026-03-12 10:53 ` Alexander Wilhelm
2026-03-12 11:05 ` Johannes Berg
2026-03-12 12:10 ` Johannes Berg
2026-03-12 13:00 ` Alexander Wilhelm
2026-03-13 7:45 ` Alexander Wilhelm
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox