uaccess_pt.c 9.8 KB
Newer Older
1 2 3
/*
 *  arch/s390/lib/uaccess_pt.c
 *
G
Gerald Schaefer 已提交
4 5
 *  User access functions based on page table walks for enhanced
 *  system layout without hardware support.
6 7 8 9 10 11
 *
 *    Copyright IBM Corp. 2006
 *    Author(s): Gerald Schaefer (gerald.schaefer@de.ibm.com)
 */

#include <linux/errno.h>
12
#include <linux/hardirq.h>
13
#include <linux/mm.h>
14
#include <asm/uaccess.h>
15
#include <asm/futex.h>
16
#include "uaccess.h"
17

18 19 20
static inline pte_t *follow_table(struct mm_struct *mm, unsigned long addr)
{
	pgd_t *pgd;
M
Martin Schwidefsky 已提交
21
	pud_t *pud;
22 23 24 25
	pmd_t *pmd;

	pgd = pgd_offset(mm, addr);
	if (pgd_none(*pgd) || unlikely(pgd_bad(*pgd)))
26
		return (pte_t *) 0x3a;
27

M
Martin Schwidefsky 已提交
28 29
	pud = pud_offset(pgd, addr);
	if (pud_none(*pud) || unlikely(pud_bad(*pud)))
30
		return (pte_t *) 0x3b;
M
Martin Schwidefsky 已提交
31 32

	pmd = pmd_offset(pud, addr);
33
	if (pmd_none(*pmd) || unlikely(pmd_bad(*pmd)))
34
		return (pte_t *) 0x10;
35 36 37 38

	return pte_offset_map(pmd, addr);
}

39 40
static __always_inline size_t __user_copy_pt(unsigned long uaddr, void *kptr,
					     size_t n, int write_user)
41 42 43 44 45 46 47 48 49 50
{
	struct mm_struct *mm = current->mm;
	unsigned long offset, pfn, done, size;
	pte_t *pte;
	void *from, *to;

	done = 0;
retry:
	spin_lock(&mm->page_table_lock);
	do {
51
		pte = follow_table(mm, uaddr);
52 53 54 55
		if ((unsigned long) pte < 0x1000)
			goto fault;
		if (!pte_present(*pte)) {
			pte = (pte_t *) 0x11;
56
			goto fault;
57 58 59 60
		} else if (write_user && !pte_write(*pte)) {
			pte = (pte_t *) 0x04;
			goto fault;
		}
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79

		pfn = pte_pfn(*pte);
		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);
	spin_unlock(&mm->page_table_lock);
	return n - done;
fault:
	spin_unlock(&mm->page_table_lock);
80
	if (__handle_fault(uaddr, (unsigned long) pte, write_user))
81 82 83 84
		return n - done;
	goto retry;
}

G
Gerald Schaefer 已提交
85 86 87 88
/*
 * Do DAT for user address by page table walk, return kernel address.
 * This function needs to be called with current->mm->page_table_lock held.
 */
89
static __always_inline unsigned long __dat_user_addr(unsigned long uaddr)
G
Gerald Schaefer 已提交
90 91
{
	struct mm_struct *mm = current->mm;
92
	unsigned long pfn;
G
Gerald Schaefer 已提交
93 94 95 96
	pte_t *pte;
	int rc;

retry:
97
	pte = follow_table(mm, uaddr);
98 99 100 101
	if ((unsigned long) pte < 0x1000)
		goto fault;
	if (!pte_present(*pte)) {
		pte = (pte_t *) 0x11;
G
Gerald Schaefer 已提交
102
		goto fault;
103
	}
G
Gerald Schaefer 已提交
104 105

	pfn = pte_pfn(*pte);
106
	return (pfn << PAGE_SHIFT) + (uaddr & (PAGE_SIZE - 1));
G
Gerald Schaefer 已提交
107 108
fault:
	spin_unlock(&mm->page_table_lock);
109
	rc = __handle_fault(uaddr, (unsigned long) pte, 0);
G
Gerald Schaefer 已提交
110
	spin_lock(&mm->page_table_lock);
111 112 113
	if (!rc)
		goto retry;
	return 0;
G
Gerald Schaefer 已提交
114 115
}

116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
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);
}
G
Gerald Schaefer 已提交
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176

static size_t clear_user_pt(size_t n, void __user *to)
{
	long done, size, ret;

	if (segment_eq(get_fs(), KERNEL_DS)) {
		memset((void __kernel __force *) to, 0, n);
		return 0;
	}
	done = 0;
	do {
		if (n - done > PAGE_SIZE)
			size = PAGE_SIZE;
		else
			size = n - done;
		ret = __user_copy_pt((unsigned long) to + done,
				      &empty_zero_page, size, 1);
		done += size;
		if (ret)
			return ret + n - done;
	} while (done < n);
	return 0;
}

static size_t strnlen_user_pt(size_t count, const char __user *src)
{
	char *addr;
	unsigned long uaddr = (unsigned long) src;
	struct mm_struct *mm = current->mm;
	unsigned long offset, pfn, done, len;
	pte_t *pte;
	size_t len_str;

	if (segment_eq(get_fs(), KERNEL_DS))
		return strnlen((const char __kernel __force *) src, count) + 1;
	done = 0;
retry:
	spin_lock(&mm->page_table_lock);
	do {
177
		pte = follow_table(mm, uaddr);
178
		if ((unsigned long) pte < 0x1000)
G
Gerald Schaefer 已提交
179
			goto fault;
180 181 182 183
		if (!pte_present(*pte)) {
			pte = (pte_t *) 0x11;
			goto fault;
		}
G
Gerald Schaefer 已提交
184 185 186 187 188 189 190 191 192 193 194 195 196

		pfn = pte_pfn(*pte);
		offset = uaddr & (PAGE_SIZE-1);
		addr = (char *)(pfn << PAGE_SHIFT) + offset;
		len = min(count - done, PAGE_SIZE - offset);
		len_str = strnlen(addr, len);
		done += len_str;
		uaddr += len_str;
	} while ((len_str == len) && (done < count));
	spin_unlock(&mm->page_table_lock);
	return done + 1;
fault:
	spin_unlock(&mm->page_table_lock);
197
	if (__handle_fault(uaddr, (unsigned long) pte, 0))
G
Gerald Schaefer 已提交
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
		return 0;
	goto retry;
}

static size_t strncpy_from_user_pt(size_t count, const char __user *src,
				   char *dst)
{
	size_t n = strnlen_user_pt(count, src);

	if (!n)
		return -EFAULT;
	if (n > count)
		n = count;
	if (segment_eq(get_fs(), KERNEL_DS)) {
		memcpy(dst, (const char __kernel __force *) src, n);
		if (dst[n-1] == '\0')
			return n-1;
		else
			return n;
	}
	if (__user_copy_pt((unsigned long) src, dst, n, 0))
		return -EFAULT;
	if (dst[n-1] == '\0')
		return n-1;
	else
		return n;
}

static size_t copy_in_user_pt(size_t n, void __user *to,
			      const void __user *from)
{
	struct mm_struct *mm = current->mm;
	unsigned long offset_from, offset_to, offset_max, pfn_from, pfn_to,
231
		      uaddr, done, size, error_code;
G
Gerald Schaefer 已提交
232 233 234 235 236
	unsigned long uaddr_from = (unsigned long) from;
	unsigned long uaddr_to = (unsigned long) to;
	pte_t *pte_from, *pte_to;
	int write_user;

237 238 239 240
	if (segment_eq(get_fs(), KERNEL_DS)) {
		memcpy((void __force *) to, (void __force *) from, n);
		return 0;
	}
G
Gerald Schaefer 已提交
241 242 243 244
	done = 0;
retry:
	spin_lock(&mm->page_table_lock);
	do {
245 246
		write_user = 0;
		uaddr = uaddr_from;
247
		pte_from = follow_table(mm, uaddr_from);
248 249 250 251 252
		error_code = (unsigned long) pte_from;
		if (error_code < 0x1000)
			goto fault;
		if (!pte_present(*pte_from)) {
			error_code = 0x11;
G
Gerald Schaefer 已提交
253 254
			goto fault;
		}
255

256 257
		write_user = 1;
		uaddr = uaddr_to;
258
		pte_to = follow_table(mm, uaddr_to);
259 260 261 262 263 264 265 266
		error_code = (unsigned long) pte_to;
		if (error_code < 0x1000)
			goto fault;
		if (!pte_present(*pte_to)) {
			error_code = 0x11;
			goto fault;
		} else if (!pte_write(*pte_to)) {
			error_code = 0x04;
G
Gerald Schaefer 已提交
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286
			goto fault;
		}

		pfn_from = pte_pfn(*pte_from);
		pfn_to = pte_pfn(*pte_to);
		offset_from = uaddr_from & (PAGE_SIZE-1);
		offset_to = uaddr_from & (PAGE_SIZE-1);
		offset_max = max(offset_from, offset_to);
		size = min(n - done, PAGE_SIZE - offset_max);

		memcpy((void *)(pfn_to << PAGE_SHIFT) + offset_to,
		       (void *)(pfn_from << PAGE_SHIFT) + offset_from, size);
		done += size;
		uaddr_from += size;
		uaddr_to += size;
	} while (done < n);
	spin_unlock(&mm->page_table_lock);
	return n - done;
fault:
	spin_unlock(&mm->page_table_lock);
287
	if (__handle_fault(uaddr, error_code, write_user))
G
Gerald Schaefer 已提交
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304
		return n - done;
	goto retry;
}

#define __futex_atomic_op(insn, ret, oldval, newval, uaddr, oparg)	\
	asm volatile("0: l   %1,0(%6)\n"				\
		     "1: " insn						\
		     "2: cs  %1,%2,0(%6)\n"				\
		     "3: jl  1b\n"					\
		     "   lhi %0,0\n"					\
		     "4:\n"						\
		     EX_TABLE(0b,4b) EX_TABLE(2b,4b) EX_TABLE(3b,4b)	\
		     : "=d" (ret), "=&d" (oldval), "=&d" (newval),	\
		       "=m" (*uaddr)					\
		     : "0" (-EFAULT), "d" (oparg), "a" (uaddr),		\
		       "m" (*uaddr) : "cc" );

305
static int __futex_atomic_op_pt(int op, u32 __user *uaddr, int oparg, int *old)
G
Gerald Schaefer 已提交
306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332
{
	int oldval = 0, newval, ret;

	switch (op) {
	case FUTEX_OP_SET:
		__futex_atomic_op("lr %2,%5\n",
				  ret, oldval, newval, uaddr, oparg);
		break;
	case FUTEX_OP_ADD:
		__futex_atomic_op("lr %2,%1\nar %2,%5\n",
				  ret, oldval, newval, uaddr, oparg);
		break;
	case FUTEX_OP_OR:
		__futex_atomic_op("lr %2,%1\nor %2,%5\n",
				  ret, oldval, newval, uaddr, oparg);
		break;
	case FUTEX_OP_ANDN:
		__futex_atomic_op("lr %2,%1\nnr %2,%5\n",
				  ret, oldval, newval, uaddr, oparg);
		break;
	case FUTEX_OP_XOR:
		__futex_atomic_op("lr %2,%1\nxr %2,%5\n",
				  ret, oldval, newval, uaddr, oparg);
		break;
	default:
		ret = -ENOSYS;
	}
333 334
	if (ret == 0)
		*old = oldval;
G
Gerald Schaefer 已提交
335 336 337
	return ret;
}

338
int futex_atomic_op_pt(int op, u32 __user *uaddr, int oparg, int *old)
G
Gerald Schaefer 已提交
339 340 341
{
	int ret;

342 343
	if (segment_eq(get_fs(), KERNEL_DS))
		return __futex_atomic_op_pt(op, uaddr, oparg, old);
G
Gerald Schaefer 已提交
344 345 346 347 348 349 350 351
	spin_lock(&current->mm->page_table_lock);
	uaddr = (int __user *) __dat_user_addr((unsigned long) uaddr);
	if (!uaddr) {
		spin_unlock(&current->mm->page_table_lock);
		return -EFAULT;
	}
	get_page(virt_to_page(uaddr));
	spin_unlock(&current->mm->page_table_lock);
352 353 354 355 356
	ret = __futex_atomic_op_pt(op, uaddr, oparg, old);
	put_page(virt_to_page(uaddr));
	return ret;
}

357 358
static int __futex_atomic_cmpxchg_pt(u32 *uval, u32 __user *uaddr,
				     u32 oldval, u32 newval)
359 360 361 362
{
	int ret;

	asm volatile("0: cs   %1,%4,0(%5)\n"
363
		     "1: la   %0,0\n"
364 365
		     "2:\n"
		     EX_TABLE(0b,2b) EX_TABLE(1b,2b)
G
Gerald Schaefer 已提交
366 367 368
		     : "=d" (ret), "+d" (oldval), "=m" (*uaddr)
		     : "0" (-EFAULT), "d" (newval), "a" (uaddr), "m" (*uaddr)
		     : "cc", "memory" );
369
	*uval = oldval;
370 371 372
	return ret;
}

373 374
int futex_atomic_cmpxchg_pt(u32 *uval, u32 __user *uaddr,
			    u32 oldval, u32 newval)
375 376 377 378
{
	int ret;

	if (segment_eq(get_fs(), KERNEL_DS))
379
		return __futex_atomic_cmpxchg_pt(uval, uaddr, oldval, newval);
380 381 382 383 384 385 386 387
	spin_lock(&current->mm->page_table_lock);
	uaddr = (int __user *) __dat_user_addr((unsigned long) uaddr);
	if (!uaddr) {
		spin_unlock(&current->mm->page_table_lock);
		return -EFAULT;
	}
	get_page(virt_to_page(uaddr));
	spin_unlock(&current->mm->page_table_lock);
388
	ret = __futex_atomic_cmpxchg_pt(uval, uaddr, oldval, newval);
G
Gerald Schaefer 已提交
389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404
	put_page(virt_to_page(uaddr));
	return ret;
}

struct uaccess_ops uaccess_pt = {
	.copy_from_user		= copy_from_user_pt,
	.copy_from_user_small	= copy_from_user_pt,
	.copy_to_user		= copy_to_user_pt,
	.copy_to_user_small	= copy_to_user_pt,
	.copy_in_user		= copy_in_user_pt,
	.clear_user		= clear_user_pt,
	.strnlen_user		= strnlen_user_pt,
	.strncpy_from_user	= strncpy_from_user_pt,
	.futex_atomic_op	= futex_atomic_op_pt,
	.futex_atomic_cmpxchg	= futex_atomic_cmpxchg_pt,
};