7.3 lands the last part of a two-commit series that converts several
cfg80211_ops cookie parameters from driver-invented output values to
values cfg80211 pre-assigns and passes in as input, and renames
probe_client to probe_peer. Building against 7.3 fails with 5 errors:
- cfg80211_probe_status() gained an MLO link_id parameter between cookie
and acked (kernel commit 010e955c203e). This driver has no MLO
support; -1 is the documented value for non-MLO.
- cfg80211_ops::probe_client was renamed to probe_peer (4ab9b637b94a).
- cfg80211_ops::mgmt_tx, ::remain_on_channel and ::probe_peer all
changed their cookie parameter from u64 * to u64 (914781c72813):
cfg80211 now pre-assigns the cookie before calling the driver instead
of the driver inventing one and returning it through the pointer.
The cookie change is more than a type fix. This driver invents its
mgmt_tx/remain_on_channel/probe_peer cookies from a skb pointer, an
internal roc_cookie_cnt counter, and an internal probe_id counter
respectively, and separately re-derives "the same" value later when
reporting completion. Once cfg80211 assigns the cookie itself, the
completion report must echo back the value cfg80211 gave the driver.
Two of the three ops are thin wrappers around a lower-level helper that
is also called from a purely-internal path with no cfg80211-assigned
cookie to honor: rwnx_start_mgmt_xmit() is reused by TDLS, and
rwnx_cfg80211_remain_on_channel_() starts the internal RoC from inside
mgmt_tx. Each of those helpers gets a bool (use_given_cookie / the
existing mgmt_roc_flag) so the internal callers keep inventing a cookie
exactly as before on every kernel, while the cfg80211 op wrappers honor
the pre-assigned value on 7.3+ and thread it through to the completion
report. probe_peer has no helper and no internal caller; the op stores
the cookie itself.
- rwnx_start_mgmt_xmit() gains use_given_cookie; the resulting cookie is
stored in a new rwnx_sw_txhdr::cookie and echoed back via
cfg80211_mgmt_tx_status(). Because sw_txhdr comes from a kmem_cache
and is never zeroed, every one of the five allocation sites
initialises the field -- to the skb pointer, which is exactly what the
completion path reported before the field existed -- so the
TXU_CNTRL_MGMT filter on the reporting side is not what keeps it safe.
- rwnx_cfg80211_remain_on_channel_()'s mgmt_roc_flag also selects the
cookie source, stored in a new rwnx_roc_elem::cookie that the
rwnx_msg_rx.c completion handlers read.
- rwnx_cfg80211_probe_client() stores the given cookie in a new
apm_probe_sta::cookie field for the async probe-status work.
Every gate this adds uses AICWF_CFG80211_VERSION_CODE, so an OpenWrt
backports build selects these paths from the wireless stack's version
rather than the kernel's, matching the existing 7.2 gates.
On kernels before 7.3 the compiled behaviour is identical to main with
two exceptions, both requested in review and both changing only the
order of a store relative to a queue/send: *cookie for probe_client is
taken from probe_id before queue_work() rather than after it, and the
RoC cookie is read from roc_cookie_cnt once, before rwnx_send_roc(),
rather than after it returns. In each case the value differs from
main's only if the worker/RX path has already completed and bumped the
counter in between -- a window in which main returns N+1 while having
already reported N to cfg80211. The reported and returned values now
agree by construction.
v2, addressing review:
- RoC: the cookie is decided and stored in roc_elem before the element
is published and rwnx_send_roc() is called, so an early channel-switch
indication cannot report an uninitialised cookie.
- probe_peer: the cookie is stored before queue_work(), so a worker
running immediately on another CPU reports the right one.
- Monitor injection: intended to initialise sw_txhdr->cookie in
rwnx_start_monitor_if_xmit(), but the store landed in
rwnx_start_xmit() instead, where it is never read. Fixed in v5.
- Pre-3.14: use_given_cookie is part of that signature too and both
old-signature callers pass false.
- Rebased onto the OpenWrt backport-version change.
v3:
- rwnx_main.h gated the probe_client prototype on LINUX_VERSION_CODE
while rwnx_main.c gated the definition on AICWF_CFG80211_VERSION_CODE.
Identical on a native build, but a backports build with the two
decoupled would see a u64 * prototype against a u64 definition.
- rwnx_cfg80211_remain_on_channel() no longer duplicates the whole call
to the shared helper just to change one argument; only the cookie
pointer is selected by version.
v4:
- rwnx_cfg80211_remain_on_channel_() no longer writes *cookie before
rwnx_send_roc(). The cookie is snapshotted into a local, roc_elem
keeps its early assignment, and *cookie is written where it was
before this patch: inside the error == 0 branch.
v5:
- The monitor-injection initialisation is now where it was always
meant to be: rwnx_start_monitor_if_xmit(), between the skb
assignment and raw_frame = 1, ungated, since that path runs on every
kernel with CONFIG_RWNX_MON_XMIT. The value is (unsigned long)skb_mgmt,
which is what rwnx_txdatacfm() reported for these frames before.
- The remaining allocation sites that did not set the field --
intf_tx() and rwnx_probersp_work() -- now do, so all five do.
- The internal RoC's block-local cookie in rwnx_cfg80211_mgmt_tx() is
renamed roc_cookie: on 7.3 it had come to shadow a same-typed
parameter with a different meaning.
- Comment fixes: the rwnx_start_mgmt_xmit() kerneldoc documents
use_given_cookie; the probe_peer comment states the 7.3 contract; the
two #endif trailers that close chains now opening on the 7.3 gate say
so; the unused rx_addr is explained on the 7.3 arm as well.
- Rebased onto main (set_monitor_channel on 6.12 stable).
Verified against real linux-cachyos-bc250 (7.2.6) and linux-cachyos-rc-bc250
(7.3-rc4) headers with LLVM=1, the way DKMS builds them, asserting on exit
code and module count:
upstream main @ 7.2.6 rc=0, 3 modules, 13 warnings
upstream main @ 7.3-rc4 rc=2 - the errors this patch fixes
this branch @ 7.2.6 rc=0, 3 modules, 13 warnings
this branch @ 7.3-rc4 rc=0, 3 modules, 13 warnings
warning sets on 7.2.6: identical, and none names a function this
patch touches.
The backports configuration is not something I can build here, so the
header/definition agreement in v3 rests on both sites using the same
macro rather than on a test.
AIC8800 Linux Wi-Fi and Bluetooth Driver
This driver is for the AIC8800D80 chipset, supported by devices such as the Tenda U11 and AX913B.
Important
Choose the branch for your hardware revision before installing. If the driver log reports
chip_id=7, chip_mcu_id=1on an AIC8800D80 or AIC8800DC/DW device, use thelegacy-mcu1branch. It contains the matched legacy firmware and loader profile required to avoid the deterministic firmware upload timeout at0x170400. Usemainwhenchip_mcu_id=0or when the MCU revision is unknown.Hardware revision determines the firmware branch. MCU1 devices should use
legacy-mcu1, which also initializes Bluetooth for the kernel's standardbtusbdriver. After switching branches, rerunsudo ./install.shand reboot; switching the Git branch alone does not replace the firmware already installed under/lib/firmware.
Added support for devices with Vendor ID 368B (tested).
Tested on Linux kernel 6.16 with Ubuntu 25.04 and 6.1.0.27 with Debian 12.
Note
Maintained branches: This repository now maintains two hardware branches:
mainforchip_mcu_id=0or an unknown MCU revision, andlegacy-mcu1forchip_mcu_id=1. Wi-Fi, the kernel's standardbtusbBluetooth path, and the device-scoped ZLP support are integrated into both branches. The former separatebluetoothbranch is retired and should no longer be used.
The same driver supports Wi-Fi-only adapters and Wi-Fi/Bluetooth combo
adapters. On combo devices, aic_load_fw uploads the AIC firmware and the
standard Linux btusb driver handles the Bluetooth HCI interface. The obsolete
custom aic_btusb module is not used.
USB device 368b:8d81 also uses the bundled aic_zlp_quirk companion module.
It adds the Bluetooth ACL bulk TX zero-length-packet behavior validated in
issue #63, while leaving
the distribution's original btusb.ko installed and bound to the device. The
quirk is filtered to that VID:PID and fails closed when the required kernel
probe support is unavailable.
Disclaimer
I did not develop this software, The code is sourced from the Tenda U11 driver. I only made some modifications to the code to adapt it to newer kernel versions. Apart from compilation issues, I am unable to address other problems.
Attention
Before installing the driver, delete all aic8800-related folders under /lib/firmware. Using an incorrect firmware version may cause the system to freeze.
Pandora 88M80 mode switching
Pandora 88M80 adapters that initially appear as USB device 1111:1111 are
automatically switched to a69c:8d80 by sending the required F3 then F2
commands. Existing users must rerun sudo ./install.sh to replace the installed
usb_modeswitch configuration, then unplug the adapter completely and plug it
back in.
If automatic switching does not occur, try the same sequence manually:
sudo usb_modeswitch -v 1111 -p 1111 \
-M "555342438765432100000000000010fd0000000000000000000000000000f3" \
-2 "555342438765432100000000000010fd0000000000000000000000000000f2"
This only addresses switching from 1111:1111 to a69c:8d80. If the device
has reached a69c:8d80 but firmware startup still fails, see
issue #79.
Installation Steps
Method 1: Quick Installation (Recommended)
Method 2: Manual Installation
Copy udev rules:
Copy the aic.rules file to /usr/lib/udev/rules.d/:
sudo cp aic.rules /usr/lib/udev/rules.d/
Copy firmware:
Copy the firmware directories from ./fw to /lib/firmware/:
sudo cp -r ./fw/aic8800* /lib/firmware/
Navigate to the driver directory:
Change to the drivers/aic8800 directory:
cd ./drivers/aic8800
Compile and Install the Driver:
First, compile the driver:
make
Then, install the driver:
sudo make install
For any kernel updates, you'll need to reinstall the driver:
make clean
make
sudo make install
Load the Driver
After installation, load the driver with the following command:
sudo modprobe aic8800_fdrv
Verify the Module is Active
Check if the module is loaded correctly:
lsmod | grep aic
You should see output similar to:
aic8800_fdrv 536576 0
cfg80211 1146880 1 aic8800_fdrv
aic_load_fw 69632 1 aic8800_fdrv
usbcore 348160 10 xhci_hcd,ehci_pci,usbhid,usb_storage,ehci_hcd,xhci_pci,uas,aic_load_fw,uhci_hcd,aic8800_fdrv
After that, plug in your USB wireless network card.
Verify Wi-Fi Device is Active
To check if the Wi-Fi interface is recognized, run:
iwconfig
If the device is still not active, check the kernel logs for any errors related to the driver:
sudo dmesg
Bluetooth on Combo Adapters
Bluetooth support does not require a separate AIC transport module. After
aic_load_fw initializes a combo adapter, the kernel automatically binds its
Bluetooth interface to btusb. A Wi-Fi-only adapter does not expose that
interface, so the Bluetooth path remains inactive.
Verify the expected modules and controller with:
lsmod | grep -E 'aic_load_fw|aic8800_fdrv|aic_zlp_quirk|btusb'
lsusb -t
bluetoothctl list
To scan after a controller appears:
bluetoothctl
power on
scan on
If Bluetooth is missing or reports HCI timeouts, run the read-only diagnostic script and attach its output together with the current boot log:
chmod +x diagnose_bt.sh
sudo ./diagnose_bt.sh
sudo journalctl -k -b --no-pager
The installer removes active references to the retired aic_btusb integration.
It does not force-load btusb or globally change the Bluetooth rfkill state;
normal kernel device matching and the user's system policy remain in control.
For 368b:8d81, verify the ZLP hook and its injection counter while Bluetooth
traffic is active:
cat /sys/module/aic_zlp_quirk/parameters/hook
cat /sys/module/aic_zlp_quirk/parameters/injections
The Wi-Fi-reset recovery behavior tracked in issue #53 remains a known limitation: after an airplane-mode or hotspot reset, Bluetooth may require a physical unplug/replug of the adapter.