mirror of
https://github.com/shenmintao/aic8800d80.git
synced 2026-09-26 17:44:16 +00:00
feat: unify Bluetooth support and add ZLP quirk
This commit is contained in:
@@ -1,104 +1,100 @@
|
||||
# Issue #63: standard `btusb` ZLP companion-module test
|
||||
# Issue #63: standard `btusb` ACL ZLP support
|
||||
|
||||
This experiment keeps the normal `aic_load_fw + system btusb` architecture.
|
||||
It does not install or bind the vendor `aic_btusb` transport, and it does not
|
||||
replace the distribution's `btusb.ko`.
|
||||
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 underlying ZLP behavior was confirmed in
|
||||
[issue #63](https://github.com/shenmintao/aic8800d80/issues/63): AAC playback
|
||||
that previously stalled after about one minute remained stable for more than
|
||||
one hour with the standard `btusb` ZLP patch, including after the obsolete
|
||||
`aic_btusb` module was removed.
|
||||
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.
|
||||
|
||||
The small `aic_zlp_quirk.ko` module first tries to attach a kretprobe to the
|
||||
standard `btusb` function that allocates Bluetooth ACL bulk OUT URBs. The
|
||||
target is module-qualified as `btusb:alloc_bulk_urb`, avoiding similarly named
|
||||
symbols in unrelated USB drivers.
|
||||
## Architecture and scope
|
||||
|
||||
Some distribution builds inline that private `btusb` function. When the
|
||||
preferred hook is unavailable, the same module falls back to the stable
|
||||
`usb_submit_urb` entry point. The fallback requires all of the following before
|
||||
changing an URB:
|
||||
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.
|
||||
|
||||
- USB device `368b:8d81`;
|
||||
- bulk OUT transfer;
|
||||
- endpoint declared by Bluetooth interface 0 (`e0/01/01`).
|
||||
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:
|
||||
|
||||
Both paths add `URB_ZERO_PACKET` before the USB core submits the transfer. The
|
||||
Wi-Fi interface and unrelated USB devices are left unchanged.
|
||||
- USB device is exactly `368b:8d81`;
|
||||
- the transfer is bulk OUT;
|
||||
- the endpoint belongs to Bluetooth interface 0 (`e0/01/01`).
|
||||
|
||||
The hook fails closed: if neither probe can be installed or kprobes are
|
||||
disabled, the companion module refuses to load and the system `btusb` remains
|
||||
unchanged.
|
||||
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.
|
||||
|
||||
## Before installing
|
||||
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.
|
||||
|
||||
First remove the earlier patched-`btusb` diagnostic build, if installed:
|
||||
## Install from the unified branch
|
||||
|
||||
```bash
|
||||
cd ../issue63-btusb-zlp
|
||||
sudo ./btusb-zlp-test.sh remove
|
||||
git fetch origin
|
||||
git switch test/unified-wifi-bt-zlp
|
||||
git pull --ff-only
|
||||
sudo ./install.sh
|
||||
sudo reboot
|
||||
```
|
||||
|
||||
An obsolete `aic_btusb` installation must also be unloaded and removed before
|
||||
this test. Verify that the AIC Bluetooth interfaces use the system driver:
|
||||
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_btusb)\b'
|
||||
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
|
||||
```
|
||||
|
||||
## Install
|
||||
|
||||
Install the normal build dependencies. For Fedora:
|
||||
|
||||
```bash
|
||||
sudo dnf install dkms gcc make kernel-devel-$(uname -r)
|
||||
```
|
||||
|
||||
Then install and load the companion module:
|
||||
|
||||
```bash
|
||||
cd tests/issue63-zlp-quirk
|
||||
sudo ./aic-zlp-quirk-test.sh install
|
||||
./aic-zlp-quirk-test.sh status
|
||||
```
|
||||
|
||||
Expected status includes:
|
||||
Expected results include:
|
||||
|
||||
```text
|
||||
quirk loaded: yes
|
||||
active hook: btusb:alloc_bulk_urb
|
||||
legacy aic_btusb: not loaded
|
||||
AIC BT driver: btusb
|
||||
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 distribution compiler
|
||||
inlined the preferred private `btusb` function.
|
||||
`active hook: usb_submit_urb` is also valid when the compiler inlined the
|
||||
preferred function. During Bluetooth traffic, the `injections` value must
|
||||
increase.
|
||||
|
||||
Select AAC and play audio for at least one hour:
|
||||
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.
|
||||
|
||||
```bash
|
||||
bluetoothctl scan off
|
||||
pactl set-card-profile bluez_card.28_6F_40_46_AB_B1 a2dp-sink-aac
|
||||
```
|
||||
## Isolated retest helper
|
||||
|
||||
While audio is playing, the `ZLP injections` counter should increase:
|
||||
|
||||
```bash
|
||||
./aic-zlp-quirk-test.sh status
|
||||
sudo dmesg | grep -i aic_zlp_quirk
|
||||
```
|
||||
|
||||
## Remove
|
||||
|
||||
```bash
|
||||
sudo ./aic-zlp-quirk-test.sh remove
|
||||
./aic-zlp-quirk-test.sh status
|
||||
```
|
||||
|
||||
Removal unloads only `aic_zlp_quirk.ko`. The distribution `btusb.ko` was never
|
||||
overwritten and remains the Bluetooth transport throughout the test.
|
||||
`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
|
||||
|
||||
@@ -108,8 +104,6 @@ overwritten and remains the Bluetooth transport throughout the test.
|
||||
`usb_submit_urb` symbol;
|
||||
- a valid module signature when Secure Boot policy requires one.
|
||||
|
||||
The `alloc_bulk_urb` function is present with the same signature in Linux
|
||||
5.15, 6.1, 6.6, 6.12, 6.18, and 7.1.3, although compilers may inline it. The
|
||||
fallback avoids depending on that implementation detail. DKMS compiles a
|
||||
separate binary for each installed kernel while the repository maintains one
|
||||
small source file.
|
||||
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.
|
||||
|
||||
@@ -9,6 +9,8 @@ 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' "$*"
|
||||
@@ -53,7 +55,7 @@ patched_btusb_installed() {
|
||||
|
||||
copy_sources() {
|
||||
install -d "${SOURCE_DIR}"
|
||||
install -m 0644 "${SCRIPT_DIR}/aic_zlp_quirk.c" "${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}/"
|
||||
}
|
||||
|
||||
@@ -1,228 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/kprobes.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/ptrace.h>
|
||||
#include <linux/usb.h>
|
||||
|
||||
#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 <cx330.shen@autocore.ai>");
|
||||
MODULE_DESCRIPTION("AIC 8800D80 standard btusb ACL bulk TX ZLP quirk");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_VERSION("0.1");
|
||||
MODULE_SOFTDEP("pre: btusb");
|
||||
Reference in New Issue
Block a user