DF-5754 / 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 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 | 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 <net/netisr.h> #include <sys/mplock2.h> +#include <sys/spinlock2.h> #include <netbt/hci.h> 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 <sys/systm.h> #include <sys/endian.h> #include <sys/bus.h> +#include <sys/spinlock2.h> #include <net/netisr.h> @@ -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) { |