#ifndef UNIX
/* memory allocation routines
 * Copyright 1991 Phil Karn, KA9Q
 *
 * Adapted from alloc routine in K&R; memory statistics and interrupt
 * protection added for use with net package. Must be used in place of
 * standard Turbo-C library routines because the latter check for stack/heap
 * collisions. This causes erroneous failures because process stacks are
 * allocated off the heap.
 *
 * Mods by G1EMM , PA0GRI, KO4KS
 */

#include <stdio.h>
#include <dos.h>
#include <alloc.h>
#include "global.h"
#include "config.h"
#include "proc.h"
#include "cmdparse.h"
#include "mbuf.h"

static unsigned long Memfail;	/* Count of allocation failures */
static unsigned long Allocs;	/* Total allocations */
unsigned long Frees;	/* Total frees */
static unsigned long Invalid;	/* Total calls to free with garbage arg */
static unsigned long Yellows;	/* Yellow alert garbage collections */
static unsigned long Reds;	/* Red alert garbage collections */
static unsigned long Overuse;	/* Total calls to free with overused arg */
static unsigned long Intalloc;	/* Calls to malloc with ints disabled */
static unsigned long Intfree;	/* Calls to free with ints disabled */
static int Memwait;		/* Number of tasks waiting for memory */
static unsigned long Availmem;		/* Heap memory, ABLKSIZE units */
static unsigned long ReallocSys;
static unsigned long Morecores;
#ifdef notdef
static int Efficient = 1;	/* 0 = normal/fast, 1 = effecient/slow */
#endif
static int Memdebug = 0;	/* 0 = normal, 1 = call logstat() */
#ifdef MULTITASK
unsigned Minheap = 16 * 1024;	/* Min free heap when shelling out */
#endif
static char freewarn[] ="free: WARNING! %s (%Fp) pc = %04x:%04x proc %s%c";
static char freewarn1[] = "invalid pointer";
static char HeapSizeStr[] = "heap size %lu, avail %lu (%lu%%), morecores %lu (-%lu), coreleft %lu";
static char AllocStr[] = "allocs %lu, frees %lu (diff %lu), alloc fails %lu, invalid frees %lu, overused %lu";
static char GarbageStr[] = "garbage collections yellow %lu, red %lu";
#ifdef notdef
static char EfficientStr[] = "efficient %u, threshold %lu";
#else
static char EfficientStr[] = "threshold %lu";
#endif
static char InterruptStr[] = "interrupts-off calls to malloc %lu, free %lu\n";
#ifdef Kelvdebug
static char freewarn2[] = "overused buffer";
#endif

extern void vmsstat();
extern void displayStatLine __ARGS((int offset, int phase));
extern char vgaDesired;

static unsigned long Sizes[16];

int logstat __ARGS((void));

static int dostat __ARGS((int argc,char *argv[],void *p));
static int dofreelist __ARGS((int argc,char *argv[],void *p));
static int doibufsize __ARGS((int argc,char *argv[],void *p));
static int donibufs __ARGS((int argc,char *argv[],void *p));
static int dothresh __ARGS((int argc,char *argv[],void *p));
static int dosizes __ARGS((int argc,char *argv[],void *p));
static int doefficient __ARGS((int argc,char *argv[],void *p));
static int domemdebug __ARGS((int argc,char *argv[],void *p));
static int dominheap __ARGS((int argc,char *argv[],void *p));
static int dogcollect __ARGS((int argc,char *argv[],void *p));

struct cmds DFAR Memcmds[] = {
	"debug",	domemdebug,	0, 0, NULLCHAR,
#ifdef notdef
	"efficient",	doefficient,	0, 0, NULLCHAR,
#endif
	"freelist",	dofreelist,	0, 0, NULLCHAR,
	"gcollect",	dogcollect,	0, 0, NULLCHAR,
	"ibufsize",	doibufsize,	0, 0, NULLCHAR,
#ifdef MULTITASK
    "minheap",  dominheap,  0, 0, NULLCHAR,
#endif
    "nibufs",   donibufs,   0, 0, NULLCHAR,
	"sizes",	dosizes,	0, 0, NULLCHAR,
	"status",	dostat,		0, 0, NULLCHAR,
	"thresh",	dothresh,	0, 0, NULLCHAR,
	NULLCHAR,
};

#ifdef	LARGEDATA
#define	HUGE	huge
#else
#define	HUGE
#endif

union header {
	struct {
		union header HUGE *ptr;
		unsigned long size;
	} s;
	long l[2];
};

typedef union header HEADER;
#define	NULLHDR	(HEADER HUGE *)NULL

#define	ABLKSIZE	(sizeof (HEADER))

static HEADER HUGE *morecore __ARGS((unsigned nu));

static HEADER Base;
static HEADER HUGE *Allocp = NULLHDR;
unsigned long Heapsize;
static char HUGE *newbrk;

#ifdef Kelvdebug
#define MARKER		0x766c654bL /* Kelv in reverse */
#endif

/* Allocate block of 'nb' bytes */
void *
malloc(nb)
unsigned nb;
{
	register HEADER HUGE *p, HUGE *q;
	register unsigned nu;
	int i;

	if(!istate())
		Intalloc++;
	if(nb == 0)
		return NULL;

	/* Record the size of this request */
	if((i = log2(nb)) >= 0)
		Sizes[i]++;
	
#ifndef Kelvdebug
 	/* Round up to full block, then add one for header */

 	nu = ((nb + ABLKSIZE - 1) / ABLKSIZE) + 2;	/* force allocated memory  */
	nu &= 0xfffffffeL;				/* to be on offset 0x0008 */
#else
 	/* Round up to full block, then add one for header and one for debug */

 	nu = ((nb + ABLKSIZE - 1) / ABLKSIZE) + 4;	/* force allocated memory  */
	nu &= 0xfffffffeL;				/* to be on offset 0x0008 */
#endif

	if ((q = Allocp) == NULLHDR){
		Base.s.ptr = Allocp = q = &Base;
		Base.s.size = 1;
	}

#ifdef notdef
	if(Efficient) {
		Allocp = q = &Base;	/* Start at the very beginning again */
	}
#endif

	for (p = q->s.ptr; ; q = p, p = p->s.ptr){
		if (p->s.size >= nu){
			/* This chunk is at least as large as we need */
			if (p->s.size <= nu + 1){
				/* This is either a perfect fit (size == nu)
				 * or the free chunk is just one unit larger.
				 * In either case, alloc the whole thing,
				 * because there's no point in keeping a free
				 * block only large enough to hold the header.
				 */
				q->s.ptr = p->s.ptr;
			} else {
				/* Carve out piece from end of entry */
				p->s.size -= nu;
				p += p->s.size;
				p->s.size = nu;
			}
			p->s.ptr = p;	/* for auditing */
#ifdef Kelvdebug
 			p->l[(p->s.size * 2) - 2] = (long)p;	/* debug */
 			p->l[(p->s.size * 2) - 1] = MARKER;	/* debug */
#endif
			Allocs++;
			Availmem -= p->s.size;
			p++;
#ifdef	LARGEDATA
			/* On the brain-damaged Intel CPUs in
			 * "large data" model, make sure the offset field
			 * in the pointer we return isn't null.
			 * The Turbo C compiler and certain
			 * library functions like strrchr() assume this.
			 */
			if(FP_OFF(p) == 0){
				/* Return denormalized but equivalent pointer */
				return (void *)MK_FP(FP_SEG(p)-1,16);
			}
#endif
			return (void *)p;
		}
		if (p == Allocp && ((p = morecore(nu)) == NULLHDR)){
			Memfail++;
			return NULL;
		}
	}
}
/* Get more memory from the system and put it on the heap */
static HEADER HUGE *
morecore(nu)
unsigned nu;
{
register HEADER HUGE *up;

	Morecores++;

	if ((long)nu * ABLKSIZE > 32767L)
		return NULLHDR;

	if ((int)(newbrk = (char HUGE *)sbrk(nu * ABLKSIZE)) == -1){
		if (Memdebug)	{
			log(-1,"morecore: Failure requesting %ul (coreleft %ul)",
				(unsigned long) nu * ABLKSIZE,(unsigned long) coreleft());
			logstat();
		}
		return NULLHDR;
	}

	up = (HEADER *)newbrk;
	newbrk += (nu * ABLKSIZE);
	up->s.size = nu;
	up->s.ptr = up;	/* satisfy audit */
#ifdef Kelvdebug
 	up->l[(up->s.size * 2) - 2] = (long)up;		/* satisfy debug */
 	up->l[(up->s.size * 2) - 1] = MARKER;		/* satisfy debug */
#endif
	free((void *)(up + 1));
	Heapsize += nu*ABLKSIZE;
	Frees--;	/* Nullify increment inside free() */
	return Allocp;
}

extern void stktrace();

/* Put memory block back on heap */
void
free(blk)
void *blk;
{
	register HEADER HUGE *p, HUGE *q;
	unsigned short HUGE *ptr;

	if(!istate())
		Intfree++;
	if(blk == NULL)
		return;		/* Required by ANSI */
	p = (HEADER HUGE *)blk - 1;
	/* Audit check */
	if(p->s.ptr != p){
		ptr = (unsigned short *)&blk;
		printf(freewarn,freewarn1,blk,ptr[-1],ptr[-2],Curproc->name,'\n');
		fflush(stdout);
#ifdef STKTRACE
		stktrace();
#endif
		Invalid++;
		log(-1,freewarn,freewarn1,blk,ptr[-1],ptr[-2],Curproc->name,' ');
		logstat();
		return;
	}
#ifdef Kelvdebug
 	if(p->l[(p->s.size * 2) - 2] != (long)p || p->l[(p->s.size * 2) - 1] != MARKER){
 		ptr = (unsigned short *)&blk;
		printf(freewarn,freewarn2,blk,ptr[-1],ptr[-2],Curproc->name,'\n');
		fflush(stdout);
 		Overuse++;
		log(-1,freewarn,freewarn2,blk,ptr[-1],ptr[-2],Curproc->name,' ');
		logstat();
 		return;
 	}
#endif
	Availmem += p->s.size;
	/* Search the free list looking for the right place to insert */
	for(q = Allocp; !(p > q && p < q->s.ptr); q = q->s.ptr){
		/* Highest address on circular list? */
		if(q >= q->s.ptr && (p > q || p < q->s.ptr))
			break;
	}
	if(p + p->s.size == q->s.ptr){
		/* Combine with front of this entry */
		p->s.size += q->s.ptr->s.size;
		p->s.ptr = q->s.ptr->s.ptr;
	} else {
		/* Link to front of this entry */
		p->s.ptr = q->s.ptr;
	}
	if(q + q->s.size == p){
		/* Combine with end of this entry */
		q->s.size += p->s.size;
		q->s.ptr = p->s.ptr;
	} else {
		/* Link to end of this entry */
		q->s.ptr = p;
	}

	Frees++;
	if(Memwait != 0)
		psignal(&Memwait,0);
}

#ifdef	notdef	/* Not presently used */
/* Move existing block to new area */
void *
realloc(area,size)
void *area;
unsigned size;
{
	unsigned osize;
	HEADER HUGE *hp;
	char HUGE *cp;

	hp = ((HEADER *)area) - 1;
	osize = (hp->s.size -1) * ABLKSIZE;

	free(area);	/* Hopefully you have your interrupts off , Phil. */
	if((cp = malloc(size)) != NULL && cp != area)
		memcpy((char *)cp,(char *)area,size>osize? osize : size);
	return cp;
}
#endif
/* Allocate block of cleared memory */
void *
calloc(nelem,size)
unsigned nelem;	/* Number of elements */
unsigned size;	/* Size of each element */
{
	register unsigned i;
	register char *cp;

	i = nelem * size;
	if((cp = malloc(i)) != NULL)
		memset(cp,0,i);
	return cp;
}
/* Version of malloc() that waits if necessary for memory to become available */
void *
mallocw(nb)
unsigned nb;
{
	register void *p;

	while((p = malloc(nb)) == NULL){
		Memwait++;
		pwait(&Memwait);
		Memwait--;
	}
	return p;
}
/* Version of calloc that waits if necessary for memory to become available */
void *
callocw(nelem,size)
unsigned nelem;	/* Number of elements */
unsigned size;	/* Size of each element */
{
	register unsigned i;
	register char *cp;

	i = nelem * size;
	cp = mallocw(i);
	memset(cp,0,i);
	return cp;
}
/* Return available memory on our heap plus available system memory */
unsigned long
availmem()
{
	return Availmem * ABLKSIZE + coreleft();
}

/* Log heap stats */
int
logstat()
{
	if(Memdebug){
		log(-1,"Memory status :");
        log(-1,HeapSizeStr,Heapsize,Availmem*ABLKSIZE, \
            100L*Availmem*ABLKSIZE/Heapsize,Morecores,ReallocSys,coreleft());
        log(-1,AllocStr,Allocs,Frees,Allocs-Frees,Memfail,Invalid,Overuse);
        log(-1,GarbageStr,Yellows,Reds);
#ifdef notdef
        log(-1,EfficientStr,Efficient,Memthresh);
#else
        log(-1,EfficientStr,Memthresh);
#endif
        log(-1,InterruptStr,Intalloc,Intfree);
	}

	return 0;
}

void
mbmemory ()
{
    tprintf(HeapSizeStr,Heapsize,Availmem*ABLKSIZE, \
            100L*Availmem*ABLKSIZE/Heapsize,Morecores,ReallocSys,coreleft());
    tputc('\n');
}


/* Print heap stats */
static int
dostat(argc,argv,envp)
int argc;
char *argv[];
void *envp;
{
    mbmemory();
    tprintf(AllocStr,Allocs,Frees,Allocs-Frees,Memfail,Invalid,Overuse);
    tputc('\n');
    tprintf(GarbageStr,Yellows,Reds);
    tputc('\n');
    tprintf(InterruptStr,Intalloc,Intfree);
        vmsstat();
	iqstat();
	return 0;
}

/* Print heap free list */
static int
dofreelist(argc,argv,envp)
int argc;
char *argv[];
void *envp;
{
	HEADER HUGE *p;
	int i = 0;

	for(p = Base.s.ptr;p != &Base;p = p->s.ptr){
        tprintf("%4.4x %6lu",FP_SEG(p),p->s.size * ABLKSIZE);
        if(++i == 5){
			i = 0;
			if(tprintf("\n") == EOF)
				return 0;
		} else
			tprintf(" | ");
	}
	if(i != 0)
		tprintf("\n");
	return 0;
}
static int
dosizes(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	int i;

	for(i=0;i<16;i += 4){
		tprintf("N>=%5u:%7ld| N>=%5u:%7ld| N>=%5u:%7ld| N>=%5u:%7ld\n",
		 1<<i,Sizes[i],	2<<i,Sizes[i+1],
		 4<<i,Sizes[i+2],8<<i,Sizes[i+3]);
	}
	return 0;
}
int
domem(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return subcmd(Memcmds,argc,argv,p);
}

static int
dothresh(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return setlong(&Memthresh,"Free memory threshold (bytes)",argc,argv);
}

static int
donibufs(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	if(setint(&Nibufs,"Interrupt pool buffers",argc,argv) == 0){
		iqclear();
		return 0;
	}
	return 1;
}
static int
doibufsize(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return setuns(&Ibufsize,"Interrupt buffer size",argc,argv);
}

static int
dogcollect(argc,argv,p)
int argc;
char *argv[];
void *p;
{
void (**fp)();

	Yellows++;
	for(fp = Gcollect;*fp != NULL;fp++)
		(**fp)(0);
	tputs ("Garbage collection forced at Yellow level!\n");
	return 0;
}


void
gcollect(i,v1,v2)
int i;		/* Args not used */
char *v1;
void *v2;
{
void (**fp)();
int red;
HEADER HUGE *p, *last;
char HUGE *calc;
unsigned long backto, backcnt;
int backed;

	for(;;){
		mspause(500L);	/* Run displayStatLine every 1/2 second */
		displayStatLine (0, 1);
		mspause(500L);	/* and run the rest every second */
		displayStatLine (0, 0);
#ifdef SCREENSAVER
		screensaver ();
#endif
		/* If memory is low, collect some garbage. If memory is VERY
		 * low, invoke the garbage collection routines in "red" mode.
		 */
		if(availmem() < Memthresh){
			if(availmem() < Memthresh/2){
				red = 1;
				Reds++;
			} else {
				red = 0;
				Yellows++;
			}
			for(fp = Gcollect;*fp != NULL;fp++)
				(**fp)(red);
		}
		last = 0;
		for (p = Base.s.ptr; p->s.ptr != &Base; p = p->s.ptr)
			last = p;
		if (!last)
			continue;
		calc = (char HUGE *)(p + p->s.size);
		if (newbrk <= calc)		{
			backcnt = backto = (p->s.size * ABLKSIZE);
			do	{
				backed = (backcnt < 32767) ? backcnt : 32767;
				sbrk (-1 * backed);
				backcnt -= backed;
			} while (backcnt);
			newbrk -= backto;
			Heapsize -= backto;
			Availmem -= p->s.size;
			last->s.ptr = &Base;
			ReallocSys++;
		}
	}
}

#ifdef notdef
static int
doefficient(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return setbool(&Efficient,"Efficient/slower mode",argc,argv);
}
#endif

static int
domemdebug(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return setbool(&Memdebug,"\"Mem stat\" to log after failures",argc,argv);
}

#ifdef MULTITASK
static int
dominheap(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	return setuns(&Minheap,"Minimum free heap when shelled out",argc,argv);
}
#endif
#endif /* !UNIX */
