/* mtest.c - MemTest-86 */

/* Copyright 1996,  Chris Brady
 *
 * Permission to use, copy, modify, and distribute this
 * software and its documentation for any purpose and without fee
 * is granted provided that the above copyright notice appears in all copies.
 * It is provided "as is" without express or implied warranty.
 */

#include <linux/tty.h>
#include <linux/sched.h>
#include <asm/io.h>
#include "mtest.h"
#include <linux/serial_reg.h>
#define serial_echo_outb(v,a) outb((v),(a)+0x3f8)
#define serial_echo_inb(a)    inb((a)+0x3f8)
#define BOTH_EMPTY (UART_LSR_TEMT | UART_LSR_THRE)
/* Wait for transmitter & holding register to empty */
#define WAIT_FOR_XMITR \
 do { \
       lsr = serial_echo_inb(UART_LSR); \
 } while ((lsr & BOTH_EMPTY) != BOTH_EMPTY)

void itoa(char s[], int n); 
void reverse(char *p);
void serial_echo_init();
void serial_echo_print(char *s);
void ttyprint(int y, int x, char *s);
void ttyprintc(int y, int x, char c);

#ifndef UNIXTEST
unsigned short memsz;
#else
unsigned memsz;
#endif

void cprint(int y,int x,char *s);
void hprint(int y,int x,unsigned long val);
void dprint(int y,int x,unsigned long val,int len);
void check(long p1,long p2);
void error(long* adr,long good,long bad);
void do_spin(int init);
void init();
void inter();
void set_cache();
void set_refresh();
void check_input();
void footer();
void clr_footer();
int get_key();

extern long idt_descr;
extern long trap_regs[];

extern __inline__ void cache_off()
{
        __asm__("push %eax\n\t"
		"movl %cr0,%eax\n\t"
                "orl $0x60000000,%eax\n\t"
                "movl %eax,%cr0\n\t"
		".byte 0x0f,0x09\n\t"	/* Invalidate and flush cache */
		"pop  %eax\n\t");
}
extern __inline__ void cache_on()
{
        __asm__("push %eax\n\t"
		"movl %cr0,%eax\n\t"
                "andl $0x9fffffff,%eax\n\t"
                "movl %eax,%cr0\n\t"
		"pop  %eax\n\t");
}

extern __inline__ void reboot()
{
        __asm__(
		"movl %cr0,%eax\n\t"
       		"andl  $0x00000011,%eax\n\t"
       		"orl   $0x60000000,%eax\n\t"
       		"movl  %eax,%cr0\n\t"
       		"movl  %eax,%cr3\n\t"
		"movl  %cr0,%ebx\n\t"
		"andl  $0x60000000,%ebx\n\t"
		"jz    f\n\t"
		".byte 0x0f,0x09\n\t"	/* Invalidate and flush cache */
		"f: andb  $0x10,%al\n\t"
		"movl  %eax,%cr0\n\t"
		"movw $0x0010,%ax\n\t"
		"movw %ax,%ds\n\t"
		"movw %ax,%es\n\t"
		"movw %ax,%fs\n\t"
		"movw %ax,%gs\n\t"
		"movw %ax,%ss\n\t"
		"ljmp  $0xffff,$0x0000\n\t");
}

struct mmap {
	volatile long *start;
	volatile long *end;
};

/* Define common variables for normal and relocated test locations */
struct vars {
	int firsttime;
	int pass;
	long *eadr;
	long exor;
	int msg_line;
	int ecount;
	int msegs;
	int cache_flag;
	int ref_flag;
	int scroll_start;
};

char spin[] = {'/', '-', '\\', '|'};

char *codes[] = {
	"  Divide",
	"   Debug",
	"     NMI",
	"  Brkpnt",
	"Overflow",
	"   Bound",
	"  Inv_Op",
	" No_Math",
	"Double_Fault"
	"Seg_Over",
	" Inv_TSS",
	"  Seg_NP",
	"Stack_Fault",
	"Gen_Prot",
	"Page_Fault",
	"   Resvd",
	"     FPE",
	"Alignment"
	" Mch_Chk"
};


#ifdef UNIXTEST
#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#undef START_ADR
#define START_ADR start_adr
#undef SCREEN_ADR
#define SCREEN_ADR screen_adr
#define MEMSZ 4096 /* test area size Kbytes */
char start_adr[MEMSZ*1024];
unsigned long screen_adr;
void do_test(void);
int main ()
{
  int fd;
  memsz = (unsigned)start_adr/1024 + MEMSZ - 1024;
  fd = open("/dev/mem",O_RDWR);
  screen_adr = (unsigned)mmap(0,4096,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0xb8000);
  close(fd);
  do_test();
}
#endif


int i = 0;
int cnt = 0, seq = 0;
unsigned long p1 = 0, p2 = 0, p0 = 0;
volatile long *p = 0;
volatile long *pd = 0;
volatile char *pp = 0;
unsigned long m_lim = 0;
char buf[18];
int segs = 0;
int s = 0;
int slock = 0;
struct vars *v = 0;
struct mmap map[10];
int lsr;

asmlinkage void do_test(void)
{
	/* Since all phases of the test have the same entry point we use
	 * the address of a static variable (i) to know if the test code has
	 * been relocated.  */
	 if (&i < (int *)RELOBASE) { 
		/* Not relocated */
		/* Set stack and idt */
		__asm__ __volatile__ ("mov %0,%%esp" : : "a" (TESTADR));
		__asm__ __volatile__ ("lidt %0" : : "m" (idt_descr));

		/* Set pointer to common variable area */
		v = (struct vars *)(TESTADR+TSTSIZE-0x400);

		/* If first time, initialize test */
		if (v->firsttime == 0) {
			init();
			v->firsttime = 1;
		}

		/* Update display of memory segments being tested */
		cprint(1, 0, "          ");
		for (i=0; i<v->msegs; i++) {
			dprint(2+i, 9, (long)map[i].start/1024, 5);
			cprint(2+i, 14, "k - ");
			dprint(2+i, 18, (long)map[i].end/1024, 7);
			cprint(2+i, 25, "k");
		}
		segs = v->msegs;
	} else {
		/* Relocated */
		/* Set stack and idt */
		__asm__ __volatile__ ("mov %0,%%esp" : : "a" (TESTADR+RELOBASE));
		__asm__ __volatile__ ("lidt %0" : : "m" (idt_descr));
		/* Set pointer to common variable area */
		v = (struct vars *)(RELOBASE+TESTADR+TSTSIZE-0x400);

		/* If this is the first time relocated, find the lowest
		 * address that can be tested without causing an exception.
		 * The trap handler (inter) setups up the test addresses
		 * for us when an exeption occurs.  */
		if (map[0].end == 0) {
			for (p=(long *)TESTADR-0x100; p;) {
				*(--p) = 0;
				*p = -1;
			}
			map[0].start = (volatile long *)0;
			map[0].end = (volatile long *)SKIP_START;
		}

		/* Update display of memory segments being tested */
		segs = 1;
		cprint(1, 0, "Relocated");
		dprint(2, 9, (long)map[0].start, 5);
		cprint(2, 14, "  - ");
		dprint(2, 18, (long)map[0].end/1024, 7);
		for (i=1; i<v->msegs; i++) {
			cprint(2+i, 10, "                 ");
		}
	}

	/* Use a 4 bit wide walking ones pattern and it's complement.
	 * This will check out 4 bit wide chips.  This should be
	 * changed if chips more than 4 bits wide become available.  */
	p0 = 8;
	for (i=0; i<5; i++, p0=p0>>1) {
		p1 = p0 | (p0<<4) | (p0<<8) | (p0<<12) | (p0<<16) |
			(p0<<20) | (p0<<24) | (p0<<28);
		p2 = ~p1;
		check(p1,p2);
	
		/* Switch patterns */
		p2 = p1;
		p1 = ~p2;
		check(p1,p2);
	}

	/* End of a test phase so relocate the test
	 * Only if there is more than 1 meg of memory */
	if (&i < (int *)RELOBASE && map[v->msegs-1].end > (long *)0x110000) {
		/* Copy test code to high memory */
		p = (long *)TESTADR;
		pd = (long *)(RELOBASE+TESTADR);
		for (i=0; i<(TSTSIZE)/4; i++) {
			*pd = *p;
			p++;
			pd++;
		}

		/* Jump to relocated code */
		p = (long *)(RELOBASE+TESTADR+MAINSZ);
		goto *p;
	} else {
		/* Since the code is relocated to high memory, this is
		 * the end of a pass.  Adjust cache and refresh settings
		 * for the next pass.  */
		v->pass++;
		dprint(0, 71, v->pass, 5);
		set_cache();
		set_refresh();

		/* If relocated, move back to low memory */
		if (&i > (int *)RELOBASE) {

			/* Copy test code to low memory */
			p = (long *)(RELOBASE+TESTADR);
			pd = (long *)TESTADR;
			for (i=0; i<(TSTSIZE)/4; i++) {
				*pd = *p;
				p++;
				pd++;
			}

			/* Jump to test start */
			p = (long *)TESTADR;
			goto *p;
		} else {
			do_test();
		}
	}
}

/*
 * Initialize test, setup screen and find out how much memory there is.
 */
void init()
{
	outb(0x8, 0x3f2);  /* Kill Floppy Motor */
	serial_echo_init();
        serial_echo_print("[6;24r");    /* Set scrolling region row 6-23 */
        serial_echo_print("[H[2J");   /* Clear Screen */

	/* Set background to blue for the title */
	for(i=0, pp=(char *)(SCREEN_ADR+1); i<17; i++, pp+=2) {
		*pp = 0x17;
	}

	/* Do reverse video for the bottom display line */
	for(i=0, pp=(char *)(SCREEN_ADR+1+(24 * 160)); i<80; i++, pp+=2) {
		*pp = 0x70;
	}

        serial_echo_print("[37m[44m");
        serial_echo_print("[0m");

        serial_echo_print("[37m[44m");
	cprint(0, 0, " Memtest-86 v1.4a");
        serial_echo_print("[0m");
	cprint(2, 0, "Testing: ");

	/* Since all address bits are not decoded, the search for memory
	 * must be limited.  The max address is found by checking for
	 * memory wrap from 1mb to 4gb.  */
	map[0].start = (long *)0x1234569;
	p1 = (long)&map[0].start;
	m_lim = 0xffffffff; 
	for (p2 = 0x100000; p2; p2 <<= 1) {  
		p = (long *)(p1 + p2);
		if (*p == 0x1234569) {
			m_lim = --p2;
			break;
		}
	}

	cprint(0, 34, "Max_Mem:");
	dprint(0, 43, m_lim/1024, 7);
	cprint(0, 50, "k");
	
	/* Find all segments of RAM */
	p = (long *)(START_ADR + 0x400);
	i = 0;
	map[i].start = p;
	dprint(2, 9, (long)p/1024, 5);
	cprint(2, 14, "k - ");

	/* Limit search for memory to m_lim and make sure we don't 
	 * overflow the 32 bit size of p.  */
	while ((long)p < m_lim && (long)p > START_ADR) {
		/*
		 * Skip over reserved memory
		 */
		if ((long)p < SKIP_END && (long)p >= SKIP_START) {
			map[i].end = (long *)SKIP_START;
			dprint(2+i, 18, (long)p/1024, 7);
			cprint(2+i, 25, "k");
			p = (long *)SKIP_END;
			i++;
			map[i].start = 0;
			goto fstart;
		}

		/* Determine if this is memory by reading and then writing
		 * the complement.  We then check that at least one bit
		 * changed in each byte before believing that it really
		 * is memory.  */
		p1 = *p;
		*p = ~p1;
		p2 = *p;
		s = 0;
		if ((0xff & p1) != (0xff & p2)) {
			s++;
		}
		if ((0xff00 & p1) != (0xff00 & p2)) {
			s++;
		}
		if ((0xff0000 & p1) != (0xff0000 & p2)) {
			s++;
		}
		if ((0xff000000 & p1) != (0xff000000 & p2)) {
			s++;
		}
		
		if (s < 4) {

			/* ROM or nothing at this address, record end addrs */
			map[i].end = p;
			dprint(2 + i, 18, (long)p/1024, 7);
			cprint(2 + i, 25, "k");
			i++;
			map[i].start = 0;

			/* If we are past the first meg then stop scanning 
			 * at the first gap */
			if ((long)p > 0x100000) {
				break;
			}
fstart:
			while ((long)p < m_lim && (long)p > START_ADR) {

				/* Skip over video memory */
				if ((long)p < SKIP_END &&
					(long)p >= SKIP_START) {
					p = (long *)SKIP_END;
				}
				p1 = *p;
				*p = ~p1;
				p2 = *p;
				s = 0;
				if ((0xff & p1) != (0xff & p2)) {
					s++;
				}
				if ((0xff00 & p1) != (0xff00 & p2)) {
					s++;
				}
				if ((0xff0000 & p1) != (0xff0000 & p2)) {
					s++;
				}
				if ((0xff000000 & p1) != (0xff000000 & p2)) {
					s++;
				}
		
				if (s == 4) {

					/* More RAM, record start addrs */
					map[i].start = p;
					dprint(2 + i, 9, (long)p/1024, 5);
					cprint(2 + i, 14, "k - ");
					break;
				}
				p += 0x4;
			}
		}
		p += 0x4;
	}

	/* If there is ram right up to the memory limit this will record
	 * the last address.  */
	if (map[i].start) {
		map[i].end = (long *)m_lim;
		dprint(2 + i, 18, (long)p/1024, 7);
		cprint(2 + i, 25, "k");
		i++;
		map[i].start = 0;
	}

	v->msegs = i;
	v->msg_line = v->msegs + 2;
	v->scroll_start = v->msg_line * 160;
	cprint(1, 34, "Pattern:");
	cprint(2, 34, "Refresh:");
	cprint(2, 43, " Default");
	cprint(0, 63, "  Pass:");
	dprint(0, 71, v->pass, 5);
	cprint(1, 63, "Errors:");
	dprint(1, 71, 0, 5);
	cprint(2, 63, " Cache:");
	cprint(2, 73, " on");
	footer();
}

/*
 * Scroll the error message area of the screen
 * Starts at line 3 + v->msegs and ends at line 24
 */
void scroll() {
	int i, j;
	char *s;
        for (i=4+v->msegs; i<=23; i++) {
		s = (char *)(SCREEN_ADR + (i * 160));
                for (j=0; j<160; j++, s++) {
			*(s-160) = *s;
		}
	}
}

/*
 * Test all of memory using a "moving inversions" algorithm using the
 * pattern in p1 and it's complement in p2.
 */
void check(long p1, long p2)
{
	register int i, j;
	volatile register long *p;
	volatile long *start,*end;
	long bad;

        hprint(1, 43, p1);

	/* Initialize memory with the initial pattern.  */
	do_spin(1);
	for (j=0; j<segs; j++) {
		start = map[j].start;
		end = map[j].end;
		for (p = start; p < end; p++) {
			*p = p1;
		}
	}

	/* Do moving inversions test. Check for initial pattern and then
	 * write the complement for each memory location. Test from bottom
	 * up and then from the top down.  */
	for (i=0; i<3; i++) {
		do_spin(0);
		for (j=0; j<segs; j++) {
			start = map[j].start;
			end = map[j].end;
			for (p = start; p < end; p++) {
				if ((bad=*p) != p1) {
					error((long*)p, p1, bad);
				}
				*p = p2;
			}
		}
		do_spin(0);
		for (j=segs-1; j>=0; j--) {
			start = map[j].start;
			end = map[j].end;
			p = --end;
			do {
				if ((bad=*p) != p2) {
					error((long*)p, p2, bad);
				}
				*p = p1;
			} while (p-- > start);
		}
	}
}

/*
 * Display data error message. Don't display repeat duplicate errors.
 */
void error(long *adr, long good, long bad)
{
	long xor;

	/* Check for keyboard input */
	check_input();

	xor = good ^ bad;

	/* Don't display duplicate errors */
	if (adr == v->eadr && xor == v->exor) {
		dprint(1, 71, ++(v->ecount), 5);
		dprint(v->msg_line, 70, ++cnt, 5);
		return;
	}

        /* Advance line for error message
         * If at the bottom of the screen, scroll */
        if (v->msg_line < 23) {
                v->msg_line++;
        } else {
		/* If scroll lock is on, loop till it is cleared */
		while (slock) {
			check_input();
		}
                scroll();
		ttyprint(23,0,"\n");
        }

	cnt = 1;
	cprint(v->msg_line, 0, "Error - Addrs:");
	hprint(v->msg_line, 11, (long)adr);
	cprint(v->msg_line, 21, "Good:");
	hprint(v->msg_line, 26, good);
	cprint(v->msg_line, 36, "Bad:");
	hprint(v->msg_line, 40, bad);
	cprint(v->msg_line, 50, "Xor:");
	hprint(v->msg_line, 54, xor);
	cprint(v->msg_line, 64, "Count:");
	dprint(v->msg_line, 70, cnt, 5);
	v->eadr = adr;
	v->exor = xor;
	dprint(1, 71, ++(v->ecount), 5);
}

/*
 * Print characters on screen
 */
void cprint(int y, int x, char *text)
{
	register int i;
	char *dptr;

	dptr = (char *)(SCREEN_ADR + (160*y) + (2*x));
	for (i=0; text[i]; i++) {
		*dptr = text[i];
		dptr += 2;
	}
	ttyprint(y,x,text);
}

void itoa(char s[], int n) 
{
  int i, sign;

  if((sign = n) < 0)
    n = -n;
  i=0;
  do {
    s[i++] = n % 10 + '0';
  } while ((n /= 10) > 0);
  if(sign < 0)
    s[i++] = '-';
  s[i] = '\0';
  reverse(s);
}

void reverse(char s[])
{
  int c, i, j;

  for(i=0, j = strlen(s) -1; i < j; i++, j--) {
    c = s[i];
    s[i] = s[j];
    s[j] = c;
  }
}

void ttyprintc(int y, int x, char c)
{
  static char cbuf[2];

  cbuf[0] = c;
  cbuf[1] = '\0';
  ttyprint(y,x,cbuf);
}


void ttyprint(int y, int x, char *p)
{
       static char sx[3];
       static char sy[3];

       sx[0]='\0';
       sy[0]='\0';
       x++; y++;
       itoa(sx, x);
       itoa(sy, y);
       serial_echo_print("[");
       serial_echo_print(sy);
       serial_echo_print(";");
       serial_echo_print(sx);
       serial_echo_print("H");
       serial_echo_print(p);
}

void serial_echo_init()
{
	int comstat, hi, lo;
	
	/* read the Divisor Latch */
	comstat = serial_echo_inb(UART_LCR);
	serial_echo_outb(comstat | UART_LCR_DLAB, UART_LCR);
	hi = serial_echo_inb(UART_DLM);
	lo = serial_echo_inb(UART_DLL);
	serial_echo_outb(comstat, UART_LCR);

	/* now do hardwired init */
	serial_echo_outb(0x03, UART_LCR); /* No parity, 8 data bits, 1 stop */
	serial_echo_outb(0x83, UART_LCR); /* Access divisor latch */
	serial_echo_outb(0x00, UART_DLM); /* 9600 baud */
	serial_echo_outb(0x0c, UART_DLL);
	serial_echo_outb(0x03, UART_LCR); /* Done with divisor */

	/* Prior to disabling interrupts, read the LSR and RBR
	 * registers */
	comstat = serial_echo_inb(UART_LSR); /* COM? LSR */
	comstat = serial_echo_inb(UART_RX);	/* COM? RBR */
	serial_echo_outb(0x00, UART_IER); /* Disable all interrupts */

	return;
}

void serial_echo_print(char *p)
{
  /* Now, do each character */
  while (*p) {
    WAIT_FOR_XMITR;

    /* Send the character out. */
    serial_echo_outb(*p, UART_TX);
    if(*p==10) {
      WAIT_FOR_XMITR;
      serial_echo_outb(13, UART_TX);
    }
    p++;
  }
}
/*
 * Print a decimal number on screen
 */
void dprint(int y, int x, unsigned long val, int len)
{
        unsigned long j, k;
        int i, flag=0;

        for(i=0, j=1; i<len-1; i++) {
                j *= 10;
        }
        for (i=0; j>0; j/=10) {
                k = val/j;
                if (k > 9) {
                        j *= 100;
                        continue;
                }
                if (flag || k || j == 1) {
                        buf[i++] = k + '0';
			flag++;
                } else {
			buf[i++] = ' ';
		}
                val -= k * j;
        }
        buf[i] = 0;
	cprint(y,x,buf);
}

/*
 * Print a hex number on screen
 */
void hprint(int y,int x, unsigned long val)
{
	unsigned long j;
	int i, idx, flag = 0;

        for (i=0, idx=0; i<8; i++) {
                j = val >> (28 - (4 * i));
		j &= 0xf;
		if (j < 10) {
			if (flag || j || i == 7) {
		                buf[idx++] = j + '0';
				flag++;
			} else {
				buf[idx++] = ' ';
			}
		} else {
			buf[idx++] = j + 'a' - 10;
			flag++;
		}
        }
        buf[idx] = 0;
	cprint(y,x,buf);
}
	
/*
 * Display a spinning pattern to show progress
 */
void do_spin(int init)
{
	char *dptr = (char *)(SCREEN_ADR+0x24);

	*dptr = spin[s]&0x7f;
	dptr++;
	*dptr = 0xf;
	ttyprintc(0,18,spin[s]&0x7f);
	if (++s > 3) {
		s = 0;
	}
	if (init) {
		cprint(0, 20, ".        ");
		seq = 0;
	} else {
		seq++;
		cprint(0, 20+seq, ".");
	}

	/* Check for keyboard input */
	check_input();
}

void inter()
{
	
	if (map[0].start == 0) {
		map[0].start = (volatile long *)++p;
		map[0].end = (volatile long *)SKIP_START;
		do_test();
	}
	cprint(18, 0, "Unexpected Interrupt - Halting");
	cprint(19, 0, "    Type: ");
	cprint(20, 0, "      PC: ");
	cprint(21, 0, "   Eflag: ");
	cprint(22, 0, "      CS: ");
	cprint(23, 0, "Err Code: ");
	if (trap_regs[0] <= 18) {
		cprint(19, 10, codes[trap_regs[0]-1]);
	} else {
		hprint(19, 10, trap_regs[0]);
	}
	hprint(20, 10, trap_regs[3]);
	hprint(21, 10, trap_regs[1]);
	hprint(22, 10, trap_regs[2]);
	hprint(23, 10, trap_regs[4]);
	while(1);
}

void set_cache() {
	switch(v->cache_flag) {
	case 0:
		/* Alternate cache on and off for each pass
		 * on for even and off for odd */
		if ((v->pass & 1)) {
			cache_off();
			cprint(2, 73, "off");
		} else {
			cache_on();
			cprint(2, 73, " on");
		}
		break;
	case 1:
		cache_on();
		cprint(2, 73, " ON");
		break;
	case 2:
		cache_off();	
		cprint(2, 73, "OFF");
		break;
	}
}

void set_refresh() {
	switch(v->ref_flag) {
	case 0:
		if (v->pass < 2) {
			return;
		}
		/* For every 2 passes alternate with short and long
		 * refresh rates */
		if ((v->pass & 2)) {
			/* set refresh to 150ms */
			outb(0x74, 0x43);
			outb(0xb4, 0x41);
			outb(0x00, 0x41);
			cprint(2, 43, "Extended (150ms)");
		} else {
			/* set refresh to 15ms */
			outb(0x74, 0x43);
			outb(0x12, 0x41);
			outb(0x00, 0x41);
			cprint(2, 43, "  Normal (15ms) ");
		}
		break;
	case 1:
		/* set refresh to 15ms */
		outb(0x74, 0x43);
		outb(0x12, 0x41);
		outb(0x00, 0x41);
		cprint(2, 43, "  NORMAL (15ms) ");
		break;
	case 2:
		/* set refresh to 150ms */
		outb(0x74, 0x43);
		outb(0xb4, 0x41);
		outb(0x00, 0x41);
		cprint(2, 43, "EXTENDED (150ms)");
		break;
	case 3:
		/* set refresh to 500ms */
		outb(0x74, 0x43);
		outb(0x58, 0x41);
		outb(0x02, 0x41);
		cprint(2, 43, "XLONG (500ms)   ");
		break;
	}
}

int get_key() {
	int c;

	c = __inbc(0x64);
	if ((c & 1) == 0) {
		return(0);
	}
	c = __inbc(0x60);
	return((c & 0x7f));
}

void check_input()
{
	int flag;
	unsigned char c;

	while((c = get_key())) {
		switch(c) {
		case 1:	
		case 42:
			/* "DEL" or "ESC" key was pressed, bail out.  */
			cprint(1, 0, "Halting... ");

			/* tell the BIOS to do a warm start */
			*((unsigned short *)0x472) = 0x1234;

			cache_on();
			outb(0xfe,0x64);
			break;

		case 46:
			/* c - Set cache mode */
			clr_footer();
			cprint(24, 0,
				"Enter Cache Mode: (1)Toggle (2)On (3)Off");
			flag = 0;
			while (1) {
				c = get_key();
				switch(c) {
				case 2:
					/* alternate */
					v->cache_flag = 0;
					set_cache();
					flag++;
					break;
				case 3:
					/* Cache on */
					v->cache_flag = 1;
					set_cache();
					flag++;
					break;
				case 4:
					/* Cache off */
					v->cache_flag = 2;
					set_cache();
					flag++;
					break;
				}
				if (flag) {
					break;
				}
			}
			footer();
			break;

		case 19:
			/* r - Set refresh mode */
			clr_footer();
			cprint(24, 0, "Enter Refresh Mode: (1)Toggle (2)Normal (3)Extended (4)Xlong (5)Nochange");
			flag = 0;
			while (1) {
				c = get_key();
				switch(c) {
				case 2:
					/* alternate */
					v->ref_flag = 0;
					set_refresh();
					flag++;
					break;
				case 3:
					/* refresh normal */
					v->ref_flag = 1;
					set_refresh();
					flag++;
					break;
				case 4:
					/* refresh extended */
					v->ref_flag = 2;
					set_refresh();
					flag++;
					break;
				case 5:
					/* refresh way long */
					v->ref_flag = 3;
					set_refresh();
					flag++;
					break;
				case 6:
					/* refresh nochange */
					v->ref_flag = 4;
					set_refresh();
					flag++;
					break;
				}
				if (flag) {
					break;
				}
			}
			footer();
			break;
		case 44:
			/* z - test mode */
			map[v->msegs-1].end = (long *)0x180000;
			dprint(1+v->msegs, 18, (long)map[v->msegs-1].end/1024, 7);
			break;
		case 28:
			/* SP - set scroll lock */
			slock = 0;
			footer();
			break;
		case 57:
			/* CR - clear scroll lock */
			slock = 1;
			footer();
			break;
		}
	}
}

void footer()
{
	clr_footer();
	cprint(24, 0, "(ESC)exit (c)cache_mode (r)refresh_mode (SP)scroll_lock (CR)scroll_unlock");
	if (slock) {
		cprint(24, 74, "Locked");
	} else {
		cprint(24, 84, "      ");
	}
}

void clr_footer()
{
	int i;
	char *pp;

	for(i=0, pp=(char *)(SCREEN_ADR+(24 * 160)); i<80; i++, pp+=2) {
		*pp = ' ';
	}
}
