Viewing file: sv.c (12.99 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
/* * This file is subject to the terms and conditions of the GNU General Public * License. See the file "COPYING" in the main directory of this archive * for more details. * * Copyright (C) 2000 Silicon Graphics, Inc. All rights reserved * * This implemenation of synchronization variables is heavily based on * one done by Steve Lord <lord@sgi.com> * * Paul Cassella <pwc@sgi.com> */
#include <linux/kernel.h> #include <linux/sched.h> #include <linux/init.h>
#include <asm/semaphore.h> #include <asm/hardirq.h> #include <asm/softirq.h> #include <asm/current.h>
#include <asm/sn/sv.h>
/* Define this to have sv_test() run some simple tests. kernel_thread() must behave as expected when this is called. */ #undef RUN_SV_TEST
#define DEBUG
/* Set up some macros so sv_wait(), sv_signal(), and sv_broadcast() can sanity check interrupt state on architectures where we know how. */ #ifdef DEBUG #define SV_DEBUG_INTERRUPT_STATE #ifdef __mips64 #define SV_TEST_INTERRUPTS_ENABLED(flags) ((flags & 0x1) != 0) #define SV_TEST_INTERRUPTS_DISABLED(flags) ((flags & 0x1) == 0) #define SV_INTERRUPT_TEST_WORKERS 31 #elif defined(__ia64) #define SV_TEST_INTERRUPTS_ENABLED(flags) ((flags & 0x4000) != 0) #define SV_TEST_INTERRUPTS_DISABLED(flags) ((flags & 0x4000) == 0) #define SV_INTERRUPT_TEST_WORKERS 4 /* simulator's slow */ #else #undef SV_DEBUG_INTERRUPT_STATE #define SV_INTERRUPT_TEST_WORKERS 4 /* reasonable? default. */ #endif /* __mips64 */ #endif /* DEBUG */
/* XXX FIXME hack hack hack. Our mips64 tree is from before the switch to WQ_FLAG_EXCLUSIVE, and our ia64 tree is from after it. */ #ifdef TASK_EXCLUSIVE #undef EXCLUSIVE_IN_QUEUE #else #define EXCLUSIVE_IN_QUEUE #define TASK_EXCLUSIVE 0 /* for the set_current_state() in sv_wait() */ #endif
static inline void sv_lock(sv_t *sv) { spin_lock(&sv->sv_lock); }
static inline void sv_unlock(sv_t *sv) { spin_unlock(&sv->sv_lock); }
/* up() is "extern inline", so we can't pass its address to sv_wait. Use this function's address instead. */ static void up_wrapper(struct semaphore *sem) { up(sem); }
/* spin_unlock() is sometimes a macro. */ static void spin_unlock_wrapper(spinlock_t *s) { spin_unlock(s); }
/* XXX Perhaps sv_wait() should do the switch() each time and avoid the extra indirection and the need for the _wrapper functions? */
static inline void sv_set_mon_type(sv_t *sv, int type) { switch (type) { case SV_MON_SPIN: sv->sv_mon_unlock_func = (sv_mon_unlock_func_t)spin_unlock_wrapper; break; case SV_MON_SEMA: sv->sv_mon_unlock_func = (sv_mon_unlock_func_t)up_wrapper; if(sv->sv_flags & SV_INTS) { printk(KERN_ERR "sv_set_mon_type: The monitor lock " "cannot be shared with interrupts if it is a " "semaphore!\n"); BUG(); } if(sv->sv_flags & SV_BHS) { printk(KERN_ERR "sv_set_mon_type: The monitor lock " "cannot be shared with bottom-halves if it is " "a semaphore!\n"); BUG(); } break; #if 0 /* * If needed, and will need to think about interrupts. This * may be needed, for example, if someone wants to use sv's * with something like dev_base; writers need to hold two * locks. */ case SV_MON_CUSTOM: { struct sv_mon_custom *c = lock; sv->sv_mon_unlock_func = c->sv_mon_unlock_func; sv->sv_mon_lock = c->sv_mon_lock; break; } #endif default: printk(KERN_ERR "sv_set_mon_type: unknown type %d (0x%x)! " "(flags 0x%x)\n", type, type, sv->sv_flags); BUG(); break; } sv->sv_flags |= type; }
static inline void sv_set_ord(sv_t *sv, int ord) { if (!ord) ord = SV_ORDER_DEFAULT;
if (ord != SV_ORDER_FIFO && ord != SV_ORDER_LIFO) { printk(KERN_EMERG "sv_set_ord: unknown order %d (0x%x)! ", ord, ord); BUG(); }
sv->sv_flags |= ord; }
void sv_init(sv_t *sv, sv_mon_lock_t *lock, int flags) { int ord = flags & SV_ORDER_MASK; int type = flags & SV_MON_MASK;
/* Copy all non-order, non-type flags */ sv->sv_flags = (flags & ~(SV_ORDER_MASK | SV_MON_MASK));
if((sv->sv_flags & (SV_INTS | SV_BHS)) == (SV_INTS | SV_BHS)) { printk(KERN_ERR "sv_init: do not set both SV_INTS and SV_BHS, only SV_INTS.\n"); BUG(); }
sv_set_ord(sv, ord); sv_set_mon_type(sv, type);
/* If lock is NULL, we'll get it from sv_wait_compat() (and ignore it in sv_signal() and sv_broadcast()). */ sv->sv_mon_lock = lock;
spin_lock_init(&sv->sv_lock); init_waitqueue_head(&sv->sv_waiters); }
/* * The associated lock must be locked on entry. It is unlocked on return. * * Return values: * * n < 0 : interrupted, -n jiffies remaining on timeout, or -1 if timeout == 0 * n = 0 : timeout expired * n > 0 : sv_signal()'d, n jiffies remaining on timeout, or 1 if timeout == 0 */ signed long sv_wait(sv_t *sv, int sv_wait_flags, unsigned long timeout) { DECLARE_WAITQUEUE( wait, current ); unsigned long flags; signed long ret = 0;
#ifdef SV_DEBUG_INTERRUPT_STATE { unsigned long flags; __save_flags(flags);
if(sv->sv_flags & SV_INTS) { if(SV_TEST_INTERRUPTS_ENABLED(flags)) { printk(KERN_ERR "sv_wait: SV_INTS and interrupts " "enabled (flags: 0x%lx)\n", flags); BUG(); } } else { if (SV_TEST_INTERRUPTS_DISABLED(flags)) { printk(KERN_WARNING "sv_wait: !SV_INTS and interrupts " "disabled! (flags: 0x%lx)\n", flags); } } } #endif /* SV_DEBUG_INTERRUPT_STATE */
sv_lock(sv);
sv->sv_mon_unlock_func(sv->sv_mon_lock);
/* Add ourselves to the wait queue and set the state before * releasing the sv_lock so as to avoid racing with the * wake_up() in sv_signal() and sv_broadcast(). */
/* don't need the _irqsave part, but there is no wq_write_lock() */ wq_write_lock_irqsave(&sv->sv_waiters.lock, flags);
#ifdef EXCLUSIVE_IN_QUEUE wait.flags |= WQ_FLAG_EXCLUSIVE; #endif
switch(sv->sv_flags & SV_ORDER_MASK) { case SV_ORDER_FIFO: __add_wait_queue_tail(&sv->sv_waiters, &wait); break; case SV_ORDER_FILO: __add_wait_queue(&sv->sv_waiters, &wait); break; default: printk(KERN_ERR "sv_wait: unknown order! (sv: 0x%p, flags: 0x%x)\n", sv, sv->sv_flags); BUG(); } wq_write_unlock_irqrestore(&sv->sv_waiters.lock, flags);
if(sv_wait_flags & SV_WAIT_SIG) set_current_state(TASK_EXCLUSIVE | TASK_INTERRUPTIBLE ); else set_current_state(TASK_EXCLUSIVE | TASK_UNINTERRUPTIBLE);
spin_unlock(&sv->sv_lock);
if(sv->sv_flags & SV_INTS) local_irq_enable(); else if(sv->sv_flags & SV_BHS) local_bh_enable();
if (timeout) ret = schedule_timeout(timeout); else schedule();
if(current->state != TASK_RUNNING) /* XXX Is this possible? */ { printk(KERN_ERR "sv_wait: state not TASK_RUNNING after " "schedule().\n"); set_current_state(TASK_RUNNING); }
remove_wait_queue(&sv->sv_waiters, &wait);
/* Return cases: - woken by a sv_signal/sv_broadcast - woken by a signal - woken by timeout expiring */
/* XXX This isn't really accurate; we may have been woken before the signal anyway.... */ if(signal_pending(current)) return timeout ? -ret : -1; return timeout ? ret : 1; }
void sv_signal(sv_t *sv) { /* If interrupts can acquire this lock, they can also acquire the sv_mon_lock, which we must already have to have called this, so interrupts must be disabled already. If interrupts cannot contend for this lock, we don't have to worry about it. */
#ifdef SV_DEBUG_INTERRUPT_STATE if(sv->sv_flags & SV_INTS) { unsigned long flags; __save_flags(flags); if(SV_TEST_INTERRUPTS_ENABLED(flags)) printk(KERN_ERR "sv_signal: SV_INTS and " "interrupts enabled! (flags: 0x%lx)\n", flags); } #endif /* SV_DEBUG_INTERRUPT_STATE */
sv_lock(sv); wake_up(&sv->sv_waiters); sv_unlock(sv); }
void sv_broadcast(sv_t *sv) { #ifdef SV_DEBUG_INTERRUPT_STATE if(sv->sv_flags & SV_INTS) { unsigned long flags; __save_flags(flags); if(SV_TEST_INTERRUPTS_ENABLED(flags)) printk(KERN_ERR "sv_broadcast: SV_INTS and " "interrupts enabled! (flags: 0x%lx)\n", flags); } #endif /* SV_DEBUG_INTERRUPT_STATE */
sv_lock(sv); wake_up_all(&sv->sv_waiters); sv_unlock(sv); }
void sv_destroy(sv_t *sv) { if(!spin_trylock(&sv->sv_lock)) { printk(KERN_ERR "sv_destroy: someone else has sv 0x%p locked!\n", sv); BUG(); }
/* XXX Check that the waitqueue is empty? Mark the sv destroyed? */ }
#ifdef RUN_SV_TEST
static DECLARE_MUTEX_LOCKED(talkback); static DECLARE_MUTEX_LOCKED(sem); sv_t sv; sv_t sv_filo;
static int sv_test_1_w(void *arg) { printk("sv_test_1_w: acquiring spinlock 0x%p...\n", arg);
spin_lock((spinlock_t*)arg); printk("sv_test_1_w: spinlock acquired, waking sv_test_1_s.\n");
up(&sem);
printk("sv_test_1_w: sv_spin_wait()'ing.\n");
sv_spin_wait(&sv, arg);
printk("sv_test_1_w: talkback.\n"); up(&talkback);
printk("sv_test_1_w: exiting.\n"); return 0; }
static int sv_test_1_s(void *arg) { printk("sv_test_1_s: waiting for semaphore.\n"); down(&sem); printk("sv_test_1_s: semaphore acquired. Acquiring spinlock.\n"); spin_lock((spinlock_t*)arg); printk("sv_test_1_s: spinlock acquired. sv_signaling.\n"); sv_signal(&sv); printk("sv_test_1_s: talkback.\n"); up(&talkback); printk("sv_test_1_s: exiting.\n"); return 0;
}
static int count; static DECLARE_MUTEX(monitor);
static int sv_test_2_w(void *arg) { int dummy = count++; sv_t *sv = (sv_t *)arg;
down(&monitor); up(&talkback); printk("sv_test_2_w: thread %d started, sv_waiting.\n", dummy); sv_sema_wait(sv, &monitor); printk("sv_test_2_w: thread %d woken, exiting.\n", dummy); up(&sem); return 0; }
static int sv_test_2_s_1(void *arg) { int i; sv_t *sv = (sv_t *)arg;
down(&monitor); for(i = 0; i < 3; i++) { printk("sv_test_2_s_1: waking one thread.\n"); sv_signal(sv); down(&sem); }
printk("sv_test_2_s_1: signaling and broadcasting again. Nothing should happen.\n"); sv_signal(sv); sv_broadcast(sv); sv_signal(sv); sv_broadcast(sv);
printk("sv_test_2_s_1: talkbacking.\n"); up(&talkback); up(&monitor); return 0; }
static int sv_test_2_s(void *arg) { int i; sv_t *sv = (sv_t *)arg;
down(&monitor); for(i = 0; i < 3; i++) { printk("sv_test_2_s: waking one thread (should be %d.)\n", i); sv_signal(sv); down(&sem); }
printk("sv_test_3_s: waking remaining threads with broadcast.\n"); sv_broadcast(sv); for(; i < 10; i++) down(&sem);
printk("sv_test_3_s: sending talkback.\n"); up(&talkback);
printk("sv_test_3_s: exiting.\n"); up(&monitor); return 0; }
static void big_test(sv_t *sv) { int i;
count = 0;
for(i = 0; i < 3; i++) { printk("big_test: spawning thread %d.\n", i); kernel_thread(sv_test_2_w, sv, 0); down(&talkback); }
printk("big_test: spawning first wake-up thread.\n"); kernel_thread(sv_test_2_s_1, sv, 0);
down(&talkback); printk("big_test: talkback happened.\n");
for(i = 3; i < 13; i++) { printk("big_test: spawning thread %d.\n", i); kernel_thread(sv_test_2_w, sv, 0); down(&talkback); }
printk("big_test: spawning wake-up thread.\n"); kernel_thread(sv_test_2_s, sv, 0);
down(&talkback); }
sv_t int_test_sv; spinlock_t int_test_spin = SPIN_LOCK_UNLOCKED; int int_test_ready; static int irqtestcount;
static int interrupt_test_worker(void *unused) { int id = ++irqtestcount; int it = 0; unsigned long flags, flags2;
printk("ITW: thread %d started.\n", id);
while(1) { __save_flags(flags2); if(jiffies % 3) { printk("ITW %2d %5d: irqsaving (%lx)\n", id, it, flags2); spin_lock_irqsave(&int_test_spin, flags); } else { printk("ITW %2d %5d: spin_lock_irqing (%lx)\n", id, it, flags2); spin_lock_irq(&int_test_spin); }
__save_flags(flags2); printk("ITW %2d %5d: locked, sv_waiting (%lx).\n", id, it, flags2); sv_wait(&int_test_sv, 0, 0);
__save_flags(flags2); printk("ITW %2d %5d: wait finished (%lx), pausing\n", id, it, flags2); set_current_state(TASK_INTERRUPTIBLE); schedule_timeout(jiffies & 0xf); if(current->state != TASK_RUNNING) printk("ITW: current->state isn't RUNNING after schedule!\n"); it++; } }
static void interrupt_test(void) { int i;
printk("interrupt_test: initing sv.\n"); sv_init(&int_test_sv, &int_test_spin, SV_MON_SPIN | SV_INTS);
for(i = 0; i < SV_INTERRUPT_TEST_WORKERS; i++) { printk("interrupt_test: starting test thread %d.\n", i); kernel_thread(interrupt_test_worker, 0, 0); } printk("interrupt_test: done with init part.\n"); int_test_ready = 1; }
int sv_test(void) { spinlock_t s = SPIN_LOCK_UNLOCKED;
sv_init(&sv, &s, SV_MON_SPIN); printk("sv_test: starting sv_test_1_w.\n"); kernel_thread(sv_test_1_w, &s, 0); printk("sv_test: starting sv_test_1_s.\n"); kernel_thread(sv_test_1_s, &s, 0);
printk("sv_test: waiting for talkback.\n"); down(&talkback); down(&talkback); printk("sv_test: talkback happened, sv_destroying.\n"); sv_destroy(&sv);
count = 0;
printk("sv_test: beginning big_test on sv.\n");
sv_init(&sv, &monitor, SV_MON_SEMA); big_test(&sv); sv_destroy(&sv);
printk("sv_test: beginning big_test on sv_filo.\n"); sv_init(&sv_filo, &monitor, SV_MON_SEMA | SV_ORDER_FILO); big_test(&sv_filo); sv_destroy(&sv_filo);
interrupt_test();
printk("sv_test: done.\n"); return 0; }
__initcall(sv_test);
#endif /* RUN_SV_TEST */
|