#include <sys/cdefs.h>
__FBSDID("$FreeBSD: head/sys/dev/netmap/netmap.c 257176 2013-10-26 17:58:36Z glebius $");
#include <sys/types.h>
#include <sys/errno.h>
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/conf.h>
#include <sys/devfs.h>
#include <sys/sockio.h>
#include <sys/socketvar.h>
#include <sys/malloc.h>
#include <sys/poll.h>
#include <sys/lock.h>
#include <sys/socket.h>
#include <sys/event.h>
#include <sys/sysctl.h>
#include <net/if.h>
#include <net/if_var.h>
#include <net/bpf.h>
#include <sys/bus.h>
#include <sys/endian.h>
#include <sys/refcount.h>
#define init_waitqueue_head(x)
extern struct dev_ops netmap_cdevsw;
#include <net/netmap/netmap.h>
#include <net/netmap/netmap_kern.h>
#include <net/netmap/netmap_mem2.h>
MALLOC_DEFINE(M_NETMAP, "netmap", "Network memory map");
u_int netmap_total_buffers;
u_int netmap_buf_size;
char *netmap_buffer_base;
int netmap_verbose;
static int netmap_no_timestamp;
SYSCTL_NODE(_dev, OID_AUTO, netmap, CTLFLAG_RW, 0, "Netmap args");
SYSCTL_INT(_dev_netmap, OID_AUTO, verbose,
CTLFLAG_RW, &netmap_verbose, 0, "Verbose mode");
SYSCTL_INT(_dev_netmap, OID_AUTO, no_timestamp,
CTLFLAG_RW, &netmap_no_timestamp, 0, "no_timestamp");
int netmap_mitigate = 1;
SYSCTL_INT(_dev_netmap, OID_AUTO, mitigate, CTLFLAG_RW, &netmap_mitigate, 0, "");
int netmap_no_pendintr = 1;
SYSCTL_INT(_dev_netmap, OID_AUTO, no_pendintr,
CTLFLAG_RW, &netmap_no_pendintr, 0, "Always look for new received packets.");
int netmap_txsync_retry = 2;
SYSCTL_INT(_dev_netmap, OID_AUTO, txsync_retry, CTLFLAG_RW,
&netmap_txsync_retry, 0 , "Number of txsync loops in bridge's flush.");
int netmap_flags = 0;
int netmap_fwd = 0;
int netmap_mmap_unreg = 0;
enum { NETMAP_ADMODE_BEST = 0,
NETMAP_ADMODE_NATIVE,
NETMAP_ADMODE_GENERIC,
NETMAP_ADMODE_LAST };
#define NETMAP_ADMODE_NATIVE 1
#define NETMAP_ADMODE_GENERIC 2
#define NETMAP_ADMODE_BEST 0
static int netmap_admode = NETMAP_ADMODE_BEST;
int netmap_generic_mit = 100*1000;
int netmap_generic_ringsize = 1024;
SYSCTL_INT(_dev_netmap, OID_AUTO, flags, CTLFLAG_RW, &netmap_flags, 0 , "");
SYSCTL_INT(_dev_netmap, OID_AUTO, fwd, CTLFLAG_RW, &netmap_fwd, 0 , "");
SYSCTL_INT(_dev_netmap, OID_AUTO, mmap_unreg, CTLFLAG_RW, &netmap_mmap_unreg, 0, "");
SYSCTL_INT(_dev_netmap, OID_AUTO, admode, CTLFLAG_RW, &netmap_admode, 0 , "");
SYSCTL_INT(_dev_netmap, OID_AUTO, generic_mit, CTLFLAG_RW, &netmap_generic_mit, 0 , "");
SYSCTL_INT(_dev_netmap, OID_AUTO, generic_ringsize, CTLFLAG_RW, &netmap_generic_ringsize, 0 , "");
NMG_LOCK_T netmap_global_lock;
static void
nm_kr_get(struct netmap_kring *kr)
{
while (NM_ATOMIC_TEST_AND_SET(&kr->nr_busy))
tsleep(kr, 0, "NM_KR_GET", 4);
}
void
netmap_disable_ring(struct netmap_kring *kr)
{
kr->nkr_stopped = 1;
nm_kr_get(kr);
lockmgr(&kr->q_lock, LK_EXCLUSIVE);
lockmgr(&kr->q_lock, LK_RELEASE);
nm_kr_put(kr);
}
static void
netmap_set_all_rings(struct ifnet *ifp, int stopped)
{
struct netmap_adapter *na;
int i;
if (!(ifp->if_capenable & IFCAP_NETMAP))
return;
na = NA(ifp);
for (i = 0; i <= na->num_tx_rings; i++) {
if (stopped)
netmap_disable_ring(na->tx_rings + i);
else
na->tx_rings[i].nkr_stopped = 0;
na->nm_notify(na, i, NR_TX, NAF_DISABLE_NOTIFY |
(i == na->num_tx_rings ? NAF_GLOBAL_NOTIFY: 0));
}
for (i = 0; i <= na->num_rx_rings; i++) {
if (stopped)
netmap_disable_ring(na->rx_rings + i);
else
na->rx_rings[i].nkr_stopped = 0;
na->nm_notify(na, i, NR_RX, NAF_DISABLE_NOTIFY |
(i == na->num_rx_rings ? NAF_GLOBAL_NOTIFY: 0));
}
}
void
netmap_disable_all_rings(struct ifnet *ifp)
{
netmap_set_all_rings(ifp, 1 );
}
void
netmap_enable_all_rings(struct ifnet *ifp)
{
netmap_set_all_rings(ifp, 0 );
}
u_int
nm_bound_var(u_int *v, u_int dflt, u_int lo, u_int hi, const char *msg)
{
u_int oldv = *v;
const char *op = NULL;
if (dflt < lo)
dflt = lo;
if (dflt > hi)
dflt = hi;
if (oldv < lo) {
*v = dflt;
op = "Bump";
} else if (oldv > hi) {
*v = hi;
op = "Clamp";
}
if (op && msg)
kprintf("%s %s to %d (was %d)\n", op, msg, *v, oldv);
return *v;
}
const char *
nm_dump_buf(char *p, int len, int lim, char *dst)
{
static char _dst[8192];
int i, j, i0;
static char hex[] ="0123456789abcdef";
char *o;
#define P_HI(x) hex[((x) & 0xf0)>>4]
#define P_LO(x) hex[((x) & 0xf)]
#define P_C(x) ((x) >= 0x20 && (x) <= 0x7e ? (x) : '.')
if (!dst)
dst = _dst;
if (lim <= 0 || lim > len)
lim = len;
o = dst;
ksprintf(o, "buf 0x%p len %d lim %d\n", p, len, lim);
o += strlen(o);
for (i = 0; i < lim; ) {
ksprintf(o, "%5d: ", i);
o += strlen(o);
memset(o, ' ', 48);
i0 = i;
for (j=0; j < 16 && i < lim; i++, j++) {
o[j*3] = P_HI(p[i]);
o[j*3+1] = P_LO(p[i]);
}
i = i0;
for (j=0; j < 16 && i < lim; i++, j++)
o[j + 48] = P_C(p[i]);
o[j+48] = '\n';
o += j+49;
}
*o = '\0';
#undef P_HI
#undef P_LO
#undef P_C
return dst;
}
int
netmap_update_config(struct netmap_adapter *na)
{
struct ifnet *ifp = na->ifp;
u_int txr, txd, rxr, rxd;
txr = txd = rxr = rxd = 0;
if (na->nm_config) {
na->nm_config(na, &txr, &txd, &rxr, &rxd);
} else {
txr = na->num_tx_rings;
txd = na->num_tx_desc;
rxr = na->num_rx_rings;
rxd = na->num_rx_desc;
}
if (na->num_tx_rings == txr && na->num_tx_desc == txd &&
na->num_rx_rings == rxr && na->num_rx_desc == rxd)
return 0;
if (netmap_verbose || na->active_fds > 0) {
D("stored config %s: txring %d x %d, rxring %d x %d",
NM_IFPNAME(ifp),
na->num_tx_rings, na->num_tx_desc,
na->num_rx_rings, na->num_rx_desc);
D("new config %s: txring %d x %d, rxring %d x %d",
NM_IFPNAME(ifp), txr, txd, rxr, rxd);
}
if (na->active_fds == 0) {
D("configuration changed (but fine)");
na->num_tx_rings = txr;
na->num_tx_desc = txd;
na->num_rx_rings = rxr;
na->num_rx_desc = rxd;
return 0;
}
D("configuration changed while active, this is bad...");
return 1;
}
int
netmap_krings_create(struct netmap_adapter *na, u_int ntx, u_int nrx, u_int tailroom)
{
u_int i, len, ndesc;
struct netmap_kring *kring;
len = (ntx + nrx) * sizeof(struct netmap_kring) + tailroom;
na->tx_rings = kmalloc((size_t)len, M_DEVBUF, M_NOWAIT | M_ZERO);
if (na->tx_rings == NULL) {
D("Cannot allocate krings");
return ENOMEM;
}
na->rx_rings = na->tx_rings + ntx;
ndesc = na->num_tx_desc;
for (i = 0; i < ntx; i++) {
kring = &na->tx_rings[i];
bzero(kring, sizeof(*kring));
kring->na = na;
kring->nkr_num_slots = ndesc;
kring->nr_hwavail = ndesc - 1;
lockinit(&kring->q_lock, "nm_txq_lock", 0, LK_CANRECURSE);
init_waitqueue_head(&kring->si);
}
ndesc = na->num_rx_desc;
for (i = 0; i < nrx; i++) {
kring = &na->rx_rings[i];
bzero(kring, sizeof(*kring));
kring->na = na;
kring->nkr_num_slots = ndesc;
lockinit(&kring->q_lock, "nm_rxq_lock", 0, LK_CANRECURSE);
init_waitqueue_head(&kring->si);
}
init_waitqueue_head(&na->tx_si);
init_waitqueue_head(&na->rx_si);
na->tailroom = na->rx_rings + nrx;
return 0;
}
void
netmap_krings_delete(struct netmap_adapter *na)
{
int i;
for (i = 0; i < na->num_tx_rings + 1; i++) {
lockuninit(&na->tx_rings[i].q_lock);
}
for (i = 0; i < na->num_rx_rings + 1; i++) {
lockuninit(&na->rx_rings[i].q_lock);
}
kfree(na->tx_rings, M_DEVBUF);
na->tx_rings = na->rx_rings = na->tailroom = NULL;
}
static struct netmap_if*
netmap_if_new(const char *ifname, struct netmap_adapter *na)
{
struct netmap_if *nifp;
if (netmap_update_config(na)) {
return NULL;
}
if (na->active_fds)
goto final;
if (na->nm_krings_create(na))
goto cleanup;
if (netmap_mem_rings_create(na))
goto cleanup;
final:
nifp = netmap_mem_if_new(ifname, na);
if (nifp == NULL)
goto cleanup;
return (nifp);
cleanup:
if (na->active_fds == 0) {
netmap_mem_rings_delete(na);
na->nm_krings_delete(na);
}
return NULL;
}
static int
netmap_get_memory_locked(struct netmap_priv_d* p)
{
struct netmap_mem_d *nmd;
int error = 0;
if (p->np_na == NULL) {
if (!netmap_mmap_unreg)
return ENODEV;
nmd = &nm_mem;
} else {
nmd = p->np_na->nm_mem;
}
if (p->np_mref == NULL) {
error = netmap_mem_finalize(nmd);
if (!error)
p->np_mref = nmd;
} else if (p->np_mref != nmd) {
error = ENODEV;
}
return error;
}
int
netmap_get_memory(struct netmap_priv_d* p)
{
int error;
NMG_LOCK();
error = netmap_get_memory_locked(p);
NMG_UNLOCK();
return error;
}
static int
netmap_have_memory_locked(struct netmap_priv_d* p)
{
return p->np_mref != NULL;
}
static void
netmap_drop_memory_locked(struct netmap_priv_d* p)
{
if (p->np_mref) {
netmap_mem_deref(p->np_mref);
p->np_mref = NULL;
}
}
static void
netmap_do_unregif(struct netmap_priv_d *priv, struct netmap_if *nifp)
{
struct netmap_adapter *na = priv->np_na;
struct ifnet *ifp = na->ifp;
NMG_LOCK_ASSERT();
na->active_fds--;
if (na->active_fds <= 0) {
if (netmap_verbose)
D("deleting last instance for %s", NM_IFPNAME(ifp));
if (ifp)
na->nm_register(na, 0);
netmap_mem_rings_delete(na);
na->nm_krings_delete(na);
}
netmap_mem_if_delete(na, nifp);
}
int
netmap_dtor_locked(struct netmap_priv_d *priv)
{
struct netmap_adapter *na = priv->np_na;
if (--priv->np_refcount > 0) {
return 0;
}
if (!na) {
return 1;
}
netmap_do_unregif(priv, priv->np_nifp);
priv->np_nifp = NULL;
netmap_drop_memory_locked(priv);
if (priv->np_na) {
netmap_adapter_put(na);
priv->np_na = NULL;
}
return 1;
}
void
netmap_dtor(void *data)
{
struct netmap_priv_d *priv = data;
int last_instance;
NMG_LOCK();
last_instance = netmap_dtor_locked(priv);
NMG_UNLOCK();
if (last_instance) {
bzero(priv, sizeof(*priv));
kfree(priv, M_DEVBUF);
}
}
static void
netmap_send_up(struct ifnet *dst, struct mbq *q)
{
struct mbuf *m;
while ((m = mbq_dequeue(q)) != NULL) {
if (netmap_verbose & NM_VERB_HOST)
D("sending up pkt %p size %d", m, MBUF_LEN(m));
NM_SEND_UP(dst, m);
}
mbq_destroy(q);
}
static void
netmap_grab_packets(struct netmap_kring *kring, struct mbq *q, int force)
{
u_int lim = kring->nkr_num_slots - 1;
struct mbuf *m;
u_int k = kring->ring->cur, n = kring->ring->reserved;
struct netmap_adapter *na = kring->na;
if (n > 0) {
if (k < n)
k += kring->nkr_num_slots;
k += n;
}
for (n = kring->nr_hwcur; n != k;) {
struct netmap_slot *slot = &kring->ring->slot[n];
n = nm_next(n, lim);
if ((slot->flags & NS_FORWARD) == 0 && !force)
continue;
if (slot->len < 14 || slot->len > NETMAP_BDG_BUF_SIZE(na->nm_mem)) {
D("bad pkt at %d len %d", n, slot->len);
continue;
}
slot->flags &= ~NS_FORWARD;
m = m_devget(BDG_NMB(na, slot), slot->len, 0, na->ifp);
if (m == NULL)
break;
mbq_enqueue(q, m);
}
}
static void
netmap_sw_to_nic(struct netmap_adapter *na)
{
struct netmap_kring *kring = &na->rx_rings[na->num_rx_rings];
struct netmap_kring *k1 = &na->tx_rings[0];
u_int i, howmany, src_lim, dst_lim;
if (kring->nkr_stopped)
return;
lockmgr(&kring->q_lock, LK_EXCLUSIVE);
if (kring->nkr_stopped)
goto out;
howmany = kring->nr_hwavail;
src_lim = kring->nkr_num_slots - 1;
for (i = 0; howmany > 0 && i < na->num_tx_rings; i++, k1++) {
ND("%d packets left to ring %d (space %d)", howmany, i, k1->nr_hwavail);
dst_lim = k1->nkr_num_slots - 1;
while (howmany > 0 && k1->ring->avail > 0) {
struct netmap_slot *src, *dst, tmp;
src = &kring->ring->slot[kring->nr_hwcur];
dst = &k1->ring->slot[k1->ring->cur];
tmp = *src;
src->buf_idx = dst->buf_idx;
src->flags = NS_BUF_CHANGED;
dst->buf_idx = tmp.buf_idx;
dst->len = tmp.len;
dst->flags = NS_BUF_CHANGED;
ND("out len %d buf %d from %d to %d",
dst->len, dst->buf_idx,
kring->nr_hwcur, k1->ring->cur);
kring->nr_hwcur = nm_next(kring->nr_hwcur, src_lim);
howmany--;
kring->nr_hwavail--;
k1->ring->cur = nm_next(k1->ring->cur, dst_lim);
k1->ring->avail--;
}
kring->ring->cur = kring->nr_hwcur;
k1++;
}
out:
lockmgr(&kring->q_lock, LK_RELEASE);
}
void
netmap_txsync_to_host(struct netmap_adapter *na)
{
struct netmap_kring *kring = &na->tx_rings[na->num_tx_rings];
struct netmap_ring *ring = kring->ring;
u_int k, lim = kring->nkr_num_slots - 1;
struct mbq q;
int error;
error = nm_kr_tryget(kring);
if (error) {
if (error == NM_KR_BUSY)
D("ring %p busy (user error)", kring);
return;
}
k = ring->cur;
if (k > lim) {
D("invalid ring index in stack TX kring %p", kring);
netmap_ring_reinit(kring);
nm_kr_put(kring);
return;
}
mbq_init(&q);
netmap_grab_packets(kring, &q, 1);
kring->nr_hwcur = k;
kring->nr_hwavail = ring->avail = lim;
nm_kr_put(kring);
netmap_send_up(na->ifp, &q);
}
static void
netmap_rxsync_from_host(struct netmap_adapter *na, struct thread *td, void *pwait)
{
struct netmap_kring *kring = &na->rx_rings[na->num_rx_rings];
struct netmap_ring *ring = kring->ring;
u_int j, n, lim = kring->nkr_num_slots;
u_int k = ring->cur, resvd = ring->reserved;
(void)pwait;
if (kring->nkr_stopped)
return;
lockmgr(&kring->q_lock, LK_EXCLUSIVE);
if (kring->nkr_stopped)
goto unlock_out;
if (k >= lim) {
netmap_ring_reinit(kring);
goto unlock_out;
}
j = kring->nr_hwcur;
if (resvd > 0) {
if (resvd + ring->avail >= lim + 1) {
D("XXX invalid reserve/avail %d %d", resvd, ring->avail);
ring->reserved = resvd = 0;
}
k = (k >= resvd) ? k - resvd : k + lim - resvd;
}
if (j != k) {
n = k >= j ? k - j : k + lim - j;
kring->nr_hwavail -= n;
kring->nr_hwcur = k;
}
k = ring->avail = kring->nr_hwavail - resvd;
if (k == 0 && td)
KNOTE(&kring->si.ki_note, 0);
if (k && (netmap_verbose & NM_VERB_HOST))
D("%d pkts from stack", k);
unlock_out:
lockmgr(&kring->q_lock, LK_RELEASE);
}
int
netmap_get_hw_na(struct ifnet *ifp, struct netmap_adapter **na)
{
int i = netmap_admode;
int error = 0;
struct netmap_adapter *prev_na;
struct netmap_generic_adapter *gna;
*na = NULL;
if (i < NETMAP_ADMODE_BEST || i >= NETMAP_ADMODE_LAST)
i = netmap_admode = NETMAP_ADMODE_BEST;
if (NETMAP_CAPABLE(ifp)) {
if (NETMAP_OWNED_BY_KERN(NA(ifp)))
return EBUSY;
if (NA(ifp)->active_fds > 0 ||
i != NETMAP_ADMODE_GENERIC) {
*na = NA(ifp);
return 0;
}
}
if (!NETMAP_CAPABLE(ifp) && i == NETMAP_ADMODE_NATIVE)
return EINVAL;
prev_na = NA(ifp);
error = generic_netmap_attach(ifp);
if (error)
return error;
*na = NA(ifp);
gna = (struct netmap_generic_adapter*)NA(ifp);
gna->prev = prev_na;
if (prev_na != NULL) {
netmap_adapter_get(prev_na);
}
D("Created generic NA %p (prev %p)", gna, gna->prev);
return 0;
}
int
netmap_get_na(struct nmreq *nmr, struct netmap_adapter **na, int create)
{
struct ifnet *ifp;
int error = 0;
struct netmap_adapter *ret;
*na = NULL;
NMG_LOCK_ASSERT();
error = netmap_get_bdg_na(nmr, na, create);
if (error || *na != NULL)
return error;
ifnet_lock();
ifp = ifunit(nmr->nr_name);
if (ifp == NULL) {
error = ENXIO;
goto out;
}
error = netmap_get_hw_na(ifp, &ret);
if (error)
goto out;
if (ret != NULL) {
if (NETMAP_OWNED_BY_KERN(ret)) {
error = EINVAL;
goto out;
}
error = 0;
*na = ret;
netmap_adapter_get(ret);
}
out:
ifnet_unlock();
return error;
}
int
netmap_ring_reinit(struct netmap_kring *kring)
{
struct netmap_ring *ring = kring->ring;
u_int i, lim = kring->nkr_num_slots - 1;
int errors = 0;
RD(10, "called for %s", NM_IFPNAME(kring->na->ifp));
if (ring->cur > lim)
errors++;
for (i = 0; i <= lim; i++) {
u_int idx = ring->slot[i].buf_idx;
u_int len = ring->slot[i].len;
if (idx < 2 || idx >= netmap_total_buffers) {
if (!errors++)
D("bad buffer at slot %d idx %d len %d ", i, idx, len);
ring->slot[i].buf_idx = 0;
ring->slot[i].len = 0;
} else if (len > NETMAP_BDG_BUF_SIZE(kring->na->nm_mem)) {
ring->slot[i].len = 0;
if (!errors++)
D("bad len %d at slot %d idx %d",
len, i, idx);
}
}
if (errors) {
int pos = kring - kring->na->tx_rings;
int n = kring->na->num_tx_rings + 1;
RD(10, "total %d errors", errors);
errors++;
RD(10, "%s %s[%d] reinit, cur %d -> %d avail %d -> %d",
NM_IFPNAME(kring->na->ifp),
pos < n ? "TX" : "RX", pos < n ? pos : pos - n,
ring->cur, kring->nr_hwcur,
ring->avail, kring->nr_hwavail);
ring->cur = kring->nr_hwcur;
ring->avail = kring->nr_hwavail;
}
return (errors ? 1 : 0);
}
static int
netmap_set_ringid(struct netmap_priv_d *priv, u_int ringid)
{
struct netmap_adapter *na = priv->np_na;
struct ifnet *ifp = na->ifp;
u_int i = ringid & NETMAP_RING_MASK;
u_int lim = na->num_rx_rings;
if (na->num_tx_rings > lim)
lim = na->num_tx_rings;
if ( (ringid & NETMAP_HW_RING) && i >= lim) {
D("invalid ring id %d", i);
return (EINVAL);
}
priv->np_ringid = ringid;
if (ringid & NETMAP_SW_RING) {
priv->np_qfirst = NETMAP_SW_RING;
priv->np_qlast = 0;
} else if (ringid & NETMAP_HW_RING) {
priv->np_qfirst = i;
priv->np_qlast = i + 1;
} else {
priv->np_qfirst = 0;
priv->np_qlast = NETMAP_HW_RING ;
}
priv->np_txpoll = (ringid & NETMAP_NO_TX_POLL) ? 0 : 1;
if (netmap_verbose) {
if (ringid & NETMAP_SW_RING)
D("ringid %s set to SW RING", NM_IFPNAME(ifp));
else if (ringid & NETMAP_HW_RING)
D("ringid %s set to HW RING %d", NM_IFPNAME(ifp),
priv->np_qfirst);
else
D("ringid %s set to all %d HW RINGS", NM_IFPNAME(ifp), lim);
}
return 0;
}
struct netmap_if *
netmap_do_regif(struct netmap_priv_d *priv, struct netmap_adapter *na,
uint16_t ringid, int *err)
{
struct ifnet *ifp = na->ifp;
struct netmap_if *nifp = NULL;
int error, need_mem = 0;
NMG_LOCK_ASSERT();
netmap_update_config(na);
priv->np_na = na;
error = netmap_set_ringid(priv, ringid);
if (error)
goto out;
need_mem = !netmap_have_memory_locked(priv);
if (need_mem) {
error = netmap_get_memory_locked(priv);
ND("get_memory returned %d", error);
if (error)
goto out;
}
nifp = netmap_if_new(NM_IFPNAME(ifp), na);
if (nifp == NULL) {
error = ENOMEM;
goto out;
}
na->active_fds++;
if (ifp->if_capenable & IFCAP_NETMAP) {
} else {
na->na_lut = na->nm_mem->pools[NETMAP_BUF_POOL].lut;
ND("%p->na_lut == %p", na, na->na_lut);
na->na_lut_objtotal = na->nm_mem->pools[NETMAP_BUF_POOL].objtotal;
error = na->nm_register(na, 1);
if (error) {
netmap_do_unregif(priv, nifp);
nifp = NULL;
}
}
out:
*err = error;
if (error) {
priv->np_na = NULL;
if (need_mem)
netmap_drop_memory_locked(priv);
}
if (nifp != NULL) {
wmb();
priv->np_nifp = nifp;
}
return nifp;
}
int
netmap_ioctl(struct dev_ioctl_args *ap)
{
struct netmap_priv_d *priv = NULL;
struct ifnet *ifp = NULL;
struct nmreq *nmr = (struct nmreq *) ap->a_data;
struct netmap_adapter *na = NULL;
int error;
u_int i, lim;
struct netmap_if *nifp;
struct netmap_kring *krings;
u_long cmd = ap->a_cmd;
error = devfs_get_cdevpriv(ap->a_fp, (void **)&priv);
if (error) {
return (error == ENOENT ? ENXIO : error);
}
nmr->nr_name[sizeof(nmr->nr_name) - 1] = '\0';
switch (cmd) {
case NIOCGINFO:
if (nmr->nr_version != NETMAP_API) {
D("API mismatch got %d have %d",
nmr->nr_version, NETMAP_API);
nmr->nr_version = NETMAP_API;
error = EINVAL;
break;
}
if (nmr->nr_cmd == NETMAP_BDG_LIST) {
error = netmap_bdg_ctl(nmr, NULL);
break;
}
NMG_LOCK();
do {
struct netmap_mem_d *nmd = &nm_mem;
u_int memflags;
if (nmr->nr_name[0] != '\0') {
error = netmap_get_na(nmr, &na, 1 );
if (error)
break;
nmd = na->nm_mem;
}
error = netmap_mem_get_info(nmd, &nmr->nr_memsize, &memflags);
if (error)
break;
if (na == NULL)
break;
nmr->nr_offset = 0;
nmr->nr_rx_slots = nmr->nr_tx_slots = 0;
netmap_update_config(na);
nmr->nr_rx_rings = na->num_rx_rings;
nmr->nr_tx_rings = na->num_tx_rings;
nmr->nr_rx_slots = na->num_rx_desc;
nmr->nr_tx_slots = na->num_tx_desc;
if (memflags & NETMAP_MEM_PRIVATE)
nmr->nr_ringid |= NETMAP_PRIV_MEM;
netmap_adapter_put(na);
} while (0);
NMG_UNLOCK();
break;
case NIOCREGIF:
if (nmr->nr_version != NETMAP_API) {
nmr->nr_version = NETMAP_API;
error = EINVAL;
break;
}
i = nmr->nr_cmd;
if (i == NETMAP_BDG_ATTACH || i == NETMAP_BDG_DETACH) {
error = netmap_bdg_ctl(nmr, NULL);
break;
} else if (i != 0) {
D("nr_cmd must be 0 not %d", i);
error = EINVAL;
break;
}
NMG_LOCK();
do {
u_int memflags;
if (priv->np_na != NULL) {
error = netmap_set_ringid(priv, nmr->nr_ringid);
break;
}
error = netmap_get_na(nmr, &na, 1 );
if (error)
break;
ifp = na->ifp;
if (NETMAP_OWNED_BY_KERN(na)) {
netmap_adapter_put(na);
error = EBUSY;
break;
}
nifp = netmap_do_regif(priv, na, nmr->nr_ringid, &error);
if (!nifp) {
netmap_adapter_put(na);
priv->np_nifp = NULL;
break;
}
nmr->nr_rx_rings = na->num_rx_rings;
nmr->nr_tx_rings = na->num_tx_rings;
nmr->nr_rx_slots = na->num_rx_desc;
nmr->nr_tx_slots = na->num_tx_desc;
error = netmap_mem_get_info(na->nm_mem, &nmr->nr_memsize, &memflags);
if (error) {
netmap_adapter_put(na);
break;
}
if (memflags & NETMAP_MEM_PRIVATE) {
nmr->nr_ringid |= NETMAP_PRIV_MEM;
*(uint32_t *)(uintptr_t)&nifp->ni_flags |= NI_PRIV_MEM;
}
nmr->nr_offset = netmap_mem_if_offset(na->nm_mem, nifp);
} while (0);
NMG_UNLOCK();
break;
case NIOCUNREGIF:
D("deprecated, data is %p", nmr);
error = EINVAL;
break;
case NIOCTXSYNC:
case NIOCRXSYNC:
nifp = priv->np_nifp;
if (nifp == NULL) {
error = ENXIO;
break;
}
rmb();
na = priv->np_na;
if (na == NULL) {
D("Internal error: nifp != NULL && na == NULL");
error = ENXIO;
break;
}
ifp = na->ifp;
if (ifp == NULL) {
RD(1, "the ifp is gone");
error = ENXIO;
break;
}
if (priv->np_qfirst == NETMAP_SW_RING) {
if (cmd == NIOCTXSYNC)
netmap_txsync_to_host(na);
else
netmap_rxsync_from_host(na, NULL, NULL);
break;
}
lim = priv->np_qlast;
if (lim == NETMAP_HW_RING)
lim = (cmd == NIOCTXSYNC) ?
na->num_tx_rings : na->num_rx_rings;
krings = (cmd == NIOCTXSYNC) ? na->tx_rings : na->rx_rings;
for (i = priv->np_qfirst; i < lim; i++) {
struct netmap_kring *kring = krings + i;
if (nm_kr_tryget(kring)) {
error = EBUSY;
goto out;
}
if (cmd == NIOCTXSYNC) {
if (netmap_verbose & NM_VERB_TXSYNC)
D("pre txsync ring %d cur %d hwcur %d",
i, kring->ring->cur,
kring->nr_hwcur);
na->nm_txsync(na, i, NAF_FORCE_RECLAIM);
if (netmap_verbose & NM_VERB_TXSYNC)
D("post txsync ring %d cur %d hwcur %d",
i, kring->ring->cur,
kring->nr_hwcur);
} else {
na->nm_rxsync(na, i, NAF_FORCE_READ);
microtime(&na->rx_rings[i].ring->ts);
}
nm_kr_put(kring);
}
break;
case BIOCIMMEDIATE:
case BIOCGHDRCMPLT:
case BIOCSHDRCMPLT:
case BIOCSSEESENT:
D("ignore BIOCIMMEDIATE/BIOCSHDRCMPLT/BIOCSHDRCMPLT/BIOCSSEESENT");
break;
default:
{
struct socket so;
bzero(&so, sizeof(so));
NMG_LOCK();
error = netmap_get_na(nmr, &na, 0 );
if (error) {
netmap_adapter_put(na);
NMG_UNLOCK();
break;
}
ifp = na->ifp;
error = ifioctl(&so, cmd, ap->a_data, ap->a_cred);
netmap_adapter_put(na);
NMG_UNLOCK();
break;
}
}
out:
return (error);
}
static int
netmap_kqfilter_event(struct knote *kn, long hint)
{
return (0);
}
static void
netmap_kqfilter_detach(struct knote *kn)
{
}
static struct filterops netmap_kqfilter_ops = {
FILTEROP_ISFD, NULL, netmap_kqfilter_detach, netmap_kqfilter_event,
};
int
netmap_kqfilter(struct dev_kqfilter_args *ap)
{
struct knote *kn = ap->a_kn;
ap->a_result = 0;
switch (kn->kn_filter) {
case EVFILT_READ:
case EVFILT_WRITE:
kn->kn_fop = &netmap_kqfilter_ops;
break;
default:
ap->a_result = EOPNOTSUPP;
return (0);
}
return (0);
}
static inline int
netmap_poll(struct cdev *dev, int events, struct thread *td)
{
struct netmap_priv_d *priv = NULL;
struct netmap_adapter *na;
struct ifnet *ifp;
struct netmap_kring *kring;
u_int i, check_all_tx, check_all_rx, want_tx, want_rx, revents = 0;
u_int lim_tx, lim_rx, host_forwarded = 0;
struct mbq q;
void *pwait = dev;
int retry_tx = 1;
(void)pwait;
mbq_init(&q);
if (devfs_get_cdevpriv(NULL, (void **)&priv) != 0 || priv == NULL)
return POLLERR;
if (priv->np_nifp == NULL) {
D("No if registered");
return POLLERR;
}
rmb();
na = priv->np_na;
ifp = na->ifp;
if (ifp == NULL) {
RD(1, "the ifp is gone");
return POLLERR;
}
if ( (ifp->if_capenable & IFCAP_NETMAP) == 0)
return POLLERR;
if (netmap_verbose & 0x8000)
D("device %s events 0x%x", NM_IFPNAME(ifp), events);
want_tx = events & (POLLOUT | POLLWRNORM);
want_rx = events & (POLLIN | POLLRDNORM);
lim_tx = na->num_tx_rings;
lim_rx = na->num_rx_rings;
if (priv->np_qfirst == NETMAP_SW_RING) {
if (priv->np_txpoll || want_tx) {
netmap_txsync_to_host(na);
revents |= want_tx;
}
if (want_rx) {
kring = &na->rx_rings[lim_rx];
if (kring->ring->avail == 0)
netmap_rxsync_from_host(na, td, dev);
if (kring->ring->avail > 0) {
revents |= want_rx;
}
}
return (revents);
}
kring = &na->rx_rings[lim_rx];
if ( (priv->np_qlast == NETMAP_HW_RING)
&& want_rx
&& (netmap_fwd || kring->ring->flags & NR_FORWARD) ) {
if (kring->ring->avail == 0)
netmap_rxsync_from_host(na, td, dev);
if (kring->ring->avail > 0)
revents |= want_rx;
}
check_all_tx = (priv->np_qlast == NETMAP_HW_RING) && (lim_tx > 1);
check_all_rx = (priv->np_qlast == NETMAP_HW_RING) && (lim_rx > 1);
if (priv->np_qlast != NETMAP_HW_RING) {
lim_tx = lim_rx = priv->np_qlast;
}
for (i = priv->np_qfirst; want_rx && i < lim_rx; i++) {
kring = &na->rx_rings[i];
if (kring->ring->avail > 0) {
revents |= want_rx;
want_rx = 0;
}
}
for (i = priv->np_qfirst; want_tx && i < lim_tx; i++) {
kring = &na->tx_rings[i];
if (kring->ring->avail > 0) {
revents |= want_tx;
want_tx = 0;
}
}
if (priv->np_txpoll || want_tx) {
flush_tx:
for (i = priv->np_qfirst; i < lim_tx; i++) {
kring = &na->tx_rings[i];
if (!want_tx && kring->ring->cur == kring->nr_hwcur)
continue;
if (nm_kr_tryget(kring)) {
ND("ring %p busy is %d",
kring, (int)kring->nr_busy);
revents |= POLLERR;
goto out;
}
if (netmap_verbose & NM_VERB_TXSYNC)
D("send %d on %s %d",
kring->ring->cur, NM_IFPNAME(ifp), i);
if (na->nm_txsync(na, i, 0))
revents |= POLLERR;
if (want_tx) {
if (kring->ring->avail > 0) {
revents |= want_tx;
want_tx = 0;
}
}
nm_kr_put(kring);
}
if (want_tx && retry_tx) {
KNOTE(check_all_tx ? &na->tx_si.ki_note :
&na->tx_rings[priv->np_qfirst].si.ki_note, 0);
retry_tx = 0;
goto flush_tx;
}
}
if (want_rx) {
int retry_rx = 1;
do_retry_rx:
for (i = priv->np_qfirst; i < lim_rx; i++) {
kring = &na->rx_rings[i];
if (nm_kr_tryget(kring)) {
revents |= POLLERR;
goto out;
}
if (netmap_fwd ||kring->ring->flags & NR_FORWARD) {
ND(10, "forwarding some buffers up %d to %d",
kring->nr_hwcur, kring->ring->cur);
netmap_grab_packets(kring, &q, netmap_fwd);
}
if (na->nm_rxsync(na, i, 0))
revents |= POLLERR;
if (netmap_no_timestamp == 0 ||
kring->ring->flags & NR_TIMESTAMP) {
microtime(&kring->ring->ts);
}
if (kring->ring->avail > 0) {
revents |= want_rx;
retry_rx = 0;
}
nm_kr_put(kring);
}
if (retry_rx) {
retry_rx = 0;
KNOTE(check_all_rx ? &na->rx_si.ki_note :
&na->rx_rings[priv->np_qfirst].si.ki_note, 0);
goto do_retry_rx;
}
}
kring = &na->rx_rings[lim_rx];
if ( (priv->np_qlast == NETMAP_HW_RING)
&& (netmap_fwd || kring->ring->flags & NR_FORWARD)
&& kring->nr_hwavail > 0 && !host_forwarded) {
netmap_sw_to_nic(na);
host_forwarded = 1;
want_rx = 0;
goto flush_tx;
}
if (q.head)
netmap_send_up(na->ifp, &q);
out:
return (revents);
}
static int netmap_hw_krings_create(struct netmap_adapter *);
static int
netmap_notify(struct netmap_adapter *na, u_int n_ring, enum txrx tx, int flags)
{
struct netmap_kring *kring;
if (tx == NR_TX) {
kring = na->tx_rings + n_ring;
KNOTE(&kring->si.ki_note, 0);
wakeup(&kring->si.ki_note);
if (flags & NAF_GLOBAL_NOTIFY)
wakeup(&na->tx_si.ki_note);
} else {
kring = na->rx_rings + n_ring;
KNOTE(&kring->si.ki_note, 0);
wakeup(&kring->si.ki_note);
if (flags & NAF_GLOBAL_NOTIFY)
wakeup(&na->rx_si.ki_note);
}
return 0;
}
int
netmap_attach_common(struct netmap_adapter *na)
{
struct ifnet *ifp = na->ifp;
if (na->num_tx_rings == 0 || na->num_rx_rings == 0) {
D("%s: invalid rings tx %d rx %d",
ifp->if_xname, na->num_tx_rings, na->num_rx_rings);
return EINVAL;
}
WNA(ifp) = na;
NETMAP_SET_CAPABLE(ifp);
if (na->nm_krings_create == NULL) {
na->nm_krings_create = netmap_hw_krings_create;
na->nm_krings_delete = netmap_krings_delete;
}
if (na->nm_notify == NULL)
na->nm_notify = netmap_notify;
na->active_fds = 0;
if (na->nm_mem == NULL)
na->nm_mem = &nm_mem;
return 0;
}
void
netmap_detach_common(struct netmap_adapter *na)
{
if (na->ifp)
WNA(na->ifp) = NULL;
if (na->tx_rings) {
D("freeing leftover tx_rings");
na->nm_krings_delete(na);
}
if (na->na_flags & NAF_MEM_OWNER)
netmap_mem_private_delete(na->nm_mem);
bzero(na, sizeof(*na));
kfree(na, M_DEVBUF);
}
int
netmap_attach(struct netmap_adapter *arg)
{
struct netmap_hw_adapter *hwna = NULL;
struct ifnet *ifp = arg ? arg->ifp : NULL;
if (arg == NULL || ifp == NULL)
goto fail;
hwna = kmalloc(sizeof(*hwna), M_DEVBUF, M_NOWAIT | M_ZERO);
if (hwna == NULL)
goto fail;
hwna->up = *arg;
if (netmap_attach_common(&hwna->up)) {
kfree(hwna, M_DEVBUF);
goto fail;
}
netmap_adapter_get(&hwna->up);
D("success for %s", NM_IFPNAME(ifp));
return 0;
fail:
D("fail, arg %p ifp %p na %p", arg, ifp, hwna);
netmap_detach(ifp);
return (hwna ? EINVAL : ENOMEM);
}
void
NM_DBG(netmap_adapter_get)(struct netmap_adapter *na)
{
if (!na) {
return;
}
refcount_acquire(&na->na_refcount);
}
int
NM_DBG(netmap_adapter_put)(struct netmap_adapter *na)
{
if (!na)
return 1;
if (!refcount_release(&na->na_refcount))
return 0;
if (na->nm_dtor)
na->nm_dtor(na);
netmap_detach_common(na);
return 1;
}
int
netmap_hw_krings_create(struct netmap_adapter *na)
{
return netmap_krings_create(na,
na->num_tx_rings + 1, na->num_rx_rings + 1, 0);
}
void
netmap_detach(struct ifnet *ifp)
{
struct netmap_adapter *na = NA(ifp);
if (!na)
return;
NMG_LOCK();
netmap_disable_all_rings(ifp);
netmap_adapter_put(na);
na->ifp = NULL;
netmap_enable_all_rings(ifp);
NMG_UNLOCK();
}
int
netmap_transmit(struct ifnet *ifp, struct mbuf *m)
{
struct netmap_adapter *na = NA(ifp);
struct netmap_kring *kring;
u_int i, len = MBUF_LEN(m);
u_int error = EBUSY, lim;
struct netmap_slot *slot;
if ( (ifp->if_capenable & IFCAP_NETMAP) == 0) {
error = ENXIO;
goto done;
}
kring = &na->rx_rings[na->num_rx_rings];
lim = kring->nkr_num_slots - 1;
if (netmap_verbose & NM_VERB_HOST)
D("%s packet %d len %d from the stack", NM_IFPNAME(ifp),
kring->nr_hwcur + kring->nr_hwavail, len);
if (len > NETMAP_BDG_BUF_SIZE(na->nm_mem)) {
D("%s from_host, drop packet size %d > %d", NM_IFPNAME(ifp),
len, NETMAP_BDG_BUF_SIZE(na->nm_mem));
goto done;
}
lockmgr(&kring->q_lock, LK_EXCLUSIVE);
if (kring->nr_hwavail >= lim) {
if (netmap_verbose)
D("stack ring %s full\n", NM_IFPNAME(ifp));
} else {
i = nm_kr_rxpos(kring);
slot = &kring->ring->slot[i];
m_copydata(m, 0, (int)len, BDG_NMB(na, slot));
slot->len = len;
slot->flags = kring->nkr_slot_flags;
kring->nr_hwavail++;
if (netmap_verbose & NM_VERB_HOST)
D("wake up host ring %s %d", NM_IFPNAME(na->ifp), na->num_rx_rings);
na->nm_notify(na, na->num_rx_rings, NR_RX, 0);
error = 0;
}
lockmgr(&kring->q_lock, LK_RELEASE);
done:
m_freem(m);
return (error);
}
struct netmap_slot *
netmap_reset(struct netmap_adapter *na, enum txrx tx, u_int n,
u_int new_cur)
{
struct netmap_kring *kring;
int new_hwofs, lim;
if (na == NULL) {
D("NULL na, should not happen");
return NULL;
}
if (!(na->ifp->if_capenable & IFCAP_NETMAP) || nma_is_generic(na)) {
ND("interface not in netmap mode");
return NULL;
}
if (tx == NR_TX) {
if (n >= na->num_tx_rings)
return NULL;
kring = na->tx_rings + n;
new_hwofs = kring->nr_hwcur - new_cur;
} else {
if (n >= na->num_rx_rings)
return NULL;
kring = na->rx_rings + n;
new_hwofs = kring->nr_hwcur + kring->nr_hwavail - new_cur;
}
lim = kring->nkr_num_slots - 1;
if (new_hwofs > lim)
new_hwofs -= lim + 1;
D("%s hwofs %d -> %d, hwavail %d -> %d",
tx == NR_TX ? "TX" : "RX",
kring->nkr_hwofs, new_hwofs,
kring->nr_hwavail,
tx == NR_TX ? lim : kring->nr_hwavail);
kring->nkr_hwofs = new_hwofs;
if (tx == NR_TX)
kring->nr_hwavail = lim;
kring->nr_hwreserved = 0;
na->nm_notify(na, n, tx, NAF_GLOBAL_NOTIFY);
return kring->ring->slot;
}
int
netmap_common_irq(struct ifnet *ifp, u_int q, u_int *work_done)
{
struct netmap_adapter *na = NA(ifp);
struct netmap_kring *kring;
q &= NETMAP_RING_MASK;
if (netmap_verbose) {
RD(5, "received %s queue %d", work_done ? "RX" : "TX" , q);
}
if (work_done) {
if (q >= na->num_rx_rings)
return 0;
kring = na->rx_rings + q;
kring->nr_kflags |= NKR_PENDINTR;
na->nm_notify(na, q, NR_RX,
(na->num_rx_rings > 1 ? NAF_GLOBAL_NOTIFY : 0));
*work_done = 1;
} else {
if (q >= na->num_tx_rings)
return 0;
kring = na->tx_rings + q;
na->nm_notify(na, q, NR_TX,
(na->num_tx_rings > 1 ? NAF_GLOBAL_NOTIFY : 0));
}
return 1;
}
int
netmap_rx_irq(struct ifnet *ifp, u_int q, u_int *work_done)
{
if (!(ifp->if_capenable & IFCAP_NETMAP))
return 0;
if (NA(ifp)->na_flags & NAF_SKIP_INTR) {
ND("use regular interrupt");
return 0;
}
return netmap_common_irq(ifp, q, work_done);
}
static struct cdev *netmap_dev;
int
netmap_init(void)
{
int error;
NMG_LOCK_INIT();
error = netmap_mem_init();
if (error != 0) {
kprintf("netmap: unable to initialize the memory allocator.\n");
return (error);
}
kprintf("netmap: loaded module\n");
netmap_dev = make_dev(&netmap_cdevsw, 0, UID_ROOT, GID_WHEEL, 0660,
"netmap");
netmap_init_bridges();
return (error);
}
void
netmap_fini(void)
{
destroy_dev(netmap_dev);
netmap_mem_fini();
NMG_LOCK_DESTROY();
kprintf("netmap: unloaded module.\n");
}