blob: 194d2ef1abdf8d881dea69c4994a765663cbb1df [file] [log] [blame]
#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>
#include <kmalloc.h>
#include <kdebug.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
/* Put locks you want to ignore here. */
static uintptr_t blacklist_locks[] = {
//0xffffffffc03bd000,
};
/* Could do this on the output side, though noisly locks will crowd us out */
static bool can_trace(spinlock_t *lock)
{
for (int i = 0; i < ARRAY_SIZE(blacklist_locks); i++) {
if (blacklist_locks[i] == (uintptr_t)lock)
return FALSE;
}
return TRUE;
}
/* spinlock and trylock call this after locking */
static __always_inline void post_lock(spinlock_t *lock, uint32_t coreid)
{
struct per_cpu_info *pcpui = &per_cpu_info[coreid];
if ((pcpui->__lock_checking_enabled == 1) && can_trace(lock))
pcpui_trace_locks(pcpui, 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);
}
void spin_lock(spinlock_t *lock)
{
uint32_t coreid = core_id_early();
struct per_cpu_info *pcpui = &per_cpu_info[coreid];
/* Short circuit our lock checking, so we can print or do other things
* to announce the failure that require locks. Also avoids anything
* else requiring pcpui initialization. */
if (pcpui->__lock_checking_enabled != 1)
goto lock;
if (lock->irq_okay) {
if (!can_spinwait_irq(pcpui)) {
pcpui->__lock_checking_enabled--;
print_kctx_depths("IRQOK");
panic("Lock %p tried to spin when it shouldn't\n", lock);
pcpui->__lock_checking_enabled++;
}
} else {
if (!can_spinwait_noirq(pcpui)) {
pcpui->__lock_checking_enabled--;
print_kctx_depths("NOIRQ");
panic("Lock %p tried to spin when it shouldn't\n",
lock);
pcpui->__lock_checking_enabled++;
}
}
lock:
__spin_lock(lock);
/* Memory barriers are handled by the particular arches */
post_lock(lock, coreid);
}
/* Trylock doesn't check for irq/noirq, in case we want to try and lock a
* non-irqsave lock from irq context. */
bool spin_trylock(spinlock_t *lock)
{
uint32_t coreid = core_id_early();
bool ret = __spin_trylock(lock);
if (ret)
post_lock(lock, coreid);
return ret;
}
void spin_unlock(spinlock_t *lock)
{
decrease_lock_depth(lock->calling_core);
/* Memory barriers are handled by the particular arches */
assert(spin_locked(lock));
__spin_unlock(lock);
}
void spinlock_debug(spinlock_t *lock)
{
uintptr_t pc = lock->call_site;
if (!pc) {
printk("Lock %p: never locked\n", lock);
return;
}
printk("Lock %p: currently %slocked. Last locked at [<%p>] in %s on core %d\n",
lock, spin_locked(lock) ? "" : "un", pc, get_fn_name(pc),
lock->calling_core);
}
#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();
/* no internal HW mb */
atomic_set(&tracker->need_to_run, FALSE);
/* 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);
/* ensure the in_prog write comes after the run_again. */
wmb();
/* no internal HW mb */
atomic_set(&tracker->run_in_progress, FALSE);
/* in_prog write must come before run_again read */
wrmb();
} while (atomic_read(&tracker->need_to_run));
}
// Must be called in a pair with waiton_checklist
int commit_checklist_wait(checklist_t* list, checklist_mask_t* mask)
{
assert(list->mask.size == mask->size);
// abort if the list is locked. this will protect us from trying to
// commit and thus spin on a checklist that we are already waiting on.
// it is still possible to not get the lock, but the holder is on
// another core. 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 -EBUSY;
// possession of this lock means you can wait on it and set it
spin_lock_irqsave(&list->lock);
// wait til the list is available. could have some adaptive thing here
// where it fails after X tries (like 500), gives up the lock, and
// returns an error code
while (!checklist_is_clear(list))
cpu_relax();
// list is ours and clear, set it to the settings of our list
COPY_BITMASK(list->mask.bits, mask->bits, mask->size);
return 0;
}
int commit_checklist_nowait(checklist_t* list, checklist_mask_t* mask)
{
int e = 0;
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);
return e;
}
// The deal with the lock:
// what if two different actors are waiting on the list, but for different
// reasons?
// part of the problem is we are doing both set and check via the same path
//
// aside: we made this a lot more difficult than the usual barriers or even
// the RCU grace-period checkers, since we have to worry about this construct
// being used by others before we are done with it.
//
// how about this: if we want to wait on this later, we just don't release the
// lock. if we release it, then we don't care who comes in and grabs and starts
// checking the list.
// - regardless, there are going to be issues with people looking for a free
// item. even if they grab the lock, they may end up waiting a while and
// wantint to bail (like test for a while, give up, move on, etc).
// - still limited in that only the setter can check, and only one person
// can spinwait / check for completion. if someone else tries to wait (wanting
// completion), they may miss it if someone else comes in and grabs the lock
// to use it for a new checklist
// - if we had the ability to sleep and get woken up, we could have a
// queue. actually, we could do a queue anyway, but they all spin
// and it's the bosses responsibility to *wake* them
// Must be called after commit_checklist
// Assumed we held the lock if we ever call this
int waiton_checklist(checklist_t* list)
{
extern atomic_t outstanding_calls;
// can consider breakout out early, like above, and erroring out
while (!checklist_is_clear(list))
cpu_relax();
spin_unlock_irqsave(&list->lock);
// global counter of wrappers either waited on or being contended for.
atomic_dec(&outstanding_calls);
return 0;
}
// like waiton, but don't bother waiting either
int release_checklist(checklist_t* list)
{
spin_unlock_irqsave(&list->lock);
return 0;
}
// peaks in and sees if the list is locked with it's spinlock
int checklist_is_locked(checklist_t* list)
{
return spin_locked(&list->lock);
}
// no synch guarantees - just looks at the list
int checklist_is_clear(checklist_t* list)
{
return BITMASK_IS_CLEAR(list->mask.bits, list->mask.size);
}
// no synch guarantees - just looks at the list
int checklist_is_full(checklist_t* list)
{
return BITMASK_IS_FULL(list->mask.bits, list->mask.size);
}
// no synch guarantees - just resets the list to empty
void reset_checklist(checklist_t* list)
{
CLR_BITMASK(list->mask.bits, list->mask.size);
}
// CPU mask specific - this is how cores report in
void down_checklist(checklist_t* list)
{
CLR_BITMASK_BIT_ATOMIC(list->mask.bits, core_id());
}
/* Barriers */
void init_barrier(barrier_t* barrier, uint32_t count)
{
spinlock_init_irqsave(&barrier->lock);
barrier->init_count = count;
barrier->current_count = count;
barrier->ready = 0;
}
void reset_barrier(barrier_t* barrier)
{
barrier->current_count = barrier->init_count;
}
// primitive barrier function. all cores call this.
void waiton_barrier(barrier_t* barrier)
{
uint8_t local_ready = barrier->ready;
spin_lock_irqsave(&barrier->lock);
barrier->current_count--;
if (barrier->current_count) {
spin_unlock_irqsave(&barrier->lock);
while (barrier->ready == local_ready)
cpu_relax();
} else {
spin_unlock_irqsave(&barrier->lock);
reset_barrier(barrier);
wmb();
barrier->ready++;
}
}