/*************************************************
VMEM.C: Routines for virtual memory management for LOADER.

Written by:  Douglas Braun
             99 Summit Rd.
	     Riverside, CT 06878

July 1, 1985

**************************************************/

#include "loader.h"

/* This file contains routines to manage the disk-based
virtual address space that the loader writes to. */

/* This is the array of segments */

extern unsigned num_chains;  /* The mumber of bytes to reserve for chains */
SEGPTR seglist;
int fn;  /* file ident for read(),etc */
int swapdir;   /* Whether to pick highest or lowest clean segment */


init_vmem(name)
char *name;  /* The file name */
{
    int j;

    fn = creat(name, 0644);
    if (fn == -1)
    {
        fprintf(stderr,"Can't open output file.\n");
        abort();
    }
    close(fn);

    fn = open(name,2);  /* Re-open for read AND write */
    if (fn == -1)
    {
        fprintf(stderr,"Can't open output file.\n");
        abort();
    }

    seglist = (SEGPTR)alloc(256 * sizeof(SEGMENT));

    for (j=0; j <256; j++)
    {
	seglist[j].status = UNTOUCHED;
	seglist[j].buffer = NULL;
    }
}


/* This function is simply a speed hack.
It performs a logical 8-bit right shift in
assembly language faster than the compiler can */

#ifdef PORTABLE

unsigned sh8(j)
unsigned j;
{
    return( j >> 8);
}

#else

unsigned sh8()
{
#asm 8080
	lxi	h,3
	dad	sp
	mov	l,m
	mvi	h,0
#endasm
}

#endif



int get_word(addr)
unsigned addr;
{
    return( (get_byte(addr) & 0xff) | ((get_byte(addr+1) << 8) & 0xff00));
}


get_byte(addr)
unsigned addr;
{
    int segno, offset;

    segno = sh8(addr);
    offset = addr & 0xff;

    if (! segno)
    {
	fprintf(stderr, "Trying to read below 100H.\n");
	abort();
    }

    switch(seglist[segno].status)
    {
    
    case UNTOUCHED:
	fprintf(stderr,"Reading unwritten data in segment %d.\n",segno);
	abort();
	break;
    case NOBUFFER:
	getbuffer(segno);
	/* Fall into: */
    case CLEAN:
    case DIRTY:
	return( *(seglist[segno].buffer + offset) & 0xff);
	break;
    default:
	fprintf(stderr,"Oops... Bad segment status for segment %d.\n",segno);
    }
}


put_word(word, addr)
int word;
unsigned addr;
{
    put_byte(word & 0xff, addr);
    put_byte(sh8(word), addr+1);
}



put_byte(byte, addr)
char byte;
unsigned addr;
{
    static int segno, offset; /* static for speed */

    segno = sh8(addr);
    offset = addr & 0xff;
    if (! segno )
    {
	fprintf(stderr, "Trying to write below 100H.\n");
	abort();
    }

    switch(seglist[segno].status)
    {
    
    case UNTOUCHED:
    case NOBUFFER:
	getbuffer(segno);
	/* Fall into: */
    case CLEAN:
	seglist[segno].status = DIRTY;
    case DIRTY:
	*(seglist[segno].buffer + offset) = byte;
	return;
	break;
    default:
	fprintf(stderr,"Oops... Bad segment status for segment %d.\n",segno);
    }
}



/* This assigns a buffer to segment segno, fills it from disk
if it can, and sets its status to CLEAN. */

getbuffer(segno)
int segno;
{
    int outseg;
    int temp;
    int j;

    if (seglist[segno].status == CLEAN || seglist[segno].status == DIRTY)
    {
	fprintf(stderr,"Oops... Trying to re-assign buffer to segment %d.\n",
		segno);
	abort();
    }

    if (maxsbrk() < num_chains + 256)   /* Dont sbrk if there will not be
					enough memory for chain lists */
        seglist[segno].buffer = (char *)(-1);
    else
        seglist[segno].buffer = sbrk(256);
    
    if ((int)(seglist[segno].buffer) == -1)
    {
#ifdef DEBUG
	printf("Sbrk failed for segment %d.\n",segno);
#endif

	outseg = pickseg();   /* Decide which segment's buffer to flush */
#ifdef DEBUG
        printf("Picked segment %d for replacement\n",outseg);
#endif

   	seglist[outseg].status = NOBUFFER;

	seglist[segno].buffer = seglist[outseg].buffer;
	seglist[outseg].buffer = NULL;
    }

    /* Fill in the new buffer, if there is anything to fill it with */
    if (seglist[segno].status != UNTOUCHED)
    {
    	if (seekr(fn, segno*2-2, 0) == -1)
	{
	    fprintf(stderr, "Couldn't seek to segment %d.\n",segno);
	    abort();
	}
	
	if ((temp=read(fn, seglist[segno].buffer, 256)) != 256)
	{
	    fprintf(stderr, "Couldn't read from segment %d. Result %d\n",segno,temp);
	    abort();
	}
    }
    else
    {
        /* A never-written buffer is filled with zeroes */
        for (j=0; j<256; j++)
            *(seglist[segno].buffer + j) = 0;
    }
	
    seglist[segno].status = CLEAN;
}
	


/* This flushes out all the dirty segments, fills any
'holes' in the file, and closes the file */

flush_vmem()
{
    int j;
    int lastseg;
    char zeroes[256];

    lastseg = 0;
    for (j=255; j>=1; j--)
	if (seglist[j].status != UNTOUCHED)
	{
	    lastseg = j;
	    break;
	}


    for (j=1; j<= lastseg; j++)
	if (seglist[j].status == DIRTY)
	{
	    /* Write out the old contents of the buffer */
	    if (seekr(fn, j*2-2, 0) == -1)
	    {
		fprintf(stderr, "Couldn't seek to segment %d.\n",j);
		abort();
	    }
	
	    if (write(fn, seglist[j].buffer, 256) != 256)
	    {
		fprintf(stderr, "Couldn't write to segment %d.\n",j);
		abort();
	    }
	}
	else if (seglist[j].status == UNTOUCHED)
	{
	    /* Write out 256 bytes of zeroes. */
	    if (seekr(fn, j*2-2, 0) == -1)
	    {
		fprintf(stderr, "Couldn't seek to segment %d.\n",j);
		abort();
	    }
	
	    if (write(fn, zeroes, 256) != 256)
	    {
		fprintf(stderr, "Couldn't write to segment %d.\n",j);
		abort();
	    }
	}
	

    if (close(fn) == -1)
    {
	fprintf(stderr, "Can't close output file.\n");
    }

    printf("%d blocks written out.\n",lastseg*2);
}


/* This decides upon and returns the number of a segment whose
buffer can be flushed */

pickseg()
{
    int j;

    if (swapdir == BOTTOM)
    {
        /* Return the first clean segment, if any */
    for (j=1; j < 256; j++)
	if (seglist[j].status == CLEAN)
	    return(j);

    /* If no clean segments, flush all of them to create
    lot of clean ones. */

    all_flush();

    for (j=1; j < 256; j++)
	if (seglist[j].status == CLEAN)
	    return(j);

    /* If we get this far, something is wrong */

    fprintf(stderr, "Can't find a segment to flush.\n");
    abort();

    }
    else
    {

    /* Return the first clean segment, if any */
    for (j=255; j >= 1; j--)
	if (seglist[j].status == CLEAN)
	    return(j);

    /* If no clean segments, flush all of them to create
    lot of clean ones. */

    all_flush();

    for (j=255; j >= 1; j--)
	if (seglist[j].status == CLEAN)
	    return(j);

    /* If we get this far, something is wrong */

    fprintf(stderr, "Can't find a segment to flush.\n");
    abort();
    }

}


/* This writes out all the segments with buffers, marking
their buffers CLEAN afterwards. */

all_flush()
{
    int j;

    for (j=1; j < 256; j++)
    {
	if (seglist[j].status == DIRTY)
	{
#ifdef DEBUG
            printf("Flushing segment %d\n",j);
#endif
	    /* Write out the old contents of the buffer */
	    if (seekr(fn, j*2-2, 0) == -1)
	    {
		fprintf(stderr, "Couldn't seek to segment %d.\n",j);
		abort();
	    }
	
	    if (write(fn, seglist[j].buffer, 256) != 256)
	    {
		fprintf(stderr, "Couldn't write to segment %d.\n",j);
		abort();
	    }

	    seglist[j].status = CLEAN;
	}    
    }
}



close_vmem()
{
    close(fn);
}
