summaryrefslogtreecommitdiff
path: root/arch/s390/lib/uaccess_pt.c
diff options
context:
space:
mode:
authorGerald Schaefer <geraldsc@de.ibm.com>2006-12-04 17:40:45 +0300
committerMartin Schwidefsky <schwidefsky@de.ibm.com>2006-12-04 17:40:45 +0300
commit59f35d53fde3987d071ea1c9bf1c9ba29fcb69fe (patch)
tree50853c1bf3ad3af080707fb2e6b3722d3d86db9e /arch/s390/lib/uaccess_pt.c
parentd57de5a36791cb1b7285649c62f183b0d3505f7d (diff)
downloadlinux-59f35d53fde3987d071ea1c9bf1c9ba29fcb69fe.tar.xz
[S390] Add dynamic size check for usercopy functions.
Use a wrapper for copy_to/from_user to chose the best usercopy method. The mvcos instruction is better for sizes greater than 256 bytes, if mvcos is not available a page table walk is better for sizes greater than 1024 bytes. Also removed the redundant copy_to/from_user_std_small functions. Signed-off-by: Gerald Schaefer <geraldsc@de.ibm.com> Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
Diffstat (limited to 'arch/s390/lib/uaccess_pt.c')
-rw-r--r--arch/s390/lib/uaccess_pt.c153
1 files changed, 153 insertions, 0 deletions
diff --git a/arch/s390/lib/uaccess_pt.c b/arch/s390/lib/uaccess_pt.c
new file mode 100644
index 000000000000..8741bdc09299
--- /dev/null
+++ b/arch/s390/lib/uaccess_pt.c
@@ -0,0 +1,153 @@
+/*
+ * arch/s390/lib/uaccess_pt.c
+ *
+ * User access functions based on page table walks.
+ *
+ * Copyright IBM Corp. 2006
+ * Author(s): Gerald Schaefer (gerald.schaefer@de.ibm.com)
+ */
+
+#include <linux/errno.h>
+#include <asm/uaccess.h>
+#include <linux/mm.h>
+#include <asm/futex.h>
+
+static inline int __handle_fault(struct mm_struct *mm, unsigned long address,
+ int write_access)
+{
+ struct vm_area_struct *vma;
+ int ret = -EFAULT;
+
+ down_read(&mm->mmap_sem);
+ vma = find_vma(mm, address);
+ if (unlikely(!vma))
+ goto out;
+ if (unlikely(vma->vm_start > address)) {
+ if (!(vma->vm_flags & VM_GROWSDOWN))
+ goto out;
+ if (expand_stack(vma, address))
+ goto out;
+ }
+
+ if (!write_access) {
+ /* page not present, check vm flags */
+ if (!(vma->vm_flags & (VM_READ | VM_EXEC | VM_WRITE)))
+ goto out;
+ } else {
+ if (!(vma->vm_flags & VM_WRITE))
+ goto out;
+ }
+
+survive:
+ switch (handle_mm_fault(mm, vma, address, write_access)) {
+ case VM_FAULT_MINOR:
+ current->min_flt++;
+ break;
+ case VM_FAULT_MAJOR:
+ current->maj_flt++;
+ break;
+ case VM_FAULT_SIGBUS:
+ goto out_sigbus;
+ case VM_FAULT_OOM:
+ goto out_of_memory;
+ default:
+ BUG();
+ }
+ ret = 0;
+out:
+ up_read(&mm->mmap_sem);
+ return ret;
+
+out_of_memory:
+ up_read(&mm->mmap_sem);
+ if (current->pid == 1) {
+ yield();
+ goto survive;
+ }
+ printk("VM: killing process %s\n", current->comm);
+ return ret;
+
+out_sigbus:
+ up_read(&mm->mmap_sem);
+ current->thread.prot_addr = address;
+ current->thread.trap_no = 0x11;
+ force_sig(SIGBUS, current);
+ return ret;
+}
+
+static inline size_t __user_copy_pt(unsigned long uaddr, void *kptr,
+ size_t n, int write_user)
+{
+ struct mm_struct *mm = current->mm;
+ unsigned long offset, pfn, done, size;
+ pgd_t *pgd;
+ pmd_t *pmd;
+ pte_t *pte;
+ void *from, *to;
+
+ done = 0;
+retry:
+ spin_lock(&mm->page_table_lock);
+ do {
+ pgd = pgd_offset(mm, uaddr);
+ if (pgd_none(*pgd) || unlikely(pgd_bad(*pgd)))
+ goto fault;
+
+ pmd = pmd_offset(pgd, uaddr);
+ if (pmd_none(*pmd) || unlikely(pmd_bad(*pmd)))
+ goto fault;
+
+ pte = pte_offset_map(pmd, uaddr);
+ if (!pte || !pte_present(*pte) ||
+ (write_user && !pte_write(*pte)))
+ goto fault;
+
+ pfn = pte_pfn(*pte);
+ if (!pfn_valid(pfn))
+ goto out;
+
+ offset = uaddr & (PAGE_SIZE - 1);
+ size = min(n - done, PAGE_SIZE - offset);
+ if (write_user) {
+ to = (void *)((pfn << PAGE_SHIFT) + offset);
+ from = kptr + done;
+ } else {
+ from = (void *)((pfn << PAGE_SHIFT) + offset);
+ to = kptr + done;
+ }
+ memcpy(to, from, size);
+ done += size;
+ uaddr += size;
+ } while (done < n);
+out:
+ spin_unlock(&mm->page_table_lock);
+ return n - done;
+fault:
+ spin_unlock(&mm->page_table_lock);
+ if (__handle_fault(mm, uaddr, write_user))
+ return n - done;
+ goto retry;
+}
+
+size_t copy_from_user_pt(size_t n, const void __user *from, void *to)
+{
+ size_t rc;
+
+ if (segment_eq(get_fs(), KERNEL_DS)) {
+ memcpy(to, (void __kernel __force *) from, n);
+ return 0;
+ }
+ rc = __user_copy_pt((unsigned long) from, to, n, 0);
+ if (unlikely(rc))
+ memset(to + n - rc, 0, rc);
+ return rc;
+}
+
+size_t copy_to_user_pt(size_t n, void __user *to, const void *from)
+{
+ if (segment_eq(get_fs(), KERNEL_DS)) {
+ memcpy((void __kernel __force *) to, from, n);
+ return 0;
+ }
+ return __user_copy_pt((unsigned long) to, (void *) from, n, 1);
+}