/*
 * This file handles the architecture-dependent parts of KLIB for 
 * ia64 based systems.
 *
 * Copyright 1999 Silicon Graphics, Inc. All rights reserved.
 * Copyright 2001 Silicon Graphics, Inc. All rights reserved.
 */

/* We the following include files are necessary to pick up ARCH 
 * specific defines
 */
#ifndef __KERNEL__
#define __KERNEL__
#include <klib.h>
#include <asm/page.h>
#include <asm/system.h>
#include <asm/processor.h>
#undef __KERNEL__
#else
#include <klib.h>
#include <asm/page.h>
#include <asm/system.h>
#include <asm/processor.h>
#endif

/* 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_IA64, 64, LITTLE_ENDIAN, 0, 
			PAGE_SHIFT, PAGE_SIZE, PAGE_MASK, PAGE_OFFSET,
			IA64_STK_OFFSET };

/*
 * 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;


/* Check to see if VPERNODE_BASE and VGLOBAL_BASE are defined. If they
 * aren't, then define them (so that we can look at an snia dump even
 * when lkcdutils is built from a non-ia64 kernel tree). 
 */
#ifndef VPERNODE_BASE
#if 0
#define VPERNODE_BASE           0xfffffffe00000000
#else
#define VPERNODE_BASE           0xe002000000000000
#endif
#endif

#ifndef VGLOBAL_BASE
#if 0
#define VGLOBAL_BASE            0xffffffff00000000
#else
#define VGLOBAL_BASE            0xe002100000000000
#endif
#endif

/* Structure containing key data for an SN system
 */
typedef struct snvm_info_s {
	int		flags;
	kaddr_t		vpernode_base;
	kaddr_t		vglobal_base;
} snvm_info_t;

/* Set up the snvm_info struct for a non-SN system. It will be filled
 * in the the VPERNODE_BASE and VGLOBAL_BASE values during virtop
 * initialization.
 */
snvm_info_t snvm_info = { 0, 0, 0 };

#define SNVM_FLAG	1
#define IS_SNVM (snvm_info.flags & SNVM_FLAG)
#define KL_VPERNODE_BASE snvm_info.vpernode_base
#define KL_VGLOBAL_BASE snvm_info.vglobal_base

/* 
 * REMIND: CONFIG_DISCONTIGMEM this likely needs to be enhanced.
 */
unsigned long NUM_PHYSPAGES;
kaddr_t MEM_MAP;

/*
 * Name: _snvmcheck()
 * Func: Check and see if addr falls within one of the two SN mapped
 *       regions. This function always returns zero when called for
 *       a non-SN dump.
 */
static int
_snvmcheck(kaddr_t addr)
{
	if (!(IS_SNVM)) {
		return(0);
	}
	if ((addr > KL_VPERNODE_BASE) && (addr < KL_VPERNODE_BASE + (64<<20))) {
		return(1);
	}
	if ((addr > KL_VGLOBAL_BASE) && (addr < KL_VGLOBAL_BASE + (64<<20))) {
		return(1);
	}
	return(0);
}

/*
 * Name: _snvmtop()
 * Func: Translate an SN mapped virtual address to a physical address
 */
static void
_snvmtop(kaddr_t vaddr, kaddr_t *paddr)
{
	/* At this time just mask out the special bits 
	 * that indicate the address is kernel virtual mapped 
	 * (text/data). In the future, logic will need to be 
	 * added to support variable node mapping.
	 */
	*paddr = (vaddr & (~(KL_VGLOBAL_BASE)));
}

/* Contains information about a contiguous chunk of memory installed
 * in the system.
 */
typedef struct mem_chunk {
        struct mem_chunk   *next;
        uint64_t            start_phys;
        uint64_t            size;
} mem_chunk_t;

static int valid_memory_check = 0;
static mem_chunk_t *valid_memory = (mem_chunk_t*)NULL;

/* XXX 
 *
 * Temporary defines that identify offsets into the 
 * ia64_boot_param struct (until the problem loading 
 * the ia64_boot_param struct info from stabs data is fixed).
 */
#define COMMAND_LINE            (0)
#define EFI_MEMMAP		(2*8)
#define EFI_MEMMAP_SIZE		(3*8)
#define EFI_MEMDESC_SIZE	(4*8)

/* The following defines were lifted from asm-ia64/efi.h (this was
 * easier than trying to include the actual kernel header file).
 */

/* Memory types: 
 */
#define EFI_RESERVED_TYPE                0
#define EFI_LOADER_CODE                  1
#define EFI_LOADER_DATA                  2
#define EFI_BOOT_SERVICES_CODE           3
#define EFI_BOOT_SERVICES_DATA           4
#define EFI_RUNTIME_SERVICES_CODE        5
#define EFI_RUNTIME_SERVICES_DATA        6
#define EFI_CONVENTIONAL_MEMORY          7
#define EFI_UNUSABLE_MEMORY              8
#define EFI_ACPI_RECLAIM_MEMORY          9
#define EFI_ACPI_MEMORY_NVS             10
#define EFI_MEMORY_MAPPED_IO            11
#define EFI_MEMORY_MAPPED_IO_PORT_SPACE 12
#define EFI_PAL_CODE                    13
#define EFI_MAX_MEMORY_TYPE             14

/* Attribute values: 
 */
#define EFI_MEMORY_UC       0x0000000000000001  /* uncached */
#define EFI_MEMORY_WC       0x0000000000000002  /* write-coalescing */
#define EFI_MEMORY_WT       0x0000000000000004  /* write-through */
#define EFI_MEMORY_WB       0x0000000000000008  /* write-back */
#define EFI_MEMORY_WP       0x0000000000001000  /* write-protect */
#define EFI_MEMORY_RP       0x0000000000002000  /* read-protect */
#define EFI_MEMORY_XP       0x0000000000004000  /* execute-protect */
#define EFI_MEMORY_RUNTIME  0x8000000000000000  /* requires runtime mapping */


/*
 * Name: _find_valid_memory()
 * Func: Establish a linked list of all contiguous chunks of physical 
 *       memory in the system. The valid_memory list allows us to avoid
 *       and/or step over large blocks of non-existant memory.
 */
static int
_find_valid_memory(void)
{
	int first_time = 1;
        syment_t *sp;
	uint64_t type, phys_addr, attribute, num_pages;
	uint64_t efi_memmap_size, efi_memdesc_size;
	uint64_t command_line_p;
	kaddr_t ia64_boot_param, efi_memmap_p;
	void *efi_memmap, *p;
	unsigned char *command_line;
	mem_chunk_t *cur_chunk = NULL;
	mem_chunk_t *last_chunk = NULL;

	if (valid_memory_check) {
		return(1);
	}

	/* Get the efi_memmap
	 */
	if (!(sp = kl_lkup_symname("ia64_boot_param"))) {
		return(1);
	}

	GET_BLOCK(sp->s_addr, 8, (void*)&ia64_boot_param);
	if (KL_ERROR) {
		return(1);
	}

	/*
	 * get pointers:
	 *		command_line
	 *		efi_memmap
	 *
	 * out of ia64_boot_param and convert them to virtual addresses.
	 */
	GET_BLOCK((ia64_boot_param+EFI_MEMMAP), 8, (void*)&efi_memmap_p);
	if (KL_ERROR) {
		return(1);
	}
	GET_BLOCK((ia64_boot_param+COMMAND_LINE), 8, (void*)&command_line_p);
	if (KL_ERROR) {
		return(1);
	}
	efi_memmap_p += KL_PAGE_OFFSET;
	command_line_p += KL_PAGE_OFFSET;

	/*
	 * get sizes out of ia64_boot_param
	 */
	GET_BLOCK((ia64_boot_param+EFI_MEMMAP_SIZE), 8, 
		(void*)&efi_memmap_size);

	GET_BLOCK((ia64_boot_param+EFI_MEMDESC_SIZE), 8, 
		(void*)&efi_memdesc_size);

	/*
	 * fetch efi_memmap[] and command_line[] arrays.
	 */
	efi_memmap = kl_alloc_block(efi_memmap_size, K_PERM);
	GET_BLOCK(efi_memmap_p, efi_memmap_size, efi_memmap);
	command_line = kl_alloc_block(128, K_PERM);
	GET_BLOCK(command_line_p, 128, command_line);
	command_line[127] = 0;

	
#if DUMP_DEBUG 
	/*
	 * While debugging you change kernels a lot,
	 * knowing which one you booted might help
	 * save time debugging.
	 *
	 * REMIND: any way to verify the System.map
	 *	   and Kerntypes match this kernel?
	 */
	fprintf(KL_ERRORFP, "\n\t\tCore was generated by '%s' ...", command_line);
#endif

	/* Now walk through the various entries and collect info on
	 * all contiguous chunks of memory in the system.
	 */
	p = efi_memmap; 
	while (p < (efi_memmap + efi_memmap_size)) {

		type = KL_UINT(p, "efi_memory_desc_t", "type");
		attribute = KL_UINT(p, "efi_memory_desc_t", "attribute");
		phys_addr = KL_UINT(p, "efi_memory_desc_t", "phys_addr");
		num_pages = KL_UINT(p, "efi_memory_desc_t", "num_pages");
		switch (type) {
			case EFI_LOADER_CODE:
			case EFI_LOADER_DATA:
			case EFI_BOOT_SERVICES_CODE:
			case EFI_BOOT_SERVICES_DATA:
			case EFI_RUNTIME_SERVICES_CODE:
			case EFI_RUNTIME_SERVICES_DATA:
			case EFI_CONVENTIONAL_MEMORY:
			case EFI_PAL_CODE:
				if (!(attribute & EFI_MEMORY_WB)) {
					break;
				}
				if (first_time) {
					cur_chunk = (mem_chunk_t *)
						malloc(sizeof(mem_chunk_t));
					cur_chunk->next = NULL;
					cur_chunk->start_phys = phys_addr;
					cur_chunk->size = (num_pages << 12);
					first_time = 0;
					break;
				}
				if (phys_addr == (cur_chunk->start_phys 
							+ cur_chunk->size)) {
					/* The memory is contiguous
					 */
					cur_chunk->size += 
						((uint64_t)num_pages << 12);
				} else {
					if (last_chunk) {
						last_chunk->next = cur_chunk;
					} else {
						valid_memory = cur_chunk;
					}
					last_chunk = cur_chunk;
					cur_chunk = (mem_chunk_t *)
						malloc(sizeof(mem_chunk_t));
					cur_chunk->next = NULL;
					cur_chunk->start_phys = phys_addr;
					cur_chunk->size = 
						((uint64_t)num_pages << 12);
				}
				break;

			default:
				break;

		}
		p += efi_memdesc_size;
	}

	/* Add our last chunk to the list
	 */
	if (last_chunk) {
		last_chunk->next = cur_chunk;
	} else {
		valid_memory = cur_chunk;
	}
	kl_free_block(efi_memmap);
	kl_free_block(command_line);
	valid_memory_check = 1;
	return(0);
}


/*
 * 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;

	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...
	 */
        if (_snvmcheck(m->s_addr)) {
                _snvmtop(m->s_addr, &paddr);
	} else {
		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;
	
	/* Check to see if we need to worry about SN text/data
	 * address mapping. It's a down and dirty check, but it
	 * works.
	 */
	if ((sp = kl_lkup_symname("_start"))) {
		if ((sp->s_addr & VPERNODE_BASE) == (VPERNODE_BASE)) {
			snvm_info.flags = SNVM_FLAG;
			KL_VPERNODE_BASE = VPERNODE_BASE;
			KL_VGLOBAL_BASE = VGLOBAL_BASE;
		}
	}

	/* Get the high memory address here (Note that we have to do 
         * this after checking to see if VPERNODE_BASE and VGLOBAL_BASE 
         * bits are set).
         */      
	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 (_snvmcheck(vaddr)) {
		_snvmtop(vaddr, paddr);
	} else if (KL_KADDR_IS_PHYSICAL(vaddr)) {
		*paddr = (paddr_t)(vaddr - KL_PAGE_OFFSET);
	} else {
		*paddr = (paddr_t)vaddr;
	}
	if(KL_ERROR){
                *paddr = (kaddr_t) NULL;
                return(1);
	}
	return(0);
}

/*
 * Name: kl_virtop()
 * Func: ia64 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 (_snvmcheck(vaddr)) {
		_snvmtop(vaddr, paddr);
	} else 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 it to a physical 
		 * address. 
		 */
		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.
		 *
		 * REMIND: 
		 *	NUM_PHYSPAGES and CONFIG_DISCONTIGMEM
		 */
		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;
	mem_chunk_t *c = valid_memory;

	if (kl_virtop(addr, NULL, &paddr)) {
		return(0);
	}
	while (c) {
		if ((paddr >= c->start_phys) && 
				(paddr < (c->start_phys + c->size))) {
			return(1);
		}
		c = c->next;
	}
	return(0);
}

/*
 * Name: kl_next_valid_physaddr()
 * Func: Returns the next valid physical address located beyond addr.
 */
paddr_t
kl_next_valid_physaddr(kaddr_t addr)
{
	kaddr_t paddr;
	mem_chunk_t *c = valid_memory;

	if (!(kl_virtop(addr, NULL, &paddr))) {
		while (c) {
			if (paddr < c->start_phys) {
				return (paddr_t)(c->start_phys);
			} 
			c = c->next;
		}
	}
	return(0xffffffffffffffff);
}

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

	kl_reset_error();

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

	if( MIP->core_type == dev_kmem) {
		/*
		 * We are reading /dev/kmem for the core file.
		 *
		 * We may be making a live dump in vmdump'vmdump_page_valid()
		 * and need to avoid getting a Machine Check when we reference 
		 * virtual memory that isn't backed with physical memory.
		 */ 
		if (_find_valid_memory()) {
			return(1);
		}
	}

	/* REMIND: NUM_PHYSPAGES */
	if (!(sp = kl_lkup_symname("num_physpages"))) {
		return(1);
	}
	GET_BLOCK(sp->s_addr, 8, (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, 8, (void*)&MEM_MAP);
	if (KL_ERROR) {
		return(1);
	}
	return(0);
}

/* 
 * While dumping memory the task structures might change. This function
 * is provided to redirect request for the task structures to the stack
 * structured that we took a snapshot of an saved in the ia64 dump header
 * in dha_stack.
 */
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++) {
		/* check control register instruction pointer (pc) */
		if (dha.dha_smp_regs[i].cr_iip < KL_PAGE_OFFSET) {
			/* 
			 * the task appears to be in user space, 
			 * no need to look at saved stack.
			 */
			continue; 
		}
		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;
}
