/*
 * This file handles the architecture-independent parts of system
 * memory image access operations.
 *
 * Copyright 1999 Silicon Graphics, Inc. All rights reserved.
 * Copyright (C) 2001 IBM Deutschland Entwicklung GmbH,
 *                    IBM Corporation
 */
#include <klib.h>

kaddr_t KL_HIGH_MEMORY = (kaddr_t) NULL;
paddr_t KL_INIT_MM = (paddr_t) NULL;

/*
 * open_core()
 */
static meminfo_t *
open_core(char *map_file, char *dump, int flags)
{
	int core_fd;
	meminfo_t *mip;

	KL_ERROR = 0;

	/* Make sure we were passed pointers to dump and map_file.
	 */
	if (map_file == NULL) {
		KL_ERROR = KLE_BAD_MAP_FILE;
		return((meminfo_t*)NULL);
	}
	if (dump == NULL) {
		KL_ERROR = KLE_BAD_DUMP;
		return((meminfo_t*)NULL);
	}

	if (!(mip = (meminfo_t*)calloc(1, sizeof(meminfo_t)))) {
		KL_ERROR = KLE_NO_MEMORY;
		return((meminfo_t*)NULL);
	}
	bzero(mip, sizeof(*mip));

	mip->map = (char *)calloc(1, strlen(map_file) + 1);
	if (mip->map == NULL) {
		kl_free_meminfo(mip);
		KL_ERROR = KLE_NO_MEMORY;
		return((meminfo_t*)NULL);
	}
	strcpy(mip->map, map_file);

	mip->dump = (char *)calloc(1, strlen(dump) + 1);
	if (mip->dump == NULL) {
		kl_free_meminfo(mip);
		KL_ERROR = KLE_NO_MEMORY;
		return((meminfo_t*)NULL);
	}
	strcpy(mip->dump, dump);

	if (strcmp(dump, "/dev/mem") == 0) {
		mip->core_type = dev_kmem;
		if ((core_fd = open(dump, 0)) == -1) {
			kl_free_meminfo(mip);
			KL_ERROR = KLE_OPEN_ERROR|KLE_DUMP;
			return((meminfo_t*)NULL);
		}
		mip->core_fd = core_fd;
	} else {
		mip->core_type = reg_core;
		if ((core_fd = open(dump, O_RDONLY)) == -1) {
			kl_free_meminfo(mip);
			KL_ERROR = KLE_OPEN_ERROR|KLE_DUMP;
			return((meminfo_t*)NULL);
		}
		mip->core_fd = core_fd;

		/* initialize the compression library
		 */
		if (cmpinit(mip, mip->core_fd, (char *)NULL, flags) < 0) {
			if (KL_ERROR == KLE_DUMP_HEADER_ONLY) {
				fprintf(KL_ERRORFP, "\n"
					"Warning: dump only contains a dump "
					"header.  Printing dump header:\n");
				kl_print_dump_header(KL_ERRORFP);
				KL_ERROR = 0;
			} else {
				kl_free_meminfo(mip);
				KL_ERROR = KLE_CMP_ERROR;
			}
			return((meminfo_t*)NULL);
		}
	}
	return(mip);
}

/*
 * kl_free_meminfo()
 */
void
kl_free_meminfo(meminfo_t *mip)
{
	if (mip->map) {
		free(mip->map);
	}
	if (mip->dump) {
		free(mip->dump);
	}
	if (mip->core_fd) {
		close(mip->core_fd);
	}
	free(mip);
}

/* 
 * kl_init_meminfo()
 */
meminfo_t *
kl_init_meminfo(char *map, char *dump, int flags)
{
	meminfo_t *mip;

	if ((mip = open_core(map, dump, flags))) {
		return(mip);
	}
	return((meminfo_t *)NULL);
}


/*
 * get_block()
 * 
 *   Read a size block from virtual address addr in the system memory 
 *   image. 
 */
k_error_t 
get_block(kaddr_t addr, unsigned size, void *bp, void *mmap)
{
	paddr_t paddr;
	size_t s;
	kaddr_t vaddr=addr;

	if (!bp) {
		KL_ERROR = KLE_NULL_BUFF;
	} else if (!size) {
		KL_ERROR = KLE_ZERO_SIZE;
	} else {
		while (size > 0) {
			kaddr_t tmp = vaddr;
			int got_block = 0;

			s = ((vaddr & dump_page_mask) | (~dump_page_mask)) - 
				vaddr + 1;
			s = (size > s) ? s : size;
			vaddr = kl_fix_vaddr(vaddr, s);	
			if (KL_KADDR_IS_HIGHMEM(vaddr) && KLP &&
				KLP->k_meminfo->core_type == dev_kmem) {
					kl_readkmem(vaddr, s, bp);
					if(KL_ERROR) {
						/*
						 * SGI ia64 kernel doesn't map 
						 * ia64_boot_param in 
						 * /dev/kmem or /proc/kcore.
						 *
						 * Try again with physical memory.
						 */
						kl_reset_error();
					} else {
						got_block = 1;
					}
			} 
			if (!got_block) {
				if ( kl_virtop(vaddr, mmap, &paddr) ) {
					return(KL_ERROR);
				}
				kl_readmem(paddr, s, bp);
			}
			vaddr = tmp;
			size=size - s;
			vaddr=vaddr + s;
			bp=bp + s;
		}
		return(0);
	}

	return(KL_ERROR);
}

/*
 * kl_get_kaddr() 
 *
 *   Get the kernel address pointed to by 'addr' and load it into the
 *   buffer pointed to by 'bp'.
 *
 */
uint64_t
kl_get_kaddr(kaddr_t addr, void *bp)
{
	KL_ERROR = 0;

	if (!bp) {
		KL_ERROR = KLE_NULL_BUFF;
		return(KLE_NULL_BUFF);
	}
	if (PTRSZ32) {
		/* We have to be tricky here since we may be reading in
		 * memory that is in LITTLE-ENDIAN format. Because we 
		 * are returning an 8-byte value, we have to make sure 
		 * the value is aligned properly (we can't just shift
		 * the value or we will get the wrong results).
		 */
		GET_BLOCK(addr, 4, bp);
		if (!KL_ERROR) {
			*(kaddr_t*)bp = *(uint32_t*)bp;
		}
	}else if (PTRSZ64) {
		/* We have to be tricky here since we may be reading in
		 * memory that is in LITTLE-ENDIAN format. Because we 
		 * are returning an 8-byte value, we have to make sure 
		 * the value is aligned properly (we can't just shift
		 * the value or we will get the wrong results).
		 */
		GET_BLOCK(addr, 8, bp);
		if (!KL_ERROR) {
			*(kaddr_t*)bp = *(uint64_t*)bp;
		}
	}
	return(KL_ERROR);
}

/*
 * kaddr_to_ptr() 
 *
 *   Return the pointer stored at kernel address 'k'
 *
 */
kaddr_t
kaddr_to_ptr(kaddr_t k)
{
	kaddr_t k1;

	kl_get_kaddr(k, &k1);
	if (KL_ERROR) {
		return((kaddr_t)0);
	} else {
		return(k1);
	}
}

/*
 * kl_uint() -- Return an unsigned intiger value stored in a structure
 *
 *    Pointer 'p' points to a buffer that contains a kernel structure
 *    of type 's.' If the size of member 'm' is less than eight bytes
 *    then right shift the value the appropriate number of bytes so that
 *    it lines up properly in an uint64_t space. Return the resulting
 *    value.
 */
uint64_t
kl_uint(void *p, char *s, char *m, unsigned offset)
{
	int nbytes;
	uint64_t v;
	void *source;

	if (!(nbytes = kl_member_size(s, m))) {
		KL_ERROR = KLE_BAD_FIELD;
		return((uint64_t)0);
	}
	source = (void *)(K_ADDR(p, s, m) + offset);
	switch(nbytes) {
		case 1:
			v = *((uint8_t*)source);
			break;
		case 2:
			v = *((uint16_t*)source);
			break;
		case 4:
			v = *((uint32_t*)source);
			break;
		case 8:
			v = *((uint64_t*)source);
			break;
		default:
			KL_ERROR = KLE_BAD_FIELD;
			return((uint64_t)0);
	}
	return(v);
}

/*
 * kl_int() -- Return a signed intiger value stored in a structure
 *
 *    Pointer 'p' points to a buffer that contains a kernel structure
 *    of type 's.' If the size of member 'm' is less than eight bytes
 *    then right shift the value the appropriate number of bytes so that
 *    it lines up properly in an uint64_t space. Return the resulting
 *    value.
 */
int64_t
kl_int(void *p, char *s, char *m, unsigned offset)
{
	int nbytes;
	int64_t v;
	void *source;

	if (!(nbytes = kl_member_size(s, m))) {
		KL_ERROR = KLE_BAD_FIELD;
		return((int64_t)0);
	}
	source = (void *)(K_ADDR(p, s, m) + offset);
	switch(nbytes) {
		case 1:
			v = *((int8_t*)source);
			break;
		case 2:
			v = *((int16_t*)source);
			break;
		case 4:
			v = *((int32_t*)source);
			break;
		case 8:
			v = *((int64_t*)source);
			break;
		default:
			KL_ERROR = KLE_BAD_FIELD;
			return((int64_t)0);
	}
	return(v);
}

/*
 * kl_kaddr() -- Return a kernel virtual address stored in a structure
 *
 *   Pointer 'p' points to a buffer that contains a kernel structure
 *   of type 's.' Get the kernel address located in member 'm.' 
 */
kaddr_t
kl_kaddr(void *p, char *s, char *m)
{
	kaddr_t k;

	k = *((kaddr_t*)K_PTR(p, s, m));
	return(k);
}

/*
 * kl_reg() -- Return a register value
 *
 *   Pointer 'p' points to a buffer that contains a kernel structure
 *   of type 's.' Get the register value located in member 'm.' 
 */
kaddr_t
kl_reg(void *p, char *s, char *m)
{
	kaddr_t k;

	k = *((kaddr_t*)K_PTR(p, s, m));
	return(k);
}

/*
 * kl_is_valid_kaddr();
 */
int
kl_is_valid_kaddr(kaddr_t addr, void *mmap, int flags)
{
	paddr_t dummy;
	kl_reset_error();

	kl_virtop(addr, mmap, &dummy);
	if (KL_ERROR) {
		return(0);
	}
	if ((flags & WORD_ALIGN_FLAG) && (addr % KL_NBPW)) {
		KL_ERROR = KLE_INVALID_VADDR_ALIGN;
		return(0);
	}
	return(1);
}
