diff --git a/sys/netbt/bt_input.c b/sys/netbt/bt_input.c index 3e580b0..b258c03 100644 --- a/sys/netbt/bt_input.c +++ b/sys/netbt/bt_input.c @@ -25,17 +25,36 @@ #include #include +#include #include void btintr(netmsg_t msg) { - struct hci_unit *unit; + struct hci_unit *unit, *units[16]; + int i, n = 0; get_mplock(); + + /* + * Snapshot the (referenced) unit list under hci_unit_lock so + * hci_detach() cannot remove+free a unit out from under the walk. + */ + spin_lock(&hci_unit_lock); TAILQ_FOREACH(unit, &hci_unit_list, hci_next) { - hci_intr(unit); + if (n >= (int)NELEM(units)) + break; + unit->hci_refcnt++; + units[n++] = unit; } + spin_unlock(&hci_unit_lock); + + for (i = 0; i < n; i++) + hci_intr(units[i]); + + for (i = 0; i < n; i++) + hci_unit_rele(units[i]); + rel_mplock(); } diff --git a/sys/netbt/hci.h b/sys/netbt/hci.h index f1cb2a7..42a0865 100644 --- a/sys/netbt/hci.h +++ b/sys/netbt/hci.h @@ -2503,6 +2503,8 @@ struct hci_unit { struct ifqueue hci_cmdwait; /* pending commands */ struct ifqueue hci_scodone; /* SCO done queue */ + int hci_refcnt; /* netisr walk references */ + TAILQ_ENTRY(hci_unit) hci_next; }; @@ -2563,6 +2565,11 @@ int hci_input_event(struct hci_unit *, struct mbuf *); int hci_input_acl(struct hci_unit *, struct mbuf *); int hci_input_sco(struct hci_unit *, struct mbuf *); int hci_complete_sco(struct hci_unit *, struct mbuf *); + +/* unit lifetime (hci_unit.c) */ +extern struct spinlock hci_unit_lock; +void hci_unit_ref(struct hci_unit *); +void hci_unit_rele(struct hci_unit *); void hci_output_cmd(struct hci_unit *, struct mbuf *); void hci_output_acl(struct hci_unit *, struct mbuf *); void hci_output_sco(struct hci_unit *, struct mbuf *); diff --git a/sys/netbt/hci_unit.c b/sys/netbt/hci_unit.c index 8de0c1a..b37defa 100644 --- a/sys/netbt/hci_unit.c +++ b/sys/netbt/hci_unit.c @@ -42,6 +42,7 @@ #include #include #include +#include #include @@ -50,6 +51,9 @@ struct hci_unit_list hci_unit_list = TAILQ_HEAD_INITIALIZER(hci_unit_list); +/* guards hci_unit_list and per-unit hci_refcnt (netisr walk vs detach) */ +struct spinlock hci_unit_lock = SPINLOCK_INITIALIZER(hci_unit_lock, "hci_unit_lock"); + /* * HCI Input Queue max lengths. */ @@ -110,9 +114,11 @@ hci_attach(const struct hci_if *hci_if, device_t dev, uint16_t flags) TAILQ_INIT(&unit->hci_links); LIST_INIT(&unit->hci_memos); - crit_enter(); + unit->hci_refcnt = 0; /* netisr walk references */ + + spin_lock(&hci_unit_lock); TAILQ_INSERT_TAIL(&hci_unit_list, unit, hci_next); - crit_exit(); + spin_unlock(&hci_unit_lock); return unit; } @@ -120,15 +126,49 @@ hci_attach(const struct hci_if *hci_if, device_t dev, uint16_t flags) void hci_detach(struct hci_unit *unit) { - crit_enter(); hci_disable(unit); + spin_lock(&hci_unit_lock); TAILQ_REMOVE(&hci_unit_list, unit, hci_next); - crit_exit(); + spin_unlock(&hci_unit_lock); + + /* + * Wait until the netisr walker (btintr -> hci_intr) is out of the + * unit before freeing it. The controller driver remains + * responsible for quiescing its own rx paths before hci_detach(). + */ + while (unit->hci_refcnt != 0) + tsleep(unit, 0, "hciunre", 1); kfree(unit, M_BLUETOOTH); } +/* + * Acquire/release a unit reference for the duration of a netisr-side + * hci_intr() invocation so hci_detach() cannot free the unit out from + * under the walk. The list is snapshotted under hci_unit_lock in + * btintr(). + */ +void +hci_unit_ref(struct hci_unit *unit) +{ + + spin_lock(&hci_unit_lock); + unit->hci_refcnt++; + spin_unlock(&hci_unit_lock); +} + +void +hci_unit_rele(struct hci_unit *unit) +{ + + spin_lock(&hci_unit_lock); + KKASSERT(unit->hci_refcnt > 0); + if (--unit->hci_refcnt == 0) + wakeup(unit); + spin_unlock(&hci_unit_lock); +} + int hci_enable(struct hci_unit *unit) {