mirror of https://gitee.com/openkylin/linux.git
158 lines
3.3 KiB
C
158 lines
3.3 KiB
C
/*
|
|
* 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 <linux/hardirq.h>
|
|
#include <linux/mm.h>
|
|
#include <asm/uaccess.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;
|
|
|
|
if (in_atomic())
|
|
return ret;
|
|
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 (is_init(current)) {
|
|
yield();
|
|
down_read(&mm->mmap_sem);
|
|
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);
|
|
}
|