Spinlock checking is disabled by default
[akaros.git] / kern / src / atomic.c
1 #ifdef __SHARC__
2 #pragma nosharc
3 #endif
4
5 #include <arch/arch.h>
6 #include <arch/kdebug.h>
7
8 #include <bitmask.h>
9 #include <atomic.h>
10 #include <error.h>
11 #include <string.h>
12 #include <assert.h>
13 #include <hashtable.h>
14 #include <smp.h>
15 #include <kmalloc.h>
16 #include <kdebug.h>
17
18 static void increase_lock_depth(uint32_t coreid)
19 {
20         per_cpu_info[coreid].lock_depth++;
21 }
22
23 static void decrease_lock_depth(uint32_t coreid)
24 {
25         per_cpu_info[coreid].lock_depth--;
26 }
27
28 #ifdef CONFIG_SPINLOCK_DEBUG
29 void spin_lock(spinlock_t *lock)
30 {
31         uint32_t coreid = core_id_early();
32         struct per_cpu_info *pcpui = &per_cpu_info[coreid];
33         /* Short circuit our lock checking, so we can print or do other things to
34          * announce the failure that require locks.  Also avoids anything else
35          * requiring pcpui initialization. */
36         if (pcpui->__lock_checking_enabled != 1)
37                 goto lock;
38         if (lock->irq_okay) {
39                 if (!can_spinwait_irq(pcpui)) {
40                         pcpui->__lock_checking_enabled--;
41                         print_kctx_depths("IRQOK");
42                         panic("Lock %p tried to spin when it shouldn't\n", lock);
43                         pcpui->__lock_checking_enabled++;
44                 }
45         } else {
46                 if (!can_spinwait_noirq(pcpui)) {
47                         pcpui->__lock_checking_enabled--;
48                         print_kctx_depths("NOIRQ");
49                         panic("Lock %p tried to spin when it shouldn't\n", lock);
50                         pcpui->__lock_checking_enabled++;
51                 }
52         }
53 lock:
54         __spin_lock(lock);
55         lock->call_site = get_caller_pc();
56         lock->calling_core = coreid;
57         /* TODO consider merging this with __ctx_depth (unused field) */
58         increase_lock_depth(lock->calling_core);
59         /* Memory barriers are handled by the particular arches */
60 }
61
62 void spin_unlock(spinlock_t *lock)
63 {
64         decrease_lock_depth(lock->calling_core);
65         /* Memory barriers are handled by the particular arches */
66         __spin_unlock(lock);
67 }
68
69 void spinlock_debug(spinlock_t *lock)
70 {
71         uintptr_t pc = lock->call_site;
72         char *func_name;
73
74         if (!pc) {
75                 printk("Lock %p: never locked\n", lock);
76                 return;
77         }
78         func_name = get_fn_name(pc);
79         printk("Lock %p: last locked at [<%p>] in %s on core %d\n", lock, pc,
80                func_name, lock->calling_core);
81         kfree(func_name);
82 }
83
84 #endif /* CONFIG_SPINLOCK_DEBUG */
85
86 /* Inits a hashlock. */
87 void hashlock_init(struct hashlock *hl, unsigned int nr_entries)
88 {
89         hl->nr_entries = nr_entries;
90         /* this is the right way to do it, though memset is faster.  If we ever
91          * find that this is taking a lot of time, we can change it. */
92         for (int i = 0; i < hl->nr_entries; i++) {
93                 spinlock_init(&hl->locks[i]);
94         }
95 }
96
97 void hashlock_init_irqsave(struct hashlock *hl, unsigned int nr_entries)
98 {
99         hl->nr_entries = nr_entries;
100         /* this is the right way to do it, though memset is faster.  If we ever
101          * find that this is taking a lot of time, we can change it. */
102         for (int i = 0; i < hl->nr_entries; i++) {
103                 spinlock_init_irqsave(&hl->locks[i]);
104         }
105 }
106
107 /* Helper, gets the specific spinlock for a hl/key combo. */
108 static spinlock_t *get_spinlock(struct hashlock *hl, long key)
109 {
110         /* using the hashtable's generic hash function */
111         return &hl->locks[__generic_hash((void*)key) % hl->nr_entries];
112 }
113
114 void hash_lock(struct hashlock *hl, long key)
115 {
116         spin_lock(get_spinlock(hl, key));
117 }
118
119 void hash_unlock(struct hashlock *hl, long key)
120 {
121         spin_unlock(get_spinlock(hl, key));
122 }
123
124 void hash_lock_irqsave(struct hashlock *hl, long key)
125 {
126         spin_lock_irqsave(get_spinlock(hl, key));
127 }
128
129 void hash_unlock_irqsave(struct hashlock *hl, long key)
130 {
131         spin_unlock_irqsave(get_spinlock(hl, key));
132 }
133
134 /* This is the 'post (work) and poke' style of sync.  We make sure the poke
135  * tracker's function runs.  Once this returns, the func either has run or is
136  * currently running (in case someone else is running now).  We won't wait or
137  * spin or anything, and it is safe to call this recursively (deeper in the
138  * call-graph).
139  *
140  * It's up to the caller to somehow post its work.  We'll also pass arg to the
141  * func, ONLY IF the caller is the one to execute it - so there's no guarantee
142  * the func(specific_arg) combo will actually run.  It's more for info
143  * purposes/optimizations/etc.  If no one uses it, I'll get rid of it. */
144 void poke(struct poke_tracker *tracker, void *arg)
145 {
146         atomic_set(&tracker->need_to_run, TRUE);
147         /* will need to repeatedly do it if someone keeps posting work */
148         do {
149                 /* want an wrmb() btw posting work/need_to_run and in_progress.  the
150                  * swap provides the HW mb. just need a cmb, which we do in the loop to
151                  * cover the iterations (even though i can't imagine the compiler
152                  * reordering the check it needed to do for the branch).. */
153                 cmb();
154                 /* poke / make sure someone does it.  if we get a TRUE (1) back, someone
155                  * is already running and will deal with the posted work.  (probably on
156                  * their next loop).  if we got a 0 back, we won the race and have the
157                  * 'lock'. */
158                 if (atomic_swap(&tracker->run_in_progress, TRUE))
159                         return;
160                 /* if we're here, then we're the one who needs to run the func. */
161                 /* clear the 'need to run', since we're running it now.  new users will
162                  * set it again.  this write needs to be wmb()'d after in_progress.  the
163                  * swap provided the HW mb(). */
164                 cmb();
165                 atomic_set(&tracker->need_to_run, FALSE);       /* no internal HW mb */
166                 /* run the actual function.  the poke sync makes sure only one caller is
167                  * in that func at a time. */
168                 assert(tracker->func);
169                 tracker->func(arg);
170                 wmb();  /* ensure the in_prog write comes after the run_again. */
171                 atomic_set(&tracker->run_in_progress, FALSE);   /* no internal HW mb */
172                 /* in_prog write must come before run_again read */
173                 wrmb();
174         } while (atomic_read(&tracker->need_to_run));   /* while there's more work*/
175 }
176
177 // Must be called in a pair with waiton_checklist
178 int commit_checklist_wait(checklist_t* list, checklist_mask_t* mask)
179 {
180         assert(list->mask.size == mask->size);
181         // abort if the list is locked.  this will protect us from trying to commit
182         // and thus spin on a checklist that we are already waiting on.  it is
183         // still possible to not get the lock, but the holder is on another core.
184         // Or, bail out if we can see the list is already in use.  This check is
185         // just an optimization before we try to use the list for real.
186         if ((checklist_is_locked(list)) || !checklist_is_clear(list))
187                 return -EBUSY;
188
189         // possession of this lock means you can wait on it and set it
190         spin_lock_irqsave(&list->lock);
191         // wait til the list is available.  could have some adaptive thing here
192         // where it fails after X tries (like 500), gives up the lock, and returns
193         // an error code
194         while (!checklist_is_clear(list))
195                 cpu_relax();
196
197         // list is ours and clear, set it to the settings of our list
198         COPY_BITMASK(list->mask.bits, mask->bits, mask->size); 
199         return 0;
200 }
201
202 int commit_checklist_nowait(checklist_t* list, checklist_mask_t* mask)
203 {
204         int e = 0;
205         if ((e = commit_checklist_wait(list, mask)))
206                 return e;
207         // give up the lock, since we won't wait for completion
208         spin_unlock_irqsave(&list->lock);
209         return e;
210 }
211 // The deal with the lock:
212 // what if two different actors are waiting on the list, but for different reasons?
213 // part of the problem is we are doing both set and check via the same path
214 //
215 // aside: we made this a lot more difficult than the usual barriers or even 
216 // the RCU grace-period checkers, since we have to worry about this construct
217 // being used by others before we are done with it.
218 //
219 // how about this: if we want to wait on this later, we just don't release the
220 // lock.  if we release it, then we don't care who comes in and grabs and starts
221 // checking the list.  
222 //      - regardless, there are going to be issues with people looking for a free 
223 //      item.  even if they grab the lock, they may end up waiting a while and 
224 //      wantint to bail (like test for a while, give up, move on, etc).  
225 //      - still limited in that only the setter can check, and only one person
226 //      can spinwait / check for completion.  if someone else tries to wait (wanting
227 //      completion), they may miss it if someone else comes in and grabs the lock
228 //      to use it for a new checklist
229 //              - if we had the ability to sleep and get woken up, we could have a 
230 //              queue.  actually, we could do a queue anyway, but they all spin
231 //              and it's the bosses responsibility to *wake* them
232
233 // Must be called after commit_checklist
234 // Assumed we held the lock if we ever call this
235 int waiton_checklist(checklist_t* list)
236 {
237         extern atomic_t outstanding_calls;
238         // can consider breakout out early, like above, and erroring out
239         while (!checklist_is_clear(list))
240                 cpu_relax();
241         spin_unlock_irqsave(&list->lock);
242         // global counter of wrappers either waited on or being contended for.
243         atomic_dec(&outstanding_calls);
244         return 0;
245 }
246
247 // like waiton, but don't bother waiting either
248 int release_checklist(checklist_t* list)
249 {
250         spin_unlock_irqsave(&list->lock);
251         return 0;
252 }
253
254 // peaks in and sees if the list is locked with it's spinlock
255 int checklist_is_locked(checklist_t* list)
256 {
257         return spin_locked(&list->lock);
258 }
259
260 // no synch guarantees - just looks at the list
261 int checklist_is_clear(checklist_t* list)
262 {
263         return BITMASK_IS_CLEAR(list->mask.bits, list->mask.size);
264 }
265
266 // no synch guarantees - just resets the list to empty
267 void reset_checklist(checklist_t* list)
268 {
269         CLR_BITMASK(list->mask.bits, list->mask.size);
270 }
271
272 // CPU mask specific - this is how cores report in
273 void down_checklist(checklist_t* list)
274 {
275         CLR_BITMASK_BIT_ATOMIC(list->mask.bits, core_id());
276 }
277
278 /* Barriers */
279 void init_barrier(barrier_t* barrier, uint32_t count)
280 {
281         spinlock_init_irqsave(&barrier->lock);
282         barrier->init_count = count;
283         barrier->current_count = count;
284         barrier->ready = 0;
285 }
286
287 void reset_barrier(barrier_t* barrier)
288 {
289         barrier->current_count = barrier->init_count;
290 }
291
292 // primitive barrier function.  all cores call this.
293 void waiton_barrier(barrier_t* barrier)
294 {
295         uint8_t local_ready = barrier->ready;
296
297         spin_lock_irqsave(&barrier->lock);
298         barrier->current_count--;
299         if (barrier->current_count) {
300                 spin_unlock_irqsave(&barrier->lock);
301                 while (barrier->ready == local_ready)
302                         cpu_relax();
303         } else {
304                 spin_unlock_irqsave(&barrier->lock);
305                 reset_barrier(barrier);
306                 wmb();
307                 barrier->ready++;
308         }
309 }