#define DUMP_PAGEFAULTS 0

INLINE UINT8 READ8(UINT32 a)
{
	return ppc.read8(ppc.program, a);
}

INLINE UINT16 READ16(UINT32 a)
{
	if( a & 0x1 )
		return ppc.read16_unaligned(ppc.program, a);
	else
		return ppc.read16(ppc.program, a);
}

INLINE UINT32 READ32(UINT32 a)
{
	if( a & 0x3 )
		return ppc.read32_unaligned(ppc.program, a);
	else
		return ppc.read32(ppc.program, a);
}

INLINE UINT64 READ64(UINT32 a)
{
	if( a & 0x7 )
		return ppc.read64_unaligned(ppc.program, a);
	else
		return ppc.read64(ppc.program, a);
}

INLINE void WRITE8(UINT32 a, UINT8 d)
{
	ppc.write8(ppc.program, a, d);
}

INLINE void WRITE16(UINT32 a, UINT16 d)
{
	if( a & 0x1 )
		ppc.write16_unaligned(ppc.program, a, d);
	else
		ppc.write16(ppc.program, a, d);
}

INLINE void WRITE32(UINT32 a, UINT32 d)
{
	if( ppc.reserved ) {
		if( a == ppc.reserved_address ) {
			ppc.reserved = 0;
		}
	}

	if( a & 0x3 )
		ppc.write32_unaligned(ppc.program, a, d);
	else
		ppc.write32(ppc.program, a, d);
}

INLINE void WRITE64(UINT32 a, UINT64 d)
{
	if( a & 0x7 )
		ppc.write64_unaligned(ppc.program, a, d);
	else
		ppc.write64(ppc.program, a, d);
}

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

static UINT16 ppc_read16_unaligned(const address_space *space, UINT32 a)
{
	return ((UINT16)ppc.read8(space, a+0) << 8) | ((UINT16)ppc.read8(space, a+1) << 0);
}

static UINT32 ppc_read32_unaligned(const address_space *space, UINT32 a)
{
	return ((UINT32)ppc.read8(space, a+0) << 24) | ((UINT32)ppc.read8(space, a+1) << 16) |
				   ((UINT32)ppc.read8(space, a+2) << 8) | ((UINT32)ppc.read8(space, a+3) << 0);
}

static UINT64 ppc_read64_unaligned(const address_space *space, UINT32 a)
{
	return ((UINT64)READ32(space, a+0) << 32) | (UINT64)(READ32(space, a+4));
}

static void ppc_write16_unaligned(const address_space *space, UINT32 a, UINT16 d)
{
	ppc.write8(space, a+0, (UINT8)(d >> 8));
	ppc.write8(space, a+1, (UINT8)(d));
}

static void ppc_write32_unaligned(const address_space *space, UINT32 a, UINT32 d)
{
	ppc.write8(space, a+0, (UINT8)(d >> 24));
	ppc.write8(space, a+1, (UINT8)(d >> 16));
	ppc.write8(space, a+2, (UINT8)(d >> 8));
	ppc.write8(space, a+3, (UINT8)(d >> 0));
}

static void ppc_write64_unaligned(const address_space *space, UINT32 a, UINT64 d)
{
	ppc.write32(space, a+0, (UINT32)(d >> 32));
	ppc.write32(space, a+4, (UINT32)(d));
}

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

#define DSISR_PAGE		0x40000000
#define DSISR_PROT		0x08000000
#define DSISR_STORE		0x02000000

enum
{
	PPC_TRANSLATE_DATA	= 0x0000,
	PPC_TRANSLATE_CODE	= 0x0001,

	PPC_TRANSLATE_READ	= 0x0000,
	PPC_TRANSLATE_WRITE	= 0x0002,

	PPC_TRANSLATE_NOEXCEPTION = 0x0004
};

#ifdef MESS
#define PPC_MMU_ENABLED		1
#else /* !MESS */
/* MMU not enabled in MAME; model3 drivers don't work properly */
#define PPC_MMU_ENABLED		0
#endif /* MESS */



static int ppc_is_protected(UINT32 pp, int flags)
{
	if (flags & PPC_TRANSLATE_WRITE)
	{
		if ((pp & 0x00000003) != 0x00000002)
			return TRUE;
	}
	else
	{
		if ((pp & 0x00000003) == 0x00000000)
			return TRUE;
	}
	return FALSE;
}

static int ppc_translate_address(offs_t *addr_ptr, int flags)
{
	if (PPC_MMU_ENABLED)
	{
		const BATENT *bat;
		UINT32 address;
		UINT32 sr, vsid, hash;
		UINT32 pteg_address;
		UINT32 target_pte, bl, mask;
		UINT64 pte;
		UINT64 *pteg_ptr[2];
		int i, hash_type;
		UINT32 dsisr = DSISR_PROT;

		bat = (flags & PPC_TRANSLATE_CODE) ? ppc.ibat : ppc.dbat;

		address = *addr_ptr;

		/* first check the block address translation table */
		for (i = 0; i < 4; i++)
		{
			if (bat[i].u & ((MSR & MSR_PR) ? 0x00000001 : 0x00000002))
			{
				bl = bat[i].u & 0x00001FFC;
				mask = (~bl << 15) & 0xFFFE0000;

				if ((address & mask) == (bat[i].u & 0xFFFE0000))
				{
					if (ppc_is_protected(bat[i].l, flags))
						goto exception;

					*addr_ptr = (bat[i].l & 0xFFFE0000)
						| (address & ((bl << 15) | 0x0001FFFF));
					return 1;
				}
			}
		}

		/* now try page address translation */
		sr = ppc.sr[(address >> 28) & 0x0F];
		if (sr & 0x80000000)
		{
			/* direct store translation */
			if ((flags & PPC_TRANSLATE_NOEXCEPTION) == 0)
				fatalerror("ppc: direct store translation not yet implemented");
			return 0;
		}
		else
		{
			/* is no execute is set? */
			if ((flags & PPC_TRANSLATE_CODE) && (sr & 0x10000000))
				goto exception;

			vsid = sr & 0x00FFFFFF;
			hash = (vsid & 0x0007FFFF) ^ ((address >> 12) & 0xFFFF);
			target_pte = (vsid << 7) | ((address >> 22) & 0x3F) | 0x80000000;

			/* we have to try both types of hashes */
			for (hash_type = 0; hash_type <= 1; hash_type++)
			{
				pteg_address = (ppc.sdr1 & 0xFFFF0000)
					| (((ppc.sdr1 & 0x01FF) & (hash >> 10)) << 16)
					| ((hash & 0x03FF) << 6);

				pteg_ptr[hash_type] = memory_get_read_ptr(ppc->program, pteg_address);
				if (pteg_ptr[hash_type])
				{
					for (i = 0; i < 8; i++)
					{
						pte = pteg_ptr[hash_type][i];

						/* is valid? */
						if (((pte >> 32) & 0xFFFFFFFF) == target_pte)
						{
							if (ppc_is_protected((UINT32) pte, flags))
								goto exception;

							*addr_ptr = ((UINT32) (pte & 0xFFFFF000))
								| (address & 0x0FFF);
							return 1;
						}
					}
				}

				hash ^= 0x7FFFF;
				target_pte ^= 0x40;
			}

			if (DUMP_PAGEFAULTS)
			{
				mame_printf_debug("PAGE FAULT: address=%08X PC=%08X SDR1=%08X MSR=%08X\n", address, ppc.pc, ppc.sdr1, ppc.msr);
				mame_printf_debug("\n");

				for (i = 0; i < 4; i++)
				{
					bl = bat[i].u & 0x00001FFC;
					mask = (~bl << 15) & 0xFFFE0000;
					mame_printf_debug("    BAT[%d]=%08X%08X    (A & %08X = %08X)\n", i, bat[i].u, bat[i].l,
						mask, bat[i].u & 0xFFFE0000);
				}
				mame_printf_debug("\n");
				mame_printf_debug("    VSID=%06X HASH=%05X HASH\'=%05X\n", vsid, hash, hash ^ 0x7FFFF);

				for (hash_type = 0; hash_type <= 1; hash_type++)
				{
					if (pteg_ptr[hash_type])
					{
						for (i = 0; i < 8; i++)
						{
							pte = pteg_ptr[hash_type][i];
							mame_printf_debug("    PTE[%i%c]=%08X%08X\n",
								i,
								hash_type ? '\'' : ' ',
								(unsigned) (pte >> 32),
								(unsigned) (pte >> 0));
						}
					}
				}
			}
		}

		dsisr = DSISR_PAGE;

exception:
		/* lookup failure - exception */
		if ((flags & PPC_TRANSLATE_NOEXCEPTION) == 0)
		{
			if (flags & PPC_TRANSLATE_CODE)
			{
				ppc_exception(EXCEPTION_ISI);
			}
			else
			{
				ppc.dar = address;
				if (flags & PPC_TRANSLATE_WRITE)
					ppc.dsisr = dsisr | DSISR_STORE;
				else
					ppc.dsisr = dsisr;

				ppc_exception(EXCEPTION_DSI);
			}
		}
		return 0;
	}
	else
	{
		/* MMU not enabled in MAME; model3 drivers don't work properly */
		return 1;
	}
}

static int ppc_translate_address_cb(int space, offs_t *addr)
{
	int success = 1;

	if (space == ADDRESS_SPACE_PROGRAM)
	{
		if (MSR & MSR_DR)
			success = ppc_translate_address(addr, PPC_TRANSLATE_CODE | PPC_TRANSLATE_READ | PPC_TRANSLATE_NOEXCEPTION);
	}
	return success;
}

static UINT8 ppc_read8_translated(const address_space *space, offs_t address)
{
	ppc_translate_address(&address, PPC_TRANSLATE_DATA | PPC_TRANSLATE_READ);
	return memory_read_byte_64be(space, address);
}

static UINT16 ppc_read16_translated(const address_space *space, offs_t address)
{
	ppc_translate_address(&address, PPC_TRANSLATE_DATA | PPC_TRANSLATE_READ);
	return memory_read_word_64be(space, address);
}

static UINT32 ppc_read32_translated(const address_space *space, offs_t address)
{
	ppc_translate_address(&address, PPC_TRANSLATE_DATA | PPC_TRANSLATE_READ);
	return memory_read_dword_64be(space, address);
}

static UINT64 ppc_read64_translated(const address_space *space, offs_t address)
{
	ppc_translate_address(&address, PPC_TRANSLATE_DATA | PPC_TRANSLATE_READ);
	return memory_read_qword_64be(space, address);
}

static void ppc_write8_translated(const address_space *space, offs_t address, UINT8 data)
{
	ppc_translate_address(&address, PPC_TRANSLATE_DATA | PPC_TRANSLATE_WRITE);
	memory_write_byte_64be(space, address, data);
}

static void ppc_write16_translated(const address_space *space, offs_t address, UINT16 data)
{
	ppc_translate_address(&address, PPC_TRANSLATE_DATA | PPC_TRANSLATE_WRITE);
	memory_write_word_64be(space, address, data);
}

static void ppc_write32_translated(const address_space *space, offs_t address, UINT32 data)
{
	ppc_translate_address(&address, PPC_TRANSLATE_DATA | PPC_TRANSLATE_WRITE);
	memory_write_dword_64be(space, address, data);
}

static void ppc_write64_translated(const address_space *space, offs_t address, UINT64 data)
{
	ppc_translate_address(&address, PPC_TRANSLATE_DATA | PPC_TRANSLATE_WRITE);
	memory_write_qword_64be(space, address, data);
}

#ifndef PPC_DRC
static UINT32 ppc_readop_translated(const address_space *space, offs_t address)
{
	ppc_translate_address(&address, PPC_TRANSLATE_CODE | PPC_TRANSLATE_READ);
	return memory_read_dword_64be(space, address);
}
#endif

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


static CPU_DISASSEMBLE( ppc )
{
	UINT32 op;
	op = BIG_ENDIANIZE_INT32(*((UINT32 *) oprom));
	return ppc_dasm_one(buffer, pc, op);
}

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

static CPU_READOP( ppc )
{
	if (!(ppc.msr & MSR_IR))
		return 0;

	*value = 0;

	if (ppc_translate_address(&offset, PPC_TRANSLATE_CODE | PPC_TRANSLATE_READ | PPC_TRANSLATE_NOEXCEPTION))
	{
		switch(size)
		{
			case 1:	*value = memory_read_byte(ppc.program, offset);	break;
			case 2:	*value = memory_read_word(ppc.program, offset);	break;
			case 4:	*value = memory_read_dword(ppc.program, offset);	break;
			case 8:	*value = memory_read_qword(ppc.program, offset);	break;
		}
	}

	return 1;
}

static CPU_READ( ppc )
{
	if (!(ppc.msr & MSR_DR))
		return 0;

	*value = 0;

	if (ppc_translate_address(&offset, PPC_TRANSLATE_DATA | PPC_TRANSLATE_READ | PPC_TRANSLATE_NOEXCEPTION))
	{
		switch(size)
		{
			case 1:	*value = memory_read_byte(ppc.program, offset);	break;
			case 2:	*value = memory_read_word(ppc.program, offset);	break;
			case 4:	*value = memory_read_dword(ppc.program, offset);	break;
			case 8:	*value = memory_read_qword(ppc.program, offset);	break;
		}
	}

	return 1;
}

static CPU_WRITE( ppc )
{
	if (!(ppc.msr & MSR_DR))
		return 0;

	if (ppc_translate_address(&offset, PPC_TRANSLATE_DATA | PPC_TRANSLATE_WRITE | PPC_TRANSLATE_NOEXCEPTION))
	{
		switch(size)
		{
			case 1:	memory_write_byte(ppc.program, offset, value);	break;
			case 2:	memory_write_word(ppc.program, offset, value);	break;
			case 4:	memory_write_dword(ppc.program, offset, value);	break;
			case 8:	memory_write_qword(ppc.program, offset, value);	break;
		}
	}

	return 1;
}
