diff options
Diffstat (limited to 'arch/xtensa/kernel/process.c')
| -rw-r--r-- | arch/xtensa/kernel/process.c | 128 |
1 files changed, 71 insertions, 57 deletions
diff --git a/arch/xtensa/kernel/process.c b/arch/xtensa/kernel/process.c index 1908f6642d31..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,18 +159,30 @@ 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. |
| @@ -185,43 +198,63 @@ int arch_dup_task_struct(struct task_struct *dst, struct task_struct *src) | |||
| 185 | * involved. Much simpler to just not copy those live frames across. | 198 | * involved. Much simpler to just not copy those live frames across. |
| 186 | */ | 199 | */ |
| 187 | 200 | ||
| 188 | int copy_thread(unsigned long clone_flags, unsigned long usp, | 201 | int copy_thread(unsigned long clone_flags, unsigned long usp_thread_fn, |
| 189 | unsigned long unused, | 202 | unsigned long thread_fn_arg, |
| 190 | struct task_struct * p, struct pt_regs * regs) | 203 | struct task_struct *p, struct pt_regs *unused) |
| 191 | { | 204 | { |
| 192 | struct pt_regs *childregs; | 205 | struct pt_regs *childregs = task_pt_regs(p); |
| 193 | unsigned long tos; | ||
| 194 | int user_mode = user_mode(regs); | ||
| 195 | 206 | ||
| 196 | #if (XTENSA_HAVE_COPROCESSORS || XTENSA_HAVE_IO_PORTS) | 207 | #if (XTENSA_HAVE_COPROCESSORS || XTENSA_HAVE_IO_PORTS) |
| 197 | struct thread_info *ti; | 208 | struct thread_info *ti; |
| 198 | #endif | 209 | #endif |
| 199 | 210 | ||
| 200 | /* Set up new TSS. */ | ||
| 201 | tos = (unsigned long)task_stack_page(p) + THREAD_SIZE; | ||
| 202 | if (user_mode) | ||
| 203 | childregs = (struct pt_regs*)(tos - PT_USER_SIZE); | ||
| 204 | else | ||
| 205 | childregs = (struct pt_regs*)tos - 1; | ||
| 206 | |||
| 207 | /* This does not copy all the regs. In a bout of brilliance or madness, | ||
| 208 | ARs beyond a0-a15 exist past the end of the struct. */ | ||
| 209 | *childregs = *regs; | ||
| 210 | |||
| 211 | /* Create a call4 dummy-frame: a0 = 0, a1 = childregs. */ | 211 | /* Create a call4 dummy-frame: a0 = 0, a1 = childregs. */ |
| 212 | *((int*)childregs - 3) = (unsigned long)childregs; | 212 | *((int*)childregs - 3) = (unsigned long)childregs; |
| 213 | *((int*)childregs - 4) = 0; | 213 | *((int*)childregs - 4) = 0; |
| 214 | 214 | ||
| 215 | childregs->areg[2] = 0; | ||
| 216 | p->set_child_tid = p->clear_child_tid = NULL; | ||
| 217 | p->thread.ra = MAKE_RA_FOR_CALL((unsigned long)ret_from_fork, 0x1); | ||
| 218 | p->thread.sp = (unsigned long)childregs; | 215 | p->thread.sp = (unsigned long)childregs; |
| 219 | 216 | ||
| 220 | 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]; | ||
| 221 | 221 | ||
| 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; | ||
| 222 | childregs->areg[1] = usp; | 230 | childregs->areg[1] = usp; |
| 231 | childregs->areg[2] = 0; | ||
| 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 | |||
| 223 | if (clone_flags & CLONE_VM) { | 246 | if (clone_flags & CLONE_VM) { |
| 224 | childregs->wmask = 1; /* can't share live windows */ | 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; | ||
| 225 | } else { | 258 | } else { |
| 226 | int len = childregs->wmask & ~0xf; | 259 | int len = childregs->wmask & ~0xf; |
| 227 | memcpy(&childregs->areg[XCHAL_NUM_AREGS - len/4], | 260 | memcpy(&childregs->areg[XCHAL_NUM_AREGS - len/4], |
| @@ -230,11 +263,19 @@ int copy_thread(unsigned long clone_flags, unsigned long usp, | |||
| 230 | // FIXME: we need to set THREADPTR in thread_info... | 263 | // FIXME: we need to set THREADPTR in thread_info... |
| 231 | if (clone_flags & CLONE_SETTLS) | 264 | if (clone_flags & CLONE_SETTLS) |
| 232 | childregs->areg[2] = childregs->areg[6]; | 265 | childregs->areg[2] = childregs->areg[6]; |
| 233 | |||
| 234 | } else { | 266 | } else { |
| 235 | /* In kernel space, we start a new thread with a new stack. */ | 267 | p->thread.ra = MAKE_RA_FOR_CALL( |
| 236 | childregs->wmask = 1; | 268 | (unsigned long)ret_from_kernel_thread, 1); |
| 237 | childregs->areg[1] = tos; | 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 | */ | ||
| 238 | } | 279 | } |
| 239 | 280 | ||
| 240 | #if (XTENSA_HAVE_COPROCESSORS || XTENSA_HAVE_IO_PORTS) | 281 | #if (XTENSA_HAVE_COPROCESSORS || XTENSA_HAVE_IO_PORTS) |
| @@ -330,32 +371,5 @@ long xtensa_clone(unsigned long clone_flags, unsigned long newsp, | |||
| 330 | void __user *child_tid, long a5, | 371 | void __user *child_tid, long a5, |
| 331 | struct pt_regs *regs) | 372 | struct pt_regs *regs) |
| 332 | { | 373 | { |
| 333 | if (!newsp) | ||
| 334 | newsp = regs->areg[1]; | ||
| 335 | 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); |
| 336 | } | 375 | } |
| 337 | |||
| 338 | /* | ||
| 339 | * xtensa_execve() executes a new program. | ||
| 340 | */ | ||
| 341 | |||
| 342 | asmlinkage | ||
| 343 | long xtensa_execve(const char __user *name, | ||
| 344 | const char __user *const __user *argv, | ||
| 345 | const char __user *const __user *envp, | ||
| 346 | long a3, long a4, long a5, | ||
| 347 | struct pt_regs *regs) | ||
| 348 | { | ||
| 349 | long error; | ||
| 350 | struct filename *filename; | ||
| 351 | |||
| 352 | filename = getname(name); | ||
| 353 | error = PTR_ERR(filename); | ||
| 354 | if (IS_ERR(filename)) | ||
| 355 | goto out; | ||
| 356 | error = do_execve(filename->name, argv, envp, regs); | ||
| 357 | putname(filename); | ||
| 358 | out: | ||
| 359 | return error; | ||
| 360 | } | ||
| 361 | |||
