diff --git a/.gitattributes b/.gitattributes index 1ff2de7..7434425 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,3 +2,10 @@ fw/aic8800D80/*.txt text eol=lf fw/aic8800D80/*.bin binary fw/aic8800DC/*.txt text eol=lf fw/aic8800DC/*.bin binary +/.gitattributes text eol=lf +drivers/aic8800/aic_zlp_quirk/*.c text eol=lf +drivers/aic8800/aic_zlp_quirk/Makefile text eol=lf +tests/issue63-zlp-quirk/*.conf text eol=lf +tests/issue63-zlp-quirk/*.md text eol=lf +tests/issue63-zlp-quirk/*.sh text eol=lf +tests/issue63-zlp-quirk/Makefile text eol=lf diff --git a/INSTALL_SCRIPT.md b/INSTALL_SCRIPT.md index 9eda17c..8ed3c41 100644 --- a/INSTALL_SCRIPT.md +++ b/INSTALL_SCRIPT.md @@ -2,12 +2,19 @@ ## Overview -This automated installation script (`install.sh`) simplifies the process of installing the AIC8800D80 WiFi driver on Linux systems. +This automated installation script (`install.sh`) installs the AIC8800 Wi-Fi +driver and firmware loader on Linux systems. Wi-Fi/Bluetooth combo adapters use +the standard kernel `btusb` driver after firmware initialization. For USB +device `368b:8d81`, it also installs a device-scoped ACL bulk TX ZLP companion +module without replacing the distribution's `btusb.ko`. - Automatic Secure Boot detection - Automatic dependency installation - Automatic driver compilation and installation - Automatic module loading on boot +- Automatic cleanup of obsolete `aic_btusb` configuration +- Automatic `aic_zlp_quirk` handling for the validated `368b:8d81` device +- Wi-Fi-only and Wi-Fi/Bluetooth combo adapter support from the same branch - Comprehensive error handling - Colored output and logging - Compatible with Ubuntu, Debian, Fedora, and derivatives (DKMS supported `dkms.conf`) @@ -37,4 +44,4 @@ This script is used to diagnose build issues with the AIC8800D80 driver. Useful ```bash # Make the script executable and run it chmod +x diagnostic_build.sh && sudo ./diagnostic_build.sh -``` \ No newline at end of file +``` diff --git a/README.md b/README.md index 47f9694..9fdcc2a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# AIC8800 Linux Driver +# AIC8800 Linux Wi-Fi and Bluetooth Driver This driver supports AIC8800-family chipsets used by devices such as the Tenda U11, AX913B, and TP-Link Archer TX1U Nano. > **Legacy MCU revision 1 branch:** You are viewing `legacy-mcu1`. This branch @@ -12,14 +12,26 @@ This driver supports AIC8800-family chipsets used by devices such as the Tenda U > `chip_mcu_id=0` hardware or when the MCU revision is unknown. See the > [D80](tests/issue58-mcu1-legacy-fw/README.md) and > [DC/DW](tests/issue71-mcu1-v3-profile/README.md) support notes before installing. +> +> After switching branches, rerun `sudo ./install.sh` and reboot; switching the +> Git branch alone does not replace 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. -> **Bluetooth support:** This branch initializes the integrated Bluetooth -> controller for the kernel's standard `btusb` driver. It does not contain or -> install `aic_btusb`. +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](https://github.com/shenmintao/aic8800d80/issues/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. > [!NOTE] > **Bluetooth branch retirement:** Do not switch MCU1 hardware to the separate @@ -44,18 +56,18 @@ Before installing the driver, delete all aic8800-related folders under /lib/firm #### Method 2: Manual Installation #### Copy udev rules: -Copy the aic.rules file to /lib/udev/rules.d/: +Copy the aic.rules file to /usr/lib/udev/rules.d/: ```bash -sudo cp aic.rules /lib/udev/rules.d/ +sudo cp aic.rules /usr/lib/udev/rules.d/ ``` #### Copy firmware: -Copy the aic8800D80 folder from ./fw to /lib/firmware/: +Copy the firmware directories from `./fw` to `/lib/firmware/`: ```bash -sudo cp -r ./fw/aic8800D80 /lib/firmware/ +sudo cp -r ./fw/aic8800* /lib/firmware/ ``` #### Navigate to the driver directory: @@ -122,3 +134,52 @@ If the device is still not active, check the kernel logs for any errors related 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: + +```bash +lsmod | grep -E 'aic_load_fw|aic8800_fdrv|aic_zlp_quirk|btusb' +lsusb -t +bluetoothctl list +``` + +To scan after a controller appears: + +```bash +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: + +```bash +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: + +```bash +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](https://github.com/shenmintao/aic8800d80/issues/53) remains a known +limitation: after an airplane-mode or hotspot reset, Bluetooth may require a +physical unplug/replug of the adapter. + diff --git a/aic.rules b/aic.rules index ac85c00..31820c3 100644 --- a/aic.rules +++ b/aic.rules @@ -1,3 +1,4 @@ +# AIC8800 USB mass-storage mode switch rules KERNEL=="sd*", ATTRS{idVendor}=="a69c", ATTRS{idProduct}=="5721", SYMLINK+="aicudisk", RUN+="/usr/bin/eject /dev/%k" KERNEL=="sd*", ATTRS{idVendor}=="a69c", ATTRS{idProduct}=="5723", SYMLINK+="tendaudisk", RUN+="/usr/bin/eject /dev/%k" KERNEL=="sd*", ATTRS{idVendor}=="a69c", ATTRS{idProduct}=="5724", SYMLINK+="ugreenax900", RUN+="/usr/bin/eject /dev/%k" @@ -7,3 +8,6 @@ KERNEL=="sd*", ATTRS{idVendor}=="a69c", ATTRS{idProduct}=="5727", SYMLINK+="ten KERNEL=="sd*", ATTRS{idVendor}=="a69c", ATTRS{idProduct}=="572a", SYMLINK+="tendaudiskv5", RUN+="/usr/bin/eject /dev/%k" KERNEL=="sd*", ATTRS{idVendor}=="a69c", ATTRS{idProduct}=="572c", SYMLINK+="cudydiskv2", RUN+="/usr/bin/eject /dev/%k" KERNEL=="sd*", ATTRS{idVendor}=="a69c", ATTRS{idProduct}=="572f", SYMLINK+="aicudisk572f", RUN+="/usr/bin/eject /dev/%k" + +# AIC8800D80 "Pandora" clone: switch 1111:1111 to its operational USB ID. +ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="1111", ATTR{idProduct}=="1111", RUN+="/bin/sh -c 'if [ -x /usr/sbin/usb_modeswitch ]; then exec /usr/sbin/usb_modeswitch -c /etc/usb_modeswitch.d/1111:1111; elif [ -x /usr/bin/usb_modeswitch ]; then exec /usr/bin/usb_modeswitch -c /etc/usb_modeswitch.d/1111:1111; fi'" diff --git a/bazzite/README.md b/bazzite/README.md index 9b572b0..6efd2b0 100644 --- a/bazzite/README.md +++ b/bazzite/README.md @@ -56,7 +56,11 @@ sudo rpm-ostree install "$rpm_path" sudo systemctl reboot ~~~ -After reboot, the Wi-Fi module and firmware loader will be available. +After reboot, the Wi-Fi module and firmware loader will be available. On combo +adapters, Bluetooth is handled by the standard `btusb` kernel module after +firmware initialization. USB device `368b:8d81` also autoloads the packaged +`aic_zlp_quirk` companion module for its required Bluetooth ACL bulk TX zero +packet behavior; the distribution's `btusb` module is not replaced. This RPM is built for the kernel reported by `uname -r`. Rebuild and reinstall it after a Bazzite kernel upgrade. diff --git a/bazzite/aic8800d80.spec b/bazzite/aic8800d80.spec index 62c6ec1..4284101 100644 --- a/bazzite/aic8800d80.spec +++ b/bazzite/aic8800d80.spec @@ -1,11 +1,11 @@ -%global commit d10bc52903f30a45e49777fd8254765403c9eb64 +%global commit 51b7b6e72989afe4d21f52e55b70f5a4d6b21e5b %global shortcommit %(echo %{commit} | cut -c1-7) %{!?kver:%global kver %(uname -r)} Name: aic8800d80 Version: %{shortcommit} Release: 1%{?dist} -Summary: AIC8800D80 USB Wi-Fi driver +Summary: AIC8800 USB Wi-Fi, Bluetooth firmware, and ZLP quirk driver License: GPL-2.0-only URL: https://github.com/shenmintao/aic8800d80 @@ -22,9 +22,11 @@ Requires(post): /usr/sbin/depmod Requires(postun): /usr/sbin/depmod %description -Out-of-tree AIC8800D80 USB Wi-Fi driver with udev mode-switch rules and all -firmware variants shipped by the upstream repository. This main-branch package -does not install a custom Bluetooth transport driver. +Out-of-tree AIC8800 USB driver with Wi-Fi support, Bluetooth firmware loading, +udev mode-switch rules, and all firmware variants shipped by the upstream +repository. Combo adapters use the standard Linux btusb transport driver. A +device-scoped companion module supplies the required ACL bulk TX ZLP behavior +for USB device 368b:8d81 without replacing the distribution btusb module. %prep %autosetup -n %{name}-%{commit} @@ -44,6 +46,10 @@ install -Dpm0644 \ drivers/aic8800/aic_load_fw/aic_load_fw.ko \ %{buildroot}/usr/lib/modules/%{kver}/kernel/drivers/net/wireless/aic8800/aic_load_fw.ko +install -Dpm0644 \ + drivers/aic8800/aic_zlp_quirk/aic_zlp_quirk.ko \ + %{buildroot}/usr/lib/modules/%{kver}/kernel/drivers/bluetooth/aic8800/aic_zlp_quirk.ko + install -Dpm0644 \ aic.rules \ %{buildroot}/usr/lib/udev/rules.d/90-aic8800-mode-switch.rules @@ -65,11 +71,25 @@ cp -a fw/aic8800* %{buildroot}/usr/lib/firmware/ %dir /usr/lib/modules/%{kver}/kernel/drivers/net/wireless/aic8800 /usr/lib/modules/%{kver}/kernel/drivers/net/wireless/aic8800/aic8800_fdrv.ko /usr/lib/modules/%{kver}/kernel/drivers/net/wireless/aic8800/aic_load_fw.ko +/usr/lib/modules/%{kver}/kernel/drivers/bluetooth/aic8800/aic_zlp_quirk.ko /usr/lib/udev/rules.d/90-aic8800-mode-switch.rules %config(noreplace) /etc/usb_modeswitch.d/1111:1111 /usr/lib/firmware/aic8800* %changelog +* Sat Jul 25 2026 Shen Mintao - 51b7b6e-1 +- Package the unified installer and ZLP quirk with the legacy MCU1 profiles. +- Preserve the matched D80 and DC/DW firmware and loader combinations. + +* Sat Jul 25 2026 Shen Mintao - 13baa9b-1 +- Unify Wi-Fi and standard-btusb Bluetooth support in one package. +- Add the device-scoped 368b:8d81 ACL bulk TX ZLP companion module. +- Clean up obsolete aic_btusb and issue #63 diagnostic installations. + +* Fri Jul 24 2026 Shen Mintao - 88dbc0a-1 +- Use the unified Wi-Fi and Bluetooth installer/package description. +- Keep Bluetooth transport on the standard Linux btusb driver. + * Tue Jul 14 2026 Shen Mintao - d10bc52-1 - Build the current main branch without the legacy custom Bluetooth module. - Package all firmware variants and current mode-switch rules. diff --git a/diagnose_bt.sh b/diagnose_bt.sh new file mode 100644 index 0000000..2448481 --- /dev/null +++ b/diagnose_bt.sh @@ -0,0 +1,160 @@ +#!/bin/bash +# Diagnose Bluetooth on AIC8800 Wi-Fi/Bluetooth combo adapters. +# +# aic_load_fw initializes the device and uploads the AIC firmware. The +# standard in-kernel btusb driver must then bind to the Bluetooth HCI USB +# interface. The removed aic_btusb module must not be installed or loaded. + +echo "=== AIC8800 Bluetooth diagnostics ===" +echo "" + +echo "1. AIC USB devices:" +if command -v lsusb >/dev/null 2>&1; then + lsusb | grep -iE 'a69c|368b|3625' || echo " No known AIC USB ID found" +else + echo " lsusb is not installed" +fi +echo "" + +echo "2. USB interfaces and bound drivers:" +bt_interface_found=false +btusb_bound=false +zlp_target_found=false +for dev in /sys/bus/usb/devices/*; do + [ -f "$dev/idVendor" ] || continue + [ -f "$dev/idProduct" ] || continue + + vid=$(cat "$dev/idVendor" 2>/dev/null) + pid=$(cat "$dev/idProduct" 2>/dev/null) + case "$vid" in + a69c|368b|3625) ;; + *) continue ;; + esac + + echo " Device: $vid:$pid" + if [ "$vid" = "368b" ] && [ "$pid" = "8d81" ]; then + zlp_target_found=true + fi + for intf in "$dev"/*:*; do + [ -d "$intf" ] || continue + class=$(cat "$intf/bInterfaceClass" 2>/dev/null) + subclass=$(cat "$intf/bInterfaceSubClass" 2>/dev/null) + protocol=$(cat "$intf/bInterfaceProtocol" 2>/dev/null) + driver=$(basename "$(readlink "$intf/driver" 2>/dev/null)" 2>/dev/null) + echo " $(basename "$intf"): ${class:-??}/${subclass:-??}/${protocol:-??} -> ${driver:-unbound}" + + if [ "$driver" = "btusb" ]; then + btusb_bound=true + bt_interface_found=true + elif [ "$class" = "e0" ] && [ "$subclass" = "01" ] && [ "$protocol" = "01" ]; then + bt_interface_found=true + fi + done +done +echo "" + +echo "3. Relevant kernel modules (combo expectation: aic_load_fw + btusb):" +lsmod | grep -E '^(aic_load_fw|aic8800_fdrv|aic_zlp_quirk|aic_btusb|btusb|bluetooth)[[:space:]]' || \ + echo " No related module is currently loaded" +echo "" + +echo "4. Bluetooth controllers:" +if command -v bluetoothctl >/dev/null 2>&1; then + bluetoothctl list 2>/dev/null || echo " No controller reported by bluetoothctl" +elif command -v hciconfig >/dev/null 2>&1; then + hciconfig -a 2>/dev/null || echo " No controller reported by hciconfig" +else + echo " BlueZ command-line tools are not installed" +fi +echo "" + +echo "5. Firmware, btusb, ZLP quirk, and HCI log messages:" +dmesg 2>/dev/null | grep -iE 'fw_patch|fw_adid|aicbt|aic_zlp_quirk|bluetooth|btusb|hci' | tail -80 || true +echo "" + +echo "6. Bluetooth rfkill state:" +if command -v rfkill >/dev/null 2>&1; then + rfkill list bluetooth 2>/dev/null || echo " No Bluetooth rfkill entry" +else + echo " rfkill is not installed" +fi +echo "" + +echo "7. Obsolete aic_btusb installation/configuration:" +legacy_refs_found=false +mapfile -t legacy_modprobe_files < <( + grep -RIlE '^[[:space:]]*(softdep|alias)[^#]*aic_btusb([[:space:]]|$)' \ + /etc/modprobe.d /run/modprobe.d /usr/local/lib/modprobe.d \ + /usr/lib/modprobe.d /lib/modprobe.d 2>/dev/null | \ + grep -vE '\.aic8800-backup$' || true +) +mapfile -t legacy_udev_files < <( + grep -RIl 'aic_btusb/new_id' \ + /etc/udev/rules.d /run/udev/rules.d \ + /usr/lib/udev/rules.d /lib/udev/rules.d 2>/dev/null | \ + grep -vE '\.aic8800-backup$' || true +) + +for legacy_file in "${legacy_modprobe_files[@]}"; do + echo " Obsolete modprobe directive: $legacy_file" + legacy_refs_found=true +done +for legacy_file in "${legacy_udev_files[@]}"; do + echo " Obsolete udev binding rule: $legacy_file" + legacy_refs_found=true +done +if lsmod | awk '{print $1}' | grep -qx 'aic_btusb'; then + echo " Obsolete aic_btusb module is loaded" + legacy_refs_found=true +fi +if modinfo -n aic_btusb >/dev/null 2>&1; then + echo " Obsolete module file: $(modinfo -n aic_btusb 2>/dev/null)" + legacy_refs_found=true +fi +if [ "$legacy_refs_found" = false ]; then + echo " No active aic_btusb module, directive, or binding rule found (expected)" +fi +echo "" + +echo "8. Device-scoped Bluetooth ACL ZLP quirk:" +if [ "$zlp_target_found" = true ]; then + if [ -d /sys/module/aic_zlp_quirk ]; then + zlp_hook="unknown" + zlp_injections="unavailable" + [ -r /sys/module/aic_zlp_quirk/parameters/hook ] && \ + zlp_hook=$(cat /sys/module/aic_zlp_quirk/parameters/hook) + [ -r /sys/module/aic_zlp_quirk/parameters/injections ] && \ + zlp_injections=$(cat /sys/module/aic_zlp_quirk/parameters/injections) + echo " Target 368b:8d81 present" + echo " aic_zlp_quirk loaded: yes" + echo " active hook: $zlp_hook" + echo " ZLP injections: $zlp_injections" + else + echo " Target 368b:8d81 present, but aic_zlp_quirk is not loaded" + fi +else + echo " Target 368b:8d81 not present; quirk is not required" +fi +echo "" + +echo "=== Assessment ===" +if ! lsmod | awk '{print $1}' | grep -qx 'aic_load_fw'; then + echo "- aic_load_fw is not loaded; run: sudo modprobe aic8800_fdrv" +fi +if [ "$bt_interface_found" = true ] && [ "$btusb_bound" = false ]; then + echo "- A Bluetooth USB interface exists but is not bound to btusb." + echo " Check for a btusb blacklist, then try: sudo modprobe btusb" +fi +if dmesg 2>/dev/null | grep -iE 'hci[0-9]+:.*(command|opcode|tx).*timed out|hci[0-9]+: link tx timeout' | tail -1 | grep -q .; then + echo "- HCI timeout detected. Confirm that aic_load_fw uploaded the firmware before btusb bound." + echo " Also check for obsolete aic_btusb configuration above." +fi +if [ "$bt_interface_found" = false ]; then + echo "- No Bluetooth HCI USB interface was found. This is normal for a Wi-Fi-only adapter." +fi +if [ "$zlp_target_found" = true ] && [ ! -d /sys/module/aic_zlp_quirk ]; then + echo "- Device 368b:8d81 requires the ACL ZLP quirk, but the module is not loaded." + echo " Try: sudo modprobe aic_zlp_quirk" +fi + +echo "=== Diagnostics complete ===" diff --git a/dkms.conf b/dkms.conf index ca060ed..4332d39 100644 --- a/dkms.conf +++ b/dkms.conf @@ -13,4 +13,9 @@ BUILT_MODULE_NAME[1]="aic_load_fw" BUILT_MODULE_LOCATION[1]="drivers/aic8800/aic_load_fw" DEST_MODULE_LOCATION[1]="/updates/dkms" +# Device-scoped standard-btusb ACL bulk TX ZLP quirk (368b:8d81 only) +BUILT_MODULE_NAME[2]="aic_zlp_quirk" +BUILT_MODULE_LOCATION[2]="drivers/aic8800/aic_zlp_quirk" +DEST_MODULE_LOCATION[2]="/updates/dkms" + AUTOINSTALL="yes" diff --git a/drivers/aic8800/Makefile b/drivers/aic8800/Makefile index 54e658f..ee68eee 100644 --- a/drivers/aic8800/Makefile +++ b/drivers/aic8800/Makefile @@ -1,8 +1,10 @@ CONFIG_AIC_LOADFW_SUPPORT := m CONFIG_AIC8800_WLAN_SUPPORT := m +CONFIG_AIC_ZLP_QUIRK := m obj-$(CONFIG_AIC_LOADFW_SUPPORT) += aic_load_fw/ obj-$(CONFIG_AIC8800_WLAN_SUPPORT) += aic8800_fdrv/ +obj-$(CONFIG_AIC_ZLP_QUIRK) += aic_zlp_quirk/ ########## config option ########## export CONFIG_USE_FW_REQUEST = n @@ -51,6 +53,7 @@ KVER ?= $(shell uname -r) KDIR ?= /lib/modules/$(KVER)/build PWD = $(shell pwd) MODDESTDIR = /lib/modules/$(KVER)/kernel/drivers/net/wireless/aic8800 +BTMODDESTDIR = /lib/modules/$(KVER)/kernel/drivers/bluetooth/aic8800 SUBARCH = $(shell uname -m | sed -e s/i.86/i386/ -e s/armv.l/arm/ -e s/aarch64/arm64/ -e s/loongarch64/loongarch/ -e s/loong64/loongarch/ -e s/riscv64/riscv/) ARCH ?= $(SUBARCH) CROSS_COMPILE ?= @@ -78,16 +81,20 @@ modules: install: mkdir -p $(MODDESTDIR) + mkdir -p $(BTMODDESTDIR) install -p -m 644 aic_load_fw/aic_load_fw.ko $(MODDESTDIR)/ install -p -m 644 aic8800_fdrv/aic8800_fdrv.ko $(MODDESTDIR)/ + install -p -m 644 aic_zlp_quirk/aic_zlp_quirk.ko $(BTMODDESTDIR)/ /sbin/depmod -a ${KVER} uninstall: rm -rfv $(MODDESTDIR)/aic_load_fw.ko rm -rfv $(MODDESTDIR)/aic8800_fdrv.ko + rm -rfv $(BTMODDESTDIR)/aic_zlp_quirk.ko /sbin/depmod -a ${KVER} clean: cd aic_load_fw/;make clean;cd .. cd aic8800_fdrv/;make clean;cd .. + cd aic_zlp_quirk/;make clean;cd .. rm -rf modules.order Module.symvers .modules.order.cmd .Module.symvers.cmd .tmp_versions/ diff --git a/drivers/aic8800/aic_zlp_quirk/Makefile b/drivers/aic8800/aic_zlp_quirk/Makefile new file mode 100644 index 0000000..8595f58 --- /dev/null +++ b/drivers/aic8800/aic_zlp_quirk/Makefile @@ -0,0 +1,18 @@ +ifneq ($(KERNELRELEASE),) + +obj-m += aic_zlp_quirk.o + +else + +KVER ?= $(shell uname -r) +KDIR ?= /lib/modules/$(KVER)/build + +.PHONY: all clean + +all: + $(MAKE) -C $(KDIR) M=$(CURDIR) modules + +clean: + $(MAKE) -C $(KDIR) M=$(CURDIR) clean + +endif diff --git a/drivers/aic8800/aic_zlp_quirk/aic_zlp_quirk.c b/drivers/aic8800/aic_zlp_quirk/aic_zlp_quirk.c new file mode 100644 index 0000000..122bb68 --- /dev/null +++ b/drivers/aic8800/aic_zlp_quirk/aic_zlp_quirk.c @@ -0,0 +1,229 @@ +// SPDX-License-Identifier: GPL-2.0 + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include + +#define AIC_USB_VENDOR_ID 0x368b +#define AIC_USB_PRODUCT_ID 0x8d81 +#define USB_BT_SUBCLASS 0x01 +#define USB_BT_PROTOCOL 0x01 + +static atomic64_t injection_count = ATOMIC64_INIT(0); +static const char *hook_name = "none"; + +static int injections_get(char *buffer, const struct kernel_param *kp) +{ + (void)kp; + + return scnprintf(buffer, PAGE_SIZE, "%lld\n", + (long long)atomic64_read(&injection_count)); +} + +static const struct kernel_param_ops injections_ops = { + .get = injections_get, +}; + +module_param_cb(injections, &injections_ops, NULL, 0444); +MODULE_PARM_DESC(injections, "Number of AIC ACL bulk OUT URBs modified"); + +static int hook_get(char *buffer, const struct kernel_param *kp) +{ + (void)kp; + + return scnprintf(buffer, PAGE_SIZE, "%s\n", hook_name); +} + +static const struct kernel_param_ops hook_ops = { + .get = hook_get, +}; + +module_param_cb(hook, &hook_ops, NULL, 0444); +MODULE_PARM_DESC(hook, "Active injection hook"); + +#if IS_ENABLED(CONFIG_KPROBES) + +enum aic_zlp_hook { + AIC_ZLP_HOOK_NONE, + AIC_ZLP_HOOK_BTUSB_RETURN, + AIC_ZLP_HOOK_USB_SUBMIT, +}; + +static enum aic_zlp_hook active_hook; + +static bool is_aic_bulk_out(const struct urb *urb) +{ + const struct usb_device *udev; + + if (!urb) + return false; + + udev = urb->dev; + if (!udev) + return false; + + if (le16_to_cpu(udev->descriptor.idVendor) != AIC_USB_VENDOR_ID || + le16_to_cpu(udev->descriptor.idProduct) != AIC_USB_PRODUCT_ID) + return false; + + return usb_pipetype(urb->pipe) == PIPE_BULK && + usb_pipeout(urb->pipe); +} + +static bool is_bluetooth_acl_endpoint(const struct urb *urb) +{ + const struct usb_endpoint_descriptor *ep; + struct usb_host_interface *alt; + struct usb_interface *intf; + unsigned int endpoint; + int i; + + if (!is_aic_bulk_out(urb)) + return false; + + intf = usb_ifnum_to_if(urb->dev, 0); + if (!intf) + return false; + + alt = READ_ONCE(intf->cur_altsetting); + if (!alt || + alt->desc.bInterfaceClass != USB_CLASS_WIRELESS_CONTROLLER || + alt->desc.bInterfaceSubClass != USB_BT_SUBCLASS || + alt->desc.bInterfaceProtocol != USB_BT_PROTOCOL) + return false; + + endpoint = usb_pipeendpoint(urb->pipe); + for (i = 0; i < alt->desc.bNumEndpoints; i++) { + ep = &alt->endpoint[i].desc; + if (usb_endpoint_is_bulk_out(ep) && + usb_endpoint_num(ep) == endpoint) + return true; + } + + return false; +} + +static void enable_zlp(struct urb *urb) +{ + long long count; + + if (urb->transfer_flags & URB_ZERO_PACKET) + return; + + urb->transfer_flags |= URB_ZERO_PACKET; + count = atomic64_inc_return(&injection_count); + + if (count == 1) + pr_info("enabled ZLP on the first 368b:8d81 ACL bulk OUT URB\n"); +} + +static int alloc_bulk_urb_ret_handler(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct urb *urb; + + (void)ri; + + urb = (struct urb *)regs_return_value(regs); + if (!is_aic_bulk_out(urb)) + return 0; + + enable_zlp(urb); + return 0; +} + +static struct kretprobe alloc_bulk_urb_probe = { + .kp.symbol_name = "btusb:alloc_bulk_urb", + .handler = alloc_bulk_urb_ret_handler, +}; + +static int usb_submit_urb_pre_handler(struct kprobe *p, struct pt_regs *regs) +{ + struct urb *urb; + + (void)p; + + urb = (struct urb *)regs_get_kernel_argument(regs, 0); + if (is_bluetooth_acl_endpoint(urb)) + enable_zlp(urb); + + return 0; +} + +static struct kprobe usb_submit_urb_probe = { + .symbol_name = "usb_submit_urb", + .pre_handler = usb_submit_urb_pre_handler, +}; + +static int __init aic_zlp_quirk_init(void) +{ + int ret; + + ret = register_kretprobe(&alloc_bulk_urb_probe); + if (!ret) { + active_hook = AIC_ZLP_HOOK_BTUSB_RETURN; + hook_name = "btusb:alloc_bulk_urb"; + pr_info("attached to system btusb for USB device 368b:8d81\n"); + return 0; + } + + pr_warn("btusb return hook unavailable (%d), trying USB submit fallback\n", + ret); + + ret = register_kprobe(&usb_submit_urb_probe); + if (ret) { + pr_err("cannot attach to usb_submit_urb: %d\n", ret); + return ret; + } + + active_hook = AIC_ZLP_HOOK_USB_SUBMIT; + hook_name = "usb_submit_urb"; + pr_info("attached USB submit fallback for device 368b:8d81 interface 0\n"); + return 0; +} + +static void __exit aic_zlp_quirk_exit(void) +{ + unsigned long missed = 0; + + if (active_hook == AIC_ZLP_HOOK_BTUSB_RETURN) { + unregister_kretprobe(&alloc_bulk_urb_probe); + missed = alloc_bulk_urb_probe.nmissed; + } else if (active_hook == AIC_ZLP_HOOK_USB_SUBMIT) { + unregister_kprobe(&usb_submit_urb_probe); + missed = usb_submit_urb_probe.nmissed; + } + + pr_info("detached %s after %lld injections (%lu missed hits)\n", + hook_name, (long long)atomic64_read(&injection_count), missed); +} + +#else + +static int __init aic_zlp_quirk_init(void) +{ + pr_err("CONFIG_KPROBES is disabled in this kernel\n"); + return -EOPNOTSUPP; +} + +static void __exit aic_zlp_quirk_exit(void) +{ +} + +#endif + +module_init(aic_zlp_quirk_init); +module_exit(aic_zlp_quirk_exit); + +MODULE_AUTHOR("Shen Mintao "); +MODULE_DESCRIPTION("AIC 8800D80 standard btusb ACL bulk TX ZLP quirk"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1.0"); +MODULE_SOFTDEP("pre: btusb"); +MODULE_ALIAS("usb:v368Bp8D81d*dc*dsc*dp*ic*isc*ip*in*"); diff --git a/install.sh b/install.sh index 4d82ae3..4ddad4c 100755 --- a/install.sh +++ b/install.sh @@ -323,6 +323,62 @@ check_kernel_build_tree() { # Firmware Installation ############################################################################# +cleanup_legacy_bluetooth_config() { + local legacy_conf="/etc/modprobe.d/aic8800-bt.conf" + local legacy_udev="/etc/udev/rules.d/90-aic8800-mode-switch.rules" + local legacy_package + local changed=false + + if [ -f "$legacy_conf" ] && grep -Eq '^[[:space:]]*(softdep|alias)[^#]*aic_btusb([[:space:]]|$)' "$legacy_conf"; then + print_info "Removing obsolete aic_btusb directives from $legacy_conf..." + if [ ! -f "${legacy_conf}.aic8800-backup" ]; then + cp -a "$legacy_conf" "${legacy_conf}.aic8800-backup" >> "$LOG_FILE" 2>&1 + fi + sed -i -E '/^[[:space:]]*(softdep|alias)[^#]*aic_btusb([[:space:]]|$)/d' "$legacy_conf" + if ! grep -Eq '^[[:space:]]*[^#[:space:]]' "$legacy_conf"; then + rm -f "$legacy_conf" + fi + changed=true + fi + + if [ -f "$legacy_udev" ] && grep -q 'aic_btusb/new_id' "$legacy_udev"; then + print_info "Removing obsolete aic_btusb binding rules from $legacy_udev..." + if [ ! -f "${legacy_udev}.aic8800-backup" ]; then + cp -a "$legacy_udev" "${legacy_udev}.aic8800-backup" >> "$LOG_FILE" 2>&1 + fi + sed -i '/aic_btusb\/new_id/d' "$legacy_udev" + changed=true + fi + + if lsmod | awk '{print $1}' | grep -qx 'aic_btusb'; then + print_info "Unloading obsolete aic_btusb module..." + if modprobe -r aic_btusb >> "$LOG_FILE" 2>&1; then + changed=true + else + print_warning "aic_btusb is still in use; reboot or replug the adapter after installation." + fi + fi + + # Remove the two issue #63 diagnostic DKMS packages before the production + # quirk is installed as part of aic8800/1.0.0. A patched btusb may remain + # active in memory until reboot, but DKMS restores the distribution module + # on disk when its test package is removed. + for legacy_package in "btusb-aic-zlp/0.1" "aic-zlp-quirk/0.1"; do + if dkms status 2>/dev/null | grep -q "^${legacy_package},"; then + print_info "Removing superseded test package ${legacy_package}..." + if dkms remove "$legacy_package" --all >> "$LOG_FILE" 2>&1; then + changed=true + else + print_warning "Could not remove ${legacy_package}; remove it manually before reboot." + fi + fi + done + + if [ "$changed" = true ]; then + print_success "Legacy Bluetooth configuration cleaned up." + fi +} + install_firmware() { print_step "Installing firmware..." @@ -334,6 +390,8 @@ install_firmware() { exit 1 fi + cleanup_legacy_bluetooth_config + # Remove old firmware versions if [ -d "/lib/firmware" ] && [ -n "$(find /lib/firmware -maxdepth 1 -name 'aic8800*' -type d 2>/dev/null)" ]; then print_info "Removing existing firmware..." @@ -351,6 +409,8 @@ install_firmware() { fi done + print_success "Firmware installed for all supported chip variants." + # Install udev rules local rules_source="${SCRIPT_DIR}/aic.rules" local rules_dest="/usr/lib/udev/rules.d/aic.rules" @@ -416,6 +476,11 @@ BUILT_MODULE_NAME[1]="aic_load_fw" BUILT_MODULE_LOCATION[1]="drivers/aic8800/aic_load_fw" DEST_MODULE_LOCATION[1]="/updates/dkms" +# Quirk ZLP para Bluetooth ACL bulk TX (somente 368b:8d81) +BUILT_MODULE_NAME[2]="aic_zlp_quirk" +BUILT_MODULE_LOCATION[2]="drivers/aic8800/aic_zlp_quirk" +DEST_MODULE_LOCATION[2]="/updates/dkms" + AUTOINSTALL="yes" EOF @@ -525,23 +590,38 @@ refresh_initramfs() { # Module Loading ############################################################################# +aic_zlp_target_present() { + local device + + for device in /sys/bus/usb/devices/*; do + [ -r "$device/idVendor" ] || continue + [ -r "$device/idProduct" ] || continue + [ "$(cat "$device/idVendor")" = "368b" ] || continue + [ "$(cat "$device/idProduct")" = "8d81" ] || continue + return 0 + done + + return 1 +} + load_module() { - print_step "Loading kernel module..." + print_step "Loading kernel modules..." # Update module dependencies print_info "Updating module dependencies..." depmod -a >> "$LOG_FILE" 2>&1 - # Unload module if already loaded if lsmod | grep -q "$MODULE_NAME"; then - print_info "Module already loaded. Reloading..." + print_info "Wi-Fi module already loaded. Reloading..." modprobe -r "$MODULE_NAME" >> "$LOG_FILE" 2>&1 || true fi - # Load the module + # aic8800_fdrv depends on aic_load_fw, which initializes both Wi-Fi-only + # adapters and the Bluetooth firmware on combo adapters. If a standard HCI + # USB interface appears afterwards, the kernel USB modalias loads btusb. print_info "Loading $MODULE_NAME..." if modprobe "$MODULE_NAME" >> "$LOG_FILE" 2>&1; then - print_success "Module loaded successfully." + print_success "Wi-Fi module loaded successfully." # Verify module is loaded print_info "Waiting for module to initialize..." @@ -555,15 +635,30 @@ load_module() { done if [ "$module_loaded" = true ]; then - print_success "Module is active in kernel." + print_success "Wi-Fi module is active in kernel." else - print_warning "Module may not be fully initialized yet." + print_warning "Wi-Fi module may not be fully initialized yet." fi else - print_warning "Module installed but could not be loaded immediately." + print_warning "Wi-Fi module installed but could not be loaded immediately." print_info "This may be due to Secure Boot or missing hardware." print_info "Try rebooting or check: sudo dmesg | grep aic8800" fi + + print_info "Combo adapters use the standard btusb driver when their Bluetooth interface appears." + + # The module has a USB alias and normally autoloads when 368b:8d81 appears. + # Explicitly load it here as well when that device was already present before + # DKMS installation and therefore did not generate a new modalias event. + if aic_zlp_target_present; then + print_info "Loading the 368b:8d81 Bluetooth ACL ZLP quirk..." + if modprobe aic_zlp_quirk >> "$LOG_FILE" 2>&1; then + print_success "Device-scoped Bluetooth ZLP quirk is active." + else + print_warning "The ZLP quirk could not attach; system btusb remains unchanged." + print_info "Check CONFIG_KPROBES, Secure Boot, and: sudo dmesg | grep aic_zlp_quirk" + fi + fi } ############################################################################# @@ -583,14 +678,36 @@ verify_installation() { print_warning "DKMS status unclear: $dkms_status" fi - # Check if module is loaded + # Check if the Wi-Fi module is loaded if lsmod | grep -q "$MODULE_NAME"; then - print_success "Kernel module is loaded." - echo "" - lsmod | grep aic + print_success "Wi-Fi kernel module is loaded." else - print_info "Module not currently loaded (this is OK if no hardware is connected)." + print_info "Wi-Fi module not currently loaded (this is OK if no hardware is connected)." fi + + if lsmod | awk '{print $1}' | grep -qx 'btusb' || compgen -G '/sys/class/bluetooth/hci*' > /dev/null; then + print_success "A Bluetooth controller or the standard btusb module is present." + else + print_info "No Bluetooth controller is present (normal for Wi-Fi-only adapters or when no combo adapter is connected)." + fi + + if aic_zlp_target_present; then + if lsmod | awk '{print $1}' | grep -qx 'aic_zlp_quirk'; then + local zlp_hook="unknown" + if [ -r /sys/module/aic_zlp_quirk/parameters/hook ]; then + zlp_hook=$(cat /sys/module/aic_zlp_quirk/parameters/hook) + fi + print_success "Bluetooth ACL ZLP quirk is loaded (hook: $zlp_hook)." + else + print_warning "USB device 368b:8d81 is present but aic_zlp_quirk is not loaded." + fi + else + print_info "Bluetooth ZLP quirk is installed but inactive (no 368b:8d81 device present)." + fi + + echo "" + print_info "Loaded AIC modules:" + lsmod | grep aic || echo " (none)" # Check firmware local fw_count=0 @@ -610,6 +727,15 @@ verify_installation() { if command -v iwconfig &> /dev/null; then iwconfig 2>/dev/null | grep -E "wlan|IEEE" || echo "No wireless interfaces detected (hardware may not be connected)" fi + + print_info "Checking for Bluetooth interfaces on combo adapters..." + if command -v bluetoothctl &> /dev/null; then + bluetoothctl list 2>/dev/null || echo "No Bluetooth interfaces detected (normal for Wi-Fi-only adapters)" + elif command -v hciconfig &> /dev/null; then + hciconfig 2>/dev/null || echo "No Bluetooth interfaces detected (normal for Wi-Fi-only adapters)" + else + echo "Bluetooth tools not installed (install BlueZ to manage a combo adapter)" + fi } ############################################################################# @@ -624,13 +750,15 @@ show_final_instructions() { echo "" echo -e "${CYAN}Important Information:${NC}" echo "" - echo "✓ Driver installed via DKMS" + echo "✓ Wi-Fi driver and firmware loader installed via DKMS" + echo "✓ Combo adapters use the standard Linux btusb driver" + echo "✓ Device-scoped Bluetooth ACL ZLP quirk installed for 368b:8d81" echo "✓ Automatic rebuild enabled for kernel updates" echo "✓ Firmware installed in /lib/firmware/" echo "" echo -e "${CYAN}Next Steps:${NC}" echo "" - echo "1. Connect your AIC8800D80 USB WiFi adapter" + echo "1. Connect your AIC8800 USB Wi-Fi or Wi-Fi/Bluetooth adapter" echo "" echo "2. Check if the adapter is detected:" echo " ${BLUE}lsusb | grep -i aic${NC}" @@ -639,32 +767,37 @@ show_final_instructions() { echo "" echo "3. View kernel messages about the driver:" echo " ${BLUE}sudo dmesg | grep aic8800${NC}" + echo " ${BLUE}sudo dmesg | grep -iE 'bluetooth|btusb|hci'${NC}" echo "" echo "4. Connect to a WiFi network:" echo " ${BLUE}nmcli device wifi list${NC}" echo " ${BLUE}nmcli device wifi connect \"SSID\" password \"PASSWORD\"${NC}" echo "" + echo "5. For a combo adapter, check Bluetooth:" + echo " ${BLUE}bluetoothctl list${NC}" + echo " ${BLUE}bluetoothctl scan on${NC}" + echo "" echo -e "${CYAN}Troubleshooting:${NC}" echo "" echo "• Check DKMS status:" echo " ${BLUE}dkms status${NC}" echo "" echo "• Check loaded modules:" - echo " ${BLUE}lsmod | grep aic8800${NC}" + echo " ${BLUE}lsmod | grep -E 'aic|btusb'${NC}" + echo " ${BLUE}cat /sys/module/aic_zlp_quirk/parameters/{hook,injections}${NC}" echo "" - echo "• Manually load the module:" + echo "• Manually load the Wi-Fi module and firmware loader:" echo " ${BLUE}sudo modprobe aic8800_fdrv${NC}" echo "" echo "• View detailed logs:" echo " ${BLUE}cat $LOG_FILE${NC}" echo "" echo -e "${YELLOW}Known Limitations:${NC}" - echo "• Bluetooth functionality is not supported" echo "• Secure Boot may prevent module loading (disable in BIOS if needed)" echo "" echo -e "${CYAN}Uninstallation:${NC}" echo " ${BLUE}sudo dkms remove ${DRV_NAME}/${DRV_VERSION} --all${NC}" - echo " ${BLUE}sudo rm -rf /lib/firmware/aic8800D80${NC}" + echo " ${BLUE}sudo rm -rf /lib/firmware/aic8800*${NC}" echo "" } diff --git a/tests/issue63-zlp-quirk/Makefile b/tests/issue63-zlp-quirk/Makefile new file mode 100644 index 0000000..26ad7b0 --- /dev/null +++ b/tests/issue63-zlp-quirk/Makefile @@ -0,0 +1,12 @@ +KVER ?= $(shell uname -r) +KDIR ?= /lib/modules/$(KVER)/build + +obj-m += aic_zlp_quirk.o + +.PHONY: all clean + +all: + $(MAKE) -C $(KDIR) M=$(CURDIR) modules + +clean: + $(MAKE) -C $(KDIR) M=$(CURDIR) clean diff --git a/tests/issue63-zlp-quirk/README.md b/tests/issue63-zlp-quirk/README.md new file mode 100644 index 0000000..7d62b3c --- /dev/null +++ b/tests/issue63-zlp-quirk/README.md @@ -0,0 +1,109 @@ +# Issue #63: standard `btusb` ACL ZLP support + +The `aic_zlp_quirk` companion module supplies the Bluetooth ACL bulk TX +zero-length-packet behavior confirmed in +[issue #63](https://github.com/shenmintao/aic8800d80/issues/63). It is now part +of the normal `aic8800` DKMS build rather than a replacement for the +distribution's Bluetooth driver. + +The validated failure was specific and reproducible: AAC playback through USB +device `368b:8d81` stalled after about one minute, while SBC remained stable. +Adding `URB_ZERO_PACKET` to the ACL bulk OUT URBs kept AAC connected for more +than one hour. The standalone module was then verified with the system +`btusb`, no loaded `aic_btusb`, and 2,113 observed ZLP injections. + +## Architecture and scope + +The module first tries to attach a kretprobe to the standard `btusb` function +that allocates ACL bulk OUT URBs. The target is module-qualified as +`btusb:alloc_bulk_urb`, avoiding similarly named symbols in unrelated USB +drivers. + +Some distribution builds inline that private function. When the preferred hook +is unavailable, the module falls back to the stable `usb_submit_urb` entry +point. The fallback changes an URB only when all of these checks pass: + +- USB device is exactly `368b:8d81`; +- the transfer is bulk OUT; +- the endpoint belongs to Bluetooth interface 0 (`e0/01/01`). + +Both paths add only `URB_ZERO_PACKET`. Wi-Fi interfaces and unrelated USB +devices are unchanged. If neither probe can be installed or kprobes are +disabled, the module refuses to load and system `btusb` remains unchanged. + +The module carries a USB modalias for `368b:8d81`, so it is inactive on other +hardware and normally autoloads only when the validated device appears. + +## Install from the unified branch + +```bash +git fetch origin +git switch test/unified-wifi-bt-zlp +git pull --ff-only +sudo ./install.sh +sudo reboot +``` + +The installer removes the earlier `btusb-aic-zlp/0.1` and +`aic-zlp-quirk/0.1` diagnostic DKMS packages when present. To remove them +manually before installation, use: + +```bash +sudo dkms remove btusb-aic-zlp/0.1 --all +sudo dkms remove aic-zlp-quirk/0.1 --all +sudo depmod -a +``` + +The unified installer also removes active configuration left by the obsolete +custom `aic_btusb` transport. + +## Verify + +Confirm that system `btusb` owns Bluetooth interfaces 0/1 and that the quirk is +loaded: + +```bash +lsusb -t +lsmod | grep -E '^(btusb|aic_zlp_quirk|aic_btusb)\b' +modinfo -n btusb +modinfo -n aic_zlp_quirk +cat /sys/module/aic_zlp_quirk/parameters/hook +cat /sys/module/aic_zlp_quirk/parameters/injections +sudo ./diagnose_bt.sh +``` + +Expected results include: + +```text +Bluetooth interfaces 0/1: btusb +aic_btusb: not loaded +aic_zlp_quirk: loaded +active hook: btusb:alloc_bulk_urb +``` + +`active hook: usb_submit_urb` is also valid when the compiler inlined the +preferred function. During Bluetooth traffic, the `injections` value must +increase. + +For the promotion test, select AAC and play audio for at least one hour while +also confirming that Wi-Fi scan, association, DHCP, and real traffic remain +working. + +## Isolated retest helper + +`aic-zlp-quirk-test.sh` remains available only for isolated issue #63 retests. +It builds the same canonical source from +`drivers/aic8800/aic_zlp_quirk/aic_zlp_quirk.c` as a separate DKMS package. A +normal installation should use the repository-level `install.sh` instead. + +## Kernel requirements + +- matching kernel headers; +- `CONFIG_KPROBES=y` and kretprobe support; +- standard `btusb` plus either its `alloc_bulk_urb` symbol or the USB core + `usb_submit_urb` symbol; +- a valid module signature when Secure Boot policy requires one. + +The preferred function has the same signature in Linux 5.15, 6.1, 6.6, 6.12, +6.18, and 7.1.3, although compilers may inline it. DKMS builds one module for +each installed kernel from the single canonical source file. diff --git a/tests/issue63-zlp-quirk/aic-zlp-quirk-test.sh b/tests/issue63-zlp-quirk/aic-zlp-quirk-test.sh new file mode 100755 index 0000000..544062a --- /dev/null +++ b/tests/issue63-zlp-quirk/aic-zlp-quirk-test.sh @@ -0,0 +1,239 @@ +#!/usr/bin/env bash +set -euo pipefail + +PACKAGE_NAME="aic-zlp-quirk" +PACKAGE_VERSION="0.1" +MODULE_NAME="aic_zlp_quirk" +KERNEL_RELEASE="${KERNEL_RELEASE:-$(uname -r)}" +KERNEL_BUILD_DIR="/lib/modules/${KERNEL_RELEASE}/build" +SOURCE_DIR="/usr/src/${PACKAGE_NAME}-${PACKAGE_VERSION}" +MODULES_LOAD_FILE="/etc/modules-load.d/${PACKAGE_NAME}.conf" +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd -- "${SCRIPT_DIR}/../.." && pwd)" +QUIRK_SOURCE="${REPO_ROOT}/drivers/aic8800/aic_zlp_quirk/aic_zlp_quirk.c" + +log() { + printf '[aic-zlp-quirk-test] %s\n' "$*" +} + +die() { + printf '[aic-zlp-quirk-test] ERROR: %s\n' "$*" >&2 + exit 1 +} + +require_root() { + [ "$(id -u)" -eq 0 ] || die "run this command as root" +} + +require_command() { + command -v "$1" >/dev/null 2>&1 || die "required command not found: $1" +} + +stop_bluetooth() { + if command -v systemctl >/dev/null 2>&1; then + systemctl stop bluetooth.service 2>/dev/null || true + fi +} + +start_bluetooth() { + if command -v systemctl >/dev/null 2>&1; then + systemctl start bluetooth.service 2>/dev/null || true + fi +} + +module_loaded() { + [ -d "/sys/module/$1" ] +} + +legacy_btusb_loaded() { + module_loaded aic_btusb +} + +patched_btusb_installed() { + dkms status -m btusb-aic-zlp -v 0.1 2>/dev/null | grep -q . +} + +copy_sources() { + install -d "${SOURCE_DIR}" + install -m 0644 "${QUIRK_SOURCE}" "${SOURCE_DIR}/" + install -m 0644 "${SCRIPT_DIR}/Makefile" "${SOURCE_DIR}/" + install -m 0644 "${SCRIPT_DIR}/dkms.conf" "${SOURCE_DIR}/" +} + +rollback_install() { + log "rolling back the ZLP companion module" + rm -f -- "${MODULES_LOAD_FILE}" + modprobe -r "${MODULE_NAME}" >/dev/null 2>&1 || true + dkms remove -m "${PACKAGE_NAME}" -v "${PACKAGE_VERSION}" --all >/dev/null 2>&1 || true + rm -rf -- "${SOURCE_DIR}" + depmod -a "${KERNEL_RELEASE}" >/dev/null 2>&1 || true + start_bluetooth +} + +verify_standard_btusb() { + if legacy_btusb_loaded; then + die "obsolete aic_btusb is loaded; run 'sudo modprobe -r aic_btusb' and remove its old autoload configuration first" + fi + + if patched_btusb_installed; then + die "the patched btusb test package is still installed; remove it before testing the companion module" + fi + + modprobe btusb || die "could not load the system btusb module" +} + +install_module() { + require_root + require_command dkms + require_command make + require_command modprobe + require_command depmod + + [ -r "${KERNEL_BUILD_DIR}/Makefile" ] || \ + die "kernel build tree not found: ${KERNEL_BUILD_DIR}" + + if command -v lsusb >/dev/null 2>&1 && ! lsusb -d 368b:8d81 >/dev/null 2>&1; then + die "this test is restricted to USB device 368b:8d81, which is not connected" + fi + + verify_standard_btusb + + if module_loaded "${MODULE_NAME}"; then + log "unloading the earlier companion module" + if ! modprobe -r "${MODULE_NAME}"; then + die "could not unload the earlier ${MODULE_NAME} module" + fi + fi + + trap rollback_install ERR + + if dkms status -m "${PACKAGE_NAME}" -v "${PACKAGE_VERSION}" 2>/dev/null | grep -q .; then + log "removing an earlier companion-module build" + dkms remove -m "${PACKAGE_NAME}" -v "${PACKAGE_VERSION}" --all + fi + + rm -rf -- "${SOURCE_DIR}" + copy_sources + + log "building the ZLP companion module for ${KERNEL_RELEASE}" + dkms add -m "${PACKAGE_NAME}" -v "${PACKAGE_VERSION}" + dkms build -m "${PACKAGE_NAME}" -v "${PACKAGE_VERSION}" -k "${KERNEL_RELEASE}" + dkms install -m "${PACKAGE_NAME}" -v "${PACKAGE_VERSION}" \ + -k "${KERNEL_RELEASE}" --force + depmod -a "${KERNEL_RELEASE}" + + printf '%s\n' "${MODULE_NAME}" > "${MODULES_LOAD_FILE}" + + stop_bluetooth + if ! modprobe "${MODULE_NAME}"; then + trap - ERR + rollback_install + die "the companion module could not attach; the system btusb was left unchanged" + fi + start_bluetooth + trap - ERR + + log "loaded module: $(modinfo -n "${MODULE_NAME}")" + log "system btusb remains active; test AAC playback for at least one hour" +} + +remove_module() { + require_root + require_command dkms + require_command modprobe + require_command depmod + + stop_bluetooth + rm -f -- "${MODULES_LOAD_FILE}" + if module_loaded "${MODULE_NAME}" && ! modprobe -r "${MODULE_NAME}"; then + start_bluetooth + die "could not unload ${MODULE_NAME}; no files were removed" + fi + if dkms status -m "${PACKAGE_NAME}" -v "${PACKAGE_VERSION}" 2>/dev/null | grep -q .; then + if ! dkms remove -m "${PACKAGE_NAME}" -v "${PACKAGE_VERSION}" --all; then + start_bluetooth + die "DKMS removal failed; source files were left in place" + fi + fi + rm -rf -- "${SOURCE_DIR}" + depmod -a "${KERNEL_RELEASE}" + start_bluetooth + + log "removed the companion module; the distribution btusb was never replaced" +} + +show_aic_binding() { + local device + local driver + + for device in /sys/bus/usb/devices/*; do + [ -r "${device}/idVendor" ] || continue + [ -r "${device}/idProduct" ] || continue + [ "$(cat "${device}/idVendor")" = "368b" ] || continue + [ "$(cat "${device}/idProduct")" = "8d81" ] || continue + + driver="not bound" + if [ -L "${device}:1.0/driver" ]; then + driver="$(basename "$(readlink -f "${device}:1.0/driver")")" + fi + + printf 'AIC BT driver: %s\n' "${driver}" + return + done + + printf 'AIC BT driver: device not found\n' +} + +show_status() { + local loaded="no" + local injections="unavailable" + local hook="unavailable" + local integrated_dkms_status + local standalone_dkms_status + + if module_loaded "${MODULE_NAME}"; then + loaded="yes" + if [ -r "/sys/module/${MODULE_NAME}/parameters/injections" ]; then + injections="$(cat "/sys/module/${MODULE_NAME}/parameters/injections")" + fi + if [ -r "/sys/module/${MODULE_NAME}/parameters/hook" ]; then + hook="$(cat "/sys/module/${MODULE_NAME}/parameters/hook")" + fi + fi + + standalone_dkms_status="$(dkms status -m "${PACKAGE_NAME}" -v "${PACKAGE_VERSION}" 2>/dev/null || true)" + if [ -z "${standalone_dkms_status}" ]; then + standalone_dkms_status="not installed" + fi + integrated_dkms_status="$(dkms status -m aic8800 -v 1.0.0 2>/dev/null || true)" + if [ -z "${integrated_dkms_status}" ]; then + integrated_dkms_status="not installed" + fi + + printf 'kernel: %s\n' "${KERNEL_RELEASE}" + printf 'btusb module: %s\n' "$(modinfo -n btusb 2>/dev/null || printf 'not found')" + printf 'quirk module: %s\n' "$(modinfo -n "${MODULE_NAME}" 2>/dev/null || printf 'not found')" + printf 'quirk loaded: %s\n' "${loaded}" + printf 'active hook: %s\n' "${hook}" + printf 'ZLP injections:%s\n' " ${injections}" + printf 'legacy aic_btusb: %s\n' "$(legacy_btusb_loaded && printf 'loaded' || printf 'not loaded')" + printf 'integrated DKMS: %s\n' "${integrated_dkms_status}" + printf 'standalone DKMS: %s\n' "${standalone_dkms_status}" + show_aic_binding +} + +case "${1:-}" in + install) + install_module + ;; + remove) + remove_module + ;; + status) + show_status + ;; + *) + printf 'Usage: %s {install|remove|status}\n' "$0" >&2 + exit 2 + ;; +esac diff --git a/tests/issue63-zlp-quirk/dkms.conf b/tests/issue63-zlp-quirk/dkms.conf new file mode 100644 index 0000000..1795467 --- /dev/null +++ b/tests/issue63-zlp-quirk/dkms.conf @@ -0,0 +1,10 @@ +PACKAGE_NAME="aic-zlp-quirk" +PACKAGE_VERSION="0.1" + +MAKE[0]="make KVER=${kernelver}" +CLEAN="make KVER=${kernelver} clean" + +BUILT_MODULE_NAME[0]="aic_zlp_quirk" +DEST_MODULE_LOCATION[0]="/updates/dkms" + +AUTOINSTALL="yes" diff --git a/tests/unified-wifi-bt/README.md b/tests/unified-wifi-bt/README.md new file mode 100644 index 0000000..dc74a5b --- /dev/null +++ b/tests/unified-wifi-bt/README.md @@ -0,0 +1,87 @@ +# Unified Wi-Fi, Bluetooth, and ZLP branch test + +This branch tests replacing the separate `main` and `bluetooth` branches with +one driver and installer. The Wi-Fi driver and firmware were already identical +on both branches. This test unifies their installer, migration, diagnostics, +packaging, and documentation, and adds the device-scoped ZLP fix from issue +#63 as a third DKMS module. + +The expected architecture is: + +- `aic_load_fw` initializes every supported adapter and uploads firmware. +- `aic8800_fdrv` handles Wi-Fi. +- A combo adapter exposes a Bluetooth HCI USB interface after initialization; + the standard Linux `btusb` driver binds through normal USB device matching. +- A Wi-Fi-only adapter exposes no HCI interface and does not need `btusb`. +- The retired custom `aic_btusb` module is never installed or used. +- USB device `368b:8d81` autoloads `aic_zlp_quirk`, which adds + `URB_ZERO_PACKET` only to its Bluetooth ACL bulk OUT transfers. +- The distribution's original `btusb.ko` is never replaced. + +The installer deliberately does not force-load `btusb` and does not globally +unblock Bluetooth through rfkill. + +## Install + +```bash +git fetch origin +git switch test/unified-wifi-bt-zlp +git pull --ff-only +sudo ./install.sh +sudo reboot +``` + +## Test a Wi-Fi-only adapter + +Confirm that: + +1. The Wi-Fi interface is created. +2. Scanning, association, DHCP, and real traffic work. +3. No `aic_btusb` module is loaded. +4. The absence of `btusb` or an HCI controller is treated as normal. + +## Test a Wi-Fi/Bluetooth combo adapter + +Confirm that: + +1. Wi-Fi scanning, association, DHCP, and real traffic work. +2. `aic_load_fw` uploads the firmware before the HCI interface is used. +3. The Bluetooth USB interface is bound to the standard `btusb` driver. +4. `bluetoothctl` can power on, scan, pair, and exchange real traffic. +5. No `aic_btusb` module, alias, soft dependency, or udev binding rule remains. + +## Test the `368b:8d81` ZLP path + +In addition to the combo-adapter checks, confirm that: + +1. `aic_zlp_quirk` loads automatically and system `btusb` owns interfaces 0/1. +2. `hook` reports `btusb:alloc_bulk_urb` or `usb_submit_urb`. +3. The `injections` counter increases during Bluetooth audio traffic. +4. AAC playback remains connected for at least one hour. +5. Wi-Fi scan, association, and traffic remain unaffected during the test. + +## Collect results + +```bash +git rev-parse --short HEAD +lsusb +lsusb -t +lsmod | grep -E 'aic|btusb|bluetooth' +iw dev +bluetoothctl list +cat /sys/module/aic_zlp_quirk/parameters/{hook,injections} 2>/dev/null || true +sudo ./diagnose_bt.sh | tee unified-wifi-bt-diagnostic.log +sudo journalctl -k -b --no-pager | tee unified-wifi-bt-kernel.log +``` + +Please attach both logs and identify whether the tested adapter is Wi-Fi-only +or a combo device. + +## Roll back during testing + +```bash +git switch main +git pull --ff-only +sudo ./install.sh +sudo reboot +```