Inlines spinlocks when not using SPINLOCK_DEBUG
[akaros.git] / kern / src / atomic.c
index f7d9e97..9a49bfc 100644 (file)
@@ -1,9 +1,153 @@
-#include <inc/string.h>
-#include <inc/assert.h>
-#include <inc/error.h>
+#ifdef __SHARC__
+#pragma nosharc
+#endif
 
-#include <kern/atomic.h>
-#include <kern/apic.h>
+#include <arch/arch.h>
+#include <arch/kdebug.h>
+
+#include <bitmask.h>
+#include <atomic.h>
+#include <error.h>
+#include <string.h>
+#include <assert.h>
+#include <hashtable.h>
+#include <smp.h>
+
+static void increase_lock_depth(uint32_t coreid)
+{
+       per_cpu_info[coreid].lock_depth++;
+}
+
+static void decrease_lock_depth(uint32_t coreid)
+{
+       per_cpu_info[coreid].lock_depth--;
+}
+
+#ifdef __CONFIG_SPINLOCK_DEBUG__
+void spin_lock(spinlock_t *lock)
+{
+       uint32_t coreid = core_id();
+       struct per_cpu_info *pcpui = &per_cpu_info[coreid];
+       /* TODO: don't print directly.  If we have a lock on the print path that
+        * fails, we'll recurse and/or deadlock */
+       if (lock->irq_okay) {
+               if (!can_spinwait_irq(pcpui)) {
+                       print_kctx_depths("IRQOK");
+                       panic("Lock %08p tried to spin when it shouldn't\n", lock);
+               }
+       } else {
+               if (!can_spinwait_noirq(pcpui)) {
+                       print_kctx_depths("NOIRQ");
+                       panic("Lock %08p tried to spin when it shouldn't\n", lock);
+               }
+       }
+       __spin_lock(lock);
+       lock->call_site = get_caller_pc();
+       lock->calling_core = coreid;
+       /* TODO consider merging this with __ctx_depth (unused field) */
+       increase_lock_depth(lock->calling_core);
+       __spin_lock(lock);
+       /* Memory barriers are handled by the particular arches */
+}
+
+void spin_unlock(spinlock_t *lock)
+{
+       decrease_lock_depth(lock->calling_core);
+       /* Memory barriers are handled by the particular arches */
+       __spin_unlock(lock);
+}
+#endif /* __CONFIG_SPINLOCK_DEBUG__ */
+
+/* Inits a hashlock. */
+void hashlock_init(struct hashlock *hl, unsigned int nr_entries)
+{
+       hl->nr_entries = nr_entries;
+       /* this is the right way to do it, though memset is faster.  If we ever
+        * find that this is taking a lot of time, we can change it. */
+       for (int i = 0; i < hl->nr_entries; i++) {
+               spinlock_init(&hl->locks[i]);
+       }
+}
+
+void hashlock_init_irqsave(struct hashlock *hl, unsigned int nr_entries)
+{
+       hl->nr_entries = nr_entries;
+       /* this is the right way to do it, though memset is faster.  If we ever
+        * find that this is taking a lot of time, we can change it. */
+       for (int i = 0; i < hl->nr_entries; i++) {
+               spinlock_init_irqsave(&hl->locks[i]);
+       }
+}
+
+/* Helper, gets the specific spinlock for a hl/key combo. */
+static spinlock_t *get_spinlock(struct hashlock *hl, long key)
+{
+       /* using the hashtable's generic hash function */
+       return &hl->locks[__generic_hash((void*)key) % hl->nr_entries];
+}
+
+void hash_lock(struct hashlock *hl, long key)
+{
+       spin_lock(get_spinlock(hl, key));
+}
+
+void hash_unlock(struct hashlock *hl, long key)
+{
+       spin_unlock(get_spinlock(hl, key));
+}
+
+void hash_lock_irqsave(struct hashlock *hl, long key)
+{
+       spin_lock_irqsave(get_spinlock(hl, key));
+}
+
+void hash_unlock_irqsave(struct hashlock *hl, long key)
+{
+       spin_unlock_irqsave(get_spinlock(hl, key));
+}
+
+/* This is the 'post (work) and poke' style of sync.  We make sure the poke
+ * tracker's function runs.  Once this returns, the func either has run or is
+ * currently running (in case someone else is running now).  We won't wait or
+ * spin or anything, and it is safe to call this recursively (deeper in the
+ * call-graph).
+ *
+ * It's up to the caller to somehow post its work.  We'll also pass arg to the
+ * func, ONLY IF the caller is the one to execute it - so there's no guarantee
+ * the func(specific_arg) combo will actually run.  It's more for info
+ * purposes/optimizations/etc.  If no one uses it, I'll get rid of it. */
+void poke(struct poke_tracker *tracker, void *arg)
+{
+       atomic_set(&tracker->need_to_run, TRUE);
+       /* will need to repeatedly do it if someone keeps posting work */
+       do {
+               /* want an wrmb() btw posting work/need_to_run and in_progress.  the
+                * swap provides the HW mb. just need a cmb, which we do in the loop to
+                * cover the iterations (even though i can't imagine the compiler
+                * reordering the check it needed to do for the branch).. */
+               cmb();
+               /* poke / make sure someone does it.  if we get a TRUE (1) back, someone
+                * is already running and will deal with the posted work.  (probably on
+                * their next loop).  if we got a 0 back, we won the race and have the
+                * 'lock'. */
+               if (atomic_swap(&tracker->run_in_progress, TRUE))
+                       return;
+               /* if we're here, then we're the one who needs to run the func. */
+               /* clear the 'need to run', since we're running it now.  new users will
+                * set it again.  this write needs to be wmb()'d after in_progress.  the
+                * swap provided the HW mb(). */
+               cmb();
+               atomic_set(&tracker->need_to_run, FALSE);       /* no internal HW mb */
+               /* run the actual function.  the poke sync makes sure only one caller is
+                * in that func at a time. */
+               assert(tracker->func);
+               tracker->func(arg);
+               wmb();  /* ensure the in_prog write comes after the run_again. */
+               atomic_set(&tracker->run_in_progress, FALSE);   /* no internal HW mb */
+               /* in_prog write must come before run_again read */
+               wrmb();
+       } while (atomic_read(&tracker->need_to_run));   /* while there's more work*/
+}
 
 // Must be called in a pair with waiton_checklist
 int commit_checklist_wait(checklist_t* list, checklist_mask_t* mask)
@@ -15,7 +159,7 @@ int commit_checklist_wait(checklist_t* list, checklist_mask_t* mask)
        // Or, bail out if we can see the list is already in use.  This check is
        // just an optimization before we try to use the list for real.
        if ((checklist_is_locked(list)) || !checklist_is_clear(list))
-               return E_BUSY;
+               return -EBUSY;
 
        // possession of this lock means you can wait on it and set it
        spin_lock_irqsave(&list->lock);
@@ -33,7 +177,7 @@ int commit_checklist_wait(checklist_t* list, checklist_mask_t* mask)
 int commit_checklist_nowait(checklist_t* list, checklist_mask_t* mask)
 {
        int e = 0;
-       if (e = commit_checklist_wait(list, mask))
+       if ((e = commit_checklist_wait(list, mask)))
                return e;
        // give up the lock, since we won't wait for completion
        spin_unlock_irqsave(&list->lock);
@@ -65,7 +209,7 @@ int commit_checklist_nowait(checklist_t* list, checklist_mask_t* mask)
 // Assumed we held the lock if we ever call this
 int waiton_checklist(checklist_t* list)
 {
-       extern uint32_t outstanding_calls;
+       extern atomic_t outstanding_calls;
        // can consider breakout out early, like above, and erroring out
        while (!checklist_is_clear(list))
                cpu_relax();
@@ -85,8 +229,7 @@ int release_checklist(checklist_t* list)
 // peaks in and sees if the list is locked with it's spinlock
 int checklist_is_locked(checklist_t* list)
 {
-       // remember the lock status is the lowest byte of the lock
-       return list->lock & 0xff;
+       return spin_locked(&list->lock);
 }
 
 // no synch guarantees - just looks at the list
@@ -104,13 +247,13 @@ void reset_checklist(checklist_t* list)
 // CPU mask specific - this is how cores report in
 void down_checklist(checklist_t* list)
 {
-       CLR_BITMASK_BIT_ATOMIC(list->mask.bits, lapic_get_id());
+       CLR_BITMASK_BIT_ATOMIC(list->mask.bits, core_id());
 }
 
 /* Barriers */
 void init_barrier(barrier_t* barrier, uint32_t count)
 {
-       barrier->lock = 0;
+       spinlock_init_irqsave(&barrier->lock);
        barrier->init_count = count;
        barrier->current_count = count;
        barrier->ready = 0;
@@ -135,7 +278,7 @@ void waiton_barrier(barrier_t* barrier)
        } else {
                spin_unlock_irqsave(&barrier->lock);
                reset_barrier(barrier);
-               // if we need to wmb(), it'll be here
+               wmb();
                barrier->ready++;
        }
 }