#include "opt_param.h"
#include "opt_mbuf_stress_test.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/file.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/kernel.h>
#include <sys/sysctl.h>
#include <sys/domain.h>
#include <sys/objcache.h>
#include <sys/tree.h>
#include <sys/protosw.h>
#include <sys/uio.h>
#include <sys/thread.h>
#include <sys/proc.h>
#include <sys/globaldata.h>
#include <sys/spinlock2.h>
#include <machine/atomic.h>
#include <machine/limits.h>
#include <vm/vm.h>
#include <vm/vm_kern.h>
#include <vm/vm_extern.h>
#ifdef INVARIANTS
#include <machine/cpu.h>
#endif
struct mbcluster {
int32_t mcl_refs;
void *mcl_data;
};
#ifdef MBUF_DEBUG
static MALLOC_DEFINE(M_MTRACK, "mtrack", "mtrack");
struct mbctrack;
RB_HEAD(mbuf_rb_tree, mbtrack);
RB_PROTOTYPE2(mbuf_rb_tree, mbtrack, rb_node, mbtrack_cmp, struct mbuf *);
struct mbtrack {
RB_ENTRY(mbtrack) rb_node;
int trackid;
struct mbuf *m;
};
static int
mbtrack_cmp(struct mbtrack *mb1, struct mbtrack *mb2)
{
if (mb1->m < mb2->m)
return (-1);
if (mb1->m > mb2->m)
return (1);
return (0);
}
RB_GENERATE2(mbuf_rb_tree, mbtrack, rb_node, mbtrack_cmp, struct mbuf *, m);
struct mbuf_rb_tree mbuf_track_root;
static struct spinlock mbuf_track_spin =
SPINLOCK_INITIALIZER(mbuf_track_spin, "mbuf_track_spin");
static void
mbuftrack(struct mbuf *m)
{
struct mbtrack *mbt;
mbt = kmalloc(sizeof(*mbt), M_MTRACK, M_INTWAIT|M_ZERO);
spin_lock(&mbuf_track_spin);
mbt->m = m;
if (mbuf_rb_tree_RB_INSERT(&mbuf_track_root, mbt)) {
spin_unlock(&mbuf_track_spin);
panic("%s: mbuf %p already being tracked", __func__, m);
}
spin_unlock(&mbuf_track_spin);
}
static void
mbufuntrack(struct mbuf *m)
{
struct mbtrack *mbt;
spin_lock(&mbuf_track_spin);
mbt = mbuf_rb_tree_RB_LOOKUP(&mbuf_track_root, m);
if (mbt == NULL) {
spin_unlock(&mbuf_track_spin);
panic("%s: mbuf %p was not tracked", __func__, m);
} else {
mbuf_rb_tree_RB_REMOVE(&mbuf_track_root, mbt);
spin_unlock(&mbuf_track_spin);
kfree(mbt, M_MTRACK);
}
}
void
mbuftrackid(struct mbuf *m, int trackid)
{
struct mbtrack *mbt;
struct mbuf *n;
spin_lock(&mbuf_track_spin);
while (m) {
n = m->m_nextpkt;
while (m) {
mbt = mbuf_rb_tree_RB_LOOKUP(&mbuf_track_root, m);
if (mbt == NULL) {
spin_unlock(&mbuf_track_spin);
panic("%s: mbuf %p not tracked", __func__, m);
}
mbt->trackid = trackid;
m = m->m_next;
}
m = n;
}
spin_unlock(&mbuf_track_spin);
}
static int
mbuftrack_callback(struct mbtrack *mbt, void *arg)
{
struct sysctl_req *req = arg;
char buf[64];
int error;
ksnprintf(buf, sizeof(buf), "mbuf %p track %d\n", mbt->m, mbt->trackid);
spin_unlock(&mbuf_track_spin);
error = SYSCTL_OUT(req, buf, strlen(buf));
spin_lock(&mbuf_track_spin);
if (error)
return (-error);
return (0);
}
static int
mbuftrack_show(SYSCTL_HANDLER_ARGS)
{
int error;
spin_lock(&mbuf_track_spin);
error = mbuf_rb_tree_RB_SCAN(&mbuf_track_root, NULL,
mbuftrack_callback, req);
spin_unlock(&mbuf_track_spin);
return (-error);
}
SYSCTL_PROC(_kern_ipc, OID_AUTO, showmbufs, CTLFLAG_RD|CTLTYPE_STRING,
0, 0, mbuftrack_show, "A", "Show all in-use mbufs");
#else
#define mbuftrack(m)
#define mbufuntrack(m)
#endif
static void mbinit(void *);
SYSINIT(mbuf, SI_BOOT2_MACHDEP, SI_ORDER_FIRST, mbinit, NULL);
struct mbtypes_stat {
u_long stats[MT_NTYPES];
} __cachealign;
static struct mbtypes_stat mbtypes[SMP_MAXCPU];
static struct mbstat mbstat[SMP_MAXCPU] __cachealign;
int max_linkhdr;
int max_protohdr;
int max_hdr;
int max_datalen;
int m_defragpackets;
int m_defragbytes;
int m_defraguseless;
int m_defragfailure;
#ifdef MBUF_STRESS_TEST
int m_defragrandomfailures;
#endif
struct objcache *mbuf_cache, *mbufphdr_cache;
struct objcache *mclmeta_cache, *mjclmeta_cache;
struct objcache *mbufcluster_cache, *mbufphdrcluster_cache;
struct objcache *mbufjcluster_cache, *mbufphdrjcluster_cache;
struct lock mbupdate_lk = LOCK_INITIALIZER("mbupdate", 0, LK_CANRECURSE);
int nmbclusters;
static int nmbjclusters;
int nmbufs;
static int mjclph_cachefrac;
static int mjcl_cachefrac;
static int mclph_cachefrac;
static int mcl_cachefrac;
SYSCTL_INT(_kern_ipc, KIPC_MAX_LINKHDR, max_linkhdr, CTLFLAG_RW,
&max_linkhdr, 0, "Max size of a link-level header");
SYSCTL_INT(_kern_ipc, KIPC_MAX_PROTOHDR, max_protohdr, CTLFLAG_RW,
&max_protohdr, 0, "Max size of a protocol header");
SYSCTL_INT(_kern_ipc, KIPC_MAX_HDR, max_hdr, CTLFLAG_RW, &max_hdr, 0,
"Max size of link+protocol headers");
SYSCTL_INT(_kern_ipc, KIPC_MAX_DATALEN, max_datalen, CTLFLAG_RW,
&max_datalen, 0, "Max data payload size without headers");
static int do_mbstat(SYSCTL_HANDLER_ARGS);
SYSCTL_PROC(_kern_ipc, KIPC_MBSTAT, mbstat, CTLTYPE_STRUCT|CTLFLAG_RD,
0, 0, do_mbstat, "S,mbstat", "mbuf usage statistics");
static int do_mbtypes(SYSCTL_HANDLER_ARGS);
SYSCTL_PROC(_kern_ipc, OID_AUTO, mbtypes, CTLTYPE_ULONG|CTLFLAG_RD,
0, 0, do_mbtypes, "LU", "");
static int
do_mbstat(SYSCTL_HANDLER_ARGS)
{
struct mbstat mbstat_total;
struct mbstat *mbstat_totalp;
int i;
bzero(&mbstat_total, sizeof(mbstat_total));
mbstat_totalp = &mbstat_total;
for (i = 0; i < ncpus; i++) {
mbstat_total.m_mbufs += mbstat[i].m_mbufs;
mbstat_total.m_clusters += mbstat[i].m_clusters;
mbstat_total.m_jclusters += mbstat[i].m_jclusters;
mbstat_total.m_clfree += mbstat[i].m_clfree;
mbstat_total.m_drops += mbstat[i].m_drops;
mbstat_total.m_wait += mbstat[i].m_wait;
mbstat_total.m_drain += mbstat[i].m_drain;
mbstat_total.m_mcfail += mbstat[i].m_mcfail;
mbstat_total.m_mpfail += mbstat[i].m_mpfail;
}
mbstat_total.m_msize = mbstat[0].m_msize;
mbstat_total.m_mclbytes = mbstat[0].m_mclbytes;
mbstat_total.m_minclsize = mbstat[0].m_minclsize;
mbstat_total.m_mlen = mbstat[0].m_mlen;
mbstat_total.m_mhlen = mbstat[0].m_mhlen;
return sysctl_handle_opaque(oidp, mbstat_totalp,
sizeof(mbstat_total), req);
}
static int
do_mbtypes(SYSCTL_HANDLER_ARGS)
{
u_long totals[MT_NTYPES];
int i, j;
for (i = 0; i < MT_NTYPES; i++)
totals[i] = 0;
for (i = 0; i < ncpus; i++) {
for (j = 0; j < MT_NTYPES; j++)
totals[j] += mbtypes[i].stats[j];
}
return sysctl_handle_opaque(oidp, totals, sizeof(totals), req);
}
static int sysctl_nmbclusters(SYSCTL_HANDLER_ARGS);
static int sysctl_nmbjclusters(SYSCTL_HANDLER_ARGS);
static int sysctl_nmbufs(SYSCTL_HANDLER_ARGS);
SYSCTL_PROC(_kern_ipc, KIPC_NMBCLUSTERS, nmbclusters, CTLTYPE_INT | CTLFLAG_RW,
0, 0, sysctl_nmbclusters, "I",
"Maximum number of mbuf clusters available");
SYSCTL_PROC(_kern_ipc, OID_AUTO, nmbjclusters, CTLTYPE_INT | CTLFLAG_RW,
0, 0, sysctl_nmbjclusters, "I",
"Maximum number of mbuf jclusters available");
SYSCTL_PROC(_kern_ipc, OID_AUTO, nmbufs, CTLTYPE_INT | CTLFLAG_RW,
0, 0, sysctl_nmbufs, "I",
"Maximum number of mbufs available");
SYSCTL_INT(_kern_ipc, OID_AUTO, mjclph_cachefrac, CTLFLAG_RD,
&mjclph_cachefrac, 0,
"Fraction of cacheable mbuf jclusters w/ pkthdr");
SYSCTL_INT(_kern_ipc, OID_AUTO, mjcl_cachefrac, CTLFLAG_RD,
&mjcl_cachefrac, 0,
"Fraction of cacheable mbuf jclusters");
SYSCTL_INT(_kern_ipc, OID_AUTO, mclph_cachefrac, CTLFLAG_RD,
&mclph_cachefrac, 0,
"Fraction of cacheable mbuf clusters w/ pkthdr");
SYSCTL_INT(_kern_ipc, OID_AUTO, mcl_cachefrac, CTLFLAG_RD,
&mcl_cachefrac, 0, "Fraction of cacheable mbuf clusters");
SYSCTL_INT(_kern_ipc, OID_AUTO, m_defragpackets, CTLFLAG_RD,
&m_defragpackets, 0, "Number of defragment packets");
SYSCTL_INT(_kern_ipc, OID_AUTO, m_defragbytes, CTLFLAG_RD,
&m_defragbytes, 0, "Number of defragment bytes");
SYSCTL_INT(_kern_ipc, OID_AUTO, m_defraguseless, CTLFLAG_RD,
&m_defraguseless, 0,
"Number of useless defragment mbuf chain operations");
SYSCTL_INT(_kern_ipc, OID_AUTO, m_defragfailure, CTLFLAG_RD,
&m_defragfailure, 0,
"Number of failed defragment mbuf chain operations");
#ifdef MBUF_STRESS_TEST
SYSCTL_INT(_kern_ipc, OID_AUTO, m_defragrandomfailures, CTLFLAG_RW,
&m_defragrandomfailures, 0, "");
#endif
static MALLOC_DEFINE(M_MBUF, "mbuf", "mbuf");
static MALLOC_DEFINE(M_MBUFCL, "mbufcl", "mbufcl");
static MALLOC_DEFINE(M_MCLMETA, "mclmeta", "mclmeta");
static void m_reclaim (void);
static void m_mclref(void *arg);
static void m_mclfree(void *arg);
static void m_mjclfree(void *arg);
static void mbupdatelimits(void);
#ifndef NMBCLUSTERS
#define NMBCLUSTERS (512 + maxproc * 4)
#endif
#ifndef BASE_CACHEFRAC
#define BASE_CACHEFRAC 16
#endif
#ifndef MJCLPH_CACHEFRAC
#define MJCLPH_CACHEFRAC (BASE_CACHEFRAC * 2)
#endif
#ifndef MJCL_CACHEFRAC
#define MJCL_CACHEFRAC (BASE_CACHEFRAC * 2)
#endif
#ifndef MCLPH_CACHEFRAC
#define MCLPH_CACHEFRAC (BASE_CACHEFRAC * 2)
#endif
#ifndef MCL_CACHEFRAC
#define MCL_CACHEFRAC (BASE_CACHEFRAC * 2)
#endif
#ifndef NMBJCLUSTERS
#define NMBJCLUSTERS (NMBCLUSTERS / 4)
#endif
#ifndef NMBUFS
#define NMBUFS (nmbclusters / 2 + maxfiles)
#endif
#define NMBCLUSTERS_MIN (NMBCLUSTERS / 2)
#define NMBJCLUSTERS_MIN (NMBJCLUSTERS / 2)
#define NMBUFS_MIN (NMBUFS / 2)
static void
tunable_mbinit(void *dummy __unused)
{
nmbclusters = NMBCLUSTERS;
TUNABLE_INT_FETCH("kern.ipc.nmbclusters", &nmbclusters);
mjclph_cachefrac = MJCLPH_CACHEFRAC;
TUNABLE_INT_FETCH("kern.ipc.mjclph_cachefrac", &mjclph_cachefrac);
mjcl_cachefrac = MJCL_CACHEFRAC;
TUNABLE_INT_FETCH("kern.ipc.mjcl_cachefrac", &mjcl_cachefrac);
mclph_cachefrac = MCLPH_CACHEFRAC;
TUNABLE_INT_FETCH("kern.ipc.mclph_cachefrac", &mclph_cachefrac);
mcl_cachefrac = MCL_CACHEFRAC;
TUNABLE_INT_FETCH("kern.ipc.mcl_cachefrac", &mcl_cachefrac);
if (mjclph_cachefrac < 3)
mjclph_cachefrac = 3;
if (mjcl_cachefrac < 3)
mjcl_cachefrac = 3;
if (mclph_cachefrac < 3)
mclph_cachefrac = 3;
if (mcl_cachefrac < 3)
mcl_cachefrac = 3;
nmbjclusters = NMBJCLUSTERS;
TUNABLE_INT_FETCH("kern.ipc.nmbjclusters", &nmbjclusters);
nmbufs = NMBUFS;
TUNABLE_INT_FETCH("kern.ipc.nmbufs", &nmbufs);
if (nmbufs < nmbclusters * 2)
nmbufs = nmbclusters * 2;
}
SYSINIT(tunable_mbinit, SI_BOOT1_TUNABLES, SI_ORDER_ANY,
tunable_mbinit, NULL);
static void
mbinclimit(int *limit, int inc, int minlim)
{
int new_limit;
lockmgr(&mbupdate_lk, LK_EXCLUSIVE);
new_limit = *limit + inc;
if (new_limit < minlim)
new_limit = minlim;
if (*limit != new_limit) {
*limit = new_limit;
mbupdatelimits();
}
lockmgr(&mbupdate_lk, LK_RELEASE);
}
static int
mbsetlimit(int *limit, int new_limit, int minlim)
{
if (new_limit < minlim)
return EINVAL;
lockmgr(&mbupdate_lk, LK_EXCLUSIVE);
mbinclimit(limit, new_limit - *limit, minlim);
lockmgr(&mbupdate_lk, LK_RELEASE);
return 0;
}
static int
sysctl_mblimit(SYSCTL_HANDLER_ARGS, int *limit, int minlim)
{
int error, value;
value = *limit;
error = sysctl_handle_int(oidp, &value, 0, req);
if (error || req->newptr == NULL)
return error;
return mbsetlimit(limit, value, minlim);
}
static int
sysctl_nmbclusters(SYSCTL_HANDLER_ARGS)
{
return sysctl_mblimit(oidp, arg1, arg2, req, &nmbclusters,
NMBCLUSTERS_MIN);
}
static int
sysctl_nmbjclusters(SYSCTL_HANDLER_ARGS)
{
return sysctl_mblimit(oidp, arg1, arg2, req, &nmbjclusters,
NMBJCLUSTERS_MIN);
}
static int
sysctl_nmbufs(SYSCTL_HANDLER_ARGS)
{
return sysctl_mblimit(oidp, arg1, arg2, req, &nmbufs, NMBUFS_MIN);
}
void
mcl_inclimit(int inc)
{
mbinclimit(&nmbclusters, inc, NMBCLUSTERS_MIN);
}
void
mjcl_inclimit(int inc)
{
mbinclimit(&nmbjclusters, inc, NMBJCLUSTERS_MIN);
}
void
mb_inclimit(int inc)
{
mbinclimit(&nmbufs, inc, NMBUFS_MIN);
}
static __inline boolean_t
mbuf_ctor(void *obj, void *private __unused, int ocflags __unused)
{
struct mbuf *m = obj;
m->m_next = NULL;
m->m_nextpkt = NULL;
m->m_data = m->m_dat;
m->m_flags = 0;
return (TRUE);
}
static boolean_t
mbufphdr_ctor(void *obj, void *private __unused, int ocflags __unused)
{
struct mbuf *m = obj;
m->m_next = NULL;
m->m_nextpkt = NULL;
m->m_data = m->m_pktdat;
m->m_flags = M_PKTHDR | M_PHCACHE;
m->m_pkthdr.rcvif = NULL;
SLIST_INIT(&m->m_pkthdr.tags);
m->m_pkthdr.csum_flags = 0;
m->m_pkthdr.fw_flags = 0;
return (TRUE);
}
static boolean_t
mclmeta_ctor(void *obj, void *private __unused, int ocflags)
{
struct mbcluster *cl = obj;
void *buf;
if (ocflags & M_NOWAIT)
buf = kmalloc(MCLBYTES, M_MBUFCL, M_NOWAIT | M_ZERO);
else
buf = kmalloc(MCLBYTES, M_MBUFCL, M_INTWAIT | M_ZERO);
if (buf == NULL)
return (FALSE);
cl->mcl_refs = 0;
cl->mcl_data = buf;
return (TRUE);
}
static boolean_t
mjclmeta_ctor(void *obj, void *private __unused, int ocflags)
{
struct mbcluster *cl = obj;
void *buf;
if (ocflags & M_NOWAIT)
buf = kmalloc(MJUMPAGESIZE, M_MBUFCL, M_NOWAIT | M_ZERO);
else
buf = kmalloc(MJUMPAGESIZE, M_MBUFCL, M_INTWAIT | M_ZERO);
if (buf == NULL)
return (FALSE);
cl->mcl_refs = 0;
cl->mcl_data = buf;
return (TRUE);
}
static void
mclmeta_dtor(void *obj, void *private __unused)
{
struct mbcluster *mcl = obj;
KKASSERT(mcl->mcl_refs == 0);
kfree(mcl->mcl_data, M_MBUFCL);
}
static void
linkjcluster(struct mbuf *m, struct mbcluster *cl, u_int size)
{
m->m_ext.ext_arg = cl;
m->m_ext.ext_buf = cl->mcl_data;
m->m_ext.ext_ref = m_mclref;
if (size != MCLBYTES)
m->m_ext.ext_free = m_mjclfree;
else
m->m_ext.ext_free = m_mclfree;
m->m_ext.ext_size = size;
atomic_add_int(&cl->mcl_refs, 1);
m->m_data = m->m_ext.ext_buf;
m->m_flags |= M_EXT | M_EXT_CLUSTER;
}
static void
linkcluster(struct mbuf *m, struct mbcluster *cl)
{
linkjcluster(m, cl, MCLBYTES);
}
static boolean_t
mbufphdrcluster_ctor(void *obj, void *private, int ocflags)
{
struct mbuf *m = obj;
struct mbcluster *cl;
mbufphdr_ctor(obj, private, ocflags);
cl = objcache_get(mclmeta_cache, ocflags);
if (cl == NULL) {
++mbstat[mycpu->gd_cpuid].m_drops;
return (FALSE);
}
m->m_flags |= M_CLCACHE;
linkcluster(m, cl);
return (TRUE);
}
static boolean_t
mbufphdrjcluster_ctor(void *obj, void *private, int ocflags)
{
struct mbuf *m = obj;
struct mbcluster *cl;
mbufphdr_ctor(obj, private, ocflags);
cl = objcache_get(mjclmeta_cache, ocflags);
if (cl == NULL) {
++mbstat[mycpu->gd_cpuid].m_drops;
return (FALSE);
}
m->m_flags |= M_CLCACHE;
linkjcluster(m, cl, MJUMPAGESIZE);
return (TRUE);
}
static boolean_t
mbufcluster_ctor(void *obj, void *private, int ocflags)
{
struct mbuf *m = obj;
struct mbcluster *cl;
mbuf_ctor(obj, private, ocflags);
cl = objcache_get(mclmeta_cache, ocflags);
if (cl == NULL) {
++mbstat[mycpu->gd_cpuid].m_drops;
return (FALSE);
}
m->m_flags |= M_CLCACHE;
linkcluster(m, cl);
return (TRUE);
}
static boolean_t
mbufjcluster_ctor(void *obj, void *private, int ocflags)
{
struct mbuf *m = obj;
struct mbcluster *cl;
mbuf_ctor(obj, private, ocflags);
cl = objcache_get(mjclmeta_cache, ocflags);
if (cl == NULL) {
++mbstat[mycpu->gd_cpuid].m_drops;
return (FALSE);
}
m->m_flags |= M_CLCACHE;
linkjcluster(m, cl, MJUMPAGESIZE);
return (TRUE);
}
static void
mbufcluster_dtor(void *obj, void *private)
{
struct mbuf *m = obj;
struct mbcluster *mcl;
if (m->m_flags & M_EXT) {
KKASSERT((m->m_flags & M_EXT_CLUSTER) != 0);
mcl = m->m_ext.ext_arg;
KKASSERT(mcl->mcl_refs == 1);
mcl->mcl_refs = 0;
if (m->m_flags & M_EXT && m->m_ext.ext_size != MCLBYTES)
objcache_put(mjclmeta_cache, mcl);
else
objcache_put(mclmeta_cache, mcl);
}
}
struct objcache_malloc_args mbuf_malloc_args = { MSIZE, M_MBUF };
struct objcache_malloc_args mclmeta_malloc_args =
{ sizeof(struct mbcluster), M_MCLMETA };
static void
mbinit(void *dummy __unused)
{
int limit, mb_limit, cl_limit, ncl_limit, jcl_limit, i;
for (i = 0; i < ncpus; i++) {
mbstat[i].m_msize = MSIZE;
mbstat[i].m_mclbytes = MCLBYTES;
mbstat[i].m_mjumpagesize = MJUMPAGESIZE;
mbstat[i].m_minclsize = MINCLSIZE;
mbstat[i].m_mlen = MLEN;
mbstat[i].m_mhlen = MHLEN;
}
mb_limit = cl_limit = 0;
limit = nmbufs;
mbuf_cache = objcache_create("mbuf",
limit, nmbufs / BASE_CACHEFRAC,
mbuf_ctor, NULL, NULL,
objcache_malloc_alloc, objcache_malloc_free, &mbuf_malloc_args);
mb_limit += limit;
limit = nmbufs;
mbufphdr_cache = objcache_create("mbuf pkthdr",
limit, nmbufs / BASE_CACHEFRAC,
mbufphdr_ctor, NULL, NULL,
objcache_malloc_alloc, objcache_malloc_free, &mbuf_malloc_args);
mb_limit += limit;
ncl_limit = nmbclusters;
mclmeta_cache = objcache_create("mbuf cluster",
ncl_limit, nmbclusters / BASE_CACHEFRAC,
mclmeta_ctor, mclmeta_dtor, NULL,
objcache_malloc_alloc, objcache_malloc_free, &mclmeta_malloc_args);
cl_limit += ncl_limit;
jcl_limit = nmbjclusters;
mjclmeta_cache = objcache_create("mbuf jcluster",
jcl_limit, nmbjclusters / BASE_CACHEFRAC,
mjclmeta_ctor, mclmeta_dtor, NULL,
objcache_malloc_alloc, objcache_malloc_free, &mclmeta_malloc_args);
cl_limit += jcl_limit;
limit = nmbclusters;
mbufcluster_cache = objcache_create("mbuf+cl",
limit, nmbclusters / mcl_cachefrac,
mbufcluster_ctor, mbufcluster_dtor, NULL,
objcache_malloc_alloc, objcache_malloc_free, &mbuf_malloc_args);
mb_limit += limit;
limit = nmbclusters;
mbufphdrcluster_cache = objcache_create("mbuf pkthdr+cl",
limit, nmbclusters / mclph_cachefrac,
mbufphdrcluster_ctor, mbufcluster_dtor, NULL,
objcache_malloc_alloc, objcache_malloc_free, &mbuf_malloc_args);
mb_limit += limit;
limit = nmbjclusters;
mbufjcluster_cache = objcache_create("mbuf+jcl",
limit, nmbjclusters / mjcl_cachefrac,
mbufjcluster_ctor, mbufcluster_dtor, NULL,
objcache_malloc_alloc, objcache_malloc_free, &mbuf_malloc_args);
mb_limit += limit;
limit = nmbjclusters;
mbufphdrjcluster_cache = objcache_create("mbuf pkthdr+jcl",
limit, nmbjclusters / mjclph_cachefrac,
mbufphdrjcluster_ctor, mbufcluster_dtor, NULL,
objcache_malloc_alloc, objcache_malloc_free, &mbuf_malloc_args);
mb_limit += limit;
cl_limit += cl_limit / 8;
kmalloc_raise_limit(mclmeta_malloc_args.mtype,
mclmeta_malloc_args.objsize * (size_t)cl_limit);
kmalloc_raise_limit(M_MBUFCL,
(MCLBYTES * (size_t)ncl_limit) +
(MJUMPAGESIZE * (size_t)jcl_limit));
mb_limit += mb_limit / 8;
kmalloc_raise_limit(mbuf_malloc_args.mtype,
mbuf_malloc_args.objsize * (size_t)mb_limit);
}
static void
mbupdatelimits(void)
{
int limit, mb_limit, cl_limit, ncl_limit, jcl_limit;
KASSERT(lockowned(&mbupdate_lk), ("mbupdate_lk is not held"));
mb_limit = cl_limit = 0;
limit = nmbufs;
objcache_set_cluster_limit(mbuf_cache, limit);
mb_limit += limit;
limit = nmbufs;
objcache_set_cluster_limit(mbufphdr_cache, limit);
mb_limit += limit;
ncl_limit = nmbclusters;
objcache_set_cluster_limit(mclmeta_cache, ncl_limit);
cl_limit += ncl_limit;
jcl_limit = nmbjclusters;
objcache_set_cluster_limit(mjclmeta_cache, jcl_limit);
cl_limit += jcl_limit;
limit = nmbclusters;
objcache_set_cluster_limit(mbufcluster_cache, limit);
mb_limit += limit;
limit = nmbclusters;
objcache_set_cluster_limit(mbufphdrcluster_cache, limit);
mb_limit += limit;
limit = nmbjclusters;
objcache_set_cluster_limit(mbufjcluster_cache, limit);
mb_limit += limit;
limit = nmbjclusters;
objcache_set_cluster_limit(mbufphdrjcluster_cache, limit);
mb_limit += limit;
cl_limit += cl_limit / 8;
kmalloc_raise_limit(mclmeta_malloc_args.mtype,
mclmeta_malloc_args.objsize * (size_t)cl_limit);
kmalloc_raise_limit(M_MBUFCL,
(MCLBYTES * (size_t)ncl_limit) +
(MJUMPAGESIZE * (size_t)jcl_limit));
mb_limit += mb_limit / 8;
kmalloc_raise_limit(mbuf_malloc_args.mtype,
mbuf_malloc_args.objsize * (size_t)mb_limit);
}
int
m_sharecount(struct mbuf *m)
{
switch (m->m_flags & (M_EXT | M_EXT_CLUSTER)) {
case 0:
return (0);
case M_EXT:
return (99);
case M_EXT | M_EXT_CLUSTER:
return (((struct mbcluster *)m->m_ext.ext_arg)->mcl_refs);
}
return (0);
}
void
m_chtype(struct mbuf *m, int type)
{
struct globaldata *gd = mycpu;
++mbtypes[gd->gd_cpuid].stats[type];
--mbtypes[gd->gd_cpuid].stats[m->m_type];
m->m_type = type;
}
static void
m_reclaim(void)
{
struct domain *dp;
struct protosw *pr;
kprintf("Debug: m_reclaim() called\n");
SLIST_FOREACH(dp, &domains, dom_next) {
for (pr = dp->dom_protosw; pr < dp->dom_protoswNPROTOSW; pr++) {
if (pr->pr_drain)
(*pr->pr_drain)();
}
}
++mbstat[mycpu->gd_cpuid].m_drain;
}
static __inline void
updatestats(struct mbuf *m, int type)
{
struct globaldata *gd = mycpu;
m->m_type = type;
mbuftrack(m);
#ifdef MBUF_DEBUG
KASSERT(m->m_next == NULL, ("mbuf %p: bad m_next in get", m));
KASSERT(m->m_nextpkt == NULL, ("mbuf %p: bad m_nextpkt in get", m));
#endif
++mbtypes[gd->gd_cpuid].stats[type];
++mbstat[gd->gd_cpuid].m_mbufs;
}
struct mbuf *
m_get(int how, int type)
{
struct mbuf *m;
int ntries = 0;
int ocf = MB_OCFLAG(how);
retryonce:
m = objcache_get(mbuf_cache, ocf);
if (m == NULL) {
if ((ocf & M_WAITOK) && ntries++ == 0) {
struct objcache *reclaimlist[] = {
mbufphdr_cache,
mbufcluster_cache,
mbufphdrcluster_cache,
mbufjcluster_cache,
mbufphdrjcluster_cache
};
const int nreclaims = NELEM(reclaimlist);
if (!objcache_reclaimlist(reclaimlist, nreclaims))
m_reclaim();
goto retryonce;
}
++mbstat[mycpu->gd_cpuid].m_drops;
return (NULL);
}
#ifdef MBUF_DEBUG
KASSERT(m->m_data == m->m_dat, ("mbuf %p: bad m_data in get", m));
#endif
m->m_len = 0;
updatestats(m, type);
return (m);
}
struct mbuf *
m_gethdr(int how, int type)
{
struct mbuf *m;
int ocf = MB_OCFLAG(how);
int ntries = 0;
retryonce:
m = objcache_get(mbufphdr_cache, ocf);
if (m == NULL) {
if ((ocf & M_WAITOK) && ntries++ == 0) {
struct objcache *reclaimlist[] = {
mbuf_cache,
mbufcluster_cache, mbufphdrcluster_cache,
mbufjcluster_cache, mbufphdrjcluster_cache
};
const int nreclaims = NELEM(reclaimlist);
if (!objcache_reclaimlist(reclaimlist, nreclaims))
m_reclaim();
goto retryonce;
}
++mbstat[mycpu->gd_cpuid].m_drops;
return (NULL);
}
#ifdef MBUF_DEBUG
KASSERT(m->m_data == m->m_pktdat, ("mbuf %p: bad m_data in get", m));
#endif
m->m_len = 0;
m->m_pkthdr.len = 0;
updatestats(m, type);
return (m);
}
struct mbuf *
m_getclr(int how, int type)
{
struct mbuf *m;
m = m_get(how, type);
if (m != NULL)
bzero(m->m_data, MLEN);
return (m);
}
static struct mbuf *
m_getcl_cache(int how, short type, int flags, struct objcache *mbclc,
struct objcache *mbphclc, u_long *cl_stats)
{
struct mbuf *m = NULL;
int ocflags = MB_OCFLAG(how);
int ntries = 0;
retryonce:
if (flags & M_PKTHDR)
m = objcache_get(mbphclc, ocflags);
else
m = objcache_get(mbclc, ocflags);
if (m == NULL) {
if ((ocflags & M_WAITOK) && ntries++ == 0) {
struct objcache *reclaimlist[1];
if (flags & M_PKTHDR)
reclaimlist[0] = mbclc;
else
reclaimlist[0] = mbphclc;
if (!objcache_reclaimlist(reclaimlist, 1))
m_reclaim();
goto retryonce;
}
++mbstat[mycpu->gd_cpuid].m_drops;
return (NULL);
}
#ifdef MBUF_DEBUG
KASSERT(m->m_data == m->m_ext.ext_buf,
("mbuf %p: bad m_data in get", m));
#endif
m->m_type = type;
m->m_len = 0;
m->m_pkthdr.len = 0;
mbuftrack(m);
++mbtypes[mycpu->gd_cpuid].stats[type];
++(*cl_stats);
return (m);
}
struct mbuf *
m_getjcl(int how, short type, int flags, size_t size)
{
struct objcache *mbclc, *mbphclc;
u_long *cl_stats;
switch (size) {
case MCLBYTES:
mbclc = mbufcluster_cache;
mbphclc = mbufphdrcluster_cache;
cl_stats = &mbstat[mycpu->gd_cpuid].m_clusters;
break;
default:
mbclc = mbufjcluster_cache;
mbphclc = mbufphdrjcluster_cache;
cl_stats = &mbstat[mycpu->gd_cpuid].m_jclusters;
break;
}
return m_getcl_cache(how, type, flags, mbclc, mbphclc, cl_stats);
}
struct mbuf *
m_getcl(int how, short type, int flags)
{
return m_getcl_cache(how, type, flags,
mbufcluster_cache, mbufphdrcluster_cache,
&mbstat[mycpu->gd_cpuid].m_clusters);
}
struct mbuf *
m_getc(int len, int how, int type)
{
struct mbuf *n, *nfirst = NULL, **ntail = &nfirst;
int nsize;
while (len > 0) {
n = m_getl(len, how, type, 0, &nsize);
if (n == NULL)
goto failed;
n->m_len = 0;
*ntail = n;
ntail = &n->m_next;
len -= nsize;
}
return (nfirst);
failed:
m_freem(nfirst);
return (NULL);
}
struct mbuf *
m_getm(struct mbuf *m0, int len, int type, int how)
{
struct mbuf *nfirst;
nfirst = m_getc(len, how, type);
if (m0 != NULL) {
m_last(m0)->m_next = nfirst;
return (m0);
}
return (nfirst);
}
void
m_mclget(struct mbuf *m, int how)
{
struct mbcluster *mcl;
KKASSERT((m->m_flags & M_EXT) == 0);
mcl = objcache_get(mclmeta_cache, MB_OCFLAG(how));
if (mcl != NULL) {
linkcluster(m, mcl);
++mbstat[mycpu->gd_cpuid].m_clusters;
} else {
++mbstat[mycpu->gd_cpuid].m_drops;
}
}
static void
m_mclref(void *arg)
{
struct mbcluster *mcl = arg;
atomic_add_int(&mcl->mcl_refs, 1);
}
static void
m_mclfree(void *arg)
{
struct mbcluster *mcl = arg;
if (atomic_fetchadd_int(&mcl->mcl_refs, -1) == 1) {
--mbstat[mycpu->gd_cpuid].m_clusters;
objcache_put(mclmeta_cache, mcl);
}
}
static void
m_mjclfree(void *arg)
{
struct mbcluster *mcl = arg;
if (atomic_fetchadd_int(&mcl->mcl_refs, -1) == 1) {
--mbstat[mycpu->gd_cpuid].m_jclusters;
objcache_put(mjclmeta_cache, mcl);
}
}
struct mbuf *
#ifdef MBUF_DEBUG
_m_free(struct mbuf *m, const char *func)
#else
m_free(struct mbuf *m)
#endif
{
struct mbuf *n;
struct globaldata *gd = mycpu;
KASSERT(m->m_type != MT_FREE, ("freeing free mbuf %p", m));
KASSERT(M_TRAILINGSPACE(m) >= 0, ("overflowed mbuf %p", m));
--mbtypes[gd->gd_cpuid].stats[m->m_type];
n = m->m_next;
m->m_next = NULL;
mbufuntrack(m);
#ifdef MBUF_DEBUG
m->m_hdr.mh_lastfunc = func;
#endif
#ifdef notyet
KKASSERT(m->m_nextpkt == NULL);
#else
if (m->m_nextpkt != NULL) {
static int afewtimes = 10;
if (afewtimes-- > 0) {
kprintf("mfree: m->m_nextpkt != NULL\n");
print_backtrace(-1);
}
m->m_nextpkt = NULL;
}
#endif
if (m->m_flags & M_PKTHDR) {
m_tag_delete_chain(m);
}
m->m_flags &= (M_EXT | M_EXT_CLUSTER | M_CLCACHE | M_PHCACHE);
if (m->m_flags & M_PHCACHE) {
m->m_flags |= M_PKTHDR;
m->m_pkthdr.rcvif = NULL;
m->m_pkthdr.csum_flags = 0;
m->m_pkthdr.fw_flags = 0;
SLIST_INIT(&m->m_pkthdr.tags);
}
switch (m->m_flags & (M_CLCACHE | M_EXT | M_EXT_CLUSTER)) {
case M_CLCACHE | M_EXT | M_EXT_CLUSTER:
if (m_sharecount(m) == 1) {
m->m_data = m->m_ext.ext_buf;
if ((m->m_flags & M_EXT) &&
m->m_ext.ext_size != MCLBYTES) {
if (m->m_flags & M_PHCACHE)
objcache_put(mbufphdrjcluster_cache, m);
else
objcache_put(mbufjcluster_cache, m);
--mbstat[mycpu->gd_cpuid].m_jclusters;
} else {
if (m->m_flags & M_PHCACHE)
objcache_put(mbufphdrcluster_cache, m);
else
objcache_put(mbufcluster_cache, m);
--mbstat[mycpu->gd_cpuid].m_clusters;
}
} else {
m->m_ext.ext_free(m->m_ext.ext_arg);
m->m_flags &= ~(M_EXT | M_EXT_CLUSTER);
if (m->m_ext.ext_size == MCLBYTES) {
if (m->m_flags & M_PHCACHE)
objcache_dtor(mbufphdrcluster_cache, m);
else
objcache_dtor(mbufcluster_cache, m);
} else {
if (m->m_flags & M_PHCACHE)
objcache_dtor(mbufphdrjcluster_cache, m);
else
objcache_dtor(mbufjcluster_cache, m);
}
}
break;
case M_EXT | M_EXT_CLUSTER:
case M_EXT:
m->m_ext.ext_free(m->m_ext.ext_arg);
m->m_flags &= ~(M_EXT | M_EXT_CLUSTER);
case 0:
if (m->m_flags & M_PHCACHE) {
m->m_data = m->m_pktdat;
objcache_put(mbufphdr_cache, m);
} else {
m->m_data = m->m_dat;
objcache_put(mbuf_cache, m);
}
--mbstat[mycpu->gd_cpuid].m_mbufs;
break;
default:
if (!panicstr)
panic("bad mbuf flags %p %08x", m, m->m_flags);
break;
}
return (n);
}
#ifdef MBUF_DEBUG
void
_m_freem(struct mbuf *m, const char *func)
{
while (m)
m = _m_free(m, func);
}
#else
void
m_freem(struct mbuf *m)
{
while (m)
m = m_free(m);
}
#endif
void
m_extadd(struct mbuf *m, void *buf, u_int size, void (*reff)(void *),
void (*freef)(void *), void *arg)
{
m->m_ext.ext_arg = arg;
m->m_ext.ext_buf = buf;
m->m_ext.ext_ref = reff;
m->m_ext.ext_free = freef;
m->m_ext.ext_size = size;
reff(arg);
m->m_data = buf;
m->m_flags |= M_EXT;
}
struct mbuf *
m_prepend(struct mbuf *m, int len, int how)
{
struct mbuf *mn;
if (m->m_flags & M_PKTHDR)
mn = m_gethdr(how, m->m_type);
else
mn = m_get(how, m->m_type);
if (mn == NULL) {
m_freem(m);
return (NULL);
}
if (m->m_flags & M_PKTHDR)
M_MOVE_PKTHDR(mn, m);
mn->m_next = m;
m = mn;
if (len < MHLEN)
MH_ALIGN(m, len);
m->m_len = len;
return (m);
}
struct mbuf *
m_copym(const struct mbuf *m, int off0, int len, int wait)
{
struct mbuf *n, **np;
int off = off0;
struct mbuf *top;
int copyhdr = 0;
KASSERT(off >= 0, ("%s: negative off %d", __func__, off));
KASSERT(len >= 0, ("%s: negative len %d", __func__, len));
if (off == 0 && (m->m_flags & M_PKTHDR))
copyhdr = 1;
while (off > 0) {
KASSERT(m != NULL,
("%s: offset > size of mbuf chain", __func__));
if (off < m->m_len)
break;
off -= m->m_len;
m = m->m_next;
}
np = ⊤
top = NULL;
while (len > 0) {
if (m == NULL) {
KASSERT(len == M_COPYALL,
("%s: length > size of mbuf chain", __func__));
break;
}
if (copyhdr)
n = m_gethdr(wait, m->m_type);
else
n = m_get(wait, m->m_type);
*np = n;
if (n == NULL)
goto nospace;
if (copyhdr) {
if (!m_dup_pkthdr(n, m, wait))
goto nospace;
if (len == M_COPYALL)
n->m_pkthdr.len -= off0;
else
n->m_pkthdr.len = len;
copyhdr = 0;
}
n->m_len = min(len, m->m_len - off);
if (m->m_flags & M_EXT) {
KKASSERT((n->m_flags & M_EXT) == 0);
n->m_data = m->m_data + off;
m->m_ext.ext_ref(m->m_ext.ext_arg);
n->m_ext = m->m_ext;
n->m_flags |= m->m_flags & (M_EXT | M_EXT_CLUSTER);
} else {
bcopy(mtod(m, caddr_t) + off, mtod(n, caddr_t),
n->m_len);
}
if (len != M_COPYALL)
len -= n->m_len;
off = 0;
m = m->m_next;
np = &n->m_next;
}
if (top == NULL)
++mbstat[mycpu->gd_cpuid].m_mcfail;
return (top);
nospace:
m_freem(top);
++mbstat[mycpu->gd_cpuid].m_mcfail;
return (NULL);
}
struct mbuf *
m_copypacket(struct mbuf *m, int how)
{
struct mbuf *top, *n, *o;
n = m_gethdr(how, m->m_type);
top = n;
if (!n)
goto nospace;
if (!m_dup_pkthdr(n, m, how))
goto nospace;
n->m_len = m->m_len;
if (m->m_flags & M_EXT) {
KKASSERT((n->m_flags & M_EXT) == 0);
n->m_data = m->m_data;
m->m_ext.ext_ref(m->m_ext.ext_arg);
n->m_ext = m->m_ext;
n->m_flags |= m->m_flags & (M_EXT | M_EXT_CLUSTER);
} else {
n->m_data = n->m_pktdat + (m->m_data - m->m_pktdat);
bcopy(mtod(m, void *), mtod(n, void *), n->m_len);
}
m = m->m_next;
while (m) {
o = m_get(how, m->m_type);
if (!o)
goto nospace;
n->m_next = o;
n = n->m_next;
n->m_len = m->m_len;
if (m->m_flags & M_EXT) {
KKASSERT((n->m_flags & M_EXT) == 0);
n->m_data = m->m_data;
m->m_ext.ext_ref(m->m_ext.ext_arg);
n->m_ext = m->m_ext;
n->m_flags |= m->m_flags & (M_EXT | M_EXT_CLUSTER);
} else {
bcopy(mtod(m, void *), mtod(n, void *), n->m_len);
}
m = m->m_next;
}
return top;
nospace:
m_freem(top);
++mbstat[mycpu->gd_cpuid].m_mcfail;
return (NULL);
}
void
m_copydata(const struct mbuf *m, int off, int len, void *_cp)
{
caddr_t cp = _cp;
unsigned count;
KASSERT(off >= 0, ("%s: negative off %d", __func__, off));
KASSERT(len >= 0, ("%s: negative len %d", __func__, len));
while (off > 0) {
KASSERT(m != NULL,
("%s: offset > size of mbuf chain", __func__));
if (off < m->m_len)
break;
off -= m->m_len;
m = m->m_next;
}
while (len > 0) {
KASSERT(m != NULL,
("%s: length > size of mbuf chain", __func__));
count = min(m->m_len - off, len);
bcopy(mtod(m, caddr_t) + off, cp, count);
len -= count;
cp += count;
off = 0;
m = m->m_next;
}
}
struct mbuf *
m_dup(struct mbuf *m, int how)
{
struct mbuf **p, *n, *top = NULL;
int remain, moff, nsize, chunk;
if (m == NULL)
return (NULL);
KASSERT((m->m_flags & M_PKTHDR) != 0, ("%s: !PKTHDR", __func__));
remain = m->m_pkthdr.len;
moff = 0;
p = ⊤
while (remain > 0 || top == NULL) {
n = m_getl(remain, how, m->m_type, top == NULL ? M_PKTHDR : 0,
&nsize);
if (n == NULL)
goto nospace;
if (top == NULL)
if (!m_dup_pkthdr(n, m, how))
goto nospace0;
*p = n;
p = &n->m_next;
n->m_len = 0;
while (n->m_len < nsize && m != NULL) {
chunk = min(nsize - n->m_len, m->m_len - moff);
bcopy(m->m_data + moff, n->m_data + n->m_len, chunk);
moff += chunk;
n->m_len += chunk;
remain -= chunk;
if (moff == m->m_len) {
m = m->m_next;
moff = 0;
}
}
KASSERT((remain > 0 && m != NULL) || (remain == 0 && m == NULL),
("%s: bogus m_pkthdr.len", __func__));
}
return (top);
nospace:
m_freem(top);
nospace0:
++mbstat[mycpu->gd_cpuid].m_mcfail;
return (NULL);
}
struct mbuf *
m_dup_data(struct mbuf *m, int how)
{
struct mbuf **p, *n, *top = NULL;
int mlen, moff, chunk, gsize, nsize;
if (m == NULL)
return (NULL);
if (m->m_next || m->m_len > MLEN)
if (m->m_flags & M_EXT && m->m_ext.ext_size == MCLBYTES)
gsize = MCLBYTES;
else
gsize = MJUMPAGESIZE;
else
gsize = MLEN;
p = ⊤
n = NULL;
nsize = 0;
while (m) {
mlen = m->m_len;
moff = 0;
while (mlen) {
KKASSERT(m->m_type == MT_DATA);
if (n == NULL) {
n = m_getl(gsize, how, MT_DATA, 0, &nsize);
if (n == NULL)
goto nospace;
n->m_len = 0;
*p = n;
p = &n->m_next;
}
chunk = imin(mlen, nsize);
bcopy(m->m_data + moff, n->m_data + n->m_len, chunk);
mlen -= chunk;
moff += chunk;
n->m_len += chunk;
nsize -= chunk;
if (nsize == 0)
n = NULL;
}
m = m->m_next;
}
*p = NULL;
return(top);
nospace:
*p = NULL;
m_freem(top);
++mbstat[mycpu->gd_cpuid].m_mcfail;
return (NULL);
}
void
m_cat(struct mbuf *m, struct mbuf *n)
{
m = m_last(m);
while (n) {
if (m->m_flags & M_EXT ||
m->m_data + m->m_len + n->m_len >= &m->m_dat[MLEN]) {
m->m_next = n;
return;
}
bcopy(mtod(n, caddr_t), mtod(m, caddr_t) + m->m_len, n->m_len);
m->m_len += n->m_len;
n = m_free(n);
}
}
void
m_adj(struct mbuf *mp, int req_len)
{
struct mbuf *m;
int count, len = req_len;
if ((m = mp) == NULL)
return;
if (len >= 0) {
while (m != NULL && len > 0) {
if (m->m_len <= len) {
len -= m->m_len;
m->m_len = 0;
m = m->m_next;
} else {
m->m_len -= len;
m->m_data += len;
len = 0;
}
}
m = mp;
if (mp->m_flags & M_PKTHDR)
m->m_pkthdr.len -= (req_len - len);
} else {
len = -len;
count = 0;
for (;;) {
count += m->m_len;
if (m->m_next == NULL)
break;
m = m->m_next;
}
if (m->m_len >= len) {
m->m_len -= len;
if (mp->m_flags & M_PKTHDR)
mp->m_pkthdr.len -= len;
return;
}
count -= len;
if (count < 0)
count = 0;
m = mp;
if (m->m_flags & M_PKTHDR)
m->m_pkthdr.len = count;
for (; m; m = m->m_next) {
if (m->m_len >= count) {
m->m_len = count;
break;
}
count -= m->m_len;
}
while ((m = m->m_next) != NULL)
m->m_len = 0;
}
}
void
m_align(struct mbuf *m, int len)
{
int adjust;
if (m->m_flags & M_EXT)
adjust = m->m_ext.ext_size - len;
else if (m->m_flags & M_PKTHDR)
adjust = MHLEN - len;
else
adjust = MLEN - len;
m->m_data += rounddown2(adjust, sizeof(long));
}
struct mbuf *
m_unshare(struct mbuf *m0, int how)
{
struct mbuf *m, *mprev;
struct mbuf *n, *mfirst, *mlast;
int len, off;
mprev = NULL;
for (m = m0; m != NULL; m = mprev->m_next) {
if ((m->m_flags & M_EXT) == 0) {
if (mprev != NULL && (mprev->m_flags & M_EXT) &&
m->m_len <= M_TRAILINGSPACE(mprev)) {
memcpy(mtod(mprev, caddr_t) + mprev->m_len,
mtod(m, caddr_t), m->m_len);
mprev->m_len += m->m_len;
mprev->m_next = m->m_next;
m_free(m);
} else {
mprev = m;
}
continue;
}
if (M_WRITABLE(m)) {
mprev = m;
continue;
}
KASSERT(m->m_flags & M_EXT, ("m_flags 0x%x", m->m_flags));
if (mprev != NULL && (mprev->m_flags & M_EXT) &&
m->m_len <= M_TRAILINGSPACE(mprev)) {
memcpy(mtod(mprev, caddr_t) + mprev->m_len,
mtod(m, caddr_t), m->m_len);
mprev->m_len += m->m_len;
mprev->m_next = m->m_next;
m_free(m);
continue;
}
if (mprev == NULL && (m->m_flags & M_PKTHDR)) {
MGETHDR(n, how, m->m_type);
if (n == NULL) {
m_freem(m0);
return (NULL);
}
M_MOVE_PKTHDR(n, m);
MCLGET(n, how);
if ((n->m_flags & M_EXT) == 0) {
m_free(n);
m_freem(m0);
return (NULL);
}
} else {
n = m_getcl(how, m->m_type, m->m_flags);
if (n == NULL) {
m_freem(m0);
return (NULL);
}
}
len = m->m_len;
off = 0;
mfirst = n;
mlast = NULL;
for (;;) {
int cc = min(len, MCLBYTES);
memcpy(mtod(n, caddr_t), mtod(m, caddr_t) + off, cc);
n->m_len = cc;
if (mlast != NULL)
mlast->m_next = n;
mlast = n;
len -= cc;
if (len <= 0)
break;
off += cc;
n = m_getcl(how, m->m_type, m->m_flags);
if (n == NULL) {
m_freem(mfirst);
m_freem(m0);
return (NULL);
}
}
n->m_next = m->m_next;
if (mprev == NULL)
m0 = mfirst;
else
mprev->m_next = mfirst;
m_free(m);
mprev = mfirst;
}
return (m0);
}
struct mbuf *
m_pullup(struct mbuf *n, int len)
{
struct mbuf *m;
int count;
int space;
if (!(n->m_flags & M_EXT) &&
n->m_data + len < &n->m_dat[MLEN] &&
n->m_next) {
if (n->m_len >= len)
return (n);
m = n;
n = n->m_next;
len -= m->m_len;
} else {
if (len > MHLEN)
goto bad;
if (n->m_flags & M_PKTHDR)
m = m_gethdr(M_NOWAIT, n->m_type);
else
m = m_get(M_NOWAIT, n->m_type);
if (m == NULL)
goto bad;
m->m_len = 0;
if (n->m_flags & M_PKTHDR)
M_MOVE_PKTHDR(m, n);
}
space = &m->m_dat[MLEN] - (m->m_data + m->m_len);
do {
count = min(min(max(len, max_protohdr), space), n->m_len);
bcopy(mtod(n, caddr_t), mtod(m, caddr_t) + m->m_len, count);
len -= count;
m->m_len += count;
n->m_len -= count;
space -= count;
if (n->m_len)
n->m_data += count;
else
n = m_free(n);
} while (len > 0 && n);
if (len > 0) {
m_free(m);
goto bad;
}
m->m_next = n;
return (m);
bad:
m_freem(n);
++mbstat[mycpu->gd_cpuid].m_mcfail;
return (NULL);
}
struct mbuf *
m_split(struct mbuf *m0, int len0, int wait)
{
struct mbuf *m, *n;
unsigned len = len0, remain;
for (m = m0; m && len > m->m_len; m = m->m_next)
len -= m->m_len;
if (m == NULL)
return (NULL);
remain = m->m_len - len;
if (m0->m_flags & M_PKTHDR) {
n = m_gethdr(wait, m0->m_type);
if (n == NULL)
return (NULL);
n->m_pkthdr.rcvif = m0->m_pkthdr.rcvif;
n->m_pkthdr.len = m0->m_pkthdr.len - len0;
m0->m_pkthdr.len = len0;
if (m->m_flags & M_EXT)
goto extpacket;
if (remain > MHLEN) {
MH_ALIGN(n, 0);
n->m_next = m_split(m, len, wait);
if (n->m_next == NULL) {
m_free(n);
return (NULL);
} else {
n->m_len = 0;
return (n);
}
} else
MH_ALIGN(n, remain);
} else if (remain == 0) {
n = m->m_next;
m->m_next = NULL;
return (n);
} else {
n = m_get(wait, m->m_type);
if (n == NULL)
return (NULL);
M_ALIGN(n, remain);
}
extpacket:
if (m->m_flags & M_EXT) {
KKASSERT((n->m_flags & M_EXT) == 0);
n->m_data = m->m_data + len;
m->m_ext.ext_ref(m->m_ext.ext_arg);
n->m_ext = m->m_ext;
n->m_flags |= m->m_flags & (M_EXT | M_EXT_CLUSTER);
} else {
bcopy(mtod(m, caddr_t) + len, mtod(n, caddr_t), remain);
}
n->m_len = remain;
m->m_len = len;
n->m_next = m->m_next;
m->m_next = NULL;
return (n);
}
struct mbuf *
m_devget(void *_buf, int len, int offset __unused, struct ifnet *ifp)
{
struct mbuf *m, *mfirst = NULL, **mtail;
caddr_t buf = _buf;
int nsize, flags;
KKASSERT(offset == 0);
mtail = &mfirst;
flags = M_PKTHDR;
while (len > 0) {
m = m_getl(len, M_NOWAIT, MT_DATA, flags, &nsize);
if (m == NULL) {
m_freem(mfirst);
return (NULL);
}
m->m_len = min(len, nsize);
if (flags & M_PKTHDR) {
if (len + max_linkhdr <= nsize)
m->m_data += max_linkhdr;
m->m_pkthdr.rcvif = ifp;
m->m_pkthdr.len = len;
flags = 0;
}
bcopy(buf, m->m_data, m->m_len);
buf += m->m_len;
len -= m->m_len;
*mtail = m;
mtail = &m->m_next;
}
return (mfirst);
}
int
m_devpad(struct mbuf *m, int padto)
{
struct mbuf *last = NULL;
int padlen;
if (padto <= m->m_pkthdr.len)
return 0;
padlen = padto - m->m_pkthdr.len;
if (m->m_pkthdr.len == m->m_len && M_TRAILINGSPACE(m) >= padlen) {
last = m;
} else {
for (last = m; last->m_next != NULL; last = last->m_next)
;
if (M_TRAILINGSPACE(last) < padlen) {
struct mbuf *n;
MGET(n, M_NOWAIT, MT_DATA);
if (n == NULL)
return ENOBUFS;
n->m_len = 0;
last->m_next = n;
last = n;
}
}
KKASSERT(M_TRAILINGSPACE(last) >= padlen);
KKASSERT(M_WRITABLE(last));
bzero(mtod(last, caddr_t) + last->m_len, padlen);
last->m_len += padlen;
m->m_pkthdr.len += padlen;
return 0;
}
static __inline int
_m_copyback2(struct mbuf *m0, int off, int len, const void *_cp, int how,
boolean_t allow_alloc)
{
struct mbuf *m = m0, *n;
c_caddr_t cp = _cp;
int mlen, tlen, nsize, totlen = 0, error = ENOBUFS;
KASSERT(off >= 0, ("%s: negative off %d", __func__, off));
KASSERT(len >= 0, ("%s: negative len %d", __func__, len));
if (m0 == NULL)
return (0);
while (off > m->m_len) {
if (m->m_next == NULL && (tlen = M_TRAILINGSPACE(m)) > 0) {
mlen = min(off - m->m_len, tlen);
bzero(mtod(m, caddr_t) + m->m_len, mlen);
m->m_len += mlen;
}
off -= m->m_len;
totlen += m->m_len;
if (m->m_next == NULL) {
if (!allow_alloc)
goto out;
n = m_getl(off + len, how, m->m_type, 0, &nsize);
if (n == NULL)
goto out;
n->m_len = min(nsize, off + len);
bzero(mtod(n, void *), n->m_len);
m->m_next = n;
}
m = m->m_next;
}
while (len > 0) {
if (m->m_next == NULL &&
m->m_len < off + len &&
(tlen = M_TRAILINGSPACE(m)) > 0) {
m->m_len += min(off + len - m->m_len, tlen);
}
mlen = min(m->m_len - off, len);
bcopy(cp, mtod(m, caddr_t) + off, mlen);
off = 0;
cp += mlen;
len -= mlen;
totlen += mlen + off;
if (len == 0)
break;
if (m->m_next == NULL) {
if (!allow_alloc)
goto out;
n = m_getl(len, how, m->m_type, 0, &nsize);
if (n == NULL)
goto out;
n->m_len = min(nsize, len);
m->m_next = n;
}
m = m->m_next;
}
error = 0;
out:
if ((m0->m_flags & M_PKTHDR) && (m0->m_pkthdr.len < totlen))
m0->m_pkthdr.len = totlen;
return (error);
}
int
m_copyback2(struct mbuf *m0, int off, int len, const void *cp, int how)
{
return _m_copyback2(m0, off, len, cp, how, TRUE);
}
void
m_copyback(struct mbuf *m0, int off, int len, const void *cp)
{
if (_m_copyback2(m0, off, len, cp, 0, FALSE) != 0) {
kprintf("%s: unexpected mbuf expansion required, "
"code path needs to be fixed:\n", __func__);
print_backtrace(8);
}
}
int
m_append(struct mbuf *m0, int len, const void *_cp)
{
struct mbuf *m, *n;
c_caddr_t cp = _cp;
int remainder, space;
for (m = m0; m->m_next != NULL; m = m->m_next)
;
remainder = len;
space = M_TRAILINGSPACE(m);
if (space > 0) {
if (space > remainder)
space = remainder;
bcopy(cp, mtod(m, caddr_t) + m->m_len, space);
m->m_len += space;
cp += space, remainder -= space;
}
while (remainder > 0) {
n = m_get(M_NOWAIT, m->m_type);
if (n == NULL)
break;
n->m_len = min(MLEN, remainder);
bcopy(cp, mtod(n, caddr_t), n->m_len);
cp += n->m_len, remainder -= n->m_len;
m->m_next = n;
m = n;
}
if (m0->m_flags & M_PKTHDR)
m0->m_pkthdr.len += len - remainder;
return (remainder == 0);
}
int
m_apply(struct mbuf *m, int off, int len,
int (*f)(void *, void *, u_int), void *arg)
{
u_int count;
int rval;
KASSERT(off >= 0, ("%s: negative off %d", __func__, off));
KASSERT(len >= 0, ("%s: negative len %d", __func__, len));
while (off > 0) {
KASSERT(m != NULL,
("%s: offset > size of mbuf chain", __func__));
if (off < m->m_len)
break;
off -= m->m_len;
m = m->m_next;
}
while (len > 0) {
KASSERT(m != NULL,
("%s: offset > size of mbuf chain", __func__));
count = min(m->m_len - off, len);
rval = (*f)(arg, mtod(m, caddr_t) + off, count);
if (rval)
return (rval);
len -= count;
off = 0;
m = m->m_next;
}
return (0);
}
struct mbuf *
m_getptr(struct mbuf *m, int loc, int *off)
{
while (loc >= 0) {
if (m->m_len > loc) {
*off = loc;
return (m);
} else {
loc -= m->m_len;
if (m->m_next == NULL) {
if (loc == 0) {
*off = m->m_len;
return (m);
}
return (NULL);
}
m = m->m_next;
}
}
return (NULL);
}
void
m_print(const struct mbuf *m)
{
int len;
const struct mbuf *m2;
char *hexstr;
len = m->m_pkthdr.len;
m2 = m;
hexstr = kmalloc(HEX_NCPYLEN(len), M_TEMP, M_ZERO | M_WAITOK);
while (len) {
kprintf("%p %s\n", m2, hexncpy(m2->m_data, m2->m_len, hexstr,
HEX_NCPYLEN(m2->m_len), "-"));
len -= m2->m_len;
m2 = m2->m_next;
}
kfree(hexstr, M_TEMP);
return;
}
void
m_move_pkthdr(struct mbuf *to, struct mbuf *from)
{
KASSERT((to->m_flags & M_PKTHDR), ("%s: not packet header", __func__));
to->m_flags |= from->m_flags & M_COPYFLAGS;
to->m_pkthdr = from->m_pkthdr;
SLIST_INIT(&from->m_pkthdr.tags);
}
int
m_dup_pkthdr(struct mbuf *to, const struct mbuf *from, int how)
{
KASSERT((to->m_flags & M_PKTHDR), ("%s: not packet header", __func__));
to->m_flags = (from->m_flags & M_COPYFLAGS) |
(to->m_flags & ~M_COPYFLAGS);
to->m_pkthdr = from->m_pkthdr;
SLIST_INIT(&to->m_pkthdr.tags);
return (m_tag_copy_chain(to, from, how));
}
struct mbuf *
m_defrag(struct mbuf *m0, int how)
{
struct mbuf *m_new;
if ((m_new = m_defrag_nofree(m0, how)) == NULL)
return (NULL);
if (m_new != m0)
m_freem(m0);
return (m_new);
}
struct mbuf *
m_defrag_nofree(struct mbuf *m0, int how)
{
struct mbuf *m_new = NULL, *m_final = NULL;
int progress = 0, length, nsize;
if (!(m0->m_flags & M_PKTHDR))
return (m0);
#ifdef MBUF_STRESS_TEST
if (m_defragrandomfailures) {
int temp = karc4random() & 0xff;
if (temp == 0xba)
goto nospace;
}
#endif
m_final = m_getl(m0->m_pkthdr.len, how, MT_DATA, M_PKTHDR, &nsize);
if (m_final == NULL)
goto nospace;
m_final->m_len = 0;
if (m_dup_pkthdr(m_final, m0, how) == 0)
goto nospace;
m_new = m_final;
while (progress < m0->m_pkthdr.len) {
length = m0->m_pkthdr.len - progress;
if (length > MCLBYTES)
length = MCLBYTES;
if (m_new == NULL) {
m_new = m_getl(length, how, MT_DATA, 0, &nsize);
if (m_new == NULL)
goto nospace;
}
m_copydata(m0, progress, length, mtod(m_new, void *));
progress += length;
m_new->m_len = length;
if (m_new != m_final)
m_cat(m_final, m_new);
m_new = NULL;
}
if (m0->m_next == NULL)
m_defraguseless++;
m_defragpackets++;
m_defragbytes += m_final->m_pkthdr.len;
return (m_final);
nospace:
m_defragfailure++;
if (m_new)
m_free(m_new);
m_freem(m_final);
return (NULL);
}
struct mbuf *
m_uiomove(struct uio *uio)
{
struct mbuf *m;
struct mbuf *head = NULL;
struct mbuf **mp = &head;
int flags = M_PKTHDR;
int nsize;
int error;
int resid;
do {
if (uio->uio_resid > INT_MAX)
resid = INT_MAX;
else
resid = (int)uio->uio_resid;
m = m_getl(resid, M_WAITOK, MT_DATA, flags, &nsize);
if (flags) {
m->m_pkthdr.len = 0;
if (resid < MHLEN)
MH_ALIGN(m, resid);
flags = 0;
}
m->m_len = imin(nsize, resid);
error = uiomove(mtod(m, caddr_t), m->m_len, uio);
if (error) {
m_free(m);
goto failed;
}
*mp = m;
mp = &m->m_next;
head->m_pkthdr.len += m->m_len;
} while (uio->uio_resid > 0);
return (head);
failed:
m_freem(head);
return (NULL);
}
struct mbuf *
m_last(struct mbuf *m)
{
while (m->m_next)
m = m->m_next;
return (m);
}
u_int
m_lengthm(struct mbuf *m, struct mbuf **lastm)
{
u_int len = 0;
struct mbuf *prev = m;
while (m) {
len += m->m_len;
prev = m;
m = m->m_next;
}
if (lastm != NULL)
*lastm = prev;
return (len);
}
u_int
m_countm(struct mbuf *m, struct mbuf **lastm, u_int *pmbcnt)
{
u_int len = 0, mbcnt = 0;
struct mbuf *prev = m;
while (m) {
len += m->m_len;
mbcnt += MSIZE;
if (m->m_flags & M_EXT)
mbcnt += m->m_ext.ext_size;
prev = m;
m = m->m_next;
}
if (lastm != NULL)
*lastm = prev;
*pmbcnt = mbcnt;
return (len);
}