Proposal: change the net80211 channel representation (May 2026)

Overview

The current net80211 channel representation is an array of "struct ieee80211_channel" entries, one for each combination of (frequency, type, flags, width, secondary channel). This is scaling very poorly post 802.11n (well, I would argue it wasn't great for 802.11n either.) The net80211 stack and driver uses "struct ieee80211_channel *" pointers everywhere to represent channels, pointing into this static array of channels. This avoids having to do any kind of locking, refcounting, etc to protect the contents of the channel structures, but it means we have a combinatorial scaling problem as we add more frequencies, operating modes and widths.

This proposal covers refactoring net80211 and drivers in multiple passes to convert them away from using a pointer to using a local struct - kind of what Linux does with its channel context structure. Here the APIs would be slowly moved from pointer assignment and comparisons to macros/methods that handle channel assignment, comparison and fetching field contents. A subset of net80211 will need to be changed for drivers to be fully migrated away from channel pointers, and then the rest of the work will be around regulatory handling, channel list management and lookups.

Background

Here's what the channel list looks like for 2GHz. You can view it by doing "ifconfig -v wlan0 list chan" on a configured wifi VAP.

For 5GHz it is worse because (a) there's a lot more channels, (b) there's a lot more places for 40MHz wide 802.11 channels, (c) there's 802.11ac / VHT channels, and (d) there's a lot more combinations of VHT channels (20, 40+, 40-, VHT40+, VHT40-, VHT80+, VHT80-)

This is going to get even more ridiculous for 6GHz wifi, 802.11ax channel support, for eventual 60GHz wifi support, etc, etc, etc.

Introducing 802.11ac required bumping the channel list up to 1024. 802.11ax, 6GHz, VHT160/VHT320 support, etc will require significantly more channels and that's just too many.

Proposal

The core of the proposal is instead to move everything to using private copies of "struct ieee80211_channel", rather than the pointers to the global channel list. Channels are found by using ieee80211_find_channel() and variants thereof, which instead of returning a pointer to a channel can just populate a channel entry. Channel pointers are found in the ieee80211com struct (desired chan, bsschan, curchan), the node (ni_chan), various state in the scan routines, used in the receive path, and then as arguments/variables for doing, well, everything else.

The ieee80211com list of all channel combinations and active channels would then be removed and replaced with a much smaller set of channels with properties (allowable channel widths, phy modes, operating modes (sta, ap, etc), transmit power and other flags.) The regulatory and channel list / find code would be replaced with versions that know how to iterate through this set of channels+properties rather than simply searching the channel list looking for frequency/flag matches.

The rest of the channel representation would broadly stay the same. Code would be migrated to use channel structs from net80211 state (node channel, ieee80211com channel, scan channel, vap desired channel, etc) and copy the channel contents into their own state for use rather than using a channel pointer. Channel pointers inside net80211 and drivers can still be passed around as long as they're const and they're local - no persisting channel pointers in state structs.

The biggest functional change is likely the global channel list that the regulatory domain / channel list code handles and that ifconfig assembles when it creates a VAP. Ideally those would be the last pieces to migrate as they may require a flag day for userland API changes (although I may have a way to work around that..)

Step 1: Channel manipulation APIs

The first pass is to define the channel manipulation APIs and behaviour. There's two sets of proposed macros - one for dealing with existing channel pointers and one for dealing with structs. The motivation behind having both is to define the channel struct behaviours for both cases and to make it easier to search and do later refactoring as things migrate from pointers to structs.

The channel pointer variants:

The channel struct variants:

An initial exploration of this can be found in https://reviews.freebsd.org/D48172 .

Step 2: Initial migration targets (MVP for drivers)

The initial migration target is whatever fields are necessary to migrate drivers to the above API and remove as many references to ieee80211_channel pointers back into net80211 as possible.

This going to require migrating a few core net80211 types:

If drivers need to use ieee80211_channel pointers (eg for channel info when calling into their own HAL/HW code for programming stuff) then the code can use NET80211_CHANNEL_GET_PTR() / NET80211_CHANNEL_P_GET_PTR() with the understanding it can't be used outside of the call + stack. If drivers need to persist channel information then they'll need to create a copy of the channel state, or re-fetch it from the vap/node/ic when needed.

Note this won't cover EVERYTHING. I'll keep a list of them here:

This will likely be a good point to convert things to use const channel pointers rather than non-const pointers. The pointers shouldn't be changed by anything anyway, so whilst APIs are refactored they should be converted.

Step 3: net80211 migration - channel search APIs

Next is migrating the channel search APIs. Right now ieee80211_find_channel() and all its variants to find ht, vht, turbo, etc channels return a channel pointer or NULL if a suitable channel can't be found.

They'll need to be updated to instead take a channel structure to populate, and return true or false based on whether they found a suitable channel or not. If they found a channel, they'll copy the channel structure into it and return true. If they did not find a channel, they'll bzero() the channel structure (so a subsequent IEEE80211_IS_CHAN_DEFINED() will return false).

There'll likely be a whole lot of flow-on effects from this migration and hopefully the bulk of the net80211 codebase will end up being migrated here.

This will likely be a good point to convert things to use const channel pointers rather than non-const pointers. The pointers shouldn't be changed by anything anyway, so whilst APIs are refactored they should be converted.

Step 4 - net80211 migration - migrate everything but regulatory/channel setup

This step involves finding whatever channel pointers in structs are left in the net80211 stack and migrate away from them. Again, they're fine to use inside the current call stack via a call to NET80211_CHANNEL_GET_PTR() / NET80211_CHANNEL_P_GET_PTR(), but there shouldn't be anything left that is /storing/ a channel pointer in net80211.

Step 5 - regulatory / channel lists

NOTE: This will need a bunch more thought - but again, my hope is everything done above will get net80211 and the stack into a shape where this is possible.

At this point we shouldn't need the channel lists any further, however they're being used as a way to iterate through channels by matching on flags. Part of this step is to figure out how to replace the iteration/searching that relies on that channel list to instead generatively iterate through the channel/flags space.

The channel representation in net80211 would instead be a set of channels, and for each channel a bunch of parameters (power, phy/operating mode, channel widths, other flags.)

When searching for 20MHz wide channels, the search space is just the channel list and phy/operating mode (and well, if 20MHz is ok - remember, net80211 and the 802.11 spec does define 5 and 10MHz channels.) For other channel widths, the search space is going to include both the channel and some other operating mode (eg HT40U/HT40D flags for 11n/11ac VHT40, HT80U/HT80D for VHT80, and then 80+80/160MHz will have other search parameters..) Then instead of just returning the channel struct with all the HT/VHT information (such as true centre frequency, split vht 80+80 frequency, upper/lower config, etc), the find channel code will have to create this information and return it.

The regulatory domain code could then be changed to basically be .. well, a representation in userland of the above information.

Unknowns


AdrianChadd/Net80211ChannelRepresentation2026 (last edited 2026-05-29T15:09:59+0000 by AdrianChadd)