/*
 * arch/i386/kl_page.c
 *
 * This file handles the architecture-dependent parts of KLIB for
 * i386 based systems.
 *
 * Copyright 1999 Silicon Graphics, Inc. All rights reserved.
 * Copyright 2000 Junichi Nomura, NEC Solutions <j-nomura@ce.jp.nec.com>
 */
#include <klib.h>

/*
 * page table traversal functions
 */
kl_pgd_t pgd_offset(kaddr_t pgd_base, kaddr_t vaddr)
{
	kaddr_t pgd_off;
	paddr_t pgd_entry;
	kl_pgd_t pgd;

	pgd_off = ((vaddr >> KL_PGDIR_SHIFT)&(KL_PTRS_PER_PGD-1)) * sizeof(pgd);
	kl_vtop((pgd_base + pgd_off), &pgd_entry);
	kl_readmem(pgd_entry, sizeof(pgd), &pgd);

	return pgd;
}

kl_pmd_t pmd_offset(kl_pgd_t pgd, kaddr_t vaddr)
{
	kl_pmd_t pmd;
#ifdef CONFIG_X86_PAE
	kaddr_t pmd_off;

	pmd_off = ((vaddr >> KL_PMD_SHIFT) & (KL_PTRS_PER_PMD-1)) * sizeof(pmd);
	kl_pgd_val(pgd) &= KL_PMD_BASE_MASK;
	kl_readmem((kl_pgd_val(pgd) + pmd_off), sizeof(pmd), &pmd);
	return pmd;
#else /* CONFIG_X86_PAE */
	kl_pmd_val(pmd) = kl_pgd_val(pgd);
	return pmd;
#endif /* CONFIG_X86_PAE */
}

kl_pte_t pte_offset(kl_pmd_t pmd, kaddr_t vaddr)
{
	kaddr_t pte_off;
	kl_pte_t pte;

	pte_off = ((vaddr >> KL_PAGE_SHIFT) & (KL_PTRS_PER_PTE-1)) * 
		sizeof(pte);
	kl_pmd_val(pmd) &= KL_PT_BASE_MASK;
	kl_readmem((kl_pmd_val(pmd) + pte_off), sizeof(pte), &pte);
	return pte;
}

paddr_t mmap_virtop(kaddr_t vaddr, void *mmp)
{
	kaddr_t pgd_base;
	paddr_t paddr;
	kl_pgd_t pgd;
	kl_pmd_t pmd;
	kl_pte_t pte;

	/* pgd_offset, pmd_offset, pte_offset is architecture dependent */
	pgd_base = kl_kaddr(mmp, "mm_struct", "pgd");
	pgd = pgd_offset(pgd_base, vaddr);
	if (kl_pgd_none(pgd) || kl_pgd_bad(pgd) || KL_ERROR) {
		KL_ERROR = KLE_INVALID_MAPPING;
		return(0);
	}

	/* check for use of page size extension, i.e. 4MB page */
	if(kl_pgd_val(pgd) & KL_PSE_BIT) {
		paddr = (kl_pgd_val(pgd) & KL_PSE_PAGE_MASK) | 
			(vaddr & KL_PSE_OFFSET_MASK);
		return(paddr);
	} else {
		/* get the pmd entry */
		pmd = pmd_offset(pgd, vaddr);
		if (kl_pmd_none(pmd) || kl_pmd_bad(pmd) || KL_ERROR) {
			KL_ERROR = KLE_INVALID_MAPPING;
			return(0);
		}
#ifdef CONFIG_X86_PAE
		/* check for use of page size extension, i.e. 2MB page */
		if(kl_pmd_val(pmd) & KL_PSE_BIT) {
			paddr = (kl_pmd_val(pmd) & KL_PSE_PAGE_MASK) | 
				(vaddr & KL_PSE_OFFSET_MASK);
			return(paddr);
		}
#endif		
		/* get the pte */
		pte = pte_offset(pmd, vaddr);
		if (kl_pte_none(pte) || kl_pte_bad(pte) || KL_ERROR) {
			KL_ERROR = KLE_INVALID_MAPPING;
			return(0);
		}

		paddr = (kl_pte_val(pte) & KL_PAGE_BASE_MASK) | 
			(vaddr & (~(KL_PAGE_MASK)));
		return(paddr);
	}
}
