diff mbox series

[v3,1/2] wifi: mac80211: add utility for converting op_class into chandef

Message ID 20231129054321.10199-1-michael-cy.lee@mediatek.com
State Superseded
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
This utility is used in STA CSA handling. The op_class in the ECSA
Element can be converted into chandef.

Signed-off-by: Michael-CY Lee <michael-cy.lee@mediatek.com>
Signed-off-by: Money Wang <money.wang@mediatek.com>
---
v3:
  There were two utilities for op_class conversion in version 2.
  This version combines them into one.
---
 include/net/cfg80211.h | 13 ++++++++
 net/wireless/util.c    | 76 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 89 insertions(+)

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
Johannes Berg Dec. 14, 2023, 1:31 p.m. UTC | #2
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/include/net/cfg80211.h b/include/net/cfg80211.h
index b137a33a1b68..0ea1f1a53324 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -8669,6 +8669,19 @@  void cfg80211_ch_switch_started_notify(struct net_device *dev,
 bool ieee80211_operating_class_to_band(u8 operating_class,
 				       enum nl80211_band *band);
 
+/**
+ * ieee80211_operating_class_to_chandef - convert operating class to chandef
+ *
+ * @operating_class: the operating class to convert
+ * @chan: the ieee80211_channel to convert
+ * @chandef: a pointer to the resulting chandef
+ *
+ * Returns %true if the conversion was successful, %false otherwise.
+ */
+bool ieee80211_operating_class_to_chandef(u8 operating_class,
+					  struct ieee80211_channel *chan,
+					  struct cfg80211_chan_def *chandef);
+
 /**
  * ieee80211_chandef_to_operating_class - convert chandef to operation class
  *
diff --git a/net/wireless/util.c b/net/wireless/util.c
index 626b858b4b35..edcc6f202c19 100644
--- a/net/wireless/util.c
+++ b/net/wireless/util.c
@@ -2017,6 +2017,82 @@  bool ieee80211_operating_class_to_band(u8 operating_class,
 }
 EXPORT_SYMBOL(ieee80211_operating_class_to_band);
 
+bool ieee80211_operating_class_to_chandef(u8 operating_class,
+					  struct ieee80211_channel *chan,
+					  struct cfg80211_chan_def *chandef)
+{
+	u32 control_freq, offset = 0;
+	enum nl80211_band band;
+
+	if (!ieee80211_operating_class_to_band(operating_class, &band) ||
+	    !chan || band != chan->band)
+		return false;
+
+	control_freq = chan->center_freq;
+	chandef->chan = chan;
+
+	if (control_freq >= 5955)
+		offset = control_freq - 5955;
+	else if (control_freq >= 5745)
+		offset = control_freq - 5745;
+	else if (control_freq >= 5180)
+		offset = control_freq - 5180;
+	offset /= 20;
+
+	switch (operating_class) {
+	case 81:  /* 2 GHz band; 20 MHz; channels 1..13 */
+	case 82:  /* 2 GHz band; 20 MHz; channel 14 */
+	case 115: /* 5 GHz band; 20 MHz; channels 36,40,44,48 */
+	case 118: /* 5 GHz band; 20 MHz; channels 52,56,60,64 */
+	case 121: /* 5 GHz band; 20 MHz; channels 100..144 */
+	case 124: /* 5 GHz band; 20 MHz; channels 149,153,157,161 */
+	case 125: /* 5 GHz band; 20 MHz; channels 149..177 */
+	case 131: /* 6 GHz band; 20 MHz; channels 1..233*/
+	case 136: /* 6 GHz band; 20 MHz; channel 2 */
+		chandef->center_freq1 = control_freq;
+		chandef->width = NL80211_CHAN_WIDTH_20;
+		return true;
+	case 83:  /* 2 GHz band; 40 MHz; channels 1..9 */
+	case 116: /* 5 GHz band; 40 MHz; channels 36,44 */
+	case 119: /* 5 GHz band; 40 MHz; channels 52,60 */
+	case 122: /* 5 GHz band; 40 MHz; channels 100,108,116,124,132,140 */
+	case 126: /* 5 GHz band; 40 MHz; channels 149,157,165,173 */
+		chandef->center_freq1 = control_freq + 10;
+		chandef->width = NL80211_CHAN_WIDTH_40;
+		return true;
+	case 84:  /* 2 GHz band; 40 MHz; channels 5..13 */
+	case 117: /* 5 GHz band; 40 MHz; channels 40,48 */
+	case 120: /* 5 GHz band; 40 MHz; channels 56,64 */
+	case 123: /* 5 GHz band; 40 MHz; channels 104,112,120,128,136,144 */
+	case 127: /* 5 GHz band; 40 MHz; channels 153,161,169,177 */
+		chandef->center_freq1 = control_freq - 10;
+		chandef->width = NL80211_CHAN_WIDTH_40;
+		return true;
+	case 132: /* 6 GHz band; 40 MHz; channels 1,5,..,229*/
+		chandef->center_freq1 = control_freq + 10 - (offset & 1) * 20;
+		chandef->width = NL80211_CHAN_WIDTH_40;
+		return true;
+	case 128: /* 5 GHz band; 80 MHz; channels 36..64,100..144,149..177 */
+	case 133: /* 6 GHz band; 80 MHz; channels 1,5,..,229 */
+		chandef->center_freq1 = control_freq + 30 - (offset & 3) * 20;
+		chandef->width = NL80211_CHAN_WIDTH_80;
+		return true;
+	case 129: /* 5 GHz band; 160 MHz; channels 36..64,100..144,149..177 */
+	case 134: /* 6 GHz band; 160 MHz; channels 1,5,..,229 */
+		chandef->center_freq1 = control_freq + 70 - (offset & 7) * 20;
+		chandef->width = NL80211_CHAN_WIDTH_160;
+		return true;
+	case 130: /* 5 GHz band; 80+80 MHz; channels 36..64,100..144,149..177 */
+	case 135: /* 6 GHz band; 80+80 MHz; channels 1,5,..,229 */
+		  /* The center_freq2 of 80+80 MHz is unknown */
+	case 137: /* 6 GHz band; 320 MHz; channels 1,5,..,229 */
+		  /* 320-1 or 320-2 channelization is unknown */
+	default:
+		return false;
+	}
+}
+EXPORT_SYMBOL(ieee80211_operating_class_to_chandef);
+
 bool ieee80211_chandef_to_operating_class(struct cfg80211_chan_def *chandef,
 					  u8 *op_class)
 {