/* receive buffer allocation routines */
/* used to provide buffers that can be gotten at interrupt time */

#include "global.h"
#include "mbuf.h"
#include "buffers.h"

#if (defined(MSDOS) && !defined(MSC))
# include "alloc.h"
#endif

static unsigned max_rxbufs = 0;		/* max number of rx bufs */
static unsigned min_bufsize = MINRXBUFSIZ; /* min size of buffers */
static unsigned max_bufsize = MAXRXBUFSIZ; /* max size of buffers */

unsigned cur_rxbufs = 0;		/* number of pre-allocated rx bufs */
struct mbuf *sys_rxbufs = NULLBUF;	/* list of pre-allocated buffers */
struct mbuf *sys_txfree = NULLBUF;	/* list of buffers freed by transmitter */

/* "buffers" command sets the number of receive buffers */

int
dobuffers (argc,argv)
int argc;
char *argv[];

{
	if (argc == 1)
	    printf("%u buffers of %u .. %u bytes\n",max_rxbufs,
		   min_bufsize,max_bufsize);
	else{
	    max_rxbufs = atoi(argv[1]);
	    if (argc > 2)
		min_bufsize = atoi(argv[2]);
	    if (argc > 3)
		max_bufsize = atoi(argv[3]);
	    if (min_bufsize < 2)
		min_bufsize = MINRXBUFSIZ;
	    if (max_bufsize < min_bufsize)
		max_bufsize = min_bufsize;
	}

	buf_alloc();			/* alloc initial set of buffers */
	return 0;
}

/* check if enough buffers are on the receive buffer list
 * if not, allocate them and link to the list
 * also, buffers on the tx "to be freed" list are freed
 * malloc and free are only called with interrupts enabled,
 * because overruns will occur during the free list scan...
 */
void
buf_alloc()
{
    register struct mbuf *bp;
    unsigned bufsize;
    int i_state;
#if (defined(ATARI_ST) && defined(MWC))
    struct memblock {			/* MW C freelist structure */
	long size;
	struct memblock *next;
    };
    extern struct memblock *_a_scanp;	/* current start of scan */
    long blocksize;
#endif
#if (defined(MSDOS) && !defined(MSC))
    extern HEADER PTR allocp;		/* current start of scan */
    unsigned blocksize;
#endif

    while (sys_txfree != NULLBUF){	/* buffers to be freed */
	i_state = disable();
	bp = sys_txfree;		/* unlink one buffer */
	sys_txfree = bp->next;
	restore(i_state);

	free(bp);			/* free it with ints enabled */
    }

    while (cur_rxbufs < max_rxbufs){
	bufsize = max_bufsize;		/* allocate maximum buffer */

	/* this piece of code tries to minimize the free list fragmentation
	 * by taking a small block from the free list as a receive buffer.
	 * any block between MIN and MAX bufsize will be allocated, resulting
	 * in many exact fit allocations.
	 */

#if (defined(ATARI_ST) && defined(MWC))
	if (_a_scanp->size & 1)		/* pointing to a free block? */
	{
	    /* when free, check if next also free and join if so */

	    while ((blocksize = ((struct memblock *) ((char *) _a_scanp + _a_scanp->size - 1))->size) & 1)
	    {
		/* join next (free) block with this one */
		_a_scanp->size += blocksize - 1;
	    }

	    /* joined free blocks, now check if within limits */
	    /* compute blocksize = sizeof(free block) - sizeof(struct mbuf) */

	    blocksize = _a_scanp->size - 1 - sizeof(long) - sizeof(struct mbuf);

	    if (blocksize >= min_bufsize &&	/* big enough? */
		blocksize <= max_bufsize)	/* not too big? */
	    {
		bufsize = (int) blocksize;
	    }
	}
#endif
#if (defined(MSDOS) && !defined(MSC))
	/* compute blocksize = sizeof(free block) - sizeof(struct mbuf) */

	blocksize = allocp->s.size * sizeof(HEADER) - sizeof(HEADER) - sizeof(struct mbuf);

	if (blocksize >= min_bufsize &&		/* big enough? */
	    blocksize <= max_bufsize)		/* not too big? */
	{
	    bufsize = blocksize;
	}
#endif

	if ((bp = alloc_mbuf(bufsize)) == NULLBUF)
	    break;			/* no space, try again later */

	i_state = disable();
	bp->next = sys_rxbufs;		/* link at head of chain */
	sys_rxbufs = bp;
	cur_rxbufs++;
	restore(i_state);
    }
}

