mirror of
https://github.com/shenmintao/aic8800d80.git
synced 2026-09-26 17:44:16 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa3f63e7a2 | ||
|
|
cbc12c7bdb |
@@ -0,0 +1,8 @@
|
||||
/.gitattributes text eol=lf
|
||||
tests/issue63-btusb-zlp/*.patch -whitespace
|
||||
tests/issue63-btusb-zlp/*.sh text eol=lf
|
||||
tests/issue63-zlp-quirk/*.c 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
|
||||
@@ -0,0 +1,74 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Shen Mintao <cx330.shen@autocore.ai>
|
||||
Date: Fri, 17 Jul 2026 21:00:00 +0800
|
||||
Subject: [PATCH] Bluetooth: btusb: add bulk TX ZLP quirk for AIC 8800D80
|
||||
|
||||
The AIC 8800D80 controller can stop returning HCI Number Of Completed
|
||||
Packets events during sustained AAC A2DP traffic when driven by the
|
||||
generic btusb transport. Once the controller's ACL credits are
|
||||
exhausted, the connection is terminated by the HCI ACL TX watchdog.
|
||||
|
||||
The vendor transport sets URB_ZERO_PACKET on every ACL bulk OUT URB.
|
||||
Add an equivalent, device-specific quirk to btusb for the 368b:8d81
|
||||
variant so that the standard kernel driver retains control of the
|
||||
device.
|
||||
|
||||
This is intentionally limited to the USB ID from the reproducer while
|
||||
the behaviour is validated on additional AIC variants.
|
||||
|
||||
Link: https://github.com/shenmintao/aic8800d80/issues/63
|
||||
Signed-off-by: Shen Mintao <cx330.shen@autocore.ai>
|
||||
---
|
||||
drivers/bluetooth/btusb.c | 10 ++++++++++
|
||||
1 file changed, 10 insertions(+)
|
||||
|
||||
diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c
|
||||
index 830fefb342c6..97118c095369 100644
|
||||
--- a/drivers/bluetooth/btusb.c
|
||||
+++ b/drivers/bluetooth/btusb.c
|
||||
@@ -67,6 +67,7 @@ static bool reset = true;
|
||||
#define BTUSB_INTEL_NO_WBS_SUPPORT BIT(26)
|
||||
#define BTUSB_ACTIONS_SEMI BIT(27)
|
||||
#define BTUSB_BARROT BIT(28)
|
||||
+#define BTUSB_BULK_TX_ZLP BIT(29)
|
||||
|
||||
static const struct usb_device_id btusb_table[] = {
|
||||
/* Generic Bluetooth USB device */
|
||||
@@ -178,5 +179,9 @@ MODULE_DEVICE_TABLE(usb, btusb_table);
|
||||
|
||||
static const struct usb_device_id quirks_table[] = {
|
||||
+ /* AIC Semiconductor AIC 8800D80 */
|
||||
+ { USB_DEVICE(0x368b, 0x8d81),
|
||||
+ .driver_info = BTUSB_BULK_TX_ZLP },
|
||||
+
|
||||
/* CSR BlueCore devices */
|
||||
{ USB_DEVICE(0x0a12, 0x0001), .driver_info = BTUSB_CSR },
|
||||
|
||||
@@ -949,6 +954,7 @@ struct btusb_data {
|
||||
unsigned long flags;
|
||||
|
||||
bool poll_sync;
|
||||
+ bool bulk_tx_zlp;
|
||||
int intr_interval;
|
||||
struct work_struct work;
|
||||
struct work_struct waker;
|
||||
@@ -2138,6 +2144,9 @@ static struct urb *alloc_bulk_urb(struct hci_dev *hdev, struct sk_buff *skb)
|
||||
usb_fill_bulk_urb(urb, data->udev, pipe,
|
||||
skb->data, skb->len, btusb_tx_complete, skb);
|
||||
|
||||
+ if (data->bulk_tx_zlp)
|
||||
+ urb->transfer_flags |= URB_ZERO_PACKET;
|
||||
+
|
||||
skb->dev = (void *)hdev;
|
||||
|
||||
return urb;
|
||||
@@ -4100,6 +4109,7 @@ static int btusb_probe(struct usb_interface *intf,
|
||||
|
||||
data->udev = interface_to_usbdev(intf);
|
||||
data->intf = intf;
|
||||
+ data->bulk_tx_zlp = id->driver_info & BTUSB_BULK_TX_ZLP;
|
||||
|
||||
INIT_WORK(&data->work, btusb_work);
|
||||
INIT_WORK(&data->waker, btusb_waker);
|
||||
--
|
||||
2.50.1
|
||||
@@ -0,0 +1,61 @@
|
||||
# Issue #63: system `btusb` ZLP test
|
||||
|
||||
This test keeps the normal `aic_load_fw + btusb` architecture. It does not
|
||||
restore or install the vendor `aic_btusb` driver.
|
||||
|
||||
The patch adds one quirk to the standard Linux `btusb` source:
|
||||
|
||||
- match only AICSemi `368b:8d81`;
|
||||
- set `URB_ZERO_PACKET` on Bluetooth ACL bulk OUT transfers;
|
||||
- leave every other `btusb` device and code path unchanged.
|
||||
|
||||
The test installer downloads the standard `btusb` source matching the running
|
||||
kernel's upstream version, applies the patch, and installs the resulting
|
||||
`btusb.ko` through DKMS. The distribution module is not overwritten and can be
|
||||
restored by removing the DKMS test package.
|
||||
|
||||
## Install the test module
|
||||
|
||||
On Fedora, make sure the test build dependencies are installed:
|
||||
|
||||
```bash
|
||||
sudo dnf install dkms gcc make kernel-devel-$(uname -r) curl patch
|
||||
```
|
||||
|
||||
Stop Bluetooth scanning and disconnect Bluetooth devices, then run:
|
||||
|
||||
```bash
|
||||
cd tests/issue63-btusb-zlp
|
||||
sudo ./btusb-zlp-test.sh install
|
||||
./btusb-zlp-test.sh status
|
||||
```
|
||||
|
||||
`modinfo -n btusb` should point to an `updates/dkms` path. Secure Boot systems
|
||||
may require the DKMS module to be signed or enrolled before it can load.
|
||||
|
||||
Select AAC again and play audio for at least one hour:
|
||||
|
||||
```bash
|
||||
bluetoothctl scan off
|
||||
pactl set-card-profile bluez_card.28_6F_40_46_AB_B1 a2dp-sink-aac
|
||||
```
|
||||
|
||||
Please capture `sudo btmon -w issue63-zlp.btsnoop` and relevant `dmesg` lines if
|
||||
the connection still stalls.
|
||||
|
||||
## Restore the distribution module
|
||||
|
||||
```bash
|
||||
sudo ./btusb-zlp-test.sh remove
|
||||
./btusb-zlp-test.sh status
|
||||
```
|
||||
|
||||
After removal, `modinfo -n btusb` should point back to the distribution's
|
||||
`kernel/drivers/bluetooth/btusb.ko` path.
|
||||
|
||||
## Scope
|
||||
|
||||
This is an A/B diagnostic patch, not a production workaround. If AAC remains
|
||||
stable, the small kernel patch can be submitted upstream. If it still fails,
|
||||
the next isolated experiment should add an AIC-specific runtime-PM quirk rather
|
||||
than replacing the standard Bluetooth driver.
|
||||
Executable
+196
@@ -0,0 +1,196 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
PACKAGE_NAME="btusb-aic-zlp"
|
||||
PACKAGE_VERSION="0.1"
|
||||
KERNEL_RELEASE="${KERNEL_RELEASE:-$(uname -r)}"
|
||||
UPSTREAM_VERSION="${UPSTREAM_VERSION:-$(printf '%s' "$KERNEL_RELEASE" | sed -nE 's/^([0-9]+\.[0-9]+\.[0-9]+).*/\1/p')}"
|
||||
UPSTREAM_TAG="${UPSTREAM_TAG:-v${UPSTREAM_VERSION}}"
|
||||
KERNEL_BUILD_DIR="/lib/modules/${KERNEL_RELEASE}/build"
|
||||
SOURCE_DIR="/usr/src/${PACKAGE_NAME}-${PACKAGE_VERSION}"
|
||||
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PATCH_FILE="${SCRIPT_DIR}/0001-Bluetooth-btusb-add-AIC-bulk-TX-ZLP-quirk.patch"
|
||||
BASE_URL="https://raw.githubusercontent.com/gregkh/linux/${UPSTREAM_TAG}/drivers/bluetooth"
|
||||
|
||||
log() {
|
||||
printf '[btusb-zlp-test] %s\n' "$*"
|
||||
}
|
||||
|
||||
die() {
|
||||
printf '[btusb-zlp-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
|
||||
}
|
||||
|
||||
rollback_test_install() {
|
||||
log "rolling back to the distribution btusb module"
|
||||
dkms remove -m "${PACKAGE_NAME}" -v "${PACKAGE_VERSION}" --all >/dev/null 2>&1 || true
|
||||
rm -rf -- "${SOURCE_DIR}"
|
||||
depmod -a "${KERNEL_RELEASE}" || true
|
||||
modprobe btusb || true
|
||||
start_bluetooth
|
||||
}
|
||||
|
||||
activate_test_module() {
|
||||
stop_bluetooth
|
||||
|
||||
if ! modprobe -r btusb; then
|
||||
rollback_test_install
|
||||
die "could not unload btusb; disconnect active Bluetooth devices and retry"
|
||||
fi
|
||||
|
||||
if ! modprobe btusb; then
|
||||
rollback_test_install
|
||||
die "the patched btusb could not load; the distribution module was restored"
|
||||
fi
|
||||
|
||||
start_bluetooth
|
||||
}
|
||||
|
||||
write_build_files() {
|
||||
cat > "${SOURCE_DIR}/Makefile" <<'EOF'
|
||||
KVER ?= $(shell uname -r)
|
||||
KDIR ?= /lib/modules/$(KVER)/build
|
||||
|
||||
obj-m += btusb.o
|
||||
btusb-y := drivers/bluetooth/btusb.o
|
||||
ccflags-y += -I$(src)/drivers/bluetooth
|
||||
|
||||
all:
|
||||
$(MAKE) -C $(KDIR) M=$(CURDIR) modules
|
||||
|
||||
clean:
|
||||
$(MAKE) -C $(KDIR) M=$(CURDIR) clean
|
||||
EOF
|
||||
|
||||
cat > "${SOURCE_DIR}/dkms.conf" <<'EOF'
|
||||
PACKAGE_NAME="btusb-aic-zlp"
|
||||
PACKAGE_VERSION="0.1"
|
||||
MAKE[0]="make KVER=${kernelver}"
|
||||
CLEAN="make KVER=${kernelver} clean"
|
||||
BUILT_MODULE_NAME[0]="btusb"
|
||||
BUILT_MODULE_LOCATION[0]="."
|
||||
DEST_MODULE_LOCATION[0]="/updates/dkms"
|
||||
AUTOINSTALL="no"
|
||||
EOF
|
||||
}
|
||||
|
||||
download_sources() {
|
||||
local file
|
||||
|
||||
install -d "${SOURCE_DIR}/drivers/bluetooth"
|
||||
|
||||
for file in btusb.c btintel.h btbcm.h btrtl.h btmtk.h; do
|
||||
log "downloading ${file} from Linux ${UPSTREAM_TAG}"
|
||||
curl --fail --location --silent --show-error \
|
||||
"${BASE_URL}/${file}" \
|
||||
--output "${SOURCE_DIR}/drivers/bluetooth/${file}"
|
||||
done
|
||||
}
|
||||
|
||||
install_test_module() {
|
||||
require_root
|
||||
require_command curl
|
||||
require_command dkms
|
||||
require_command make
|
||||
require_command modprobe
|
||||
require_command patch
|
||||
|
||||
[ -n "${UPSTREAM_VERSION}" ] || die "could not derive an upstream version from ${KERNEL_RELEASE}"
|
||||
[ -r "${KERNEL_BUILD_DIR}/Makefile" ] || die "kernel build tree not found: ${KERNEL_BUILD_DIR}"
|
||||
[ -r "${PATCH_FILE}" ] || die "patch file not found: ${PATCH_FILE}"
|
||||
|
||||
if command -v lsusb >/dev/null 2>&1 && ! lsusb -d 368b:8d81 >/dev/null 2>&1; then
|
||||
die "the test patch is restricted to USB device 368b:8d81, which is not connected"
|
||||
fi
|
||||
|
||||
if dkms status -m "${PACKAGE_NAME}" -v "${PACKAGE_VERSION}" 2>/dev/null | grep -q .; then
|
||||
log "removing an earlier test build"
|
||||
dkms remove -m "${PACKAGE_NAME}" -v "${PACKAGE_VERSION}" --all || true
|
||||
fi
|
||||
|
||||
rm -rf -- "${SOURCE_DIR}"
|
||||
download_sources
|
||||
write_build_files
|
||||
|
||||
log "applying the AIC bulk TX ZLP quirk"
|
||||
patch --directory "${SOURCE_DIR}" --strip 1 --input "${PATCH_FILE}"
|
||||
|
||||
log "building patched system btusb 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}"
|
||||
|
||||
activate_test_module
|
||||
|
||||
log "loaded module: $(modinfo -n btusb)"
|
||||
log "test AAC playback for at least one hour; use '$0 remove' to restore the distribution module"
|
||||
}
|
||||
|
||||
remove_test_module() {
|
||||
require_root
|
||||
require_command dkms
|
||||
require_command modprobe
|
||||
|
||||
stop_bluetooth
|
||||
modprobe -r btusb || {
|
||||
start_bluetooth
|
||||
die "could not unload btusb; disconnect active Bluetooth devices and retry"
|
||||
}
|
||||
|
||||
dkms remove -m "${PACKAGE_NAME}" -v "${PACKAGE_VERSION}" --all || true
|
||||
rm -rf -- "${SOURCE_DIR}"
|
||||
depmod -a "${KERNEL_RELEASE}"
|
||||
|
||||
modprobe btusb || {
|
||||
start_bluetooth
|
||||
die "the distribution btusb module could not be loaded"
|
||||
}
|
||||
start_bluetooth
|
||||
|
||||
log "restored module: $(modinfo -n btusb)"
|
||||
}
|
||||
|
||||
show_status() {
|
||||
printf 'kernel: %s\n' "${KERNEL_RELEASE}"
|
||||
printf 'upstream tag: %s\n' "${UPSTREAM_TAG}"
|
||||
printf 'btusb module: %s\n' "$(modinfo -n btusb 2>/dev/null || printf 'not found')"
|
||||
printf 'dkms status: %s\n' "$(dkms status -m "${PACKAGE_NAME}" -v "${PACKAGE_VERSION}" 2>/dev/null || printf 'not installed')"
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
install)
|
||||
install_test_module
|
||||
;;
|
||||
remove)
|
||||
remove_test_module
|
||||
;;
|
||||
status)
|
||||
show_status
|
||||
;;
|
||||
*)
|
||||
printf 'Usage: %s {install|remove|status}\n' "$0" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
@@ -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
|
||||
@@ -0,0 +1,115 @@
|
||||
# Issue #63: standard `btusb` ZLP companion-module test
|
||||
|
||||
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 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 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.
|
||||
|
||||
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:
|
||||
|
||||
- USB device `368b:8d81`;
|
||||
- bulk OUT transfer;
|
||||
- endpoint declared by Bluetooth interface 0 (`e0/01/01`).
|
||||
|
||||
Both paths add `URB_ZERO_PACKET` before the USB core submits the transfer. The
|
||||
Wi-Fi interface and unrelated USB devices are left unchanged.
|
||||
|
||||
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.
|
||||
|
||||
## Before installing
|
||||
|
||||
First remove the earlier patched-`btusb` diagnostic build, if installed:
|
||||
|
||||
```bash
|
||||
cd ../issue63-btusb-zlp
|
||||
sudo ./btusb-zlp-test.sh remove
|
||||
```
|
||||
|
||||
An obsolete `aic_btusb` installation must also be unloaded and removed before
|
||||
this test. Verify that the AIC Bluetooth interfaces use the system driver:
|
||||
|
||||
```bash
|
||||
lsusb -t
|
||||
lsmod | grep -E '^(btusb|aic_btusb)\b'
|
||||
```
|
||||
|
||||
## 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:
|
||||
|
||||
```text
|
||||
quirk loaded: yes
|
||||
active hook: btusb:alloc_bulk_urb
|
||||
legacy aic_btusb: not loaded
|
||||
AIC BT driver: btusb
|
||||
```
|
||||
|
||||
`active hook: usb_submit_urb` is also valid when the distribution compiler
|
||||
inlined the preferred private `btusb` function.
|
||||
|
||||
Select AAC and play audio for at least one hour:
|
||||
|
||||
```bash
|
||||
bluetoothctl scan off
|
||||
pactl set-card-profile bluez_card.28_6F_40_46_AB_B1 a2dp-sink-aac
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
## 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 `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.
|
||||
+231
@@ -0,0 +1,231 @@
|
||||
#!/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)"
|
||||
|
||||
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 "${SCRIPT_DIR}/aic_zlp_quirk.c" "${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 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
|
||||
|
||||
dkms_status="$(dkms status -m "${PACKAGE_NAME}" -v "${PACKAGE_VERSION}" 2>/dev/null || true)"
|
||||
if [ -z "${dkms_status}" ]; then
|
||||
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 'dkms status: %s\n' "${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
|
||||
@@ -0,0 +1,228 @@
|
||||
// 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");
|
||||
@@ -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"
|
||||
Reference in New Issue
Block a user