linux/arch/arm/lib/uaccess_with_memcpy.c
<<
>>
Prefs
   1/*
   2 *  linux/arch/arm/lib/uaccess_with_memcpy.c
   3 *
   4 *  Written by: Lennert Buytenhek and Nicolas Pitre
   5 *  Copyright (C) 2009 Marvell Semiconductor
   6 *
   7 * This program is free software; you can redistribute it and/or modify
   8 * it under the terms of the GNU General Public License version 2 as
   9 * published by the Free Software Foundation.
  10 */
  11
  12#include <linux/kernel.h>
  13#include <linux/ctype.h>
  14#include <linux/uaccess.h>
  15#include <linux/rwsem.h>
  16#include <linux/mm.h>
  17#include <linux/sched.h>
  18#include <linux/hardirq.h> /* for in_atomic() */
  19#include <asm/current.h>
  20#include <asm/page.h>
  21
  22static int
  23pin_page_for_write(const void __user *_addr, pte_t **ptep, spinlock_t **ptlp)
  24{
  25        unsigned long addr = (unsigned long)_addr;
  26        pgd_t *pgd;
  27        pmd_t *pmd;
  28        pte_t *pte;
  29        spinlock_t *ptl;
  30
  31        pgd = pgd_offset(current->mm, addr);
  32        if (unlikely(pgd_none(*pgd) || pgd_bad(*pgd)))
  33                return 0;
  34
  35        pmd = pmd_offset(pgd, addr);
  36        if (unlikely(pmd_none(*pmd) || pmd_bad(*pmd)))
  37                return 0;
  38
  39        pte = pte_offset_map_lock(current->mm, pmd, addr, &ptl);
  40        if (unlikely(!pte_present(*pte) || !pte_young(*pte) ||
  41            !pte_write(*pte) || !pte_dirty(*pte))) {
  42                pte_unmap_unlock(pte, ptl);
  43                return 0;
  44        }
  45
  46        *ptep = pte;
  47        *ptlp = ptl;
  48
  49        return 1;
  50}
  51
  52static unsigned long noinline
  53__copy_to_user_memcpy(void __user *to, const void *from, unsigned long n)
  54{
  55        int atomic;
  56
  57        if (unlikely(segment_eq(get_fs(), KERNEL_DS))) {
  58                memcpy((void *)to, from, n);
  59                return 0;
  60        }
  61
  62        /* the mmap semaphore is taken only if not in an atomic context */
  63        atomic = in_atomic();
  64
  65        if (!atomic)
  66                down_read(&current->mm->mmap_sem);
  67        while (n) {
  68                pte_t *pte;
  69                spinlock_t *ptl;
  70                int tocopy;
  71
  72                while (!pin_page_for_write(to, &pte, &ptl)) {
  73                        if (!atomic)
  74                                up_read(&current->mm->mmap_sem);
  75                        if (__put_user(0, (char __user *)to))
  76                                goto out;
  77                        if (!atomic)
  78                                down_read(&current->mm->mmap_sem);
  79                }
  80
  81                tocopy = (~(unsigned long)to & ~PAGE_MASK) + 1;
  82                if (tocopy > n)
  83                        tocopy = n;
  84
  85                memcpy((void *)to, from, tocopy);
  86                to += tocopy;
  87                from += tocopy;
  88                n -= tocopy;
  89
  90                pte_unmap_unlock(pte, ptl);
  91        }
  92        if (!atomic)
  93                up_read(&current->mm->mmap_sem);
  94
  95out:
  96        return n;
  97}
  98
  99unsigned long
 100__copy_to_user(void __user *to, const void *from, unsigned long n)
 101{
 102        /*
 103         * This test is stubbed out of the main function above to keep
 104         * the overhead for small copies low by avoiding a large
 105         * register dump on the stack just to reload them right away.
 106         * With frame pointer disabled, tail call optimization kicks in
 107         * as well making this test almost invisible.
 108         */
 109        if (n < 64)
 110                return __copy_to_user_std(to, from, n);
 111        return __copy_to_user_memcpy(to, from, n);
 112}
 113        
 114static unsigned long noinline
 115__clear_user_memset(void __user *addr, unsigned long n)
 116{
 117        if (unlikely(segment_eq(get_fs(), KERNEL_DS))) {
 118                memset((void *)addr, 0, n);
 119                return 0;
 120        }
 121
 122        down_read(&current->mm->mmap_sem);
 123        while (n) {
 124                pte_t *pte;
 125                spinlock_t *ptl;
 126                int tocopy;
 127
 128                while (!pin_page_for_write(addr, &pte, &ptl)) {
 129                        up_read(&current->mm->mmap_sem);
 130                        if (__put_user(0, (char __user *)addr))
 131                                goto out;
 132                        down_read(&current->mm->mmap_sem);
 133                }
 134
 135                tocopy = (~(unsigned long)addr & ~PAGE_MASK) + 1;
 136                if (tocopy > n)
 137                        tocopy = n;
 138
 139                memset((void *)addr, 0, tocopy);
 140                addr += tocopy;
 141                n -= tocopy;
 142
 143                pte_unmap_unlock(pte, ptl);
 144        }
 145        up_read(&current->mm->mmap_sem);
 146
 147out:
 148        return n;
 149}
 150
 151unsigned long __clear_user(void __user *addr, unsigned long n)
 152{
 153        /* See rational for this in __copy_to_user() above. */
 154        if (n < 64)
 155                return __clear_user_std(addr, n);
 156        return __clear_user_memset(addr, n);
 157}
 158
 159#if 0
 160
 161/*
 162 * This code is disabled by default, but kept around in case the chosen
 163 * thresholds need to be revalidated.  Some overhead (small but still)
 164 * would be implied by a runtime determined variable threshold, and
 165 * so far the measurement on concerned targets didn't show a worthwhile
 166 * variation.
 167 *
 168 * Note that a fairly precise sched_clock() implementation is needed
 169 * for results to make some sense.
 170 */
 171
 172#include <linux/vmalloc.h>
 173
 174static int __init test_size_treshold(void)
 175{
 176        struct page *src_page, *dst_page;
 177        void *user_ptr, *kernel_ptr;
 178        unsigned long long t0, t1, t2;
 179        int size, ret;
 180
 181        ret = -ENOMEM;
 182        src_page = alloc_page(GFP_KERNEL);
 183        if (!src_page)
 184                goto no_src;
 185        dst_page = alloc_page(GFP_KERNEL);
 186        if (!dst_page)
 187                goto no_dst;
 188        kernel_ptr = page_address(src_page);
 189        user_ptr = vmap(&dst_page, 1, VM_IOREMAP, __pgprot(__P010));
 190        if (!user_ptr)
 191                goto no_vmap;
 192
 193        /* warm up the src page dcache */
 194        ret = __copy_to_user_memcpy(user_ptr, kernel_ptr, PAGE_SIZE);
 195
 196        for (size = PAGE_SIZE; size >= 4; size /= 2) {
 197                t0 = sched_clock();
 198                ret |= __copy_to_user_memcpy(user_ptr, kernel_ptr, size);
 199                t1 = sched_clock();
 200                ret |= __copy_to_user_std(user_ptr, kernel_ptr, size);
 201                t2 = sched_clock();
 202                printk("copy_to_user: %d %llu %llu\n", size, t1 - t0, t2 - t1);
 203        }
 204
 205        for (size = PAGE_SIZE; size >= 4; size /= 2) {
 206                t0 = sched_clock();
 207                ret |= __clear_user_memset(user_ptr, size);
 208                t1 = sched_clock();
 209                ret |= __clear_user_std(user_ptr, size);
 210                t2 = sched_clock();
 211                printk("clear_user: %d %llu %llu\n", size, t1 - t0, t2 - t1);
 212        }
 213
 214        if (ret)
 215                ret = -EFAULT;
 216
 217        vunmap(user_ptr);
 218no_vmap:
 219        put_page(dst_page);
 220no_dst:
 221        put_page(src_page);
 222no_src:
 223        return ret;
 224}
 225
 226subsys_initcall(test_size_treshold);
 227
 228#endif
 229