blob: a6c261ae956a68483effc0fb984b24f33e34050c [file] [log] [blame]
/* Copyright (c) 2009-2011 The Regents of the University of California
* Barret Rhoden <brho@cs.berkeley.edu>
* See LICENSE for details.
*
* Kernel atomics and locking functions.
*
* The extern inline declarations are arch-dependent functions. We do this
* so that each arch can either static inline or just have a regular function,
* whichever is appropriate. The actual implementation usually will be in
* arch/atomic.h (for inlines).
*
* The static inlines are defined farther down in the file (as always). */
#pragma once
#include <ros/common.h>
#include <ros/atomic.h>
#include <arch/membar.h>
#include <arch/mmu.h>
#include <arch/arch.h>
#include <assert.h>
/* Atomics */
extern inline void atomic_init(atomic_t *number, long val);
extern inline long atomic_read(atomic_t *number);
extern inline void atomic_set(atomic_t *number, long val);
extern inline void atomic_add(atomic_t *number, long val);
extern inline void atomic_inc(atomic_t *number);
extern inline void atomic_dec(atomic_t *number);
extern inline long atomic_fetch_and_add(atomic_t *number, long val);
extern inline void atomic_and(atomic_t *number, long mask);
extern inline void atomic_or(atomic_t *number, long mask);
extern inline long atomic_swap(atomic_t *addr, long val);
extern inline bool atomic_cas(atomic_t *addr, long exp_val, long new_val);
extern inline bool atomic_cas_ptr(void **addr, void *exp_val, void *new_val);
extern inline bool atomic_cas_u32(uint32_t *addr, uint32_t exp_val,
uint32_t new_val);
extern inline bool atomic_add_not_zero(atomic_t *number, long val);
extern inline bool atomic_sub_and_test(atomic_t *number, long val);
/* Spin locks */
struct spinlock {
volatile uint32_t rlock;
#ifdef CONFIG_SPINLOCK_DEBUG
uintptr_t call_site;
uint32_t calling_core;
bool irq_okay;
#endif
};
typedef struct spinlock spinlock_t;
#define SPINLOCK_INITIALIZER {0}
#ifdef CONFIG_SPINLOCK_DEBUG
#define SPINLOCK_INITIALIZER_IRQSAVE {0, .irq_okay = TRUE}
#else
#define SPINLOCK_INITIALIZER_IRQSAVE SPINLOCK_INITIALIZER
#endif
/* Arch dependent helpers/funcs: */
extern inline void __spinlock_init(spinlock_t *lock);
extern inline bool spin_locked(spinlock_t *lock);
extern inline void __spin_lock(spinlock_t *lock);
extern inline void __spin_unlock(spinlock_t *lock);
/* So we can inline a __spin_lock if we want. Even though we don't need this
* if we're debugging, its helpful to keep the include at the same place for
* all builds. */
#include <arch/atomic.h>
#ifdef CONFIG_SPINLOCK_DEBUG
/* Arch indep, in k/s/atomic.c */
void spin_lock(spinlock_t *lock);
bool spin_trylock(spinlock_t *lock);
void spin_unlock(spinlock_t *lock);
void spinlock_debug(spinlock_t *lock);
#else
/* Just inline the arch-specific __ versions */
static inline void spin_lock(spinlock_t *lock)
{
__spin_lock(lock);
}
static inline bool spin_trylock(spinlock_t *lock)
{
return __spin_trylock(lock);
}
static inline void spin_unlock(spinlock_t *lock)
{
__spin_unlock(lock);
}
static inline void spinlock_debug(spinlock_t *lock)
{
}
#endif /* CONFIG_SPINLOCK_DEBUG */
/* Inlines, defined below */
static inline void spinlock_init(spinlock_t *lock);
static inline void spinlock_init_irqsave(spinlock_t *lock);
static inline void spin_lock_irqsave(spinlock_t *lock);
static inline bool spin_trylock_irqsave(spinlock_t *lock);
static inline void spin_unlock_irqsave(spinlock_t *lock);
static inline bool spin_lock_irq_enabled(spinlock_t *lock);
/* Hash locks (array of spinlocks). Most all users will want the default one,
* so point your pointer to one of them, though you could always kmalloc a
* bigger one. In the future, they might be growable, etc, which init code may
* care about. */
struct hashlock {
unsigned int nr_entries;
struct spinlock locks[];
};
#define HASHLOCK_DEFAULT_SZ 53 /* nice prime, might be a bit large */
struct small_hashlock {
unsigned int nr_entries;
struct spinlock locks[HASHLOCK_DEFAULT_SZ];
};
void hashlock_init(struct hashlock *hl, unsigned int nr_entries);
void hashlock_init_irqsave(struct hashlock *hl, unsigned int nr_entries);
void hash_lock(struct hashlock *hl, long key);
void hash_unlock(struct hashlock *hl, long key);
void hash_lock_irqsave(struct hashlock *hl, long key);
void hash_unlock_irqsave(struct hashlock *hl, long key);
/* Seq locks */
/* An example seq lock, built from the counter. I don't particularly like this,
* since it forces you to use a specific locking type. */
typedef struct seq_lock {
spinlock_t w_lock;
seq_ctr_t r_ctr;
} seqlock_t;
static inline void __seq_start_write(seq_ctr_t *seq_ctr);
static inline void __seq_end_write(seq_ctr_t *seq_ctr);
static inline void write_seqlock(seqlock_t *lock);
static inline void write_sequnlock(seqlock_t *lock);
static inline seq_ctr_t read_seqbegin(seqlock_t *lock);
static inline bool read_seqretry(seqlock_t *lock, seq_ctr_t ctr);
/* Post work and poke synchronization. This is a wait-free way to make sure
* some code is run, usually by the calling core, but potentially by any core.
* Under contention, everyone just posts work, and one core will carry out the
* work. Callers post work (the meaning of which is particular to their
* subsystem), then call this function. The function is not run concurrently
* with itself.
*
* In the future, this may send RKMs to LL cores to ensure the work is done
* somewhere, but not necessarily on the calling core. Will reserve 'flags'
* for that. */
struct poke_tracker {
atomic_t need_to_run;
atomic_t run_in_progress;
void (*func)(void *);
};
void poke(struct poke_tracker *tracker, void *arg);
static inline void poke_init(struct poke_tracker *tracker, void (*func)(void*))
{
tracker->need_to_run = 0;
tracker->run_in_progress = 0;
tracker->func = func;
}
#define POKE_INITIALIZER(f) {.func = f}
/* Arch-specific implementations / declarations go here */
#include <arch/atomic.h>
#define MAX_SPINS 1000000000
/* Will spin for a little while, but not deadlock if it never happens */
#define spin_on(x) \
for (int i = 0; (x); i++) { \
cpu_relax(); \
if (i == MAX_SPINS) { \
printk("Probably timed out/failed.\n"); \
break; \
} \
}
/*********************** Checklist stuff **********************/
typedef struct checklist_mask {
// only need an uint8_t, but we need the bits[] to be word aligned
uint32_t size;
volatile uint8_t bits[MAX_NUM_CORES];
} checklist_mask_t;
// mask contains an unspecified array, so it needs to be at the bottom
struct checklist {
spinlock_t lock;
checklist_mask_t mask;
// eagle-eyed readers may know why this might have been needed.
// 2009-09-04
//volatile uint8_t (COUNT(BYTES_FOR_BITMASK(size)) bits)[];
};
typedef struct checklist checklist_t;
#define ZEROS_ARRAY(size) {[0 ... ((size)-1)] 0}
#define DEFAULT_CHECKLIST_MASK(sz) {(sz), ZEROS_ARRAY(BYTES_FOR_BITMASK(sz))}
#define DEFAULT_CHECKLIST(sz) {SPINLOCK_INITIALIZER_IRQSAVE, \
DEFAULT_CHECKLIST_MASK(sz)}
#define INIT_CHECKLIST(nm, sz) \
checklist_t nm = DEFAULT_CHECKLIST(sz);
#define INIT_CHECKLIST_MASK(nm, sz) \
checklist_mask_t nm = DEFAULT_CHECKLIST_MASK(sz);
int commit_checklist_wait(checklist_t* list, checklist_mask_t* mask);
int commit_checklist_nowait(checklist_t* list, checklist_mask_t* mask);
int waiton_checklist(checklist_t* list);
int release_checklist(checklist_t* list);
int checklist_is_locked(checklist_t* list);
int checklist_is_clear(checklist_t* list);
int checklist_is_full(checklist_t* list);
void reset_checklist(checklist_t* list);
void down_checklist(checklist_t* list);
// TODO - do we want to adjust the size? (YES, don't want to check it all)
// TODO - do we want to be able to call waiton without having called commit?
// - in the case of protected checklists
// TODO - want a destroy checklist (when we have kmalloc, or whatever)
// TODO - some sort of dynamic allocation of them in the future
// TODO - think about deadlock issues with one core spinning on a lock for
// something that it is the hold out for...
// - probably should have interrupts enabled, and never grab these locks
// from interrupt context (and not use irq_save)
/**************************************************************/
/* Barrier: currently made for everyone barriering. Change to use checklist */
struct barrier {
spinlock_t lock;
uint32_t init_count;
uint32_t current_count;
volatile uint8_t ready;
};
typedef struct barrier barrier_t;
void init_barrier(barrier_t *barrier, uint32_t count);
void reset_barrier(barrier_t* barrier);
void waiton_barrier(barrier_t* barrier);
/* Spinlock bit flags */
#define SPINLOCK_IRQ_EN 0x80000000
static inline void spinlock_init(spinlock_t *lock)
{
__spinlock_init(lock);
#ifdef CONFIG_SPINLOCK_DEBUG
lock->call_site = 0;
lock->calling_core = 0;
lock->irq_okay = FALSE;
#endif
}
static inline void spinlock_init_irqsave(spinlock_t *lock)
{
__spinlock_init(lock);
#ifdef CONFIG_SPINLOCK_DEBUG
lock->call_site = 0;
lock->calling_core = 0;
lock->irq_okay = TRUE;
#endif
}
// If ints are enabled, disable them and note it in the top bit of the lock
// There is an assumption about releasing locks in order here...
static inline void spin_lock_irqsave(spinlock_t *lock)
{
uint32_t irq_en;
irq_en = irq_is_enabled();
disable_irq();
spin_lock(lock);
if (irq_en)
lock->rlock |= SPINLOCK_IRQ_EN;
}
static inline bool spin_trylock_irqsave(spinlock_t *lock)
{
uint32_t irq_en = irq_is_enabled();
disable_irq();
if (!spin_trylock(lock)) {
if (irq_en)
enable_irq();
return FALSE;
}
if (irq_en)
lock->rlock |= SPINLOCK_IRQ_EN;
return TRUE;
}
// if the high bit of the lock is set, then re-enable interrupts
// (note from asw: you're lucky this works, you little-endian jerks)
static inline void spin_unlock_irqsave(spinlock_t *lock)
{
if (spin_lock_irq_enabled(lock)) {
spin_unlock(lock);
enable_irq();
} else
spin_unlock(lock);
}
/* Returns whether or not unlocking this lock should enable interrupts or not.
* Is meaningless on locks that weren't locked with irqsave. */
static inline bool spin_lock_irq_enabled(spinlock_t *lock)
{
return lock->rlock & SPINLOCK_IRQ_EN;
}
/* Note, the seq_ctr is not a full seq lock - just the counter guts. Write
* access can be controlled by another lock (like the proc-lock). start_ and
* end_write are the writer's responsibility to signal the readers of a
* concurrent write. */
static inline void __seq_start_write(seq_ctr_t *seq_ctr)
{
#ifdef CONFIG_SEQLOCK_DEBUG
assert(*seq_ctr % 2 == 0);
#endif
(*seq_ctr)++;
/* We're the only writer, so we need to prevent the compiler (and some
* arches) from reordering writes before this point. */
wmb();
}
static inline void __seq_end_write(seq_ctr_t *seq_ctr)
{
#ifdef CONFIG_SEQLOCK_DEBUG
assert(*seq_ctr % 2 == 1);
#endif
/* Need to prevent the compiler (and some arches) from reordering older
* stores */
wmb();
(*seq_ctr)++;
}
/* Untested reference implementation of a seq lock. As mentioned above, we
* might need a variety of these (for instance, this doesn't do an irqsave). Or
* there may be other invariants that we need the lock to protect. */
static inline void write_seqlock(seqlock_t *lock)
{
spin_lock(&lock->w_lock);
__seq_start_write(&lock->r_ctr);
}
static inline void write_sequnlock(seqlock_t *lock)
{
__seq_end_write(&lock->r_ctr);
spin_unlock(&lock->w_lock);
}
static inline seq_ctr_t read_seqbegin(seqlock_t *lock)
{
seq_ctr_t retval = lock->r_ctr;
rmb(); /* don't want future reads to come before our ctr read */
return retval;
}
static inline bool read_seqretry(seqlock_t *lock, seq_ctr_t ctr)
{
return seqctr_retry(lock->r_ctr, ctr);
}