/*

mem.c - MTX memory

We have a 8KB monitor ROM and 8 other 8KB ROMs.
By default, ROM 2 is subpaged into 16 subpages.
We have a number of 16KB RAM pages.

Memory is never paged in less than 8KB chunks.
8 chunks comprise the whole 64KB visible to the Z80.
So we keep two maps, one for reading, one for writing.

This version has changes by Bill Brendling, allowing larger memory sizes,
and selective enabling of ROMs.

It implements the memory map rules, as documented in the manual,
as implemented correctly by MTX500 and MTX512, and REMEMOTECH.
However, the 512KB extra memory on certain SDXs is known to fail to
implement the rules properly, and it is expected that MTX S2 and MTX 2000
could have problems also.

*/

/*...sincludes:0:*/
#include <stdio.h>
#include <string.h>

#include "types.h"
#include "diag.h"
#include "common.h"
#include "roms.h"
#include "mem.h"

/*...vtypes\46\h:0:*/
/*...vdiag\46\h:0:*/
/*...vcommon\46\h:0:*/
/*...vroms\46\h:0:*/
/*...vmem\46\h:0:*/
/*...e*/

static byte mem_high[ROM_SIZE]; /* Read this when no chip selected (all 1's) */
static byte mem_vapor[ROM_SIZE]; /* Write here when no chip, or ROM selected */
static byte mem_rom_os[ROM_SIZE]; /* Monitor ROM */
static int mem_n_subpages[8] = { 0,0,0,0,0,0,0,0 };
static byte *mem_subpages[8][0x100];
static byte *mem_ram[MAX_BLOCKS];
static byte *mem_read[8]; /* Read through these */
static byte *mem_write[8]; /* Write through these */
static byte mem_iobyte; /* IOBYTE */
static byte mem_subpage = 0x00;
static int mem_blocks;

/* If enabled, you can snapshot RAM, and query from it */
static byte *mem_ram_snapshot[MAX_BLOCKS];
static int mem_blocks_snapshot;

#ifdef DYNAMIC_ROMS
static int rom_enable = 0xff;
/*...smem_get_rom_enable:0:*/
int mem_get_rom_enable (void)
	{
	return rom_enable;
	}
/*...e*/
/*...smem_set_rom_enable:0:*/
void mem_set_rom_enable(int ienable)
	{
	rom_enable = ienable;
	}
/*...e*/
#endif

/*...smem_set_n_subpages:0:*/
void mem_set_n_subpages(int rom, int n_subpages)
	{
	int i;
	for ( i = mem_n_subpages[rom]; i < n_subpages; i++ )
		{
		mem_subpages[rom][i] = (byte *) emalloc(ROM_SIZE);
		memset(mem_subpages[rom][i], 0xff, ROM_SIZE);
		}
	for ( ; i < mem_n_subpages[rom]; i++ )
		free(mem_subpages[rom][i]);
	mem_n_subpages[rom] = n_subpages;
	}
/*...e*/
/*...smem_get_n_subpages:0:*/
int mem_get_n_subpages(int rom)
	{
	return mem_n_subpages[rom];
	}
/*...e*/

/*...sRdZ80:0:*/
byte RdZ80(word addr)
	{
	return mem_read[addr>>13][addr&0x1fff];
	}
/*...e*/
/*...sWrZ80:0:*/
void WrZ80(word addr, byte value)
	{
#if 0
	if ( diag_flags[DIAG_SPEED] && addr < 0x3700 )
		{
		diag_message(DIAG_ALWAYS, "ouch");
		return;
		}
#endif
	if ( (addr>>13) != 0 || (mem_iobyte&0x80) != 0 )
		/* Normal write */
		mem_write[addr>>13][addr&0x1fff] = value;
	else
		/* Write to first 8KB in RELCPMH=0 mode, sets sub-page */
		{
		diag_message(DIAG_MEM_SUBPAGE, "ROM sub-page set to 0x%02x", value);
		mem_set_rom_subpage(value);
		}
	}
/*...e*/

/*...smem_read_byte:0:*/
byte mem_read_byte(word addr)
	{
	return mem_read[addr>>13][addr&0x1fff];
	}
/*...e*/
/*...smem_write_byte:0:*/
/* Notice this deliberately uses mem_read.
   In other words, you can write ROM using this call.
   This call is used internally within MEMU, but not by the Z80.
   The Z80 will use WrZ80, which can't. */

void mem_write_byte(word addr, byte value)
	{
	mem_read[addr>>13][addr&0x1fff] = value;
	}
/*...e*/
/*...smem_read_block:0:*/
void mem_read_block(word addr, word len, byte *buf)
	{
	while ( len-- )
		*buf++ = mem_read_byte(addr++);
	}
/*...e*/
/*...smem_write_block:0:*/
void mem_write_block(word addr, word len, const byte *buf)
	{
	while ( len-- )
		mem_write_byte(addr++, *buf++);
	}
/*...e*/

/*...smem_set_iobyte:0:*/
/* This happens infrequently, so we do lots of hard work here,
   to ensure that RdZ80 and WrZ80 can be very fast.
   Note that mem_ram[3] is both
     RELCPMH=1 P=0 0x0000-0x3fff
     RELCPMH=0 P=1 0x8000-0xbfff
   Looking at the PALASM source we can work out what happens in RELCPMH=1
   mode when there is only 32KB of RAM. p247 doesn't show it.
   Note also that there is a magic jumper on the board called I2H4L which is
   wired high on MTX500, preventing 16KB of the RAM appearing more than once.
   Its wired low on MTX512, otherwise lots of RAM would vanish.
*/

#if 0
/*...smy original implementation:0:*/
void mem_set_iobyte(byte val)
	{
	mem_iobyte = val;
	if ( mem_iobyte & 0x80 )
		{
		mem_read[7] = mem_write[7] = mem_ram[0]+ROM_SIZE;
		mem_read[6] = mem_write[6] = mem_ram[0];
		switch ( mem_iobyte & 0x0f )
			{
			case 0:
				mem_read[5] = mem_write[5] = mem_ram[1]+ROM_SIZE;
				mem_read[4] = mem_write[4] = mem_ram[1];
				if ( mem_500 )
					{
					mem_read[3] = mem_high; mem_write[3] = mem_vapor;
					mem_read[2] = mem_high; mem_write[2] = mem_vapor;
					mem_read[1] = mem_high; mem_write[1] = mem_vapor;
					mem_read[0] = mem_high; mem_write[0] = mem_vapor;
					}
				else
					{
					mem_read[3] = mem_write[3] = mem_ram[2]+ROM_SIZE;
					mem_read[2] = mem_write[2] = mem_ram[2];
					mem_read[1] = mem_write[1] = mem_ram[3]+ROM_SIZE;
					mem_read[0] = mem_write[0] = mem_ram[3];
					}
				break;
			default:
				mem_read[5] = mem_high; mem_write[5] = mem_vapor;
				mem_read[4] = mem_high; mem_write[4] = mem_vapor;
				mem_read[3] = mem_high; mem_write[3] = mem_vapor;
				mem_read[2] = mem_high; mem_write[2] = mem_vapor;
				mem_read[1] = mem_high; mem_write[1] = mem_vapor;
				mem_read[0] = mem_high; mem_write[0] = mem_vapor;
				break;
			}
		}
	else
		{
		mem_read[7] = mem_write[7] = mem_ram[0]+ROM_SIZE;
		mem_read[6] = mem_write[6] = mem_ram[0];
		switch ( mem_iobyte & 0x0f )
			{
			case 0x00:
				mem_read[5] = mem_write[5] = mem_ram[1]+ROM_SIZE;
				mem_read[4] = mem_write[4] = mem_ram[1];
				if ( mem_500 )
					{
					mem_read[3] = mem_high; mem_write[3] = mem_vapor;
					mem_read[2] = mem_high; mem_write[2] = mem_vapor;
					}
				else
					{
					mem_read[3] = mem_write[3] = mem_ram[2]+ROM_SIZE;
					mem_read[2] = mem_write[2] = mem_ram[2];
					}
				break;
			case 0x01:
				if ( mem_500 )
					{
					mem_read[5] = mem_high; mem_write[5] = mem_vapor;
					mem_read[4] = mem_high; mem_write[4] = mem_vapor;
					}
				else
					{
					mem_read[5] = mem_write[5] = mem_ram[3]+ROM_SIZE;
					mem_read[4] = mem_write[4] = mem_ram[3];
					}
				mem_read[3] = mem_high; mem_write[3] = mem_vapor;
				mem_read[2] = mem_high; mem_write[2] = mem_vapor;
				break;
			default:
				mem_read[5] = mem_high; mem_write[5] = mem_vapor;
				mem_read[4] = mem_high; mem_write[4] = mem_vapor;
				mem_read[3] = mem_high; mem_write[3] = mem_vapor;
				mem_read[2] = mem_high; mem_write[2] = mem_vapor;
				break;
			}
		mem_read[1] = mem_rom[(mem_iobyte>>4)&7]; mem_write[1] = mem_vapor;
		mem_read[0] = mem_rom_os; mem_write[0] = mem_vapor;
		}
	}
/*...e*/
#endif

/*...smem_set_iobyte_ram:0:*/
static void mem_set_iobyte_ram(int ipage, int iblock)
	{
	if ( iblock < mem_blocks )
		{
		mem_read [2*ipage  ] = mem_ram[iblock]         ;
		mem_write[2*ipage  ] = mem_ram[iblock]         ;
		mem_read [2*ipage+1] = mem_ram[iblock]+ROM_SIZE;
		mem_write[2*ipage+1] = mem_ram[iblock]+ROM_SIZE;
		}
	else
		{
		mem_read [2*ipage  ] = mem_high ;
		mem_write[2*ipage  ] = mem_vapor;
		mem_read [2*ipage+1] = mem_high ;
		mem_write[2*ipage+1] = mem_vapor;
		}
	}
/*...e*/

void mem_set_iobyte(byte val)
	{
	int iblock, ipage;

	mem_iobyte = val;
	mem_read[7] = mem_write[7] = mem_ram[0]+ROM_SIZE;
	mem_read[6] = mem_write[6] = mem_ram[0]         ;
	if ( mem_iobyte & 0x80 )
		{
		iblock = 3 * ( mem_iobyte & 0x0f ) + 1;
		if ( mem_iobyte & 0x0f )
			for ( ipage = 0; ipage <= 2; ++ipage, ++iblock )
				mem_set_iobyte_ram(ipage, iblock);
		else
			for ( ipage = 2; ipage >= 0; --ipage, ++iblock )
				mem_set_iobyte_ram(ipage, iblock);
		}
	else
		{
		int irom = ( mem_iobyte >> 4 ) & 7;
#ifdef DYNAMIC_ROMS
		if ( ( ( rom_enable >> irom ) & 0x01 ) == 0 )
			mem_read[1] = mem_high;
		else
#endif
			{
			int mask = mem_n_subpages[irom]-1;
			mem_read[1] = mem_subpages[irom][mem_subpage&mask];
			}
		mem_write[1] = mem_vapor;
		mem_read[0] = mem_rom_os; mem_write[0] = mem_vapor;
		iblock = 2 * ( mem_iobyte & 0x0f ) + 1;
		for ( ipage = 2; ipage >= 1; --ipage, ++iblock )
			mem_set_iobyte_ram(ipage, iblock);
		}
	}
/*...e*/
/*...smem_get_iobyte:0:*/
byte mem_get_iobyte(void)
	{
	return mem_iobyte;
	}
/*...e*/

/*...smem_get_rom_subpage:0:*/
byte mem_get_rom_subpage(void)
	{
	return mem_subpage;
	}
/*...e*/
/*...smem_set_rom_subpage:0:*/
void mem_set_rom_subpage(byte subpage)
	{
	mem_subpage = subpage;
	mem_set_iobyte(mem_iobyte);
	}
/*...e*/

/*...smem_out0:0:*/
void mem_out0(byte val)
	{
	diag_message(DIAG_MEM_IOBYTE, "memory IOBYTE set to 0x%02x", val);
	mem_set_iobyte(val);
	}
/*...e*/

/*...smem_alloc:0:*/
void mem_alloc(int nblocks)
	{
	int i;
	if ( ( nblocks < 2 ) || ( nblocks > MAX_BLOCKS ) )
		fatal("invalid amount of memory");
	mem_blocks = nblocks;
	for ( i = 0; i < nblocks; ++i )
		if ( mem_ram[i] == NULL )
			mem_ram[i] = (byte *) emalloc(0x4000);
	mem_set_iobyte(mem_iobyte); /* Recalculate page visibility */
	}
/*...e*/
/*...smem_get_alloc:0:*/
int mem_get_alloc(void)
	{
	return mem_blocks;
	}
/*...e*/

/*...smem_alloc_snapshot:0:*/
void mem_alloc_snapshot(int nblocks)
	{
	int i;
	if ( ( nblocks < 0 ) || ( nblocks > MAX_BLOCKS ) )
		fatal("invalid amount of snapshot memory");
	mem_blocks_snapshot = nblocks;
	for ( i = 0; i < nblocks; ++i )
		if ( mem_ram_snapshot[i] == NULL )
			mem_ram_snapshot[i] = (byte *) emalloc(0x4000);
	}
/*...e*/
/*...smem_snapshot:0:*/
void mem_snapshot(void)
	{
	int i;
	for ( i = 0; i < mem_blocks && i < mem_blocks_snapshot; i++ )
		memcpy(mem_ram_snapshot[i], mem_ram[i], 0x4000);
	}
/*...e*/
/*...smem_read_byte_snapshot:0:*/
byte mem_read_byte_snapshot(word addr)
	{
	byte *p = mem_read[addr>>13];
	int i;
	for ( i = 0; i < mem_blocks && i < mem_blocks_snapshot; i++ )
		if ( p >= mem_ram[i] && p < mem_ram[i]+0x4000 )
			{
			p = p - mem_ram[i] + mem_ram_snapshot[i];
			break;
			}
	return p[addr&0x1fff];
	}
/*...e*/

/*...smem_type_at_address:0:*/
int mem_type_at_address(word addr)
	{
	byte *p = mem_read[addr>>13];
	int i, j;
	for ( i = 0; i < mem_blocks && i < mem_blocks_snapshot; i++ )
		if ( p >= mem_ram[i] && p < mem_ram[i]+0x4000 )
			return MEMT_RAM_SNAPSHOT;
	for ( ; i < mem_blocks ; i++ )
		if ( p >= mem_ram[i] && p < mem_ram[i]+0x4000 )
			return MEMT_RAM_NO_SNAPSHOT;
	if ( p >= mem_rom_os && p < mem_rom_os+ROM_SIZE )
		return MEMT_ROM;
	for ( i = 0; i < 8; i++ )	
		for ( j = 0; j < mem_n_subpages[i]; j++ )
			if ( p >= mem_subpages[i][j] && p < mem_subpages[i][j]+ROM_SIZE )
				return MEMT_ROM;
	return MEMT_VOID;
	}
/*...e*/

/*...smem_init:0:*/
void mem_init(void)
	{
	int i;
	for ( i = 0; i < MAX_BLOCKS; ++i )
		{
		mem_ram         [i] = NULL;
		mem_ram_snapshot[i] = NULL;
		}

	memset(mem_high, 0xff, ROM_SIZE);

	mem_set_n_subpages(0,  1);
	mem_set_n_subpages(1,  1);
	mem_set_n_subpages(2, 16);
	mem_set_n_subpages(3,  1);
	mem_set_n_subpages(4,  1);
	mem_set_n_subpages(5,  1);
	mem_set_n_subpages(6,  1);
	mem_set_n_subpages(7,  1);
	memcpy(mem_rom_os        , rom_os   , ROM_SIZE);
	memcpy(mem_subpages[0][0], rom_basic, ROM_SIZE);
	memcpy(mem_subpages[1][0], rom_assem, ROM_SIZE);

	mem_alloc(4);
	mem_alloc_snapshot(0);

	mem_set_iobyte(0x00);
	}
/*...e*/
/*...smem_dump:0:*/
/* Just a chance to dump the Z80 address space on exit */

void mem_dump(void)
	{
	if ( diag_flags[DIAG_MEM_DUMP] )
		{
		FILE *fp;
		byte buf[0x10000];
		/* Can't read 0x10000 in one go */
		mem_read_block(0x0000,0xffff,buf);
		buf[0xffff] = mem_read_byte(0xffff);
		if ( (fp = fopen("memu.mem", "wb")) != NULL )
			{
			fwrite(buf, 1, sizeof(buf), fp);
			fclose(fp);
			}
		}
	}
/*...e*/

/*...smem_rom_ptr:0:*/
/* Backdoor access to memory map implementation.
   Used by keyboard remapping logic, to patch ROM. */

byte *mem_rom_ptr(int rom)
	{
	int mask = mem_n_subpages[rom]-1;
	return mem_subpages[rom][mem_subpage&mask];
	}
/*...e*/
