Cores/Nds/CP15.cs
namespace Emulotl.Nds;

public sealed partial class ARMv5
{
	private const uint ItcmMask = NdsConstants.Arm9ItcmSize - 1;
	private const uint DtcmMask = NdsConstants.Arm9DtcmSize - 1;
	private const int CodeCacheTiming = 3;

	public void CP15Reset()
	{
		CP15Control = 0x2078;

		RNGSeed = 44203;
		TraceProcessID = 0;

		DTCMSetting = 0;
		ITCMSetting = 0;

		Array.Clear( ITCM );
		Array.Clear( DTCM );

		ITCMSize = 0;
		DTCMBase = 0xFFFFFFFF;
		DTCMMask = 0;

		Array.Clear( ICache );
		Array.Clear( ICacheTags );
		Array.Clear( ICacheCount );

		PU_CodeCacheable = 0;
		PU_DataCacheable = 0;
		PU_DataCacheWrite = 0;
		PU_CodeRW = 0;
		PU_DataRW = 0;

		for ( int i = 0; i < 8; i++ )
			PU_Region[i] = 0;

		UpdateRegionTimings( 0, 0x100000 );

		UpdatePURegions( true );
	}

	public void UpdateRegionTimings( uint addrstart, uint addrend )
	{
		for ( uint i = addrstart; i < addrend; i++ )
		{
			byte pu = PU_Map[i];
			uint bt0 = NDS.ARM9MemTimings[i >> 2, 0];
			uint bt2 = NDS.ARM9MemTimings[i >> 2, 2];
			uint bt3 = NDS.ARM9MemTimings[i >> 2, 3];

			MemTimings[i, 0] = (pu & 0x40) != 0 ? (byte)0xFF : (byte)(bt2 << 1);

			if ( (pu & 0x10) != 0 )
			{
				MemTimings[i, 1] = 3;
				MemTimings[i, 2] = 3;
				MemTimings[i, 3] = 1;
			}
			else
			{
				MemTimings[i, 1] = (byte)(bt0 << 1);
				MemTimings[i, 2] = (byte)(bt2 << 1);
				MemTimings[i, 3] = (byte)(bt3 << 1);
			}
		}
	}

	public void UpdateDTCMSetting()
	{
		uint newDTCMBase;
		uint newDTCMMask;
		uint newDTCMSize;

		if ( (CP15Control & (1 << 16)) != 0 )
		{
			newDTCMSize = 0x200u << (int)((DTCMSetting >> 1) & 0x1F);
			if ( newDTCMSize < 0x1000 ) newDTCMSize = 0x1000;
			newDTCMMask = 0xFFFFF000 & ~(newDTCMSize - 1);
			newDTCMBase = DTCMSetting & newDTCMMask;
		}
		else
		{
			newDTCMSize = 0;
			newDTCMBase = 0xFFFFFFFF;
			newDTCMMask = 0;
		}

		DTCMBase = newDTCMBase;
		DTCMMask = newDTCMMask;
	}

	public void UpdateITCMSetting()
	{
		if ( (CP15Control & (1 << 18)) != 0 )
			ITCMSize = 0x200u << (int)((ITCMSetting >> 1) & 0x1F);
		else
			ITCMSize = 0;
	}

	public void UpdatePURegion( uint n )
	{
		if ( (CP15Control & (1 << 0)) == 0 )
			return;

		uint coderw = (PU_CodeRW >> (int)(4 * n)) & 0xF;
		uint datarw = (PU_DataRW >> (int)(4 * n)) & 0xF;

		uint codecache, datacache, datawrite;

		if ( (CP15Control & (1 << 12)) != 0 )
			codecache = (PU_CodeCacheable >> (int)n) & 0x1;
		else
			codecache = 0;

		if ( (CP15Control & (1 << 2)) != 0 )
		{
			datacache = (PU_DataCacheable >> (int)n) & 0x1;
			datawrite = (PU_DataCacheWrite >> (int)n) & 0x1;
		}
		else
		{
			datacache = 0;
			datawrite = 0;
		}

		uint rgn = PU_Region[n];
		if ( (rgn & (1 << 0)) == 0 )
			return;

		int size = Math.Max( (int)((rgn >> 1) & 0x1F) - 11, 0 );
		uint start = ((rgn >> 12) >> size) << size;
		uint end = start + (1u << size);

		byte usermask = 0;
		byte privmask = 0;

		switch ( datarw )
		{
			case 0: break;
			case 1: privmask |= 0x03; break;
			case 2: privmask |= 0x03; usermask |= 0x01; break;
			case 3: privmask |= 0x03; usermask |= 0x03; break;
			case 5: privmask |= 0x01; break;
			case 6: privmask |= 0x01; usermask |= 0x01; break;
		}

		switch ( coderw )
		{
			case 0: break;
			case 1: privmask |= 0x04; break;
			case 2: privmask |= 0x04; usermask |= 0x04; break;
			case 3: privmask |= 0x04; usermask |= 0x04; break;
			case 5: privmask |= 0x04; break;
			case 6: privmask |= 0x04; usermask |= 0x04; break;
		}

		if ( (datacache & 0x1) != 0 )
		{
			privmask |= 0x10;
			usermask |= 0x10;

			if ( (datawrite & 0x1) != 0 )
			{
				privmask |= 0x20;
				usermask |= 0x20;
			}
		}

		if ( (codecache & 0x1) != 0 )
		{
			privmask |= 0x40;
			usermask |= 0x40;
		}

		for ( uint i = start; i < end; i++ )
		{
			PU_UserMap[i] = usermask;
			PU_PrivMap[i] = privmask;
		}

		UpdateRegionTimings( start, end );
	}

	public void UpdatePURegions( bool updateAll )
	{
		if ( (CP15Control & (1 << 0)) == 0 )
		{
			byte mask = 0x07;
			if ( (CP15Control & (1 << 2)) != 0 ) mask |= 0x30;
			if ( (CP15Control & (1 << 12)) != 0 ) mask |= 0x40;

			Array.Fill( PU_UserMap, mask );
			Array.Fill( PU_PrivMap, mask );

			UpdateRegionTimings( 0x00000, 0x100000 );
		}
		else
		{
			if ( updateAll )
			{
				Array.Clear( PU_UserMap );
				Array.Clear( PU_PrivMap );
			}

			for ( uint n = 0; n < 8; n++ )
				UpdatePURegion( n );

			if ( updateAll )
				UpdateRegionTimings( 0x00000, 0x100000 );
		}

		PU_Map = (CPSR & 0x1F) == 0x10 ? PU_UserMap : PU_PrivMap;
	}

	public void ICacheInvalidateAll() { }
	public void ICacheInvalidateByAddr( uint addr ) { }

	public void CP15Write( uint id, uint val )
	{
		switch ( id )
		{
			case 0x100:
				{
					uint old = CP15Control;
					val &= 0x000FF085;
					CP15Control &= ~0x000FF085u;
					CP15Control |= val;
					UpdateDTCMSetting();
					UpdateITCMSetting();
					if ( (old & 0x1005) != (val & 0x1005) )
						UpdatePURegions( (old & 0x1) != (val & 0x1) );
					ExceptionBase = (val & (1 << 13)) != 0 ? 0xFFFF0000 : 0x00000000;
					return;
				}

			case 0x200: PU_DataCacheable = val; return;
			case 0x201: PU_CodeCacheable = val; return;
			case 0x300: PU_DataCacheWrite = val; return;

			case 0x500:
				PU_DataRW = 0;
				PU_DataRW |= val & 0x0003;
				PU_DataRW |= (val & 0x000C) << 2;
				PU_DataRW |= (val & 0x0030) << 4;
				PU_DataRW |= (val & 0x00C0) << 6;
				PU_DataRW |= (val & 0x0300) << 8;
				PU_DataRW |= (val & 0x0C00) << 10;
				PU_DataRW |= (val & 0x3000) << 12;
				PU_DataRW |= (val & 0xC000) << 14;
				return;

			case 0x501:
				PU_CodeRW = 0;
				PU_CodeRW |= val & 0x0003;
				PU_CodeRW |= (val & 0x000C) << 2;
				PU_CodeRW |= (val & 0x0030) << 4;
				PU_CodeRW |= (val & 0x00C0) << 6;
				PU_CodeRW |= (val & 0x0300) << 8;
				PU_CodeRW |= (val & 0x0C00) << 10;
				PU_CodeRW |= (val & 0x3000) << 12;
				PU_CodeRW |= (val & 0xC000) << 14;
				return;

			case 0x502: PU_DataRW = val; return;
			case 0x503: PU_CodeRW = val; return;

			case 0x600:
			case 0x601:
			case 0x610:
			case 0x611:
			case 0x620:
			case 0x621:
			case 0x630:
			case 0x631:
			case 0x640:
			case 0x641:
			case 0x650:
			case 0x651:
			case 0x660:
			case 0x661:
			case 0x670:
			case 0x671:
				PU_Region[(id >> 4) & 0xF] = val;
				UpdatePURegions( true );
				return;

			case 0x704:
			case 0x782:
				Halt( 1 );
				return;

			case 0x750: ICacheInvalidateAll(); return;
			case 0x751: ICacheInvalidateByAddr( val ); return;

			case 0x910:
				DTCMSetting = val & 0xFFFFF03E;
				UpdateDTCMSetting();
				return;

			case 0x911:
				ITCMSetting = val & 0x0000003E;
				UpdateITCMSetting();
				return;

			case 0xD01:
				TraceProcessID = val;
				return;
		}
	}

	public uint CP15Read( uint id )
	{
		switch ( id )
		{
			case 0x000:
			case 0x003:
			case 0x004:
			case 0x005:
			case 0x006:
			case 0x007: return 0x41059461;

			case 0x001: return 0x0F0D2112;
			case 0x002: return (6 << 6) | (5 << 18);

			case 0x100: return CP15Control;

			case 0x200: return PU_DataCacheable;
			case 0x201: return PU_CodeCacheable;
			case 0x300: return PU_DataCacheWrite;

			case 0x500:
				{
					uint ret = 0;
					ret |= PU_DataRW & 0x00000003;
					ret |= (PU_DataRW & 0x00000030) >> 2;
					ret |= (PU_DataRW & 0x00000300) >> 4;
					ret |= (PU_DataRW & 0x00003000) >> 6;
					ret |= (PU_DataRW & 0x00030000) >> 8;
					ret |= (PU_DataRW & 0x00300000) >> 10;
					ret |= (PU_DataRW & 0x03000000) >> 12;
					ret |= (PU_DataRW & 0x30000000) >> 14;
					return ret;
				}
			case 0x501:
				{
					uint ret = 0;
					ret |= PU_CodeRW & 0x00000003;
					ret |= (PU_CodeRW & 0x00000030) >> 2;
					ret |= (PU_CodeRW & 0x00000300) >> 4;
					ret |= (PU_CodeRW & 0x00003000) >> 6;
					ret |= (PU_CodeRW & 0x00030000) >> 8;
					ret |= (PU_CodeRW & 0x00300000) >> 10;
					ret |= (PU_CodeRW & 0x03000000) >> 12;
					ret |= (PU_CodeRW & 0x30000000) >> 14;
					return ret;
				}
			case 0x502: return PU_DataRW;
			case 0x503: return PU_CodeRW;

			case 0x600:
			case 0x601:
			case 0x610:
			case 0x611:
			case 0x620:
			case 0x621:
			case 0x630:
			case 0x631:
			case 0x640:
			case 0x641:
			case 0x650:
			case 0x651:
			case 0x660:
			case 0x661:
			case 0x670:
			case 0x671: return PU_Region[(id >> 4) & 0xF];

			case 0x910: return DTCMSetting;
			case 0x911: return ITCMSetting;
			case 0xD01: return TraceProcessID;
		}

		return 0;
	}

	private static ushort RdU16( byte[] m, uint o ) => (ushort)(m[o] | (m[o + 1] << 8));
	private static uint RdU32( byte[] m, uint o ) => (uint)(m[o] | (m[o + 1] << 8) | (m[o + 2] << 16) | (m[o + 3] << 24));
	private static void WrU16( byte[] m, uint o, ushort v ) { m[o] = (byte)v; m[o + 1] = (byte)(v >> 8); }
	private static void WrU32( byte[] m, uint o, uint v ) { m[o] = (byte)v; m[o + 1] = (byte)(v >> 8); m[o + 2] = (byte)(v >> 16); m[o + 3] = (byte)(v >> 24); }

	public uint CodeRead32( uint addr, bool branch )
	{
		if ( addr < ITCMSize )
		{
			CodeCycles = 1;
			return RdU32( ITCM, addr & ItcmMask );
		}

		CodeCycles = RegionCodeCycles;
		if ( CodeCycles == 0xFF )
			CodeCycles = branch || (addr & 0x1F) == 0 ? CodeCacheTiming : 1;

		if ( (addr & 0xFF800000) != CodeMemBase )
			SetupCodeMem( addr );

		if ( CodeMem.Mem != null )
			return RdU32( CodeMem.Mem, CodeMem.Offset + (addr & CodeMem.Mask) );

		return BusRead32( addr );
	}

	public override void DataRead8( uint addr, ref uint val )
	{
		if ( (PU_Map[addr >> 12] & 0x01) == 0 ) { DataAbort(); return; }
		DataRegion = addr;

		if ( addr < ITCMSize ) { DataCycles = 1; val = ITCM[addr & ItcmMask]; return; }
		if ( (addr & DTCMMask) == DTCMBase ) { DataCycles = 1; val = DTCM[addr & DtcmMask]; return; }

		val = BusRead8( addr );
		DataCycles = MemTimings[addr >> 12, 1];
	}

	public override void DataRead16( uint addr, ref uint val )
	{
		if ( (PU_Map[addr >> 12] & 0x01) == 0 ) { DataAbort(); return; }
		DataRegion = addr;
		addr &= ~1u;

		if ( addr < ITCMSize ) { DataCycles = 1; val = RdU16( ITCM, addr & ItcmMask ); return; }
		if ( (addr & DTCMMask) == DTCMBase ) { DataCycles = 1; val = RdU16( DTCM, addr & DtcmMask ); return; }

		if ( (addr & 0xFF000000) == 0x02000000 ) val = RdU16( NDS.MainRAM, addr & NDS.MainRAMMask );
		else val = BusRead16( addr );
		DataCycles = MemTimings[addr >> 12, 1];
	}

	public override void DataRead32( uint addr, ref uint val )
	{
		if ( (PU_Map[addr >> 12] & 0x01) == 0 ) { DataAbort(); return; }
		DataRegion = addr;
		addr &= ~3u;

		if ( addr < ITCMSize ) { DataCycles = 1; val = RdU32( ITCM, addr & ItcmMask ); return; }
		if ( (addr & DTCMMask) == DTCMBase ) { DataCycles = 1; val = RdU32( DTCM, addr & DtcmMask ); return; }

		if ( (addr & 0xFF000000) == 0x02000000 ) val = RdU32( NDS.MainRAM, addr & NDS.MainRAMMask );
		else val = BusRead32( addr );
		DataCycles = MemTimings[addr >> 12, 2];
	}

	public override void DataRead32S( uint addr, ref uint val )
	{
		addr &= ~3u;

		if ( addr < ITCMSize ) { DataCycles += 1; val = RdU32( ITCM, addr & ItcmMask ); return; }
		if ( (addr & DTCMMask) == DTCMBase ) { DataCycles += 1; val = RdU32( DTCM, addr & DtcmMask ); return; }

		if ( (addr & 0xFF000000) == 0x02000000 ) val = RdU32( NDS.MainRAM, addr & NDS.MainRAMMask );
		else val = BusRead32( addr );
		DataCycles += MemTimings[addr >> 12, 3];
	}

	public override void DataWrite8( uint addr, byte val )
	{
		if ( (PU_Map[addr >> 12] & 0x02) == 0 ) { DataAbort(); return; }
		DataRegion = addr;

		if ( addr < ITCMSize ) { DataCycles = 1; ITCM[addr & ItcmMask] = val; return; }
		if ( (addr & DTCMMask) == DTCMBase ) { DataCycles = 1; DTCM[addr & DtcmMask] = val; return; }

		BusWrite8( addr, val );
		DataCycles = MemTimings[addr >> 12, 1];
	}

	public override void DataWrite16( uint addr, ushort val )
	{
		if ( (PU_Map[addr >> 12] & 0x02) == 0 ) { DataAbort(); return; }
		DataRegion = addr;
		addr &= ~1u;

		if ( addr < ITCMSize ) { DataCycles = 1; WrU16( ITCM, addr & ItcmMask, val ); return; }
		if ( (addr & DTCMMask) == DTCMBase ) { DataCycles = 1; WrU16( DTCM, addr & DtcmMask, val ); return; }

		if ( (addr & 0xFF000000) == 0x02000000 ) WrU16( NDS.MainRAM, addr & NDS.MainRAMMask, val );
		else BusWrite16( addr, val );
		DataCycles = MemTimings[addr >> 12, 1];
	}

	public override void DataWrite32( uint addr, uint val )
	{
		if ( (PU_Map[addr >> 12] & 0x02) == 0 ) { DataAbort(); return; }
		DataRegion = addr;
		addr &= ~3u;

		if ( addr < ITCMSize ) { DataCycles = 1; WrU32( ITCM, addr & ItcmMask, val ); return; }
		if ( (addr & DTCMMask) == DTCMBase ) { DataCycles = 1; WrU32( DTCM, addr & DtcmMask, val ); return; }

		if ( (addr & 0xFF000000) == 0x02000000 ) WrU32( NDS.MainRAM, addr & NDS.MainRAMMask, val );
		else BusWrite32( addr, val );
		DataCycles = MemTimings[addr >> 12, 2];
	}

	public override void DataWrite32S( uint addr, uint val )
	{
		addr &= ~3u;

		if ( addr < ITCMSize ) { DataCycles += 1; WrU32( ITCM, addr & ItcmMask, val ); return; }
		if ( (addr & DTCMMask) == DTCMBase ) { DataCycles += 1; WrU32( DTCM, addr & DtcmMask, val ); return; }

		if ( (addr & 0xFF000000) == 0x02000000 ) WrU32( NDS.MainRAM, addr & NDS.MainRAMMask, val );
		else BusWrite32( addr, val );
		DataCycles += MemTimings[addr >> 12, 3];
	}

	public void GetCodeMemRegion( uint addr, ref MemRegion region )
	{
		NDS.GetARM9CodeMem( addr, ref region );
	}
}