PCI hot-plug: use dedicated taskqueue for device attach / detach

Attaching and detaching devices can be heavy-weight and detaching can
sleep waiting for events.  For that reason using the system-wide
single-threaded taskqueue_thread is not really appropriate.
There is even a possibility for a deadlock if taskqueue_thread is used
for detaching.

In fact, there is an easy to reproduce deadlock involving nvme, pass
and a sudden removal of an NVMe device.
A pass peripheral would not release a reference on an nvme sim until
pass_shutdown_kqueue() is executed via taskqueue_thread.  But the
taskqueue's thread is blocked in nvme_detach() -> ... -> cam_sim_free()
because of the outstanding reference.

MFC after:	10 days
Sponsored by:	CyberSecure
Reviewed by:	mav, imp
Differential Revision:	https://reviews.freebsd.org/D30144
This commit is contained in:
Andriy Gapon 2021-05-06 21:49:37 +03:00
parent 420dbe763f
commit 12588ce02d

View File

@ -925,6 +925,8 @@ SYSCTL_INT(_hw_pci, OID_AUTO, enable_pcie_hp, CTLFLAG_RDTUN,
&pci_enable_pcie_hp, 0, &pci_enable_pcie_hp, 0,
"Enable support for native PCI-express HotPlug."); "Enable support for native PCI-express HotPlug.");
TASKQUEUE_DEFINE_THREAD(pci_hp);
static void static void
pcib_probe_hotplug(struct pcib_softc *sc) pcib_probe_hotplug(struct pcib_softc *sc)
{ {
@ -1154,7 +1156,7 @@ pcib_pcie_hotplug_update(struct pcib_softc *sc, uint16_t val, uint16_t mask,
*/ */
if (schedule_task && if (schedule_task &&
(pcib_hotplug_present(sc) != 0) != (sc->child != NULL)) (pcib_hotplug_present(sc) != 0) != (sc->child != NULL))
taskqueue_enqueue(taskqueue_thread, &sc->pcie_hp_task); taskqueue_enqueue(taskqueue_pci_hp, &sc->pcie_hp_task);
} }
static void static void
@ -1449,7 +1451,7 @@ pcib_detach_hotplug(struct pcib_softc *sc)
error = pcib_release_pcie_irq(sc); error = pcib_release_pcie_irq(sc);
if (error) if (error)
return (error); return (error);
taskqueue_drain(taskqueue_thread, &sc->pcie_hp_task); taskqueue_drain(taskqueue_pci_hp, &sc->pcie_hp_task);
callout_drain(&sc->pcie_ab_timer); callout_drain(&sc->pcie_ab_timer);
callout_drain(&sc->pcie_cc_timer); callout_drain(&sc->pcie_cc_timer);
callout_drain(&sc->pcie_dll_timer); callout_drain(&sc->pcie_dll_timer);