
BC-250 USB Wi-Fi¶
The driver for AIC8800D80 USB Wi-Fi sticks on the AMD BC-250 with Bazzite. The UGREEN AX900 and many
other small Wi-Fi 6 sticks use this chip, and Bazzite's kernel has no driver for it: the stick shows up, and no
wlan0 comes. The app builds the driver on the board for the running kernel, installs it with a system service
that loads it at every boot, and builds it again after a kernel update.
The driver is a kernel module, so this app changes the board. It is software-only: Remove unloads it and takes every file away, and the board is as before.
What you need¶
- An AIC8800D80 stick. It is sold under many names;
lsusbshows it asa69c:5723(or a nearby number) in storage mode and asa69c:8d80ora69c:8d81once switched. - The kernel's build tree (
kernel-devel),gcc,makeandgit. Bazzite's image has all four. The Dependency Checker shows them. - The network, once, for the sources: a shallow fetch of one commit, about 13 MB.
Using it¶
The top row shows the stick (storage mode, waiting for its firmware, or Wi-Fi mode), the chip (its revision and MCU as the loader reported it in the kernel log), the driver (built, or the installed branch) and the Wi-Fi interface the driver created.
- Driver branch: chosen from the chip's answer. A chip that reports MCU 1 takes
legacy-mcu1; MCU 0 takesmain. When the chip has not answered yet, the app proposesmain, and says what to do when the firmware upload then breaks off (see below). - Build: fetches the pinned sources into your home folder and builds the two modules against the running kernel, as your own user. The output shows in the window; it takes a minute or two. The sources come from a mirror of the driver's repository under the suite's own account, and from the original repository when the mirror does not answer. Newest sources (untested) fetches the branch's current head instead of the tested commit: for the day a new kernel breaks the pinned build and the fix is already upstream but not yet in a release of this app. The window marks such a driver as newest; the next release brings a tested pin again.
- Install…: copies the build and the sources to
/var/lib/bc250-usb-wifi, writes the stick's udev rule and the service, and starts the service, which loads the driver now. The app asks first, then asks for your password. A stick in storage mode is switched at the same time. - Connect in the desktop's network settings. The stick's firmware does WPA2, not WPA3: when your router
offers both on one network, set the connection to WPA2 (in the connection's Wi-Fi security, or
nmcli connection modify "<network>" wifi-sec.key-mgmt wpa-psk). With WPA3 chosen, the router rejects every attempt and the desktop keeps asking for the password.
Update… replaces the installed driver with the current build (another branch, or the service of a newer app version). Remove… unloads the driver and removes it, its service and its rule; the build in your home folder stays. What the service did shows the service's last run: what it built, loaded and ejected, and any error.
Wi-Fi adapters lists every Wi-Fi adapter on the board, of any chip, and whether the running kernel has a driver and firmware for it: plug a stick in and click the Refresh pill to see whether it works on Bazzite, before or instead of this app's driver. It is the same check as BC-250 Bazzite Test's test 53, and only reads.
The chip and its two firmware sets¶
The AIC8800D80 exists in two MCU revisions, and the loader prints which one it found before it uploads anything:
chip_id=7, chip_mcu_id = 1 in the kernel log. The newer firmware (SDK 5, the main branch) is larger than the
older MCU takes: its upload breaks off at address 0x170400, the stick gets no firmware, and it then answers
chip_id=0 until it is unplugged and plugged in again. The legacy-mcu1 branch carries the older firmware (SDK 3)
with a loader matched to it; on that MCU, Wi-Fi on both bands and Bluetooth through the kernel's own btusb work.
So, when the chip is unknown: install main; if the log then shows the failed upload, the app says so and
proposes legacy-mcu1; build and install that, and plug the stick in again. The app reads the chip's answer from
every boot the journal keeps, so after the first time it knows.
Compatibility¶
The driver is for the AIC8800 chip family, not for one stick. Its match tables claim AICSemi's two USB vendor IDs
(a69c and 368b) with the 8800, 8801, D80, D81, D40 and D41 product IDs, the DC, DE, DW, DLN and DWN siblings,
the D80N and D40N parts, the M80 and FC customer IDs, and the branded IDs of Tenda (U2, U11, U11 Pro, TX1U Nano)
and UGREEN (8d88). A stick with one of these IDs is claimed by the modules the app builds, whatever name it is
sold under. Wi-Fi-only sticks and Wi-Fi/Bluetooth combo sticks both work: the Bluetooth side goes to the kernel's
own btusb.
The firmware is what narrows it. The driver's sources carry firmware for six variants (8800, D80, D80N, D80X2, DC
and DLN); the app installs the AIC8800D80 set only, in the two MCU revisions described above. A stick with a
D80N, DC or DLN chip binds to the driver but needs a firmware set the app does not install, so it gets no
wlan0; its chip line in the kernel log says which chip it is.
What the window recognizes is narrower than the driver: the stick box knows the storage-mode IDs a69c:5721
to a69c:572c, the loader ID a69c:8d80 and the Wi-Fi ID a69c:8d81, and shows any other ID as unknown mode.
The udev rule that ejects the virtual disk is written for vendor a69c, so a 368b stick in storage mode is not
switched by the rule; eject on its disk does it by hand. Both vendor IDs count as a stick for the rest of the
window.
The modules are built on the board against the running kernel, so they follow Bazzite's kernel rather than one
version; the pinned sources build on kernels up to 7.3, and the service builds them again after a kernel update.
The driver's own tests, upstream, were on 6.1 and 6.16. Tested in the suite: the UGREEN AX900 (a69c, MCU 1,
legacy-mcu1) on the BC-250.
The service¶
bc250-usb-wifi.service runs driver.py load at every boot:
- when there are no modules for the running kernel, it builds them from the kept sources with the kernel's build tree (no network; a minute or two, in the background, with a desktop notification when done or when it failed);
- loads
cfg80211, then the loaderaic_load_fwwith the firmware folder as its parameter, thenaic8800_fdrv; - ejects the virtual disk of a stick that sits in storage mode, which switches it to loader mode; the udev rule does the same for a stick plugged in later.
The loader uploads the firmware, the stick re-enumerates in Wi-Fi mode, and aic8800_fdrv creates wlan0.
Stopping the service unloads the modules. Its log: journalctl -u bc250-usb-wifi.
Bazzite's /usr is read-only, so the modules cannot go where modprobe looks and the firmware cannot go under
/lib/firmware; the service loads them with insmod from /var/lib instead, and the driver's own firmware-path
parameter points at the folder there.
Files¶
~/.local/share/bc250-usb-wifi/src/<branch>/: the sources, at the pinned commit.~/.local/share/bc250-usb-wifi/build/<branch>/<kernel>/: the modules, the firmware folder and the build log./etc/bc250-usb-wifi/driver.pyanddriver.json: the service's helper and which branch is installed./etc/systemd/system/bc250-usb-wifi.serviceand/etc/udev/rules.d/90-bc250-usb-wifi.rules./var/lib/bc250-usb-wifi/src/<branch>/andmodules/<kernel>/: what the service builds from and loads./run/bc250-usb-wifi/state.json: the service's last run, which the app shows.~/.config/bc250-usb-wifi/bc250-usb-wifi.ini: the window's size and place, and a branch chosen by hand.
When it does not work¶
- The stick stays in storage mode: the udev rule ejects its virtual disk when the stick is plugged in and the
service does so at boot. By hand:
sudo eject /dev/sdX(the disklsblkshows with vendor AIC). - The chip box turns red: the firmware upload broke off. The chip needs the other branch; build and install it, then unplug the stick and plug it in again, so it starts afresh.
- No wlan0 after a kernel update: the service builds at boot; give it two minutes and watch for the
notification. When the build failed, the window shows the error and
journalctl -u bc250-usb-wifithe log. A kernel the pinned driver does not build on yet needs a newer pin (a new release of this app); until then, tick Newest sources, Build and Update: when the fix is upstream already, that build works. - The desktop keeps asking for the Wi-Fi password: the connection uses WPA3; set it to WPA2, see Using it.
- Bluetooth: the stick's Bluetooth comes up on the kernel's own
btusbdriver once the firmware runs; the app does nothing for it.
Install¶
Install it from the BC250 Bazzite Suite portal, or from a clone or release folder, as your own user:
./install.sh # app menu entry and Desktop icon
./install.sh --no-desktop-shortcut # app menu entry only
./install.sh --uninstall # remove the app and the driver's service; keeps its settings and builds
./install.sh --uninstall --purge # also remove its settings and the builds in your home folder
Installing and removing the driver needs pkexec and the system python3; Bazzite has both. Uninstalling the app
also unloads and removes the driver (with sudo).
License¶
GPL-3.0-or-later. See LICENSE. The driver it builds is shenmintao/aic8800d80, GPL-2.0-only, fetched at a pinned commit from the suite's mirror RobertoTorino/aic8800d80 or from the original; the firmware files in it are AICSemi's.