DF-5761 / fix.diff
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | --- /home/maxx/dfbsd/dfbsd/sys/netbt/hci_link.c 2026-06-29 12:51:19.000000000 +0000 +++ hci_link.c 2026-10-11 12:11:04.653039910 +0000 @@ -39,6 +39,7 @@ #include <sys/queue.h> #include <sys/systm.h> #include <sys/endian.h> +#include <sys/spinlock2.h> #include <sys/callout.h> #include <net/if.h> #include <sys/bus.h> @@ -783,13 +784,41 @@ if (acl == NULL || acl->hl_state != HCI_LINK_OPEN) return NULL; - LIST_FOREACH(pcb, &sco_pcb, sp_next) { - if ((pcb->sp_flags & SP_LISTENING) == 0) - continue; + { + /* Snapshot + pin the listening candidates under the lock: + * newconn() can block (sonewconn) so it must run without + * the spinlock, but the pcb must stay allocated across it. + * XXX 16 simultaneous SCO listeners is beyond any use case. + */ + struct sco_pcb *cand[16]; + int n = 0, i; + + new = NULL; + + spin_lock(&sco_pcblock); + LIST_FOREACH(pcb, &sco_pcb, sp_next) { + if ((pcb->sp_flags & SP_LISTENING) == 0) + continue; + if (n == (int)NELEM(cand)) + break; + sco_pcb_hold(pcb); + cand[n++] = pcb; + } + spin_unlock(&sco_pcblock); - new = (*pcb->sp_proto->newconn)(pcb->sp_upper, &laddr, &raddr); + for (i = 0; i < n; i++) { + pcb = cand[i]; + new = (*pcb->sp_proto->newconn)(pcb->sp_upper, + &laddr, &raddr); + if (new != NULL) + break; + sco_pcb_rele(pcb); + } if (new == NULL) - continue; + return NULL; + + for (; i < n; i++) + sco_pcb_rele(cand[i]); /* * Ok, got new pcb so we can start a new link and fill --- /home/maxx/dfbsd/dfbsd/sys/netbt/sco.h 2026-06-29 12:51:19.000000000 +0000 +++ sco.h 2026-10-11 11:32:46.290598700 +0000 @@ -43,6 +43,7 @@ */ struct sco_pcb { struct hci_link *sp_link; /* SCO link */ + int sp_refcnt; /* list ref + walker pins */ unsigned int sp_flags; /* flags */ bdaddr_t sp_laddr; /* local address */ bdaddr_t sp_raddr; /* remote address */ @@ -57,6 +58,9 @@ LIST_HEAD(sco_pcb_list, sco_pcb); extern struct sco_pcb_list sco_pcb; +extern struct spinlock sco_pcblock; +void sco_pcb_hold(struct sco_pcb *); +void sco_pcb_rele(struct sco_pcb *); /* sp_flags */ #define SP_LISTENING (1<<0) /* is listening pcb */ --- /home/maxx/dfbsd/dfbsd/sys/netbt/sco_upper.c 2026-06-29 12:51:19.000000000 +0000 +++ sco_upper.c 2026-10-11 11:58:19.242892084 +0000 @@ -40,6 +40,7 @@ #include <sys/systm.h> #include <sys/endian.h> #include <sys/bus.h> +#include <sys/spinlock2.h> #include <netbt/bluetooth.h> #include <netbt/hci.h> @@ -51,6 +52,21 @@ */ struct sco_pcb_list sco_pcb = LIST_HEAD_INITIALIZER(sco_pcb); +struct spinlock sco_pcblock = SPINLOCK_INITIALIZER(sco_pcblock, "sco_pcblock"); + +void +sco_pcb_hold(struct sco_pcb *pcb) +{ + /* atomic: races rele() which runs without the list lock */ + atomic_add_int(&pcb->sp_refcnt, 1); +} + +void +sco_pcb_rele(struct sco_pcb *pcb) +{ + if (atomic_fetchadd_int(&pcb->sp_refcnt, -1) == 1) + kfree(pcb, M_BLUETOOTH); +} /* * sco_attach(handle, proto, upper) @@ -71,10 +87,13 @@ if (pcb == NULL) return ENOMEM; + pcb->sp_refcnt = 1; /* the list's reference */ pcb->sp_proto = proto; pcb->sp_upper = upper; + spin_lock(&sco_pcblock); LIST_INSERT_HEAD(&sco_pcb, pcb, sp_next); + spin_unlock(&sco_pcblock); *handle = pcb; return 0; @@ -242,8 +261,10 @@ pcb->sp_link = NULL; } + spin_lock(&sco_pcblock); LIST_REMOVE(pcb, sp_next); - kfree(pcb, M_BLUETOOTH); + spin_unlock(&sco_pcblock); + sco_pcb_rele(pcb); /* may defer the kfree to the last walker */ return 0; } |