diff options
Diffstat (limited to 'arch/xtensa/kernel/process.c')
-rw-r--r-- | arch/xtensa/kernel/process.c | 147 |
1 files changed, 90 insertions, 57 deletions
diff --git a/arch/xtensa/kernel/process.c b/arch/xtensa/kernel/process.c index bc020825cce5..09ae7bfab9a7 100644 --- a/arch/xtensa/kernel/process.c +++ b/arch/xtensa/kernel/process.c | |||
@@ -45,6 +45,7 @@ | |||
45 | #include <asm/regs.h> | 45 | #include <asm/regs.h> |
46 | 46 | ||
47 | extern void ret_from_fork(void); | 47 | extern void ret_from_fork(void); |
48 | extern void ret_from_kernel_thread(void); | ||
48 | 49 | ||
49 | struct task_struct *current_set[NR_CPUS] = {&init_task, }; | 50 | struct task_struct *current_set[NR_CPUS] = {&init_task, }; |
50 | 51 | ||
@@ -158,64 +159,123 @@ int arch_dup_task_struct(struct task_struct *dst, struct task_struct *src) | |||
158 | /* | 159 | /* |
159 | * Copy thread. | 160 | * Copy thread. |
160 | * | 161 | * |
162 | * There are two modes in which this function is called: | ||
163 | * 1) Userspace thread creation, | ||
164 | * regs != NULL, usp_thread_fn is userspace stack pointer. | ||
165 | * It is expected to copy parent regs (in case CLONE_VM is not set | ||
166 | * in the clone_flags) and set up passed usp in the childregs. | ||
167 | * 2) Kernel thread creation, | ||
168 | * regs == NULL, usp_thread_fn is the function to run in the new thread | ||
169 | * and thread_fn_arg is its parameter. | ||
170 | * childregs are not used for the kernel threads. | ||
171 | * | ||
161 | * The stack layout for the new thread looks like this: | 172 | * The stack layout for the new thread looks like this: |
162 | * | 173 | * |
163 | * +------------------------+ <- sp in childregs (= tos) | 174 | * +------------------------+ |
164 | * | childregs | | 175 | * | childregs | |
165 | * +------------------------+ <- thread.sp = sp in dummy-frame | 176 | * +------------------------+ <- thread.sp = sp in dummy-frame |
166 | * | dummy-frame | (saved in dummy-frame spill-area) | 177 | * | dummy-frame | (saved in dummy-frame spill-area) |
167 | * +------------------------+ | 178 | * +------------------------+ |
168 | * | 179 | * |
169 | * We create a dummy frame to return to ret_from_fork: | 180 | * We create a dummy frame to return to either ret_from_fork or |
170 | * a0 points to ret_from_fork (simulating a call4) | 181 | * ret_from_kernel_thread: |
182 | * a0 points to ret_from_fork/ret_from_kernel_thread (simulating a call4) | ||
171 | * sp points to itself (thread.sp) | 183 | * sp points to itself (thread.sp) |
172 | * a2, a3 are unused. | 184 | * a2, a3 are unused for userspace threads, |
185 | * a2 points to thread_fn, a3 holds thread_fn arg for kernel threads. | ||
173 | * | 186 | * |
174 | * Note: This is a pristine frame, so we don't need any spill region on top of | 187 | * Note: This is a pristine frame, so we don't need any spill region on top of |
175 | * childregs. | 188 | * childregs. |
189 | * | ||
190 | * The fun part: if we're keeping the same VM (i.e. cloning a thread, | ||
191 | * not an entire process), we're normally given a new usp, and we CANNOT share | ||
192 | * any live address register windows. If we just copy those live frames over, | ||
193 | * the two threads (parent and child) will overflow the same frames onto the | ||
194 | * parent stack at different times, likely corrupting the parent stack (esp. | ||
195 | * if the parent returns from functions that called clone() and calls new | ||
196 | * ones, before the child overflows its now old copies of its parent windows). | ||
197 | * One solution is to spill windows to the parent stack, but that's fairly | ||
198 | * involved. Much simpler to just not copy those live frames across. | ||
176 | */ | 199 | */ |
177 | 200 | ||
178 | int copy_thread(unsigned long clone_flags, unsigned long usp, | 201 | int copy_thread(unsigned long clone_flags, unsigned long usp_thread_fn, |
179 | unsigned long unused, | 202 | unsigned long thread_fn_arg, |
180 | struct task_struct * p, struct pt_regs * regs) | 203 | struct task_struct *p, struct pt_regs *unused) |
181 | { | 204 | { |
182 | struct pt_regs *childregs; | 205 | struct pt_regs *childregs = task_pt_regs(p); |
183 | struct thread_info *ti; | ||
184 | unsigned long tos; | ||
185 | int user_mode = user_mode(regs); | ||
186 | |||
187 | /* Set up new TSS. */ | ||
188 | tos = (unsigned long)task_stack_page(p) + THREAD_SIZE; | ||
189 | if (user_mode) | ||
190 | childregs = (struct pt_regs*)(tos - PT_USER_SIZE); | ||
191 | else | ||
192 | childregs = (struct pt_regs*)tos - 1; | ||
193 | 206 | ||
194 | *childregs = *regs; | 207 | #if (XTENSA_HAVE_COPROCESSORS || XTENSA_HAVE_IO_PORTS) |
208 | struct thread_info *ti; | ||
209 | #endif | ||
195 | 210 | ||
196 | /* Create a call4 dummy-frame: a0 = 0, a1 = childregs. */ | 211 | /* Create a call4 dummy-frame: a0 = 0, a1 = childregs. */ |
197 | *((int*)childregs - 3) = (unsigned long)childregs; | 212 | *((int*)childregs - 3) = (unsigned long)childregs; |
198 | *((int*)childregs - 4) = 0; | 213 | *((int*)childregs - 4) = 0; |
199 | 214 | ||
200 | childregs->areg[1] = tos; | ||
201 | childregs->areg[2] = 0; | ||
202 | p->set_child_tid = p->clear_child_tid = NULL; | ||
203 | p->thread.ra = MAKE_RA_FOR_CALL((unsigned long)ret_from_fork, 0x1); | ||
204 | p->thread.sp = (unsigned long)childregs; | 215 | p->thread.sp = (unsigned long)childregs; |
205 | 216 | ||
206 | if (user_mode(regs)) { | 217 | if (!(p->flags & PF_KTHREAD)) { |
218 | struct pt_regs *regs = current_pt_regs(); | ||
219 | unsigned long usp = usp_thread_fn ? | ||
220 | usp_thread_fn : regs->areg[1]; | ||
207 | 221 | ||
208 | int len = childregs->wmask & ~0xf; | 222 | p->thread.ra = MAKE_RA_FOR_CALL( |
223 | (unsigned long)ret_from_fork, 0x1); | ||
224 | |||
225 | /* This does not copy all the regs. | ||
226 | * In a bout of brilliance or madness, | ||
227 | * ARs beyond a0-a15 exist past the end of the struct. | ||
228 | */ | ||
229 | *childregs = *regs; | ||
209 | childregs->areg[1] = usp; | 230 | childregs->areg[1] = usp; |
210 | memcpy(&childregs->areg[XCHAL_NUM_AREGS - len/4], | 231 | childregs->areg[2] = 0; |
211 | ®s->areg[XCHAL_NUM_AREGS - len/4], len); | 232 | |
233 | /* When sharing memory with the parent thread, the child | ||
234 | usually starts on a pristine stack, so we have to reset | ||
235 | windowbase, windowstart and wmask. | ||
236 | (Note that such a new thread is required to always create | ||
237 | an initial call4 frame) | ||
238 | The exception is vfork, where the new thread continues to | ||
239 | run on the parent's stack until it calls execve. This could | ||
240 | be a call8 or call12, which requires a legal stack frame | ||
241 | of the previous caller for the overflow handlers to work. | ||
242 | (Note that it's always legal to overflow live registers). | ||
243 | In this case, ensure to spill at least the stack pointer | ||
244 | of that frame. */ | ||
245 | |||
246 | if (clone_flags & CLONE_VM) { | ||
247 | /* check that caller window is live and same stack */ | ||
248 | int len = childregs->wmask & ~0xf; | ||
249 | if (regs->areg[1] == usp && len != 0) { | ||
250 | int callinc = (regs->areg[0] >> 30) & 3; | ||
251 | int caller_ars = XCHAL_NUM_AREGS - callinc * 4; | ||
252 | put_user(regs->areg[caller_ars+1], | ||
253 | (unsigned __user*)(usp - 12)); | ||
254 | } | ||
255 | childregs->wmask = 1; | ||
256 | childregs->windowstart = 1; | ||
257 | childregs->windowbase = 0; | ||
258 | } else { | ||
259 | int len = childregs->wmask & ~0xf; | ||
260 | memcpy(&childregs->areg[XCHAL_NUM_AREGS - len/4], | ||
261 | ®s->areg[XCHAL_NUM_AREGS - len/4], len); | ||
262 | } | ||
212 | // FIXME: we need to set THREADPTR in thread_info... | 263 | // FIXME: we need to set THREADPTR in thread_info... |
213 | if (clone_flags & CLONE_SETTLS) | 264 | if (clone_flags & CLONE_SETTLS) |
214 | childregs->areg[2] = childregs->areg[6]; | 265 | childregs->areg[2] = childregs->areg[6]; |
215 | |||
216 | } else { | 266 | } else { |
217 | /* In kernel space, we start a new thread with a new stack. */ | 267 | p->thread.ra = MAKE_RA_FOR_CALL( |
218 | childregs->wmask = 1; | 268 | (unsigned long)ret_from_kernel_thread, 1); |
269 | |||
270 | /* pass parameters to ret_from_kernel_thread: | ||
271 | * a2 = thread_fn, a3 = thread_fn arg | ||
272 | */ | ||
273 | *((int *)childregs - 1) = thread_fn_arg; | ||
274 | *((int *)childregs - 2) = usp_thread_fn; | ||
275 | |||
276 | /* Childregs are only used when we're going to userspace | ||
277 | * in which case start_thread will set them up. | ||
278 | */ | ||
219 | } | 279 | } |
220 | 280 | ||
221 | #if (XTENSA_HAVE_COPROCESSORS || XTENSA_HAVE_IO_PORTS) | 281 | #if (XTENSA_HAVE_COPROCESSORS || XTENSA_HAVE_IO_PORTS) |
@@ -311,32 +371,5 @@ long xtensa_clone(unsigned long clone_flags, unsigned long newsp, | |||
311 | void __user *child_tid, long a5, | 371 | void __user *child_tid, long a5, |
312 | struct pt_regs *regs) | 372 | struct pt_regs *regs) |
313 | { | 373 | { |
314 | if (!newsp) | ||
315 | newsp = regs->areg[1]; | ||
316 | return do_fork(clone_flags, newsp, regs, 0, parent_tid, child_tid); | 374 | return do_fork(clone_flags, newsp, regs, 0, parent_tid, child_tid); |
317 | } | 375 | } |
318 | |||
319 | /* | ||
320 | * xtensa_execve() executes a new program. | ||
321 | */ | ||
322 | |||
323 | asmlinkage | ||
324 | long xtensa_execve(const char __user *name, | ||
325 | const char __user *const __user *argv, | ||
326 | const char __user *const __user *envp, | ||
327 | long a3, long a4, long a5, | ||
328 | struct pt_regs *regs) | ||
329 | { | ||
330 | long error; | ||
331 | struct filename *filename; | ||
332 | |||
333 | filename = getname(name); | ||
334 | error = PTR_ERR(filename); | ||
335 | if (IS_ERR(filename)) | ||
336 | goto out; | ||
337 | error = do_execve(filename->name, argv, envp, regs); | ||
338 | putname(filename); | ||
339 | out: | ||
340 | return error; | ||
341 | } | ||
342 | |||