Summer of Code 2023 progress blog

NetBSD

Update Atheros Wi-Fi driver(s) for new Wi-Fi stack

Jeandre Kruger

View final submission

athn(4) USB

I do not yet have an Atheros USB Wi-Fi dongle, but I should soon. In spite of that, a while ago I began trying to convert athn(4) for USB – first I tried without usbwifi (on a branch in the git repository, and here). I also tried with usbwifi, changing sc_ic to a pointer (probably not the best way to go about it; that is on the branch "usbwifi" and here). Of course neither are at all tested at this point; if the former doesn't work, it would probably be easier to fix. Both compiled and PCI athn still works on the latter.


Rate adaption

Yesterday added calls to ratectl to athn. Now I realized that ni_txrate has changed in meaning (from being an index to rs_rates to the rate itself) and fixed ath(4) and athn(4) accordingly.


Testing

Here is some code to help confirm get_radiocaps behaves the same on the old & new wifi stack. It may not be perfect but there is a slim chance someone could take advantage of it or improve it somehow.

static void
chan_flags2string(char *str, uint32_t flags)
{
	const char *flag_names[] = {
		"PRIV0", "PRIV1", "PRIV2", "PRIV3", "TURBO", "CCK", "OFDM",
		"2GHZ", "5GHZ", "PASSIVE", "DYN", "GFSK", "GSM", "STURBO",
		"HALF", "QUARTER", "HT20", "HT40U", "HT40D", "DFS",
		"4MSXMIT", "NOADHOC", "NOHOSTAP", "11D", "VHT20", "VHT40U",
		"VHT40D", "VHT80", "VHT80_80", "VHT160"
	};
	int pow = nitems(flag_names), i = 0;
	memset(str, 0, 200);
	do {
		pow--;
		if (flags & (1 << pow)) {
			if (i != 0) str[i++] = ',';
			strcat(str, flag_names[pow]);
			i += strlen(flag_names[pow]);
		}
	} while (pow > 0);
	str[i] = 0;
}

static void
display_channels(struct ieee80211com *ic)
{
#define NEWSTACK
	char flagstr[200];

	printf("Channels by IEEE number:\n");
#ifdef NEWSTACK
	struct {uint16_t freq; uint32_t flags;} byieee[256] = {0};
	for (int i = 0; i < ic->ic_nchans; i++) {
		int n = ic->ic_channels[i].ic_ieee;
		byieee[n].freq = ic->ic_channels[i].ic_freq;
		byieee[n].flags |= ic->ic_channels[i].ic_flags;
	}
	for (int i = 0; i < 256; i++) {
		if (byieee[i].flags != 0) {
			chan_flags2string(flagstr, byieee[i].flags);
			printf("%d\t%d\t%x\t%s\n", i,
				byieee[i].freq,
				byieee[i].flags, flagstr);
		}
	}
#else
	for (int i = 0; i < IEEE80211_CHAN_MAX; i++) {
		if (ic->ic_channels[i].ic_freq != 0) {
			chan_flags2string(flagstr, ic->ic_channels[i].ic_flags);
			printf("%d\t%d\t%x\t%s\n", i,
				ic->ic_channels[i].ic_freq,
				ic->ic_channels[i].ic_flags, flagstr);
		}
	}
#endif

	printf("Channels per mode:\n");
#ifdef NEWSTACK
	for (int i = 0; i < ic->ic_nchans; i++) {
		chan_flags2string(flagstr, ic->ic_channels[i].ic_flags);
		printf("%d\t%d\t%x\t%s\n",
			ic->ic_channels[i].ic_ieee,
			ic->ic_channels[i].ic_freq,
			ic->ic_channels[i].ic_flags, flagstr);
	}
#else
#define ADDPERMODE(_i, _freq, _flags)		\
	do {					\
		permode[nchans].ieee = _i;	\
		permode[nchans].freq = _freq;	\
		permode[nchans].flags = _flag;	\
		nchans++;			\
	} while (0);
	struct {
		uint8_t ieee;
		uint32_t flags;
		uint16_t freq;
	} *permode;
	int nchans = 0;
	permode = kmem_zalloc(1024*sizeof(*permode), KM_SLEEP);
	for (int i = 0; i < IEEE80211_CHAN_MAX; i++) {
		uint16_t freq = ic->ic_channels[i].ic_freq;
		uint32_t flags = ic->ic_channels[i].ic_flags;
		if (IEEE80211_IS_CHAN_A(&ic->ic_channels[i])) {
			ADDPERMODE(i, freq, IEEE80211_CHAN_A);
			if (flags & IEEE80211_CHAN_TURBO)
				ADDPERMODE(i, freq,
				    IEEE80211_CHAN_A | IEEE80211_CHAN_TURBO);
		}
		if (IEEE80211_IS_CHAN_B(&ic->ic_channels[i])) {
			ADDPERMODE(i, freq, IEEE80211_CHAN_B);
		}
		if (IEEE80211_IS_CHAN_G(&ic->ic_channels[i])
		    || IEEE80211_IS_CHAN_G(&ic->ic_channels[i])) {
			ADDPERMODE(i, freq, IEEE80211_CHAN_G);
			if (flags & IEEE80211_CHAN_TURBO)
				ADDPERMODE(i, freq,
				    IEEE80211_CHAN_G | IEEE80211_CHAN_TURBO);
		}
	}
	for (int i = 0; i < nchans; i++) {
		chan_flags2string(flagstr, permode[i].flags);
		printf("%d\t%d\t%x\t%s\n",
			permode[i].ieee,
			permode[i].freq,
			permode[i].flags, flagstr);
	}
	kmem_free(permode, 1024*sizeof(*permode));
#undef ADDPERMODE
#endif

#undef NEWSTACK
}

Rate adaption – ath(4)

Got athrate-amrr & athrate-onoe working (before I only had athrate-sample). This was easy enough because everything rate-adaption-related is in a single driver-specific module (though ultimately it will probably be redundant because of net80211).

This commit could be relevant to athrate-sample (then again net80211 may make it obsolete).


athn(4) – starting over based on existing patch

Martin (my mentor) recently let me know of an existing patch for athn(4) written by James Brown and suggested I could use parts or merge with my effort. Since it's written by someone more knowledgeable I'll take the latter path. Some of the changes are very similar; others are just more sensible, or reflect stuff of which I was ignorant. So I changed it enough to compile and work as before, merging it with a bit of my previous work. From here I can look into rate adaption, etc.

Also made a few changes to ath(4) – ideally hostapd(8) would work (if that's possible at this point), but not yet.


Some gradual changes – ath(4)/athn(4)

I've made a few gradual changes to athn(4). ieee80211_tx_complete is now called, and presumably athn_get_radiocaps behaves now as did athn_get_chanlist before. It works on 2.4GHz and 5GHz.

Also fixed (I believe) [io]errors on both drivers.


athn(4)

athn(4) appears to be in a working state after some more changes. The changes will need to be cleaned up, and I haven't yet figured out what to do with rate adaption.


ath(4)/athn(4)

Made some minor changes to ath(4). Since 27/06 I've worked a bit on converting athn(4) (on a separate git branch) – so far it compiles and boots but creating a VAP causes a panic.

Patch of all progress on ath(4)


encap/caps – ath(4)

Turns out I hadn't actually fixed that. It happened whenever ath_start was invoked by the ath_tx_proc family (without IEEE80211_TX_LOCK) and then called ieee80211_encap. Removing the call to ieee80211_encap would fix this as the call was hoisted up into net80211; the only reason it worked previously was because I mixed up ic_caps with ic_cryptocaps.

All of that is fixed now, except that if the code sets ic_cryptocaps then wifi no longer works; so for now it's 0. No crashes yet since.


Fixes – ath(4)

I couldn't reproduce the crash I thought was caused by wpa_supplicant mentioned in the last post. However, it did tend to crash when streaming video for more than a few minutes. M_CLEARCTX seemed to fix this, and it hasn't crashed since (ignoring mdnsd). Reenabling ath_chan_change fixed the "bad rate" messages reported by athrate-sample. But reenabling ath_watchdog results in occasional "device timeouts."


Wi-Fi working – ath(4)

After making the code regarding getting channels behave (mostly) correctly, adding IEEE80211_C_WPA to ic_caps, and restoring/adjusting athrate-sample.c, wpa_supplicant connects to my network and Wi-Fi mostly works(!) ignoring some kernel messages about "bad rates." Doubtless some things still need to be corrected (and other things need to be cleaned up, fixed, and made less hacky). mdnsd (Bonjour) causes an assertion to fail during some ioctl. wpa_supplicant also sometimes seems to crash the system based on the exact sequence of commands used to set Wi-Fi up, so I'll also need to perform some debugging.


Creating a VAP & scanning

The code is now on GitHub. Re-enabling code, adjusting as necessary and incorporating some bits from FreeBSD brought the driver to the point (around last Friday [per my report on tech-net]) where one can create a VAP and scan for networks. Since then adding ath_recv_mgmt back seems to have somehow made scanning faster (…or maybe it didn't really). I have not yet been able to join a network with wpa_supplicant, and there's still a lot that I might need to try to better understand.

Successfully initialized wpa_supplicant
ioctl[SIOCS80211, op=26, val=3, arg_len=0]: Operation not supported
wpa_driver_bsd_scan: failed to set wpa: Operation not supported
wlan0: CTRL-EVENT-SCAN-FAILED ret=-1 retry=1
<etc.>

Getting started trying stuff out

Trying to build athn(4) on the wifi topic results in a lot of errors in multiple arnXXXX.c files. Trying to build ath(4) on the other hand seemed to result in fewer errors. So it might be better to convert ath first.

I substituted ic_macaddr for ic_myaddr; made newstate take a VAP; replaced ic->ic_ifp->if_softc with ic->ic_softc; changed some other references; haphazardly disabled swathes of (important) code (a lot of important functions); and added a few random stopgaps. Eventually it compiled without error; ieee80211_ifattach panicked but taking the contents of rtwn_get_radiocaps served as a stopgap. Thus it was able to boot, and ath0 was shown by sysctl.

Creating a VAP with ifconfig is (unsurprisingly) still far away (it reported "STA mode not supported").


Basic differences in new drivers

As noted by Martin's guide, the main difference in new style drivers is the VAP concept. To that end FreeBSD ath(4) defines struct ath_vap, deriving from struct ieee80211vap. So whereas before it would look something like this:

Old representation

Now for any driver, the representation will look more or less like this:

New representation

By comparing with FreeBSD ath(4) & the new rtwn driver:


Basic differences (ath vs athn)

ath(4) and athn(4) support somewhat different sets of hardware. The most obvious difference in implementation is that ath(4) uses the Atheros HAL. This was initially a blob but open-sourced in 2009. According to the FreeBSD wiki,

The Atheros HAL is an abstraction layer which attempts to hide much of the card specific configuration from the rest of the atheros wireless driver.

The HAL takes care of the MAC, baseband and radio. It exports a set of routines which allow the atheros wireless driver to do certain tasks (set channel, configure TX queue, queue TX packet, queue RX buffer, setup interrupts, do calibration, set/get TX power, configure beacon parameters, enter power saving/sleep mode, etc.)

It doesn't seem that it'll require any changes (except ah_osdep.c).

So, for example, athn.c has routines that directly change particular registers (using AR_WRITE i.e. sc_ops.write which might resolve to athn_pci_write), the lowest-level routines being implemented in if_athn_pci.c & if_athn_cardbus.c, whereas ath.c uses the higher-level routines already provided by the HAL.

ath_softc and athn_softc are the most important structs in both, which, presumably, will have a few mutexes on them when finished (as FreeBSD). ath(4) seems to be the more complicated of the two drivers.


Beginning GSoC 2023

NetBSD is undergoing a transition to a new Wi-Fi stack. My project is to convert the drivers athn(4) and if time allows ath(4).

This far I have ensured that the wifi branch builds and runs on the target netbook. All the unconverted drivers need to be disabled. Ethernet works. Presently it identifies as NetBSD 9.99.100.

To examine:

One of the most obvious differences in the converted drivers is the locking (mutex_init, mutex_enter, mutex_exit).