#include	"../h/param.h"
#include	"../h/systm.h"
#include	"../h/dir.h"
#include	"../h/user.h"
#include	"../h/tty.h"
#include	"../h/buf.h"
#include	"../h/conf.h"
#include	"../h/msg.h"


/*
 *   Message storage system.
 *	Manages 'buddy' system blocks of storage
 *	for data, but in contiguous blocks, unlike
 *	clists, which are scattered.
 */


/*
 *   Set up the arena with 2 allocated areas at the top and bottom,
 *   both consuming "no space".  These serve to delimit the searches,
 *   and make free block coercion somewhat easier.  The real allocatable
 *   memory is in a large chunk in the middle of the two allocated.
 */
msginit(arena, size)
register struct buddy *arena;
register int size;
{
	register struct buddy *rp;
	register struct buddy *p;

	if (size < 5*(sizeof (struct buddy)))
		panic("msg arena too small");

	/* set up lower buddy segment - always allocated */
	p = arena;
	p->bs_state = 0;
	p->bs_magic = MSGMAGIC;
	p->bs_back = NULL;
	p->bs_forw = p + 1;
	rp = p;

	/* set up free memory - in between 2 always allocated buddies */
	p = p->bs_forw;
	p->bs_state = 0;
	p->bs_magic = 0;
	p->bs_back = rp;
	p->bs_forw = arena + (size / sizeof (struct buddy)) - 1;
	rp = p;

	/* set up high buddy segment - always allocated */
	p = p->bs_forw;
	p->bs_state = 0;
	p->bs_magic = MSGMAGIC;
	p->bs_forw = NULL;
	p->bs_back = rp;
}


/*
 *   Allocate a message buffer of at least n bytes,
 *   Force alignment to a full word boundary.
 */
msgblock(arena, n)
register struct buddy *arena;
register int n;
{
	register struct buddy *p;
	register struct buddy *f;
	register int sizreq;
	register int siz;
	register int s;

	trace(TRC, "msgblock", n);
	/* force alignment to long int boundary */
	sizreq = n + sizeof (struct buddy) + sizeof (long) - 1;
	sizreq =  (sizreq / sizeof (long)) * sizeof (long);

	/* search the msg buffer until a free block large enough found */
	for (;;)  {
		/* search buffer again */
		s = spl6();
		p = arena->bs_forw;	/* first possible free block */
		while (p)  {
			if (p->bs_magic)  {
				/* block allocated - move to next one */
				trace(TRC, "msg-allocated", p);
				msgcheck(p);
				p = p->bs_forw;
				continue;
				}

			siz = p->bs_forw - p;
			if (siz < sizreq)  {
				/* too small */
				trace(TRC, "msg-too small", p);
				p = p->bs_forw;
				continue;
				}


			/* free area - check size */
			if (siz == sizreq)  {
				/* found block the right size */
				p->bs_state = 0;
				p->bs_magic = MSGMAGIC;
				splx(s);
				return(++p);
				}

			/* block too large - split if possible */
			if (siz <= (sizreq + sizeof (struct buddy)))  {
				/* no room for buddy structure */
				/* just return the whole area  */
				p->bs_state = 0;
				p->bs_magic = MSGMAGIC;
				splx(s);
				return(++p);
				}

			/* split it and allocate the lower half */
			trace(TRC, "msg-split", p);
			f = ((char *) p) + sizreq;
			p->bs_forw->bs_back = f;
			f->bs_back = p;
			f->bs_forw = p->bs_forw;
			p->bs_forw = f;
			p->bs_state = 0;
			p->bs_magic = MSGMAGIC;
			f->bs_state = 0;
			f->bs_magic = 0;
			splx(s);
			return(++p);
			}

		/* no blocks found - wait until one freed */
		splx(s);
		trace(TRC, "msgblock-none", arena);
		arena->bs_state = MSGWAIT;
		sleep((caddr_t) arena, TTIPRI);
		}
}


msgfree(arena, p)
register struct buddy *arena;
register struct buddy *p;
{
	register struct buddy *f;
	register int s;

	trace(TRC, "msgfree", p);
	s = spl6();
	p--;
	msgcheck(p);
	p->bs_magic = 0;
	f = p->bs_forw;
	if (!f->bs_magic)  {
		/* one ahead is free - merge the two blocks */
		p->bs_forw = f->bs_forw;
		f->bs_forw->bs_back = p;
		}

	f = p->bs_back;
	if (!f->bs_magic)  {
		/* prev. one is free - merge the blocks */
		f->bs_forw = p->bs_forw;
		p->bs_forw->bs_back = f;
		}

	splx(s);
	/* wakeup anyone waiting for a message buffer */
	if (arena->bs_state & MSGWAIT)  {
		arena->bs_state &= ~MSGWAIT;
		wakeup((caddr_t) arena);
		}
}


msgcheck(p)
register struct buddy *p;
{
	if (p->bs_magic != MSGMAGIC)  {
		printf("p=%x\n", p);
		panic("corrupt msg arena");
		}
}
