/******************************************************************************
 * xc_pagetab.c
 *
 * Function to translate virtual to physical addresses.
 */
#include "xc_private.h"

#if defined(__i386__)

#define L1_PAGETABLE_SHIFT_PAE	12
#define L2_PAGETABLE_SHIFT_PAE	21
#define L3_PAGETABLE_SHIFT_PAE	30

#define L1_PAGETABLE_SHIFT		12
#define L2_PAGETABLE_SHIFT		22

#define L0_PAGETABLE_MASK_PAE	0x00000ffffffff000ULL
#define L1_PAGETABLE_MASK_PAE	0x1ffULL
#define L2_PAGETABLE_MASK_PAE	0x1ffULL
#define L3_PAGETABLE_MASK_PAE	0x3ULL

#define L0_PAGETABLE_MASK		0xfffff000ULL
#define L1_PAGETABLE_MASK		0x3ffULL
#define L2_PAGETABLE_MASK		0x3ffULL

#elif defined(__x86_64__)

#define L1_PAGETABLE_SHIFT_PAE	12
#define L2_PAGETABLE_SHIFT_PAE	21
#define L3_PAGETABLE_SHIFT_PAE	30
#define L4_PAGETABLE_SHIFT_PAE	39

#define L1_PAGETABLE_SHIFT		L1_PAGETABLE_SHIFT_PAE
#define L2_PAGETABLE_SHIFT		L2_PAGETABLE_SHIFT_PAE

#define L0_PAGETABLE_MASK_PAE	0x000ffffffffff000ULL
#define L1_PAGETABLE_MASK_PAE	0x1ffULL
#define L2_PAGETABLE_MASK_PAE	0x1ffULL
#define L3_PAGETABLE_MASK_PAE	0x1ffULL
#define L4_PAGETABLE_MASK_PAE	0x1ffULL

#define L0_PAGETABLE_MASK		L0_PAGETABLE_MASK_PAE
#define L1_PAGETABLE_MASK		L1_PAGETABLE_MASK_PAE
#define L2_PAGETABLE_MASK		L2_PAGETABLE_MASK_PAE

#endif

unsigned long xc_translate_foreign_address(int xc_handle, uint32_t dom,
                                           int vcpu, unsigned long long virt )
{
    vcpu_guest_context_t ctx;
    unsigned long long cr3;
    void *pd, *pt, *pdppage = NULL, *pdp, *pml = NULL;
    unsigned long long pde, pte, pdpe, pmle;
    unsigned long mfn = 0;
#if defined (__i386__)
    static int pt_levels = 0;

    if (pt_levels == 0) {
        xen_capabilities_info_t xen_caps = "";

        if (xc_version(xc_handle, XENVER_capabilities, &xen_caps) != 0)
            goto out;
        if (strstr(xen_caps, "xen-3.0-x86_64"))
            pt_levels = 4;
        else if (strstr(xen_caps, "xen-3.0-x86_32p"))
            pt_levels = 3;
        else if (strstr(xen_caps, "xen-3.0-x86_32"))
            pt_levels = 2;
        else
            goto out;
    }
#elif defined (__x86_64__)
#define pt_levels 4
#endif

    if (xc_vcpu_getcontext(xc_handle, dom, vcpu, &ctx) != 0) {
        DPRINTF("failed to retreive vcpu context\n");
        goto out;
    }
    cr3 = ((unsigned long long)xen_cr3_to_pfn(ctx.ctrlreg[3])) << PAGE_SHIFT;

    /* Page Map Level 4 */

#if defined(__i386__)
    pmle = cr3;
#elif defined(__x86_64__)
    pml = xc_map_foreign_range(xc_handle, dom, PAGE_SIZE, PROT_READ, cr3 >> PAGE_SHIFT);
    if (pml == NULL) {
        DPRINTF("failed to map PML4\n");
        goto out;
    }
    pmle = *(unsigned long long *)(pml + 8 * ((virt >> L4_PAGETABLE_SHIFT_PAE) & L4_PAGETABLE_MASK_PAE));
    if((pmle & 1) == 0) {
        DPRINTF("page entry not present in PML4\n");
        goto out_unmap_pml;
    }
#endif

    /* Page Directory Pointer Table */

    if (pt_levels >= 3) {
        pdppage = xc_map_foreign_range(xc_handle, dom, PAGE_SIZE, PROT_READ, pmle >> PAGE_SHIFT);
        if (pdppage == NULL) {
            DPRINTF("failed to map PDP\n");
            goto out_unmap_pml;
        }
        if (pt_levels >= 4)
            pdp = pdppage;
        else
            /* PDP is only 32 bit aligned with 3 level pts */
            pdp = pdppage + (pmle & ~(XC_PAGE_MASK | 0x1f));

        pdpe = *(unsigned long long *)(pdp + 8 * ((virt >> L3_PAGETABLE_SHIFT_PAE) & L3_PAGETABLE_MASK_PAE));

        if((pdpe & 1) == 0) {
            DPRINTF("page entry not present in PDP\n");
            goto out_unmap_pdp;
        }
    } else {
        pdpe = pmle;
    }

    /* Page Directory */

    pd = xc_map_foreign_range(xc_handle, dom, PAGE_SIZE, PROT_READ, pdpe >> PAGE_SHIFT);
    if (pd == NULL) {
        DPRINTF("failed to map PD\n");
        goto out_unmap_pdp;
    }

    if (pt_levels >= 3)
        pde = *(unsigned long long *)(pd + 8 * ((virt >> L2_PAGETABLE_SHIFT_PAE) & L2_PAGETABLE_MASK_PAE));
    else
        pde = *(unsigned long *)(pd + 4 * ((virt >> L2_PAGETABLE_SHIFT) & L2_PAGETABLE_MASK));

    if ((pde & 1) == 0) {
        DPRINTF("page entry not present in PD\n");
        goto out_unmap_pd;
    }

    /* Page Table */

    if (pde & 0x00000008) { /* 4M page (or 2M in PAE mode) */
        DPRINTF("Cannot currently cope with 2/4M pages\n");
        exit(-1);
    } else { /* 4k page */
        pt = xc_map_foreign_range(xc_handle, dom, PAGE_SIZE, PROT_READ,
                                  pde >> PAGE_SHIFT);

        if (pt == NULL) {
            DPRINTF("failed to map PT\n");
            goto out_unmap_pd;
        }

        if (pt_levels >= 3)
            pte = *(unsigned long long *)(pt + 8 * ((virt >> L1_PAGETABLE_SHIFT_PAE) & L1_PAGETABLE_MASK_PAE));
        else
            pte = *(unsigned long *)(pt + 4 * ((virt >> L1_PAGETABLE_SHIFT) & L1_PAGETABLE_MASK));

        if ((pte & 1) == 0) {
            DPRINTF("page entry not present in PT\n");
            goto out_unmap_pt;
        }

        if (pt_levels >= 3)
            mfn = (pte & L0_PAGETABLE_MASK_PAE) >> PAGE_SHIFT;
        else
            mfn = (pte & L0_PAGETABLE_MASK) >> PAGE_SHIFT;
    }

 out_unmap_pt:
    munmap(pt, PAGE_SIZE);
 out_unmap_pd:
    munmap(pd, PAGE_SIZE);
 out_unmap_pdp:
    munmap(pdppage, PAGE_SIZE);
 out_unmap_pml:
    munmap(pml, PAGE_SIZE);
 out:
    return mfn;
}

/*
 * Local variables:
 * mode: C
 * c-set-style: "BSD"
 * c-basic-offset: 4
 * tab-width: 4
 * indent-tabs-mode: nil
 * End:
 */
