/*
 * lkcdutils/libklib/arch/ia64/kl_page.c
 *
 * Copyright (C) 1999 - 2002 Silicon Graphics, Inc. All rights reserved.
 * Copyright (C) 2000 - 2002 Junichi Nomura, NEC Solutions <j-nomura@ce.jp.nec.com>
 * Copyright (C) 2002 Free Software Foundation, Inc. All rights reserved.
 */
#include <klib.h>

/*
 * IA-64 Page Table Traversal Functions:
 *
 * IA-64 Linux has two types of page table, the one is for user space
 * and the other is for kernel virtually mapped space.
 * 
 * You can find detailed explanation in linux/arch/ia64/kernel/ivt.S.
 * 
 * The description below is explanation for 16KB page size case.
 * 
 * User space page table:
 * 
 *    63 61          44     36       25       14           0
 *    +---+-----------+------+--------+--------+-----------+
 *     reg   -----     _pgd   pmd_idx  pte_idx  pte_offset
 *
 * The figure above explains structure of virtual address.
 * Bits 63-61 represent region number [0..4].
 * Bits 0 -13 represent page offset.
 * Bits 14-24 represent PTE index.
 * Bits 25-35 represent PMD index.
 * Bits 36-43 represent lower ranks of PGD index.
 * Bits 44-60 are sign extention of bit 43.
 *
 * PGD is indexed by bits [63,62,61,43,42,..,36].
 * 
 * 
 * Kernel space page table:
 * 
 *    63 61        47       36       25       14           0
 *    +---+---------+--------+--------+--------+-----------+
 *     0x5   -----   pgd_idx  pmd_idx  pte_idx  pte_offset
 *
 * The figure above explains structure of virtual address.
 * Bits 63-61 represent region number of kernel virtual space. [5]
 * Bits 0 -13 represent page offset.
 * Bits 14-24 represent PTE index.
 * Bits 25-35 represent PMD index.
 * Bits 36-46 represent PGD index.
 * Bits 44-60 are sign extention of bit 47.
 *
 * PGD is indexed by bits [46,45,44,43,42,..,36].
 */


/* 
 * VMALLOC_START is defined in <asm/pgtable.h>, but including this file
 * causes compile error. 
 */
#define VMALLOC_START 0xa000000000000000

#define IS_KERNEL_VADDR(vaddr) (vaddr > VMALLOC_START)

/*
 * pgd_offset():
 *    Searching PMD from given PGD and virtual address
 *
 * Input:
 *   pgd_base = kernel virtual address pointing to page directory
 *   vaddr    = virtual address
 *
 * Output:
 *   Returns kernel virtual address of PMD
 */
#define PGDIR_REGBIT_SHIFT (KL_PAGE_SHIFT - 6)
kaddr_t pgd_offset(kaddr_t pgd_base, kaddr_t vaddr)
{
	kaddr_t pgd_off, pgd_entry, pmd_base;
	unsigned long pgdir_index, region;

	pgdir_index = vaddr >> KL_PGDIR_SHIFT;
	region = vaddr >> 61;

	if (IS_KERNEL_VADDR(vaddr)) {
		pgd_off = (pgdir_index & (KL_PTRS_PER_PGD - 1)) * KL_NBPW;
	} else {
		pgd_off = ( (region << PGDIR_REGBIT_SHIFT)
			| ( pgdir_index & ((1 << PGDIR_REGBIT_SHIFT)-1)) )
			* KL_NBPW;
	}
	kl_vtop((pgd_base + pgd_off), &pgd_entry);
	kl_readmem(pgd_entry, KL_NBPW, &pmd_base);

	return pmd_base;
}

/*
 * pmd_offset():
 *     Searching page table from given PMD and virtual address
 *
 * Input:
 *   pmd_base = kernel virtual address pointing to PMD
 *   vaddr    = virtual address
 *
 * Output:
 *   Returns kernel virtual address of page table
 */
kaddr_t pmd_offset(kaddr_t pmd_base, kaddr_t vaddr)
{
	kaddr_t pmd_off, pmd_entry, pte_base;
	unsigned long pmd_index;

	pmd_index = (vaddr >> KL_PMD_SHIFT) & (KL_PTRS_PER_PMD-1);
	pmd_off = pmd_index * KL_NBPW;
	kl_vtop((pmd_base + pmd_off), &pmd_entry);
	kl_readmem(pmd_entry, KL_NBPW, &pte_base);

	return pte_base;
}

/*
 * pte_offset():
 *    Searching page from given page table and virtual address
 *
 * Input:
 *   pte_base = kernel virtual address pointing to page table
 *   vaddr    = virtual address
 *
 * Output:
 *   Returns kernel virtual address of page
 */
kaddr_t pte_offset(kaddr_t pte_base, kaddr_t vaddr)
{
	kaddr_t pte_off, pte, pte_val;
	unsigned long pte_index;

	pte_index = (vaddr >> KL_PAGE_SHIFT) & (KL_PTRS_PER_PTE-1);
	pte_off = pte_index * KL_NBPW;
	kl_vtop((pte_base + pte_off), &pte);
	kl_readmem(pte, KL_NBPW, &pte_val);

	return pte_val;
}

/*
 * mmap_virtop():
 *     Translating virtual address to physical address
 *
 * Input:
 *   vaddr    = virtual address to be resolved
 *   mmp      = pointer to mm_struct data
 *
 * Output:
 *   Returns physical address corresponding to the virtual address
 */
paddr_t mmap_virtop(kaddr_t vaddr, void *mmp)
{
	syment_t *sp;
	kaddr_t pgd_base, pmd_base;
	kaddr_t pte_base, pte_val;
	paddr_t paddr;

	/* pgd_offset, pmd_offset, pte_offset is architecture dependent */

	/* check if vaddr is kernel space or user space */
	if (IS_KERNEL_VADDR(vaddr)) {
		/* vaddr points to kernel virtual mapped space (vmalloc area) */
		sp = kl_lkup_symname("swapper_pg_dir");
		pgd_base = sp->s_addr;
	} else {
		/* vaddr points to user space */
		pgd_base = kl_kaddr(mmp, "mm_struct", "pgd");
	}

	/* Search for PMD */
	pmd_base = pgd_offset(pgd_base,vaddr);
	if (KL_ERROR) {
		return(0);
	}

	/* add KL_PAGE_OFFSET to make pmd_base as virtual address */
	pmd_base = pmd_base & KL_PMD_BASE_MASK;

	/* Search for page table */
	pte_base = pmd_offset(pmd_base,vaddr);
	if (KL_ERROR) {
		return(0);
	}

	/* add KL_PAGE_OFFSET to make pte_base as virtual address */
	pte_base = pte_base & KL_PT_BASE_MASK;


	/* Search for page address */
	pte_val = pte_offset(pte_base,vaddr);
	if (KL_ERROR) {
		return(0);
	}

	/* Creating a physical address */
	paddr = (pte_val & KL_PAGE_BASE_MASK) | (vaddr & (~(KL_PAGE_MASK)));

	return(paddr);
}
