-/*
- * Copyright (c) 2011 The Regents of the University of California
+/* Copyright (c) 2013 The Regents of the University of California
* Barret Rhoden <brho@cs.berkeley.edu>
* Kevin Klues <klueska@cs.berkeley.edu>
*
+ * Spinlocks and Spin-PDR locks (preemption detection/recovery)
+ *
* This file is part of Parlib.
*
* Parlib is free software: you can redistribute it and/or modify
* Lesser GNU General Public License for more details.
*
* See COPYING.LESSER for details on the GNU Lesser General Public License.
- * See COPYING for details on the GNU General Public License.
- */
+ * See COPYING for details on the GNU General Public License. */
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
-#include "spinlock.h"
+
+#include <spinlock.h>
+#include <vcore.h>
+#include <uthread.h>
void spinlock_init(spinlock_t *lock)
{
- assert(lock);
- *lock = SPINLOCK_UNLOCKED;
+ lock->lock = 0;
}
-
int spinlock_trylock(spinlock_t *lock)
{
- assert(lock);
- if (*lock == SPINLOCK_LOCKED)
- return EBUSY;
+ if (lock->lock)
+ return EBUSY;
+ return __sync_lock_test_and_set(&lock->lock, EBUSY);
+}
+
+void spinlock_lock(spinlock_t *lock)
+{
+ while (spinlock_trylock(lock))
+ cpu_relax();
+}
- return (int)atomic_cas(lock, (long)SPINLOCK_LOCKED, (long)SPINLOCK_UNLOCKED);
+void spinlock_unlock(spinlock_t *lock)
+{
+ __sync_lock_release(&lock->lock, 0);
}
+/* Two different versions, with and without CAS. Default is with CAS. */
+#ifndef CONFIG_SPINPDR_NO_CAS /* CAS version */
-void spinlock_lock(spinlock_t *lock)
+/* Spin-PRD locks (preemption detection/recovery). Idea is to CAS and put the
+ * lockholder's vcoreid in the lock, and all spinners ensure that vcore runs. */
+void spin_pdr_init(struct spin_pdr_lock *pdr_lock)
{
- assert(lock);
- while (spinlock_trylock(lock) != (int)SPINLOCK_UNLOCKED)
- cpu_relax();
+ pdr_lock->lock = SPINPDR_UNLOCKED;
}
+/* Internal version of the locking func, doesn't care if notifs are disabled */
+void __spin_pdr_lock(struct spin_pdr_lock *pdr_lock)
+{
+ uint32_t vcoreid = vcore_id();
+ uint32_t lock_val;
+ do {
+ while ((lock_val = pdr_lock->lock) != SPINPDR_UNLOCKED) {
+ ensure_vcore_runs(lock_val);
+ cmb();
+ }
+ } while (!atomic_cas_u32(&pdr_lock->lock, lock_val, vcoreid));
+ cmb(); /* just need a cmb, the CAS handles the CPU wmb/wrmb() */
+}
-void spinlock_unlock(spinlock_t *lock)
+void __spin_pdr_unlock(struct spin_pdr_lock *pdr_lock)
+{
+ /* could make an arch-dependent 'release barrier' out of these */
+ wmb(); /* Need to prevent the compiler from reordering older stores. */
+ rwmb(); /* And no old reads passing either. x86 makes both mbs a cmb() */
+ pdr_lock->lock = SPINPDR_UNLOCKED;
+}
+
+#else /* NON-CAS version */
+
+/* Using regular spinlocks, with SPINPDR_VCOREID_UNKNOWN (-1) meaning 'no
+ * lockholder advertised yet'. There are two spots where the lockholder still
+ * holds the lock but hasn't advertised its vcoreid, and in those cases we
+ * ensure all vcores aren't preempted (linear scan). */
+void spin_pdr_init(struct spin_pdr_lock *pdr_lock)
+{
+ spinlock_init(&pdr_lock->spinlock);
+ pdr_lock->lockholder = SPINPDR_VCOREID_UNKNOWN;
+}
+
+void __spin_pdr_lock(struct spin_pdr_lock *pdr_lock)
+{
+ uint32_t vcoreid = vcore_id();
+ uint32_t ensure_tgt;
+ while (spinlock_trylock(&pdr_lock->spinlock)) {
+ ensure_tgt = pdr_lock->lockholder;
+ /* ensure will make sure *every* vcore runs if you pass it your self. */
+ if (ensure_tgt == SPINPDR_VCOREID_UNKNOWN)
+ ensure_tgt = vcoreid;
+ ensure_vcore_runs(ensure_tgt);
+ cpu_relax();
+ }
+ pdr_lock->lockholder = vcoreid;
+}
+
+void __spin_pdr_unlock(struct spin_pdr_lock *pdr_lock)
+{
+ pdr_lock->lockholder = SPINPDR_VCOREID_UNKNOWN;
+ spinlock_unlock(&pdr_lock->spinlock);
+}
+
+#endif /* CONFIG_SPINPDR_NO_CAS */
+
+void spin_pdr_lock(struct spin_pdr_lock *pdr_lock)
+{
+ /* Disable notifs, if we're an _M uthread */
+ uth_disable_notifs();
+ __spin_pdr_lock(pdr_lock);
+}
+
+void spin_pdr_unlock(struct spin_pdr_lock *pdr_lock)
{
- assert(lock);
- *lock = SPINLOCK_UNLOCKED;
+ __spin_pdr_unlock(pdr_lock);
+ /* Enable notifs, if we're an _M uthread */
+ uth_enable_notifs();
}