linux/arch/um/kernel/skas/uaccess.c
Mike Rapoport e19f97ed67 um: add support for folded p4d page tables
The UML port uses 4 and 5 level fixups to support higher level page
table directories in the generic VM code.

Implement primitives necessary for the 4th level folding, add walks of
p4d level where appropriate and drop usage of __ARCH_USE_5LEVEL_HACK.

Link: http://lkml.kernel.org/r/1572938135-31886-13-git-send-email-rppt@kernel.org
Signed-off-by: Mike Rapoport <rppt@linux.ibm.com>
Cc: Anatoly Pugachev <matorola@gmail.com>
Cc: Anton Ivanov <anton.ivanov@cambridgegreys.com>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: "David S. Miller" <davem@davemloft.net>
Cc: Geert Uytterhoeven <geert@linux-m68k.org>
Cc: Greentime Hu <green.hu@gmail.com>
Cc: Greg Ungerer <gerg@linux-m68k.org>
Cc: Helge Deller <deller@gmx.de>
Cc: "James E.J. Bottomley" <James.Bottomley@HansenPartnership.com>
Cc: Jeff Dike <jdike@addtoit.com>
Cc: "Kirill A. Shutemov" <kirill@shutemov.name>
Cc: Mark Salter <msalter@redhat.com>
Cc: Matt Turner <mattst88@gmail.com>
Cc: Michal Simek <monstr@monstr.eu>
Cc: Peter Rosin <peda@axentia.se>
Cc: Richard Weinberger <richard@nod.at>
Cc: Rolf Eike Beer <eike-kernel@sf-tec.de>
Cc: Russell King <linux@armlinux.org.uk>
Cc: Russell King <rmk+kernel@armlinux.org.uk>
Cc: Sam Creasey <sammy@sammy.net>
Cc: Vincent Chen <deanbo422@gmail.com>
Cc: Vineet Gupta <Vineet.Gupta1@synopsys.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2019-12-04 19:44:15 -08:00

252 lines
4.9 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2002 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com)
*/
#include <linux/err.h>
#include <linux/highmem.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <asm/current.h>
#include <asm/page.h>
#include <asm/pgtable.h>
#include <kern_util.h>
#include <os.h>
pte_t *virt_to_pte(struct mm_struct *mm, unsigned long addr)
{
pgd_t *pgd;
p4d_t *p4d;
pud_t *pud;
pmd_t *pmd;
if (mm == NULL)
return NULL;
pgd = pgd_offset(mm, addr);
if (!pgd_present(*pgd))
return NULL;
p4d = p4d_offset(pgd, addr);
if (!p4d_present(*p4d))
return NULL;
pud = pud_offset(p4d, addr);
if (!pud_present(*pud))
return NULL;
pmd = pmd_offset(pud, addr);
if (!pmd_present(*pmd))
return NULL;
return pte_offset_kernel(pmd, addr);
}
static pte_t *maybe_map(unsigned long virt, int is_write)
{
pte_t *pte = virt_to_pte(current->mm, virt);
int err, dummy_code;
if ((pte == NULL) || !pte_present(*pte) ||
(is_write && !pte_write(*pte))) {
err = handle_page_fault(virt, 0, is_write, 1, &dummy_code);
if (err)
return NULL;
pte = virt_to_pte(current->mm, virt);
}
if (!pte_present(*pte))
pte = NULL;
return pte;
}
static int do_op_one_page(unsigned long addr, int len, int is_write,
int (*op)(unsigned long addr, int len, void *arg), void *arg)
{
struct page *page;
pte_t *pte;
int n;
pte = maybe_map(addr, is_write);
if (pte == NULL)
return -1;
page = pte_page(*pte);
#ifdef CONFIG_64BIT
pagefault_disable();
addr = (unsigned long) page_address(page) +
(addr & ~PAGE_MASK);
#else
addr = (unsigned long) kmap_atomic(page) +
(addr & ~PAGE_MASK);
#endif
n = (*op)(addr, len, arg);
#ifdef CONFIG_64BIT
pagefault_enable();
#else
kunmap_atomic((void *)addr);
#endif
return n;
}
static long buffer_op(unsigned long addr, int len, int is_write,
int (*op)(unsigned long, int, void *), void *arg)
{
long size, remain, n;
size = min(PAGE_ALIGN(addr) - addr, (unsigned long) len);
remain = len;
n = do_op_one_page(addr, size, is_write, op, arg);
if (n != 0) {
remain = (n < 0 ? remain : 0);
goto out;
}
addr += size;
remain -= size;
if (remain == 0)
goto out;
while (addr < ((addr + remain) & PAGE_MASK)) {
n = do_op_one_page(addr, PAGE_SIZE, is_write, op, arg);
if (n != 0) {
remain = (n < 0 ? remain : 0);
goto out;
}
addr += PAGE_SIZE;
remain -= PAGE_SIZE;
}
if (remain == 0)
goto out;
n = do_op_one_page(addr, remain, is_write, op, arg);
if (n != 0) {
remain = (n < 0 ? remain : 0);
goto out;
}
return 0;
out:
return remain;
}
static int copy_chunk_from_user(unsigned long from, int len, void *arg)
{
unsigned long *to_ptr = arg, to = *to_ptr;
memcpy((void *) to, (void *) from, len);
*to_ptr += len;
return 0;
}
unsigned long raw_copy_from_user(void *to, const void __user *from, unsigned long n)
{
if (uaccess_kernel()) {
memcpy(to, (__force void*)from, n);
return 0;
}
return buffer_op((unsigned long) from, n, 0, copy_chunk_from_user, &to);
}
EXPORT_SYMBOL(raw_copy_from_user);
static int copy_chunk_to_user(unsigned long to, int len, void *arg)
{
unsigned long *from_ptr = arg, from = *from_ptr;
memcpy((void *) to, (void *) from, len);
*from_ptr += len;
return 0;
}
unsigned long raw_copy_to_user(void __user *to, const void *from, unsigned long n)
{
if (uaccess_kernel()) {
memcpy((__force void *) to, from, n);
return 0;
}
return buffer_op((unsigned long) to, n, 1, copy_chunk_to_user, &from);
}
EXPORT_SYMBOL(raw_copy_to_user);
static int strncpy_chunk_from_user(unsigned long from, int len, void *arg)
{
char **to_ptr = arg, *to = *to_ptr;
int n;
strncpy(to, (void *) from, len);
n = strnlen(to, len);
*to_ptr += n;
if (n < len)
return 1;
return 0;
}
long __strncpy_from_user(char *dst, const char __user *src, long count)
{
long n;
char *ptr = dst;
if (uaccess_kernel()) {
strncpy(dst, (__force void *) src, count);
return strnlen(dst, count);
}
n = buffer_op((unsigned long) src, count, 0, strncpy_chunk_from_user,
&ptr);
if (n != 0)
return -EFAULT;
return strnlen(dst, count);
}
EXPORT_SYMBOL(__strncpy_from_user);
static int clear_chunk(unsigned long addr, int len, void *unused)
{
memset((void *) addr, 0, len);
return 0;
}
unsigned long __clear_user(void __user *mem, unsigned long len)
{
if (uaccess_kernel()) {
memset((__force void*)mem, 0, len);
return 0;
}
return buffer_op((unsigned long) mem, len, 1, clear_chunk, NULL);
}
EXPORT_SYMBOL(__clear_user);
static int strnlen_chunk(unsigned long str, int len, void *arg)
{
int *len_ptr = arg, n;
n = strnlen((void *) str, len);
*len_ptr += n;
if (n < len)
return 1;
return 0;
}
long __strnlen_user(const void __user *str, long len)
{
int count = 0, n;
if (uaccess_kernel())
return strnlen((__force char*)str, len) + 1;
n = buffer_op((unsigned long) str, len, 0, strnlen_chunk, &count);
if (n == 0)
return count + 1;
return 0;
}
EXPORT_SYMBOL(__strnlen_user);