/*
 * This file handles the architecture-dependent parts KLIB for 
 * i386 based systems.
 *
 * Copyright 1999 Silicon Graphics, Inc. All rights reserved.
 */
#include <klib.h>

/* We the following include file is necessary to pick up ARCH
 * specific defines
 */
#ifndef __KERNEL__
#define __KERNEL__
#include <asm/page.h>
#undef __KERNEL__
#else
#include <asm/page.h>
#endif

/* Function prototype
 */
int get_dump_header_asm(dump_header_asm_t *);

/* Setup the lkcdinfo struct with architecture specific values.
 *
 * XXX - note that we should obtain these values from a kernel
 *       data structure we can read in from the dump at startup.
 */
lkcdinfo_t lkcdinfo = { ARCH_I386, 32, LITTLE_ENDIAN, 0, 
			PAGE_SHIFT, PAGE_SIZE, PAGE_MASK, PAGE_OFFSET, 0 };

/*
 * Dump Constants:
 *
 * NB: may be modified when reading in a core file to reflect the
 *     values of the kernel that crashed.
 *  
 * REMOND: Need to be extracted from live kernel like lkcdinfo is.
 */
int dump_page_size = DUMP_PAGE_SIZE;
int dump_page_shift = DUMP_PAGE_SHIFT;
long dump_page_mask = DUMP_PAGE_MASK;
int dump_header_offset = DUMP_HEADER_OFFSET;
int dump_header_size = DUMP_HEADER_SIZE;
int dump_buffer_size = DUMP_BUFFER_SIZE;


unsigned long NUM_PHYSPAGES;
kaddr_t MEM_MAP;
int kl_has_highmem = FALSE;

/*
 * Name: _init_high_memory()
 * Func: Establishes the highest physical memory address. Needed for
 *       virtual to physical address translation.
 */
static int
_init_high_memory(void)
{
	paddr_t paddr;
	syment_t * m = NULL;

	/* set a highmem flag */
	if ((m = kl_lkup_symname("highmem_start_page"))) {
		kl_has_highmem = TRUE;
	}

	m = kl_lkup_symname("high_memory");

	/* We need to make sure that the address is not virtually mapped
	 * kernel data. We have to do this by hand since we haven't
	 * got virtop mechanism working yet...
	 */
	paddr = (paddr_t)(m->s_addr - KL_PAGE_OFFSET);
	kl_readmem(paddr, KL_NBPW, &KL_HIGH_MEMORY);
	if (KL_ERROR) {
		KL_HIGH_MEMORY = (kaddr_t) -1;
		return(1);
	}

	/* if high_memory is not set, we assume all addresses above
	 * PAGE_OFFSET to mapped to physical addresses (see macro
	 * KL_KADDR_IS_PHYSICAL in kl_mem.h)
	 */
	if(!KL_HIGH_MEMORY){
		KL_HIGH_MEMORY = (kaddr_t) -1;
	}
	return(0);
}

/*
 * Name: _init_virtop()
 * Func: Takes care of any architecture specific initialization
 *       necessary for virtual to physical memory translation.
 */
static int
_init_virtop(void)
{
        syment_t *sp;

	/* Get the high memory address here
	 */
	if (_init_high_memory()) {
		return(1);
	}

        /* Get the address of init_mm and convert it to a physical address
         * so that we can make direct calls to kl_readmem(). We make a call
         * to kl_vtop() since we have not finished setting up for calls to
         * kl_virtop().
         */
        if (!(sp = kl_lkup_symname("init_mm"))) {
                return(1);
        }
        if (kl_vtop(sp->s_addr, &KL_INIT_MM)) {
                return(1);
        }
        return(0);
}

/* 
 * Name: kl_kernelstack()
 * Func: Returns the address of the kernel stack for 'task'
 */
kaddr_t
kl_kernelstack(kaddr_t task)
{
	kaddr_t saddr = 0;
	void *tsp;

	if ((tsp = kl_alloc_block(TASK_STRUCT_SZ, K_TEMP))) {
		kl_get_task_struct(task, 2, tsp);
		if (!KL_ERROR) {
			saddr = (task + KSTACK_SIZE);
		}
		kl_free_block(tsp);
	}
	return(saddr);
} 

/*
 * Name: kl_vtop()
 * Func: Performs simple virtual to physical address translation
 *       without checking mmap memory.
 *
 */
int
kl_vtop(kaddr_t vaddr, paddr_t *paddr)
{
	if (KL_KADDR_IS_PHYSICAL(vaddr)) {
		*paddr = (paddr_t)(vaddr - KL_PAGE_OFFSET);
	} else {
		*paddr = (paddr_t)vaddr;
	}
	return(0);
}

/*
 * Name: kl_virtop()
 * Func: i386 specific system vertual to physical memory translator.
 *       This is where we validate if physical memory exists (for
 *       systems that support discontiguous memory), if a virtual
 *       address is mapped by the kernel, etc.
 */
int
kl_virtop(kaddr_t vaddr, void *m, paddr_t *paddr)
{
	int mm_alloced = 0;
	void *mmp = m;

	*paddr = (paddr_t) NULL;

	kl_reset_error();

	if (!mmp && KL_KADDR_IS_PHYSICAL(vaddr)) {
		*paddr = (paddr_t)(vaddr - KL_PAGE_OFFSET);
	} else if (mmp || KL_INIT_MM) {
		/* Treat address as logical and map to a physical one */
		if (!mmp) {
			if((mmp = kl_alloc_block(MM_STRUCT_SZ, K_TEMP))) {
				kl_readmem(KL_INIT_MM, MM_STRUCT_SZ, mmp);
				if (KL_ERROR) {
					kl_free_block(mmp);
					mmp = NULL;
				} else {
					mm_alloced++;
				}
			}
		}
		if (mmp) {
			*paddr = mmap_virtop(vaddr, mmp);
			if(KL_ERROR){
				KL_ERROR = KLE_INVALID_MAPPING;
			}
		}
	} else {
		/* Treat as a physical address but make sure
		 * the address does not exceed maximum physical
		 * memory.
		 */
		if(vaddr > KL_PAGE_OFFSET){
			vaddr -= KL_PAGE_OFFSET;
		}
		if ((vaddr >> KL_PAGE_SHIFT) < NUM_PHYSPAGES) {
			*paddr = (paddr_t)vaddr;
		} else {
			KL_ERROR = KLE_INVALID_PADDR;
		}
	}

	if (mm_alloced) {
		kl_free_block(mmp);
	}

	if(KL_ERROR){
		*paddr = (paddr_t) NULL;
		return(1);
	} else {
		return(0);
	}
}

/*
 * Name: kl_valid_physmem()
 * Func: Returns 1 if a physical address represents valid physical
 *       memory, otherwise 1 is returned. In the event that the addr
 *       passed in is invalid, a 0 is returned and KL_ERROR is set
 *       to indicate which error occurred.
 */
int
kl_valid_physmem(kaddr_t addr)
{
	paddr_t paddr;

	if (kl_virtop(addr, NULL, &paddr)) {
		return(0);
	}
	return(1);
}

/*
 * Name: kl_next_valid_physaddr()
 * Func: Returns the next valid physical address located beyond addr.
 *       Note that the physical address returned will be word aligned.
 *       If the address passed in is invalid (kl_virtop() fails), or
 *       there is no valid next physical address, then high_mem is
 *       returned and KL_ERROR is set to indicate the cause of the
 *       error.
 */
paddr_t
kl_next_valid_physaddr(kaddr_t addr)
{
	paddr_t paddr;

	if (addr == KL_HIGH_MEMORY) {
		KL_ERROR = KLE_INVALID_PADDR;
		return (paddr_t)(KL_HIGH_MEMORY);
	} else if (kl_virtop(addr, NULL, &paddr)) {
		return (paddr_t)(KL_HIGH_MEMORY);
	}

	/* We know that paddr is valid. Now compute the next valid
	 * (word aligned) physical address and return it.
	 */
	paddr = ((paddr + KL_NBPW) & ~(KL_NBPW));
	return(paddr);
}

/*
 * kl_arch_init()
 */
int
kl_arch_init(void)
{
        syment_t *sp;

        kl_reset_error();

        if (_init_virtop()) {
                return(1);
        }

        if (!(sp = kl_lkup_symname("num_physpages"))) {
                return(1);
        }
        GET_BLOCK(sp->s_addr, 4, (void*)&NUM_PHYSPAGES);
        if (KL_ERROR) {
                return(1);
        }

        /* Get the start address of the kernel page table
         */
        if (!(sp = kl_lkup_symname("mem_map"))) {
                return(1);
        }
        GET_BLOCK(sp->s_addr, 4, (void*)&MEM_MAP);
        if (KL_ERROR) {
                return(1);
        }
        return(0);
}

kaddr_t
kl_fix_vaddr(kaddr_t vaddr, size_t sz)
{
	dump_header_asm_t dha;
	kaddr_t cur_task;
	int i;

	if (MIP->core_type != reg_core) {
		return vaddr;
	}
	if (get_dump_header_asm(&dha))
		return vaddr;

	/*
	 * We look thru the saved snapshots of the task structures and if
	 * the requested memory from vaddr thru vaddr + sz is within the
	 * snapshot then we return the requested address within the snapshot.
	 *
	 * If this is a request for the saved task struct then vaddr should
	 * be page aligned. Otherwise original vaddr is returned even
	 * if a part of the range vaddr to vaddr+sz falls within the range
	 * of saved task_struct+stack.
	 */
	for (i = 0; i < dha.dha_smp_num_cpus; i++) {
		if (dha.dha_smp_regs[i].eip < KL_PAGE_OFFSET)
			continue; /* if task is in user space, no need to look at saved stack */
		cur_task = (kaddr_t)dha.dha_smp_current_task[i];
		if (vaddr >= cur_task && vaddr + sz <=  cur_task + KSTACK_SIZE)
			return (kaddr_t)(dha.dha_stack[i] + (vaddr - cur_task));
	}
	return vaddr;
}
