2 * ----------------------------------------------------------------------------
3 * "THE BEER-WARE LICENSE" (Revision 42):
4 * <phk@FreeBSD.org> wrote this file. As long as you retain this notice you
5 * can do whatever you want with this stuff. If we meet some day, and you think
6 * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
7 * ----------------------------------------------------------------------------
9 * $FreeBSD: src/sys/i386/i386/mplock.s,v 1.29.2.2 2000/05/16 06:58:06 dillon Exp $
11 * Functions for locking between CPUs in a SMP system.
13 * This is an "exclusive counting semaphore". This means that it can be
14 * free (0xffffffff) or be owned by a CPU (0xXXYYYYYY where XX is CPU-id
15 * and YYYYYY is the count).
17 * Contrary to most implementations around, this one is entirely atomic:
18 * The attempt to seize/release the semaphore and the increment/decrement
19 * is done in one atomic operation. This way we are safe from all kinds
20 * of weird reentrancy situations.
23 #include <machine/asmacros.h>
24 #include <machine/smptests.h> /** GRAB_LOPRIO */
25 #include <machine/apic.h>
31 /* we assumme that the 'reserved bits' can be written with zeros */
35 #error HEADS UP: this code needs work
37 * The APIC doc says that reserved bits must be written with whatever
38 * value they currently contain, ie you should: read, modify, write,
39 * instead of just writing new values to the TPR register. Current
40 * silicon seems happy with just writing. If the behaviour of the
41 * silicon changes, all code that access the lapic_tpr must be modified.
42 * The last version to contain such code was:
43 * Id: mplock.s,v 1.17 1997/08/10 20:59:07 fsmp Exp
46 #endif /* CHEAP_TPR */
50 * Claim LOWest PRIOrity, ie. attempt to grab ALL INTerrupts.
53 /* after 1st acquire of lock we grab all hardware INTs */
54 #define GRAB_HWI movl $ALLHWI_LEVEL, lapic_tpr
56 /* after last release of lock give up LOW PRIO (ie, arbitrate INTerrupts) */
57 #define ARB_HWI movl $LOPRIO_LEVEL, lapic_tpr /* CHEAP_TPR */
59 #else /* GRAB_LOPRIO */
61 #define GRAB_HWI /* nop */
62 #define ARB_HWI /* nop */
64 #endif /* GRAB_LOPRIO */
71 /***********************************************************************
72 * void MPgetlock_edx(unsigned int *lock : %edx)
73 * ----------------------------------
74 * Destroys %eax, %ecx. %edx must hold lock argument.
76 * Grabs hardware interrupts on first aquire.
78 * NOTE: Serialization is not required if we already hold the lock, since
79 * we already hold the lock, nor do we need a locked instruction if we
80 * already hold the lock.
83 NON_GPROF_ENTRY(MPgetlock_edx)
85 movl (%edx), %eax /* Get current contents of lock */
88 cmpl _cpu_lockid, %ecx /* Do we already own the lock? */
90 incl %eax /* yes, just bump the count */
91 movl %eax, (%edx) /* serialization not required */
94 movl $FREE_LOCK, %eax /* lock must be free */
95 movl _cpu_lockid, %ecx
98 cmpxchg %ecx, (%edx) /* attempt to replace %eax<->%ecx */
104 #endif /* GLPROFILE */
105 GRAB_HWI /* 1st acquire, grab hw INTs */
113 /***********************************************************************
114 * int MPtrylock(unsigned int *lock)
115 * ---------------------------------
116 * Destroys %eax, %ecx and %edx.
117 * Returns 1 if lock was successfull
120 NON_GPROF_ENTRY(MPtrylock)
121 movl 4(%esp), %edx /* Get the address of the lock */
123 movl $FREE_LOCK, %eax /* Assume it's free */
124 movl _cpu_lockid, %ecx /* - get pre-shifted logical cpu id */
125 incl %ecx /* - new count is one */
127 cmpxchg %ecx, (%edx) /* - try it atomically */
128 jne 1f /* ...do not collect $200 */
131 #endif /* GLPROFILE */
132 GRAB_HWI /* 1st acquire, grab hw INTs */
136 movl (%edx), %eax /* Try to see if we have it already */
137 andl $COUNT_FIELD, %eax /* - get count */
138 movl _cpu_lockid, %ecx /* - get pre-shifted logical cpu id */
139 orl %ecx, %eax /* - combine them */
141 incl %ecx /* - new count is one more */
143 cmpxchg %ecx, (%edx) /* - try it atomically */
147 #endif /* GLPROFILE */
153 #endif /* GLPROFILE */
158 /***********************************************************************
159 * void MPrellock_edx(unsigned int *lock : %edx)
160 * ----------------------------------
161 * Destroys %ecx, argument must be in %edx
163 * SERIALIZATION NOTE!
165 * After a lot of arguing, it turns out that there is no problem with
166 * not having a synchronizing instruction in the MP unlock code. There
167 * are two things to keep in mind: First, Intel guarentees that writes
168 * are ordered amoungst themselves. Second, the P6 is allowed to reorder
169 * reads around writes. Third, the P6 maintains cache consistency (snoops
170 * the bus). The second is not an issue since the one read we do is the
171 * basis for the conditional which determines whether the write will be
174 * Therefore, no synchronizing instruction is required on unlock. There are
175 * three performance cases: First, if a single cpu is getting and releasing
176 * the lock the removal of the synchronizing instruction saves approx
177 * 200 nS (testing w/ duel cpu PIII 450). Second, if one cpu is contending
178 * for the lock while the other holds it, the removal of the synchronizing
179 * instruction results in a 700nS LOSS in performance. Third, if two cpu's
180 * are switching off ownership of the MP lock but not contending for it (the
181 * most common case), this results in a 400nS IMPROVEMENT in performance.
183 * Since our goal is to reduce lock contention in the first place, we have
184 * decided to remove the synchronizing instruction from the unlock code.
187 NON_GPROF_ENTRY(MPrellock_edx)
188 movl (%edx), %ecx /* - get the value */
189 decl %ecx /* - new count is one less */
190 testl $COUNT_FIELD, %ecx /* - Unless it's zero... */
192 ARB_HWI /* last release, arbitrate hw INTs */
193 movl $FREE_LOCK, %ecx /* - In which case we release it */
196 addl $0,0(%esp) /* see note above */
202 /***********************************************************************
205 * All registers preserved
207 * Stack (after call to _MPgetlock):
213 * Requirements: Interrupts should be enabled on call so we can take
214 * IPI's and FAST INTs while we are waiting for the lock
215 * (else the system may not be able to halt).
217 * XXX there are still places where get_mplock() is called
218 * with interrupts disabled, so we have to temporarily reenable
221 * Side effects: The current cpu will be given ownership of the
222 * hardware interrupts when it first aquires the lock.
224 * Costs: Initial aquisition requires the use of a costly locked
225 * instruction, but recursive aquisition is cheap. Release
229 NON_GPROF_ENTRY(get_mplock)
235 testl $(1<<9), (%esp)
251 * Special version of get_mplock that is used during bootstrap when we can't
252 * yet enable interrupts of any sort since the APIC isn't online yet. We
253 * do an endrun around MPgetlock_edx to avoid enabling interrupts.
255 * XXX FIXME.. - APIC should be online from the start to simplify IPI's.
257 NON_GPROF_ENTRY(boot_get_mplock)
279 /***********************************************************************
282 * reg %eax == 1 if success
285 NON_GPROF_ENTRY(try_mplock)
295 /***********************************************************************
298 * All registers preserved
301 NON_GPROF_ENTRY(rel_mplock)
312 /***********************************************************************
316 .p2align 2 /* xx_lock aligned on int boundary */
341 .asciz "lock hits: 0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x\n"
342 #endif /* GLPROFILE */