/*
 * 5799-WZQ (C) COPYRIGHT = NONE
 * LICENSED MATERIALS - PROPERTY OF IBM
 */
/* $Header:uipc_mbuf.c 12.0$ */
/* $ACIS:uipc_mbuf.c 12.0$ */
/* $Source: /ibm/acis/usr/sys/sys/RCS/uipc_mbuf.c,v $ */

#if !defined(lint) && !defined(NO_RCS_HDRS)
static char *rcsid = "$Header:uipc_mbuf.c 12.0$";
#endif

/*
 * Copyright (c) 1982, 1986 Regents of the University of California.
 * All rights reserved.  The Berkeley software License Agreement
 * specifies the terms and conditions for redistribution.
 *
 *	@(#)uipc_mbuf.c	7.1 (Berkeley) 6/5/86
 */

#include "../machine/pte.h"

#include "param.h"
#include "dir.h"
#ifdef VFS
#include "systm.h"
#endif VFS
#include "user.h"
#include "proc.h"
#include "cmap.h"
#include "map.h"
#include "mbuf.h"
#include "vm.h"
#include "kernel.h"
#ifdef VFS
#include "socketvar.h"
#endif VFS

#ifdef ibm032
extern struct pte Mbmap[];	/* page tables to map Netutl */
#endif

#if	defined(ibm370)
struct pte Mbmap[NMBCLUSTERS];
struct mbuf mbutl[NMBCLUSTERS*MB_CLBYTES/sizeof (struct mbuf)];
#endif	/* defined(ibm370) */

#if	defined(ibm370)
/*
 * Note that what used to be CL{BYTES,SIZE,SHIFT} has been changed
 * to MB_CL{BYTES,SIZE,SHIFT}.
 *
 * This corresponds to a change in mbuf.h.
 */
#endif	/* defined(ibm370) */

#ifdef	ibm370
/* This routine deallocates some number of clusters (or mbufs).
 * The code is essentially stolen from m_clalloc().
 *
 * Must be called at splimp
 */

static
mb_to_free_list(m, m2, how)
register struct mbuf *m;	/* first mbuf to free */
register struct mbuf *m2;	/* last mbuf to free */
int how;			/* MPG_{CLUSTERS,MBUFS) */
{
	switch (how) {

	case MPG_CLUSTERS:
		while (m <= m2) {
			m->m_off = 0;
			m->m_next = mclfree;
			mclfree = m;
			m += MB_CLBYTES / sizeof (*m);
			mbstat.m_clfree++;
			mbstat.m_clusters += 1;
		}
		break;

	case MPG_MBUFS:
		while (m <= m2) {
			m->m_off = 0;
			m->m_type = MT_DATA;
			mbstat.m_mtypes[MT_DATA]++;
			mbstat.m_mbufs++;
			(void) m_free(m);
			m++;
		}
		break;
	}
}
#endif	/* ibm370 */

mbinit()
{
	int s;

	s = splimp();
#ifdef	ibm370
	/* What we do is allocate almost everything
	 * as a cluster.  As we need more regular mbuf's,
	 * m_clalloc will get them for us by stealing free
	 * clusters.  The assumpion is that before we start
	 * to make heavy use of clusters we will have hit the
	 * mbuf high water mark.
	 */
	register struct mbuf *m, *m2;

	m = (struct mbuf *) (((char *)&mbutl[0]) - 1);
	m = dtom(m);	/* point to first 'mbuf' before us */
	m++;		/* m is now THE FIRST good mbuf in mbutl */

	m2 = (struct mbuf *) ((char *)&mbutl[0] - 1);
	m2 = (struct mbuf *) ((int)m2&~MB_CLOFSET);
	m2 += NMBPCL;	/* m2 is now THE FIRST good cluster in mbutl */

	mb_to_free_list(m, m2-1, MPG_MBUFS);

	m = m2;		/* m is now THE FIRST good cluster */

	m2 = (struct mbuf *) (((char *)&mbutl[0]) + sizeof mbutl);
	m2 = (struct mbuf *) ((int)m2&~MB_CLOFSET);
	m2 -= NMBPCL;
			/* m2 is now THE LAST cluster in mbutl */

	mb_to_free_list(m, m2, MPG_CLUSTERS);

	m = m2 + NMBPCL;	/* m is now first mbuf after last cluster */

	m2 = (struct mbuf *) (((char *)&mbutl[0]) + sizeof mbutl);
	m2 = dtom(m2);	/* point to the first 'mbuf' AFTER us */

	mb_to_free_list(m, m2-1, MPG_MBUFS);

	splx(s);
	return;

#else	/* ibm370 */
	if (m_clalloc(4096/MB_CLBYTES, MPG_MBUFS, M_DONTWAIT) == 0)
		goto bad;
	if (m_clalloc(8*4096/MB_CLBYTES, MPG_CLUSTERS, M_DONTWAIT) == 0)
		goto bad;
	splx(s);
	return;
#endif	/* ibm370 */
bad:
	panic("mbinit");
}

/*
 * Must be called at splimp.
 */
caddr_t
m_clalloc(ncl, how, canwait)
	register int ncl;
	int how;
	int canwait;
{
#ifdef	ibm370			/* No clusters yet... */
	register struct mbuf *m;

	if (how == MPG_CLUSTERS) {	/* we don't do this yet */
		return(0);
	} else if (how != MPG_MBUFS) {
		return(0);		/* (keep two cases separate) */
	}
	/* So, we assume we need some mbufs.  The process is to allocate
	 * a cluster, and turn it into mbufs.
	 */

	while (ncl--) {
		MCLGET(m, M_DONTWAIT);
		if (m == 0) {
			return(0);	/* failure */
		}
		mb_to_free_list(m, m+NMBPCL-1, MPG_MBUFS);
	}
	return((caddr_t)1);

#else	/* ibm370 */
	int npg, mbx;
	register struct mbuf *m;
	register int i;

	npg = ncl * MB_CLSIZE;
	mbx = rmalloc(mbmap, (long)npg);
	if (mbx == 0) {
		if (canwait == M_WAIT)
			panic("out of mbufs: map full");
		return (0);
	}
	m = cltom(mbx / MB_CLSIZE);
	if (memall(&Mbmap[mbx], npg, proc, CSYS) == 0) {
		rmfree(mbmap, (long)npg, (long)mbx);
		return (0);
	}
	vmaccess(&Mbmap[mbx], (caddr_t)m, npg);
	switch (how) {

	case MPG_CLUSTERS:
		for (i = 0; i < ncl; i++) {
			m->m_off = 0;
			m->m_next = mclfree;
			mclfree = m;
			m += MB_CLBYTES / sizeof (*m);
			mbstat.m_clfree++;
		}
		mbstat.m_clusters += ncl;
		break;

	case MPG_MBUFS:
		for (i = ncl * MB_CLBYTES / sizeof (*m); i > 0; i--) {
			m->m_off = 0;
			m->m_type = MT_DATA;
			mbstat.m_mtypes[MT_DATA]++;
			mbstat.m_mbufs++;
			(void) m_free(m);
			m++;
		}
		break;
	}
	return ((caddr_t)m);
#endif	/* ibm370 */
}

m_pgfree(addr, n)
	caddr_t addr;
	int n;
{

#ifdef lint
	addr = addr; n = n;
#endif
}

/*
#ifdef VFS
 * Must be called at splimp.  The routine always returns
 * without sleeping.  Callers are themselves responsible for
 * sleeping when resources aren't available.  Returns 1
 * upon success, 0 otherwise.
#endif VFS
 */

#ifndef VFS
m_expand(canwait)
	int canwait;
#else !VFS
m_expand()
#endif !VFS
{
#ifndef VFS
	if (m_clalloc(1, MPG_MBUFS, canwait) == 0)
#else !VFS
	/*
	 * Always return immediately.  
	 */
	if (m_clalloc(1, MPG_MBUFS, M_DONTWAIT) == 0)
#endif !VFS
		goto steal;
	return (1);
steal:
	/* should ask protocols to free code */
#ifndef VFS
	return (0);
#else !VFS
	return (mfree != NULL);
#endif !VFS
}

/* NEED SOME WAY TO RELEASE SPACE */

/*
 * Space allocation routines.
 * These are also available as macros
 * for critical paths.
 */
struct mbuf *
m_get(canwait, type)
	int canwait, type;
{
	register struct mbuf *m;

	MGET(m, canwait, type);
	return (m);
}

struct mbuf *
m_getclr(canwait, type)
	int canwait, type;
{
	register struct mbuf *m;

	MGET(m, canwait, type);
	if (m == 0)
		return (0);
	bzero(mtod(m, caddr_t), MLEN);
	return (m);
}

#ifdef VFS
/*
 * Generic mbuf unallocator
 * frees the first mbuf in the chain
 * and returns the rest of the chain
 * (compare with m_freem())
 */
#endif VFS

struct mbuf *
m_free(m)
	struct mbuf *m;
{
	register struct mbuf *n;

	MFREE(m, n);
	return (n);
}

/*
 * Get more mbufs; called from MGET macro if mfree list is empty.
 * Must be called at splimp.
 */
/*ARGSUSED*/
struct mbuf *
m_more(canwait, type)
	int canwait, type;
{
	register struct mbuf *m;

#ifndef VFS
	while (m_expand(canwait) == 0) {
#else !VFS
	/*
	 * If we can afford to wait for storage to appear,
	 * we sleep after returning from m_expand, instead
	 * of counting on m_expand to sleep itself.
	 */
	while (m_expand() == 0) {
#endif !VFS
		if (canwait == M_WAIT) {
			m_want++;
			sleep((caddr_t)&mfree, PZERO - 1);
		} else {
			mbstat.m_drops++;
			return (NULL);
		}
	}
#define m_more(x,y) (panic("m_more"), (struct mbuf *)0)
	MGET(m, canwait, type);
#undef m_more
	return (m);
}

#ifdef VFS
/*
 * Generic mbuf unallocator
 * frees the entire mbuf chain
 * (compare with  m_free())
 */
#endif  VFS
m_freem(m)
	register struct mbuf *m;
{
	register struct mbuf *n;
	register int s;

	if (m == NULL)
		return;
	s = splimp();
	do {
		MFREE(m, n);
	} while (m = n);
	splx(s);
}

/*
 * Mbuffer utility routines.
 */

/*
 * Make a copy of an mbuf chain starting "off" bytes from the beginning,
 * continuing for "len" bytes.  If len is M_COPYALL, copy to end of mbuf.
 * Should get M_WAIT/M_DONTWAIT from caller.
 */
struct mbuf *
m_copy(m, off, len)
	register struct mbuf *m;
	int off;
	register int len;
{
	register struct mbuf *n, **np;
#ifdef VFS
	struct mbuf *top;
#else VFS
	struct mbuf *top, *p;
#endif VFS

	if (len == 0)
		return (0);
	if (off < 0 || len < 0)
		panic("m_copy");
	while (off > 0) {
		if (m == 0)
			panic("m_copy");
		if (off < m->m_len)
			break;
		off -= m->m_len;
		m = m->m_next;
	}
	np = &top;
	top = 0;
	while (len > 0) {
		if (m == 0) {
			if (len != M_COPYALL)
				panic("m_copy");
			break;
		}
		MGET(n, M_DONTWAIT, m->m_type);
		*np = n;
		if (n == 0)
			goto nospace;
		n->m_len = MIN(len, m->m_len - off);
#ifdef VFS
		if (m->m_off > MMAXOFF && n->m_len > MLEN) {
			mcldup(m, n, off);
			n->m_off += off;
#else VFS
		if (m->m_off > MMAXOFF) {
			p = mtod(m, struct mbuf *);
			n->m_off = ((int)p - (int)n) + off;
			mclrefcnt[mtocl(p)]++;
#endif VFS
		} else
			bcopy(mtod(m, caddr_t)+off, mtod(n, caddr_t),
			    (unsigned)n->m_len);
		if (len != M_COPYALL)
			len -= n->m_len;
		off = 0;
		m = m->m_next;
		np = &n->m_next;
	}
	return (top);
nospace:
	m_freem(top);
	return (0);
}

/*
 * The mbuf chain, n, is appended to the end of m.   Where
 * possible, compaction is performed.
 */
m_cat(m, n)
	register struct mbuf *m, *n;
{
	while (m->m_next)
		m = m->m_next;
	while (n) {
		if (m->m_off >= MMAXOFF ||
		    m->m_off + m->m_len + n->m_len > MMAXOFF) {
			/* just join the two chains */
			m->m_next = n;
			return;
		}
		/* splat the data from one into the other */
		bcopy(mtod(n, caddr_t), mtod(m, caddr_t) + m->m_len,
		    (u_int)n->m_len);
		m->m_len += n->m_len;
		n = m_free(n);
	}
}

/*
 * The mbuf chain, m is adjusted in size  by  diff  bytes.
 * If  diff is non-negative, diff bytes are shaved off the
 * front of the mbuf chain.   If  diff  is  negative,  the
 * alteration  is  performed from back to front.  No space
 * is reclaimed in this operation, alterations are  accom-
 * plished  by  changing  the  m_len  and  m_off fields of
 * mbufs.
 */
m_adj(mp, len)
	struct mbuf *mp;
	register int len;
{
	register struct mbuf *m;
	register int count;

	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_off += len;
				break;
			}
		}
	} else {
		/*
		 * Trim from tail.  Scan the mbuf chain,
		 * calculating its length and finding the last mbuf.
		 * If the adjustment only affects this mbuf, then just
		 * adjust and return.  Otherwise, rescan and truncate
		 * after the remaining size.
		 */
		len = -len;
		count = 0;
		for (;;) {
			count += m->m_len;
			if (m->m_next == (struct mbuf *)0)
				break;
			m = m->m_next;
		}
		if (m->m_len >= len) {
			m->m_len -= len;
			return;
		}
		count -= len;
		/*
		 * Correct length for chain is "count".
		 * Find the mbuf with last data, adjust its length,
		 * and toss data from remaining mbufs on chain.
		 */
		for (m = mp; m; m = m->m_next) {
			if (m->m_len >= count) {
				m->m_len = count;
				break;
			}
			count -= m->m_len;
		}
		while (m = m->m_next)
			m->m_len = 0;
	}
}

/*
 * Rearange an mbuf chain so that len bytes are contiguous
 * and in the data area of an mbuf (so that mtod and dtom
 * will work for a structure of size len).  Returns the resulting
 * mbuf chain on success, frees it and returns null on failure.
 * If there is room, it will add up to MPULL_EXTRA bytes to the
 * contiguous region in an attempt to avoid being called next time.
 */
struct mbuf *
m_pullup(n, len)
	register struct mbuf *n;
	int len;
{
	register struct mbuf *m;
	register int count;
	int space;

	if (n->m_off + len <= MMAXOFF && n->m_next) {
		m = n;
		n = n->m_next;
		len -= m->m_len;
	} else {
		if (len > MLEN)
			goto bad;
		MGET(m, M_DONTWAIT, n->m_type);
		if (m == 0)
			goto bad;
		m->m_len = 0;
	}
	/* Calculate total room for data in this mbuf. */
	space = MMAXOFF - m->m_off;
	do {
		/*
		 * Determine amount to move from the next mbuf.  The inner
		 * MIN calculates the desired amount to move from the amount
		 * of room left in this mbuf and the overall remaining length
		 * desired.
		 */
		count = MIN(MIN(space - m->m_len, len + MPULL_EXTRA), n->m_len);
		bcopy(mtod(n, caddr_t), mtod(m, caddr_t)+m->m_len,
		  (unsigned)count);
		len -= count;
		m->m_len += count;
		n->m_len -= count;
		if (n->m_len)
			n->m_off += count;
		else
			n = m_free(n);
	} while (len > 0 && n);
	if (len > 0) {
		(void) m_free(m);
		goto bad;
	}
	m->m_next = n;
	return (m);
bad:
	m_freem(n);
	return (0);
}

#ifdef AFS
/*
 * Given an mbuf, allocate and attach a cluster mbuf to it.
 * Return 1 if successful, 0 otherwise.
 * NOTE: m->m_len is set to CLBYTES!
 */
mclget(m)
	register struct mbuf *m;
{
	int ms;
	register struct mbuf *p;

	ms = splimp();
	if (mclfree == 0)
		/* XXX need a way to  reclaim */
		(void) m_clalloc(1, MPG_CLUSTERS, 0);
	if (p = mclfree) {
		++mclrefcnt[mtocl(p)];
		mbstat.m_clfree--;
		mclfree = p->m_next;
		m->m_len = CLBYTES;
		m->m_off = (int)p - (int)m;
		m->m_cltype = 1;
	}
	(void) splx(ms);
	return (p ? 1 : 0);
}

/*
 * Copy an mbuf to the contiguous area pointed to by cp.
 * Skip <off> bytes and copy <len> bytes.
 * Returns the number of bytes not transferred.
 * The mbuf is NOT changed.
 */
int
m_cpytoc(m, off, len, cp)
	register struct mbuf *m;
	register int off, len;
	register caddr_t cp;
{
	register int ml;

	if (m == NULL || off < 0 || len < 0 || cp == NULL)
		panic("m_cpytoc");
	while (off && m)
		if (m->m_len <= off) {
			off -= m->m_len;
			m = m->m_next;
			continue;
		} else
			break;
	if (m == NULL)
		return (len);

	ml = imin(len, m->m_len - off);
	bcopy(mtod(m, caddr_t)+off, cp, (u_int)ml);
	cp += ml;
	len -= ml;
	m = m->m_next;

	while (len && m) {
		ml = m->m_len;
		bcopy(mtod(m, caddr_t), cp, (u_int)ml);
		cp += ml;
		len -= ml;
		m = m->m_next;
	}

	return (len);
}
#endif AFS

#ifdef VFS
/* Allocate a "funny" mbuf, that is, one whose data is owned by someone else */
struct mbuf *
mclgetx(fun, arg, addr, len, wait)
	int (*fun)(), arg, len, wait;
	caddr_t addr;
{
	register struct mbuf *m;

	MGET(m, wait, MT_DATA);
	if (m == 0)
		return (0);
	m->m_off = (int)addr - (int)m;
	m->m_len = len;
	m->m_cltype = MCL_LOANED;
	m->m_clfun = fun;
	m->m_clarg = arg;
	m->m_clswp = NULL;
	return (m);
}
/* Generic cluster mbuf unallocator -- invoked from within MFREE */
mclput(m)
	register struct mbuf *m;
{

	switch (m->m_cltype) {
	case MCL_STATIC:
		m = MTOCL(m); 
		MCLFREE(m); 
		break;

	case MCL_LOANED:
		(*m->m_clfun)(m->m_clarg);
		break;

	default:
		panic("mclput");
	}
}

/*
 * Deallocation routine for MCL_LOANED cluster mbufs
 * created by mcldup.
 */
static
buffree(arg)
	int arg;
{
	extern int kmem_free_intr();

	kmem_free_intr((caddr_t)arg, *(u_int *)arg);
}

/*
 * Generic cluster mbuf duplicator
 * which duplicates <m> into <n>.
 * If <m> is a regular cluster mbuf, mcldup simply
 * bumps the reference count and ignores <off>.
 * If <m> is a funny mbuf, mcldup allocates a chunck
 * kernel memory and makes a copy, starting at <off>.
 * XXX does not set the m_len field in <n>!
 */
mcldup(m, n, off)
	register struct mbuf *m, *n;
	int off;
{
	extern caddr_t kmem_alloc();
	register struct mbuf *p;
	register caddr_t copybuf;

	switch (m->m_cltype) {
	case MCL_STATIC:
		p = mtod(m, struct mbuf *);
		n->m_off = (int)p - (int)n;
		n->m_cltype = MCL_STATIC;
		mclrefcnt[mtocl(p)]++;
		break;
	case MCL_LOANED:
		copybuf = kmem_alloc((u_int)(n->m_len + sizeof (int)));
		* (int *) copybuf = n->m_len + sizeof (int);
		bcopy(mtod(m, caddr_t) + off, copybuf + sizeof (int),
		    (u_int)n->m_len);
		n->m_off = (int)copybuf + sizeof (int) - (int)n - off;
		n->m_cltype = MCL_LOANED;
		n->m_clfun = buffree;
		n->m_clarg = (int)copybuf;
		n->m_clswp = NULL;
		break;
	default:
		panic("mcldup");
	}
}
#endif VFS
