* [PATCH] Bluetooth: btusb: Gate ACL delivery on HCI handle presence @ 2026-07-22 1:23 Amir Abudubai 2026-07-22 4:48 ` bluez.test.bot 2026-07-27 20:34 ` [PATCH] " Luiz Augusto von Dentz 0 siblings, 2 replies; 6+ messages in thread From: Amir Abudubai @ 2026-07-22 1:23 UTC (permalink / raw) To: linux-bluetooth; +Cc: marcel, luiz.dentz, Amir Abudubai On some USB devices, the interrupt-IN endpoint can lag the bulk-IN endpoint by multiple polling intervals. Early ACL data can then reach HCI before the Connection Complete event establishes its handle, causing the data to be dropped for an unknown handle. This issue was observed and the fix verified on the following adapters: * 8087:0025 Intel Corp. Wireless-AC 9260 Bluetooth Adapter * 7392:c611 Edimax Technology Co., Ltd Edimax Bluetooth Adapter Extend the existing poll_sync queue to support independent release gates while preserving its underlying behavior. Add an optional gate which holds ACL delivery until the HCI handle appears or a bounded timeout expires. Anchor both time gates to packet arrival to prevent timeout drift from accumulating as queued packets reach the FIFO head. BT_HCIBTUSB_EARLY_ACL_HOLD controls whether the gate is enabled by default. The force_early_acl_hold debugfs entry controls it per adapter while the adapter is down. Assisted-by: OpenCode:openai/gpt-5.6-sol Signed-off-by: Amir Abudubai <amirabudubai@gmail.com> --- drivers/bluetooth/Kconfig | 10 ++ drivers/bluetooth/btusb.c | 292 +++++++++++++++++++++++++++++++++----- 2 files changed, 266 insertions(+), 36 deletions(-) diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig index 4e8c24d757e9..648c0d8b1496 100644 --- a/drivers/bluetooth/Kconfig +++ b/drivers/bluetooth/Kconfig @@ -56,6 +56,16 @@ config BT_HCIBTUSB_POLL_SYNC Say Y here to enable USB poll_sync for Bluetooth USB devices by default. +config BT_HCIBTUSB_EARLY_ACL_HOLD + bool "Enable early ACL hold for Bluetooth USB devices by default" + depends on BT_HCIBTUSB + help + Hold ACL packets whose connection handle is not yet known until the + connection appears or a bounded timeout expires. + + Say Y here to enable early ACL hold for Bluetooth USB devices by + default. + config BT_HCIBTUSB_BCM bool "Broadcom protocol support" depends on BT_HCIBTUSB diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index 4d59b28ab30f..a59b0ed91d18 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -34,6 +34,8 @@ static bool disable_scofix; static bool force_scofix; static bool enable_autosuspend = IS_ENABLED(CONFIG_BT_HCIBTUSB_AUTOSUSPEND); static bool enable_poll_sync = IS_ENABLED(CONFIG_BT_HCIBTUSB_POLL_SYNC); +static bool enable_early_acl_hold = + IS_ENABLED(CONFIG_BT_HCIBTUSB_EARLY_ACL_HOLD); static bool reset = true; static struct usb_driver btusb_driver; @@ -956,6 +958,8 @@ struct btqca_data { }; #define BTUSB_MAX_ISOC_FRAMES 10 +#define BTUSB_RX_RECHECK_DELAY msecs_to_jiffies(1) +#define BTUSB_EARLY_ACL_HOLD_MIN_US 5000U #define BTUSB_INTR_RUNNING 0 #define BTUSB_BULK_RUNNING 1 @@ -987,7 +991,10 @@ struct btusb_data { unsigned long flags; bool poll_sync; + bool early_acl_hold; int intr_interval; + unsigned long early_acl_timeout; + ktime_t last_event; struct work_struct work; struct work_struct waker; struct delayed_work rx_work; @@ -1252,14 +1259,100 @@ static inline void btusb_free_frags(struct btusb_data *data) spin_unlock_irqrestore(&data->rxlock, flags); } -static int btusb_recv_event(struct btusb_data *data, struct sk_buff *skb) +static void btusb_rx_queue_purge(struct btusb_data *data) +{ + unsigned long flags; + + spin_lock_irqsave(&data->rxlock, flags); + skb_queue_purge(&data->acl_q); + spin_unlock_irqrestore(&data->rxlock, flags); +} + +static ktime_t btusb_rx_deadline(struct sk_buff *skb, unsigned long timeout) { - if (data->intr_interval) { - /* Trigger dequeue immediately if an event is received */ - schedule_delayed_work(&data->rx_work, 0); + return ktime_add_ns(skb->tstamp, jiffies_to_nsecs(timeout)); +} + +static bool btusb_rx_hold_expired(struct sk_buff *skb, unsigned long timeout, + ktime_t now) +{ + return !ktime_before(now, btusb_rx_deadline(skb, timeout)); +} + +static unsigned long btusb_rx_hold_delay(struct sk_buff *skb, + unsigned long timeout, ktime_t now) +{ + s64 remaining = ktime_to_ns(ktime_sub(btusb_rx_deadline(skb, timeout), + now)); + + if (remaining <= 0) + return 0; + + return max_t(unsigned long, 1, nsecs_to_jiffies(remaining)); +} + +static bool btusb_poll_sync_should_hold(struct btusb_data *data, + struct sk_buff *skb, ktime_t now) +{ + unsigned long interval = READ_ONCE(data->intr_interval); + + if (!interval || !ktime_before(data->last_event, skb->tstamp)) + return false; + + return !btusb_rx_hold_expired(skb, interval, now); +} + +static bool btusb_acl_should_hold(struct btusb_data *data, + struct sk_buff *skb, ktime_t now) +{ + unsigned long timeout = READ_ONCE(data->early_acl_timeout); + u16 handle; + + if (!timeout || hci_dev_test_flag(data->hdev, HCI_USER_CHANNEL) || + btusb_rx_hold_expired(skb, timeout, now) || + skb->len < HCI_ACL_HDR_SIZE) + return false; + + handle = le16_to_cpu(hci_acl_hdr(skb)->handle); + + /* Only use the returned connection as an existence snapshot. */ + return !hci_conn_hash_lookup_handle(data->hdev, hci_handle(handle)); +} + +static unsigned long btusb_rx_delay(struct btusb_data *data, + struct sk_buff *skb, ktime_t now, + bool poll_hold, bool acl_hold) +{ + unsigned long timeout; + + if (poll_hold) { + timeout = READ_ONCE(data->intr_interval); + return btusb_rx_hold_delay(skb, timeout, now); + } + + if (acl_hold) { + timeout = READ_ONCE(data->early_acl_timeout); + return min(BTUSB_RX_RECHECK_DELAY, + btusb_rx_hold_delay(skb, timeout, now)); } - return data->recv_event(data->hdev, skb); + return 0; +} + +static int btusb_recv_event(struct btusb_data *data, struct sk_buff *skb) +{ + int err; + + err = data->recv_event(data->hdev, skb); + + if (READ_ONCE(data->intr_interval)) + data->last_event = ktime_get(); + + if (READ_ONCE(data->intr_interval) || + READ_ONCE(data->early_acl_timeout)) + mod_delayed_work(system_wq, &data->rx_work, 0); + + return err; } static int btusb_recv_intr(struct btusb_data *data, void *buffer, int count) @@ -1332,14 +1425,31 @@ static int btusb_recv_intr(struct btusb_data *data, void *buffer, int count) static int btusb_recv_acl(struct btusb_data *data, struct sk_buff *skb) { - /* Only queue ACL packet if intr_interval is set as it means - * force_poll_sync has been enabled. - */ - if (!data->intr_interval) + bool poll_hold, acl_hold; + bool queue_empty; + ktime_t now; + + if (!READ_ONCE(data->intr_interval) && + !READ_ONCE(data->early_acl_timeout)) + return data->recv_acl(data->hdev, skb); + + now = ktime_get(); + /* hci_recv_frame() replaces this temporary monotonic arrival time. */ + skb_set_delivery_time(skb, now, SKB_CLOCK_MONOTONIC); + + queue_empty = skb_queue_empty(&data->acl_q); + poll_hold = btusb_poll_sync_should_hold(data, skb, now); + acl_hold = btusb_acl_should_hold(data, skb, now); + + if (queue_empty && !poll_hold && !acl_hold) return data->recv_acl(data->hdev, skb); skb_queue_tail(&data->acl_q, skb); - schedule_delayed_work(&data->rx_work, data->intr_interval); + + if (queue_empty) + schedule_delayed_work(&data->rx_work, + btusb_rx_delay(data, skb, now, + poll_hold, acl_hold)); return 0; } @@ -1539,6 +1649,10 @@ static int btusb_submit_intr_urb(struct hci_dev *hdev, gfp_t mem_flags) struct btusb_data *data = hci_get_drvdata(hdev); struct urb *urb; unsigned char *buf; + unsigned long early_acl_timeout = 0; + int intr_interval = 0; + unsigned int hold_us; + unsigned int interval_us; unsigned int pipe; int err, size; @@ -1587,27 +1701,52 @@ static int btusb_submit_intr_urb(struct hci_dev *hdev, gfp_t mem_flags) } /* Only initialize intr_interval if URB poll sync is enabled */ - if (!data->poll_sync) - goto done; + if (data->poll_sync) { + /* The units are frames (milliseconds) for full and low speed + * devices, and microframes (1/8 millisecond) for highspeed and + * SuperSpeed devices. + * + * This is done once on open/resume so it shouldn't change even + * if force_poll_sync changes. + */ + switch (urb->dev->speed) { + case USB_SPEED_SUPER_PLUS: + case USB_SPEED_SUPER: + intr_interval = usecs_to_jiffies(urb->interval * 125); + break; + default: + intr_interval = msecs_to_jiffies(urb->interval); + break; + } + } - /* The units are frames (milliseconds) for full and low speed devices, - * and microframes (1/8 millisecond) for highspeed and SuperSpeed - * devices. - * - * This is done once on open/resume so it shouldn't change even if - * force_poll_sync changes. - */ - switch (urb->dev->speed) { - case USB_SPEED_SUPER_PLUS: - case USB_SPEED_SUPER: /* units are 125us */ - data->intr_interval = usecs_to_jiffies(urb->interval * 125); - break; - default: - data->intr_interval = msecs_to_jiffies(urb->interval); - break; + interval_us = 0; + if (data->early_acl_hold && urb->interval > 0) { + switch (urb->dev->speed) { + case USB_SPEED_SUPER_PLUS: + case USB_SPEED_SUPER: + case USB_SPEED_HIGH: + interval_us = urb->interval * 125; + break; + case USB_SPEED_FULL: + interval_us = urb->interval * USEC_PER_MSEC; + break; + default: + break; + } } -done: + if (interval_us) { + /* Allow an event which misses one polling window to arrive in + * the next. + */ + hold_us = max(BTUSB_EARLY_ACL_HOLD_MIN_US, interval_us * 2); + early_acl_timeout = usecs_to_jiffies(hold_us); + } + + WRITE_ONCE(data->intr_interval, intr_interval); + WRITE_ONCE(data->early_acl_timeout, early_acl_timeout); + usb_free_urb(urb); return err; @@ -2077,18 +2216,22 @@ static int btusb_close(struct hci_dev *hdev) BT_DBG("%s", hdev->name); - cancel_delayed_work(&data->rx_work); + /* Stop queued RX delivery before shutting down the endpoints. */ + cancel_delayed_work_sync(&data->rx_work); cancel_work_sync(&data->work); cancel_work_sync(&data->waker); - skb_queue_purge(&data->acl_q); - clear_bit(BTUSB_ISOC_RUNNING, &data->flags); clear_bit(BTUSB_BULK_RUNNING, &data->flags); clear_bit(BTUSB_INTR_RUNNING, &data->flags); clear_bit(BTUSB_DIAG_RUNNING, &data->flags); btusb_stop_traffic(data); + + /* An in-flight URB completion may have requeued RX work. */ + cancel_delayed_work_sync(&data->rx_work); + + btusb_rx_queue_purge(data); btusb_free_frags(data); err = usb_autopm_get_interface(data->intf); @@ -2114,9 +2257,9 @@ static int btusb_flush(struct hci_dev *hdev) BT_DBG("%s", hdev->name); - cancel_delayed_work(&data->rx_work); + cancel_delayed_work_sync(&data->rx_work); - skb_queue_purge(&data->acl_q); + btusb_rx_queue_purge(data); usb_kill_anchored_urbs(&data->tx_anchor); btusb_free_frags(data); @@ -2497,11 +2640,40 @@ static void btusb_rx_work(struct work_struct *work) { struct btusb_data *data = container_of(work, struct btusb_data, rx_work.work); + unsigned long delay; + unsigned long flags; struct sk_buff *skb; + bool poll_hold, acl_hold; + ktime_t now; + + for (;;) { + spin_lock_irqsave(&data->rxlock, flags); + now = ktime_get(); + skb = skb_peek(&data->acl_q); + + if (!skb) { + spin_unlock_irqrestore(&data->rxlock, flags); + return; + } + + poll_hold = btusb_poll_sync_should_hold(data, skb, now); + acl_hold = btusb_acl_should_hold(data, skb, now); + if (!poll_hold && !acl_hold) { + skb = skb_dequeue(&data->acl_q); + data->recv_acl(data->hdev, skb); + spin_unlock_irqrestore(&data->rxlock, flags); + continue; + } + + delay = btusb_rx_delay(data, skb, now, poll_hold, acl_hold); + spin_unlock_irqrestore(&data->rxlock, flags); - /* Dequeue ACL data received during the interval */ - while ((skb = skb_dequeue(&data->acl_q))) - data->recv_acl(data->hdev, skb); + /* A zero-delay event kick that raced with this worker remains + * pending; schedule_delayed_work() cannot move it later. + */ + schedule_delayed_work(&data->rx_work, delay); + return; + } } static int btusb_setup_bcm92035(struct hci_dev *hdev) @@ -3967,6 +4139,51 @@ static const struct file_operations force_poll_sync_fops = { .llseek = default_llseek, }; +static ssize_t force_early_acl_hold_read(struct file *file, + char __user *user_buf, size_t count, + loff_t *ppos) +{ + struct btusb_data *data = file->private_data; + char buf[3]; + + buf[0] = data->early_acl_hold ? 'Y' : 'N'; + buf[1] = '\n'; + buf[2] = '\0'; + return simple_read_from_buffer(user_buf, count, ppos, buf, 2); +} + +static ssize_t force_early_acl_hold_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct btusb_data *data = file->private_data; + bool enable; + int err; + + err = kstrtobool_from_user(user_buf, count, &enable); + if (err) + return err; + + /* Only allow changes while the adapter is down */ + if (test_bit(HCI_UP, &data->hdev->flags)) + return -EPERM; + + if (data->early_acl_hold == enable) + return -EALREADY; + + data->early_acl_hold = enable; + + return count; +} + +static const struct file_operations force_early_acl_hold_fops = { + .owner = THIS_MODULE, + .open = simple_open, + .read = force_early_acl_hold_read, + .write = force_early_acl_hold_write, + .llseek = default_llseek, +}; + #define BTUSB_HCI_DRV_OP_SUPPORTED_ALTSETTINGS \ hci_opcode_pack(HCI_DRV_OGF_DRIVER_SPECIFIC, 0x0000) #define BTUSB_HCI_DRV_SUPPORTED_ALTSETTINGS_SIZE 0 @@ -4464,6 +4681,7 @@ static int btusb_probe(struct usb_interface *intf, usb_enable_autosuspend(data->udev); data->poll_sync = enable_poll_sync; + data->early_acl_hold = enable_early_acl_hold; err = hci_register_dev(hdev); if (err < 0) @@ -4473,6 +4691,8 @@ static int btusb_probe(struct usb_interface *intf, debugfs_create_file("force_poll_sync", 0644, hdev->debugfs, data, &force_poll_sync_fops); + debugfs_create_file("force_early_acl_hold", 0644, hdev->debugfs, data, + &force_early_acl_hold_fops); return 0; base-commit: 6f55ad8fb0acd861e5c4e526b2272fc71952e1e3 -- 2.43.0 ^ permalink raw reply related [flat|nested] 6+ messages in thread
* RE: Bluetooth: btusb: Gate ACL delivery on HCI handle presence 2026-07-22 1:23 [PATCH] Bluetooth: btusb: Gate ACL delivery on HCI handle presence Amir Abudubai @ 2026-07-22 4:48 ` bluez.test.bot 2026-07-27 20:34 ` [PATCH] " Luiz Augusto von Dentz 1 sibling, 0 replies; 6+ messages in thread From: bluez.test.bot @ 2026-07-22 4:48 UTC (permalink / raw) To: linux-bluetooth, amirabudubai [-- Attachment #1: Type: text/plain, Size: 1181 bytes --] This is automated email and please do not reply to this email! Dear submitter, Thank you for submitting the patches to the linux bluetooth mailing list. This is a CI test results with your patch series: PW Link:https://patchwork.kernel.org/project/bluetooth/list/?series=1132023 ---Test result--- Test Summary: CheckPatch PASS 0.82 seconds VerifyFixes PASS 0.09 seconds VerifySignedoff PASS 0.09 seconds GitLint PASS 0.23 seconds SubjectPrefix PASS 0.08 seconds BuildKernel PASS 21.37 seconds CheckAllWarning PASS 23.88 seconds CheckSparse PASS 22.91 seconds BuildKernel32 PASS 21.80 seconds CheckKernelLLVM SKIP 0.00 seconds TestRunnerSetup PASS 401.47 seconds IncrementalBuild PASS 21.52 seconds Details ############################## Test: CheckKernelLLVM - SKIP Desc: Build kernel with LLVM + context analysis Output: Clang not found https://github.com/bluez/bluetooth-next/pull/472 --- Regards, Linux Bluetooth ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH] Bluetooth: btusb: Gate ACL delivery on HCI handle presence 2026-07-22 1:23 [PATCH] Bluetooth: btusb: Gate ACL delivery on HCI handle presence Amir Abudubai 2026-07-22 4:48 ` bluez.test.bot @ 2026-07-27 20:34 ` Luiz Augusto von Dentz 2026-07-27 23:02 ` Amir Abudubai 1 sibling, 1 reply; 6+ messages in thread From: Luiz Augusto von Dentz @ 2026-07-27 20:34 UTC (permalink / raw) To: Amir Abudubai; +Cc: linux-bluetooth, marcel Hi Amir, On Tue, Jul 21, 2026 at 9:23 PM Amir Abudubai <amirabudubai@gmail.com> wrote: > > On some USB devices, the interrupt-IN endpoint can lag the bulk-IN > endpoint by multiple polling intervals. Early ACL data can then reach > HCI before the Connection Complete event establishes its handle, causing > the data to be dropped for an unknown handle. > > This issue was observed and the fix verified on the following adapters: > * 8087:0025 Intel Corp. Wireless-AC 9260 Bluetooth Adapter > * 7392:c611 Edimax Technology Co., Ltd Edimax Bluetooth Adapter > > Extend the existing poll_sync queue to support independent release gates > while preserving its underlying behavior. Add an optional gate which > holds ACL delivery until the HCI handle appears or a bounded timeout > expires. Anchor both time gates to packet arrival to prevent timeout > drift from accumulating as queued packets reach the FIFO head. > > BT_HCIBTUSB_EARLY_ACL_HOLD controls whether the gate is enabled by > default. The force_early_acl_hold debugfs entry controls it per adapter > while the adapter is down. > > Assisted-by: OpenCode:openai/gpt-5.6-sol > Signed-off-by: Amir Abudubai <amirabudubai@gmail.com> This really sounds like an over-engineered design; I'm not sure why it couldn't be done by just increasing the number of polls we wait before delivering the ACL packet, it has been quite a long while we don't receive any reports of such races still exists. Also, some vendors use special handles for debugging, these never have any completing event or anything. > --- > drivers/bluetooth/Kconfig | 10 ++ > drivers/bluetooth/btusb.c | 292 +++++++++++++++++++++++++++++++++----- > 2 files changed, 266 insertions(+), 36 deletions(-) > > diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig > index 4e8c24d757e9..648c0d8b1496 100644 > --- a/drivers/bluetooth/Kconfig > +++ b/drivers/bluetooth/Kconfig > @@ -56,6 +56,16 @@ config BT_HCIBTUSB_POLL_SYNC > Say Y here to enable USB poll_sync for Bluetooth USB devices by > default. > > +config BT_HCIBTUSB_EARLY_ACL_HOLD > + bool "Enable early ACL hold for Bluetooth USB devices by default" > + depends on BT_HCIBTUSB > + help > + Hold ACL packets whose connection handle is not yet known until the > + connection appears or a bounded timeout expires. > + > + Say Y here to enable early ACL hold for Bluetooth USB devices by > + default. > + > config BT_HCIBTUSB_BCM > bool "Broadcom protocol support" > depends on BT_HCIBTUSB > diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c > index 4d59b28ab30f..a59b0ed91d18 100644 > --- a/drivers/bluetooth/btusb.c > +++ b/drivers/bluetooth/btusb.c > @@ -34,6 +34,8 @@ static bool disable_scofix; > static bool force_scofix; > static bool enable_autosuspend = IS_ENABLED(CONFIG_BT_HCIBTUSB_AUTOSUSPEND); > static bool enable_poll_sync = IS_ENABLED(CONFIG_BT_HCIBTUSB_POLL_SYNC); > +static bool enable_early_acl_hold = > + IS_ENABLED(CONFIG_BT_HCIBTUSB_EARLY_ACL_HOLD); > static bool reset = true; > > static struct usb_driver btusb_driver; > @@ -956,6 +958,8 @@ struct btqca_data { > }; > > #define BTUSB_MAX_ISOC_FRAMES 10 > +#define BTUSB_RX_RECHECK_DELAY msecs_to_jiffies(1) > +#define BTUSB_EARLY_ACL_HOLD_MIN_US 5000U > > #define BTUSB_INTR_RUNNING 0 > #define BTUSB_BULK_RUNNING 1 > @@ -987,7 +991,10 @@ struct btusb_data { > unsigned long flags; > > bool poll_sync; > + bool early_acl_hold; > int intr_interval; > + unsigned long early_acl_timeout; > + ktime_t last_event; > struct work_struct work; > struct work_struct waker; > struct delayed_work rx_work; > @@ -1252,14 +1259,100 @@ static inline void btusb_free_frags(struct btusb_data *data) > spin_unlock_irqrestore(&data->rxlock, flags); > } > > -static int btusb_recv_event(struct btusb_data *data, struct sk_buff *skb) > +static void btusb_rx_queue_purge(struct btusb_data *data) > +{ > + unsigned long flags; > + > + spin_lock_irqsave(&data->rxlock, flags); > + skb_queue_purge(&data->acl_q); > + spin_unlock_irqrestore(&data->rxlock, flags); > +} > + > +static ktime_t btusb_rx_deadline(struct sk_buff *skb, unsigned long timeout) > { > - if (data->intr_interval) { > - /* Trigger dequeue immediately if an event is received */ > - schedule_delayed_work(&data->rx_work, 0); > + return ktime_add_ns(skb->tstamp, jiffies_to_nsecs(timeout)); > +} > + > +static bool btusb_rx_hold_expired(struct sk_buff *skb, unsigned long timeout, > + ktime_t now) > +{ > + return !ktime_before(now, btusb_rx_deadline(skb, timeout)); > +} > + > +static unsigned long btusb_rx_hold_delay(struct sk_buff *skb, > + unsigned long timeout, ktime_t now) > +{ > + s64 remaining = ktime_to_ns(ktime_sub(btusb_rx_deadline(skb, timeout), > + now)); > + > + if (remaining <= 0) > + return 0; > + > + return max_t(unsigned long, 1, nsecs_to_jiffies(remaining)); > +} > + > +static bool btusb_poll_sync_should_hold(struct btusb_data *data, > + struct sk_buff *skb, ktime_t now) > +{ > + unsigned long interval = READ_ONCE(data->intr_interval); > + > + if (!interval || !ktime_before(data->last_event, skb->tstamp)) > + return false; > + > + return !btusb_rx_hold_expired(skb, interval, now); > +} > + > +static bool btusb_acl_should_hold(struct btusb_data *data, > + struct sk_buff *skb, ktime_t now) > +{ > + unsigned long timeout = READ_ONCE(data->early_acl_timeout); > + u16 handle; > + > + if (!timeout || hci_dev_test_flag(data->hdev, HCI_USER_CHANNEL) || > + btusb_rx_hold_expired(skb, timeout, now) || > + skb->len < HCI_ACL_HDR_SIZE) > + return false; > + > + handle = le16_to_cpu(hci_acl_hdr(skb)->handle); > + > + /* Only use the returned connection as an existence snapshot. */ > + return !hci_conn_hash_lookup_handle(data->hdev, hci_handle(handle)); > +} > + > +static unsigned long btusb_rx_delay(struct btusb_data *data, > + struct sk_buff *skb, ktime_t now, > + bool poll_hold, bool acl_hold) > +{ > + unsigned long timeout; > + > + if (poll_hold) { > + timeout = READ_ONCE(data->intr_interval); > + return btusb_rx_hold_delay(skb, timeout, now); > + } > + > + if (acl_hold) { > + timeout = READ_ONCE(data->early_acl_timeout); > + return min(BTUSB_RX_RECHECK_DELAY, > + btusb_rx_hold_delay(skb, timeout, now)); > } > > - return data->recv_event(data->hdev, skb); > + return 0; > +} > + > +static int btusb_recv_event(struct btusb_data *data, struct sk_buff *skb) > +{ > + int err; > + > + err = data->recv_event(data->hdev, skb); > + > + if (READ_ONCE(data->intr_interval)) > + data->last_event = ktime_get(); > + > + if (READ_ONCE(data->intr_interval) || > + READ_ONCE(data->early_acl_timeout)) > + mod_delayed_work(system_wq, &data->rx_work, 0); > + > + return err; > } > > static int btusb_recv_intr(struct btusb_data *data, void *buffer, int count) > @@ -1332,14 +1425,31 @@ static int btusb_recv_intr(struct btusb_data *data, void *buffer, int count) > > static int btusb_recv_acl(struct btusb_data *data, struct sk_buff *skb) > { > - /* Only queue ACL packet if intr_interval is set as it means > - * force_poll_sync has been enabled. > - */ > - if (!data->intr_interval) > + bool poll_hold, acl_hold; > + bool queue_empty; > + ktime_t now; > + > + if (!READ_ONCE(data->intr_interval) && > + !READ_ONCE(data->early_acl_timeout)) > + return data->recv_acl(data->hdev, skb); > + > + now = ktime_get(); > + /* hci_recv_frame() replaces this temporary monotonic arrival time. */ > + skb_set_delivery_time(skb, now, SKB_CLOCK_MONOTONIC); > + > + queue_empty = skb_queue_empty(&data->acl_q); > + poll_hold = btusb_poll_sync_should_hold(data, skb, now); > + acl_hold = btusb_acl_should_hold(data, skb, now); > + > + if (queue_empty && !poll_hold && !acl_hold) > return data->recv_acl(data->hdev, skb); > > skb_queue_tail(&data->acl_q, skb); > - schedule_delayed_work(&data->rx_work, data->intr_interval); > + > + if (queue_empty) > + schedule_delayed_work(&data->rx_work, > + btusb_rx_delay(data, skb, now, > + poll_hold, acl_hold)); > > return 0; > } > @@ -1539,6 +1649,10 @@ static int btusb_submit_intr_urb(struct hci_dev *hdev, gfp_t mem_flags) > struct btusb_data *data = hci_get_drvdata(hdev); > struct urb *urb; > unsigned char *buf; > + unsigned long early_acl_timeout = 0; > + int intr_interval = 0; > + unsigned int hold_us; > + unsigned int interval_us; > unsigned int pipe; > int err, size; > > @@ -1587,27 +1701,52 @@ static int btusb_submit_intr_urb(struct hci_dev *hdev, gfp_t mem_flags) > } > > /* Only initialize intr_interval if URB poll sync is enabled */ > - if (!data->poll_sync) > - goto done; > + if (data->poll_sync) { > + /* The units are frames (milliseconds) for full and low speed > + * devices, and microframes (1/8 millisecond) for highspeed and > + * SuperSpeed devices. > + * > + * This is done once on open/resume so it shouldn't change even > + * if force_poll_sync changes. > + */ > + switch (urb->dev->speed) { > + case USB_SPEED_SUPER_PLUS: > + case USB_SPEED_SUPER: > + intr_interval = usecs_to_jiffies(urb->interval * 125); > + break; > + default: > + intr_interval = msecs_to_jiffies(urb->interval); > + break; > + } > + } > > - /* The units are frames (milliseconds) for full and low speed devices, > - * and microframes (1/8 millisecond) for highspeed and SuperSpeed > - * devices. > - * > - * This is done once on open/resume so it shouldn't change even if > - * force_poll_sync changes. > - */ > - switch (urb->dev->speed) { > - case USB_SPEED_SUPER_PLUS: > - case USB_SPEED_SUPER: /* units are 125us */ > - data->intr_interval = usecs_to_jiffies(urb->interval * 125); > - break; > - default: > - data->intr_interval = msecs_to_jiffies(urb->interval); > - break; > + interval_us = 0; > + if (data->early_acl_hold && urb->interval > 0) { > + switch (urb->dev->speed) { > + case USB_SPEED_SUPER_PLUS: > + case USB_SPEED_SUPER: > + case USB_SPEED_HIGH: > + interval_us = urb->interval * 125; > + break; > + case USB_SPEED_FULL: > + interval_us = urb->interval * USEC_PER_MSEC; > + break; > + default: > + break; > + } > } > > -done: > + if (interval_us) { > + /* Allow an event which misses one polling window to arrive in > + * the next. > + */ > + hold_us = max(BTUSB_EARLY_ACL_HOLD_MIN_US, interval_us * 2); > + early_acl_timeout = usecs_to_jiffies(hold_us); > + } > + > + WRITE_ONCE(data->intr_interval, intr_interval); > + WRITE_ONCE(data->early_acl_timeout, early_acl_timeout); > + > usb_free_urb(urb); > > return err; > @@ -2077,18 +2216,22 @@ static int btusb_close(struct hci_dev *hdev) > > BT_DBG("%s", hdev->name); > > - cancel_delayed_work(&data->rx_work); > + /* Stop queued RX delivery before shutting down the endpoints. */ > + cancel_delayed_work_sync(&data->rx_work); > cancel_work_sync(&data->work); > cancel_work_sync(&data->waker); > > - skb_queue_purge(&data->acl_q); > - > clear_bit(BTUSB_ISOC_RUNNING, &data->flags); > clear_bit(BTUSB_BULK_RUNNING, &data->flags); > clear_bit(BTUSB_INTR_RUNNING, &data->flags); > clear_bit(BTUSB_DIAG_RUNNING, &data->flags); > > btusb_stop_traffic(data); > + > + /* An in-flight URB completion may have requeued RX work. */ > + cancel_delayed_work_sync(&data->rx_work); > + > + btusb_rx_queue_purge(data); > btusb_free_frags(data); > > err = usb_autopm_get_interface(data->intf); > @@ -2114,9 +2257,9 @@ static int btusb_flush(struct hci_dev *hdev) > > BT_DBG("%s", hdev->name); > > - cancel_delayed_work(&data->rx_work); > + cancel_delayed_work_sync(&data->rx_work); > > - skb_queue_purge(&data->acl_q); > + btusb_rx_queue_purge(data); > > usb_kill_anchored_urbs(&data->tx_anchor); > btusb_free_frags(data); > @@ -2497,11 +2640,40 @@ static void btusb_rx_work(struct work_struct *work) > { > struct btusb_data *data = container_of(work, struct btusb_data, > rx_work.work); > + unsigned long delay; > + unsigned long flags; > struct sk_buff *skb; > + bool poll_hold, acl_hold; > + ktime_t now; > + > + for (;;) { > + spin_lock_irqsave(&data->rxlock, flags); > + now = ktime_get(); > + skb = skb_peek(&data->acl_q); > + > + if (!skb) { > + spin_unlock_irqrestore(&data->rxlock, flags); > + return; > + } > + > + poll_hold = btusb_poll_sync_should_hold(data, skb, now); > + acl_hold = btusb_acl_should_hold(data, skb, now); > + if (!poll_hold && !acl_hold) { > + skb = skb_dequeue(&data->acl_q); > + data->recv_acl(data->hdev, skb); > + spin_unlock_irqrestore(&data->rxlock, flags); > + continue; > + } > + > + delay = btusb_rx_delay(data, skb, now, poll_hold, acl_hold); > + spin_unlock_irqrestore(&data->rxlock, flags); > > - /* Dequeue ACL data received during the interval */ > - while ((skb = skb_dequeue(&data->acl_q))) > - data->recv_acl(data->hdev, skb); > + /* A zero-delay event kick that raced with this worker remains > + * pending; schedule_delayed_work() cannot move it later. > + */ > + schedule_delayed_work(&data->rx_work, delay); > + return; > + } > } > > static int btusb_setup_bcm92035(struct hci_dev *hdev) > @@ -3967,6 +4139,51 @@ static const struct file_operations force_poll_sync_fops = { > .llseek = default_llseek, > }; > > +static ssize_t force_early_acl_hold_read(struct file *file, > + char __user *user_buf, size_t count, > + loff_t *ppos) > +{ > + struct btusb_data *data = file->private_data; > + char buf[3]; > + > + buf[0] = data->early_acl_hold ? 'Y' : 'N'; > + buf[1] = '\n'; > + buf[2] = '\0'; > + return simple_read_from_buffer(user_buf, count, ppos, buf, 2); > +} > + > +static ssize_t force_early_acl_hold_write(struct file *file, > + const char __user *user_buf, > + size_t count, loff_t *ppos) > +{ > + struct btusb_data *data = file->private_data; > + bool enable; > + int err; > + > + err = kstrtobool_from_user(user_buf, count, &enable); > + if (err) > + return err; > + > + /* Only allow changes while the adapter is down */ > + if (test_bit(HCI_UP, &data->hdev->flags)) > + return -EPERM; > + > + if (data->early_acl_hold == enable) > + return -EALREADY; > + > + data->early_acl_hold = enable; > + > + return count; > +} > + > +static const struct file_operations force_early_acl_hold_fops = { > + .owner = THIS_MODULE, > + .open = simple_open, > + .read = force_early_acl_hold_read, > + .write = force_early_acl_hold_write, > + .llseek = default_llseek, > +}; > + > #define BTUSB_HCI_DRV_OP_SUPPORTED_ALTSETTINGS \ > hci_opcode_pack(HCI_DRV_OGF_DRIVER_SPECIFIC, 0x0000) > #define BTUSB_HCI_DRV_SUPPORTED_ALTSETTINGS_SIZE 0 > @@ -4464,6 +4681,7 @@ static int btusb_probe(struct usb_interface *intf, > usb_enable_autosuspend(data->udev); > > data->poll_sync = enable_poll_sync; > + data->early_acl_hold = enable_early_acl_hold; > > err = hci_register_dev(hdev); > if (err < 0) > @@ -4473,6 +4691,8 @@ static int btusb_probe(struct usb_interface *intf, > > debugfs_create_file("force_poll_sync", 0644, hdev->debugfs, data, > &force_poll_sync_fops); > + debugfs_create_file("force_early_acl_hold", 0644, hdev->debugfs, data, > + &force_early_acl_hold_fops); > > return 0; > > > base-commit: 6f55ad8fb0acd861e5c4e526b2272fc71952e1e3 > -- > 2.43.0 > -- Luiz Augusto von Dentz ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH] Bluetooth: btusb: Gate ACL delivery on HCI handle presence 2026-07-27 20:34 ` [PATCH] " Luiz Augusto von Dentz @ 2026-07-27 23:02 ` Amir Abudubai 2026-07-31 1:22 ` [PATCH v2] Bluetooth: hci_core: Queue out-of-order ACL packets Amir Abudubai 0 siblings, 1 reply; 6+ messages in thread From: Amir Abudubai @ 2026-07-27 23:02 UTC (permalink / raw) To: Luiz Augusto von Dentz; +Cc: linux-bluetooth, marcel Hello Luiz, The main problem I found with increasing poll intervals is that the time to wait is highly dependent on the device. With the three I tested, one should be 2 intervals, one is a flat 3.1 ms, and the third seemingly doesn't need poll_sync at all. I figured per-device corrections would be impractical and adding 3 ms of latency would inconvenience users. I couldn't find a workable fix for poll_sync in general, so I added a fix for just the easy case I knew was causing a problem. I sent a bug report a few days before the patch, but I didn't do a good job of explaining when it was a problem. I was encountering this when trying to use Linux as a low latency peripheral. Common centrals send the MTU request fast enough that it gets bundled with the connection complete. Android will try connecting for several seconds then give up, and bluetoothd will timeout in discovery and go into a glitched state. Here is the link to that report: https://lore.kernel.org/r/CAEzAHR-rM3V0hwyrc0H3xzS_gu965Kh2oyCra9aLjxNEsrCdoQ@mail.gmail.com/ Triggering the delay on vendor specific handles was my mistake. On Mon, Jul 27, 2026 at 3:34 PM Luiz Augusto von Dentz <luiz.dentz@gmail.com> wrote: > > Hi Amir, > > On Tue, Jul 21, 2026 at 9:23 PM Amir Abudubai <amirabudubai@gmail.com> wrote: > > > > On some USB devices, the interrupt-IN endpoint can lag the bulk-IN > > endpoint by multiple polling intervals. Early ACL data can then reach > > HCI before the Connection Complete event establishes its handle, causing > > the data to be dropped for an unknown handle. > > > > This issue was observed and the fix verified on the following adapters: > > * 8087:0025 Intel Corp. Wireless-AC 9260 Bluetooth Adapter > > * 7392:c611 Edimax Technology Co., Ltd Edimax Bluetooth Adapter > > > > Extend the existing poll_sync queue to support independent release gates > > while preserving its underlying behavior. Add an optional gate which > > holds ACL delivery until the HCI handle appears or a bounded timeout > > expires. Anchor both time gates to packet arrival to prevent timeout > > drift from accumulating as queued packets reach the FIFO head. > > > > BT_HCIBTUSB_EARLY_ACL_HOLD controls whether the gate is enabled by > > default. The force_early_acl_hold debugfs entry controls it per adapter > > while the adapter is down. > > > > Assisted-by: OpenCode:openai/gpt-5.6-sol > > Signed-off-by: Amir Abudubai <amirabudubai@gmail.com> > > This really sounds like an over-engineered design; I'm not sure why it > couldn't be done by just increasing the number of polls we wait before > delivering the ACL packet, it has been quite a long while we don't > receive any reports of such races still exists. Also, some vendors use > special handles for debugging, these never have any completing event > or anything. > > > --- > > drivers/bluetooth/Kconfig | 10 ++ > > drivers/bluetooth/btusb.c | 292 +++++++++++++++++++++++++++++++++----- > > 2 files changed, 266 insertions(+), 36 deletions(-) > > > > diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig > > index 4e8c24d757e9..648c0d8b1496 100644 > > --- a/drivers/bluetooth/Kconfig > > +++ b/drivers/bluetooth/Kconfig > > @@ -56,6 +56,16 @@ config BT_HCIBTUSB_POLL_SYNC > > Say Y here to enable USB poll_sync for Bluetooth USB devices by > > default. > > > > +config BT_HCIBTUSB_EARLY_ACL_HOLD > > + bool "Enable early ACL hold for Bluetooth USB devices by default" > > + depends on BT_HCIBTUSB > > + help > > + Hold ACL packets whose connection handle is not yet known until the > > + connection appears or a bounded timeout expires. > > + > > + Say Y here to enable early ACL hold for Bluetooth USB devices by > > + default. > > + > > config BT_HCIBTUSB_BCM > > bool "Broadcom protocol support" > > depends on BT_HCIBTUSB > > diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c > > index 4d59b28ab30f..a59b0ed91d18 100644 > > --- a/drivers/bluetooth/btusb.c > > +++ b/drivers/bluetooth/btusb.c > > @@ -34,6 +34,8 @@ static bool disable_scofix; > > static bool force_scofix; > > static bool enable_autosuspend = IS_ENABLED(CONFIG_BT_HCIBTUSB_AUTOSUSPEND); > > static bool enable_poll_sync = IS_ENABLED(CONFIG_BT_HCIBTUSB_POLL_SYNC); > > +static bool enable_early_acl_hold = > > + IS_ENABLED(CONFIG_BT_HCIBTUSB_EARLY_ACL_HOLD); > > static bool reset = true; > > > > static struct usb_driver btusb_driver; > > @@ -956,6 +958,8 @@ struct btqca_data { > > }; > > > > #define BTUSB_MAX_ISOC_FRAMES 10 > > +#define BTUSB_RX_RECHECK_DELAY msecs_to_jiffies(1) > > +#define BTUSB_EARLY_ACL_HOLD_MIN_US 5000U > > > > #define BTUSB_INTR_RUNNING 0 > > #define BTUSB_BULK_RUNNING 1 > > @@ -987,7 +991,10 @@ struct btusb_data { > > unsigned long flags; > > > > bool poll_sync; > > + bool early_acl_hold; > > int intr_interval; > > + unsigned long early_acl_timeout; > > + ktime_t last_event; > > struct work_struct work; > > struct work_struct waker; > > struct delayed_work rx_work; > > @@ -1252,14 +1259,100 @@ static inline void btusb_free_frags(struct btusb_data *data) > > spin_unlock_irqrestore(&data->rxlock, flags); > > } > > > > -static int btusb_recv_event(struct btusb_data *data, struct sk_buff *skb) > > +static void btusb_rx_queue_purge(struct btusb_data *data) > > +{ > > + unsigned long flags; > > + > > + spin_lock_irqsave(&data->rxlock, flags); > > + skb_queue_purge(&data->acl_q); > > + spin_unlock_irqrestore(&data->rxlock, flags); > > +} > > + > > +static ktime_t btusb_rx_deadline(struct sk_buff *skb, unsigned long timeout) > > { > > - if (data->intr_interval) { > > - /* Trigger dequeue immediately if an event is received */ > > - schedule_delayed_work(&data->rx_work, 0); > > + return ktime_add_ns(skb->tstamp, jiffies_to_nsecs(timeout)); > > +} > > + > > +static bool btusb_rx_hold_expired(struct sk_buff *skb, unsigned long timeout, > > + ktime_t now) > > +{ > > + return !ktime_before(now, btusb_rx_deadline(skb, timeout)); > > +} > > + > > +static unsigned long btusb_rx_hold_delay(struct sk_buff *skb, > > + unsigned long timeout, ktime_t now) > > +{ > > + s64 remaining = ktime_to_ns(ktime_sub(btusb_rx_deadline(skb, timeout), > > + now)); > > + > > + if (remaining <= 0) > > + return 0; > > + > > + return max_t(unsigned long, 1, nsecs_to_jiffies(remaining)); > > +} > > + > > +static bool btusb_poll_sync_should_hold(struct btusb_data *data, > > + struct sk_buff *skb, ktime_t now) > > +{ > > + unsigned long interval = READ_ONCE(data->intr_interval); > > + > > + if (!interval || !ktime_before(data->last_event, skb->tstamp)) > > + return false; > > + > > + return !btusb_rx_hold_expired(skb, interval, now); > > +} > > + > > +static bool btusb_acl_should_hold(struct btusb_data *data, > > + struct sk_buff *skb, ktime_t now) > > +{ > > + unsigned long timeout = READ_ONCE(data->early_acl_timeout); > > + u16 handle; > > + > > + if (!timeout || hci_dev_test_flag(data->hdev, HCI_USER_CHANNEL) || > > + btusb_rx_hold_expired(skb, timeout, now) || > > + skb->len < HCI_ACL_HDR_SIZE) > > + return false; > > + > > + handle = le16_to_cpu(hci_acl_hdr(skb)->handle); > > + > > + /* Only use the returned connection as an existence snapshot. */ > > + return !hci_conn_hash_lookup_handle(data->hdev, hci_handle(handle)); > > +} > > + > > +static unsigned long btusb_rx_delay(struct btusb_data *data, > > + struct sk_buff *skb, ktime_t now, > > + bool poll_hold, bool acl_hold) > > +{ > > + unsigned long timeout; > > + > > + if (poll_hold) { > > + timeout = READ_ONCE(data->intr_interval); > > + return btusb_rx_hold_delay(skb, timeout, now); > > + } > > + > > + if (acl_hold) { > > + timeout = READ_ONCE(data->early_acl_timeout); > > + return min(BTUSB_RX_RECHECK_DELAY, > > + btusb_rx_hold_delay(skb, timeout, now)); > > } > > > > - return data->recv_event(data->hdev, skb); > > + return 0; > > +} > > + > > +static int btusb_recv_event(struct btusb_data *data, struct sk_buff *skb) > > +{ > > + int err; > > + > > + err = data->recv_event(data->hdev, skb); > > + > > + if (READ_ONCE(data->intr_interval)) > > + data->last_event = ktime_get(); > > + > > + if (READ_ONCE(data->intr_interval) || > > + READ_ONCE(data->early_acl_timeout)) > > + mod_delayed_work(system_wq, &data->rx_work, 0); > > + > > + return err; > > } > > > > static int btusb_recv_intr(struct btusb_data *data, void *buffer, int count) > > @@ -1332,14 +1425,31 @@ static int btusb_recv_intr(struct btusb_data *data, void *buffer, int count) > > > > static int btusb_recv_acl(struct btusb_data *data, struct sk_buff *skb) > > { > > - /* Only queue ACL packet if intr_interval is set as it means > > - * force_poll_sync has been enabled. > > - */ > > - if (!data->intr_interval) > > + bool poll_hold, acl_hold; > > + bool queue_empty; > > + ktime_t now; > > + > > + if (!READ_ONCE(data->intr_interval) && > > + !READ_ONCE(data->early_acl_timeout)) > > + return data->recv_acl(data->hdev, skb); > > + > > + now = ktime_get(); > > + /* hci_recv_frame() replaces this temporary monotonic arrival time. */ > > + skb_set_delivery_time(skb, now, SKB_CLOCK_MONOTONIC); > > + > > + queue_empty = skb_queue_empty(&data->acl_q); > > + poll_hold = btusb_poll_sync_should_hold(data, skb, now); > > + acl_hold = btusb_acl_should_hold(data, skb, now); > > + > > + if (queue_empty && !poll_hold && !acl_hold) > > return data->recv_acl(data->hdev, skb); > > > > skb_queue_tail(&data->acl_q, skb); > > - schedule_delayed_work(&data->rx_work, data->intr_interval); > > + > > + if (queue_empty) > > + schedule_delayed_work(&data->rx_work, > > + btusb_rx_delay(data, skb, now, > > + poll_hold, acl_hold)); > > > > return 0; > > } > > @@ -1539,6 +1649,10 @@ static int btusb_submit_intr_urb(struct hci_dev *hdev, gfp_t mem_flags) > > struct btusb_data *data = hci_get_drvdata(hdev); > > struct urb *urb; > > unsigned char *buf; > > + unsigned long early_acl_timeout = 0; > > + int intr_interval = 0; > > + unsigned int hold_us; > > + unsigned int interval_us; > > unsigned int pipe; > > int err, size; > > > > @@ -1587,27 +1701,52 @@ static int btusb_submit_intr_urb(struct hci_dev *hdev, gfp_t mem_flags) > > } > > > > /* Only initialize intr_interval if URB poll sync is enabled */ > > - if (!data->poll_sync) > > - goto done; > > + if (data->poll_sync) { > > + /* The units are frames (milliseconds) for full and low speed > > + * devices, and microframes (1/8 millisecond) for highspeed and > > + * SuperSpeed devices. > > + * > > + * This is done once on open/resume so it shouldn't change even > > + * if force_poll_sync changes. > > + */ > > + switch (urb->dev->speed) { > > + case USB_SPEED_SUPER_PLUS: > > + case USB_SPEED_SUPER: > > + intr_interval = usecs_to_jiffies(urb->interval * 125); > > + break; > > + default: > > + intr_interval = msecs_to_jiffies(urb->interval); > > + break; > > + } > > + } > > > > - /* The units are frames (milliseconds) for full and low speed devices, > > - * and microframes (1/8 millisecond) for highspeed and SuperSpeed > > - * devices. > > - * > > - * This is done once on open/resume so it shouldn't change even if > > - * force_poll_sync changes. > > - */ > > - switch (urb->dev->speed) { > > - case USB_SPEED_SUPER_PLUS: > > - case USB_SPEED_SUPER: /* units are 125us */ > > - data->intr_interval = usecs_to_jiffies(urb->interval * 125); > > - break; > > - default: > > - data->intr_interval = msecs_to_jiffies(urb->interval); > > - break; > > + interval_us = 0; > > + if (data->early_acl_hold && urb->interval > 0) { > > + switch (urb->dev->speed) { > > + case USB_SPEED_SUPER_PLUS: > > + case USB_SPEED_SUPER: > > + case USB_SPEED_HIGH: > > + interval_us = urb->interval * 125; > > + break; > > + case USB_SPEED_FULL: > > + interval_us = urb->interval * USEC_PER_MSEC; > > + break; > > + default: > > + break; > > + } > > } > > > > -done: > > + if (interval_us) { > > + /* Allow an event which misses one polling window to arrive in > > + * the next. > > + */ > > + hold_us = max(BTUSB_EARLY_ACL_HOLD_MIN_US, interval_us * 2); > > + early_acl_timeout = usecs_to_jiffies(hold_us); > > + } > > + > > + WRITE_ONCE(data->intr_interval, intr_interval); > > + WRITE_ONCE(data->early_acl_timeout, early_acl_timeout); > > + > > usb_free_urb(urb); > > > > return err; > > @@ -2077,18 +2216,22 @@ static int btusb_close(struct hci_dev *hdev) > > > > BT_DBG("%s", hdev->name); > > > > - cancel_delayed_work(&data->rx_work); > > + /* Stop queued RX delivery before shutting down the endpoints. */ > > + cancel_delayed_work_sync(&data->rx_work); > > cancel_work_sync(&data->work); > > cancel_work_sync(&data->waker); > > > > - skb_queue_purge(&data->acl_q); > > - > > clear_bit(BTUSB_ISOC_RUNNING, &data->flags); > > clear_bit(BTUSB_BULK_RUNNING, &data->flags); > > clear_bit(BTUSB_INTR_RUNNING, &data->flags); > > clear_bit(BTUSB_DIAG_RUNNING, &data->flags); > > > > btusb_stop_traffic(data); > > + > > + /* An in-flight URB completion may have requeued RX work. */ > > + cancel_delayed_work_sync(&data->rx_work); > > + > > + btusb_rx_queue_purge(data); > > btusb_free_frags(data); > > > > err = usb_autopm_get_interface(data->intf); > > @@ -2114,9 +2257,9 @@ static int btusb_flush(struct hci_dev *hdev) > > > > BT_DBG("%s", hdev->name); > > > > - cancel_delayed_work(&data->rx_work); > > + cancel_delayed_work_sync(&data->rx_work); > > > > - skb_queue_purge(&data->acl_q); > > + btusb_rx_queue_purge(data); > > > > usb_kill_anchored_urbs(&data->tx_anchor); > > btusb_free_frags(data); > > @@ -2497,11 +2640,40 @@ static void btusb_rx_work(struct work_struct *work) > > { > > struct btusb_data *data = container_of(work, struct btusb_data, > > rx_work.work); > > + unsigned long delay; > > + unsigned long flags; > > struct sk_buff *skb; > > + bool poll_hold, acl_hold; > > + ktime_t now; > > + > > + for (;;) { > > + spin_lock_irqsave(&data->rxlock, flags); > > + now = ktime_get(); > > + skb = skb_peek(&data->acl_q); > > + > > + if (!skb) { > > + spin_unlock_irqrestore(&data->rxlock, flags); > > + return; > > + } > > + > > + poll_hold = btusb_poll_sync_should_hold(data, skb, now); > > + acl_hold = btusb_acl_should_hold(data, skb, now); > > + if (!poll_hold && !acl_hold) { > > + skb = skb_dequeue(&data->acl_q); > > + data->recv_acl(data->hdev, skb); > > + spin_unlock_irqrestore(&data->rxlock, flags); > > + continue; > > + } > > + > > + delay = btusb_rx_delay(data, skb, now, poll_hold, acl_hold); > > + spin_unlock_irqrestore(&data->rxlock, flags); > > > > - /* Dequeue ACL data received during the interval */ > > - while ((skb = skb_dequeue(&data->acl_q))) > > - data->recv_acl(data->hdev, skb); > > + /* A zero-delay event kick that raced with this worker remains > > + * pending; schedule_delayed_work() cannot move it later. > > + */ > > + schedule_delayed_work(&data->rx_work, delay); > > + return; > > + } > > } > > > > static int btusb_setup_bcm92035(struct hci_dev *hdev) > > @@ -3967,6 +4139,51 @@ static const struct file_operations force_poll_sync_fops = { > > .llseek = default_llseek, > > }; > > > > +static ssize_t force_early_acl_hold_read(struct file *file, > > + char __user *user_buf, size_t count, > > + loff_t *ppos) > > +{ > > + struct btusb_data *data = file->private_data; > > + char buf[3]; > > + > > + buf[0] = data->early_acl_hold ? 'Y' : 'N'; > > + buf[1] = '\n'; > > + buf[2] = '\0'; > > + return simple_read_from_buffer(user_buf, count, ppos, buf, 2); > > +} > > + > > +static ssize_t force_early_acl_hold_write(struct file *file, > > + const char __user *user_buf, > > + size_t count, loff_t *ppos) > > +{ > > + struct btusb_data *data = file->private_data; > > + bool enable; > > + int err; > > + > > + err = kstrtobool_from_user(user_buf, count, &enable); > > + if (err) > > + return err; > > + > > + /* Only allow changes while the adapter is down */ > > + if (test_bit(HCI_UP, &data->hdev->flags)) > > + return -EPERM; > > + > > + if (data->early_acl_hold == enable) > > + return -EALREADY; > > + > > + data->early_acl_hold = enable; > > + > > + return count; > > +} > > + > > +static const struct file_operations force_early_acl_hold_fops = { > > + .owner = THIS_MODULE, > > + .open = simple_open, > > + .read = force_early_acl_hold_read, > > + .write = force_early_acl_hold_write, > > + .llseek = default_llseek, > > +}; > > + > > #define BTUSB_HCI_DRV_OP_SUPPORTED_ALTSETTINGS \ > > hci_opcode_pack(HCI_DRV_OGF_DRIVER_SPECIFIC, 0x0000) > > #define BTUSB_HCI_DRV_SUPPORTED_ALTSETTINGS_SIZE 0 > > @@ -4464,6 +4681,7 @@ static int btusb_probe(struct usb_interface *intf, > > usb_enable_autosuspend(data->udev); > > > > data->poll_sync = enable_poll_sync; > > + data->early_acl_hold = enable_early_acl_hold; > > > > err = hci_register_dev(hdev); > > if (err < 0) > > @@ -4473,6 +4691,8 @@ static int btusb_probe(struct usb_interface *intf, > > > > debugfs_create_file("force_poll_sync", 0644, hdev->debugfs, data, > > &force_poll_sync_fops); > > + debugfs_create_file("force_early_acl_hold", 0644, hdev->debugfs, data, > > + &force_early_acl_hold_fops); > > > > return 0; > > > > > > base-commit: 6f55ad8fb0acd861e5c4e526b2272fc71952e1e3 > > -- > > 2.43.0 > > > > > -- > Luiz Augusto von Dentz ^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v2] Bluetooth: hci_core: Queue out-of-order ACL packets 2026-07-27 23:02 ` Amir Abudubai @ 2026-07-31 1:22 ` Amir Abudubai 2026-07-31 5:23 ` [v2] " bluez.test.bot 0 siblings, 1 reply; 6+ messages in thread From: Amir Abudubai @ 2026-07-31 1:22 UTC (permalink / raw) To: Luiz Augusto von Dentz Cc: Marcel Holtmann, linux-bluetooth, linux-kernel, Amir Abudubai On some USB adapters, the interrupt-IN endpoint can lag behind the bulk-IN endpoint by multiple polling intervals. Early ACL data can then arrive before the Connection Complete event establishes its handle, causing packets to be dropped due to an unknown handle. To resolve this, add a queue to hold ACL traffic received with an unknown handle. Re-check queued packets after processing HCI events, and drop packets if they remain unmatched when the 4 ms timeout expires. Introduce HCI_QUIRK_OUT_OF_ORDER_ACL to restrict this behavior to transports that set the quirk. Add BT_HCIBTUSB_EARLY_ACL_HOLD to set the default behavior for USB adapters, along with a force_early_acl_hold debugfs entry to control it per adapter while the adapter is down. This issue was observed and the fix verified on: - 8087:0025 Intel Corp. Wireless-AC 9260 Bluetooth Adapter - 7392:c611 Edimax Technology Co., Ltd Edimax Bluetooth Adapter Assisted-by: OpenCode:openai/gpt-5.6-sol Signed-off-by: Amir Abudubai <amirabudubai@gmail.com> --- Hi Luiz, I worked on finding a simpler fix that didn't require adding latency, and this is what I got. I based it on Fluoride's fix for the same race condition. The main difference is doing it in HCI core, which comes out cleaner because all the logic for valid handles and timestamps is already in place. drivers/bluetooth/Kconfig | 12 ++++ drivers/bluetooth/btusb.c | 59 ++++++++++++++++ include/net/bluetooth/bluetooth.h | 6 ++ include/net/bluetooth/hci.h | 9 +++ include/net/bluetooth/hci_core.h | 3 + net/bluetooth/hci_core.c | 111 +++++++++++++++++++++++++++--- net/bluetooth/hci_sync.c | 6 ++ net/bluetooth/l2cap_core.c | 2 +- 8 files changed, 199 insertions(+), 9 deletions(-) diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig index 4e8c24d757e9..027762e0da3f 100644 --- a/drivers/bluetooth/Kconfig +++ b/drivers/bluetooth/Kconfig @@ -56,6 +56,18 @@ config BT_HCIBTUSB_POLL_SYNC Say Y here to enable USB poll_sync for Bluetooth USB devices by default. +config BT_HCIBTUSB_EARLY_ACL_HOLD + bool "Enable USB early ACL packet hold by default" + depends on BT_HCIBTUSB + default n + help + Hold ACL packets for a nominal 4 milliseconds when their connection + handle has not been registered yet. This works around USB controllers + whose event endpoint can lag behind their ACL data endpoint. + + The default can be overridden per adapter using the + force_early_acl_hold debugfs entry while the adapter is down. + config BT_HCIBTUSB_BCM bool "Broadcom protocol support" depends on BT_HCIBTUSB diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index be82bbbc1b5c..2752bfb89670 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -34,6 +34,8 @@ static bool disable_scofix; static bool force_scofix; static bool enable_autosuspend = IS_ENABLED(CONFIG_BT_HCIBTUSB_AUTOSUSPEND); static bool enable_poll_sync = IS_ENABLED(CONFIG_BT_HCIBTUSB_POLL_SYNC); +static bool enable_early_acl_hold = + IS_ENABLED(CONFIG_BT_HCIBTUSB_EARLY_ACL_HOLD); static bool reset = true; static struct usb_driver btusb_driver; @@ -3964,6 +3966,57 @@ static const struct file_operations force_poll_sync_fops = { .llseek = default_llseek, }; +static ssize_t force_early_acl_hold_read(struct file *file, + char __user *user_buf, size_t count, + loff_t *ppos) +{ + struct btusb_data *data = file->private_data; + char buf[3]; + + buf[0] = hci_dev_test_flag(data->hdev, + HCI_OUT_OF_ORDER_ACL_ENABLED) ? 'Y' : 'N'; + buf[1] = '\n'; + buf[2] = '\0'; + + return simple_read_from_buffer(user_buf, count, ppos, buf, 2); +} + +static ssize_t force_early_acl_hold_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct btusb_data *data = file->private_data; + bool enabled; + int err; + + err = kstrtobool_from_user(user_buf, count, &enabled); + if (err) + return err; + + /* Only allow changes while the adapter is down */ + if (test_bit(HCI_UP, &data->hdev->flags)) + return -EPERM; + + if (hci_dev_test_flag(data->hdev, HCI_OUT_OF_ORDER_ACL_ENABLED) == + enabled) + return -EALREADY; + + if (enabled) + hci_dev_set_flag(data->hdev, HCI_OUT_OF_ORDER_ACL_ENABLED); + else + hci_dev_clear_flag(data->hdev, HCI_OUT_OF_ORDER_ACL_ENABLED); + + return count; +} + +static const struct file_operations force_early_acl_hold_fops = { + .owner = THIS_MODULE, + .open = simple_open, + .read = force_early_acl_hold_read, + .write = force_early_acl_hold_write, + .llseek = default_llseek, +}; + #define BTUSB_HCI_DRV_OP_SUPPORTED_ALTSETTINGS \ hci_opcode_pack(HCI_DRV_OGF_DRIVER_SPECIFIC, 0x0000) #define BTUSB_HCI_DRV_SUPPORTED_ALTSETTINGS_SIZE 0 @@ -4460,6 +4513,10 @@ static int btusb_probe(struct usb_interface *intf, if (enable_autosuspend) usb_enable_autosuspend(data->udev); + hci_set_quirk(hdev, HCI_QUIRK_OUT_OF_ORDER_ACL); + if (enable_early_acl_hold) + hci_dev_set_flag(hdev, HCI_OUT_OF_ORDER_ACL_ENABLED); + data->poll_sync = enable_poll_sync; err = hci_register_dev(hdev); @@ -4470,6 +4527,8 @@ static int btusb_probe(struct usb_interface *intf, debugfs_create_file("force_poll_sync", 0644, hdev->debugfs, data, &force_poll_sync_fops); + debugfs_create_file("force_early_acl_hold", 0644, hdev->debugfs, data, + &force_early_acl_hold_fops); return 0; diff --git a/include/net/bluetooth/bluetooth.h b/include/net/bluetooth/bluetooth.h index b624da5026f5..19fd03103dff 100644 --- a/include/net/bluetooth/bluetooth.h +++ b/include/net/bluetooth/bluetooth.h @@ -486,6 +486,10 @@ struct mgmt_ctrl { u16 opcode; }; +struct hci_acl_ctrl { + unsigned long expires; +}; + struct bt_skb_cb { u8 pkt_type; u8 force_active; @@ -496,6 +500,7 @@ struct bt_skb_cb { union { struct l2cap_ctrl l2cap; struct hci_ctrl hci; + struct hci_acl_ctrl hci_acl; struct mgmt_ctrl mgmt; struct scm_creds creds; }; @@ -509,6 +514,7 @@ struct bt_skb_cb { #define hci_skb_opcode(skb) bt_cb((skb))->hci.opcode #define hci_skb_event(skb) bt_cb((skb))->hci.req_event #define hci_skb_sk(skb) bt_cb((skb))->hci.sk +#define hci_skb_acl_expires(skb) bt_cb((skb))->hci_acl.expires static inline struct sk_buff *bt_skb_alloc(unsigned int len, gfp_t how) { diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h index cd3520a29131..04b6bbc4c803 100644 --- a/include/net/bluetooth/hci.h +++ b/include/net/bluetooth/hci.h @@ -375,6 +375,14 @@ enum { */ HCI_QUIRK_BROKEN_READ_PAGE_SCAN_TYPE, + /* When this quirk is set, ACL packets received before their connection + * handle is registered may be queued briefly. This can happen on + * transports with separate event and ACL data channels. + * + * This quirk must be set before hci_register_dev is called. + */ + HCI_QUIRK_OUT_OF_ORDER_ACL, + __HCI_NUM_QUIRKS, }; @@ -468,6 +476,7 @@ enum { HCI_OFFLOAD_CODECS_ENABLED, HCI_LE_SIMULTANEOUS_ROLES, HCI_CMD_DRAIN_WORKQUEUE, + HCI_OUT_OF_ORDER_ACL_ENABLED, HCI_MESH_EXPERIMENTAL, HCI_MESH, diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index 01b938c4b24a..f23b1a11e4b2 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -516,10 +516,12 @@ struct hci_dev { struct work_struct rx_work; struct work_struct cmd_work; struct work_struct tx_work; + struct delayed_work unknown_acl_work; struct delayed_work le_scan_disable; struct sk_buff_head rx_q; + struct sk_buff_head unknown_acl_q; struct sk_buff_head raw_q; struct sk_buff_head cmd_q; @@ -873,6 +875,7 @@ extern struct mutex hci_cb_list_lock; /* ----- HCI interface to upper protocols ----- */ int l2cap_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr); int l2cap_disconn_ind(struct hci_conn *hcon); +/* The caller retains ownership of skb only when -ENOENT is returned. */ int l2cap_recv_acldata(struct hci_dev *hdev, u16 handle, struct sk_buff *skb, u16 flags); diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index 9d5adf882509..a6531926e0cd 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -47,6 +47,7 @@ static void hci_rx_work(struct work_struct *work); static void hci_cmd_work(struct work_struct *work); static void hci_tx_work(struct work_struct *work); +static void hci_unknown_acl_work(struct work_struct *work); /* HCI device list */ LIST_HEAD(hci_dev_list); @@ -2511,6 +2512,7 @@ struct hci_dev *hci_alloc_dev_priv(int sizeof_priv) INIT_WORK(&hdev->rx_work, hci_rx_work); INIT_WORK(&hdev->cmd_work, hci_cmd_work); INIT_WORK(&hdev->tx_work, hci_tx_work); + INIT_DELAYED_WORK(&hdev->unknown_acl_work, hci_unknown_acl_work); INIT_WORK(&hdev->power_on, hci_power_on); INIT_WORK(&hdev->error_reset, hci_error_reset); @@ -2519,6 +2521,7 @@ struct hci_dev *hci_alloc_dev_priv(int sizeof_priv) INIT_DELAYED_WORK(&hdev->power_off, hci_power_off); skb_queue_head_init(&hdev->rx_q); + skb_queue_head_init(&hdev->unknown_acl_q); skb_queue_head_init(&hdev->cmd_q); skb_queue_head_init(&hdev->raw_q); @@ -2669,6 +2672,7 @@ void hci_unregister_dev(struct hci_dev *hdev) disable_work_sync(&hdev->rx_work); disable_work_sync(&hdev->cmd_work); disable_work_sync(&hdev->tx_work); + disable_delayed_work_sync(&hdev->unknown_acl_work); disable_work_sync(&hdev->power_on); disable_work_sync(&hdev->error_reset); disable_delayed_work_sync(&hdev->cmd_timer); @@ -3793,8 +3797,11 @@ static void hci_tx_work(struct work_struct *work) /* ----- HCI RX task (incoming data processing) ----- */ +#define HCI_UNKNOWN_ACL_TIMEOUT_MS 4 + /* ACL data packet */ -static void hci_acldata_packet(struct hci_dev *hdev, struct sk_buff *skb) +static int hci_acldata_packet(struct hci_dev *hdev, struct sk_buff *skb, + bool retry) { struct hci_acl_hdr *hdr; __u16 handle, flags; @@ -3804,7 +3811,7 @@ static void hci_acldata_packet(struct hci_dev *hdev, struct sk_buff *skb) if (!hdr) { bt_dev_err(hdev, "ACL packet too small"); kfree_skb(skb); - return; + return -EINVAL; } handle = __le16_to_cpu(hdr->handle); @@ -3814,15 +3821,93 @@ static void hci_acldata_packet(struct hci_dev *hdev, struct sk_buff *skb) bt_dev_dbg(hdev, "len %d handle 0x%4.4x flags 0x%4.4x", skb->len, handle, flags); - hdev->stat.acl_rx++; + if (!retry) + hdev->stat.acl_rx++; err = l2cap_recv_acldata(hdev, handle, skb, flags); - if (err == -ENOENT) - bt_dev_err(hdev, "ACL packet for unknown connection handle %d", - handle); - else if (err) + if (err == -ENOENT) { + skb_push(skb, sizeof(*hdr)); + return err; + } + + if (err) bt_dev_dbg(hdev, "ACL packet recv for handle %d failed: %d", handle, err); + + return err; +} + +static bool hci_unknown_acl_expired(struct sk_buff *skb) +{ + return time_after_eq(jiffies, hci_skb_acl_expires(skb)); +} + +static u16 hci_unknown_acl_handle(struct sk_buff *skb) +{ + return hci_handle(le16_to_cpu(hci_acl_hdr(skb)->handle)); +} + +static void hci_drop_unknown_acl(struct hci_dev *hdev, struct sk_buff *skb) +{ + bt_dev_err(hdev, "ACL packet for unknown connection handle %d", + hci_unknown_acl_handle(skb)); + kfree_skb(skb); +} + +static void hci_queue_unknown_acl(struct hci_dev *hdev, struct sk_buff *skb) +{ + bt_dev_warn_ratelimited(hdev, + "Queuing ACL packet for unknown connection handle %d", + hci_unknown_acl_handle(skb)); + hci_skb_acl_expires(skb) = + jiffies + msecs_to_jiffies(HCI_UNKNOWN_ACL_TIMEOUT_MS); + skb_queue_tail(&hdev->unknown_acl_q, skb); + queue_delayed_work(hdev->workqueue, &hdev->unknown_acl_work, + msecs_to_jiffies(HCI_UNKNOWN_ACL_TIMEOUT_MS)); +} + +static void hci_retry_unknown_acl(struct hci_dev *hdev) +{ + unsigned int count = skb_queue_len(&hdev->unknown_acl_q); + struct sk_buff *skb; + unsigned long delay; + + if (hci_dev_test_flag(hdev, HCI_USER_CHANNEL)) { + cancel_delayed_work(&hdev->unknown_acl_work); + skb_queue_purge(&hdev->unknown_acl_q); + return; + } + + while (count-- && (skb = skb_dequeue(&hdev->unknown_acl_q))) { + if (hci_acldata_packet(hdev, skb, true) != -ENOENT) + continue; + + if (hci_unknown_acl_expired(skb)) { + hci_drop_unknown_acl(hdev, skb); + continue; + } + + skb_queue_tail(&hdev->unknown_acl_q, skb); + } + + if (skb_queue_empty(&hdev->unknown_acl_q)) { + cancel_delayed_work(&hdev->unknown_acl_work); + return; + } + + skb = skb_peek(&hdev->unknown_acl_q); + delay = time_after(hci_skb_acl_expires(skb), jiffies) ? + hci_skb_acl_expires(skb) - jiffies : 0; + queue_delayed_work(hdev->workqueue, &hdev->unknown_acl_work, + max_t(unsigned long, 1, delay)); +} + +static void hci_unknown_acl_work(struct work_struct *work) +{ + struct hci_dev *hdev = container_of(work, struct hci_dev, + unknown_acl_work.work); + + hci_retry_unknown_acl(hdev); } /* SCO data packet */ @@ -4039,11 +4124,21 @@ static void hci_rx_work(struct work_struct *work) case HCI_EVENT_PKT: BT_DBG("%s Event packet", hdev->name); hci_event_packet(hdev, skb); + if (!skb_queue_empty(&hdev->unknown_acl_q)) + hci_retry_unknown_acl(hdev); break; case HCI_ACLDATA_PKT: BT_DBG("%s ACL data packet", hdev->name); - hci_acldata_packet(hdev, skb); + if (hci_acldata_packet(hdev, skb, false) == -ENOENT) { + if (hci_test_quirk(hdev, + HCI_QUIRK_OUT_OF_ORDER_ACL) && + hci_dev_test_flag(hdev, + HCI_OUT_OF_ORDER_ACL_ENABLED)) + hci_queue_unknown_acl(hdev, skb); + else + hci_drop_unknown_acl(hdev, skb); + } break; case HCI_SCODATA_PKT: diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index 307fd47f8459..839f9fe91d20 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -5396,9 +5396,11 @@ int hci_dev_open_sync(struct hci_dev *hdev) */ flush_work(&hdev->rx_work); flush_work(&hdev->cmd_work); + cancel_delayed_work_sync(&hdev->unknown_acl_work); skb_queue_purge(&hdev->cmd_q); skb_queue_purge(&hdev->rx_q); + skb_queue_purge(&hdev->unknown_acl_q); if (hdev->flush) hdev->flush(hdev); @@ -5503,6 +5505,8 @@ int hci_dev_close_sync(struct hci_dev *hdev) if (!test_and_clear_bit(HCI_UP, &hdev->flags)) { cancel_delayed_work_sync(&hdev->cmd_timer); + cancel_delayed_work_sync(&hdev->unknown_acl_work); + skb_queue_purge(&hdev->unknown_acl_q); hci_dev_clear_flag(hdev, HCI_CMD_DRAIN_WORKQUEUE); return err; } @@ -5512,6 +5516,7 @@ int hci_dev_close_sync(struct hci_dev *hdev) /* Flush RX and TX works */ flush_work(&hdev->tx_work); flush_work(&hdev->rx_work); + cancel_delayed_work_sync(&hdev->unknown_acl_work); if (hdev->discov_timeout > 0) { hdev->discov_timeout = 0; @@ -5582,6 +5587,7 @@ int hci_dev_close_sync(struct hci_dev *hdev) /* Drop queues */ skb_queue_purge(&hdev->rx_q); + skb_queue_purge(&hdev->unknown_acl_q); skb_queue_purge(&hdev->cmd_q); skb_queue_purge(&hdev->raw_q); diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index 1156aba4e83c..a53179897054 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -7799,7 +7799,7 @@ int l2cap_recv_acldata(struct hci_dev *hdev, u16 handle, hcon = hci_conn_hash_lookup_handle(hdev, handle); if (!hcon) { hci_dev_unlock(hdev); - kfree_skb(skb); + /* Leave ownership with HCI so it can retry the packet. */ return -ENOENT; } base-commit: 735a14a2a4426760c5ff41ef3db5543fececcb75 -- 2.43.0 ^ permalink raw reply related [flat|nested] 6+ messages in thread
* RE: [v2] Bluetooth: hci_core: Queue out-of-order ACL packets 2026-07-31 1:22 ` [PATCH v2] Bluetooth: hci_core: Queue out-of-order ACL packets Amir Abudubai @ 2026-07-31 5:23 ` bluez.test.bot 0 siblings, 0 replies; 6+ messages in thread From: bluez.test.bot @ 2026-07-31 5:23 UTC (permalink / raw) To: linux-bluetooth, amirabudubai [-- Attachment #1: Type: text/plain, Size: 2470 bytes --] This is automated email and please do not reply to this email! Dear submitter, Thank you for submitting the patches to the linux bluetooth mailing list. This is a CI test results with your patch series: PW Link:https://patchwork.kernel.org/project/bluetooth/list/?series=1137797 ---Test result--- Test Summary: CheckPatch PASS 2.37 seconds VerifyFixes PASS 0.14 seconds VerifySignedoff PASS 0.46 seconds GitLint PASS 0.33 seconds SubjectPrefix PASS 0.13 seconds BuildKernel PASS 25.83 seconds CheckAllWarning PASS 28.48 seconds CheckSparse PASS 26.83 seconds BuildKernel32 PASS 24.89 seconds CheckKernelLLVM SKIP 0.00 seconds TestRunnerSetup PASS 465.00 seconds TestRunner_l2cap-tester PASS 63.40 seconds TestRunner_iso-tester PASS 88.38 seconds TestRunner_bnep-tester PASS 19.60 seconds TestRunner_mgmt-tester FAIL 219.23 seconds TestRunner_rfcomm-tester PASS 25.92 seconds TestRunner_sco-tester PASS 32.07 seconds TestRunner_ioctl-tester PASS 26.64 seconds TestRunner_mesh-tester FAIL 26.21 seconds TestRunner_smp-tester PASS 23.73 seconds TestRunner_userchan-tester PASS 20.46 seconds TestRunner_6lowpan-tester PASS 27.71 seconds IncrementalBuild PASS 24.81 seconds Details ############################## Test: CheckKernelLLVM - SKIP Desc: Build kernel with LLVM + context analysis Output: Clang not found ############################## Test: TestRunner_mgmt-tester - FAIL Desc: Run mgmt-tester with test-runner Output: Total: 501, Passed: 489 (97.6%), Failed: 2, Not Run: 10 Failed Test Cases Set Low Energy on 6.2 - SCI Setting Failed 0.345 seconds Read Exp Feature - Success Failed 0.246 seconds ############################## Test: TestRunner_mesh-tester - FAIL Desc: Run mesh-tester with test-runner Output: Total: 10, Passed: 8 (80.0%), Failed: 2, Not Run: 0 Failed Test Cases Mesh - Send cancel - 1 Timed out 2.522 seconds Mesh - Send cancel - 2 Timed out 1.990 seconds https://github.com/bluez/bluetooth-next/pull/520 --- Regards, Linux Bluetooth ^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-07-31 5:23 UTC | newest] Thread overview: 6+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-07-22 1:23 [PATCH] Bluetooth: btusb: Gate ACL delivery on HCI handle presence Amir Abudubai 2026-07-22 4:48 ` bluez.test.bot 2026-07-27 20:34 ` [PATCH] " Luiz Augusto von Dentz 2026-07-27 23:02 ` Amir Abudubai 2026-07-31 1:22 ` [PATCH v2] Bluetooth: hci_core: Queue out-of-order ACL packets Amir Abudubai 2026-07-31 5:23 ` [v2] " bluez.test.bot
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.