/*- * Copyright (c) 1997, by Steve Passe * All rights reserved. * * 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. The name of the developer may NOT be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 THE AUTHOR OR CONTRIBUTORS 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. * * $FreeBSD: src/sys/i386/i386/simplelock.s,v 1.11.2.2 2003/02/04 20:55:28 jhb Exp $ * $DragonFly: src/sys/i386/i386/Attic/simplelock.s,v 1.2 2003/06/17 04:28:35 dillon Exp $ */ /* * credit to Bruce Evans for help with asm optimization. */ #include /* miscellaneous macros */ #include #include #include /** FAST_HI */ /* * The following impliments the primitives described in i386/i386/param.h * necessary for the Lite2 lock manager system. * The major difference is that the "volatility" of the lock datum has been * pushed down from the various functions to lock_data itself. */ /* * The simple-lock routines are the primitives out of which the lock * package is built. The machine-dependent code must implement an * atomic test_and_set operation that indivisibly sets the simple lock * to non-zero and returns its old value. It also assumes that the * setting of the lock to zero below is indivisible. Simple locks may * only be used for exclusive locks. * * struct simplelock { * volatile int lock_data; * }; */ /* * void * s_lock_init(struct simplelock *lkp) * { * lkp->lock_data = 0; * } */ ENTRY(s_lock_init) movl 4(%esp), %eax /* get the address of the lock */ movl $0, (%eax) ret /* * void * s_lock(struct simplelock *lkp) * { * while (test_and_set(&lkp->lock_data)) * continue; * } * * Note: * If the acquire fails we do a loop of reads waiting for the lock to * become free instead of continually beating on the lock with xchgl. * The theory here is that the CPU will stay within its cache until * a write by the other CPU updates it, instead of continually updating * the local cache (and thus causing external bus writes) with repeated * writes to the lock. */ #ifndef SL_DEBUG ENTRY(s_lock) movl 4(%esp), %eax /* get the address of the lock */ movl $1, %ecx setlock: xchgl %ecx, (%eax) testl %ecx, %ecx jz gotit /* it was clear, return */ wait: pause cmpl $0, (%eax) /* wait to empty */ jne wait /* still set... */ jmp setlock /* empty again, try once more */ gotit: ret #else /* SL_DEBUG */ ENTRY(s_lock) movl 4(%esp), %edx /* get the address of the lock */ setlock: movl _cpu_lockid, %ecx /* add cpu id portion */ incl %ecx /* add lock portion */ movl $0, %eax lock cmpxchgl %ecx, (%edx) jz gotit /* it was clear, return */ pushl %eax /* save what we xchanged */ decl %eax /* remove lock portion */ cmpl _cpu_lockid, %eax /* do we hold it? */ je bad_slock /* yes, thats not good... */ addl $4, %esp /* clear the stack */ wait: pause cmpl $0, (%edx) /* wait to empty */ jne wait /* still set... */ jmp setlock /* empty again, try once more */ gotit: ret ALIGN_TEXT bad_slock: /* %eax (current lock) is already on the stack */ pushl %edx pushl _cpuid pushl $bsl1 call _panic bsl1: .asciz "rslock: cpu: %d, addr: 0x%08x, lock: 0x%08x" #endif /* SL_DEBUG */ /* * int * s_lock_try(struct simplelock *lkp) * { * return (!test_and_set(&lkp->lock_data)); * } */ #ifndef SL_DEBUG ENTRY(s_lock_try) movl 4(%esp), %eax /* get the address of the lock */ movl $1, %ecx xchgl %ecx, (%eax) testl %ecx, %ecx setz %al /* 1 if previous value was 0 */ movzbl %al, %eax /* convert to an int */ ret #else /* SL_DEBUG */ ENTRY(s_lock_try) movl 4(%esp), %edx /* get the address of the lock */ movl _cpu_lockid, %ecx /* add cpu id portion */ incl %ecx /* add lock portion */ xorl %eax, %eax lock cmpxchgl %ecx, (%edx) setz %al /* 1 if previous value was 0 */ movzbl %al, %eax /* convert to an int */ ret #endif /* SL_DEBUG */ /* * void * s_unlock(struct simplelock *lkp) * { * lkp->lock_data = 0; * } */ ENTRY(s_unlock) movl 4(%esp), %eax /* get the address of the lock */ movl $0, (%eax) ret #if 0 /* * XXX CRUFTY SS_LOCK IMPLEMENTATION REMOVED XXX * * These versions of simple_lock block interrupts, * making it suitable for regions accessed by both top and bottom levels. * This is done by saving the current value of the cpu flags in a per-cpu * global, and disabling interrupts when the lock is taken. When the * lock is released, interrupts might be enabled, depending upon the saved * cpu flags. * Because of this, it must ONLY be used for SHORT, deterministic paths! * * Note: * It would appear to be "bad behaviour" to blindly store a value in * ss_eflags, as this could destroy the previous contents. But since ss_eflags * is a per-cpu variable, and its fatal to attempt to acquire a simplelock * that you already hold, we get away with it. This needs to be cleaned * up someday... */ /* * void ss_lock(struct simplelock *lkp) */ #ifndef SL_DEBUG ENTRY(ss_lock) movl 4(%esp), %eax /* get the address of the lock */ movl $1, %ecx /* value for a held lock */ ssetlock: pushfl cli xchgl %ecx, (%eax) /* compete */ testl %ecx, %ecx jz sgotit /* it was clear, return */ popfl /* previous value while waiting */ swait: pause cmpl $0, (%eax) /* wait to empty */ jne swait /* still set... */ jmp ssetlock /* empty again, try once more */ sgotit: popl _ss_eflags /* save the old eflags */ ret #else /* SL_DEBUG */ ENTRY(ss_lock) movl 4(%esp), %edx /* get the address of the lock */ ssetlock: movl _cpu_lockid, %ecx /* add cpu id portion */ incl %ecx /* add lock portion */ pushfl cli movl $0, %eax lock cmpxchgl %ecx, (%edx) /* compete */ jz sgotit /* it was clear, return */ pushl %eax /* save what we xchanged */ decl %eax /* remove lock portion */ cmpl _cpu_lockid, %eax /* do we hold it? */ je sbad_slock /* yes, thats not good... */ addl $4, %esp /* clear the stack */ popfl swait: pause cmpl $0, (%edx) /* wait to empty */ jne swait /* still set... */ jmp ssetlock /* empty again, try once more */ sgotit: popl _ss_eflags /* save the old task priority */ sgotit2: ret ALIGN_TEXT sbad_slock: /* %eax (current lock) is already on the stack */ pushl %edx pushl _cpuid pushl $sbsl1 call _panic sbsl1: .asciz "rsslock: cpu: %d, addr: 0x%08x, lock: 0x%08x" #endif /* SL_DEBUG */ /* * void ss_unlock(struct simplelock *lkp) */ ENTRY(ss_unlock) movl 4(%esp), %eax /* get the address of the lock */ movl $0, (%eax) /* clear the simple lock */ testl $PSL_I, _ss_eflags jz ss_unlock2 sti ss_unlock2: ret #endif /* * These versions of simple_lock does not contain calls to profiling code. * Thus they can be called from the profiling code. */ /* * void s_lock_np(struct simplelock *lkp) */ NON_GPROF_ENTRY(s_lock_np) movl 4(%esp), %eax /* get the address of the lock */ movl $1, %ecx 1: xchgl %ecx, (%eax) testl %ecx, %ecx jz 3f 2: pause cmpl $0, (%eax) /* wait to empty */ jne 2b /* still set... */ jmp 1b /* empty again, try once more */ 3: NON_GPROF_RET /* * void s_unlock_np(struct simplelock *lkp) */ NON_GPROF_ENTRY(s_unlock_np) movl 4(%esp), %eax /* get the address of the lock */ movl $0, (%eax) NON_GPROF_RET