Module Name: src
Committed By: rmind
Date: Sun May 25 15:34:20 UTC 2014
Modified Files:
src/distrib/sets/lists/comp: mi
src/share/man/man9: Makefile
src/sys/kern: subr_ipi.c
src/sys/sys: cpu_data.h ipi.h
Added Files:
src/share/man/man9: ipi.9
Log Message:
MI IPI interface:
- Implement support for the asynchronous IPI calls.
- Rework synchronous IPI code to reuse the asynchronous mechanism.
- Add ipi(9) manual page; needs wizd(8).
Note: MD code can now provide a low level primitive for the ipi(9) and
reuse this interface instead of open-coding. Portmasters are encouraged
to convert. Ride 6.99.43!
To generate a diff of this commit:
cvs rdiff -u -r1.1892 -r1.1893 src/distrib/sets/lists/comp/mi
cvs rdiff -u -r1.377 -r1.378 src/share/man/man9/Makefile
cvs rdiff -u -r0 -r1.1 src/share/man/man9/ipi.9
cvs rdiff -u -r1.1 -r1.2 src/sys/kern/subr_ipi.c
cvs rdiff -u -r1.36 -r1.37 src/sys/sys/cpu_data.h
cvs rdiff -u -r1.1 -r1.2 src/sys/sys/ipi.h
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
Modified files:
Index: src/distrib/sets/lists/comp/mi
diff -u src/distrib/sets/lists/comp/mi:1.1892 src/distrib/sets/lists/comp/mi:1.1893
--- src/distrib/sets/lists/comp/mi:1.1892 Thu May 15 16:32:28 2014
+++ src/distrib/sets/lists/comp/mi Sun May 25 15:34:19 2014
@@ -1,4 +1,4 @@
-# $NetBSD: mi,v 1.1892 2014/05/15 16:32:28 apb Exp $
+# $NetBSD: mi,v 1.1893 2014/05/25 15:34:19 rmind Exp $
#
# Note: don't delete entries from here - mark them as "obsolete" instead.
#
@@ -10273,6 +10273,7 @@
./usr/share/man/cat9/ioctl.0 comp-sys-catman .cat
./usr/share/man/cat9/ioctl_copyin.0 comp-sys-catman .cat
./usr/share/man/cat9/ioctl_copyout.0 comp-sys-catman .cat
+./usr/share/man/cat9/ipi.0 comp-sys-catman .cat
./usr/share/man/cat9/ipkdb.0 comp-sys-catman .cat
./usr/share/man/cat9/ipkdb_connect.0 comp-sys-catman .cat
./usr/share/man/cat9/ipkdb_init.0 comp-sys-catman .cat
@@ -16970,6 +16971,7 @@
./usr/share/man/html9/ioctl.html comp-sys-htmlman html
./usr/share/man/html9/ioctl_copyin.html comp-sys-htmlman html
./usr/share/man/html9/ioctl_copyout.html comp-sys-htmlman html
+./usr/share/man/html9/ipi.html comp-sys-htmlman html
./usr/share/man/html9/ipkdb.html comp-sys-htmlman html
./usr/share/man/html9/ipkdb_connect.html comp-sys-htmlman html
./usr/share/man/html9/ipkdb_init.html comp-sys-htmlman html
@@ -23820,6 +23822,7 @@
./usr/share/man/man9/ioctl.9 comp-sys-man .man
./usr/share/man/man9/ioctl_copyin.9 comp-sys-man .man
./usr/share/man/man9/ioctl_copyout.9 comp-sys-man .man
+./usr/share/man/man9/ipi.9 comp-sys-man .man
./usr/share/man/man9/ipkdb.9 comp-sys-man .man
./usr/share/man/man9/ipkdb_connect.9 comp-sys-man .man
./usr/share/man/man9/ipkdb_init.9 comp-sys-man .man
Index: src/share/man/man9/Makefile
diff -u src/share/man/man9/Makefile:1.377 src/share/man/man9/Makefile:1.378
--- src/share/man/man9/Makefile:1.377 Mon Mar 24 13:42:40 2014
+++ src/share/man/man9/Makefile Sun May 25 15:34:20 2014
@@ -1,4 +1,4 @@
-# $NetBSD: Makefile,v 1.377 2014/03/24 13:42:40 hannken Exp $
+# $NetBSD: Makefile,v 1.378 2014/05/25 15:34:20 rmind Exp $
# Makefile for section 9 (kernel function and variable) manual pages.
@@ -25,7 +25,7 @@ MAN= accept_filter.9 accf_data.9 accf_ht
ieee80211_node.9 ieee80211_output.9 ieee80211_proto.9 \
ieee80211_radiotap.9 iic.9 imax.9 \
in_getifa.9 \
- in4_cksum.9 inittodr.9 intro.9 ioasic.9 ioctl.9 ipkdb.9 isa.9 \
+ in4_cksum.9 inittodr.9 intro.9 ioasic.9 ioctl.9 ipkdb.9 ipi.9 isa.9 \
isapnp.9 itimerfix.9 kauth.9 kcopy.9 kcpuset.9 kmem.9 \
kpause.9 \
kfilter_register.9 knote.9 \
Index: src/sys/kern/subr_ipi.c
diff -u src/sys/kern/subr_ipi.c:1.1 src/sys/kern/subr_ipi.c:1.2
--- src/sys/kern/subr_ipi.c:1.1 Mon May 19 22:47:54 2014
+++ src/sys/kern/subr_ipi.c Sun May 25 15:34:19 2014
@@ -1,4 +1,4 @@
-/* $NetBSD: subr_ipi.c,v 1.1 2014/05/19 22:47:54 rmind Exp $ */
+/* $NetBSD: subr_ipi.c,v 1.2 2014/05/25 15:34:19 rmind Exp $ */
/*-
* Copyright (c) 2014 The NetBSD Foundation, Inc.
@@ -30,11 +30,13 @@
*/
/*
- * Inter-processor interrupt (IPI) interface with cross-call support.
+ * Inter-processor interrupt (IPI) interface: asynchronous IPIs to
+ * invoke functions with a constant argument and synchronous IPIs
+ * with the cross-call support.
*/
#include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: subr_ipi.c,v 1.1 2014/05/19 22:47:54 rmind Exp $");
+__KERNEL_RCSID(0, "$NetBSD: subr_ipi.c,v 1.2 2014/05/25 15:34:19 rmind Exp $");
#include <sys/param.h>
#include <sys/types.h>
@@ -46,10 +48,25 @@ __KERNEL_RCSID(0, "$NetBSD: subr_ipi.c,v
#include <sys/kcpuset.h>
#include <sys/kmem.h>
#include <sys/lock.h>
+#include <sys/mutex.h>
+
+/*
+ * An array of the IPI handlers used for asynchronous invocation.
+ * The lock protects the slot allocation.
+ */
+
+typedef struct {
+ ipi_func_t func;
+ void * arg;
+} ipi_intr_t;
+
+static kmutex_t ipi_mngmt_lock;
+static ipi_intr_t ipi_intrs[IPI_MAXREG] __cacheline_aligned;
/*
* Per-CPU mailbox for IPI messages: it is a single cache line storing
- * up to IPI_MSG_MAX messages.
+ * up to IPI_MSG_MAX messages. This interface is built on top of the
+ * synchronous IPIs.
*/
#define IPI_MSG_SLOTS (CACHE_LINE_SIZE / sizeof(ipi_msg_t *))
@@ -59,8 +76,14 @@ typedef struct {
ipi_msg_t * msg[IPI_MSG_SLOTS];
} ipi_mbox_t;
+
+/* Mailboxes for the synchronous IPIs. */
static ipi_mbox_t * ipi_mboxes __read_mostly;
static struct evcnt ipi_mboxfull_ev __cacheline_aligned;
+static void ipi_msg_cpu_handler(void *);
+
+/* Handler for the synchronous IPIs - it must be zero. */
+#define IPI_SYNCH_ID 0
#ifndef MULTIPROCESSOR
#define cpu_ipi(ci) KASSERT(ci == NULL)
@@ -71,15 +94,100 @@ ipi_sysinit(void)
{
const size_t len = ncpu * sizeof(ipi_mbox_t);
+ /* Initialise the per-CPU bit fields. */
+ for (u_int i = 0; i < ncpu; i++) {
+ struct cpu_info *ci = cpu_lookup(i);
+ memset(&ci->ci_ipipend, 0, sizeof(ci->ci_ipipend));
+ }
+ mutex_init(&ipi_mngmt_lock, MUTEX_DEFAULT, IPL_NONE);
+ memset(ipi_intrs, 0, sizeof(ipi_intrs));
+
/* Allocate per-CPU IPI mailboxes. */
ipi_mboxes = kmem_zalloc(len, KM_SLEEP);
KASSERT(ipi_mboxes != NULL);
+ /*
+ * Register the handler for synchronous IPIs. This mechanism
+ * is built on top of the asynchronous interface. Slot zero is
+ * reserved permanently; it is also handy to use zero as a failure
+ * for other registers (as it is potentially less error-prone).
+ */
+ ipi_intrs[IPI_SYNCH_ID].func = ipi_msg_cpu_handler;
+
evcnt_attach_dynamic(&ipi_mboxfull_ev, EVCNT_TYPE_MISC, NULL,
"ipi", "full");
}
/*
+ * ipi_register: register an asynchronous IPI handler.
+ *
+ * => Returns IPI ID which is greater than zero; on failure - zero.
+ */
+u_int
+ipi_register(ipi_func_t func, void *arg)
+{
+ mutex_enter(&ipi_mngmt_lock);
+ for (u_int i = 0; i < IPI_MAXREG; i++) {
+ if (ipi_intrs[i].func == NULL) {
+ /* Register the function. */
+ ipi_intrs[i].func = func;
+ ipi_intrs[i].arg = arg;
+ mutex_exit(&ipi_mngmt_lock);
+
+ KASSERT(i != IPI_SYNCH_ID);
+ return i;
+ }
+ }
+ mutex_exit(&ipi_mngmt_lock);
+ printf("WARNING: ipi_register: table full, increase IPI_MAXREG\n");
+ return 0;
+}
+
+/*
+ * ipi_unregister: release the IPI handler given the ID.
+ */
+void
+ipi_unregister(u_int ipi_id)
+{
+ ipi_msg_t ipimsg = { .func = (ipi_func_t)nullop };
+
+ KASSERT(ipi_id != IPI_SYNCH_ID);
+ KASSERT(ipi_id < IPI_MAXREG);
+
+ /* Release the slot. */
+ mutex_enter(&ipi_mngmt_lock);
+ KASSERT(ipi_intrs[ipi_id].func != NULL);
+ ipi_intrs[ipi_id].func = NULL;
+
+ /* Ensure that there are no IPIs in flight. */
+ kpreempt_disable();
+ ipi_broadcast(&ipimsg);
+ ipi_wait(&ipimsg);
+ kpreempt_enable();
+ mutex_exit(&ipi_mngmt_lock);
+}
+
+/*
+ * ipi_trigger: asynchronously send an IPI to the specified CPU.
+ */
+void
+ipi_trigger(u_int ipi_id, struct cpu_info *ci)
+{
+ const u_int i = ipi_id >> IPI_BITW_SHIFT;
+ const uint32_t bitm = 1U << (ipi_id & IPI_BITW_MASK);
+
+ KASSERT(ipi_id < IPI_MAXREG);
+ KASSERT(kpreempt_disabled());
+ KASSERT(curcpu() != ci);
+
+ /* Mark as pending and send an IPI. */
+ if (membar_consumer(), (ci->ci_ipipend[i] & bitm) == 0) {
+ atomic_or_32(&ci->ci_ipipend[i], bitm);
+ cpu_ipi(ci);
+ }
+}
+
+/*
* put_msg: insert message into the mailbox.
*/
static inline void
@@ -106,11 +214,43 @@ again:
void
ipi_cpu_handler(void)
{
+ struct cpu_info * const ci = curcpu();
+
+ /*
+ * Handle asynchronous IPIs: inspect per-CPU bit field, extract
+ * IPI ID numbers and execute functions in those slots.
+ */
+ for (u_int i = 0; i < IPI_BITWORDS; i++) {
+ uint32_t pending, bit;
+
+ if (ci->ci_ipipend[i] == 0) {
+ continue;
+ }
+ pending = atomic_swap_32(&ci->ci_ipipend[i], 0);
+#ifndef __HAVE_ATOMIC_AS_MEMBAR
+ membar_producer();
+#endif
+ while ((bit = ffs(pending)) != 0) {
+ const u_int ipi_id = (i << IPI_BITW_SHIFT) | --bit;
+ ipi_intr_t *ipi_hdl = &ipi_intrs[ipi_id];
+
+ pending &= ~(1U << bit);
+ KASSERT(ipi_hdl->func != NULL);
+ ipi_hdl->func(ipi_hdl->arg);
+ }
+ }
+}
+
+/*
+ * ipi_msg_cpu_handler: handle synchronous IPIs - iterate mailbox,
+ * execute the passed functions and acknowledge the messages.
+ */
+static void
+ipi_msg_cpu_handler(void *arg __unused)
+{
const struct cpu_info * const ci = curcpu();
ipi_mbox_t *mbox = &ipi_mboxes[cpu_index(ci)];
- KASSERT(curcpu() == ci);
-
for (u_int i = 0; i < IPI_MSG_MAX; i++) {
ipi_msg_t *msg;
@@ -148,7 +288,7 @@ ipi_unicast(ipi_msg_t *msg, struct cpu_i
membar_producer();
put_msg(&ipi_mboxes[id], msg);
- cpu_ipi(ci);
+ ipi_trigger(IPI_SYNCH_ID, ci);
}
/*
@@ -182,7 +322,7 @@ ipi_multicast(ipi_msg_t *msg, const kcpu
continue;
}
put_msg(&ipi_mboxes[id], msg);
- cpu_ipi(ci);
+ ipi_trigger(IPI_SYNCH_ID, ci);
}
if (local) {
msg->func(msg->arg);
@@ -216,8 +356,8 @@ ipi_broadcast(ipi_msg_t *msg)
}
id = cpu_index(ci);
put_msg(&ipi_mboxes[id], msg);
+ ipi_trigger(IPI_SYNCH_ID, ci);
}
- cpu_ipi(NULL);
/* Finally, execute locally. */
msg->func(msg->arg);
Index: src/sys/sys/cpu_data.h
diff -u src/sys/sys/cpu_data.h:1.36 src/sys/sys/cpu_data.h:1.37
--- src/sys/sys/cpu_data.h:1.36 Mon Nov 25 03:02:30 2013
+++ src/sys/sys/cpu_data.h Sun May 25 15:34:19 2014
@@ -1,4 +1,4 @@
-/* $NetBSD: cpu_data.h,v 1.36 2013/11/25 03:02:30 christos Exp $ */
+/* $NetBSD: cpu_data.h,v 1.37 2014/05/25 15:34:19 rmind Exp $ */
/*-
* Copyright (c) 2004, 2006, 2007, 2008 The NetBSD Foundation, Inc.
@@ -43,6 +43,7 @@ struct lwp;
#include <sys/percpu_types.h>
#include <sys/queue.h>
#include <sys/kcpuset.h>
+#include <sys/ipi.h>
/*
* MI per-cpu data
@@ -71,6 +72,7 @@ struct cpu_data {
kcondvar_t cpu_xcall; /* cross-call support */
int cpu_xcall_pending; /* cross-call support */
lwp_t *cpu_onproc; /* bottom level LWP */
+ uint32_t cpu_ipipend[IPI_BITWORDS]; /* pending IPIs */
cpuid_t cpu_package_id;
cpuid_t cpu_core_id;
@@ -123,6 +125,7 @@ struct cpu_data {
#define ci_lkdebug_recurse ci_data.cpu_lkdebug_recurse
#define ci_pcu_curlwp ci_data.cpu_pcu_curlwp
#define ci_kcpuset ci_data.cpu_kcpuset
+#define ci_ipipend ci_data.cpu_ipipend
#define ci_package_id ci_data.cpu_package_id
#define ci_core_id ci_data.cpu_core_id
Index: src/sys/sys/ipi.h
diff -u src/sys/sys/ipi.h:1.1 src/sys/sys/ipi.h:1.2
--- src/sys/sys/ipi.h:1.1 Mon May 19 22:47:54 2014
+++ src/sys/sys/ipi.h Sun May 25 15:34:19 2014
@@ -1,4 +1,4 @@
-/* $NetBSD: ipi.h,v 1.1 2014/05/19 22:47:54 rmind Exp $ */
+/* $NetBSD: ipi.h,v 1.2 2014/05/25 15:34:19 rmind Exp $ */
/*-
* Copyright (c) 2014 The NetBSD Foundation, Inc.
@@ -47,13 +47,28 @@ typedef struct {
volatile u_int _pending;
} ipi_msg_t;
+/*
+ * Internal constants and implementation hooks.
+ *
+ * IPI_MAXREG: the maximum number of asynchronous handlers which can
+ * be registered on the system (normally, this should not be high).
+ */
+#define IPI_MAXREG 32
+
+#define IPI_BITW_SHIFT 5
+#define IPI_BITW_MASK (32 - 1)
+#define IPI_BITWORDS (IPI_MAXREG >> IPI_BITW_SHIFT)
+
void ipi_sysinit(void);
void ipi_cpu_handler(void);
void cpu_ipi(struct cpu_info *);
-/*
- * Public ipi(9) API.
- */
+/* Public interface: asynchronous IPIs. */
+u_int ipi_register(ipi_func_t, void *);
+void ipi_unregister(u_int);
+void ipi_trigger(u_int, struct cpu_info *);
+
+/* Public interface: synchronous IPIs. */
void ipi_unicast(ipi_msg_t *, struct cpu_info *);
void ipi_multicast(ipi_msg_t *, const kcpuset_t *);
void ipi_broadcast(ipi_msg_t *);
Added files:
Index: src/share/man/man9/ipi.9
diff -u /dev/null src/share/man/man9/ipi.9:1.1
--- /dev/null Sun May 25 15:34:20 2014
+++ src/share/man/man9/ipi.9 Sun May 25 15:34:20 2014
@@ -0,0 +1,177 @@
+.\" $NetBSD: ipi.9,v 1.1 2014/05/25 15:34:20 rmind Exp $
+.\"
+.\" Copyright (c) 2014 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Mindaugas Rasiukevicius.
+.\"
+.\" 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 THE NETBSD FOUNDATION, INC. 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 THE FOUNDATION 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 May 25, 2014
+.Dt IPI 9
+.Os
+.Sh NAME
+.Nm ipi
+.Nd MI IPI interface
+.Sh SYNOPSIS
+.In sys/ipi.h
+.Vt typedef void (*ipi_func_t)(void *);
+.\" -----
+.Ft u_int
+.Fn ipi_register "ipi_func_t func" "void *arg"
+.Ft void
+.Fn ipi_unregister "u_int ipi_id"
+.Ft void
+.Fn ipi_trigger "u_int ipi_id" "struct cpu_info *ci"
+.\" -----
+.Ft void
+.Fn ipi_unicast "ipi_msg_t *msg" "struct cpu_info *ci"
+.Ft void
+.Fn ipi_multicast "ipi_msg_t *msg" "const kcpuset_t *target"
+.Ft void
+.Fn ipi_broadcast "ipi_msg_t *msg"
+.Ft void
+.Fn ipi_wait "ipi_msg_t *msg"
+.\" -----
+.Sh DESCRIPTION
+The machine-independent
+.Nm
+interface provides capability to send inter-processor interrupts (IPIs)
+amongst CPUs.
+The interface has two mechanisms: asynchronous IPI to invoke functions
+with a constant argument and synchronous IPIs with the cross-call support.
+.Pp
+Other synchronization interfaces are built using the MI IPI interface.
+For a general purpose inter-processor cross-calls or remote interrupts, use
+.Xr xcall 9
+or
+.Xr softint 9
+interfaces.
+.Pp
+The primary use cases of the MI IPIs include the following:
+.Bl -hyphen -compact
+.It
+provide a facility for
+.Xr softint 9
+subsystem to schedule software interrupts on the remote CPUs
+.It
+provide a facility for
+.Xr xcall 9
+subsystem
+.It
+abstract IPI handling and facilitate the machine-dependent code
+.El
+.Pp
+.\" -----
+.Ss Asynchronous IPI interface
+This interface allows dynamic registration of IPI handlers with a constant
+argument and asynchronous triggering of the interrupts.
+.Bl -tag -width compact
+.It Fn ipi_register "func" "arg"
+Register an IPI handler
+.Fa func
+with an arbitrary argument
+.Fa arg .
+Returns non-zero IPI identifier on success and zero on failure.
+.It Fn ipi_unregister "ipi_id"
+Unregister the IPI handler identified by the
+.Fa ipi_id .
+.It Fn ipi_trigger "ipi_id" "ci"
+Trigger an IPI identified by
+.Fa ipi_id
+on a remote CPU specified by
+.Fa ci .
+This function must be called with the kernel preemption disabled and
+the target CPU must be remote.
+.El
+.Pp
+.\" -----
+.Ss Synchronous IPI interface
+This interface provides capability to perform cross-calls, i.e. invoke
+an arbitrary function on a remote CPU.
+The invocations are performed synchronously and the caller must wait
+for completion.
+The cross-call is described by an IPI "message".
+The caller has to fill
+.Vt ipi_msg_t
+structure which has the following public members:
+.Bd -literal
+ ipi_func_t func;
+ void arg;
+.Ed
+.Pp
+The
+.Ar func
+member specifies a function to invoke and
+.Ar arg
+is the argument to be passed to the function.
+.Pp
+.Bl -tag -width compact
+.It Fn ipi_unicast "msg" "ci"
+Send an IPI to a remote CPU specified by
+.Fa ci .
+.It Fn ipi_multicast "msg" "target"
+Send IPIs to a CPU set specified by the
+.Fa target .
+.It Fn ipi_broadcast "msg"
+Send IPIs to all CPUs.
+.It Fn ipi_wait "msg"
+Wait until all IPIs complete.
+.El
+.Pp
+All described functions, except
+.Fn ipi_wait ,
+must be called with the kernel preemption disabled.
+All synchronous IPI invocations must be awaited for completion with
+.Fn ipi_wait
+function, before IPI message structure can be destroyed or new
+cross-call requests performed.
+.\" -----
+.Sh NOTES
+Functions being called must be lightweight.
+They run at
+.Em IPL_HIGH
+and should generally not use any other synchronization interfaces,
+such as
+.Xr mutex 9 .
+If spin-locks are used, they must be used carefully and have no contention.
+.\" -----
+.Sh CODE REFERENCES
+The
+.Nm
+interface is implemented within the file
+.Pa sys/kern/subr_ipi.c .
+.\" -----
+.Sh SEE ALSO
+.Xr softint 9 ,
+.Xr spl 9 ,
+.Xr kcpuset 9 ,
+.Xr kpreempt 9 ,
+.Xr xcall 9
+.Sh HISTORY
+The
+.Nm
+interface first appeared in
+.Nx 7.0 .
+.Sh AUTHORS
+.An Mindaugas Rasiukevicius Aq Mt [email protected]