Tuesday, December 21, 2010

Convert userspace virtual address to physical address, x86_64

It is somewhat strange that kernel module developers have to write their own function to convert the userspace VA to PA. It is not very hard, but still not so easy if you are not familiar with those complex types that represent the page-tables.

It is much easier to convert the kernel VA to PA, but userspace is a different story, especially some mmap-ed memory area that is mapped by a device driver, in which case the get_user_pages can't work.

OK, I wrote one and am posting it here in case someone or I will use it in future. It can only handle x86_64 which has a full four-level-page-table. I didn't include any headers, because headers always change..
static int bad_address(void *p)
{
    unsigned long dummy;
    return probe_kernel_address((unsigned long*)p, dummy);
}

/*
 * map any virtual address of the current process to its
 * physical one.
 */
static unsigned long any_v2p(unsigned long vaddr)
{
    pgd_t *pgd = pgd_offset(current->mm, vaddr);
    pud_t *pud;
    pmd_t *pmd;
    pte_t *pte;

    /* to lock the page */
    struct page *pg;
    unsigned long paddr;

    if (bad_address(pgd)) {
        printk(KERN_ALERT "[nskk] Alert: bad address of pgd %p\n", pgd);
        goto bad;
    }
    if (!pgd_present(*pgd)) {
        printk(KERN_ALERT "[nskk] Alert: pgd not present %lu\n", *pgd);
        goto out;
    }

    pud = pud_offset(pgd, vaddr);
    if (bad_address(pud)) {
        printk(KERN_ALERT "[nskk] Alert: bad address of pud %p\n", pud);
        goto bad;
    }
    if (!pud_present(*pud) || pud_large(*pud)) {
        printk(KERN_ALERT "[nskk] Alert: pud not present %lu\n", *pud);
        goto out;
    }

    pmd = pmd_offset(pud, vaddr);
    if (bad_address(pmd)) {
        printk(KERN_ALERT "[nskk] Alert: bad address of pmd %p\n", pmd);
        goto bad;
    }
    if (!pmd_present(*pmd) || pmd_large(*pmd)) {
        printk(KERN_ALERT "[nskk] Alert: pmd not present %lu\n", *md);
        goto out;
    }

    pte = pte_offset_kernel(pmd, vaddr);
    if (bad_address(pte)) {
        printk(KERN_ALERT "[nskk] Alert: bad address of pte %p\n", pte);
        goto bad;
    }
    if (!pte_present(*pte)) {
        printk(KERN_ALERT "[nskk] Alert: pte not present %lu\n", *pte);
        goto out;
    }

    pg = pte_page(*pte);
    paddr = (pte_val(*pte) & PHYSICAL_PAGE_MASK) | (vaddr&(PAGE_SIZE-1));

out:
    return paddr;
bad:
    printk(KERN_ALERT "[nskk] Alert: Bad address\n");
    return 0;
}

1 comment:

Weibin Sun said...

I am, in fact, shamed on this code because it is filled with code doing nothing... But it can work... so don't feel surprised if you find any stupid logic inside. Hmmm, maybe this can be a quiz for people reading my post, to find those stupid logic in the code. :)