blob: d145565b5f4b386f6e7f56557f6f44520af2a8fc [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/*
Jeff Dike8ca842c2007-10-16 01:27:08 -07002 * Copyright (C) 2002 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com)
Linus Torvalds1da177e2005-04-16 15:20:36 -07003 * Licensed under the GPL
4 */
5
Jeff Dike8192ab42008-02-04 22:30:53 -08006#include <linux/err.h>
7#include <linux/highmem.h>
8#include <linux/mm.h>
9#include <linux/sched.h>
10#include <asm/current.h>
11#include <asm/page.h>
12#include <asm/pgtable.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070013#include "kern_util.h"
Gennady Sharapov4fef0c12006-01-18 17:42:41 -080014#include "os.h"
Linus Torvalds1da177e2005-04-16 15:20:36 -070015
Jeff Dike9157f902008-02-04 22:30:52 -080016static void *um_virt_to_phys(struct task_struct *task, unsigned long addr,
17 pte_t *pte_out)
18{
19 pgd_t *pgd;
20 pud_t *pud;
21 pmd_t *pmd;
22 pte_t *pte;
23 pte_t ptent;
24
25 if (task->mm == NULL)
26 return ERR_PTR(-EINVAL);
27 pgd = pgd_offset(task->mm, addr);
28 if (!pgd_present(*pgd))
29 return ERR_PTR(-EINVAL);
30
31 pud = pud_offset(pgd, addr);
32 if (!pud_present(*pud))
33 return ERR_PTR(-EINVAL);
34
35 pmd = pmd_offset(pud, addr);
36 if (!pmd_present(*pmd))
37 return ERR_PTR(-EINVAL);
38
39 pte = pte_offset_kernel(pmd, addr);
40 ptent = *pte;
41 if (!pte_present(ptent))
42 return ERR_PTR(-EINVAL);
43
44 if (pte_out != NULL)
45 *pte_out = ptent;
46 return (void *) (pte_val(ptent) & PAGE_MASK) + (addr & ~PAGE_MASK);
47}
Linus Torvalds1da177e2005-04-16 15:20:36 -070048
49static unsigned long maybe_map(unsigned long virt, int is_write)
50{
51 pte_t pte;
52 int err;
53
54 void *phys = um_virt_to_phys(current, virt, &pte);
55 int dummy_code;
56
Jeff Dike8ca842c2007-10-16 01:27:08 -070057 if (IS_ERR(phys) || (is_write && !pte_write(pte))) {
Linus Torvalds1da177e2005-04-16 15:20:36 -070058 err = handle_page_fault(virt, 0, is_write, 1, &dummy_code);
Jeff Dike8ca842c2007-10-16 01:27:08 -070059 if (err)
60 return -1UL;
Linus Torvalds1da177e2005-04-16 15:20:36 -070061 phys = um_virt_to_phys(current, virt, NULL);
62 }
Jeff Dike8ca842c2007-10-16 01:27:08 -070063 if (IS_ERR(phys))
64 phys = (void *) -1;
Jeff Dike2d58cc92005-05-06 21:30:55 -070065
Jeff Dike8ca842c2007-10-16 01:27:08 -070066 return (unsigned long) phys;
Linus Torvalds1da177e2005-04-16 15:20:36 -070067}
68
Paolo 'Blaisorblade' Giarrusso47e52432006-07-01 04:36:19 -070069static int do_op_one_page(unsigned long addr, int len, int is_write,
Linus Torvalds1da177e2005-04-16 15:20:36 -070070 int (*op)(unsigned long addr, int len, void *arg), void *arg)
71{
72 struct page *page;
73 int n;
74
75 addr = maybe_map(addr, is_write);
Jeff Dike8ca842c2007-10-16 01:27:08 -070076 if (addr == -1UL)
77 return -1;
Linus Torvalds1da177e2005-04-16 15:20:36 -070078
79 page = phys_to_page(addr);
Jeff Dike8ca842c2007-10-16 01:27:08 -070080 addr = (unsigned long) kmap_atomic(page, KM_UML_USERCOPY) +
81 (addr & ~PAGE_MASK);
Paolo 'Blaisorblade' Giarrusso47e52432006-07-01 04:36:19 -070082
Linus Torvalds1da177e2005-04-16 15:20:36 -070083 n = (*op)(addr, len, arg);
Paolo 'Blaisorblade' Giarrusso47e52432006-07-01 04:36:19 -070084
85 kunmap_atomic(page, KM_UML_USERCOPY);
Linus Torvalds1da177e2005-04-16 15:20:36 -070086
Jeff Dike8ca842c2007-10-16 01:27:08 -070087 return n;
Linus Torvalds1da177e2005-04-16 15:20:36 -070088}
89
90static void do_buffer_op(void *jmpbuf, void *arg_ptr)
91{
92 va_list args;
93 unsigned long addr;
94 int len, is_write, size, remain, n;
95 int (*op)(unsigned long, int, void *);
96 void *arg;
97 int *res;
98
Paolo 'Blaisorblade' Giarrussoe9c52712005-05-01 08:58:54 -070099 va_copy(args, *(va_list *)arg_ptr);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700100 addr = va_arg(args, unsigned long);
101 len = va_arg(args, int);
102 is_write = va_arg(args, int);
103 op = va_arg(args, void *);
104 arg = va_arg(args, void *);
105 res = va_arg(args, int *);
106 va_end(args);
107 size = min(PAGE_ALIGN(addr) - addr, (unsigned long) len);
108 remain = len;
109
110 current->thread.fault_catcher = jmpbuf;
Paolo 'Blaisorblade' Giarrusso47e52432006-07-01 04:36:19 -0700111 n = do_op_one_page(addr, size, is_write, op, arg);
Jeff Dike8ca842c2007-10-16 01:27:08 -0700112 if (n != 0) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700113 *res = (n < 0 ? remain : 0);
114 goto out;
115 }
116
117 addr += size;
118 remain -= size;
Jeff Dike8ca842c2007-10-16 01:27:08 -0700119 if (remain == 0) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700120 *res = 0;
121 goto out;
122 }
123
Jeff Dike8ca842c2007-10-16 01:27:08 -0700124 while(addr < ((addr + remain) & PAGE_MASK)) {
Paolo 'Blaisorblade' Giarrusso47e52432006-07-01 04:36:19 -0700125 n = do_op_one_page(addr, PAGE_SIZE, is_write, op, arg);
Jeff Dike8ca842c2007-10-16 01:27:08 -0700126 if (n != 0) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700127 *res = (n < 0 ? remain : 0);
128 goto out;
129 }
130
131 addr += PAGE_SIZE;
132 remain -= PAGE_SIZE;
133 }
Jeff Dike8ca842c2007-10-16 01:27:08 -0700134 if (remain == 0) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700135 *res = 0;
136 goto out;
137 }
138
Paolo 'Blaisorblade' Giarrusso47e52432006-07-01 04:36:19 -0700139 n = do_op_one_page(addr, remain, is_write, op, arg);
Jeff Dike8ca842c2007-10-16 01:27:08 -0700140 if (n != 0)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700141 *res = (n < 0 ? remain : 0);
142 else *res = 0;
143 out:
144 current->thread.fault_catcher = NULL;
145}
146
147static int buffer_op(unsigned long addr, int len, int is_write,
148 int (*op)(unsigned long addr, int len, void *arg),
149 void *arg)
150{
151 int faulted, res;
152
153 faulted = setjmp_wrapper(do_buffer_op, addr, len, is_write, op, arg,
154 &res);
Jeff Dike8ca842c2007-10-16 01:27:08 -0700155 if (!faulted)
156 return res;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700157
Jeff Dike8ca842c2007-10-16 01:27:08 -0700158 return addr + len - (unsigned long) current->thread.fault_addr;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700159}
160
161static int copy_chunk_from_user(unsigned long from, int len, void *arg)
162{
163 unsigned long *to_ptr = arg, to = *to_ptr;
164
165 memcpy((void *) to, (void *) from, len);
166 *to_ptr += len;
Jeff Dike8ca842c2007-10-16 01:27:08 -0700167 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700168}
169
Jeff Dike6aa802c2007-10-16 01:26:56 -0700170int copy_from_user(void *to, const void __user *from, int n)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700171{
Jeff Dike8ca842c2007-10-16 01:27:08 -0700172 if (segment_eq(get_fs(), KERNEL_DS)) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700173 memcpy(to, (__force void*)from, n);
Jeff Dike8ca842c2007-10-16 01:27:08 -0700174 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700175 }
176
Jeff Dike8ca842c2007-10-16 01:27:08 -0700177 return access_ok(VERIFY_READ, from, n) ?
Linus Torvalds1da177e2005-04-16 15:20:36 -0700178 buffer_op((unsigned long) from, n, 0, copy_chunk_from_user, &to):
Jeff Dike8ca842c2007-10-16 01:27:08 -0700179 n;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700180}
181
182static int copy_chunk_to_user(unsigned long to, int len, void *arg)
183{
184 unsigned long *from_ptr = arg, from = *from_ptr;
185
186 memcpy((void *) to, (void *) from, len);
187 *from_ptr += len;
Jeff Dike8ca842c2007-10-16 01:27:08 -0700188 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700189}
190
Jeff Dike6aa802c2007-10-16 01:26:56 -0700191int copy_to_user(void __user *to, const void *from, int n)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700192{
Jeff Dike8ca842c2007-10-16 01:27:08 -0700193 if (segment_eq(get_fs(), KERNEL_DS)) {
194 memcpy((__force void *) to, from, n);
195 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700196 }
197
Jeff Dike8ca842c2007-10-16 01:27:08 -0700198 return access_ok(VERIFY_WRITE, to, n) ?
Linus Torvalds1da177e2005-04-16 15:20:36 -0700199 buffer_op((unsigned long) to, n, 1, copy_chunk_to_user, &from) :
Jeff Dike8ca842c2007-10-16 01:27:08 -0700200 n;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700201}
202
203static int strncpy_chunk_from_user(unsigned long from, int len, void *arg)
204{
205 char **to_ptr = arg, *to = *to_ptr;
206 int n;
207
208 strncpy(to, (void *) from, len);
209 n = strnlen(to, len);
210 *to_ptr += n;
211
Jeff Dike8ca842c2007-10-16 01:27:08 -0700212 if (n < len)
213 return 1;
214 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700215}
216
Jeff Dike6aa802c2007-10-16 01:26:56 -0700217int strncpy_from_user(char *dst, const char __user *src, int count)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700218{
219 int n;
220 char *ptr = dst;
221
Jeff Dike8ca842c2007-10-16 01:27:08 -0700222 if (segment_eq(get_fs(), KERNEL_DS)) {
223 strncpy(dst, (__force void *) src, count);
224 return strnlen(dst, count);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700225 }
226
Jeff Dike8ca842c2007-10-16 01:27:08 -0700227 if (!access_ok(VERIFY_READ, src, 1))
228 return -EFAULT;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700229
230 n = buffer_op((unsigned long) src, count, 0, strncpy_chunk_from_user,
231 &ptr);
Jeff Dike8ca842c2007-10-16 01:27:08 -0700232 if (n != 0)
233 return -EFAULT;
234 return strnlen(dst, count);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700235}
236
237static int clear_chunk(unsigned long addr, int len, void *unused)
238{
239 memset((void *) addr, 0, len);
Jeff Dike8ca842c2007-10-16 01:27:08 -0700240 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700241}
242
Jeff Dike6aa802c2007-10-16 01:26:56 -0700243int __clear_user(void __user *mem, int len)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700244{
Jeff Dike8ca842c2007-10-16 01:27:08 -0700245 return buffer_op((unsigned long) mem, len, 1, clear_chunk, NULL);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700246}
247
Jeff Dike6aa802c2007-10-16 01:26:56 -0700248int clear_user(void __user *mem, int len)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700249{
Jeff Dike8ca842c2007-10-16 01:27:08 -0700250 if (segment_eq(get_fs(), KERNEL_DS)) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700251 memset((__force void*)mem, 0, len);
Jeff Dike8ca842c2007-10-16 01:27:08 -0700252 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700253 }
254
Jeff Dike8ca842c2007-10-16 01:27:08 -0700255 return access_ok(VERIFY_WRITE, mem, len) ?
256 buffer_op((unsigned long) mem, len, 1, clear_chunk, NULL) : len;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700257}
258
259static int strnlen_chunk(unsigned long str, int len, void *arg)
260{
261 int *len_ptr = arg, n;
262
263 n = strnlen((void *) str, len);
264 *len_ptr += n;
265
Jeff Dike8ca842c2007-10-16 01:27:08 -0700266 if (n < len)
267 return 1;
268 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700269}
270
Jeff Dike6aa802c2007-10-16 01:26:56 -0700271int strnlen_user(const void __user *str, int len)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700272{
273 int count = 0, n;
274
Jeff Dike8ca842c2007-10-16 01:27:08 -0700275 if (segment_eq(get_fs(), KERNEL_DS))
276 return strnlen((__force char*)str, len) + 1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700277
278 n = buffer_op((unsigned long) str, len, 0, strnlen_chunk, &count);
Jeff Dike8ca842c2007-10-16 01:27:08 -0700279 if (n == 0)
280 return count + 1;
281 return -EFAULT;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700282}