diff --git a/sys/dev/usb/net/if_usie.c b/sys/dev/usb/net/if_usie.c --- a/sys/dev/usb/net/if_usie.c +++ b/sys/dev/usb/net/if_usie.c @@ -483,6 +483,7 @@ usbd_transfer_unsetup(sc->sc_if_xfer, USIE_IF_N_XFER); bpfdetach(sc->sc_ifp); if_detach(sc->sc_ifp); + if_slow_drain(sc->sc_ifp); if_free(sc->sc_ifp); sc->sc_ifp = NULL; } diff --git a/sys/dev/usb/net/uhso.c b/sys/dev/usb/net/uhso.c --- a/sys/dev/usb/net/uhso.c +++ b/sys/dev/usb/net/uhso.c @@ -693,8 +693,9 @@ uhso_if_stop(sc); bpfdetach(sc->sc_ifp); if_detach(sc->sc_ifp); - if_free(sc->sc_ifp); mtx_unlock(&sc->sc_mtx); + if_slow_drain(sc->sc_ifp); + if_free(sc->sc_ifp); usbd_transfer_unsetup(sc->sc_if_xfer, UHSO_IFNET_MAX); } diff --git a/sys/dev/usb/net/usb_ethernet.c b/sys/dev/usb/net/usb_ethernet.c --- a/sys/dev/usb/net/usb_ethernet.c +++ b/sys/dev/usb/net/usb_ethernet.c @@ -292,6 +292,7 @@ /* free unit */ free_unr(ueunit, ue->ue_unit); if (ue->ue_ifp != NULL) { + if_slow_drain(ue->ue_ifp); if_free(ue->ue_ifp); ue->ue_ifp = NULL; } @@ -311,6 +312,9 @@ ifp = ue->ue_ifp; if (ifp != NULL) { + /* drain all IOCTLs */ + if_slow_drain(ifp); + /* we are not running any more */ UE_LOCK(ue); ifp->if_drv_flags &= ~IFF_DRV_RUNNING; diff --git a/sys/dev/usb/usb_pf.c b/sys/dev/usb/usb_pf.c --- a/sys/dev/usb/usb_pf.c +++ b/sys/dev/usb/usb_pf.c @@ -232,6 +232,7 @@ USB_BUS_UNLOCK(ubus); bpfdetach(ifp); if_detach(ifp); + if_slow_drain(ifp); if_free(ifp); ifc_free_unit(ifc, unit); diff --git a/sys/net/if.c b/sys/net/if.c --- a/sys/net/if.c +++ b/sys/net/if.c @@ -645,6 +645,8 @@ ifq_init(&ifp->if_snd, ifp); refcount_init(&ifp->if_refcount, 1); /* Index reference. */ + refcount_init(&ifp->if_slowref, 1); + for (int i = 0; i < IFCOUNTERS; i++) ifp->if_counters[i] = counter_u64_alloc(M_WAITOK); ifp->if_get_counter = if_get_counter_default; @@ -753,6 +755,32 @@ NET_EPOCH_CALL(if_destroy, &ifp->if_epoch_ctx); } +/* + * Keep track of slow path configuration events. + * Returns true on success and false on failure. + */ +bool +if_slow_ref(struct ifnet *ifp) +{ + return (refcount_acquire_if_not_zero(&ifp->if_slowref)); +} + +void +if_slow_drain(struct ifnet *ifp) +{ + if (refcount_release(&ifp->if_slowref)) + return; + + while (refcount_load(&ifp->if_slowref) != 0) + pause("W", hz); +} + +void +if_slow_unref(struct ifnet *ifp) +{ + (void) refcount_release(&ifp->if_slowref); +} + void ifq_init(struct ifaltq *ifq, struct ifnet *ifp) { @@ -2459,8 +2487,8 @@ /* * Hardware specific interface ioctls. */ -int -ifhwioctl(u_long cmd, struct ifnet *ifp, caddr_t data, struct thread *td) +static inline int +ifhwioctl_sub(u_long cmd, struct ifnet *ifp, caddr_t data, struct thread *td) { struct ifreq *ifr; int error = 0, do_ifup = 0; @@ -2887,6 +2915,19 @@ return (error); } +int +ifhwioctl(u_long cmd, struct ifnet *ifp, caddr_t data, struct thread *td) +{ + int error; + + if (if_slow_ref(ifp) == false) + return (ENXIO); + error = ifhwioctl_sub(cmd, ifp, data, td); + if_slow_unref(ifp); + return (error); +} + + #ifdef COMPAT_FREEBSD32 struct ifconf32 { int32_t ifc_len; diff --git a/sys/net/if_var.h b/sys/net/if_var.h --- a/sys/net/if_var.h +++ b/sys/net/if_var.h @@ -313,6 +313,7 @@ void *if_linkmib; /* link-type-specific MIB data */ size_t if_linkmiblen; /* length of above data */ u_int if_refcount; /* reference count */ + u_int if_slowref; /* reference count (slow path) */ /* These fields are shared with struct if_data. */ uint8_t if_type; /* ethernet, tokenring, etc */ @@ -660,6 +661,9 @@ int if_printf(struct ifnet *, const char *, ...) __printflike(2, 3); void if_ref(struct ifnet *); void if_rele(struct ifnet *); +bool if_slow_ref(struct ifnet *) __result_use_check; +void if_slow_drain(struct ifnet *); +void if_slow_unref(struct ifnet *); int if_setlladdr(struct ifnet *, const u_char *, int); int if_tunnel_check_nesting(struct ifnet *, struct mbuf *, uint32_t, int); void if_up(struct ifnet *);