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

Johannes Berg johannes at sipsolutions.net
Fri Dec 1 11:06:10 PST 2023


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 at mediatek.com>
> Signed-off-by: Money Wang <money.wang at 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



More information about the Linux-mediatek mailing list