Repository navigation
Conversation
This was referenced Nov 11, 2026
netmojo
force-pushed
the
fix/brcmfmac-detach-races
branch
3 times, most recently
from
November 11, 2026 18:14
77bad2c to
2bcffee
Compare
brcmf_pcie_reset() masks the mailbox interrupt and calls brcmf_detach() straight away. It leaves the PCIe state UP and does not wait for the threaded IRQ handler. A handler that is already running sees UP, passes RX to msgbuf, which can queue a firmware event while brcmf_detach() is cancelling the event worker and freeing fweh, and then unmasks the interrupt again. Do what remove does before it detaches: set the state DOWN, mask, and synchronize an allocated IRQ. A handler that read UP before the store can still unmask after the first mask, so mask again once it has finished. After that the handler neither dispatches RX nor unmasks. The firmware has usually crashed by the time a reset runs, and remove already detaches with the state DOWN, so ring writes failing with -EIO during detach is the expected path. Signed-off-by: Net Mojo <56850867+netmojo@users.noreply.github.com>
A P2P_DEVICE create holds the wiphy lock while it waits for the firmware's IF_ADD event, and when the wait ends it calls brcmf_fweh_p2pdev_setup(), which writes to drvr->fweh. A bus reset enters brcmf_detach() without that lock. brcmf_fweh_detach() cancels the event worker and frees fweh straight away, so a create that times out after that writes to freed memory. Split brcmf_fweh_detach(). brcmf_fweh_detach_events() cancels the event worker where the old call was, so no IF event can reach an interface that detach is removing. brcmf_fweh_detach() frees fweh after the interfaces are gone. Before it frees fweh, detach takes and releases the wiphy lock. P2P creation now refuses to start unless the bus is up, and that check runs under the wiphy lock, after detach has set BRCMF_BUS_DOWN. A create already past the check holds the lock until it has finished with fweh, so once detach has had the lock, nothing can still reach fweh. Signed-off-by: Net Mojo <56850867+netmojo@users.noreply.github.com>
brcmf_detach() reads iflist[i] and ifp->ndev without RTNL or the wiphy lock, then removes netdev interfaces unlocked. brcmf_p2p_del_vif(), reached from NL80211_CMD_DEL_INTERFACE under both locks, can free a P2P client or GO interface in that window. Only the P2P device interface, which has no netdev, was removed under the locks. Remove every interface except the primary one under RTNL and the wiphy lock, and re-read the slot under them. Simply taking both locks around brcmf_remove_interface(ifp, true) is not enough for a netdev that is up: cfg80211_unregister_netdevice() closes it, and cfg80211's NETDEV_GOING_DOWN and NETDEV_DOWN handlers take the wiphy mutex again. The mutex is not recursive and RTNL is held, so every RTNL user on the system hangs. awdl0 is a cfg80211 netdev and is up for the whole of an AirDrop window, so a firmware crash during a window would reach this. Do what nl80211_del_interface() does: dev_close() under RTNL alone, then take the wiphy lock and unregister. brcmf_p2p_del_vif() needs RTNL and the event worker has been cancelled, so the slot cannot change between the two reads. The primary interface is still removed unlocked. brcmf_del_if() takes RTNL itself to stop it. Signed-off-by: Net Mojo <56850867+netmojo@users.noreply.github.com>
netmojo
force-pushed
the
fix/brcmfmac-detach-races
branch
from
November 11, 2026 18:16
2bcffee to
2fd88bc
Compare
brcmf_pcie_setup() sets the PCIe state UP after the firmware download, because ring setup needs it, and only then calls brcmf_attach(), which creates the protocol layer. brcmf_pcie_preinit() enables the interrupt once that is done. The IRQ thread does not wait for it: it re-arms the interrupt whenever the state is UP, and dispatches RX whenever the state is UP and irq_ready is set. One MSI between setup's UP and preinit makes the thread re-arm and set irq_ready; the next calls brcmf_proto_msgbuf_rx_trigger() while drvr->proto is NULL, or set but without its msgbuf. A state left UP across a bus reset opens the same path during the firmware download. On a BCM4387 with a debugfs reset, running the base tree plus only a separate console-read bound (without the three preceding commits and without this one), the IRQ thread faulted loading drvr->proto->pd: Unable to handle kernel NULL pointer dereference at virtual address 0000000000000078 pc : brcmf_proto_msgbuf_rx_trigger+0x24/0x144 [brcmfmac] lr : brcmf_pcie_isr_thread+0x108/0x164 [brcmfmac] genirq: exiting task "irq/140-brcmf_p" (175906) is an active IRQ thread (irq 140) ieee80211 phy2: brcmf_attach: dongle is not responding: err=-5 Which of the two windows that MSI arrived in is not known. Gate RX and re-arming on proto_ready. preinit publishes it after brcmf_attach() has created the protocol layer, before it enables the interrupt. The bus stop op clears it, masks, synchronizes the IRQ and masks again, as reset now does. brcmf_detach() calls that op before it frees the protocol layer, on reset, on remove, and when brcmf_attach() fails. irq_ready alone cannot be the gate: the hard handler clears it whenever it masks. Signed-off-by: Net Mojo <56850867+netmojo@users.noreply.github.com>
netmojo
force-pushed
the
fix/brcmfmac-detach-races
branch
from
November 11, 2026 18:17
2fd88bc to
a9b802b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This series closes four races in brcmfmac teardown and attach that can crash the kernel or use freed memory. They happen when
brcmf_detach()runs while other code still uses what it frees (on a bus reset after a firmware crash, on unbind, and onmodprobe -r), and when the IRQ thread reaches the protocol layer beforebrcmf_attach()has created it.Changes
brcmf_pcie_reset()detaches with the PCIe state still UP, so a running handler can queue a firmware event while fweh is freed, then unmask the interrupt.drvr->fweh. A reset that frees fweh without that lock leaves a timed-out create writing to freed memory.brcmf_fweh_detach()is split, so fweh is freed only after the interfaces are gone and detach has cycled the wiphy lock. P2P creation also refuses to start once the bus is down, and looks up the primary interface only after that check.iflist[i]and removes netdevs without RTNL or the wiphy lock, racingbrcmf_p2p_del_vif(). Taking both locks would hang RTNL users: unregistering an UP netdev closes it, and cfg80211's NETDEV_GOING_DOWN handler takes the wiphy mutex again.nl80211_del_interface()does:dev_close()under RTNL alone, then take the wiphy lock and unregister.brcmf_attach(), and the IRQ thread re-arms whenever the state is UP. Two MSIs before preinit reachbrcmf_proto_msgbuf_rx_trigger()with no protocol layer. A state left UP across a reset opens the same path during firmware download.The crash
Without commits 1 and 4, a debugfs reset (
echo 1 > /sys/kernel/debug/ieee80211/phyN/reset) on a BCM4387 with an AWDL interface up oopses in the IRQ thread:The faulting load is
drvr->proto->pdwithdrvr->protoNULL. The machine later hung at shutdown. Which window the MSI arrived in, and what shutdown waited for, are unknown. This was reproduced once.Testing
Setup: MacBook Pro 16-inch (M1 Pro, j316s), BCM4387/7, firmware 20.96.31.0.8.7.148, linux-aurora 7.2.11.aurora16 (
bed544b). Modules built out of tree with clang 23.1.4,W=1andCONFIG_BRCMDBG; the only warning is Kbuild's compiler-mismatch notice, which the base build also has. Every build includes the console-read bound from #453.Each run performs one operation with an AWDL interface up and an AirDrop window open, then checks the loaded module's build ID, reattach (a fresh firmware banner), no Oops, WARNING, BUG, lockdep, hung-task,
FW failed to initializeorDongle setup failedmessage, Wi-Fi reassociation, and a new window withawdl0UP.modprobe -rEach cell is a single run, so a pass shows only that the run recovered, not a failure rate.
What a pass doesn't show:
IOMMU faults: teardown logged new
apple-dart 581008000.iommu: translation faultlines, which the run checks don't look for:modprobe -rOther messages: teardown logged
brcmf_msgbuf_delete_flowring: timed out waiting for txstatus, and attach loggedadd iface p2p-dev-wlan0 type 10 failed: err=-52. The latter also appears when the unmodified in-tree module loads.AirDrop: reopening a window sometimes needed AWDL enable retries before frames were sent; after the full series' unbind it took three. Receive wasn't tested.
One unexplained failure: an earlier run of commits 1–3, without the wifi: brcmfmac: pcie: stop reading the firmware console at a bad index #453 bound and before the P2P lookup move, failed with
FW failed to initialize. It wasn't reproduced.Not tested: BCM4378, BCM4388, SDIO and USB buses, legacy (non-MSI) interrupts, suspend and resume, and an injected P2P_DEVICE create timeout racing detach.
Not fixed here
These are inherited from the base driver and each has its own issue:
p2p_deviovar fails because the bus went down, the fail path frees the vif but leavesifp->vifandbss_idx[DEVICE].vifpointing at it, and a laterbrcmf_p2p_ifp_removed()uses it.brcmf_fw_crashed()schedules another reset, anddevice_release_driver()follows at once.