162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *  linux/arch/arm/lib/uaccess_with_memcpy.c
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci *  Written by: Lennert Buytenhek and Nicolas Pitre
662306a36Sopenharmony_ci *  Copyright (C) 2009 Marvell Semiconductor
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/kernel.h>
1062306a36Sopenharmony_ci#include <linux/ctype.h>
1162306a36Sopenharmony_ci#include <linux/uaccess.h>
1262306a36Sopenharmony_ci#include <linux/rwsem.h>
1362306a36Sopenharmony_ci#include <linux/mm.h>
1462306a36Sopenharmony_ci#include <linux/sched.h>
1562306a36Sopenharmony_ci#include <linux/hardirq.h> /* for in_atomic() */
1662306a36Sopenharmony_ci#include <linux/gfp.h>
1762306a36Sopenharmony_ci#include <linux/highmem.h>
1862306a36Sopenharmony_ci#include <linux/hugetlb.h>
1962306a36Sopenharmony_ci#include <asm/current.h>
2062306a36Sopenharmony_ci#include <asm/page.h>
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_cistatic int
2362306a36Sopenharmony_cipin_page_for_write(const void __user *_addr, pte_t **ptep, spinlock_t **ptlp)
2462306a36Sopenharmony_ci{
2562306a36Sopenharmony_ci	unsigned long addr = (unsigned long)_addr;
2662306a36Sopenharmony_ci	pgd_t *pgd;
2762306a36Sopenharmony_ci	p4d_t *p4d;
2862306a36Sopenharmony_ci	pmd_t *pmd;
2962306a36Sopenharmony_ci	pte_t *pte;
3062306a36Sopenharmony_ci	pud_t *pud;
3162306a36Sopenharmony_ci	spinlock_t *ptl;
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci	pgd = pgd_offset(current->mm, addr);
3462306a36Sopenharmony_ci	if (unlikely(pgd_none(*pgd) || pgd_bad(*pgd)))
3562306a36Sopenharmony_ci		return 0;
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci	p4d = p4d_offset(pgd, addr);
3862306a36Sopenharmony_ci	if (unlikely(p4d_none(*p4d) || p4d_bad(*p4d)))
3962306a36Sopenharmony_ci		return 0;
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	pud = pud_offset(p4d, addr);
4262306a36Sopenharmony_ci	if (unlikely(pud_none(*pud) || pud_bad(*pud)))
4362306a36Sopenharmony_ci		return 0;
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci	pmd = pmd_offset(pud, addr);
4662306a36Sopenharmony_ci	if (unlikely(pmd_none(*pmd)))
4762306a36Sopenharmony_ci		return 0;
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	/*
5062306a36Sopenharmony_ci	 * A pmd can be bad if it refers to a HugeTLB or THP page.
5162306a36Sopenharmony_ci	 *
5262306a36Sopenharmony_ci	 * Both THP and HugeTLB pages have the same pmd layout
5362306a36Sopenharmony_ci	 * and should not be manipulated by the pte functions.
5462306a36Sopenharmony_ci	 *
5562306a36Sopenharmony_ci	 * Lock the page table for the destination and check
5662306a36Sopenharmony_ci	 * to see that it's still huge and whether or not we will
5762306a36Sopenharmony_ci	 * need to fault on write.
5862306a36Sopenharmony_ci	 */
5962306a36Sopenharmony_ci	if (unlikely(pmd_thp_or_huge(*pmd))) {
6062306a36Sopenharmony_ci		ptl = &current->mm->page_table_lock;
6162306a36Sopenharmony_ci		spin_lock(ptl);
6262306a36Sopenharmony_ci		if (unlikely(!pmd_thp_or_huge(*pmd)
6362306a36Sopenharmony_ci			|| pmd_hugewillfault(*pmd))) {
6462306a36Sopenharmony_ci			spin_unlock(ptl);
6562306a36Sopenharmony_ci			return 0;
6662306a36Sopenharmony_ci		}
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci		*ptep = NULL;
6962306a36Sopenharmony_ci		*ptlp = ptl;
7062306a36Sopenharmony_ci		return 1;
7162306a36Sopenharmony_ci	}
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	if (unlikely(pmd_bad(*pmd)))
7462306a36Sopenharmony_ci		return 0;
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	pte = pte_offset_map_lock(current->mm, pmd, addr, &ptl);
7762306a36Sopenharmony_ci	if (unlikely(!pte))
7862306a36Sopenharmony_ci		return 0;
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	if (unlikely(!pte_present(*pte) || !pte_young(*pte) ||
8162306a36Sopenharmony_ci	    !pte_write(*pte) || !pte_dirty(*pte))) {
8262306a36Sopenharmony_ci		pte_unmap_unlock(pte, ptl);
8362306a36Sopenharmony_ci		return 0;
8462306a36Sopenharmony_ci	}
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	*ptep = pte;
8762306a36Sopenharmony_ci	*ptlp = ptl;
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	return 1;
9062306a36Sopenharmony_ci}
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_cistatic unsigned long noinline
9362306a36Sopenharmony_ci__copy_to_user_memcpy(void __user *to, const void *from, unsigned long n)
9462306a36Sopenharmony_ci{
9562306a36Sopenharmony_ci	unsigned long ua_flags;
9662306a36Sopenharmony_ci	int atomic;
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	/* the mmap semaphore is taken only if not in an atomic context */
9962306a36Sopenharmony_ci	atomic = faulthandler_disabled();
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	if (!atomic)
10262306a36Sopenharmony_ci		mmap_read_lock(current->mm);
10362306a36Sopenharmony_ci	while (n) {
10462306a36Sopenharmony_ci		pte_t *pte;
10562306a36Sopenharmony_ci		spinlock_t *ptl;
10662306a36Sopenharmony_ci		int tocopy;
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci		while (!pin_page_for_write(to, &pte, &ptl)) {
10962306a36Sopenharmony_ci			if (!atomic)
11062306a36Sopenharmony_ci				mmap_read_unlock(current->mm);
11162306a36Sopenharmony_ci			if (__put_user(0, (char __user *)to))
11262306a36Sopenharmony_ci				goto out;
11362306a36Sopenharmony_ci			if (!atomic)
11462306a36Sopenharmony_ci				mmap_read_lock(current->mm);
11562306a36Sopenharmony_ci		}
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci		tocopy = (~(unsigned long)to & ~PAGE_MASK) + 1;
11862306a36Sopenharmony_ci		if (tocopy > n)
11962306a36Sopenharmony_ci			tocopy = n;
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci		ua_flags = uaccess_save_and_enable();
12262306a36Sopenharmony_ci		__memcpy((void *)to, from, tocopy);
12362306a36Sopenharmony_ci		uaccess_restore(ua_flags);
12462306a36Sopenharmony_ci		to += tocopy;
12562306a36Sopenharmony_ci		from += tocopy;
12662306a36Sopenharmony_ci		n -= tocopy;
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci		if (pte)
12962306a36Sopenharmony_ci			pte_unmap_unlock(pte, ptl);
13062306a36Sopenharmony_ci		else
13162306a36Sopenharmony_ci			spin_unlock(ptl);
13262306a36Sopenharmony_ci	}
13362306a36Sopenharmony_ci	if (!atomic)
13462306a36Sopenharmony_ci		mmap_read_unlock(current->mm);
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ciout:
13762306a36Sopenharmony_ci	return n;
13862306a36Sopenharmony_ci}
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ciunsigned long
14162306a36Sopenharmony_ciarm_copy_to_user(void __user *to, const void *from, unsigned long n)
14262306a36Sopenharmony_ci{
14362306a36Sopenharmony_ci	/*
14462306a36Sopenharmony_ci	 * This test is stubbed out of the main function above to keep
14562306a36Sopenharmony_ci	 * the overhead for small copies low by avoiding a large
14662306a36Sopenharmony_ci	 * register dump on the stack just to reload them right away.
14762306a36Sopenharmony_ci	 * With frame pointer disabled, tail call optimization kicks in
14862306a36Sopenharmony_ci	 * as well making this test almost invisible.
14962306a36Sopenharmony_ci	 */
15062306a36Sopenharmony_ci	if (n < 64) {
15162306a36Sopenharmony_ci		unsigned long ua_flags = uaccess_save_and_enable();
15262306a36Sopenharmony_ci		n = __copy_to_user_std(to, from, n);
15362306a36Sopenharmony_ci		uaccess_restore(ua_flags);
15462306a36Sopenharmony_ci	} else {
15562306a36Sopenharmony_ci		n = __copy_to_user_memcpy(uaccess_mask_range_ptr(to, n),
15662306a36Sopenharmony_ci					  from, n);
15762306a36Sopenharmony_ci	}
15862306a36Sopenharmony_ci	return n;
15962306a36Sopenharmony_ci}
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_cistatic unsigned long noinline
16262306a36Sopenharmony_ci__clear_user_memset(void __user *addr, unsigned long n)
16362306a36Sopenharmony_ci{
16462306a36Sopenharmony_ci	unsigned long ua_flags;
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	mmap_read_lock(current->mm);
16762306a36Sopenharmony_ci	while (n) {
16862306a36Sopenharmony_ci		pte_t *pte;
16962306a36Sopenharmony_ci		spinlock_t *ptl;
17062306a36Sopenharmony_ci		int tocopy;
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci		while (!pin_page_for_write(addr, &pte, &ptl)) {
17362306a36Sopenharmony_ci			mmap_read_unlock(current->mm);
17462306a36Sopenharmony_ci			if (__put_user(0, (char __user *)addr))
17562306a36Sopenharmony_ci				goto out;
17662306a36Sopenharmony_ci			mmap_read_lock(current->mm);
17762306a36Sopenharmony_ci		}
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci		tocopy = (~(unsigned long)addr & ~PAGE_MASK) + 1;
18062306a36Sopenharmony_ci		if (tocopy > n)
18162306a36Sopenharmony_ci			tocopy = n;
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci		ua_flags = uaccess_save_and_enable();
18462306a36Sopenharmony_ci		__memset((void *)addr, 0, tocopy);
18562306a36Sopenharmony_ci		uaccess_restore(ua_flags);
18662306a36Sopenharmony_ci		addr += tocopy;
18762306a36Sopenharmony_ci		n -= tocopy;
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci		if (pte)
19062306a36Sopenharmony_ci			pte_unmap_unlock(pte, ptl);
19162306a36Sopenharmony_ci		else
19262306a36Sopenharmony_ci			spin_unlock(ptl);
19362306a36Sopenharmony_ci	}
19462306a36Sopenharmony_ci	mmap_read_unlock(current->mm);
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ciout:
19762306a36Sopenharmony_ci	return n;
19862306a36Sopenharmony_ci}
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ciunsigned long arm_clear_user(void __user *addr, unsigned long n)
20162306a36Sopenharmony_ci{
20262306a36Sopenharmony_ci	/* See rational for this in __copy_to_user() above. */
20362306a36Sopenharmony_ci	if (n < 64) {
20462306a36Sopenharmony_ci		unsigned long ua_flags = uaccess_save_and_enable();
20562306a36Sopenharmony_ci		n = __clear_user_std(addr, n);
20662306a36Sopenharmony_ci		uaccess_restore(ua_flags);
20762306a36Sopenharmony_ci	} else {
20862306a36Sopenharmony_ci		n = __clear_user_memset(addr, n);
20962306a36Sopenharmony_ci	}
21062306a36Sopenharmony_ci	return n;
21162306a36Sopenharmony_ci}
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci#if 0
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci/*
21662306a36Sopenharmony_ci * This code is disabled by default, but kept around in case the chosen
21762306a36Sopenharmony_ci * thresholds need to be revalidated.  Some overhead (small but still)
21862306a36Sopenharmony_ci * would be implied by a runtime determined variable threshold, and
21962306a36Sopenharmony_ci * so far the measurement on concerned targets didn't show a worthwhile
22062306a36Sopenharmony_ci * variation.
22162306a36Sopenharmony_ci *
22262306a36Sopenharmony_ci * Note that a fairly precise sched_clock() implementation is needed
22362306a36Sopenharmony_ci * for results to make some sense.
22462306a36Sopenharmony_ci */
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci#include <linux/vmalloc.h>
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_cistatic int __init test_size_treshold(void)
22962306a36Sopenharmony_ci{
23062306a36Sopenharmony_ci	struct page *src_page, *dst_page;
23162306a36Sopenharmony_ci	void *user_ptr, *kernel_ptr;
23262306a36Sopenharmony_ci	unsigned long long t0, t1, t2;
23362306a36Sopenharmony_ci	int size, ret;
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci	ret = -ENOMEM;
23662306a36Sopenharmony_ci	src_page = alloc_page(GFP_KERNEL);
23762306a36Sopenharmony_ci	if (!src_page)
23862306a36Sopenharmony_ci		goto no_src;
23962306a36Sopenharmony_ci	dst_page = alloc_page(GFP_KERNEL);
24062306a36Sopenharmony_ci	if (!dst_page)
24162306a36Sopenharmony_ci		goto no_dst;
24262306a36Sopenharmony_ci	kernel_ptr = page_address(src_page);
24362306a36Sopenharmony_ci	user_ptr = vmap(&dst_page, 1, VM_IOREMAP, __pgprot(__PAGE_COPY));
24462306a36Sopenharmony_ci	if (!user_ptr)
24562306a36Sopenharmony_ci		goto no_vmap;
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci	/* warm up the src page dcache */
24862306a36Sopenharmony_ci	ret = __copy_to_user_memcpy(user_ptr, kernel_ptr, PAGE_SIZE);
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci	for (size = PAGE_SIZE; size >= 4; size /= 2) {
25162306a36Sopenharmony_ci		t0 = sched_clock();
25262306a36Sopenharmony_ci		ret |= __copy_to_user_memcpy(user_ptr, kernel_ptr, size);
25362306a36Sopenharmony_ci		t1 = sched_clock();
25462306a36Sopenharmony_ci		ret |= __copy_to_user_std(user_ptr, kernel_ptr, size);
25562306a36Sopenharmony_ci		t2 = sched_clock();
25662306a36Sopenharmony_ci		printk("copy_to_user: %d %llu %llu\n", size, t1 - t0, t2 - t1);
25762306a36Sopenharmony_ci	}
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci	for (size = PAGE_SIZE; size >= 4; size /= 2) {
26062306a36Sopenharmony_ci		t0 = sched_clock();
26162306a36Sopenharmony_ci		ret |= __clear_user_memset(user_ptr, size);
26262306a36Sopenharmony_ci		t1 = sched_clock();
26362306a36Sopenharmony_ci		ret |= __clear_user_std(user_ptr, size);
26462306a36Sopenharmony_ci		t2 = sched_clock();
26562306a36Sopenharmony_ci		printk("clear_user: %d %llu %llu\n", size, t1 - t0, t2 - t1);
26662306a36Sopenharmony_ci	}
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci	if (ret)
26962306a36Sopenharmony_ci		ret = -EFAULT;
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_ci	vunmap(user_ptr);
27262306a36Sopenharmony_cino_vmap:
27362306a36Sopenharmony_ci	put_page(dst_page);
27462306a36Sopenharmony_cino_dst:
27562306a36Sopenharmony_ci	put_page(src_page);
27662306a36Sopenharmony_cino_src:
27762306a36Sopenharmony_ci	return ret;
27862306a36Sopenharmony_ci}
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_cisubsys_initcall(test_size_treshold);
28162306a36Sopenharmony_ci
28262306a36Sopenharmony_ci#endif
283