/* ================================================================================== */
/* Copyright (c) 1998-1999 3Com Corporation or its subsidiaries. All rights reserved. */
/* ================================================================================== */

#include "EmulatorCommon.h"
#include "Bank_SRAM.h"

#include "Byteswapping.h"		// ByteswapWords
#include "CPU_REG.h"			// Software::BusError
#include "DebugMgr.h"			// Debug::CheckStepSpy
#include "RAM_ROM.h"			// gRAMBank_Size, gRAM_Memory, gMemoryAccess
#include "SessionFile.h"		// WriteRAMImage


// ===========================================================================
//		¥ SRAM Bank Accessors
// ===========================================================================
// These functions provide fetch and store access to the emulator's random
// access memory.

static AddressBank	gAddressBank =
{
	SRAMBank::GetLong,
	SRAMBank::GetWord,
	SRAMBank::GetByte,
	SRAMBank::SetLong,
	SRAMBank::SetWord,
	SRAMBank::SetByte,
	SRAMBank::GetRealAddress,
	SRAMBank::ValidAddress,
	SRAMBank::GetMetaAddress,
	SRAMBank::AddOpcodeCycles
};

	// Note: I'd've used hwrCardBase0 here, except that that
	// changes on different hardware. It's 0x10000000 in some
	// cases, 0x00000000 in others.

static const uaecptr	kMemoryStart	= 0x10000000;
static const uaecptr	kMemoryStartEZ	= 0x00000000;
uaecptr					gMemoryStart	= kMemoryStart;

uae_u32			gRAMBank_Size;
uae_u32			gRAMBank_Mask;
uae_u8*			gRAM_Memory;
uae_u8*			gRAM_MetaMemory;

#if defined (_DEBUG)

	// In debug mode, define a global variable that points to the
	// Palm ROM's low-memory globals.  That makes it easier to find
	// out what's going wrong when something goes wrong.
	//
	// Keep in mind that on Windows, all values are "wordswapped"
	// (each pair of bytes is byteswapped).

	LowMemHdrType*	gLowMemory;

#endif


/***********************************************************************
 *
 * FUNCTION:	SRAMBank::Initialize
 *
 * DESCRIPTION:	Standard initialization function.  Responsible for
 *				initializing this sub-system when a new session is
 *				created.  May also be called from the Load function
 *				to share common functionality.
 *
 * PARAMETERS:	None.
 *
 * RETURNED:	Nothing.
 *
 ***********************************************************************/

void SRAMBank::Initialize (uae_s32 iRAMSize)
{
	if (iRAMSize > 0)
	{
		gRAMBank_Size	= iRAMSize * 1024;
		gRAMBank_Mask	= gRAMBank_Size - 1;
		gRAM_Memory		= (uae_u8*) Platform::AllocateMemory (gRAMBank_Size, true);
		gRAM_MetaMemory	= (uae_u8*) Platform::AllocateMemory (gRAMBank_Size, true);

#if defined (_DEBUG)
		// In debug mode, define a global variable that points to the
		// Palm ROM's low-memory globals.  That makes it easier to find
		// out what's going wrong when something goes wrong.

		gLowMemory		= (LowMemHdrType*) gRAM_Memory;
#endif
	}

	if (Emulator::EZMode ())
		gMemoryStart = kMemoryStartEZ;
	else
		gMemoryStart = kMemoryStart;

	// Memory banks 0x1000 to <mumble> are managed by the functions
	// in SRAMBank.  <mumble> is based on the amount of RAM being emulated.

	long	numBanks = bankindex (gMemoryStart + gRAMBank_Size - 1) -
									bankindex (gMemoryStart) + 1;
	Memory::InitializeBanks (gAddressBank, bankindex (gMemoryStart), numBanks);
}


/***********************************************************************
 *
 * FUNCTION:	SRAMBank::Reset
 *
 * DESCRIPTION:	Standard reset function.  Sets the sub-system to a
 *				default state.  This occurs not only on a Reset (as
 *				from the menu item), but also when the sub-system
 *				is first initialized (Reset is called after Initialize)
 *				as well as when the system is re-loaded from an
 *				insufficient session file.
 *
 * PARAMETERS:	None.
 *
 * RETURNED:	Nothing.
 *
 ***********************************************************************/

void SRAMBank::Reset (void)
{
	memset (gRAM_MetaMemory, 0, gRAMBank_Size);
}


/***********************************************************************
 *
 * FUNCTION:	SRAMBank::Save
 *
 * DESCRIPTION:	Standard save function.  Saves any sub-system state to
 *				the given session file.
 *
 * PARAMETERS:	None.
 *
 * RETURNED:	Nothing.
 *
 ***********************************************************************/

void SRAMBank::Save (SessionFile& f)
{
	StWordSwapper	swapper1 (gRAM_Memory, gRAMBank_Size);
	f.WriteRAMImage (gRAM_Memory, gRAMBank_Size);

	StWordSwapper	swapper2 (gRAM_MetaMemory, gRAMBank_Size);
	f.WriteMetaRAMImage (gRAM_MetaMemory, gRAMBank_Size);
}


/***********************************************************************
 *
 * FUNCTION:	SRAMBank::Load
 *
 * DESCRIPTION:	Standard load function.  Loads any sub-system state
 *				from the given session file.
 *
 * PARAMETERS:	None.
 *
 * RETURNED:	Nothing.
 *
 ***********************************************************************/

void SRAMBank::Load (SessionFile& f)
{
	assert (gRAM_Memory);
	assert (gRAM_MetaMemory);

	if (f.ReadRAMImage (gRAM_Memory))
	{
		ByteswapWords (gRAM_Memory, gRAMBank_Size);
	}
	else
	{
		f.SetCanReload (false);
	}

	if (f.ReadMetaRAMImage (gRAM_MetaMemory))
	{
		ByteswapWords (gRAM_MetaMemory, gRAMBank_Size);
	}
	else
	{
		f.SetCanReload (false);
	}
}


/***********************************************************************
 *
 * FUNCTION:	SRAMBank::Dispose
 *
 * DESCRIPTION:	Standard dispose function.  Completely release any
 *				resources acquired or allocated in Initialize and/or
 *				Load.
 *
 * PARAMETERS:	None.
 *
 * RETURNED:	Nothing.
 *
 ***********************************************************************/

void SRAMBank::Dispose (void)
{
	Platform::DisposeMemory (gRAM_Memory);
	Platform::DisposeMemory (gRAM_MetaMemory);
}


// ---------------------------------------------------------------------------
//		¥ SRAMBank::GetLong
// ---------------------------------------------------------------------------

uae_u32 SRAMBank::GetLong (uaecptr iAddress)
{
#if PROFILE_MEMORY
	gMemoryAccess[kSRAMLongRead]++;
	if (iAddress & 2)
		gMemoryAccess[kSRAMLongRead2]++;
#endif

#if (CHECK_FOR_ADDRESS_ERROR)
	if ((iAddress & 1) != 0)
	{
		Software::AddressError ();
	}
#endif

#if (PREVENT_USER_SRAM_GET)
	if (gMemAccessFlags.fProtect_SRAMGet)
	{
		ProtectedAccess (iAddress, sizeof (long), true);
	}
#endif

#if (VALIDATE_SRAM_GET)
	if (gMemAccessFlags.fValidate_SRAMGet && !ValidAddress (iAddress, sizeof (long)))
	{
		InvalidAccess (iAddress, sizeof (long), true);
	}
#endif

#if HAS_PROFILING
	CYCLE_GETLONG (WAITSTATES_SRAM)
#endif

	iAddress &= gRAMBank_Mask;

	return do_get_mem_long (gRAM_Memory + iAddress);
}


// ---------------------------------------------------------------------------
//		¥ SRAMBank::GetWord
// ---------------------------------------------------------------------------

uae_u32 SRAMBank::GetWord (uaecptr iAddress)
{
#if PROFILE_MEMORY
	gMemoryAccess[kSRAMWordRead]++;
#endif

#if (CHECK_FOR_ADDRESS_ERROR)
	if ((iAddress & 1) != 0)
	{
		Software::AddressError ();
	}
#endif

#if (PREVENT_USER_SRAM_GET)
	if (gMemAccessFlags.fProtect_SRAMGet)
	{
		ProtectedAccess (iAddress, sizeof (short), true);
	}
#endif

#if (VALIDATE_SRAM_GET)
	if (gMemAccessFlags.fValidate_SRAMGet && !ValidAddress (iAddress, sizeof (short)))
	{
		InvalidAccess (iAddress, sizeof (short), true);
	}
#endif

#if HAS_PROFILING
	CYCLE_GETWORD (WAITSTATES_SRAM)
#endif

	iAddress &= gRAMBank_Mask;

	return do_get_mem_word (gRAM_Memory + iAddress);
}


// ---------------------------------------------------------------------------
//		¥ SRAMBank::GetByte
// ---------------------------------------------------------------------------

uae_u32 SRAMBank::GetByte (uaecptr iAddress)
{
#if PROFILE_MEMORY
	gMemoryAccess[kSRAMByteRead]++;
#endif

#if (PREVENT_USER_SRAM_GET)
	if (gMemAccessFlags.fProtect_SRAMGet)
	{
		ProtectedAccess (iAddress, sizeof (char), true);
	}
#endif

#if (VALIDATE_SRAM_GET)
	if (gMemAccessFlags.fValidate_SRAMGet && !ValidAddress (iAddress, sizeof (char)))
	{
		InvalidAccess (iAddress, sizeof (char), true);
	}
#endif

#if HAS_PROFILING
	CYCLE_GETBYTE (WAITSTATES_SRAM)
#endif

	iAddress &= gRAMBank_Mask;

	return do_get_mem_byte (gRAM_Memory + iAddress);
}


// ---------------------------------------------------------------------------
//		¥ SRAMBank::SetLong
// ---------------------------------------------------------------------------

void SRAMBank::SetLong (uaecptr iAddress, uae_u32 iLongValue)
{
#if PROFILE_MEMORY
	gMemoryAccess[kSRAMLongWrite]++;
	if (iAddress & 2)
		gMemoryAccess[kSRAMLongWrite2]++;
#endif

#if (CHECK_FOR_ADDRESS_ERROR)
	if ((iAddress & 1) != 0)
	{
		Software::AddressError ();
	}
#endif

#if (PREVENT_USER_SRAM_SET)
	if (gMemAccessFlags.fProtect_SRAMSet)
	{
		ProtectedAccess (iAddress, sizeof (long), false);
	}
#endif

#if (VALIDATE_SRAM_SET)
	if (gMemAccessFlags.fValidate_SRAMSet && !ValidAddress (iAddress, sizeof (long)))
	{
		InvalidAccess (iAddress, sizeof (long), false);
	}
#endif

#if HAS_PROFILING
	CYCLE_PUTLONG (WAITSTATES_SRAM);
#endif

	uaecptr	phyAddress = iAddress;
	phyAddress &= gRAMBank_Mask;

	do_put_mem_long (gRAM_Memory + phyAddress, iLongValue);

	// See if any interesting memory locations have changed.  If so,
	// CheckStepSpy will change gBreakReason, which we'll check later.

	Debug::CheckStepSpy (iAddress, 4);
}


// ---------------------------------------------------------------------------
//		¥ SRAMBank::SetWord
// ---------------------------------------------------------------------------

void SRAMBank::SetWord (uaecptr iAddress, uae_u32 iWordValue)
{
#if PROFILE_MEMORY
	gMemoryAccess[kSRAMWordWrite]++;
#endif

#if (CHECK_FOR_ADDRESS_ERROR)
	if ((iAddress & 1) != 0)
	{
		Software::AddressError ();
	}
#endif

#if (PREVENT_USER_SRAM_SET)
	if (gMemAccessFlags.fProtect_SRAMSet)
	{
		ProtectedAccess (iAddress, sizeof (short), false);
	}
#endif

#if (VALIDATE_SRAM_SET)
	if (gMemAccessFlags.fValidate_SRAMSet && !ValidAddress (iAddress, sizeof (short)))
	{
		InvalidAccess (iAddress, sizeof (short), false);
	}
#endif

#if HAS_PROFILING
	CYCLE_PUTWORD (WAITSTATES_SRAM)
#endif

	uaecptr	phyAddress = iAddress;
	phyAddress &= gRAMBank_Mask;

	do_put_mem_word (gRAM_Memory + phyAddress, iWordValue);

	// See if any interesting memory locations have changed.  If so,
	// CheckStepSpy will change gBreakReason, which we'll check later.

	Debug::CheckStepSpy (iAddress, 2);
}


// ---------------------------------------------------------------------------
//		¥ SRAMBank::SetByte
// ---------------------------------------------------------------------------

void SRAMBank::SetByte (uaecptr iAddress, uae_u32 iByteValue)
{
#if PROFILE_MEMORY
	gMemoryAccess[kSRAMByteWrite]++;
#endif

#if (PREVENT_USER_SRAM_SET)
	if (gMemAccessFlags.fProtect_SRAMSet)
	{
		ProtectedAccess (iAddress, sizeof (char), false);
	}
#endif

#if (VALIDATE_SRAM_SET)
	if (gMemAccessFlags.fValidate_SRAMSet && !ValidAddress (iAddress, sizeof (char)))
	{
		InvalidAccess (iAddress, sizeof (char), false);
	}
#endif

#if HAS_PROFILING
	CYCLE_PUTBYTE (WAITSTATES_SRAM)
#endif

	uaecptr	phyAddress = iAddress;
	phyAddress &= gRAMBank_Mask;

	do_put_mem_byte (gRAM_Memory + phyAddress, iByteValue);

	// See if any interesting memory locations have changed.  If so,
	// CheckStepSpy will change gBreakReason, which we'll check later.

	Debug::CheckStepSpy (iAddress, 1);
}


// ---------------------------------------------------------------------------
//		¥ SRAMBank::ValidAddress
// ---------------------------------------------------------------------------

int SRAMBank::ValidAddress (uaecptr iAddress, uae_u32 iSize)
{
	int	result = false;

	// Check for a valid RAM access.

	// Strip the uppermost bit of the address.

	iAddress &= gRAMBank_Mask;

	result = (iAddress + iSize) <= gRAMBank_Size;

	assert (result);

	return result;
}


// ---------------------------------------------------------------------------
//		¥ SRAMBank::GetRealAddress
// ---------------------------------------------------------------------------

uae_u8* SRAMBank::GetRealAddress (uaecptr iAddress)
{
	// Strip the uppermost bit of the address.

	iAddress &= gRAMBank_Mask;

	return (uae_u8*) &(gRAM_Memory[iAddress]);
}


// ---------------------------------------------------------------------------
//		¥ SRAMBank::GetMetaAddress
// ---------------------------------------------------------------------------

uae_u8* SRAMBank::GetMetaAddress (uaecptr iAddress)
{
	// Strip the uppermost bit of the address.

	iAddress &= gRAMBank_Mask;

	return (uae_u8*) &(gRAM_MetaMemory[iAddress]);
}


// ---------------------------------------------------------------------------
//		¥ SRAMBank::AddOpcodeCycles
// ---------------------------------------------------------------------------

void SRAMBank::AddOpcodeCycles (void)
{
#if (HAS_PROFILING)
	CYCLE_GETWORD (WAITSTATES_SRAM);
#endif
}


// ---------------------------------------------------------------------------
//		¥ SRAMBank::InvalidAccess
// ---------------------------------------------------------------------------

void SRAMBank::InvalidAccess (uaecptr iAddress, long size, Bool forRead)
{
	UNUSED_PARAM(iAddress)
	UNUSED_PARAM(size)
	UNUSED_PARAM(forRead)

	Software::BusError ();
}


// ---------------------------------------------------------------------------
//		¥ SRAMBank::ProtectedAccess
// ---------------------------------------------------------------------------

void SRAMBank::ProtectedAccess (uaecptr iAddress, long size, Bool forRead)
{
	UNUSED_PARAM(iAddress)
	UNUSED_PARAM(size)
	UNUSED_PARAM(forRead)

	Software::BusError ();
}

