/*
 * Copyright (c) 2002-2004 NONAKA Kimihiro
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef	IA32_CPU_CPU_MEM_H__
#define	IA32_CPU_CPU_MEM_H__

#include "../cpumem.h"
#include "segments.h"

//#ifdef __cplusplus
//extern "C" {
//#endif

/*
 * memory access check
 */
#if 0
void MEMCALL cpu_stack_push_check(UINT16 s, descriptor_t *sdp, UINT32 sp, UINT len, BOOL is32bit);
void MEMCALL cpu_stack_pop_check(UINT16 s, descriptor_t *sdp, UINT32 sp, UINT len, BOOL is32bit);
#define	SS_PUSH_CHECK1(sp, len, is32bit) \
	cpu_stack_push_check(CPU_SS_INDEX, &CPU_SS_DESC, (sp), (len), (is32bit))
#define	SS_POP_CHECK1(sp, len, is32bit) \
	cpu_stack_pop_check(CPU_SS_INDEX, &CPU_SS_DESC, (sp), (len), (is32bit))
#define	SS_PUSH_CHECK(sp, len) \
	SS_PUSH_CHECK1((sp), (len), CPU_SS_DESC.d)
#define	SS_POP_CHECK(sp, len) \
	SS_POP_CHECK1((sp), (len), CPU_SS_DESC.d)
#endif
/*
 * virtual address function
 */
#if 0
void MEMCALL cpu_vmemorywrite_b(int idx, UINT32 offset, UINT8 value);
#define	cpu_vmemorywrite(i,o,v)		cpu_vmemorywrite_b(i,o,v)
void MEMCALL cpu_vmemorywrite_w(int idx, UINT32 offset, UINT16 value);
void MEMCALL cpu_vmemorywrite_d(int idx, UINT32 offset, UINT32 value);
void MEMCALL cpu_vmemorywrite_q(int idx, UINT32 offset, UINT64 value);
void MEMCALL cpu_vmemorywrite_f(int idx, UINT32 offset, const REG80 *value);
UINT8 MEMCALL cpu_vmemoryread_b(int idx, UINT32 offset);
#define	cpu_vmemoryread(i,o)		cpu_vmemoryread_b(i,o)
UINT16 MEMCALL cpu_vmemoryread_w(int idx, UINT32 offset);
UINT32 MEMCALL cpu_vmemoryread_d(int idx, UINT32 offset);
UINT64 MEMCALL cpu_vmemoryread_q(int idx, UINT32 offset);
REG80 MEMCALL cpu_vmemoryread_f(int idx, UINT32 offset);
UINT32 MEMCALL cpu_vmemory_RMW_b(int idx, UINT32 offset, UINT32 (CPUCALL *func)(UINT32, void *), void *arg);
UINT32 MEMCALL cpu_vmemory_RMW_w(int idx, UINT32 offset, UINT32 (CPUCALL *func)(UINT32, void *), void *arg);
UINT32 MEMCALL cpu_vmemory_RMW_d(int idx, UINT32 offset, UINT32 (CPUCALL *func)(UINT32, void *), void *arg);
#else
//void MEMCALL cpu_vmemorywrite_f(int idx, UINT32 offset, const REG80 *value);
//REG80 MEMCALL cpu_vmemoryread_f(int idx, UINT32 offset);

#endif
#if 0
/*
 * code fetch
 */
UINT8 MEMCALL cpu_codefetch(UINT32 offset);
UINT16 MEMCALL cpu_codefetch_w(UINT32 offset);
UINT32 MEMCALL cpu_codefetch_d(UINT32 offset);

/*
 * additional physical address function
 */
#endif
UINT64 MEMCALL cpu_memoryread_q(UINT32 paddr);
REG80 MEMCALL cpu_memoryread_f(UINT32 paddr);
void MEMCALL cpu_memorywrite_q(UINT32 paddr, UINT64 value);
void MEMCALL cpu_memorywrite_f(UINT32 paddr, const REG80 *value);


#if 1
#define	CHOOSE_EXCEPTION(sreg) \
	(((sreg) == CPU_SS_INDEX) ? SS_EXCEPTION : GP_EXCEPTION)

#include "exception.h"
#include "paging.h"

static __inline__ int MEMCALL check_limit_upstairs(descriptor_t *sdp, UINT32 offset, UINT len, BOOL is32bit);
static __inline__ void MEMCALL cpu_memoryread_check(descriptor_t *sdp, UINT32 offset, UINT len, int e);
static __inline__ void MEMCALL cpu_memorywrite_check(descriptor_t *sdp, UINT32 offset, UINT len, int e);

__inline__ void MEMCALL
cpu_stack_push_check(UINT16 s, descriptor_t *sdp, UINT32 sp, UINT len,
    BOOL is32bit)
{
	UINT32 limit;
	UINT32 start;

	__ASSERT(sdp != NULL);
	__ASSERT(len > 0);

	len--;

	__UNLIKELY_IF(!SEG_IS_VALID(sdp)
	 || !SEG_IS_PRESENT(sdp)
	 || SEG_IS_SYSTEM(sdp)
	 || SEG_IS_CODE(sdp)
	 || !SEG_IS_WRITABLE_DATA(sdp)) {
		goto exc;
	}

	//start = sp - len;
	//limit = is32bit ? 0xffffffff : 0x0000ffff;
	limit = is32bit ? 0xffffffff : 0x0000ffff;
	sp = (sp - 1) & limit;
	start = (sp - len) & limit;


	__UNLIKELY_IF(SEG_IS_EXPANDDOWN_DATA(sdp)) {
		/* expand-down stack */
		if (!SEG_IS_32BIT(sdp)) {
			__UNLIKELY_IF(sp > limit) {			/* [*] */
				goto exc;
			}
		}
		if (sdp->u.seg.limit == 0) {
			/*
			 *   32bit       16bit
			 * +-------+   +-------+ FFFFFFFFh
			 * |       |   |  [*]  |
			 * |       |   +-------+ 0000FFFFh
			 * | valid |   |       |
			 * |       |   | valid |
			 * |       |   |       |
			 * +-------+   +-------+ 00000000h
			 */
			if (!SEG_IS_32BIT(sdp)) {
				__UNLIKELY_IF(sp > limit) {		/* [1] */
					goto exc;
				}
			} else {
				sdp->flag |= CPU_DESC_FLAG_WHOLEADR;
			}
		} else {
			/*
			 *   32bit       16bit
			 * +-------+   +-------+ FFFFFFFFh
			 * |       |   |  [*]  |
			 * | valid |   +-------+ 0000FFFFh
			 * |       |   | valid |
			 * +-------+   +-------+ seg.limit + len - 1
			 * |       |   |       |
			 * +..[1]..+   +..[1]..+ seg.limit
			 * |       |   |       |
			 * +-------+   +-------+ 00000000h
			 */
			__UNLIKELY_IF((len > limit - sdp->u.seg.limit)	/* len check */
			 || (start > sp)			/* wrap check */
			 || (start < sdp->u.seg.limit)) {	/* [1] */
				goto exc;
			}
		}
	} else {
		/* expand-up stack */
		if (sdp->u.seg.limit == limit) {
			/*
			 *   32bit       16bit
			 * +-------+   +-------+ FFFFFFFFh
			 * |       |   |  [1]  |
			 * |       |   +-------+ 0000FFFFh
			 * | valid |   |       |
			 * |       |   | valid |
			 * |       |   |       |
			 * +-------+   +-------+ 00000000h
			 */
			if (!SEG_IS_32BIT(sdp)) {
				__UNLIKELY_IF(sp > limit) {		/* [1] */
					goto exc;
				}
			} else {
				sdp->flag |= CPU_DESC_FLAG_WHOLEADR;
			}
		} else {
			/*
			 *   32bit       16bit
			 * +-------+   +-------+ FFFFFFFFh
			 * |       |   |       |
			 * |  [1]  |   +  [1]  + 0000FFFFh
			 * |       |   |       |
			 * +-------+   +-------+ seg.limit
			 * | valid |   | valid |
			 * +.......+   +.......+ len - 1
			 * |  [+]  |   |  [+]  |
			 * +-------+   +-------+ 00000000h
			 *
			 * [+]: wrap check
			 */
			__UNLIKELY_IF((len > sdp->u.seg.limit)		/* len check */
			 || (start > sp)			/* wrap check */
			 || (sp > sdp->u.seg.limit + 1)) {	/* [1] */
				goto exc;
			}
		}
	}
	return;

exc:
	VERBOSE(("cpu_stack_push_check: check failure: selector = 0x%04x, sp = 0x%08x, len = %d", s, sp, len));
#if defined(DEBUG)
	segdesc_dump(sdp);
#endif
	EXCEPTION(SS_EXCEPTION, s & 0xfffc);
}

__inline__ void MEMCALL
cpu_stack_pop_check(UINT16 s, descriptor_t *sdp, UINT32 sp, UINT len,
    BOOL is32bit)
{

	__ASSERT(sdp != NULL);
	__ASSERT(len > 0);

	__UNLIKELY_IF(!SEG_IS_VALID(sdp)
	 || !SEG_IS_PRESENT(sdp)
	 || SEG_IS_SYSTEM(sdp)
	 || SEG_IS_CODE(sdp)
	 || !SEG_IS_WRITABLE_DATA(sdp)) {
		goto exc;
	}

	__UNLIKELY_IF(!check_limit_upstairs(sdp, sp, len, is32bit))
		goto exc;
	return;

exc:
	VERBOSE(("cpu_stack_pop_check: check failure: selector = 0x%04x, sp = 0x%08x, len = %d", s, sp, len));
#if defined(DEBUG)
	segdesc_dump(sdp);
#endif
	EXCEPTION(SS_EXCEPTION, s & 0xfffc);
}
#define	SS_PUSH_CHECK1(sp, len, is32bit) \
	cpu_stack_push_check(CPU_SS_INDEX, &CPU_SS_DESC, (sp), (len), (is32bit))
#define	SS_POP_CHECK1(sp, len, is32bit) \
	cpu_stack_pop_check(CPU_SS_INDEX, &CPU_SS_DESC, (sp), (len), (is32bit))
#define	SS_PUSH_CHECK(sp, len) \
	SS_PUSH_CHECK1((sp), (len), CPU_SS_DESC.d)
#define	SS_POP_CHECK(sp, len) \
	SS_POP_CHECK1((sp), (len), CPU_SS_DESC.d)

#include "cpu_mem.mcr"

/*
 * memory access check
 */
static __inline__ int MEMCALL
check_limit_upstairs(descriptor_t *sdp, UINT32 offset, UINT len, BOOL is32bit)
{
	UINT32 limit;
	UINT32 end;

	__ASSERT(sdp != NULL);
	__ASSERT(len > 0);

	len--;
	end = offset + len;

	if (SEG_IS_DATA(sdp) && SEG_IS_EXPANDDOWN_DATA(sdp)) {
		/* expand-down data segment */
		limit = SEG_IS_32BIT(sdp) ? 0xffffffff : 0x0000ffff;
		if (sdp->u.seg.limit == 0) {
			/*
			 *   32bit       16bit
			 * +-------+   +-------+ FFFFFFFFh
			 * |       |   |       |
			 * |       |   +  [1]  + 0000FFFFh
			 * | valid |   |       |
			 * |       |   +-------+ 0000FFFFh - len -1
			 * |       |   | valid |
			 * +-------+   +-------+ 00000000h
			 */
			if (!SEG_IS_32BIT(sdp)) {
				__UNLIKELY_IF((len > limit)		/* len check */
				 || (end > limit)) {		/* [1] */
					goto exc;
				}
			} else {
				sdp->flag |= CPU_DESC_FLAG_WHOLEADR;
			}
		} else {
			/*
			 *   32bit       16bit
			 * +-------+   +-------+ FFFFFFFFh
			 * |  [2]  |   |       |
			 * +-------+   +.......+ FFFFFFFFh - len - 1
			 * |       |   |  [2]  |
			 * |       |   +.......+ 0000FFFFh
			 * | valid |   |       |
			 * |       |   +-------+ 0000FFFFh - len - 1
			 * |       |   | valid |
			 * +-------+   +-------+ seg.limit
			 * |  [1]  |   |  [1]  |
			 * +-------+   +-------+ 00000000h
			 */
			__UNLIKELY_IF((len > limit - sdp->u.seg.limit)	/* len check */
			 || (end < offset)			/* wrap check */
			 || (offset < sdp->u.seg.limit) 	/* [1] */
			 || (end > limit)) {			/* [2] */
				goto exc;
			}
		}
	} else {
		/* expand-up data or code segment */
		if (sdp->u.seg.limit == 0xffffffff) {
			/*
			 *  16/32bit
			 * +-------+ FFFFFFFFh
			 * |       |
			 * |       |
			 * | valid |
			 * |       |
			 * |       |
			 * +-------+ 00000000h
			 */
			sdp->flag |= CPU_DESC_FLAG_WHOLEADR;
		} else {
			/*
			 *  16/32bit
			 * +-------+ FFFFFFFFh
			 * |       |
			 * |       |
			 * |  [1]  |
			 * +.......+ seg.limit
			 * |       |
			 * +-------+ seg.limit - len - 1
			 * | valid |
			 * +-------+ 00000000h
			 */
			__UNLIKELY_IF((len > sdp->u.seg.limit)		/* len check */
			 || (end < offset)			/* wrap check */
			 || (end > sdp->u.seg.limit + 1)) {	/* [1] */
				goto exc;
			}
		}
	}
	return 1;	/* Ok! */

exc:
	VERBOSE(("check_limit_upstairs: check failure: offset = 0x%08x, len = %d", offset, len + 1));
#if defined(DEBUG)
	segdesc_dump(sdp);
#endif
	return 0;
}

static __inline__ void MEMCALL
cpu_memoryread_check(descriptor_t *sdp, UINT32 offset, UINT len, int e)
{

	__ASSERT(sdp != NULL);
	__ASSERT(len > 0);

	__UNLIKELY_IF(!SEG_IS_VALID(sdp)) {
		e = GP_EXCEPTION;
		goto exc;
	}
	__UNLIKELY_IF(!SEG_IS_PRESENT(sdp)
	 || SEG_IS_SYSTEM(sdp)
	 || (SEG_IS_CODE(sdp) && !SEG_IS_READABLE_CODE(sdp))) {
		goto exc;
	}

	switch (sdp->type) {
	case 0:	 case 1:	/* ro */
	case 2:  case 3:	/* rw */
	case 4:  case 5:	/* ro (expand down) */
	case 6:  case 7:	/* rw (expand down) */
	case 10: case 11:	/* rx */
	case 14: case 15:	/* rxc */
		__UNLIKELY_IF(!check_limit_upstairs(sdp, offset, len, SEG_IS_32BIT(sdp)))
			goto exc;
		break;

	default:
		goto exc;
	}
	sdp->flag |= CPU_DESC_FLAG_READABLE;
	return;

exc:
	VERBOSE(("cpu_memoryread_check: check failure: offset = 0x%08x, len = %d", offset, len));
#if defined(DEBUG)
	segdesc_dump(sdp);
#endif
	EXCEPTION(e, 0);
}

static __inline__ void MEMCALL
cpu_memorywrite_check(descriptor_t *sdp, UINT32 offset, UINT len, int e)
{

	__ASSERT(sdp != NULL);
	__ASSERT(len > 0);

	__UNLIKELY_IF(!SEG_IS_VALID(sdp)) {
		e = GP_EXCEPTION;
		goto exc;
	}
	__UNLIKELY_IF(!SEG_IS_PRESENT(sdp)
	 || SEG_IS_SYSTEM(sdp)
	 || SEG_IS_CODE(sdp)
	 || (SEG_IS_DATA(sdp) && !SEG_IS_WRITABLE_DATA(sdp))) {
		goto exc;
	}

	switch (sdp->type) {
	case 2: case 3:	/* rw */
	case 6: case 7:	/* rw (expand down) */
		__UNLIKELY_IF(!check_limit_upstairs(sdp, offset, len, SEG_IS_32BIT(sdp)))
			goto exc;
		break;

	default:
		goto exc;
	}
	sdp->flag |= CPU_DESC_FLAG_WRITABLE | CPU_DESC_FLAG_READABLE;
	return;

exc:
	VERBOSE(("cpu_memorywrite_check: check failure: offset = 0x%08x, len = %d", offset, len));
#if defined(DEBUG)
	segdesc_dump(sdp);
#endif
	EXCEPTION(e, 0);
}

DECLARE_VIRTUAL_ADDRESS_MEMORY_RW_FUNCTIONS(b, UINT8, 1)
DECLARE_VIRTUAL_ADDRESS_MEMORY_RMW_FUNCTIONS(b, UINT8, 1)
DECLARE_VIRTUAL_ADDRESS_MEMORY_RW_FUNCTIONS(w, UINT16, 2)
DECLARE_VIRTUAL_ADDRESS_MEMORY_RMW_FUNCTIONS(w, UINT16, 2)
DECLARE_VIRTUAL_ADDRESS_MEMORY_RW_FUNCTIONS(d, UINT32, 4)
DECLARE_VIRTUAL_ADDRESS_MEMORY_RMW_FUNCTIONS(d, UINT32, 4)
DECLARE_VIRTUAL_ADDRESS_MEMORY_RW_FUNCTIONS(q, UINT64, 8)

#define	cpu_vmemorywrite(i,o,v)		cpu_vmemorywrite_b(i,o,v)
#define	cpu_vmemoryread(i,o)		cpu_vmemoryread_b(i,o)

__inline__ REG80 MEMCALL
cpu_vmemoryread_f(int idx, UINT32 offset)
{
	descriptor_t *sdp;
	UINT32 addr;
	int exc;

	__ASSERT((unsigned int)idx < CPU_SEGREG_NUM);

	sdp = &CPU_STAT_SREG(idx);
	addr = sdp->u.seg.segbase + offset;

	__UNLIKELY_IF(!CPU_STAT_PM)
		return cpu_memoryread_f(addr);

	__UNLIKELY_IF(!SEG_IS_VALID(sdp)) {
		exc = GP_EXCEPTION;
		goto err;
	}
	__UNLIKELY_IF(!(sdp->flag & CPU_DESC_FLAG_READABLE)) {
		cpu_memoryread_check(sdp, offset, 10, CHOOSE_EXCEPTION(idx));
	} else if (!(sdp->flag & CPU_DESC_FLAG_WHOLEADR)) {
		__UNLIKELY_IF(!check_limit_upstairs(sdp, offset, 10, SEG_IS_32BIT(sdp)))
			goto range_failure;
	} 
	return cpu_lmemoryread_f(addr, CPU_PAGE_READ_DATA | CPU_STAT_USER_MODE);

range_failure:
	VERBOSE(("cpu_vmemoryread_f: type = %d, offset = %08x, limit = %08x", sdp->type, offset, sdp->u.seg.limit));
	exc = CHOOSE_EXCEPTION(idx);
err:
	EXCEPTION(exc, 0);
	{
		REG80 dummy;
		memset(&dummy, 0, sizeof(dummy));
		return dummy;	/* compiler happy */
	}
}

__inline__ void MEMCALL
cpu_vmemorywrite_f(int idx, UINT32 offset, const REG80 *value)
{
	descriptor_t *sdp;
	UINT32 addr;
	int exc;

	__ASSERT((unsigned int)idx < CPU_SEGREG_NUM);

	sdp = &CPU_STAT_SREG(idx);
	addr = sdp->u.seg.segbase + offset;

	__UNLIKELY_IF(!CPU_STAT_PM) {
		cpu_memorywrite_f(addr, value);
		return;
	}

	__UNLIKELY_IF(!SEG_IS_VALID(sdp)) {
		exc = GP_EXCEPTION;
		goto err;
	}
	__UNLIKELY_IF(!(sdp->flag & CPU_DESC_FLAG_WRITABLE)) {
		cpu_memorywrite_check(sdp, offset, 10, CHOOSE_EXCEPTION(idx));
	} else if (!(sdp->flag & CPU_DESC_FLAG_WHOLEADR)) {
		__UNLIKELY_IF(!check_limit_upstairs(sdp, offset, 10, SEG_IS_32BIT(sdp)))
			goto range_failure;
	}
	cpu_lmemorywrite_f(addr, value, CPU_PAGE_WRITE_DATA | CPU_STAT_USER_MODE);
	return;

range_failure:
	VERBOSE(("cpu_vmemorywrite_f: type = %d, offset = %08x, limit = %08x", sdp->type, offset, sdp->u.seg.limit));
	exc = CHOOSE_EXCEPTION(idx);
err:
	EXCEPTION(exc, 0);
}

/*
 * code fetch
 */
__inline__ UINT8 MEMCALL
cpu_codefetch(UINT32 offset)
{
	const int ucrw = CPU_PAGE_READ_CODE | CPU_STAT_USER_MODE;
	descriptor_t *sdp;
	UINT32 addr;

	sdp = &CPU_CS_DESC;
	addr = sdp->u.seg.segbase + offset;

	__UNLIKELY_IF(!CPU_STAT_PM)
		return cpu_memoryread_codefetch(addr);
	__LIKELY_IF(offset <= sdp->u.seg.limit)
		return cpu_lmemoryread_codefetch(addr, ucrw);

	EXCEPTION(GP_EXCEPTION, 0);
	return 0;	/* compiler happy */
}

__inline__ UINT16 MEMCALL
cpu_codefetch_w(UINT32 offset)
{
	const int ucrw = CPU_PAGE_READ_CODE | CPU_STAT_USER_MODE;
	descriptor_t *sdp;
	UINT32 addr;

	sdp = &CPU_CS_DESC;
	addr = sdp->u.seg.segbase + offset;

	__UNLIKELY_IF(!CPU_STAT_PM)
		return cpu_memoryread_w_codefetch(addr);
	__LIKELY_IF(offset <= sdp->u.seg.limit - 1)
		return cpu_lmemoryread_w_codefetch(addr, ucrw);

	EXCEPTION(GP_EXCEPTION, 0);
	return 0;	/* compiler happy */
}

__inline__ UINT32 MEMCALL
cpu_codefetch_d(UINT32 offset)
{
	const int ucrw = CPU_PAGE_READ_CODE | CPU_STAT_USER_MODE;
	descriptor_t *sdp;
	UINT32 addr;

	sdp = &CPU_CS_DESC;
	addr = sdp->u.seg.segbase + offset;

	__UNLIKELY_IF(!CPU_STAT_PM)
		return cpu_memoryread_d_codefetch(addr);

	__LIKELY_IF(offset <= sdp->u.seg.limit - 3)
		return cpu_lmemoryread_d_codefetch(addr, ucrw);

	EXCEPTION(GP_EXCEPTION, 0);
	return 0;	/* compiler happy */
}
#endif
//#ifdef __cplusplus
//}
//#endif


#endif	/* !IA32_CPU_CPU_MEM_H__ */
