/*
 * ia64.c
 *
 * Copyright (C) 2006, 2007, 2008  NEC Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */
#ifdef __ia64__

#include "../print_info.h"
#include "../elf_info.h"
#include "../makedumpfile.h"


/*
 *  vmalloc() starting address is either the traditional 0xa000000000000000 or
 *  bumped up in 2.6 to 0xa000000200000000.
 */
int
is_vmalloc_addr_ia64(unsigned long vaddr)
{
	return ((vaddr >= info->vmalloc_start) &&
			(vaddr < (unsigned long)KERNEL_UNCACHED_BASE));
}

int
get_phys_base_ia64(void)
{
	int i;
	unsigned long long phys_start;
	unsigned long long virt_start;

	/*
	 *  Default to 64MB.
	 */
	info->phys_base = DEFAULT_PHYS_START;

	for (i = 0; get_pt_load(i, &phys_start, NULL, &virt_start, NULL); i++) {
		if (VADDR_REGION(virt_start) == KERNEL_VMALLOC_REGION) {

			info->phys_base = phys_start;
			break;
		}
	}
	return TRUE;
}

int
get_machdep_info_ia64(void)
{
	/*
	 * Get kernel_start and vmalloc_start.
	 */
	if (SYMBOL(_stext) == NOT_FOUND_SYMBOL)
		return FALSE;

	info->kernel_start = SYMBOL(_stext);

	if (VADDR_REGION(info->kernel_start) == KERNEL_VMALLOC_REGION)
		info->vmalloc_start = info->kernel_start + 4*1024UL*1024UL*1024UL;
	else
		info->vmalloc_start = KERNEL_VMALLOC_BASE;

	/*
	 * Check the pgtable (3 Levels or 4 Levels).
	 */
	if ((vt.mem_flags & MEMORY_PAGETABLE_4L)
	    || !strncmp(SRCFILE(pud_t), STR_PUD_T_4L, strlen(STR_PUD_T_4L))) {
		vt.mem_flags |= MEMORY_PAGETABLE_4L;
		DEBUG_MSG("PAGETABLE_4L : ON\n");
	} else if ((vt.mem_flags & MEMORY_PAGETABLE_3L)
	    || !strncmp(SRCFILE(pud_t), STR_PUD_T_3L, strlen(STR_PUD_T_3L))) {
		vt.mem_flags |= MEMORY_PAGETABLE_3L;
		DEBUG_MSG("PAGETABLE_3L : ON\n");
	} else {
		MSG("Can't distinguish the pgtable.\n");
	}

	info->section_size_bits = _SECTION_SIZE_BITS;
	info->max_physmem_bits  = _MAX_PHYSMEM_BITS;

	return TRUE;
}

/*
 * Translate a virtual address to a physical address by using 3 levels paging.
 */
unsigned long long
vtop3_ia64(unsigned long vaddr)
{
	unsigned long long paddr, temp, page_dir, pgd_pte, page_middle, pmd_pte;
	unsigned long long page_table, pte;

	if (SYMBOL(swapper_pg_dir) == NOT_FOUND_SYMBOL) {
		ERRMSG("Can't get the symbol of swapper_pg_dir.\n");
		return NOT_PADDR;
	}

	/*
	 * Get PGD
	 */
	temp = vaddr & MASK_PGD_3L;
	temp = temp >> (PGDIR_SHIFT_3L - 3);
	page_dir = SYMBOL(swapper_pg_dir) + temp;
	if (!readmem(VADDR, page_dir, &pgd_pte, sizeof pgd_pte)) {
		ERRMSG("Can't get pgd_pte (page_dir:%llx).\n", page_dir);
		return NOT_PADDR;
	}
	if (info->vaddr_for_vtop == vaddr)
		MSG("  PGD : %16llx => %16llx\n", page_dir, pgd_pte);

	/*
	 * Get PMD
	 */
	temp = vaddr & MASK_PMD;
	temp = temp >> (PMD_SHIFT - 3);
	page_middle = pgd_pte + temp;
	if (!readmem(PADDR, page_middle, &pmd_pte, sizeof pmd_pte)) {
		ERRMSG("Can't get pmd_pte (page_middle:%llx).\n", page_middle);
		return NOT_PADDR;
	}
	if (info->vaddr_for_vtop == vaddr)
		MSG("  PMD : %16llx => %16llx\n", page_middle, pmd_pte);

	/*
	 * Get PTE
	 */
	temp = vaddr & MASK_PTE;
	temp = temp >> (PAGESHIFT() - 3);
	page_table = pmd_pte + temp;
	if (!readmem(PADDR, page_table, &pte, sizeof pte)) {
		ERRMSG("Can't get pte (page_table:%llx).\n", page_table);
		return NOT_PADDR;
	}
	if (info->vaddr_for_vtop == vaddr)
		MSG("  PTE : %16llx => %16llx\n", page_table, pte);

	/*
	 * Get physical address
	 */
	temp = vaddr & MASK_POFFSET;
	paddr = (pte & _PAGE_PPN_MASK) + temp;

	return paddr;
}

/*
 * Translate a virtual address to a physical address by using 4 levels paging.
 */
unsigned long long
vtop4_ia64(unsigned long vaddr)
{
	unsigned long long paddr, temp, page_dir, pgd_pte, page_upper, pud_pte;
	unsigned long long page_middle, pmd_pte, page_table, pte;

	if (SYMBOL(swapper_pg_dir) == NOT_FOUND_SYMBOL) {
		ERRMSG("Can't get the symbol of swapper_pg_dir.\n");
		return NOT_PADDR;
	}

	/*
	 * Get PGD
	 */
	temp = vaddr & MASK_PGD_4L;
	temp = temp >> (PGDIR_SHIFT_4L - 3);
	page_dir = SYMBOL(swapper_pg_dir) + temp;
	if (!readmem(VADDR, page_dir, &pgd_pte, sizeof pgd_pte)) {
		ERRMSG("Can't get pgd_pte (page_dir:%llx).\n", page_dir);
		return NOT_PADDR;
	}
	if (info->vaddr_for_vtop == vaddr)
		MSG("  PGD : %16llx => %16llx\n", page_dir, pgd_pte);

	/*
	 * Get PUD
	 */
	temp = vaddr & MASK_PUD;
	temp = temp >> (PUD_SHIFT - 3);
	page_upper = pgd_pte + temp;
	if (!readmem(PADDR, page_upper, &pud_pte, sizeof pud_pte)) {
		ERRMSG("Can't get pud_pte (page_upper:%llx).\n", page_upper);
		return NOT_PADDR;
	}
	if (info->vaddr_for_vtop == vaddr)
		MSG("  PUD : %16llx => %16llx\n", page_upper, pud_pte);

	/*
	 * Get PMD
	 */
	temp = vaddr & MASK_PMD;
	temp = temp >> (PMD_SHIFT - 3);
	page_middle = pud_pte + temp;
	if (!readmem(PADDR, page_middle, &pmd_pte, sizeof pmd_pte)) {
		ERRMSG("Can't get pmd_pte (page_middle:%llx).\n", page_middle);
		return NOT_PADDR;
	}
	if (info->vaddr_for_vtop == vaddr)
		MSG("  PMD : %16llx => %16llx\n", page_middle, pmd_pte);

	/*
	 * Get PTE
	 */
	temp = vaddr & MASK_PTE;
	temp = temp >> (PAGESHIFT() - 3);
	page_table = pmd_pte + temp;
	if (!readmem(PADDR, page_table, &pte, sizeof pte)) {
		ERRMSG("Can't get pte (page_table:%llx).\n", page_table);
		return NOT_PADDR;
	}
	if (info->vaddr_for_vtop == vaddr)
		MSG("  PTE : %16llx => %16llx\n", page_table, pte);

	/*
	 * Get physical address
	 */
	temp = vaddr & MASK_POFFSET;
	paddr = (pte & _PAGE_PPN_MASK) + temp;

	return paddr;
}

unsigned long long
vtop_ia64(unsigned long vaddr)
{
	unsigned long long paddr;

	if (VADDR_REGION(vaddr) != KERNEL_VMALLOC_REGION) {
		ERRMSG("vaddr(%lx) is not KERNEL_VMALLOC_REGION.\n", vaddr);
		return NOT_PADDR;
	}
	paddr = vaddr_to_paddr_general(vaddr);
	if (paddr != NOT_PADDR)
		return paddr;

	if (!is_vmalloc_addr_ia64(vaddr)) {
		paddr = vaddr - info->kernel_start +
			(info->phys_base & KERNEL_TR_PAGE_MASK);
		if (is_xen_memory())
			paddr = ptom_xen(paddr);
		return paddr;
	}

	if (vt.mem_flags & MEMORY_PAGETABLE_4L)
		return vtop4_ia64(vaddr);
	else
		return vtop3_ia64(vaddr);
}

/*
 * Translate a virtual address to physical address.
 */
unsigned long long
vaddr_to_paddr_ia64(unsigned long vaddr)
{
	unsigned long long paddr;

	switch (VADDR_REGION(vaddr)) {
		case KERNEL_CACHED_REGION:
			paddr = vaddr - (ulong)(KERNEL_CACHED_BASE);
			break;

		case KERNEL_UNCACHED_REGION:
			paddr = vaddr - (ulong)(KERNEL_UNCACHED_BASE);
			break;

		case KERNEL_VMALLOC_REGION:
			paddr = vtop_ia64(vaddr);
			break;

		default:
			ERRMSG("Unknown region (%ld)\n", VADDR_REGION(vaddr));
			return 0x0;
	}
	return paddr;
}

/*
 * for Xen extraction
 */
unsigned long long
kvtop_xen_ia64(unsigned long kvaddr)
{
	unsigned long long addr, dirp, entry;

	if (!is_xen_vaddr(kvaddr))
		return NOT_PADDR;

	if (is_direct(kvaddr))
		return (unsigned long)kvaddr - DIRECTMAP_VIRT_START;

	if (!is_frame_table_vaddr(kvaddr))
		return NOT_PADDR;

	addr = kvaddr - VIRT_FRAME_TABLE_ADDR;

	dirp = SYMBOL(frametable_pg_dir) - DIRECTMAP_VIRT_START;
	dirp += ((addr >> PGDIR_SHIFT) & (PTRS_PER_PGD - 1)) * sizeof(unsigned long long);
	if (!readmem(PADDR, dirp, &entry, sizeof(entry)))
		return NOT_PADDR;

	dirp = entry & _PFN_MASK;
	if (!dirp)
		return NOT_PADDR;

	dirp += ((addr >> PMD_SHIFT) & (PTRS_PER_PMD - 1)) * sizeof(unsigned long long);
	if (!readmem(PADDR, dirp, &entry, sizeof(entry)))
		return NOT_PADDR;

	dirp = entry & _PFN_MASK;
	if (!dirp)
		return NOT_PADDR;

	dirp += ((addr >> PAGESHIFT()) & (PTRS_PER_PTE - 1)) * sizeof(unsigned long long);
	if (!readmem(PADDR, dirp, &entry, sizeof(entry)))
		return NOT_PADDR;

	if (!(entry & _PAGE_P))
		return NOT_PADDR;

	entry = (entry & _PFN_MASK) + (addr & ((1UL << PAGESHIFT()) - 1));

	return entry;
}

int
get_xen_basic_info_ia64(void)
{
	unsigned long xen_start, xen_end;

	info->frame_table_vaddr = VIRT_FRAME_TABLE_ADDR; /* "frame_table" is same value */

	if (!info->xen_crash_info.com ||
	    info->xen_crash_info.com->xen_major_version < 4) {
		if (SYMBOL(xenheap_phys_end) == NOT_FOUND_SYMBOL) {
			ERRMSG("Can't get the symbol of xenheap_phys_end.\n");
			return FALSE;
		}
		if (!readmem(VADDR_XEN, SYMBOL(xenheap_phys_end), &xen_end,
			     sizeof(xen_end))) {
			ERRMSG("Can't get the value of xenheap_phys_end.\n");
			return FALSE;
		}
		if (SYMBOL(xen_pstart) == NOT_FOUND_SYMBOL) {
			ERRMSG("Can't get the symbol of xen_pstart.\n");
			return FALSE;
		}
		if (!readmem(VADDR_XEN, SYMBOL(xen_pstart), &xen_start,
			     sizeof(xen_start))) {
			ERRMSG("Can't get the value of xen_pstart.\n");
			return FALSE;
		}
		info->xen_heap_start = paddr_to_pfn(xen_start);
		info->xen_heap_end   = paddr_to_pfn(xen_end);
	}

	return TRUE;
}

int
get_xen_info_ia64(void)
{
	unsigned long xen_heap_start;
	int i;

	if (SYMBOL(xen_heap_start) == NOT_FOUND_SYMBOL) {
		ERRMSG("Can't get the symbol of xen_heap_start.\n");
		return FALSE;
	}
	if (!readmem(VADDR_XEN, SYMBOL(xen_heap_start), &xen_heap_start,
	      sizeof(xen_heap_start))) {
		ERRMSG("Can't get the value of xen_heap_start.\n");
		return FALSE;
	}
	for (i = 0; i < info->num_domain; i++) {
		info->domain_list[i].pickled_id = (unsigned int)
			(info->domain_list[i].domain_addr - xen_heap_start);
	}

	return TRUE;
}

#endif /* ia64 */

