kernel - Introduce hard code sections, simplify critical sections & mplocks
[dragonfly.git] / sys / kern / kern_mplock.c
1 /*
2  * Copyright (c) 2009 The DragonFly Project.  All rights reserved.
3  *
4  * This code is derived from software contributed to The DragonFly Project
5  * by Matthew Dillon <dillon@backplane.com>
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in
15  *    the documentation and/or other materials provided with the
16  *    distribution.
17  * 3. Neither the name of The DragonFly Project nor the names of its
18  *    contributors may be used to endorse or promote products derived
19  *    from this software without specific, prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
25  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
31  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34
35 /*
36  * Helper functions for MP lock acquisition and release.
37  */
38
39 #include <sys/param.h>
40 #include <sys/systm.h>
41 #include <sys/kernel.h>
42 #include <sys/proc.h>
43 #include <sys/rtprio.h>
44 #include <sys/queue.h>
45 #include <sys/sysctl.h>
46 #include <sys/kthread.h>
47 #include <machine/cpu.h>
48 #include <sys/lock.h>
49 #include <sys/caps.h>
50 #include <sys/spinlock.h>
51 #include <sys/ktr.h>
52
53 #include <sys/thread2.h>
54 #include <sys/mplock2.h>
55 #include <sys/spinlock2.h>
56
57 #ifdef SMP
58 static int chain_mplock = 0;
59 static int bgl_yield = 10;
60 static __int64_t mplock_contention_count = 0;
61
62 SYSCTL_INT(_lwkt, OID_AUTO, chain_mplock, CTLFLAG_RW, &chain_mplock, 0, "");
63 SYSCTL_INT(_lwkt, OID_AUTO, bgl_yield_delay, CTLFLAG_RW, &bgl_yield, 0, "");
64 SYSCTL_QUAD(_lwkt, OID_AUTO, mplock_contention_count, CTLFLAG_RW,
65         &mplock_contention_count, 0, "spinning due to MPLOCK contention");
66
67 /*
68  * Kernel Trace
69  */
70 #if !defined(KTR_GIANT_CONTENTION)
71 #define KTR_GIANT_CONTENTION    KTR_ALL
72 #endif
73
74 KTR_INFO_MASTER(giant);
75 KTR_INFO(KTR_GIANT_CONTENTION, giant, beg, 0,
76         "thread=%p held %s:%-5d  want %s:%-5d",
77          sizeof(void *) * 3 + sizeof(int) * 2);
78 KTR_INFO(KTR_GIANT_CONTENTION, giant, end, 1,
79         "thread=%p held %s:%-5d  want %s:%-5d",
80          sizeof(void *) * 3 + sizeof(int) * 2);
81
82 #define loggiant(name)                                          \
83         KTR_LOG(giant_ ## name, curthread,                      \
84                 mp_lock_holder_file, mp_lock_holder_line,       \
85                 file, line)
86
87 int     mp_lock;
88 int     cpu_contention_mask;
89 const char *mp_lock_holder_file;        /* debugging */
90 int     mp_lock_holder_line;            /* debugging */
91
92 /*
93  * Sets up the initial MP lock state near the start of the kernel boot
94  */
95 void
96 cpu_get_initial_mplock(void)
97 {
98         mp_lock = 0;    /* cpu 0 */
99         curthread->td_mpcount = 1;
100 }
101
102 /*
103  * This code is called from the get_mplock() inline when the mplock
104  * is not already held.
105  */
106 void
107 _get_mplock_predisposed(const char *file, int line)
108 {
109         globaldata_t gd = mycpu;
110
111         if (gd->gd_intr_nesting_level) {
112                 panic("Attempt to acquire mplock not already held "
113                       "in hard section, ipi or interrupt %s:%d",
114                       file, line);
115         }
116         if (atomic_cmpset_int(&mp_lock, -1, gd->gd_cpuid) == 0)
117                 _get_mplock_contested(file, line);
118 #ifdef INVARIANTS
119         mp_lock_holder_file = file;
120         mp_lock_holder_line = line;
121 #endif
122 }
123
124 /*
125  * Called when the MP lock could not be trvially acquired.  The caller
126  * has already bumped td_mpcount.
127  */
128 void
129 _get_mplock_contested(const char *file, int line)
130 {
131         globaldata_t gd = mycpu;
132         int ov;
133         int nv;
134         const void **stkframe = (const void **)&file;
135
136         ++mplock_contention_count;
137         for (;;) {
138                 ov = mp_lock;
139                 nv = gd->gd_cpuid;
140                 if (ov == gd->gd_cpuid)
141                         break;
142                 if (ov == -1) {
143                         if (atomic_cmpset_int(&mp_lock, ov, gd->gd_cpuid))
144                                 break;
145                 } else {
146                         gd->gd_curthread->td_mplock_stallpc = stkframe[-1];
147                         loggiant(beg);
148                         lwkt_switch();
149                         loggiant(end);
150                         KKASSERT(gd->gd_cpuid == mp_lock);
151                         break;
152                 }
153         }
154 }
155
156 /*
157  * Called if td_mpcount went negative or if td_mpcount is 0 and we were
158  * unable to release the MP lock.  Handles sanity checks and conflicts.
159  *
160  * It is possible for the inline release to have raced an interrupt which
161  * get/rel'd the MP lock, causing the inline's cmpset to fail.  If this
162  * case occurs mp_lock will either already be in a released state or it
163  * will have already been acquired by another cpu.
164  */
165 void
166 _rel_mplock_contested(void)
167 {
168         globaldata_t gd = mycpu;
169         int ov;
170
171         KKASSERT(gd->gd_curthread->td_mpcount >= 0);
172         for (;;) {
173                 ov = mp_lock;
174                 if (ov != gd->gd_cpuid)
175                         break;
176                 if (atomic_cmpset_int(&mp_lock, ov, -1))
177                         break;
178         }
179 }
180
181 /*
182  * Called when try_mplock() fails.
183  *
184  * The inline bumped td_mpcount so we have to undo it.
185  *
186  * It is possible to race an interrupt which acquired and released the
187  * MP lock.  When combined with the td_mpcount decrement we do the MP lock
188  * can wind up in any state and possibly not even owned by us.
189  *
190  * It is also possible for this function to be called even if td_mpcount > 1
191  * if someone bumped it and raced an interrupt which then called try_mpock().
192  */
193 void
194 _try_mplock_contested(const char *file, int line)
195 {
196         globaldata_t gd = mycpu;
197         thread_t td = gd->gd_curthread;
198         int ov;
199
200         --td->td_mpcount;
201         KKASSERT(td->td_mpcount >= 0);
202         ++mplock_contention_count;
203
204         for (;;) {
205                 ov = mp_lock;
206                 if (ov != gd->gd_cpuid)
207                         break;
208                 if (atomic_cmpset_int(&mp_lock, ov, -1))
209                         break;
210         }
211 }
212
213 /*
214  * Called when cpu_try_mplock() fails.
215  *
216  * The inline did not touch td_mpcount so we do not either.
217  */
218 void
219 _cpu_try_mplock_contested(const char *file, int line)
220 {
221         ++mplock_contention_count;
222 }
223
224 /*
225  * Temporarily yield the MP lock.  This is part of lwkt_user_yield()
226  * which is kinda hackish.
227  */
228 void
229 yield_mplock(thread_t td)
230 {
231         int savecnt;
232
233         savecnt = td->td_mpcount;
234         td->td_mpcount = 1;
235         rel_mplock();
236         DELAY(bgl_yield);
237         get_mplock();
238         td->td_mpcount = savecnt;
239 }
240
241 #if 0
242
243 /*
244  * The rel_mplock() code will call this function after releasing the
245  * last reference on the MP lock if cpu_contention_mask is non-zero.
246  *
247  * We then chain an IPI to a single other cpu potentially needing the
248  * lock.  This is a bit heuristical and we can wind up with IPIs flying
249  * all over the place.
250  */
251 static void lwkt_mp_lock_uncontested_remote(void *arg __unused);
252
253 void
254 lwkt_mp_lock_uncontested(void)
255 {
256     globaldata_t gd;
257     globaldata_t dgd;
258     cpumask_t mask;
259     cpumask_t tmpmask;
260     int cpuid;
261
262     if (chain_mplock) {
263         gd = mycpu;
264         clr_mplock_contention_mask(gd);
265         mask = cpu_contention_mask;
266         tmpmask = ~((1 << gd->gd_cpuid) - 1);
267
268         if (mask) {
269             if (mask & tmpmask)
270                     cpuid = bsfl(mask & tmpmask);
271             else
272                     cpuid = bsfl(mask);
273             atomic_clear_int(&cpu_contention_mask, 1 << cpuid);
274             dgd = globaldata_find(cpuid);
275             lwkt_send_ipiq(dgd, lwkt_mp_lock_uncontested_remote, NULL);
276         }
277     }
278 }
279
280 /*
281  * The idea is for this IPI to interrupt a potentially lower priority
282  * thread, such as a user thread, to allow the scheduler to reschedule
283  * a higher priority kernel thread that needs the MP lock.
284  *
285  * For now we set the LWKT reschedule flag which generates an AST in
286  * doreti, though theoretically it is also possible to possibly preempt
287  * here if the underlying thread was operating in user mode.  Nah.
288  */
289 static void
290 lwkt_mp_lock_uncontested_remote(void *arg __unused)
291 {
292         need_lwkt_resched();
293 }
294
295 #endif
296
297 #endif  /* SMP */