git: fe00ca2f8871 - main - iflib: Complete registration failure cleanup
Kevin Bowling <[email protected]>
| Newsgroups | gmane.os.freebsd.devel.cvs.src,gmane.os.freebsd.current.scm |
|---|---|
| Message-ID | <[email protected]> |
The branch main has been updated by kbowling: URL: https://cgit.FreeBSD.org/src/commit/?id=fe00ca2f88718c7751b8953cc8ee7fc0dad0bbe1 commit fe00ca2f88718c7751b8953cc8ee7fc0dad0bbe1 Author: Kevin Bowling <[email protected]> AuthorDate: 2026-08-08 04:14:53 +0000 Commit: Kevin Bowling <[email protected]> CommitDate: 2026-08-14 21:49:21 +0000 iflib: Complete registration failure cleanup Pre-attach sysctls contain pointers into the iflib context. Any later registration failure that frees the context must first remove that sysctl tree. Failures after a successful IFDI_ATTACH_PRE also did not consistently call IFDI_DETACH or free the private taskqueue. In particular, routing a taskqueue creation failure through the context cleanup could free the driver softc while resources allocated by attach_pre remained live. Track successful interrupt and queue setup and use one common unwind path. Invoke IFDI_DETACH with IFNET_WLOCK dropped and release only resources whose setup completed. Leave a failed IFDI_ATTACH_PRE to unwind its own partial state, as required by the existing driver contract. A failed post-attach can follow driver registration of an SR-IOV schema. Remove that registration before detaching the interface and driver, matching normal deregistration, so a failed attach cannot leave a stale /dev/iov node or make the next attach report EBUSY. A successful attach_pre can now be followed by detach before driver queue allocation. Make the remaining queue-backed interrupt cleanup paths tolerate absent queue arrays. Mark a failed registration as detaching before draining the entire private taskqueue. Drivers can register configuration tasks there, and taskqueue_drain_all() does not wait for work queued during its drain. Make every current non-admin callback reject detaching contexts so late work cannot touch driver state. Drain tasks and call ether_ifdetach() with neither the ifnet nor context lock held. A callback already running may need either lock, while ether_ifdetach() acquires ifnet_detach_sx. Reacquire IFNET_WLOCK before the context lock to preserve the established lock order. The shared automatic core-offset allocator also lacked acquisition state. Late registration failures leaked its reference, while normal detach could decrement a reference belonging to another device when a configured offset or allocation failure meant that this context never acquired one. Record acquisition explicitly and release only references held. MFC after: 2 weeks Reviewed by: gallatin Sponsored by: BBOX.io Differential Revision: https://reviews.freebsd.org/D58721 --- sys/dev/bnxt/bnxt_en/if_bnxt.c | 9 ++- sys/dev/enetc/if_enetc.c | 10 ++- sys/dev/igc/if_igc.c | 5 +- sys/dev/ixgbe/if_sriov.c | 2 + sys/dev/vmware/vmxnet3/if_vmx.c | 8 ++- sys/net/iflib.c | 143 ++++++++++++++++++++++++++++++---------- sys/net/iflib.h | 1 + 7 files changed, 135 insertions(+), 43 deletions(-) diff --git a/sys/dev/bnxt/bnxt_en/if_bnxt.c b/sys/dev/bnxt/bnxt_en/if_bnxt.c index ec88b30edcde..432dceb6bfb2 100644 --- a/sys/dev/bnxt/bnxt_en/if_bnxt.c +++ b/sys/dev/bnxt/bnxt_en/if_bnxt.c @@ -5492,12 +5492,17 @@ bnxt_def_cp_task(void *context, int pending) /* Handle completions on the default completion ring */ struct cmpl_base *cmpl; - uint32_t cons = cpr->cons; - bool v_bit = cpr->v_bit; + uint32_t cons; + bool v_bit; bool last_v_bit; uint32_t last_cons; uint16_t type; + if (iflib_in_detach(ctx)) + return; + cons = cpr->cons; + v_bit = cpr->v_bit; + for (;;) { last_cons = cons; last_v_bit = v_bit; diff --git a/sys/dev/enetc/if_enetc.c b/sys/dev/enetc/if_enetc.c index 53002f9d73ce..55fec66266fe 100644 --- a/sys/dev/enetc/if_enetc.c +++ b/sys/dev/enetc/if_enetc.c @@ -456,14 +456,18 @@ enetc_detach(if_ctx_t ctx) sc = iflib_get_softc(ctx); - for (i = 0; i < sc->rx_num_queues; i++) - iflib_irq_free(ctx, &sc->rx_queues[i].irq); + if (sc->rx_queues != NULL) { + for (i = 0; i < sc->rx_num_queues; i++) + iflib_irq_free(ctx, &sc->rx_queues[i].irq); + } bus_generic_detach(sc->dev); - if (sc->regs != NULL) + if (sc->regs != NULL) { error = bus_release_resource(sc->dev, SYS_RES_MEMORY, rman_get_rid(sc->regs), sc->regs); + sc->regs = NULL; + } if (sc->ctrl_queue.dma.idi_size != 0) iflib_dma_free(&sc->ctrl_queue.dma); diff --git a/sys/dev/igc/if_igc.c b/sys/dev/igc/if_igc.c index 11fe32f62df3..7026a4e50bb9 100644 --- a/sys/dev/igc/if_igc.c +++ b/sys/dev/igc/if_igc.c @@ -1988,8 +1988,9 @@ igc_free_pci_resources(if_ctx_t ctx) if (sc->intr_type == IFLIB_INTR_MSIX) iflib_irq_free(ctx, &sc->irq); - for (int i = 0; i < sc->rx_num_queues; i++, que++) { - iflib_irq_free(ctx, &que->que_irq); + if (que != NULL) { + for (int i = 0; i < sc->rx_num_queues; i++, que++) + iflib_irq_free(ctx, &que->que_irq); } if (sc->memory != NULL) { diff --git a/sys/dev/ixgbe/if_sriov.c b/sys/dev/ixgbe/if_sriov.c index 9fc3437a2cf3..09954a519664 100644 --- a/sys/dev/ixgbe/if_sriov.c +++ b/sys/dev/ixgbe/if_sriov.c @@ -775,6 +775,8 @@ ixgbe_iov_recovery_task(void *context, int pending __unused) int i, iov_pos, n, num_vfs = 0, recovery_vf; ctx = context; + if (iflib_in_detach(ctx)) + return; sc = iflib_get_softc(ctx); ctx_lock = iflib_ctx_lock_get(ctx); diff --git a/sys/dev/vmware/vmxnet3/if_vmx.c b/sys/dev/vmware/vmxnet3/if_vmx.c index c3706ed24ff5..744205ef0e7d 100644 --- a/sys/dev/vmware/vmxnet3/if_vmx.c +++ b/sys/dev/vmware/vmxnet3/if_vmx.c @@ -527,9 +527,11 @@ vmxnet3_free_irqs(struct vmxnet3_softc *sc) scctx = sc->vmx_scctx; - for (i = 0; i < scctx->isc_nrxqsets; i++) { - rxq = &sc->vmx_rxq[i]; - iflib_irq_free(sc->vmx_ctx, &rxq->vxrxq_irq); + if (sc->vmx_rxq != NULL) { + for (i = 0; i < scctx->isc_nrxqsets; i++) { + rxq = &sc->vmx_rxq[i]; + iflib_irq_free(sc->vmx_ctx, &rxq->vxrxq_irq); + } } iflib_irq_free(sc->vmx_ctx, &sc->vmx_event_intr_irq); diff --git a/sys/net/iflib.c b/sys/net/iflib.c index d2ac8d884a60..7b5de820fd71 100644 --- a/sys/net/iflib.c +++ b/sys/net/iflib.c @@ -205,6 +205,7 @@ struct iflib_ctx { uint8_t ifc_sysctl_use_logical_cores; uint16_t ifc_sysctl_extra_msix_vectors; bool ifc_cpus_are_physical_cores; + bool ifc_core_offset_ref; bool ifc_sysctl_simple_tx; bool ifc_sysctl_tx_defer_mfree; uint16_t ifc_sysctl_tx_reclaim_thresh; @@ -4259,6 +4260,8 @@ _task_fn_iov(void *context, int pending) { if_ctx_t ctx = context; + if (iflib_in_detach(ctx)) + return; if (!(if_getdrvflags(ctx->ifc_ifp) & IFF_DRV_RUNNING) && !(ctx->ifc_sctx->isc_flags & IFLIB_ADMIN_ALWAYS_RUN)) return; @@ -5071,6 +5074,7 @@ get_ctx_core_offset(if_ctx_t ctx) unsigned int last_valid; unsigned int i; + MPASS(!ctx->ifc_core_offset_ref); first_valid = CPU_FFS(&ctx->ifc_cpus) - 1; last_valid = CPU_FLS(&ctx->ifc_cpus) - 1; @@ -5144,6 +5148,7 @@ get_ctx_core_offset(if_ctx_t ctx) cores_consumed); MPASS(op->refcount < UINT_MAX); op->refcount++; + ctx->ifc_core_offset_ref = true; break; } } @@ -5160,6 +5165,7 @@ get_ctx_core_offset(if_ctx_t ctx) op->refcount = 1; CPU_COPY(&ctx->ifc_cpus, &op->set); SLIST_INSERT_HEAD(&cpu_offsets, op, entries); + ctx->ifc_core_offset_ref = true; } } mtx_unlock(&cpu_offset_mtx); @@ -5172,6 +5178,9 @@ unref_ctx_core_offset(if_ctx_t ctx) { struct cpu_offset *op, *top; + if (!ctx->ifc_core_offset_ref) + return; + mtx_lock(&cpu_offset_mtx); SLIST_FOREACH_SAFE(op, &cpu_offsets, entries, top) { if (CPU_CMP(&ctx->ifc_cpus, &op->set) == 0) { @@ -5181,10 +5190,12 @@ unref_ctx_core_offset(if_ctx_t ctx) SLIST_REMOVE(&cpu_offsets, op, cpu_offset, entries); free(op, M_IFLIB); } + ctx->ifc_core_offset_ref = false; break; } } mtx_unlock(&cpu_offset_mtx); + MPASS(!ctx->ifc_core_offset_ref); } int @@ -5195,10 +5206,17 @@ iflib_device_register(device_t dev, void *sc, if_shared_ctx_t sctx, if_ctx_t *ct if_softc_ctx_t scctx; kobjop_desc_t kobj_desc; kobj_method_t *kobj_method; + bool attach_pre_succeeded, intr_allocated, queues_allocated; int err, msix, rid; +#ifdef PCI_IOV + int iov_error; +#endif int num_txd, num_rxd; char namebuf[TASKQUEUE_NAMELEN]; + attach_pre_succeeded = false; + intr_allocated = false; + queues_allocated = false; ctx = malloc(sizeof(*ctx), M_IFLIB, M_WAITOK | M_ZERO); if (sc == NULL) { @@ -5229,8 +5247,9 @@ iflib_device_register(device_t dev, void *sc, if_shared_ctx_t sctx, if_ctx_t *ct CTX_LOCK(ctx); if ((err = IFDI_ATTACH_PRE(ctx)) != 0) { device_printf(dev, "IFDI_ATTACH_PRE failed %d\n", err); - goto fail_unlock; + goto fail_cleanup; } + attach_pre_succeeded = true; _iflib_pre_assert(scctx); ctx->ifc_txrx = *scctx->isc_txrx; @@ -5298,7 +5317,8 @@ iflib_device_register(device_t dev, void *sc, if_shared_ctx_t sctx, if_ctx_t *ct taskqueue_thread_enqueue, &ctx->ifc_tq); if (ctx->ifc_tq == NULL) { device_printf(dev, "Unable to create admin taskqueue\n"); - return (ENOMEM); + err = ENOMEM; + goto fail_cleanup; } err = taskqueue_start_threads(&ctx->ifc_tq, 1, PI_NET, "%s", namebuf); @@ -5307,7 +5327,8 @@ iflib_device_register(device_t dev, void *sc, if_shared_ctx_t sctx, if_ctx_t *ct "Unable to start admin taskqueue threads error: %d\n", err); taskqueue_free(ctx->ifc_tq); - return (err); + ctx->ifc_tq = NULL; + goto fail_cleanup; } TASK_INIT(&ctx->ifc_admin_task, 0, _task_fn_admin, ctx); @@ -5341,14 +5362,16 @@ iflib_device_register(device_t dev, void *sc, if_shared_ctx_t sctx, if_ctx_t *ct scctx->isc_intr = IFLIB_INTR_LEGACY; msix = 0; } + intr_allocated = true; /* Get memory for the station queues */ if ((err = iflib_queues_alloc(ctx))) { device_printf(dev, "Unable to allocate queue memory\n"); - goto fail_intr_free; + goto fail_cleanup; } + queues_allocated = true; if ((err = iflib_qset_structures_setup(ctx))) - goto fail_queues; + goto fail_cleanup; /* * Now that we know how many queues there are, get the core offset. @@ -5367,7 +5390,7 @@ iflib_device_register(device_t dev, void *sc, if_shared_ctx_t sctx, if_ctx_t *ct device_printf(dev, "MSI-X requires ifdi_rx_queue_intr_enable method"); err = EOPNOTSUPP; - goto fail_queues; + goto fail_cleanup; } kobj_desc = &ifdi_tx_queue_intr_enable_desc; kobj_method = kobj_lookup_method(((kobj_t)ctx)->ops->cls, NULL, @@ -5376,7 +5399,7 @@ iflib_device_register(device_t dev, void *sc, if_shared_ctx_t sctx, if_ctx_t *ct device_printf(dev, "MSI-X requires ifdi_tx_queue_intr_enable method"); err = EOPNOTSUPP; - goto fail_queues; + goto fail_cleanup; } /* @@ -5388,7 +5411,7 @@ iflib_device_register(device_t dev, void *sc, if_shared_ctx_t sctx, if_ctx_t *ct if (err != 0) { device_printf(dev, "IFDI_MSIX_INTR_ASSIGN failed %d\n", err); - goto fail_queues; + goto fail_cleanup; } } else if (scctx->isc_intr != IFLIB_INTR_MSIX) { rid = 0; @@ -5398,13 +5421,13 @@ iflib_device_register(device_t dev, void *sc, if_shared_ctx_t sctx, if_ctx_t *ct } if ((err = iflib_legacy_setup(ctx, ctx->isc_legacy_intr, ctx->ifc_softc, &rid, "irq0")) != 0) { device_printf(dev, "iflib_legacy_setup failed %d\n", err); - goto fail_queues; + goto fail_cleanup; } } else { device_printf(dev, "Cannot use iflib with only 1 MSI-X interrupt!\n"); err = ENODEV; - goto fail_queues; + goto fail_cleanup; } /* @@ -5452,42 +5475,96 @@ iflib_device_register(device_t dev, void *sc, if_shared_ctx_t sctx, if_ctx_t *ct return (0); fail_detach: + STATE_LOCK(ctx); + ctx->ifc_flags |= IFC_IN_DETACH; + STATE_UNLOCK(ctx); + /* Tasks may need either lock; ether_ifdetach() takes ifnet_detach_sx. */ CTX_UNLOCK(ctx); - taskqueue_drain(ctx->ifc_tq, &ctx->ifc_admin_task); + IFNET_WUNLOCK(); + taskqueue_drain_all(ctx->ifc_tq); +#ifdef PCI_IOV + /* + * IFDI_ATTACH_POST may have registered an SR-IOV schema. Match the + * normal deregistration order so a failed attach cannot leave a stale + * /dev/iov node behind. device_attach() holds Giant throughout this + * path, so an IOV configuration cannot race the detach. + */ + if (!CTX_IS_VF(ctx)) { + iov_error = pci_iov_detach(dev); + if (iov_error != 0) + device_printf(dev, "Could not detach SR-IOV after " + "attach failure: %d\n", iov_error); + } +#endif ether_ifdetach(ctx->ifc_ifp); + IFNET_WLOCK(); CTX_LOCK(ctx); -fail_queues: - sysctl_ctx_free(&ctx->ifc_sysctl_ctx); - ctx->ifc_sysctl_node = NULL; + goto fail_cleanup_detaching; + +fail_cleanup: + STATE_LOCK(ctx); + ctx->ifc_flags |= IFC_IN_DETACH; + STATE_UNLOCK(ctx); + +fail_cleanup_detaching: /* - * Drain without holding CTX_LOCK so _task_fn_admin can run to - * completion if it needs the context lock. On fail_detach we already - * drained above; a second drain is a no-op when the queue is empty. + * The pre-attach sysctls contain pointers into ctx. Remove them on + * every registration failure before iflib_deregister() frees ctx. */ - CTX_UNLOCK(ctx); - taskqueue_drain(ctx->ifc_tq, &ctx->ifc_admin_task); - CTX_LOCK(ctx); - iflib_tqg_detach(ctx); - iflib_tx_structures_free(ctx); - iflib_rx_structures_free(ctx); + if (ctx->ifc_sysctl_node != NULL) { + sysctl_ctx_free(&ctx->ifc_sysctl_ctx); + ctx->ifc_sysctl_node = NULL; + } + + if (ctx->ifc_tq != NULL) { + /* + * Drain without holding the ifnet or context locks so configuration + * tasks can run to completion. On fail_detach a second drain also + * catches tasks queued during the first drain. + */ + CTX_UNLOCK(ctx); + IFNET_WUNLOCK(); + taskqueue_drain_all(ctx->ifc_tq); + IFNET_WLOCK(); + CTX_LOCK(ctx); + } + + if (queues_allocated) { + iflib_tqg_detach(ctx); + iflib_tx_structures_free(ctx); + iflib_rx_structures_free(ctx); + } + /* - * Match iflib_device_deregister: IFDI_DETACH before taskqueue_free. - * Avoid IFNET_WLOCK across driver detach (LinuxKPI workqueue drain). + * A successful IFDI_ATTACH_PRE must be matched by IFDI_DETACH, even + * when registration fails before queue allocation. Match + * iflib_device_deregister by detaching before taskqueue_free, and avoid + * holding IFNET_WLOCK across driver detach (LinuxKPI workqueue drain). */ - IFNET_WUNLOCK(); - IFDI_DETACH(ctx); - IFDI_QUEUES_FREE(ctx); - IFNET_WLOCK(); - taskqueue_free(ctx->ifc_tq); -fail_intr_free: - iflib_free_intr_mem(ctx); -fail_unlock: + if (attach_pre_succeeded) { + IFNET_WUNLOCK(); + IFDI_DETACH(ctx); + if (queues_allocated) + IFDI_QUEUES_FREE(ctx); + /* Reacquire the global lock before the context lock. */ + CTX_UNLOCK(ctx); + IFNET_WLOCK(); + CTX_LOCK(ctx); + } + if (ctx->ifc_tq != NULL) { + taskqueue_free(ctx->ifc_tq); + ctx->ifc_tq = NULL; + } + if (intr_allocated) + iflib_free_intr_mem(ctx); + CTX_UNLOCK(ctx); IFNET_WUNLOCK(); iflib_deregister(ctx); device_set_softc(ctx->ifc_dev, NULL); if (ctx->ifc_flags & IFC_SC_ALLOCATED) free(ctx->ifc_softc, M_IFLIB); + unref_ctx_core_offset(ctx); free(ctx, M_IFLIB); return (err); } diff --git a/sys/net/iflib.h b/sys/net/iflib.h index 40080b49b0ab..0f35f1080bad 100644 --- a/sys/net/iflib.h +++ b/sys/net/iflib.h @@ -481,6 +481,7 @@ void iflib_irq_free(if_ctx_t ctx, if_irq_t irq); void iflib_io_tqg_attach(struct grouptask *gt, void *uniq, int cpu, const char *name); +/* Configuration task callbacks must return when iflib_in_detach() is true. */ void iflib_config_task_init(if_ctx_t ctx, struct task *config_task, task_fn_t *fn); void iflib_config_task_enqueue(if_ctx_t ctx, struct task *config_task);