Rewrite the ARM mutex implementation to be of the simple-mutex variety.

Because pre-v6 ARM lacks support for an atomic compare-and-swap, we
implement _lock_cas() as a restartable atomic squence that is checked
in the IRQ handler right before AST processing.  (This is safe because,
for all practical purposes, there are no SMP pre-v6 ARM systems.)

This can serve as a model for other non-MP platforms that lack the
necessary atomic operations for mutexes (SuperH, for example).

Upshots of this change:
- kmutex_t is now down to 8 bytes on ARM; about as good as we can get.
- ARM2 systems don't have to trap and emulate SWP or SWPB for mutexes.

The acorn26 port is not updated by this commit to do the LOCK_CAS_CHECK.
That is left as an exercise for the port maintainer.

Reviewed and tested by Matt Thomas.
This commit is contained in:
thorpej 2007-03-09 19:21:57 +00:00
parent b1c0dd5ffc
commit 165d4e6d83
11 changed files with 151 additions and 138 deletions

View File

@ -1,4 +1,4 @@
/* $NetBSD: arm_machdep.c,v 1.13 2007/02/09 21:55:02 ad Exp $ */
/* $NetBSD: arm_machdep.c,v 1.14 2007/03/09 19:21:57 thorpej Exp $ */
/*
* Copyright (c) 2001 Wasabi Systems, Inc.
@ -73,10 +73,11 @@
#include "opt_compat_netbsd.h"
#include "opt_execfmt.h"
#include "opt_arm_debug.h"
#include <sys/param.h>
__KERNEL_RCSID(0, "$NetBSD: arm_machdep.c,v 1.13 2007/02/09 21:55:02 ad Exp $");
__KERNEL_RCSID(0, "$NetBSD: arm_machdep.c,v 1.14 2007/03/09 19:21:57 thorpej Exp $");
#include <sys/exec.h>
#include <sys/proc.h>
@ -84,6 +85,7 @@ __KERNEL_RCSID(0, "$NetBSD: arm_machdep.c,v 1.13 2007/02/09 21:55:02 ad Exp $");
#include <sys/user.h>
#include <sys/pool.h>
#include <sys/ucontext.h>
#include <sys/evcnt.h>
#include <arm/cpufunc.h>
@ -102,6 +104,24 @@ __KERNEL_RCSID(0, "$NetBSD: arm_machdep.c,v 1.13 2007/02/09 21:55:02 ad Exp $");
*/
vaddr_t vector_page;
#if defined(ARM_LOCK_CAS_DEBUG)
/*
* Event counters for tracking activity of the RAS-based _lock_cas()
* routine.
*/
struct evcnt _lock_cas_restart =
EVCNT_INITIALIZER(EVCNT_TYPE_MISC, NULL, "_lock_cas", "restart");
EVCNT_ATTACH_STATIC(_lock_cas_restart);
struct evcnt _lock_cas_success =
EVCNT_INITIALIZER(EVCNT_TYPE_MISC, NULL, "_lock_cas", "success");
EVCNT_ATTACH_STATIC(_lock_cas_success);
struct evcnt _lock_cas_fail =
EVCNT_INITIALIZER(EVCNT_TYPE_MISC, NULL, "_lock_cas", "fail");
EVCNT_ATTACH_STATIC(_lock_cas_fail);
#endif /* ARM_LOCK_CAS_DEBUG */
/*
* Clear registers on exec
*/

View File

@ -1,4 +1,4 @@
# $NetBSD: genassym.cf,v 1.31 2007/02/20 00:05:14 matt Exp $
# $NetBSD: genassym.cf,v 1.32 2007/03/09 19:21:57 thorpej Exp $
# Copyright (c) 1982, 1990 The Regents of the University of California.
# All rights reserved.
@ -142,6 +142,8 @@ define TF_R0 offsetof(struct trapframe, tf_r0)
define TF_R10 offsetof(struct trapframe, tf_r10)
define TF_PC offsetof(struct trapframe, tf_pc)
define IF_PC offsetof(struct irqframe, if_pc)
define PROCSIZE sizeof(struct proc)
define TRAPFRAMESIZE sizeof(struct trapframe)

View File

@ -1,4 +1,4 @@
/* $NetBSD: irq_dispatch.S,v 1.7 2005/12/11 12:16:41 christos Exp $ */
/* $NetBSD: irq_dispatch.S,v 1.8 2007/03/09 19:21:58 thorpej Exp $ */
/*
* Copyright (c) 2002 Fujitsu Component Limited
@ -94,6 +94,8 @@
.Lcurrent_intr_depth:
.word _C_LABEL(current_intr_depth)
LOCK_CAS_CHECK_LOCALS
AST_ALIGNMENT_FAULT_LOCALS
ASENTRY_NP(irq_entry)
@ -125,6 +127,8 @@ ASENTRY_NP(irq_entry)
*/
str r6, [r5]
LOCK_CAS_CHECK
DO_AST_AND_RESTORE_ALIGNMENT_FAULTS
PULLFRAMEFROMSVCANDEXIT
movs pc, lr /* Exit */

View File

@ -1,4 +1,4 @@
# $NetBSD: files.arm,v 1.82 2007/01/06 00:50:54 christos Exp $
# $NetBSD: files.arm,v 1.83 2007/03/09 19:21:58 thorpej Exp $
# temporary define to allow easy moving to ../arch/arm/arm32
defflag ARM32
@ -23,6 +23,9 @@ defflag opt_cpuoptions.h ARM9_CACHE_WRITE_THROUGH
# Interrupt implementation header definition.
defparam opt_arm_intr_impl.h ARM_INTR_IMPL
# ARM-specific debug options
defflag opt_arm_debug.h ARM_LOCK_CAS_DEBUG
# Board-specific bus_space(9) definitions
defflag opt_arm_bus_space.h __BUS_SPACE_HAS_STREAM_METHODS
@ -115,6 +118,7 @@ file arch/arm/arm/cpufunc_asm_xscale.S cpu_xscale_80200 |
cpu_xscale_pxa250 |
cpu_xscale_pxa270
file arch/arm/arm/cpufunc_asm_ixp12x0.S cpu_ixp12x0
file arch/arm/arm/lock_cas.S
file arch/arm/arm/process_machdep.c
file arch/arm/arm/procfs_machdep.c procfs
file arch/arm/arm/sig_machdep.c

View File

@ -1,4 +1,4 @@
/* $NetBSD: frame.h,v 1.14 2006/09/27 21:42:05 manu Exp $ */
/* $NetBSD: frame.h,v 1.15 2007/03/09 19:21:58 thorpej Exp $ */
/*
* Copyright (c) 1994-1997 Mark Brinicombe.
@ -111,6 +111,7 @@ void validate_trapframe __P((trapframe_t *, int));
#include "opt_compat_netbsd.h"
#include "opt_execfmt.h"
#include "opt_multiprocessor.h"
#include "opt_arm_debug.h"
/*
* AST_ALIGNMENT_FAULT_LOCALS and ENABLE_ALIGNMENT_FAULTS
@ -267,6 +268,55 @@ void validate_trapframe __P((trapframe_t *, int));
2:
#endif /* EXEC_AOUT */
#ifdef ARM_LOCK_CAS_DEBUG
#define LOCK_CAS_DEBUG_LOCALS \
.L_lock_cas_restart: ;\
.word _C_LABEL(_lock_cas_restart)
#if defined(__ARMEB__)
#define LOCK_CAS_DEBUG_COUNT_RESTART \
ble 99f ;\
ldr r0, .L_lock_cas_restart ;\
ldmia r0, {r1-r2} /* load ev_count */ ;\
adds r2, r2, #1 /* 64-bit incr (lo) */ ;\
adc r1, r1, #0 /* 64-bit incr (hi) */ ;\
stmia r0, {r1-r2} /* store ev_count */
#else /* __ARMEB__ */
#define LOCK_CAS_DEBUG_COUNT_RESTART \
ble 99f ;\
ldr r0, .L_lock_cas_restart ;\
ldmia r0, {r1-r2} /* load ev_count */ ;\
adds r1, r1, #1 /* 64-bit incr (lo) */ ;\
adc r2, r2, #0 /* 64-bit incr (hi) */ ;\
stmia r0, {r1-r2} /* store ev_count */
#endif /* __ARMEB__ */
#else /* ARM_LOCK_CAS_DEBUG */
#define LOCK_CAS_DEBUG_LOCALS /* nothing */
#define LOCK_CAS_DEBUG_COUNT_RESTART /* nothing */
#endif /* ARM_LOCK_CAS_DEBUG */
#define LOCK_CAS_CHECK_LOCALS \
.L_lock_cas: ;\
.word _C_LABEL(_lock_cas) ;\
.L_lock_cas_end: ;\
.word _C_LABEL(_lock_cas_end) ;\
LOCK_CAS_DEBUG_LOCALS
#define LOCK_CAS_CHECK \
ldr r0, [sp] /* get saved PSR */ ;\
and r0, r0, #(PSR_MODE) /* check for SVC32 mode */ ;\
teq r0, #(PSR_SVC32_MODE) ;\
bne 99f /* nope, get out now */ ;\
ldr r0, [sp, #(IF_PC)] ;\
ldr r1, .L_lock_cas_end ;\
cmp r0, r1 ;\
bge 99f ;\
ldr r1, .L_lock_cas ;\
cmp r0, r1 ;\
strgt r1, [sp, #(IF_PC)] ;\
LOCK_CAS_DEBUG_COUNT_RESTART ;\
99:
/*
* ASM macros for pushing and pulling trapframes from the stack
*

View File

@ -1,4 +1,4 @@
/* $NetBSD: mutex.h,v 1.6 2007/03/09 11:30:28 skrll Exp $ */
/* $NetBSD: mutex.h,v 1.7 2007/03/09 19:21:58 thorpej Exp $ */
/*-
* Copyright (c) 2002, 2007 The NetBSD Foundation, Inc.
@ -41,155 +41,68 @@
/*
* The ARM mutex implementation is troublesome, because pre-v6 ARM lacks a
* compare-and-set operation. However, there aren't any MP pre-v6 ARM
* compare-and-swap operation. However, there aren't any MP pre-v6 ARM
* systems to speak of. We are mostly concerned with atomicity with respect
* to interrupts.
*
* SMP for spin mutexes is easy - we don't need to know who owns the lock.
* For adaptive mutexes, we need an additional interlock.
* ARMv6, however, does have ldrex/strex, and can thus implement an MP-safe
* compare-and-swap.
*
* Unfortunately, not all ARM kernels are linked at the same address,
* meaning we cannot safely overlay the interlock with the MSB of the
* owner field.
*
* For a mutex acquisition, we first grab the interlock and then set the
* owner field.
*
* There is room in the owners field for a waiters bit, but we don't do
* that because it would be hard to synchronize using one without a CAS
* operation. Because the waiters bit is only needed for adaptive mutexes,
* we instead use the lock that is normally used by spin mutexes to indicate
* waiters.
*
* Spin mutexes are initialized with the interlock held to cause the
* assembly stub to go through mutex_vector_enter().
*
* When releasing an adaptive mutex, we first clear the owners field, and
* then check to see if the waiters byte is set. This ensures that there
* will always be someone to wake any sleeping waiters up (even it the mutex
* is acquired immediately after we release it, or if we are preempted
* immediatley after clearing the owners field). The setting or clearing of
* the waiters byte is serialized by the turnstile chain lock associated
* with the mutex.
*
* See comments in kern_mutex.c about releasing adaptive mutexes without
* an interlocking step.
* So, what we have done is impement simple mutexes using a compare-and-swap.
* We support pre-ARMv6 by implementing _lock_cas() as a restartable atomic
* sequence that is checked by the IRQ vector. MP-safe ARMv6 support will
* be added later.
*/
#ifndef __MUTEX_PRIVATE
struct kmutex {
uintptr_t mtx_pad1;
uint32_t mtx_pad2[2];
uint32_t mtx_pad2;
};
#else /* __MUTEX_PRIVATE */
struct kmutex {
volatile uintptr_t mtx_owner; /* 0-3 */
__cpu_simple_lock_t mtx_interlock; /* 4 */
__cpu_simple_lock_t mtx_lock; /* 5 */
ipl_cookie_t mtx_ipl; /* 6 */
uint8_t mtx_pad; /* 7 */
uint32_t mtx_id; /* 8-11 */
union {
/* Adaptive mutex */
volatile uintptr_t mtxa_owner; /* 0-3 */
/* Spin mutex */
struct {
volatile uint8_t mtxs_dummy;
ipl_cookie_t mtxs_ipl;
__cpu_simple_lock_t mtxs_lock;
volatile uint8_t mtxs_unused;
} s;
} u;
volatile uint32_t mtx_id; /* 4-7 */
};
#define mtx_owner u.mtxa_owner
#define mtx_ipl u.s.mtxs_ipl
#define mtx_lock u.s.mtxs_lock
#if 0
#define __HAVE_MUTEX_STUBS 1
#define __HAVE_SPIN_MUTEX_STUBS 1
#define __HAVE_MUTEX_STUBS 1
#define __HAVE_SPIN_MUTEX_STUBS 1
#endif
#define __HAVE_SIMPLE_MUTEXES 1
static inline uintptr_t
MUTEX_OWNER(uintptr_t owner)
{
return owner;
}
/*
* MUTEX_RECEIVE: no memory barrier required; we're synchronizing against
* interrupts, not multiple processors.
*/
#define MUTEX_RECEIVE(mtx) /* nothing */
static inline int
MUTEX_OWNED(uintptr_t owner)
{
return owner != 0;
}
/*
* MUTEX_GIVE: no memory barrier required; same reason.
*/
#define MUTEX_GIVE(mtx) /* nothing */
static inline int
MUTEX_SET_WAITERS(kmutex_t *mtx, uintptr_t owner)
{
(void)__cpu_simple_lock_try(&mtx->mtx_lock);
return mtx->mtx_owner != 0;
}
bool _lock_cas(volatile uintptr_t *, uintptr_t, uintptr_t);
static inline void
MUTEX_CLEAR_WAITERS(kmutex_t *mtx)
{
__cpu_simple_unlock(&mtx->mtx_lock);
}
static inline int
MUTEX_HAS_WAITERS(volatile kmutex_t *mtx)
{
if (mtx->mtx_owner == 0)
return 0;
return mtx->mtx_lock == __SIMPLELOCK_LOCKED;
}
static inline void
MUTEX_INITIALIZE_SPIN(kmutex_t *mtx, u_int id, int ipl)
{
mtx->mtx_id = (id << 1) | 1;
mtx->mtx_ipl = makeiplcookie(ipl);
mtx->mtx_interlock = __SIMPLELOCK_LOCKED;
__cpu_simple_lock_init(&mtx->mtx_lock);
}
static inline void
MUTEX_INITIALIZE_ADAPTIVE(kmutex_t *mtx, u_int id)
{
mtx->mtx_id = (id << 1) | 0;
__cpu_simple_lock_init(&mtx->mtx_interlock);
__cpu_simple_lock_init(&mtx->mtx_lock);
}
static inline void
MUTEX_DESTROY(kmutex_t *mtx)
{
mtx->mtx_owner = (uintptr_t)-1L;
mtx->mtx_id = ~0;
}
static inline u_int
MUTEX_GETID(kmutex_t *mtx)
{
return mtx->mtx_id >> 1;
}
static inline bool
MUTEX_SPIN_P(volatile kmutex_t *mtx)
{
return (mtx->mtx_id & 1) == 1;
}
static inline bool
MUTEX_ADAPTIVE_P(volatile kmutex_t *mtx)
{
return (mtx->mtx_id & 1) == 0;
}
static inline int
MUTEX_ACQUIRE(kmutex_t *mtx, uintptr_t curthread)
{
if (!__cpu_simple_lock_try(&mtx->mtx_interlock))
return 0;
mtx->mtx_owner = curthread;
return 1;
}
static inline void
MUTEX_RELEASE(kmutex_t *mtx)
{
mtx->mtx_owner = 0;
__cpu_simple_unlock(&mtx->mtx_lock);
__cpu_simple_unlock(&mtx->mtx_interlock);
}
#define MUTEX_CAS(p, o, n) _lock_cas((p), (o), (n))
#endif /* __MUTEX_PRIVATE */

View File

@ -1,4 +1,4 @@
/* $NetBSD: iomd_irq.S,v 1.5 2005/12/11 12:16:47 christos Exp $ */
/* $NetBSD: iomd_irq.S,v 1.6 2007/03/09 19:21:58 thorpej Exp $ */
/*
* Copyright (c) 1994-1998 Mark Brinicombe.
@ -96,6 +96,8 @@ Lcurrent_intr_depth:
Lspl_masks:
.word _C_LABEL(spl_masks)
LOCK_CAS_CHECK_LOCALS
AST_ALIGNMENT_FAULT_LOCALS
/*
@ -337,6 +339,8 @@ exitirq:
sub r1, r1, #1
str r1, [r0]
LOCK_CAS_CHECK
DO_AST_AND_RESTORE_ALIGNMENT_FAULTS
PULLFRAMEFROMSVCANDEXIT

View File

@ -1,4 +1,4 @@
/* $NetBSD: ofw_irq.S,v 1.6 2007/03/08 20:48:39 matt Exp $ */
/* $NetBSD: ofw_irq.S,v 1.7 2007/03/09 19:21:59 thorpej Exp $ */
/*
* Copyright (c) 1994-1998 Mark Brinicombe.
@ -93,6 +93,8 @@ Lirq_entry:
Lofwirqstk: /* hack */
.word ofwirqstk + 4096
LOCK_CAS_CHECK_LOCALS
AST_ALIGNMENT_FAULT_LOCALS
/*
@ -325,6 +327,8 @@ nextirq:
sub r1, r1, #1
str r1, [r0]
LOCK_CAS_CHECK
DO_AST_AND_RESTORE_ALIGNMENT_FAULTS
PULLFRAMEFROMSVCANDEXIT
movs pc, lr /* Exit */

View File

@ -1,4 +1,4 @@
/* $NetBSD: sa11x0_irq.S,v 1.9 2006/03/05 11:30:58 peter Exp $ */
/* $NetBSD: sa11x0_irq.S,v 1.10 2007/03/09 19:21:59 thorpej Exp $ */
/*
* Copyright (c) 1998 Mark Brinicombe.
@ -67,6 +67,8 @@ Ldbg_str:
.asciz "irq_entry %x %x\n"
#endif
LOCK_CAS_CHECK_LOCALS
AST_ALIGNMENT_FAULT_LOCALS
/*
@ -244,6 +246,8 @@ nextirq:
sub r1, r1, #1
str r1, [r0]
LOCK_CAS_CHECK
DO_AST_AND_RESTORE_ALIGNMENT_FAULTS
PULLFRAMEFROMSVCANDEXIT

View File

@ -1,4 +1,4 @@
/* $NetBSD: i80200_irq.S,v 1.13 2005/12/11 12:16:51 christos Exp $ */
/* $NetBSD: i80200_irq.S,v 1.14 2007/03/09 19:21:59 thorpej Exp $ */
/*
* Copyright (c) 2002 Wasabi Systems, Inc.
@ -65,6 +65,8 @@
.word _C_LABEL(xscale_pmc_dispatch)
#endif
LOCK_CAS_CHECK_LOCALS
AST_ALIGNMENT_FAULT_LOCALS
ASENTRY_NP(irq_entry)
@ -128,6 +130,8 @@ ASENTRY_NP(irq_entry)
sub r1, r1, #1
str r1, [r0]
LOCK_CAS_CHECK
DO_AST_AND_RESTORE_ALIGNMENT_FAULTS
PULLFRAMEFROMSVCANDEXIT
movs pc, lr /* Exit */

View File

@ -1,4 +1,4 @@
/* $NetBSD: isa_irq.S,v 1.6 2007/03/08 20:48:39 matt Exp $ */
/* $NetBSD: isa_irq.S,v 1.7 2007/03/09 19:21:59 thorpej Exp $ */
/*
* Copyright 1997
@ -289,6 +289,8 @@ nextirq:
sub r1, r1, #1
str r1, [r0]
LOCK_CAS_CHECK
DO_AST_AND_RESTORE_ALIGNMENT_FAULTS
PULLFRAMEFROMSVCANDEXIT
@ -301,6 +303,8 @@ Lspl_mask:
Lcurrent_mask:
.word _C_LABEL(current_mask) /* irq's that are usable */
LOCK_CAS_CHECK_LOCALS
AST_ALIGNMENT_FAULT_LOCALS