diff mbox series

[v3,2/2] wifi: mac80211: refactor STA CSA parsing flows

Message ID 20231129054321.10199-2-michael-cy.lee@mediatek.com (mailing list archive)
State Changes Requested
Delegated to: Johannes Berg
Headers show
Series [v3,1/2] wifi: mac80211: add utility for converting op_class into chandef | expand

Commit Message

Michael-CY Lee Nov. 29, 2023, 5:43 a.m. UTC
The new Wi-Fi Standard (IEEE P80211be D4.1) specifies that the Wide
Bandwidth Channel Switch (WBCS) Element subfields have the same
definitions as VHT operation information if the operating band is not
S1G.

The problem comes when the BSS is in 6 GHz band, the STA parses the WBCS
Element by ieee80211_chandef_vht_oper(), which checks the capabilities for
HT/VHT mode, not HE/EHT mode.

This patch refactors STA CSA parsing flow so that the corresponding
capabilities can be checked. Also, it adds the way to use op_class in ECSA
Element to build a new chandef.

In summary, the new steps for STA to handle CSA event are:
1. build the new chandef from the CSA-related Elements.
   (CSA, ECSA, WBCS, etc.)
2. convert the new chandef into operation information according to the
   operating band in order to check if the new chandef fits STA's
   capabilities.
3. downgrade the bandwidth until current bandwidth is not disabled.

Signed-off-by: Michael-CY Lee <michael-cy.lee@mediatek.com>
Signed-off-by: Money Wang <money.wang@mediatek.com>
---
v3:
  Version 2 follows draft 3.2, while version 3 follows draft 4.1.
  Also, version 3 simplifies the CSA handling steps.
---
 net/mac80211/spectmgmt.c | 349 +++++++++++++++++++++++++++++++++------
 1 file changed, 294 insertions(+), 55 deletions(-)

Comments

Johannes Berg Dec. 1, 2023, 7:06 p.m. UTC | #1
Hi,

So looking at this ... I'm not sure I want the operating class parsing?

On Wed, 2023-11-29 at 13:43 +0800, Michael-CY Lee wrote:
> The new Wi-Fi Standard (IEEE P80211be D4.1) specifies that the Wide
> Bandwidth Channel Switch (WBCS) Element subfields have the same
> definitions as VHT operation information if the operating band is not
> S1G.

Actually that's already in REVme, no?

"If the New Operating Class field in the frame [...] does not indicate
an S1G band, the subfields New Channel Width, New Channel Center
Frequency Segment 0 and New Channel Center Frequency Segment 1 have the
same definition, respectively, as Channel Width, Channel Center
Frequency Segment 0, Channel Center Freqauency Segment 1 in the VHT
Operation Information field, described in Table 9-313 (VHT Operation
Information subfields)."

> The problem comes when the BSS is in 6 GHz band, the STA parses the WBCS
> Element by ieee80211_chandef_vht_oper(), which checks the capabilities for
> HT/VHT mode, not HE/EHT mode.

OK, but that's an implementation issue, we can make it look at HE
capabilities too, for 6 GHz?

> This patch refactors STA CSA parsing flow so that the corresponding
> capabilities can be checked.

That seems fine.


> Also, it adds the way to use op_class in ECSA
> Element to build a new chandef.

Not sure why that?

> In summary, the new steps for STA to handle CSA event are:
> 1. build the new chandef from the CSA-related Elements.
>    (CSA, ECSA, WBCS, etc.)

Actually that's not what you do? The logic is more like

 - if BWI present: use only that
 - if operating class/channel can be used: use only that
 - if WBCS is present: use only that
 - otherwise: use (ext) chanswitch element info from before


Given that I just got a report of an MTK AP that has a broken WCBS
element, I'm not sure I'm happy with that logic ;-)

Seems to me the operating class use should maybe be further down the
list, and perhaps (if it's there) validate the other elements against
it, to make sure the AP isn't confused?

So perhaps better:
 - use, in this order: BWI, WBCS, ECSA, CSA (according to the mode
   we parse as, and our own capabilities)
 - if present, check that operating class agrees

no?

> Signed-off-by: Michael-CY Lee <michael-cy.lee@mediatek.com>
> Signed-off-by: Money Wang <money.wang@mediatek.com>

Seems that might be in the wrong order and/or should have Co-developed-
by?

> +static inline void
> +wbcs_ie_to_chandef(const struct ieee80211_wide_bw_chansw_ie *wbcs_ie,
> +		   struct cfg80211_chan_def *chandef)

I'd prefer if we generally switched to "element" instead of "IE" since
the spec did that. Yeah we didn't go back and rename all existing code
(unlike the spec), but still?

Please also drop the 'inline', compiler will do it if it makes sense.

> +{
> +	u8 ccfs0 = wbcs_ie->new_center_freq_seg0;
> +	u8 ccfs1 = wbcs_ie->new_center_freq_seg1;
> +	u32 cf0 = ieee80211_channel_to_frequency(ccfs0, chandef->chan->band);
> +	u32 cf1 = ieee80211_channel_to_frequency(ccfs1, chandef->chan->band);
> +
> +	switch (wbcs_ie->new_channel_width) {
> +	case IEEE80211_VHT_CHANWIDTH_160MHZ:
> +		chandef->width = NL80211_CHAN_WIDTH_160;
> +		chandef->center_freq1 = cf0;

maybe add a note that this encoding is deprecated?

> +		break;
> +	case IEEE80211_VHT_CHANWIDTH_80P80MHZ:
> +		chandef->width = NL80211_CHAN_WIDTH_80P80;
> +		chandef->center_freq1 = cf0;
> +		chandef->center_freq2 = cf1;

and not sure I remember well, but this one too? at least going by this:

> +		break;
> +	case IEEE80211_VHT_CHANWIDTH_80MHZ:
> +		chandef->width = NL80211_CHAN_WIDTH_80;
> +		chandef->center_freq1 = cf0;
> +
> +		if (ccfs1) {
> +			u8 diff = abs(ccfs0 - ccfs1);
> +
> +			if (diff == 8) {
> +				chandef->width = NL80211_CHAN_WIDTH_160;
> +				chandef->center_freq1 = cf1;
> +			} else if (diff > 8) {
> +				chandef->width = NL80211_CHAN_WIDTH_80P80;
> +				chandef->center_freq2 = cf1;
> +			}
> +		}
> +		break;


> +static inline int
> +validate_chandef_by_ht_vht_oper(struct ieee80211_sub_if_data *sdata,

same here about 'inline'

> +				ieee80211_conn_flags_t conn_flags,
> +				u32 vht_cap_info,
> +				struct cfg80211_chan_def *chandef)
> +{
> +	u32 control_freq, center_freq1, center_freq2;
> +	enum nl80211_chan_width chan_width;
> +	struct ieee80211_ht_operation *ht_oper = NULL;
> +	struct ieee80211_vht_operation *vht_oper = NULL;

No point initializing those to NULL?

> +	if (conn_flags & (IEEE80211_CONN_DISABLE_HT |
> +			  IEEE80211_CONN_DISABLE_40MHZ)) {
> +		chandef->chan = NULL;
> +		return 0;
> +	}
> +
> +	control_freq = chandef->chan->center_freq;
> +	center_freq1 = chandef->center_freq1;
> +	center_freq2 = chandef->center_freq2;
> +	chan_width = chandef->width;
> +
> +	ht_oper = kzalloc(sizeof(*ht_oper), GFP_KERNEL);
> +	if (!ht_oper)
> +		return -ENOMEM;

Not sure I see value in putting this on the heap, it's tiny?

> +	vht_oper = kzalloc(sizeof(*vht_oper), GFP_KERNEL);

same here

> +	if (!vht_oper) {
> +		kfree(ht_oper);
> +		return -ENOMEM;

and if you have these gone, you no longer need a return value either,
which is nice.

> +static inline int

same here

> +validate_chandef_by_6ghz_he_eht_oper(struct ieee80211_sub_if_data *sdata,
> +				     ieee80211_conn_flags_t conn_flags,
> +				     struct cfg80211_chan_def *chandef)
> +{
> +	u32 size, control_freq, center_freq1, center_freq2;
> +	enum nl80211_chan_width chan_width;
> +	struct ieee80211_he_operation *he_oper = NULL;
> +	struct ieee80211_eht_operation *eht_oper = NULL;

same here about =NULL, and for the allocations too

> +	case NL80211_CHAN_WIDTH_80P80:
> +		he_6ghz_oper->control =
> +			IEEE80211_HE_6GHZ_OPER_CTRL_CHANWIDTH_160MHZ;

Is that right? Do HE/EHT even still do 80+80?

>  int ieee80211_parse_ch_switch_ie(struct ieee80211_sub_if_data *sdata,
>  				 struct ieee802_11_elems *elems,
>  				 enum nl80211_band current_band,
> @@ -27,13 +257,14 @@ int ieee80211_parse_ch_switch_ie(struct ieee80211_sub_if_data *sdata,
>  				 struct ieee80211_csa_ie *csa_ie)
>  {
>  	enum nl80211_band new_band = current_band;
> -	int new_freq;
> -	u8 new_chan_no;
> +	int new_freq, ret;
> +	u8 new_chan_no = 0, new_op_class = 0;
>  	struct ieee80211_channel *new_chan;
> -	struct cfg80211_chan_def new_vht_chandef = {};
> +	struct cfg80211_chan_def new_chandef = {};
>  	const struct ieee80211_sec_chan_offs_ie *sec_chan_offs;
>  	const struct ieee80211_wide_bw_chansw_ie *wide_bw_chansw_ie;
>  	const struct ieee80211_bandwidth_indication *bwi;
> +	const struct ieee80211_ext_chansw_ie *ext_chansw_ie;
>  	int secondary_channel_offset = -1;
>  
>  	memset(csa_ie, 0, sizeof(*csa_ie));
> @@ -41,6 +272,7 @@ int ieee80211_parse_ch_switch_ie(struct ieee80211_sub_if_data *sdata,
>  	sec_chan_offs = elems->sec_chan_offs;
>  	wide_bw_chansw_ie = elems->wide_bw_chansw_ie;
>  	bwi = elems->bandwidth_indication;
> +	ext_chansw_ie = elems->ext_chansw_ie;
>  
>  	if (conn_flags & (IEEE80211_CONN_DISABLE_HT |
>  			  IEEE80211_CONN_DISABLE_40MHZ)) {
> @@ -51,26 +283,30 @@ int ieee80211_parse_ch_switch_ie(struct ieee80211_sub_if_data *sdata,
>  	if (conn_flags & IEEE80211_CONN_DISABLE_VHT)
>  		wide_bw_chansw_ie = NULL;
>  
> -	if (elems->ext_chansw_ie) {
> -		if (!ieee80211_operating_class_to_band(
> -				elems->ext_chansw_ie->new_operating_class,
> -				&new_band)) {
> -			sdata_info(sdata,
> -				   "cannot understand ECSA IE operating class, %d, ignoring\n",
> -				   elems->ext_chansw_ie->new_operating_class);
> +	if (ext_chansw_ie) {
> +		new_op_class = ext_chansw_ie->new_operating_class;
> +		if (!ieee80211_operating_class_to_band(new_op_class, &new_band)) {
> +			new_op_class = 0;
> +			sdata_info(sdata, "cannot understand ECSA IE "
> +					  "operating class, %d, ignoring\n",

please don't break strings like that, the previous way this was done was
just fine

> +	/* parse one of the Elements to build a new chandef */

except you don't really, as discussed above

I'd actually kind of like to have these validated against each other,
but that's for another day, and we don't build the strictest
implementation.

Though I probably will make the implementation optionally stricter in
some places like this, and enable that for all testing/certification in
the future.

> +	} else if (!ieee80211_operating_class_to_chandef(new_op_class, new_chan,
> +							 &new_chandef)) {
> +		if (wide_bw_chansw_ie)
> +			wbcs_ie_to_chandef(wide_bw_chansw_ie, &new_chandef);
> +		else
> +			new_chandef = csa_ie->chandef;

So like I said above, this starts to ignore WBCS if you have things from
operating class, why? Is the only reason it doesn't work against your
broken AP now? ;-)

>  	if (elems->max_channel_switch_time)
>  		csa_ie->max_switch_time =
>  			(elems->max_channel_switch_time[0] << 0) |
> -			(elems->max_channel_switch_time[1] <<  8) |
> +			(elems->max_channel_switch_time[1] << 8) |
> 

No need, I guess, but hey, doesn't matter much. How'd you find this
anyway? :)

johannes
Michael-CY Lee Dec. 5, 2023, 8:38 a.m. UTC | #2
Hi Johannes,
 
Thanks for the reply.
 
I would like to start by explaining why we prioritized the operating
class. 

In IEEE Std 802.11-2020 11.38.4 (Channel switching methods for a VHT
BSS), there is a note about operating class and WBCS Element:
 
“NOTE 2—The indicated operating class within the Extended Channel
Switch Announcement element identifies the bandwidth and the relative
position of the primary 20 MHz and secondary 20 MHz channels. Hence a
Wide Bandwidth Channel Switch subelement is unnecessary when the
Extended Channel Switch Announcement element is used for a channel
switch to a 40 MHz bandwidth.”
 
Although it only limits the switch to a 40 MHz bandwidth, we thought it
is applicable in 80/80+80/160 MHz bandwidth. So in the version 1, we
preferred to use the operating class than the WBCS Element.

Also, we agree your suggestion that WBCS Element has higher priority
than operating class. Our reason is, when channel switch to a bandwidth
wider than 40 MHz, WBCS Element is mandatory while ECSA (which contains
operating class) is not.
(Note that either CSA or ECSA will be sent in a channel switch.)
 
BTW, I would also like to clarify one thing. We did this upstream work
solely based on the standard and with the intention of contributing to
mac80211. In fact, we developed and tested with the upstream driver
(mt76) for AP and STA solution together. Before doing upstream, we used
the MTK AP (proprietary solution) to do IOT tests and indeed found some
problems, which were already reported and fixed.
So, we were not aware of what was going on to MTK AP until we used it
to do an IOT test. We didn’t do it with the intention of covering the
broken WBCS element sent by MTK AP.
 
For other replies, please see the inline. (we deleted some of your
replies since we just change as you suggested)
In summary, we’ll modify the code and submit a new version.
 
Best,
Michael

On Fri, 2023-12-01 at 20:06 +0100, Johannes Berg wrote:
>  	 
> External email : Please do not click links or open attachments until
> you have verified the sender or the content.
>  Hi,
> 
> So looking at this ... I'm not sure I want the operating class
> parsing?
> 
> On Wed, 2023-11-29 at 13:43 +0800, Michael-CY Lee wrote:
> > The new Wi-Fi Standard (IEEE P80211be D4.1) specifies that the Wide
> > Bandwidth Channel Switch (WBCS) Element subfields have the same
> > definitions as VHT operation information if the operating band is
> not
> > S1G.
> 
> Actually that's already in REVme, no?
> 
> "If the New Operating Class field in the frame [...] does not
> indicate
> an S1G band, the subfields New Channel Width, New Channel Center
> Frequency Segment 0 and New Channel Center Frequency Segment 1 have
> the
> same definition, respectively, as Channel Width, Channel Center
> Frequency Segment 0, Channel Center Freqauency Segment 1 in the VHT
> Operation Information field, described in Table 9-313 (VHT Operation
> Information subfields)."

Yes and actually it also shows up in IEEE Std 802.11-2020. I’ll modify
the description to make it clear.

> 
> > The problem comes when the BSS is in 6 GHz band, the STA parses the
> WBCS
> > Element by ieee80211_chandef_vht_oper(), which checks the
> capabilities for
> > HT/VHT mode, not HE/EHT mode.
> 
> OK, but that's an implementation issue, we can make it look at HE
> capabilities too, for 6 GHz?

yes, that’s what this patch intends to do.

> 
> > Also, it adds the way to use op_class in ECSA
> > Element to build a new chandef.
> 
> Not sure why that?

I explained it above.

> 
> > In summary, the new steps for STA to handle CSA event are:
> > 1. build the new chandef from the CSA-related Elements.
> >    (CSA, ECSA, WBCS, etc.)
> 
> Actually that's not what you do? The logic is more like
> 
>  - if BWI present: use only that
>  - if operating class/channel can be used: use only that
>  - if WBCS is present: use only that
>  - otherwise: use (ext) chanswitch element info from before
> 
> 
> Given that I just got a report of an MTK AP that has a broken WCBS
> element, I'm not sure I'm happy with that logic ;-)
> 
> Seems to me the operating class use should maybe be further down the
> list, and perhaps (if it's there) validate the other elements against
> it, to make sure the AP isn't confused?
> 
> So perhaps better:
>  - use, in this order: BWI, WBCS, ECSA, CSA (according to the mode
>    we parse as, and our own capabilities)
>  - if present, check that operating class agrees
> 
> no?

we agree the order. AS for the check for operating class, we think it’s
a little useless.
Image the case that the WBCS Element and the operating class indicate
two different  chandef, but both are valid, which one should we trust,
or neither?
Our answer is that WBCS Element is worth trusting, since it is
mandatory in a channel switch to bandwidth wider than 40 MHz, while
operating class isn’t. 
The operating class can be use in the case that the WBCS Element is
missing or indicates a wrong chandef. In other word, the operating
class has lower priority than WBCS Element.
What do you think?

> 
> > +break;
> > +case IEEE80211_VHT_CHANWIDTH_80P80MHZ:
> > +chandef->width = NL80211_CHAN_WIDTH_80P80;
> > +chandef->center_freq1 = cf0;
> > +chandef->center_freq2 = cf1;
> 
> and not sure I remember well, but this one too? at least going by
> this:

yes, it is also deprecated.

> 
> > +ieee80211_conn_flags_t conn_flags,
> > +u32 vht_cap_info,
> > +struct cfg80211_chan_def *chandef)
> > +{
> > +u32 control_freq, center_freq1, center_freq2;
> > +enum nl80211_chan_width chan_width;
> > +struct ieee80211_ht_operation *ht_oper = NULL;
> > +struct ieee80211_vht_operation *vht_oper = NULL;
> 
> No point initializing those to NULL?
> 
> > +if (conn_flags & (IEEE80211_CONN_DISABLE_HT |
> > +  IEEE80211_CONN_DISABLE_40MHZ)) {
> > +chandef->chan = NULL;
> > +return 0;
> > +}
> > +
> > +control_freq = chandef->chan->center_freq;
> > +center_freq1 = chandef->center_freq1;
> > +center_freq2 = chandef->center_freq2;
> > +chan_width = chandef->width;
> > +
> > +ht_oper = kzalloc(sizeof(*ht_oper), GFP_KERNEL);
> > +if (!ht_oper)
> > +return -ENOMEM;
> 
> Not sure I see value in putting this on the heap, it's tiny?
> 
> > +vht_oper = kzalloc(sizeof(*vht_oper), GFP_KERNEL);
> 
> same here
> 
> > +if (!vht_oper) {
> > +kfree(ht_oper);
> > +return -ENOMEM;
> 
> and if you have these gone, you no longer need a return value either,
> which is nice.
> 
> > +static inline int
> 
> same here
> 
> > +validate_chandef_by_6ghz_he_eht_oper(struct ieee80211_sub_if_data
> *sdata,
> > +     ieee80211_conn_flags_t conn_flags,
> > +     struct cfg80211_chan_def *chandef)
> > +{
> > +u32 size, control_freq, center_freq1, center_freq2;
> > +enum nl80211_chan_width chan_width;
> > +struct ieee80211_he_operation *he_oper = NULL;
> > +struct ieee80211_eht_operation *eht_oper = NULL;
> 
> same here about =NULL, and for the allocations too

We will change the HT/VHT operation to static allocation in the
function. However, the size of HE/EHT operation are variable, so it can
only be dynamically allocated.

> 
> > +case NL80211_CHAN_WIDTH_80P80:
> > +he_6ghz_oper->control =
> > +IEEE80211_HE_6GHZ_OPER_CTRL_CHANWIDTH_160MHZ;
> 
> Is that right? Do HE/EHT even still do 80+80?

According to the Standard, 80+80 MHz bandwidth still exists in HE mode.
The function ieee80211_chandef_he_6ghz_oper() handles this case.

However, from our experience, there are few commercial APs that support
80+80 MHz bandwidth in HE mode. We can remove it, what do you think? 

> 
> > +/* parse one of the Elements to build a new chandef */
> 
> except you don't really, as discussed above
> 
> I'd actually kind of like to have these validated against each other,
> but that's for another day, and we don't build the strictest
> implementation.
> 
> Though I probably will make the implementation optionally stricter in
> some places like this, and enable that for all testing/certification
> in
> the future.

we agree that the validation can be done in the future.

> 
> > +} else if (!ieee80211_operating_class_to_chandef(new_op_class,
> new_chan,
> > + &new_chandef)) {
> > +if (wide_bw_chansw_ie)
> > +wbcs_ie_to_chandef(wide_bw_chansw_ie, &new_chandef);
> > +else
> > +new_chandef = csa_ie->chandef;
> 
> So like I said above, this starts to ignore WBCS if you have things
> from
> operating class, why? Is the only reason it doesn't work against your
> broken AP now? ;-)

Like I explained above, we did find and report some problems during the
tests. But it's definitely not the reason.

> 
> >  if (elems->max_channel_switch_time)
> >  csa_ie->max_switch_time =
> >  (elems->max_channel_switch_time[0] << 0) |
> > -(elems->max_channel_switch_time[1] <<  8) |
> > +(elems->max_channel_switch_time[1] << 8) |
> > 
> 
> No need, I guess, but hey, doesn't matter much. How'd you find this
> anyway? :)

Just saw it accidently. Anyway, since it’s harmless, we will drop it.
Johannes Berg Dec. 14, 2023, 1:31 p.m. UTC | #3
Hi,

Sorry for the delay.
 
> I would like to start by explaining why we prioritized the operating
> class. 
> 
> In IEEE Std 802.11-2020 11.38.4 (Channel switching methods for a VHT
> BSS), there is a note about operating class and WBCS Element:
>  
> “NOTE 2—The indicated operating class within the Extended Channel
> Switch Announcement element identifies the bandwidth and the relative
> position of the primary 20 MHz and secondary 20 MHz channels. Hence a
> Wide Bandwidth Channel Switch subelement is unnecessary when the
> Extended Channel Switch Announcement element is used for a channel
> switch to a 40 MHz bandwidth.”

That's ... interesting. I hadn't seen that. But I guess it doesn't
surprise me, it's in line with what seems to be a general push to give
the operating class more relevance :)

> Although it only limits the switch to a 40 MHz bandwidth, we thought it
> is applicable in 80/80+80/160 MHz bandwidth. So in the version 1, we
> preferred to use the operating class than the WBCS Element.

Right. Though at least with 40 MHz you can do "opclass X -> secondary is
above" and "opclass Y -> secondary is below", without really relying on
the channelisation plan. For wider than 40, you can't do that any more,
I think?

> Also, we agree your suggestion that WBCS Element has higher priority
> than operating class. Our reason is, when channel switch to a bandwidth
> wider than 40 MHz, WBCS Element is mandatory while ECSA (which contains
> operating class) is not.

OK :)

> (Note that either CSA or ECSA will be sent in a channel switch.)

Right.

> BTW, I would also like to clarify one thing. We did this upstream work
> solely based on the standard and with the intention of contributing to
> mac80211. In fact, we developed and tested with the upstream driver
> (mt76) for AP and STA solution together. Before doing upstream, we used
> the MTK AP (proprietary solution) to do IOT tests and indeed found some
> problems, which were already reported and fixed.
> So, we were not aware of what was going on to MTK AP until we used it
> to do an IOT test. We didn’t do it with the intention of covering the
> broken WBCS element sent by MTK AP.

:-)
I'm sorry I shouldn't have phrased it that way, I apologize.

Just by sheer coincidence, both these patches and the AP bug report for
our client landed in my Inbox at around the same time, and I clearly
drew a conclusion I shouldn't have.
 
> For other replies, please see the inline. (we deleted some of your
> replies since we just change as you suggested)
> In summary, we’ll modify the code and submit a new version.

sounds good

> > So perhaps better:
> >  - use, in this order: BWI, WBCS, ECSA, CSA (according to the mode
> >    we parse as, and our own capabilities)
> >  - if present, check that operating class agrees
> > 
> > no?
> 
> we agree the order. AS for the check for operating class, we think it’s
> a little useless.

I don't disagree that checking the operating class is "a little
useless". Up to you :-)

What I've been brewing in my head for a while now though is a (per-
wiphy?) "strict mode" where we do more such consistency checks, and also
disable workarounds like using beacon elements if missing in association
response. I'd want to enable that in our testing, and perhaps also when
doing interoperability testing with other APs.

I realize that's only tangentially related to this patch, but I think it
could be very useful.

> Image the case that the WBCS Element and the operating class indicate
> two different  chandef, but both are valid, which one should we trust,
> or neither?
> Our answer is that WBCS Element is worth trusting, since it is
> mandatory in a channel switch to bandwidth wider than 40 MHz, while
> operating class isn’t. 

Yeah, makes sense. We could have a check behind a "strict mode" flag,
but I guess that's something for the future.

> The operating class can be use in the case that the WBCS Element is
> missing or indicates a wrong chandef. In other word, the operating
> class has lower priority than WBCS Element.
> What do you think?

I'm not sure I'd use it for the case of "wrong chandef"? You said
yourself that WBCS is mandatory and opclass is optional, so I think we
should be able to trust the AP when it says WBCS is something, and if
it's confused about it, we'd better not trust it to accidentally (?)
have gotten the opclass correct?

> We will change the HT/VHT operation to static allocation in the
> function. However, the size of HE/EHT operation are variable, so it can
> only be dynamically allocated.

There's a pretty small upper bound though, so you could just pack it
into a struct with a buffer?

struct {
	struct eht_operation oper;
	u8 buf[MAX_EHT_OPER_OPTIONAL];
} __packed eht;

and use it as "eht.oper"?

Maybe not worth it though. We cannot use dynamically sized stack arrays
in the kernel, so that wouldn't work either.


> According to the Standard, 80+80 MHz bandwidth still exists in HE mode.
> The function ieee80211_chandef_he_6ghz_oper() handles this case.

Hah, OK.

> However, from our experience, there are few commercial APs that support
> 80+80 MHz bandwidth in HE mode. We can remove it, what do you think? 

I have no idea. We don't support it even as a client, so we'd always
just use 80 MHz. So from our (Intel) POV it doesn't matter. Up to you. I
don't mind having it or leaving it out, either way. Now that you have
the code anyway maybe leave it?

> > > +/* parse one of the Elements to build a new chandef */
> > 
> > except you don't really, as discussed above
> > 
> > I'd actually kind of like to have these validated against each other,
> > but that's for another day, and we don't build the strictest
> > implementation.
> > 
> > Though I probably will make the implementation optionally stricter in
> > some places like this, and enable that for all testing/certification
> > in the future.
> 
> we agree that the validation can be done in the future.

Right, which basically the discussion above.

Thanks!

johannes
diff mbox series

Patch

diff --git a/net/mac80211/spectmgmt.c b/net/mac80211/spectmgmt.c
index 55959b0b24c5..9a5f743ee750 100644
--- a/net/mac80211/spectmgmt.c
+++ b/net/mac80211/spectmgmt.c
@@ -19,6 +19,236 @@ 
 #include "sta_info.h"
 #include "wme.h"
 
+static inline void
+wbcs_ie_to_chandef(const struct ieee80211_wide_bw_chansw_ie *wbcs_ie,
+		   struct cfg80211_chan_def *chandef)
+{
+	u8 ccfs0 = wbcs_ie->new_center_freq_seg0;
+	u8 ccfs1 = wbcs_ie->new_center_freq_seg1;
+	u32 cf0 = ieee80211_channel_to_frequency(ccfs0, chandef->chan->band);
+	u32 cf1 = ieee80211_channel_to_frequency(ccfs1, chandef->chan->band);
+
+	switch (wbcs_ie->new_channel_width) {
+	case IEEE80211_VHT_CHANWIDTH_160MHZ:
+		chandef->width = NL80211_CHAN_WIDTH_160;
+		chandef->center_freq1 = cf0;
+		break;
+	case IEEE80211_VHT_CHANWIDTH_80P80MHZ:
+		chandef->width = NL80211_CHAN_WIDTH_80P80;
+		chandef->center_freq1 = cf0;
+		chandef->center_freq2 = cf1;
+		break;
+	case IEEE80211_VHT_CHANWIDTH_80MHZ:
+		chandef->width = NL80211_CHAN_WIDTH_80;
+		chandef->center_freq1 = cf0;
+
+		if (ccfs1) {
+			u8 diff = abs(ccfs0 - ccfs1);
+
+			if (diff == 8) {
+				chandef->width = NL80211_CHAN_WIDTH_160;
+				chandef->center_freq1 = cf1;
+			} else if (diff > 8) {
+				chandef->width = NL80211_CHAN_WIDTH_80P80;
+				chandef->center_freq2 = cf1;
+			}
+		}
+		break;
+	case IEEE80211_VHT_CHANWIDTH_USE_HT:
+	default:
+		/* If the WBCS Element is present, new channel bandwidth is
+		 * at least 40 MHz.
+		 */
+		chandef->width = NL80211_CHAN_WIDTH_40;
+		chandef->center_freq1 = cf0;
+		break;
+	}
+}
+
+static inline int
+validate_chandef_by_ht_vht_oper(struct ieee80211_sub_if_data *sdata,
+				ieee80211_conn_flags_t conn_flags,
+				u32 vht_cap_info,
+				struct cfg80211_chan_def *chandef)
+{
+	u32 control_freq, center_freq1, center_freq2;
+	enum nl80211_chan_width chan_width;
+	struct ieee80211_ht_operation *ht_oper = NULL;
+	struct ieee80211_vht_operation *vht_oper = NULL;
+
+	if (conn_flags & (IEEE80211_CONN_DISABLE_HT |
+			  IEEE80211_CONN_DISABLE_40MHZ)) {
+		chandef->chan = NULL;
+		return 0;
+	}
+
+	control_freq = chandef->chan->center_freq;
+	center_freq1 = chandef->center_freq1;
+	center_freq2 = chandef->center_freq2;
+	chan_width = chandef->width;
+
+	ht_oper = kzalloc(sizeof(*ht_oper), GFP_KERNEL);
+	if (!ht_oper)
+		return -ENOMEM;
+
+	ht_oper->primary_chan = ieee80211_frequency_to_channel(control_freq);
+	if (control_freq != center_freq1)
+		ht_oper->ht_param = control_freq > center_freq1 ?
+			IEEE80211_HT_PARAM_CHA_SEC_BELOW :
+			IEEE80211_HT_PARAM_CHA_SEC_ABOVE;
+	else
+		ht_oper->ht_param = IEEE80211_HT_PARAM_CHA_SEC_NONE;
+
+	ieee80211_chandef_ht_oper(ht_oper, chandef);
+
+	if (conn_flags & IEEE80211_CONN_DISABLE_VHT) {
+		kfree(ht_oper);
+		return 0;
+	}
+
+	vht_oper = kzalloc(sizeof(*vht_oper), GFP_KERNEL);
+	if (!vht_oper) {
+		kfree(ht_oper);
+		return -ENOMEM;
+	}
+
+	vht_oper->center_freq_seg0_idx =
+		ieee80211_frequency_to_channel(center_freq1);
+	vht_oper->center_freq_seg1_idx = center_freq2 ?
+		ieee80211_frequency_to_channel(center_freq2) : 0;
+
+	switch (chan_width) {
+	case NL80211_CHAN_WIDTH_320:
+		WARN_ON(1);
+		break;
+	case NL80211_CHAN_WIDTH_160:
+		vht_oper->chan_width = IEEE80211_VHT_CHANWIDTH_80MHZ;
+		vht_oper->center_freq_seg1_idx = vht_oper->center_freq_seg0_idx;
+		vht_oper->center_freq_seg0_idx +=
+			control_freq < center_freq1 ? -8 : 8;
+		break;
+	case NL80211_CHAN_WIDTH_80P80:
+		vht_oper->chan_width = IEEE80211_VHT_CHANWIDTH_80MHZ;
+		break;
+	case NL80211_CHAN_WIDTH_80:
+		vht_oper->chan_width = IEEE80211_VHT_CHANWIDTH_80MHZ;
+		break;
+	default:
+		vht_oper->chan_width = IEEE80211_VHT_CHANWIDTH_USE_HT;
+		break;
+	}
+
+	ht_oper->operation_mode =
+		cpu_to_le16(vht_oper->center_freq_seg1_idx <<
+				IEEE80211_HT_OP_MODE_CCFS2_SHIFT);
+
+	if (!ieee80211_chandef_vht_oper(&sdata->local->hw, vht_cap_info,
+					vht_oper, ht_oper, chandef))
+		chandef->chan = NULL;
+
+	kfree(ht_oper);
+	kfree(vht_oper);
+
+	return 0;
+}
+
+static inline int
+validate_chandef_by_6ghz_he_eht_oper(struct ieee80211_sub_if_data *sdata,
+				     ieee80211_conn_flags_t conn_flags,
+				     struct cfg80211_chan_def *chandef)
+{
+	u32 size, control_freq, center_freq1, center_freq2;
+	enum nl80211_chan_width chan_width;
+	struct ieee80211_he_operation *he_oper = NULL;
+	struct ieee80211_he_6ghz_oper *he_6ghz_oper;
+	struct ieee80211_eht_operation *eht_oper = NULL;
+	struct ieee80211_eht_operation_info *eht_oper_info;
+
+	if (conn_flags & (IEEE80211_CONN_DISABLE_HE)) {
+		chandef->chan = NULL;
+		return 0;
+	}
+
+	control_freq = chandef->chan->center_freq;
+	center_freq1 = chandef->center_freq1;
+	center_freq2 = chandef->center_freq2;
+	chan_width = chandef->width;
+
+	size = sizeof(struct ieee80211_he_operation) +
+	       sizeof(struct ieee80211_he_6ghz_oper);
+
+	he_oper = kzalloc(size, GFP_KERNEL);
+	if (!he_oper)
+		return -ENOMEM;
+
+	if (!(conn_flags & IEEE80211_CONN_DISABLE_EHT)) {
+		size = sizeof(struct ieee80211_eht_operation) +
+		       sizeof(struct ieee80211_eht_operation_info);
+
+		eht_oper = kzalloc(size, GFP_KERNEL);
+		if (!eht_oper) {
+			kfree(he_oper);
+			return -ENOMEM;
+		}
+	}
+
+	he_oper->he_oper_params = cpu_to_le32(u32_encode_bits(1,
+					IEEE80211_HE_OPERATION_6GHZ_OP_INFO));
+
+	he_6ghz_oper = (struct ieee80211_he_6ghz_oper *)he_oper->optional;
+	he_6ghz_oper->primary =
+		ieee80211_frequency_to_channel(control_freq);
+	he_6ghz_oper->ccfs0 = ieee80211_frequency_to_channel(center_freq1);
+	he_6ghz_oper->ccfs1 = center_freq2 ?
+		ieee80211_frequency_to_channel(center_freq2) : 0;
+
+	switch (chan_width) {
+	case NL80211_CHAN_WIDTH_320:
+		he_6ghz_oper->ccfs1 = he_6ghz_oper->ccfs0;
+		he_6ghz_oper->ccfs0 += control_freq < center_freq1 ? -16 : 16;
+		he_6ghz_oper->control = IEEE80211_EHT_OPER_CHAN_WIDTH_320MHZ;
+		break;
+	case NL80211_CHAN_WIDTH_160:
+		he_6ghz_oper->ccfs1 = he_6ghz_oper->ccfs0;
+		he_6ghz_oper->ccfs0 += control_freq < center_freq1 ? -8 : 8;
+		fallthrough;
+	case NL80211_CHAN_WIDTH_80P80:
+		he_6ghz_oper->control =
+			IEEE80211_HE_6GHZ_OPER_CTRL_CHANWIDTH_160MHZ;
+		break;
+	case NL80211_CHAN_WIDTH_80:
+		he_6ghz_oper->control =
+			IEEE80211_HE_6GHZ_OPER_CTRL_CHANWIDTH_80MHZ;
+		break;
+	case NL80211_CHAN_WIDTH_40:
+		he_6ghz_oper->control =
+			IEEE80211_HE_6GHZ_OPER_CTRL_CHANWIDTH_40MHZ;
+		break;
+	default:
+		he_6ghz_oper->control =
+			IEEE80211_HE_6GHZ_OPER_CTRL_CHANWIDTH_20MHZ;
+		break;
+	}
+
+	if (eht_oper) {
+		eht_oper->params = IEEE80211_EHT_OPER_INFO_PRESENT;
+
+		eht_oper_info =
+			(struct ieee80211_eht_operation_info *)eht_oper->optional;
+		eht_oper_info->control = he_6ghz_oper->control;
+		eht_oper_info->ccfs0 = he_6ghz_oper->ccfs0;
+		eht_oper_info->ccfs1 = he_6ghz_oper->ccfs1;
+	}
+
+	if (!ieee80211_chandef_he_6ghz_oper(sdata, he_oper, eht_oper, chandef))
+		chandef->chan = NULL;
+
+	kfree(he_oper);
+	kfree(eht_oper);
+
+	return 0;
+}
+
 int ieee80211_parse_ch_switch_ie(struct ieee80211_sub_if_data *sdata,
 				 struct ieee802_11_elems *elems,
 				 enum nl80211_band current_band,
@@ -27,13 +257,14 @@  int ieee80211_parse_ch_switch_ie(struct ieee80211_sub_if_data *sdata,
 				 struct ieee80211_csa_ie *csa_ie)
 {
 	enum nl80211_band new_band = current_band;
-	int new_freq;
-	u8 new_chan_no;
+	int new_freq, ret;
+	u8 new_chan_no = 0, new_op_class = 0;
 	struct ieee80211_channel *new_chan;
-	struct cfg80211_chan_def new_vht_chandef = {};
+	struct cfg80211_chan_def new_chandef = {};
 	const struct ieee80211_sec_chan_offs_ie *sec_chan_offs;
 	const struct ieee80211_wide_bw_chansw_ie *wide_bw_chansw_ie;
 	const struct ieee80211_bandwidth_indication *bwi;
+	const struct ieee80211_ext_chansw_ie *ext_chansw_ie;
 	int secondary_channel_offset = -1;
 
 	memset(csa_ie, 0, sizeof(*csa_ie));
@@ -41,6 +272,7 @@  int ieee80211_parse_ch_switch_ie(struct ieee80211_sub_if_data *sdata,
 	sec_chan_offs = elems->sec_chan_offs;
 	wide_bw_chansw_ie = elems->wide_bw_chansw_ie;
 	bwi = elems->bandwidth_indication;
+	ext_chansw_ie = elems->ext_chansw_ie;
 
 	if (conn_flags & (IEEE80211_CONN_DISABLE_HT |
 			  IEEE80211_CONN_DISABLE_40MHZ)) {
@@ -51,26 +283,30 @@  int ieee80211_parse_ch_switch_ie(struct ieee80211_sub_if_data *sdata,
 	if (conn_flags & IEEE80211_CONN_DISABLE_VHT)
 		wide_bw_chansw_ie = NULL;
 
-	if (elems->ext_chansw_ie) {
-		if (!ieee80211_operating_class_to_band(
-				elems->ext_chansw_ie->new_operating_class,
-				&new_band)) {
-			sdata_info(sdata,
-				   "cannot understand ECSA IE operating class, %d, ignoring\n",
-				   elems->ext_chansw_ie->new_operating_class);
+	if (ext_chansw_ie) {
+		new_op_class = ext_chansw_ie->new_operating_class;
+		if (!ieee80211_operating_class_to_band(new_op_class, &new_band)) {
+			new_op_class = 0;
+			sdata_info(sdata, "cannot understand ECSA IE "
+					  "operating class, %d, ignoring\n",
+				   ext_chansw_ie->new_operating_class);
+		} else {
+			new_chan_no = ext_chansw_ie->new_ch_num;
+			csa_ie->count = ext_chansw_ie->count;
+			csa_ie->mode = ext_chansw_ie->mode;
 		}
-		new_chan_no = elems->ext_chansw_ie->new_ch_num;
-		csa_ie->count = elems->ext_chansw_ie->count;
-		csa_ie->mode = elems->ext_chansw_ie->mode;
-	} else if (elems->ch_switch_ie) {
+	}
+
+	if (!new_op_class && elems->ch_switch_ie) {
 		new_chan_no = elems->ch_switch_ie->new_ch_num;
 		csa_ie->count = elems->ch_switch_ie->count;
 		csa_ie->mode = elems->ch_switch_ie->mode;
-	} else {
-		/* nothing here we understand */
-		return 1;
 	}
 
+	/* nothing here we understand */
+	if (!new_chan_no)
+		return 1;
+
 	/* Mesh Channel Switch Parameters Element */
 	if (elems->mesh_chansw_params_ie) {
 		csa_ie->ttl = elems->mesh_chansw_params_ie->mesh_ttl;
@@ -134,65 +370,68 @@  int ieee80211_parse_ch_switch_ie(struct ieee80211_sub_if_data *sdata,
 		break;
 	}
 
+	/* parse one of the Elements to build a new chandef */
+	memset(&new_chandef, 0, sizeof(new_chandef));
+	new_chandef.chan = new_chan;
 	if (bwi) {
 		/* start with the CSA one */
-		new_vht_chandef = csa_ie->chandef;
+		new_chandef = csa_ie->chandef;
 		/* and update the width accordingly */
 		/* FIXME: support 160/320 */
 		ieee80211_chandef_eht_oper(&bwi->info, true, true,
-					   &new_vht_chandef);
-	} else if (wide_bw_chansw_ie) {
-		u8 new_seg1 = wide_bw_chansw_ie->new_center_freq_seg1;
-		struct ieee80211_vht_operation vht_oper = {
-			.chan_width =
-				wide_bw_chansw_ie->new_channel_width,
-			.center_freq_seg0_idx =
-				wide_bw_chansw_ie->new_center_freq_seg0,
-			.center_freq_seg1_idx = new_seg1,
-			/* .basic_mcs_set doesn't matter */
-		};
-		struct ieee80211_ht_operation ht_oper = {
-			.operation_mode =
-				cpu_to_le16(new_seg1 <<
-					    IEEE80211_HT_OP_MODE_CCFS2_SHIFT),
-		};
-
-		/* default, for the case of IEEE80211_VHT_CHANWIDTH_USE_HT,
-		 * to the previously parsed chandef
-		 */
-		new_vht_chandef = csa_ie->chandef;
+					   &new_chandef);
+	} else if (!ieee80211_operating_class_to_chandef(new_op_class, new_chan,
+							 &new_chandef)) {
+		if (wide_bw_chansw_ie)
+			wbcs_ie_to_chandef(wide_bw_chansw_ie, &new_chandef);
+		else
+			new_chandef = csa_ie->chandef;
+	}
+
+	if (!cfg80211_chandef_valid(&new_chandef))
+		new_chandef = csa_ie->chandef;
 
-		/* ignore if parsing fails */
-		if (!ieee80211_chandef_vht_oper(&sdata->local->hw,
-						vht_cap_info,
-						&vht_oper, &ht_oper,
-						&new_vht_chandef))
-			new_vht_chandef.chan = NULL;
+	/* check if the new chandef fits the capabilities */
+	if (new_band == NL80211_BAND_6GHZ) {
+		ret = validate_chandef_by_6ghz_he_eht_oper(sdata, conn_flags,
+							   &new_chandef);
+		if (ret)
+			return ret;
+	} else {
+		ret = validate_chandef_by_ht_vht_oper(sdata, conn_flags,
+						      vht_cap_info, &new_chandef);
+		if (ret)
+			return ret;
+	}
+
+	/* if data is there validate the bandwidth & use it */
+	if (new_chandef.chan) {
+		if (conn_flags & IEEE80211_CONN_DISABLE_320MHZ &&
+		    new_chandef.width == NL80211_CHAN_WIDTH_320)
+			ieee80211_chandef_downgrade(&new_chandef);
 
 		if (conn_flags & IEEE80211_CONN_DISABLE_80P80MHZ &&
-		    new_vht_chandef.width == NL80211_CHAN_WIDTH_80P80)
-			ieee80211_chandef_downgrade(&new_vht_chandef);
+		    new_chandef.width == NL80211_CHAN_WIDTH_80P80)
+			ieee80211_chandef_downgrade(&new_chandef);
+
 		if (conn_flags & IEEE80211_CONN_DISABLE_160MHZ &&
-		    new_vht_chandef.width == NL80211_CHAN_WIDTH_160)
-			ieee80211_chandef_downgrade(&new_vht_chandef);
-	}
+		    new_chandef.width == NL80211_CHAN_WIDTH_160)
+			ieee80211_chandef_downgrade(&new_chandef);
 
-	/* if VHT data is there validate & use it */
-	if (new_vht_chandef.chan) {
-		if (!cfg80211_chandef_compatible(&new_vht_chandef,
+		if (!cfg80211_chandef_compatible(&new_chandef,
 						 &csa_ie->chandef)) {
 			sdata_info(sdata,
 				   "BSS %pM: CSA has inconsistent channel data, disconnecting\n",
 				   bssid);
 			return -EINVAL;
 		}
-		csa_ie->chandef = new_vht_chandef;
+		csa_ie->chandef = new_chandef;
 	}
 
 	if (elems->max_channel_switch_time)
 		csa_ie->max_switch_time =
 			(elems->max_channel_switch_time[0] << 0) |
-			(elems->max_channel_switch_time[1] <<  8) |
+			(elems->max_channel_switch_time[1] << 8) |
 			(elems->max_channel_switch_time[2] << 16);
 
 	return 0;