NetBSD/lib/libpthread/arch/arm/pthread_switch.S

399 lines
11 KiB
ArmAsm

/* $NetBSD: pthread_switch.S,v 1.8 2004/08/21 11:31:44 rearnsha Exp $ */
/*
* Copyright (c) 2001 Wasabi Systems, Inc.
* All rights reserved.
*
* Written by Jason R. Thorpe for Wasabi Systems, Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed for the NetBSD Project by
* Wasabi Systems, Inc.
* 4. The name of Wasabi Systems, Inc. may not be used to endorse
* or promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL WASABI SYSTEMS, INC
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <machine/asm.h>
#include "assym.h"
#define NOTREACHED /* nothing, for now */
/*
* Define:
* void pthread__switch(pthread_t self, pthread_t next)
* void pthread__upcall_switch(pthread_t self, pthread_t next)
* void pthread__locked_switch(pthread_t self, pthread_t next,
* pt_spin_t *lock)
*/
/* *** WARNING ***
* STACK_SWITCH is more subtle than it looks. Please go read the extended
* comment in the i386 pthread_switch.S file.
*/
#define STACK_SWITCH(pt, tmp, z) \
ldr tmp, [pt, #(PT_TRAPUC)] ; \
teq tmp, #0x00000000 ; \
bne 1f ; \
ldr tmp, [pt, #(PT_UC)] ; \
1: sub sp, tmp, #(STACKSPACE) ; \
mov z, #00000000 ; \
str z, [pt, #(PT_TRAPUC)]
/*
* void
* pthread__switch(pthread_t self (r0), pthread_t next (r1))
*
* Plain switch that doesn't do any special checking or handle spin-
* preemption. It isn't used much by normal code, actually; its main
* purpose is to be a basic switch engine when the MI code is already
* dealing with spin-preemption or other gunk.
*
* What we do here is allocate the ucontext for the 'self' thread on its
* stack, saving the pointer in self's pthread structure, then swapping
* context to 'next', effectively deallocating next's context on the way
* out.
*/
ENTRY(pthread__switch)
sub sp, sp, #(RND_CTXSIZE) /* push space for ucontext_t */
mov r3, sp /* r3 = &context */
/* Save r0-r2, r4-r5, lr */
stmfd sp!, {r0-r2, r4-r5, lr}
/* Now r5 will point to &context for the duration. */
mov r5, r3
/*
* Stack is:
* context (RND_CTXSIZE bytes)
* saved lr ( 4 bytes)
* saved r5 ( 4 bytes)
* saved r4 ( 4 bytes)
* saved r2 ( 4 bytes)
* saved r1 ( 4 bytes)
* saved r0 ( 4 bytes)
* Note, no space needed
* for callees.
*/
/* Get the current context. */
mov r0, r5 /* r0 = &context */
bl PIC_SYM(_C_LABEL(_getcontext_u), PLT)
/* Edit the context to make it continue below, rather than here */
add r0, pc, #pthread__switch_return_point - . - 8
str r0, [r5, #(_REG_PC)]
/* Get back r0-r2 */
ldmfd sp, {r0-r2}
str r5, [r0, #(PT_UC)] /* self->pt_uc = &context */
/* Side-effect: r4 = next->pt_uc */
STACK_SWITCH(r1, r4, r3)
/* NOTE: we can no longer recover registers stored on the stack */
mov r0, r4 /* r0 = next->pt_uc */
b PIC_SYM(_C_LABEL(_setcontext_u), PLT)
NOTREACHED
ENTRY_NP(pthread__switch_return_point)
/*
* Stack is:
* context (RND_CTXSIZE bytes)
* saved lr ( 4 bytes)
* saved r5 ( 4 bytes)
* saved r4 ( 4 bytes)
* saved r2 ( 4 bytes)
* saved r1 ( 4 bytes)
* saved r0 ( 4 bytes)
* Note, no space needed
* for callees.
*/
add sp, sp, #12 /* skip over r0-r2 */
ldmfd sp!, {r4-r5, lr} /* restore r4-r5, lr */
add sp, sp, #(RND_CTXSIZE) /* pop ucontext_t */
RET
/*
* Helper routine; r0 = from, r1 = to, r2 = decr-spinlocks (boolean)
*/
pthread__switch_away:
/* Side-effect: r3 = r1->pt_uc */
STACK_SWITCH(r1, r3, r14)
/*
* If we're invoked from the switch-to-next provisions of
* pthread__locked_switch() or pthread__upcall_switch(),
* there may be a fake spinlock-count set. If so, they will
* set r2 to let us know, and we decrement it now that we're
* no longer using the old stack.
*/
teq r2, #0x00000000
beq 1f
ldr r2, [r0, #(PT_SPINLOCKS)]
sub r2, r2, #1
str r2, [r0, #(PT_SPINLOCKS)]
1: mov r0, r3
b PIC_SYM(_C_LABEL(_setcontext_u), PLT)
NOTREACHED
/*
* void
* pthread__upcall_switch(pthread_t self (r0), pthread_t next (r1))
*
* Quit an upcall, recycle it, and jump to the selected thread.
*
* Note: This code never returns, so we can use the callee-saved
* registers to our heart's content (and they will be preserved
* by functions that we call).
*/
ENTRY(pthread__upcall_switch)
/* Create a "fake" lock count so that this code will be continued */
ldr r3, [r1, #(PT_SPINLOCKS)]
add r3, r3, #1
str r3, [r1, #(PT_SPINLOCKS)]
/* Side-effect: r7 = next->pt_uc */
STACK_SWITCH(r1, r7, r2)
/* Check if the upcall code was preempted and continued to here. */
ldr r3, [r0, #(PT_NEXT)]
teq r3, #0x00000000
beq 1f
/*
* Yes, it was. Stash the tread we were going to switch to,
* and switch to the next thread in the continuation chain.
*/
str r7, [r0, #(PT_SWITCHTOUC)]
str r1, [r0, #(PT_SWITCHTO)]
mov r0, r1 /* from = next */
mov r1, r3 /* to = self->pt_next */
mov r2, #0x00000001 /* decr-spinlocks = TRUE */
b pthread__switch_away
NOTREACHED
1: /* No old-upcall-preemption */
mov r4, r0 /* stash from */
mov r5, r1 /* stash to */
bl PIC_SYM(_C_LABEL(pthread__sa_recycle), PLT)
mov r0, r4 /* restore from */
mov r1, r5 /* restore to */
/* We can now release the fake lock. */
ldr r3, [r1, #(PT_SPINLOCKS)]
sub r3, r3, #1
str r3, [r1, #(PT_SPINLOCKS)]
/* Check if we were preempted and continued while faking the lock */
ldr r3, [r1, #(PT_NEXT)]
teq r3, #0x00000000
beq 2f
/*
* Yes, we were. Stash the thread we were going to switch to
* in itself, and switch to the next thread in the continuation
* chain.
*/
str r7, [r1, #(PT_SWITCHTOUC)]
str r1, [r1, #(PT_SWITCHTO)]
mov r0, r1 /* from = next */
mov r1, r3 /* to = next->pt_next */
mov r2, #0x00000000 /* decr-spinlocks = FALSE */
b pthread__switch_away
NOTREACHED
2: /* No new-upcall-preemption */
mov r0, r7 /* r0 = next->pt_uc */
b PIC_SYM(_C_LABEL(_setcontext_u), PLT)
/*
* void
* pthread__locked_switch(pthread_t self (r0), pthread_t next (r1),
* pt_spin_t *lock (r2))
*
* Total stack is:
* context (RND_CTXSIZE bytes)
* saved lr ( 4 bytes)
* saved r5 ( 4 bytes)
* saved r4 ( 4 bytes)
* saved r2 ( 4 bytes)
* saved r1 ( 4 bytes)
* saved r0 ( 4 bytes)
* Note, no space needed
* for callees.
*
* STACKSPACE is the space between the bottom of the stack and
* the ucontext on the stack. i.e., (6 * sizeof(reg)).
*
* Note: We use r4 as a temporary register, and r5 as a pointer to
* the context on the stack.
*/
ENTRY(pthread__locked_switch)
sub sp, sp, #(RND_CTXSIZE) /* push space for ucontext_t */
/* Make sure we get continued */
ldr r3, [r1, #(PT_SPINLOCKS)]
add r3, r3, #1
str r3, [r1, #(PT_SPINLOCKS)]
mov r3, sp /* r3 = &context */
/* Save r0-r2, r4-r5, lr */
stmfd sp!, {r0-r2, r4-r5, lr}
/* Now r5 will point to &context for the duration. */
mov r5, r3
/*
* Stack is:
* context (RND_CTXSIZE bytes)
* saved lr ( 4 bytes)
* saved r5 ( 4 bytes)
* saved r4 ( 4 bytes)
* saved r2 ( 4 bytes)
* saved r1 ( 4 bytes)
* saved r0 ( 4 bytes)
* Note, no space needed
* for callees.
*/
/* Get the current context. */
mov r0, r5 /* r0 = &context */
bl PIC_SYM(_C_LABEL(_getcontext_u), PLT)
/* Edit the context to make it continue below, rather than here */
add r0, pc, #locked_return_point - . - 8
str r0, [r5, #(_REG_PC)]
/* Get back r0-r2 */
ldmfd sp, {r0-r2}
str r5, [r0, #(PT_UC)] /* self->pt_uc = &context */
/* Side-effect: r4 = next->pt_uc */
STACK_SWITCH(r1, r4, r3)
/* NOTE: we can no longer recover registers stored on the stack */
/* Check if the switcher was preempted and continued to here. */
ldr r3, [r0, #(PT_NEXT)]
teq r3, #0x00000000
beq 1f
/*
* Yes, it was. Stash the thread we were going to switch to,
* the lock the original thread was holding, and switch to
* the next thread in the continuation chain. Mark the fact
* that this was a locked switch, and so the thread does not
* need to be put on a run queue.
*
* DO NOT RELEASE THE LOCK. It's possible that if we do so,
* PT_SWITCHTO will be stomped on by another switch_lock and
* preemption.
*/
str r2, [r0, #(PT_HELDLOCK)]
str r4, [r0, #(PT_SWITCHTOUC)]
str r1, [r0, #(PT_SWITCHTO)]
ldr r4, [r0, #(PT_SPINLOCKS)]
sub r4, r4, #1
str r4, [r0, #(PT_SPINLOCKS)]
mov r0, r1 /* from = next */
mov r1, r3 /* to = self->pt_next */
mov r2, #0x00000001 /* decr-spinlocks = TRUE */
b pthread__switch_away
NOTREACHED
1: /* No locked-old-preemption */
/*
* Note: r4 == next->pt_uc still. We no longer need to
* reference the context on the stack, so put next->pt_uc
* in r5 so that we can use r4 as a temporary.
*/
mov r5, r4
/*
* We've moved to the new stack, and the old context has
* been saved. The queue lock can be released.
*/
ldr r4, [r0, #(PT_SPINLOCKS)]
sub r4, r4, #1
str r4, [r0, #(PT_SPINLOCKS)]
/* ...actually release the lock... */
mov r4, #(__SIMPLELOCK_UNLOCKED)
str r4, [r2]
/* ...and remove the fake lock... */
ldr r4, [r1, #(PT_SPINLOCKS)]
sub r4, r4, #1
str r4, [r1, #(PT_SPINLOCKS)]
/* Check if we were preempted while holding the fake lock. */
ldr r3, [r1, #(PT_NEXT)]
teq r3, #0x00000000
beq 2f
/* Yes, we were. Go to the next thread in the chain. */
str r5, [r1, #(PT_SWITCHTOUC)]
str r1, [r1, #(PT_SWITCHTO)]
mov r0, r1 /* from = next */
mov r1, r3 /* to = next->pt_next */
mov r2, #0x00000000 /* decr-spinlocks = FALSE */
b pthread__switch_away
2: /* No locked-new-preemption */
mov r0, r5 /* r0 = next->pt_uc */
b PIC_SYM(_C_LABEL(_setcontext_u), PLT)
NOTREACHED
locked_return_point:
/*
* Stack is:
* context (RND_CTXSIZE bytes)
* saved lr ( 4 bytes)
* saved r5 ( 4 bytes)
* saved r4 ( 4 bytes)
* saved r2 ( 4 bytes)
* saved r1 ( 4 bytes)
* saved r0 ( 4 bytes)
* Note, no space needed
* for callees.
*/
add sp, sp, #12 /* skip over r0-r2 */
ldmfd sp!, {r4-r5, lr} /* restore r4-r5, lr */
add sp, sp, #(RND_CTXSIZE) /* pop ucontext_t */
RET