Module Name: src Committed By: thorpej Date: Tue Nov 14 14:47:04 UTC 2023
Modified Files: src/sys/net [thorpej-ifq]: if.c if.h Log Message: New network interface output queue API. To generate a diff of this commit: cvs rdiff -u -r1.529.2.1 -r1.529.2.1.2.1 src/sys/net/if.c cvs rdiff -u -r1.305.2.1 -r1.305.2.1.2.1 src/sys/net/if.h Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/sys/net/if.c diff -u src/sys/net/if.c:1.529.2.1 src/sys/net/if.c:1.529.2.1.2.1 --- src/sys/net/if.c:1.529.2.1 Sat Nov 11 13:16:30 2023 +++ src/sys/net/if.c Tue Nov 14 14:47:03 2023 @@ -1,7 +1,7 @@ -/* $NetBSD: if.c,v 1.529.2.1 2023/11/11 13:16:30 thorpej Exp $ */ +/* $NetBSD: if.c,v 1.529.2.1.2.1 2023/11/14 14:47:03 thorpej Exp $ */ /*- - * Copyright (c) 1999, 2000, 2001, 2008 The NetBSD Foundation, Inc. + * Copyright (c) 1999, 2000, 2001, 2008, 2023 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation @@ -90,7 +90,7 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: if.c,v 1.529.2.1 2023/11/11 13:16:30 thorpej Exp $"); +__KERNEL_RCSID(0, "$NetBSD: if.c,v 1.529.2.1.2.1 2023/11/14 14:47:03 thorpej Exp $"); #if defined(_KERNEL_OPT) #include "opt_inet.h" @@ -105,6 +105,7 @@ __KERNEL_RCSID(0, "$NetBSD: if.c,v 1.529 #include <sys/mbuf.h> #include <sys/systm.h> #include <sys/callout.h> +#include <sys/condvar.h> #include <sys/proc.h> #include <sys/socket.h> #include <sys/socketvar.h> @@ -199,6 +200,7 @@ static struct workqueue *ifnet_link_sta static struct workqueue *if_slowtimo_wq __read_mostly; static kmutex_t if_clone_mtx; +static kcondvar_t ifq_stop_cond; struct ifnet *lo0ifp; int ifqmaxlen = IFQ_MAXLEN; @@ -330,6 +332,7 @@ ifinit1(void) #endif mutex_init(&if_clone_mtx, MUTEX_DEFAULT, IPL_NONE); + cv_init(&ifq_stop_cond, "ifq_stop"); TAILQ_INIT(&ifnet_list); mutex_init(&ifnet_mtx, MUTEX_DEFAULT, IPL_NONE); @@ -728,9 +731,6 @@ if_initialize(ifnet_t *ifp) * if_alloc_sadl(). */ - if (ifp->if_snd.ifq_maxlen == 0) - ifp->if_snd.ifq_maxlen = ifqmaxlen; - ifp->if_broadcastaddr = 0; /* reliably crash if used uninitialized */ ifp->if_link_state = LINK_STATE_UNKNOWN; @@ -745,7 +745,10 @@ if_initialize(ifnet_t *ifp) altq_alloc(&ifp->if_snd, ifp); #endif - IFQ_LOCK_INIT(&ifp->if_snd); + if (ifp->if_snd.ifq_lock == NULL) { + ifq_init(&ifp->if_snd, ifqmaxlen); + } + KASSERT(ifp->if_snd.ifq_lock != NULL); ifp->if_pfil = pfil_head_create(PFIL_TYPE_IFNET, ifp); pfil_run_ifhooks(if_pfil, PFIL_IFNET_ATTACH, ifp); @@ -1535,7 +1538,7 @@ restart: mutex_obj_free(ifp->if_ioctl_lock); ifp->if_ioctl_lock = NULL; - mutex_obj_free(ifp->if_snd.ifq_lock); + ifq_fini(&ifp->if_snd); if_stats_fini(ifp); KASSERT(!simplehook_has_hooks(ifp->if_linkstate_hooks)); simplehook_destroy(ifp->if_linkstate_hooks); @@ -3821,8 +3824,599 @@ if_transmit_lock(struct ifnet *ifp, stru } /* + * ifq_init -- + * + * Initialize an interface queue. + */ +void +ifq_init(struct ifqueue * const ifq, unsigned int maxqlen) +{ +#ifdef ALTQ + /* + * ALTQ data can be allocated via IFQ_SET_READY() which + * can be called before if_initialize(), which in turn + * calls ifq_init(). Preserve it. + */ + struct ifaltq *altq = ifq->ifq_altq; +#endif + + /* + * XXX Temporary measure to handle drivers that set ifq_maxqlen + * XXX before calling if_attach(). + */ + if (ifq->ifq_maxlen != 0) { + maxqlen = ifq->ifq_maxlen; + } + + memset(ifq, 0, sizeof(*ifq)); +#ifdef ALTQ + ifq->ifq_altq = altq; +#endif + ifq->ifq_lock = mutex_obj_alloc(MUTEX_DEFAULT, IPL_NET); + ifq_set_maxlen(ifq, maxqlen); + ifq->ifq_state = IFQ_STATE_INVALID; /* transitional */ +} + +/* + * ifq_fini -- + * + * Tear down an interface queue. + */ +void +ifq_fini(struct ifqueue * const ifq) +{ + if (ifq->ifq_lock != NULL) { + ifq_purge(ifq); + mutex_obj_free(ifq->ifq_lock); + ifq->ifq_lock = NULL; + } + ifq->ifq_state = IFQ_STATE_DEAD; +} + +/* + * ifq_start -- + * + * Record that the Tx process is ready to run. Called from + * an interface's init routine. + */ +void +ifq_start(struct ifqueue * const ifq) +{ + mutex_enter(ifq->ifq_lock); + KASSERT(ifq->ifq_state == IFQ_STATE_STOPPED || + ifq->ifq_state == IFQ_STATE_INVALID); + ifq->ifq_state = IFQ_STATE_READY; + mutex_exit(ifq->ifq_lock); +} + +/* + * ifq_stop -- + * + * Request that the Tx process stop and wait for it to do so. + * Called from an interface's stop routine. + */ +void +ifq_stop(struct ifqueue * const ifq) +{ + ASSERT_SLEEPABLE(); + + mutex_enter(ifq->ifq_lock); + switch (ifq->ifq_state) { + case IFQ_STATE_INVALID: /* transitional */ + case IFQ_STATE_STOPPED: + break; + + case IFQ_STATE_READY: + ifq->ifq_state = IFQ_STATE_STOPPED; + break; + + case IFQ_STATE_BUSY: + ifq->ifq_state = IFQ_STATE_WAIT_STOP; + /* FALLTHROUGH */ + + default: + KASSERT(ifq->ifq_state == IFQ_STATE_WAIT_STOP); + while (ifq->ifq_state != IFQ_STATE_STOPPED) { + cv_wait(&ifq_stop_cond, ifq->ifq_lock); + } + break; + } + mutex_exit(ifq->ifq_lock); +} + +__CTASSERT(IFQ_MAXLEN <= INT_MAX); + +/* + * ifq_set_maxlen -- + * + * Set the max queue length on the specified interface queue. + * Silently saturates to the system limits. + */ +void +ifq_set_maxlen(struct ifqueue * const ifq, unsigned int maxqlen) +{ + if (maxqlen > IFQ_MAXLEN) { + maxqlen = IFQ_MAXLEN; + } else if (maxqlen == 0) { + KASSERT(ifqmaxlen > 0); + maxqlen = ifqmaxlen; + } + mutex_enter(ifq->ifq_lock); + ifq->ifq_maxlen = (int)maxqlen; + mutex_exit(ifq->ifq_lock); +} + +/* + * ifq_maxlen -- + * + * Return the max queue length for the specified interface + * queue. + */ +unsigned int +ifq_maxlen(struct ifqueue * const ifq) +{ + unsigned int rv; + + mutex_enter(ifq->ifq_lock); + rv = ifq->ifq_maxlen; + mutex_exit(ifq->ifq_lock); + + return rv; +} + +/* + * ifq_drops -- + * + * Return the current drop count on the specified interface + * queue. + */ +uint64_t +ifq_drops(struct ifqueue * const ifq) +{ + uint64_t rv; + + mutex_enter(ifq->ifq_lock); + rv = ifq->ifq_drops; + mutex_exit(ifq->ifq_lock); + + return rv; +} + +#ifdef ALTQ +/* + * ifq_nextpkt_slow -- + * + * Internal helper for ifq_get() and ifq_stage(). This handles + * the ALTQ case, which must take the KERNEL_LOCK. + */ +static struct mbuf * +ifq_nextpkt_slow(struct ifqueue * const ifq, bool const staging) +{ + struct mbuf *m; + + /* + * We have to acquire the KERNEL_LOCK, so we need to + * drop the ifq_lock temporarily so that we can acquire + * them in the correct order. + * + * Once we've done that, we need to check everything again. + */ + mutex_exit(ifq->ifq_lock); + KERNEL_LOCK(1, NULL); + mutex_enter(ifq->ifq_lock); + + if (ifq->ifq_staged != NULL) { + m = ifq->ifq_staged; + } else if (TBR_IS_ENABLED(ifq)) { + m = tbr_dequeue(ifq->ifq_altq, ALTDQ_REMOVE); + } else if (ALTQ_IS_ENABLED(ifq)) { + ALTQ_DEQUEUE(ifq, m); + } else { + IF_DEQUEUE(ifq, m); + } + KERNEL_UNLOCK_ONE(NULL); + + KASSERT(ifq->ifq_staged == NULL || ifq->ifq_staged == m); + if (__predict_true(staging)) { + ifq->ifq_staged = m; + } else { + ifq->ifq_staged = NULL; + } + + return m; +} +#endif /* ALTQ */ + +/* + * ifq_get_nextstate -- + * + * Perform the state transition for ifq_get() (BUSY -> READY or + * WAIT_STOP -> STOPPED). The same state transitions are used + * for ifq_stage() and ifq_continue(). + */ +static inline void +ifq_get_nextstate(struct ifqueue * const ifq) +{ + switch (ifq->ifq_state) { + case IFQ_STATE_INVALID: /* transitional */ + break; + + case IFQ_STATE_BUSY: + ifq->ifq_state = IFQ_STATE_READY; + break; + + default: + KASSERTMSG(ifq->ifq_state == IFQ_STATE_WAIT_STOP, + "invalid state=%d\n", (int)ifq->ifq_state); + ifq->ifq_state = IFQ_STATE_STOPPED; + cv_broadcast(&ifq_stop_cond); + break; + } +} + +/* + * ifq_get -- + * + * Get the next packet of off the specified interface queue. + * The packet is removed from the queue, and the caller is + * responsible for disposing of it. + */ +struct mbuf * +ifq_get(struct ifqueue * const ifq) +{ + struct mbuf *m; + + mutex_enter(ifq->ifq_lock); + + if ((m = ifq->ifq_staged) != NULL) + ifq->ifq_staged = NULL; + else +#ifdef ALTQ + if (__predict_false(ALTQ_IS_ENABLED(ifq))) + m = ifq_nextpkt_slow(ifq, false); + else +#endif /* ALTQ */ + IF_DEQUEUE(ifq, m); + + if (m == NULL) { + ifq_get_nextstate(ifq); + } + + mutex_exit(ifq->ifq_lock); + + return m; +} + +/* + * ifq_stage -- + * + * Stage a packet on the specified interface queue for processing. + * If a packet is already in the staging area, then we return that + * one, otherwise we get the next one from the queue and place it + * into the staging area. + */ +struct mbuf * +ifq_stage(struct ifqueue * const ifq) +{ + struct mbuf *m; + + mutex_enter(ifq->ifq_lock); + + if (ifq->ifq_staged != NULL) + m = ifq->ifq_staged; + else +#ifdef ALTQ + if (__predict_false(ALTQ_IS_ENABLED(ifq))) + m = ifq_nextpkt_slow(ifq, true); + else +#endif /* ALTQ */ + { + IF_DEQUEUE(ifq, m); + ifq->ifq_staged = m; + } + + if (m == NULL) { + ifq_get_nextstate(ifq); + } + + mutex_exit(ifq->ifq_lock); + + return m; +} + +#ifdef ALTQ +/* + * ifq_put_slow -- + * + * This handles the ALTQ case, which must take the KERNEL_LOCK. + */ +static int +ifq_put_slow(struct ifqueue * const ifq, struct mbuf *m) +{ + int error; + + /* + * We have to acquire the KERNEL_LOCK, so we need to + * drop the ifq_lock temporarily so that we can acquire + * them in the correct order. + * + * Once we've done that, we need to check everything again. + */ + mutex_exit(ifq->ifq_lock); + KERNEL_LOCK(1, NULL); + mutex_enter(ifq->ifq_lock); + + if (__predict_false(ALTQ_IS_ENABLED(ifq))) { + ALTQ_ENQUEUE(ifq, m, error); + } else { + if (__predict_false(IF_QFULL(ifq))) { + error = ENOBUFS; + } else { + IF_ENQUEUE(ifq, m); + error = 0; + } + } + KERNEL_UNLOCK_ONE(NULL); + + return error; +} +#endif /* ALTQ */ + +/* + * ifq_put -- + * + * Put a packet into the specified interface queue. + * If the queue is full, or any other error occurs, + * the packet is dropped. + */ +int +ifq_put(struct ifqueue * const ifq, struct mbuf *m) +{ + int error; + + KASSERT(m != NULL); + + mutex_enter(ifq->ifq_lock); + +#ifdef ALTQ + if (__predict_false(ALTQ_IS_ENABLED(ifq))) + error = ifq_put_slow(ifq, m); + else +#endif /* ALTQ */ + { + if (__predict_false(IF_QFULL(ifq))) { + error = ENOBUFS; + } else { + IF_ENQUEUE(ifq, m); + error = 0; + } + } + + if (__predict_false(error != 0)) { + ifq->ifq_drops++; + mutex_exit(ifq->ifq_lock); + m_freem(m); + } else { + mutex_exit(ifq->ifq_lock); + } + + return error; +} + +/* + * ifq_restage -- + * + * Re-stage a packet in the specified interface queue. + * This is used when a packet has been previously staged, + * but after some processing work, we need to postpone + * processing and replace the previous buffer (e.g. after + * allocating a new copy of the packet, for example). + */ +void +ifq_restage(struct ifqueue * const ifq, struct mbuf *m0, struct mbuf *m) +{ + KASSERT(m0 != NULL); + KASSERT(m != NULL); + + mutex_enter(ifq->ifq_lock); + + KASSERT(ifq->ifq_staged == m0); + if (m == ifq->ifq_staged) { + m0 = NULL; + } else { + m0 = ifq->ifq_staged; + ifq->ifq_staged = m; + } + + mutex_exit(ifq->ifq_lock); + + if (m0 != NULL) { + m_freem(m0); + } +} + +/* + * ifq_commit -- + * + * Commit the specified packet. An assertion is made that the + * specified packet is the packet currently in the staging area. + * The caller is repsonsible for disposing of it. + */ +void +ifq_commit(struct ifqueue * const ifq, struct mbuf * const m __diagused) +{ + mutex_enter(ifq->ifq_lock); + + KASSERT(ifq->ifq_staged != NULL); + KASSERT(ifq->ifq_staged == m); + ifq->ifq_staged = NULL; + + mutex_exit(ifq->ifq_lock); +} + +/* + * ifq_abort -- + * + * Abort the specified packet. An assertion is made that the + * specified packet is the packet currently in the staging area. + * The packet is freed. + */ +void +ifq_abort(struct ifqueue * const ifq, struct mbuf * const m __diagused) +{ + mutex_enter(ifq->ifq_lock); + + KASSERT(ifq->ifq_staged != NULL); + KASSERT(ifq->ifq_staged == m); + ifq->ifq_staged = NULL; + + mutex_exit(ifq->ifq_lock); + + m_freem(m); +} + +/* + * ifq_continue -- + * + * Check that processing of the queue should continue after + * being busy. Returns true if (*if_start)() should be scheduled. + */ +bool +ifq_continue(struct ifqueue * const ifq) +{ + mutex_enter(ifq->ifq_lock); + + ifq_get_nextstate(ifq); + + const bool do_start = (ifq->ifq_state == IFQ_STATE_READY) && ( +#ifdef ALTQ + ALTQ_IS_ENABLED(ifq) || +#endif + ifq->ifq_len != 0 || + ifq->ifq_staged != NULL); + + mutex_exit(ifq->ifq_lock); + + return do_start; +} + +#ifdef ALTQ +/* + * ifq_purge_slow -- + * + * Internal helper routine for ifq_purge(). This handles the ALTQ case, + * which must take the KERNEL_LOCK. + */ +static struct mbuf * +ifq_purge_slow(struct ifqueue * const ifq) +{ + struct mbuf *m; + + /* + * We have to acquire the KERNEL_LOCK, so we need to + * drop the ifq_lock temporarily so that we can acquire + * them in the correct order. + * + * Once we've done that, we need to check everything again. + */ + mutex_exit(ifq->ifq_lock); + KERNEL_LOCK(1, NULL); + mutex_enter(ifq->ifq_lock); + + if (__predict_false(ALTQ_IS_ENABLED(ifq))) { + ALTQ_PURGE(ifq); + m = NULL; + } else { + m = ifq->ifq_head; + ifq->ifq_head = ifq->ifq_tail = NULL; + ifq->ifq_len = 0; + } + KERNEL_UNLOCK_ONE(NULL); + + return m; +} +#endif /* ALTQ */ + +/* + * ifq_purge -- + * + * Purge all of the packets from the specified interface queue. + */ +void +ifq_purge(struct ifqueue * const ifq) +{ + struct mbuf *m, *nextm; + + mutex_enter(ifq->ifq_lock); + +#ifdef ALTQ + if (__predict_false(ALTQ_IS_ENABLED(ifq))) + m = ifq_purge_slow(ifq); + else +#endif /* ALTQ */ + { + m = ifq->ifq_head; + ifq->ifq_head = ifq->ifq_tail = NULL; + ifq->ifq_len = 0; + } + + if (ifq->ifq_staged != NULL) { + ifq->ifq_staged->m_nextpkt = m; + m = ifq->ifq_staged; + ifq->ifq_staged = NULL; + } + + mutex_exit(ifq->ifq_lock); + + for (; m != NULL; m = nextm) { + nextm = m->m_nextpkt; + m->m_nextpkt = NULL; + m_freem(m); + } +} + +/* + * ifq_classify_packet -- + * + * Decorate a packet with classificaiton information per the + * queue's queueing discipline. + * + * XXX This should be a private function. It's unfortunate that + * XXX it's called from where it's called. + */ +void +ifq_classify_packet(struct ifqueue * const ifq, struct mbuf * const m, + sa_family_t af) +{ + KASSERT(((m)->m_flags & M_PKTHDR) != 0); +#ifdef ALTQ + mutex_enter((ifq)->ifq_lock); + if (ALTQ_IS_ENABLED(ifq)) { + mutex_exit((ifq)->ifq_lock); + KERNEL_LOCK(1, NULL); + mutex_enter((ifq)->ifq_lock); + + if (ALTQ_IS_ENABLED(ifq)) { + if (ALTQ_NEEDS_CLASSIFY(ifq)) { + struct ifaltq * const altq = ifq->ifq_altq; + (m)->m_pkthdr.pattr_class = + (*altq->altq_classify) + (altq->altq_clfier, m, af); + } + m->m_pkthdr.pattr_af = af; + m->m_pkthdr.pattr_hdr = mtod(m, void *); + } + KERNEL_UNLOCK_ONE(NULL); + } + mutex_exit((ifq)->ifq_lock); +#endif /* ALTQ */ +} + +/* * Queue message on interface, and start output if interface * not yet active. + * + * XXX Should be renamed if_enqueue(). */ int ifq_enqueue(struct ifnet *ifp, struct mbuf *m) @@ -3831,33 +4425,75 @@ ifq_enqueue(struct ifnet *ifp, struct mb return if_transmit_lock(ifp, m); } +#ifdef ALTQ +static void +ifq_lock2(struct ifqueue * const ifq0, struct ifqueue * const ifq1) +{ + KASSERT(ifq0 != ifq1); + if (ifq0 < ifq1) { + mutex_enter(ifq0->ifq_lock); + mutex_enter(ifq1->ifq_lock); + } else { + mutex_enter(ifq1->ifq_lock); + mutex_enter(ifq0->ifq_lock); + } +} +#endif /* ALTQ */ + /* * Queue message on interface, possibly using a second fast queue + * + * N.B. Unlike ifq_enqueue(), this does *not* start transmission on + * the interface. + * + * XXX Should be renamed if_enqueue2(). */ int ifq_enqueue2(struct ifnet *ifp, struct ifqueue *ifq, struct mbuf *m) { + struct ifqueue * const ifq0 = &ifp->if_snd; int error = 0; - if (ifq != NULL + KASSERT(ifq == NULL || ifq->ifq_lock != NULL); + + if (__predict_true(ifq == NULL)) { + error = ifq_put(ifq0, m); + goto done; + } + #ifdef ALTQ - && ALTQ_IS_ENABLED(&ifp->if_snd) == 0 -#endif - ) { - if (IF_QFULL(ifq)) { - IF_DROP(&ifp->if_snd); - m_freem(m); - if (error == 0) - error = ENOBUFS; - } else + ifq_lock2(ifq0, ifq); + if (__predict_false(ALTQ_IS_ENABLED(ifq0))) { + /* + * ALTQ is enabled on the base send queue; use it for + * traffic shaping, not the "fast queue". + */ + mutex_exit(ifq->ifq_lock); + error = ifq_put_slow(ifq0, m); + mutex_exit(ifq0->ifq_lock); + } else { + /* Put the packet into the "fast queue". */ + mutex_exit(ifq0->ifq_lock); + if (__predict_false(IF_QFULL(ifq))) { + error = ENOBUFS; + } else { IF_ENQUEUE(ifq, m); - } else - IFQ_ENQUEUE(&ifp->if_snd, m, error); - if (error != 0) { + error = 0; + } + mutex_exit(ifq->ifq_lock); + } + if (__predict_false(error != 0)) { + m_freem(m); + } +#else + error = ifq_put(ifq, m); +#endif /* ALTQ */ + + done: + if (__predict_false(error != 0)) { if_statinc(ifp, if_oerrors); - return error; } - return 0; + return error; } int Index: src/sys/net/if.h diff -u src/sys/net/if.h:1.305.2.1 src/sys/net/if.h:1.305.2.1.2.1 --- src/sys/net/if.h:1.305.2.1 Sat Nov 11 13:16:30 2023 +++ src/sys/net/if.h Tue Nov 14 14:47:03 2023 @@ -1,7 +1,7 @@ -/* $NetBSD: if.h,v 1.305.2.1 2023/11/11 13:16:30 thorpej Exp $ */ +/* $NetBSD: if.h,v 1.305.2.1.2.1 2023/11/14 14:47:03 thorpej Exp $ */ /*- - * Copyright (c) 1999, 2000, 2001 The NetBSD Foundation, Inc. + * Copyright (c) 1999, 2000, 2001, 2023 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation @@ -234,13 +234,71 @@ struct if_status_description { } /* + * Interface transmit process states: + * + * ==> IFQ_STATE_INVALID Initial state, and used as a transitional + * measure until all drivers are converted to + * the new mechanism (opts queue out of the + * automatic state transitions). + * + * ==> IFQ_STATE_STOPPED Tx process is stopped. + * + * ==> IFQ_STATE_READY Tx process is ready to run the queue. + * + * ==> IFQ_STATE_BUSY Tx process is busy transmitting packets. + * + * ==> IFQ_STATE_WAIT_STOP Someone is waiting for the Tx process to stop. + * + * ==> IFQ_STATE_DEAD Terminal state before interface is detached. + * + * Transitions: + * + * IFQ_STATE_INVALID Initial state + * --> IFQ_STATE_READY Result of calling ifq_start(). + * --> IFQ_STATE_DEAD Result of calling ifq_fini(). + * + * IFQ_STATE_STOPPED Initial state + * --> IFQ_STATE_READY Result of calling ifq_start(). + * --> IFQ_STATE_DEAD Result of calling ifq_fini(). + * + * IFQ_STATE_READY + * --> IFQ_STATE_STOPPED Result of calling ifq_stop(). + * --> IFQ_STATE_BUSY (*if_start)() has been called and is + * processing packets. + * + * IFQ_STATE_BUSY + * --> IFQ_STATE_READY Tx process has finished processing the queue. + * --> IFQ_STATE_WAIT_STOP Result of calling ifq_stop(). + * + * IFQ_STATE_WAIT_STOP + * --> IFQ_STATE_STOPPED By way of noticing WAIT_STOP before + * transitioning from BUSY to READY. + * + * IFQ_STATE_DEAD Terminal state + */ +typedef enum { + IFQ_STATE_INVALID = -1, /* transitional */ + IFQ_STATE_STOPPED = 0, + IFQ_STATE_READY = 1, + IFQ_STATE_BUSY = 2, + IFQ_STATE_WAIT_STOP = 3, + IFQ_STATE_DEAD = 0xdeadbeef, +} ifq_state_t; + +#define IFQ_ASSERT_RUNNABLE(ifq) \ + KASSERT((ifq)->ifq_state == IFQ_STATE_BUSY || \ + (ifq)->ifq_state == IFQ_STATE_WAIT_STOP) + +/* * Structure defining a queue for a network interface. */ struct ifqueue { kmutex_t *ifq_lock; struct mbuf *ifq_head; struct mbuf *ifq_tail; + struct mbuf *ifq_staged; struct ifaltq *ifq_altq; + ifq_state_t ifq_state; int ifq_len; int ifq_maxlen; uint64_t ifq_drops; @@ -252,6 +310,36 @@ struct ifqueue { #include <sys/rwlock.h> #include <sys/workqueue.h> +static inline bool +_ifq_start_enter(struct ifqueue * const ifq) +{ + /* We return true if the caller should proceed with (*if_start)(). */ + bool rv = true; + + mutex_enter(ifq->ifq_lock); + switch (ifq->ifq_state) { + case IFQ_STATE_INVALID: /* transitional */ + break; + + case IFQ_STATE_STOPPED: + case IFQ_STATE_BUSY: + rv = false; + break; + + case IFQ_STATE_READY: + ifq->ifq_state = IFQ_STATE_BUSY; + break; + + default: + KASSERTMSG(ifq->ifq_state == IFQ_STATE_WAIT_STOP, + "invalid state=%d", (int)ifq->ifq_state); + rv = false; + break; + } + mutex_exit(ifq->ifq_lock); + + return rv; +} #endif /* _KERNEL */ /* @@ -547,6 +635,10 @@ static __inline void if_start_lock(struct ifnet *ifp) { + if (!_ifq_start_enter(&ifp->if_snd)) { + return; + } + if (if_is_mpsafe(ifp)) { (*ifp->if_start)(ifp); } else { @@ -1160,6 +1252,24 @@ void if_put(const struct ifnet *, struct void if_acquire(struct ifnet *, struct psref *); #define if_release if_put +void ifq_init(struct ifqueue *, unsigned int); +void ifq_fini(struct ifqueue *); +void ifq_start(struct ifqueue *); +void ifq_stop(struct ifqueue *); +void ifq_set_maxlen(struct ifqueue *, unsigned int); +unsigned int ifq_maxlen(struct ifqueue *); +uint64_t ifq_drops(struct ifqueue *); +struct mbuf * ifq_get(struct ifqueue *); +struct mbuf * ifq_stage(struct ifqueue *); +int ifq_put(struct ifqueue *, struct mbuf *); +void ifq_restage(struct ifqueue *, struct mbuf *, struct mbuf *); +void ifq_commit(struct ifqueue *, struct mbuf *); +void ifq_abort(struct ifqueue *, struct mbuf *); +bool ifq_continue(struct ifqueue *); +void ifq_purge(struct ifqueue *); +void ifq_classify_packet(struct ifqueue *, struct mbuf *, + sa_family_t); + int if_tunnel_check_nesting(struct ifnet *, struct mbuf *, int); percpu_t *if_tunnel_alloc_ro_percpu(void); void if_tunnel_free_ro_percpu(percpu_t *);