From 6d6ebe430cf1f223e329b902649544446126e197 Mon Sep 17 00:00:00 2001
From: itojun <itojun@NetBSD.org>
Date: Thu, 12 Jul 2001 12:35:16 +0000
Subject: [PATCH] add altq(9).

---
 distrib/sets/lists/comp/mi |   4 +-
 share/man/man9/Makefile    |   4 +-
 share/man/man9/altq.9      | 575 +++++++++++++++++++++++++++++++++++++
 3 files changed, 580 insertions(+), 3 deletions(-)
 create mode 100644 share/man/man9/altq.9

diff --git a/distrib/sets/lists/comp/mi b/distrib/sets/lists/comp/mi
index 16a365a9dbd2..1fd541596d65 100644
--- a/distrib/sets/lists/comp/mi
+++ b/distrib/sets/lists/comp/mi
@@ -1,4 +1,4 @@
-#	$NetBSD: mi,v 1.355 2001/07/07 15:57:15 perry Exp $
+#	$NetBSD: mi,v 1.356 2001/07/12 12:35:17 itojun Exp $
 ./sys				comp-sysutil-root
 ./usr/bin/addr2line		comp-debug-bin
 ./usr/bin/ar			comp-util-bin
@@ -2910,6 +2910,7 @@
 ./usr/share/man/cat9/PCI_REVISION.0		comp-sys-catman
 ./usr/share/man/cat9/PCI_VENDOR.0		comp-sys-catman
 ./usr/share/man/cat9/access.0		comp-sys-catman
+./usr/share/man/cat9/altq.0		comp-sys-catman
 ./usr/share/man/cat9/arp.0		comp-sys-catman
 ./usr/share/man/cat9/arp_ifinit.0		comp-sys-catman
 ./usr/share/man/cat9/arpintr.0		comp-sys-catman
@@ -5021,6 +5022,7 @@
 ./usr/share/man/man9/PCI_REVISION.9		comp-sys-man
 ./usr/share/man/man9/PCI_VENDOR.9		comp-sys-man
 ./usr/share/man/man9/access.9		comp-sys-man
+./usr/share/man/man9/altq.9		comp-sys-man
 ./usr/share/man/man9/arp.9		comp-sys-man
 ./usr/share/man/man9/arp_ifinit.9		comp-sys-man
 ./usr/share/man/man9/arpintr.9		comp-sys-man
diff --git a/share/man/man9/Makefile b/share/man/man9/Makefile
index 07b906f0f451..dec83dbcb0d1 100644
--- a/share/man/man9/Makefile
+++ b/share/man/man9/Makefile
@@ -1,8 +1,8 @@
-#       $NetBSD: Makefile,v 1.86 2001/07/07 05:08:19 perry Exp $
+#       $NetBSD: Makefile,v 1.87 2001/07/12 12:35:16 itojun Exp $
 
 #	Makefile for section 9 (kernel function and variable) manual pages.
 
-MAN=	access.9 arp.9 audio.9 autoconf.9 bitmask_snprintf.9 \
+MAN=	access.9 altq.9 arp.9 audio.9 autoconf.9 bitmask_snprintf.9 \
 	bcmp.9 bcopy.9 bzero.9 \
 	bus_dma.9 bus_space.9 \
 	callout.9 cardbus.9 cnmagic.9 config.9 cons.9 copy.9 cpu_reboot.9 \
diff --git a/share/man/man9/altq.9 b/share/man/man9/altq.9
new file mode 100644
index 000000000000..34503b7213b1
--- /dev/null
+++ b/share/man/man9/altq.9
@@ -0,0 +1,575 @@
+.\"	$NetBSD: altq.9,v 1.1 2001/07/12 12:35:16 itojun Exp $
+.\"	$OpenBSD: altq.9,v 1.2 2001/07/12 12:33:28 itojun Exp $
+.\"
+.\" Copyright (C) 2001
+.\" Sony Computer Science Laboratories Inc.  All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY SONY CSL AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED.  IN NO EVENT SHALL SONY CSL OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd July 10, 2001
+.Dt ALTQ 9
+.Os
+.\"
+.Sh NAME
+.Nm ALTQ
+.Nd kernel interfaces for manipulating output queues on network interfaces
+.Sh SYNOPSIS
+.Fd #include <sys/types.h>
+.Fd #include <sys/socket.h>
+.Fd #include <net/if.h>
+.Ft void \"macro
+.Fn IFQ_ENQUEUE "struct ifaltq *ifq" "struct mbuf *m" "int error"
+.Ft void \"macro
+.Fn IFQ_DEQUEUE "struct ifaltq *ifq" "struct mbuf *m"
+.Ft void \"macro
+.Fn IFQ_POLL "struct ifaltq *ifq" "struct mbuf *m"
+.Ft void \"macro
+.Fn IFQ_PURGE "struct ifaltq *ifq"
+.Ft void \"macro
+.Fn IFQ_CLASSIFY "struct ifaltq *ifq" "struct mbuf *m" "int af" "struct altq_pktattr *pktattr"
+.Ft void \"macro
+.Fn IFQ_IS_EMPTY "struct ifaltq *ifq"
+.Ft void \"macro
+.Fn IFQ_SET_MAXLEN "struct ifaltq *ifq" "int len"
+.Ft void \"macro
+.Fn IFQ_INC_LEN "struct ifaltq *ifq"
+.Ft void \"macro
+.Fn IFQ_DEC_LEN "struct ifaltq *ifq"
+.Ft void \"macro
+.Fn IFQ_INC_DROPS "struct ifaltq *ifq"
+.Ft void \"macro
+.Fn IFQ_SET_READY "struct ifaltq *ifq"
+.Sh DESCRIPTION
+The ALTQ system is a framework to manage queueing disciplines on network
+interfaces.  ALTQ introduces new macros to manipulate output queues.
+The output queue macros are used to abstract queue operations and not to
+touch the internal fields of the output queue structure.
+The macros are independent from the ALTQ implementation, and compatible with the
+traditional
+.Dv ifqueue
+macros for ease of transition.
+.Pp
+.Fn IFQ_ENQUEUE
+enqueues a packet
+.Fa m ,
+to the queue 
+.Fa ifq .
+The underlying queueing discipline may discard the packet.
+0 is set to
+.Fa error
+on success and 
+.Dv ENOBUFS
+is set if the packet is discarded.
+.Fa m
+will be freed by the device driver on success or by the queueing discipline on
+failure so that the caller should not touch
+.Fa m
+after calling
+.Fn IFQ_ENQUEUE .
+.Pp
+.Fn IFQ_DEQUEUE
+dequeues a packet from the queue.  The dequeued packet is set to
+.Fa m ,
+or
+.Dv NULL
+is set if no packet is dequeued.
+The caller must always check
+.Fa m
+since a non-empty queue could return
+.Dv NULL
+under rate-limiting.
+.Pp
+.Fn IFQ_POLL
+returns the next packet without removing it from the queue.
+It is guaranteed by the underlying queueing discipline that
+.Fn IFQ_DEQUEUE
+immediately after
+.Fn IFQ_POLL
+returns the same packet.
+.Pp
+.Fn IFQ_PURGE
+discards all the packets in the queue.
+The purge operation is needed since a non-work conserving queue cannot be
+emptied by a dequeue loop.
+.Pp
+.Fn IFQ_CLASSIFY
+classifies a packet to a scheduling class, and set the result to
+.Fa pktattr .
+.Pp
+.Fn IFQ_IS_EMPTY
+can be used to check if the queue is empty.
+Note that
+.Fn IFQ_DEQUEUE
+could still return
+.Dv NULL
+if the queueing discipline is non-work conserving.
+.Pp
+.Fn IFQ_SET_MAXLEN
+sets the queue length limit to the default FIFO queue.
+.Pp
+.Fn IFQ_INC_LEN
+and
+.Fn IFQ_DEC_LEN
+increment or decrement the current queue length in packets.
+.Pp
+.Fn IFQ_INC_DROPS
+increments the drop counter and is equal to
+.Fn IF_DROP .
+It is defined for naming consistency.
+.Pp
+.Fn IFQ_SET_READY
+sets a flag to indicate this driver is converted to use the new macros.
+ALTQ can be enabled only on interfaces with this flag.
+.Sh COMPATIBILITY
+.Ss ifaltq structure
+In order to keep compatibility with the existing code, the new
+output queue structure
+.Dv ifaltq
+has the same fields.  The traditional
+.Fn IF_XXX
+macros and the code directly referencing to the fields within
+.Dv if_snd
+still work with
+.Dv ifaltq .
+(Once we finish conversions of all the drivers, we no longer need
+these fields.)
+.Bd -literal
+            ##old-style##                           ##new-style##
+                                       |
+ struct ifqueue {                      | struct ifaltq {
+    struct mbuf *ifq_head;             |    struct mbuf *ifq_head;
+    struct mbuf *ifq_tail;             |    struct mbuf *ifq_tail;
+    int          ifq_len;              |    int          ifq_len;
+    int          ifq_maxlen;           |    int          ifq_maxlen;
+    int          ifq_drops;            |    int          ifq_drops;
+ };                                    |    /* altq related fields */
+                                       |    ......
+                                       | };
+                                       |
+.Ed
+The new structure replaces
+.Dv struct ifqueue
+in
+.Dv struct ifnet .
+.Bd -literal
+            ##old-style##                           ##new-style##
+                                       |
+ struct ifnet {                        | struct ifnet {
+     ....                              |     ....
+                                       |
+     struct ifqueue if_snd;            |     struct ifaltq if_snd;
+                                       |
+     ....                              |     ....
+ };                                    | };
+                                       |
+.Ed
+The (simplified) new
+.Fn IFQ_XXX
+macros looks like:
+.Bd -literal
+	#ifdef ALTQ
+	#define IFQ_DEQUEUE(ifq, m)			\e
+		if (ALTQ_IS_ENABLED((ifq))		\e
+			ALTQ_DEQUEUE((ifq), (m));	\e
+		else					\e
+			IF_DEQUEUE((ifq), (m));
+	#else
+	#define IFQ_DEQUEUE(ifq, m)	IF_DEQUEUE((ifq), (m));
+	#endif
+.Ed
+.Ss Enqueue operation
+The semantics of the enqueue operation is changed.  In the new style,
+the enqueue and packet drop are combined since they cannot be easily
+separated in many queueing disciplines.
+The new enqueue operation corresponds to the following macro that is
+written with the old macros.
+.Bd -literal
+#define	IFQ_ENQUEUE(ifq, m, error)                      \e
+do {                                                    \e
+        if (IF_QFULL((ifq))) {                          \e
+                m_freem((m));                           \e
+                (error) = ENOBUFS;                      \e
+                IF_DROP(ifq);                           \e
+         } else {                                       \e
+                IF_ENQUEUE((ifq), (m));                 \e
+                (error) = 0;                            \e
+         }                                              \e
+} while (0)
+.Ed
+.Pp
+.Fn IFQ_ENQUEUE
+does the followings:
+.Bl -hyphen -compact
+.It
+queue a packet
+.It
+drop (and free) a packet if the enqueue operation fails
+.El
+If the enqueue operation fails, an error
+.Dv ENOBUFS
+is set to
+.Dv error .
+.Dv mbuf
+is freed by the queueing discipline.
+The caller should not touch mbuf after calling
+.Fn IFQ_ENQUEUE
+so that the caller may need to copy
+.Fa m_pkthdr.len
+or
+.Fa m_flags
+field beforehand for statistics.
+The caller should not use
+.Fn senderr
+since mbuf was already freed.
+.Pp
+The new style
+.Fn if_output
+looks as follows:
+.Bd -literal
+            ##old-style##                           ##new-style##
+                                       |
+ int                                   | int 
+ ether_output(ifp, m0, dst, rt0)       | ether_output(ifp, m0, dst, rt0)
+ {                                     | {
+     ......                            |     ......
+                                       |
+                                       |     mflags = m->m_flags;
+                                       |     len = m->m_pkthdr.len;
+     s = splimp();                     |     s = splimp();
+     if (IF_QFULL(&ifp->if_snd)) {     |     IFQ_ENQUEUE(&ifp->if_snd, m,
+                                       |                 error);
+         IF_DROP(&ifp->if_snd);        |     if (error != 0) {
+         splx(s);                      |         splx(s);
+         senderr(ENOBUFS);             |         retuen (error);
+     }                                 |     }
+     IF_ENQUEUE(&ifp->if_snd, m);      |
+     ifp->if_obytes +=                 |     ifp->if_obytes += len;
+                    m->m_pkthdr.len;   |
+     if (m->m_flags & M_MCAST)         |     if (mflags & M_MCAST)
+         ifp->if_omcasts++;            |         ifp->if_omcasts++;
+                                       |
+     if ((ifp->if_flags & IFF_OACTIVE) |     if ((ifp->if_flags & IFF_OACTIVE)
+         == 0)                         |         == 0)
+         (*ifp->if_start)(ifp);        |         (*ifp->if_start)(ifp);
+     splx(s);                          |     splx(s);
+     return (error);                   |     return (error);
+                                       |
+ bad:                                  | bad:
+     if (m)                            |     if (m)
+         m_freem(m);                   |         m_freem(m);
+     return (error);                   |     return (error);
+ }                                     | }
+                                       |
+.Ed
+.Ss Classifier
+The classifier mechanism is currently implemented in
+.Fn if_output .
+.Dv struct altq_pktattr
+is used to store the classifier result, and it is passed to the enqueue
+function.
+(We will change the method to tag the classifier result to mbuf in the future.)
+.Bd -literal
+int
+ether_output(ifp, m0, dst, rt0)
+{
+	......
+	struct altq_pktattr pktattr;
+
+	......
+
+	/* classify the packet before prepending link-headers */
+	IFQ_CLASSIFY(&ifp->if_snd, m, dst->sa_family, &pktattr);
+
+	/* prepend link-level headers */
+	......
+
+	IFQ_ENQUEUE(&ifp->if_snd, m, &pktattr, error);
+
+	......
+}
+.Ed
+.Sh HOW TO CONVERT THE EXISTING DRIVERS
+First, make sure the corresponding
+.Fn if_output
+is already converted to the new style.
+.Pp
+Look for
+.Fa if_snd
+in the driver.  Probably, you need to make changes to the lines that include
+.Fa if_snd .
+.Ss Empty check operation
+If the code checks
+.Fa ifq_head
+to see whether the queue is empty or not, use
+.Fn IFQ_IS_EMPTY .
+.Bd -literal
+            ##old-style##                           ##new-style##
+                                       |
+ if (ifp->if_snd.ifq_head != NULL)     | if (IFQ_IS_EMPTY(&ifp->if_snd) == 0)
+                                       |
+.Ed
+Note that
+.Fn IFQ_POLL
+can be used for the same purpose, but
+.Fn IFQ_POLL
+could be costly for a complex scheduling algorithm since
+.Fn IFQ_POLL
+needs to run the scheduling algorithm to select the next packet.
+On the other hand,
+.Fn IFQ_EMPTY
+checks only if there is any packet stored in the queue.
+Another difference is that even when
+.Fn IFQ_EMPTY
+is
+.Dv FALSE ,
+.Fn IFQ_DEQUEUE
+could still return
+.Dv NULL
+if the queue is under rate-limiting.
+.Ss Dequeue operaion
+Replace
+.Fn IF_DEQUEUE
+by
+.Fn IFQ_DEQUEUE .
+Always check whether the dequeued mbuf is
+.Dv NULL
+or not.
+Note that even when
+.Fn IFQ_IS_EMPTY
+is
+.Dv FALSE ,
+.Fn IFQ_DEQUEUE
+could return
+.Dv NULL
+due to rate-limiting.
+.Bd -literal
+            ##old-style##                           ##new-style##
+                                       |
+ IF_DEQUEUE(&ifp->if_snd, m);          | IFQ_DEQUEUE(&ifp->if_snd, m);
+                                       | if (m == NULL)
+                                       |     return;
+                                       |
+.Ed
+A driver is supposed to call
+.Fn if_start
+from transmission complete interrupts in order to trigger the next dequeue.
+.Ss Poll-and-dequeue operation
+If the code polls the packet at the head of the queue and actually uses
+the packet before dequeueing it, use
+.Fn IFQ_POLL
+and
+IFQ_DEQUEUE .
+.Bd -literal
+            ##old-style##                           ##new-style##
+                                       |
+ m = ifp->if_snd.ifq_head;             | IFQ_POLL(&ifp->if_snd, m);
+ if (m != NULL) {                      | if (m != NULL) {
+                                       |
+     /* use m to get resources */      |     /* use m to get resources */
+     if (something goes wrong)         |     if (something goes wrong)
+         return;                       |         return;
+                                       |
+     IF_DEQUEUE(&ifp->if_snd, m);      |     IFQ_DEQUEUE(&ifp->if_snd, m);
+                                       |
+     /* kick the hardware */           |     /* kick the hardware */
+ }                                     | }
+                                       |
+.Ed
+It is guaranteed that
+.Fn IFQ_DEQUEUE
+immediately after
+.Fn IFQ_POLL
+returns the same packet.
+.Po
+They need to be guarded by 
+.Fn splimp
+if called from outside of
+.Fn if_start
+.Pc .
+.Ss Eliminating IF_PREPEND
+If the code uses
+.Fn IF_PREPEND ,
+you have to eliminate it since the prepend operation is not possible for many
+queueing disciplines.  A common use of
+.Fn IF_PREPEND
+is to cancel the previous dequeue operation.
+You have to convert the logic into poll-and-dequeue.
+.Bd -literal
+            ##old-style##                           ##new-style##
+                                       |
+ IF_DEQUEUE(&ifp->if_snd, m);          | IFQ_POLL(&ifp->if_snd, m);
+ if (m != NULL) {                      | if (m != NULL) {
+                                       |
+     if (something_goes_wrong) {       |     if (something_goes_wrong) {
+         IF_PREPEND(&ifp->if_snd, m);  |
+         return;                       |         return;
+     }                                 |     }
+                                       |
+                                       |     /* at this point, the driver
+                                       |      * is committed to send this
+                                       |      * packet.
+                                       |      */
+                                       |     IFQ_DEQUEUE(&ifp->if_snd, m);
+                                       |
+     /* kick the hardware */           |     /* kick the hardware */
+ }                                     | }
+                                       |
+.Ed
+.Ss Purge operation
+Use
+.Fn IFQ_PURGE
+to empty the queue.
+Note that a non-work conserving queue cannot be emptied by a dequeue loop.
+.Bd -literal
+            ##old-style##                           ##new-style##
+                                       |
+ while (ifp->if_snd.ifq_head != NULL) {|  IFQ_PURGE(&ifp->if_snd);
+     IF_DEQUEUE(&ifp->if_snd, m);      |
+     m_freem(m);                       |
+ }                                     |
+                                       |
+.Ed
+.Ss Attach routine
+Use
+.Fn IFQ_SET_MAXLEN
+to set a value to
+.Fa ifq_maxlen .
+Add
+.Fn IFQ_SET_READY
+to show this driver is converted to the new style.
+(this is used to distinguish new-style drivers.)
+.Bd -literal
+            ##old-style##                           ##new-style##
+                                       |
+ ifp->if_snd.ifq_maxlen = qsize;       | IFQ_SET_MAXLEN(&ifp->if_snd, qsize);
+                                       | IFQ_SET_READY(&ifp->if_snd);
+ if_attach(ifp);                       | if_attach(ifp);
+                                       |
+.Ed
+.Ss Other issues
+The new macros for statistics:
+.Bd -literal
+            ##old-style##                           ##new-style##
+                                       |
+ IF_DROP(&ifp->if_snd);                | IFQ_INC_DROPS(&ifp->if_snd);
+                                       |
+ ifp->if_snd.ifq_len++;                | IFQ_INC_LEN(&ifp->if_snd);
+                                       |
+ ifp->if_snd.ifq_len--;                | IFQ_INC_LEN(&ifp->if_snd);
+                                       |
+.Ed
+Some drivers instruct the hardware to invoke transmission complete
+interruts only when it thinks necessary.  Rate-limiting breaks its assumption.
+.Ss How to convert drivers using multiple ifqueues
+Some (pseudo) devices (such as slip) have another
+.Dv ifqueue
+to prioritize packets.  It is possible to eliminate the second queue
+since ALTQ provides more flexible mechanisms but the following shows
+how to keep the original behavior.
+.Bd -literal
+struct sl_softc {
+	struct	ifnet sc_if;		/* network-visible interface */
+	...
+	struct	ifqueue sc_fastq;	/* interactive output queue */
+	...
+};
+.Ed
+The driver doesn't compile in the new model since it has the following
+line
+.Po
+.Fa if_snd
+is no longer a type of
+.Dv struct ifqueue
+.Pc .
+.Bd -literal
+	struct ifqueue *ifq = &ifp->if_snd;
+.Ed
+A simple way is to use the original
+.Fn IF_XXX
+macros for
+.Fa sc_fastq
+and use the new
+.Fn IFQ_XXX
+macros for
+.Fa if_snd .
+The enqueue operation looks like:
+.Bd -literal
+            ##old-style##                           ##new-style##
+                                       |
+ struct ifqueue *ifq = &ifp->if_snd;   | struct ifqueue *ifq = NULL;
+                                       |
+ if (ip->ip_tos & IPTOS_LOWDELAY)      | if ((ip->ip_tos & IPTOS_LOWDELAY) &&
+     ifq = &sc->sc_fastq;              | !ALTQ_IS_ENABLED(&sc->sc_if.if_snd)) {
+                                       |     ifq = &sc->sc_fastq;
+ if (IF_QFULL(ifq)) {                  |     if (IF_QFULL(ifq)) {
+     IF_DROP(ifq);                     |         IF_DROP(ifq);
+     m_freem(m);                       |         m_freem(m);
+     splx(s);                          |         error = ENOBUFS;
+     sc->sc_if.if_oerrors++;           |     } else {
+     return (ENOBUFS);                 |         IF_ENQUEUE(ifq, m);
+ }                                     |         error = 0;
+ IF_ENQUEUE(ifq, m);                   |     }
+                                       | } else
+                                       |     IFQ_ENQUEUE(&sc->sc_if.if_snd, 
+                                       |                 m, error);
+                                       |
+                                       | if (error) {
+                                       |     splx(s);
+                                       |     sc->sc_if.if_oerrors++;
+                                       |     return (error);
+                                       | }
+ if ((sc->sc_oqlen =                   | if ((sc->sc_oqlen =
+      sc->sc_ttyp->t_outq.c_cc) == 0)  |      sc->sc_ttyp->t_outq.c_cc) == 0)
+     slstart(sc->sc_ttyp);             |     slstart(sc->sc_ttyp);
+ splx(s);                              | splx(s);
+                                       |
+.Ed
+The dequeue operations looks like:
+.Bd -literal
+            ##old-style##                           ##new-style##
+                                       |
+ s = splimp();                         | s = splimp();
+ IF_DEQUEUE(&sc->sc_fastq, m);         | IF_DEQUEUE(&sc->sc_fastq, m);
+ if (m == NULL)                        | if (m == NULL)
+     IF_DEQUEUE(&sc->sc_if.if_snd, m); |     IFQ_DEQUEUE(&sc->sc_if.if_snd, m);
+ splx(s);                              | splx(s);
+                                       |
+.Ed
+.Sh QUEUEING DISCIPLINES
+Queueing disciplines need to maintain
+.Fa ifq_len .
+.Po
+used by
+.Fn IFQ_IS_EMPTY
+.Pc .
+Queueing disciplines also need to guarantee the same mbuf is returned if
+.Fn IFQ_DEQUEUE
+is called immediately after
+.Fn IFQ_POLL .
+.Sh SEE ALSO
+.Xr altq.conf 5 ,
+.Xr altqd 8 ,
+.Xr tbrconfig 8
+.Sh HISTORY
+The
+.Nm
+system first appeared in March 1997.