blob: f0ab595b1faa864c1b1ab3f4b4f046e97038ffac [file] [log] [blame]
// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
/*
* Simple spinlock
*
* Copyright 2013-2019 IBM Corp.
*/
#include <skiboot.h>
#include <lock.h>
#include <assert.h>
#include <processor.h>
#include <cpu.h>
#include <console.h>
#include <timebase.h>
/* Set to bust locks. Note, this is initialized to true because our
* lock debugging code is not going to work until we have the per
* CPU data initialized
*/
bool bust_locks = true;
#define LOCK_TIMEOUT_MS 5000
#ifdef DEBUG_LOCKS
static void __nomcount lock_error(struct lock *l, const char *reason, uint16_t err)
{
fprintf(stderr, "LOCK ERROR: %s @%p (state: 0x%016llx)\n",
reason, l, l->lock_val);
op_display(OP_FATAL, OP_MOD_LOCK, err);
abort();
}
static inline void __nomcount lock_check(struct lock *l)
{
if ((l->lock_val & 1) && (l->lock_val >> 32) == this_cpu()->pir)
lock_error(l, "Invalid recursive lock", 0);
}
static inline void __nomcount unlock_check(struct lock *l)
{
if (!(l->lock_val & 1))
lock_error(l, "Unlocking unlocked lock", 1);
if ((l->lock_val >> 32) != this_cpu()->pir)
lock_error(l, "Unlocked non-owned lock", 2);
if (l->in_con_path && this_cpu()->con_suspend == 0)
lock_error(l, "Unlock con lock with console not suspended", 3);
if (list_empty(&this_cpu()->locks_held))
lock_error(l, "Releasing lock we don't hold depth", 4);
}
static inline bool __nomcount __try_lock(struct cpu_thread *cpu, struct lock *l)
{
uint64_t val;
val = cpu->pir;
val <<= 32;
val |= 1;
barrier();
if (__cmpxchg64(&l->lock_val, 0, val) == 0) {
sync();
return true;
}
return false;
}
static inline bool lock_timeout(unsigned long start)
{
/* Print warning if lock has been spinning for more than TIMEOUT_MS */
unsigned long wait = tb_to_msecs(mftb());
if (wait - start > LOCK_TIMEOUT_MS) {
/*
* If the timebase is invalid, we shouldn't
* throw an error. This is possible with pending HMIs
* that need to recover TB.
*/
if( !(mfspr(SPR_TFMR) & SPR_TFMR_TB_VALID))
return false;
return true;
}
return false;
}
#else
static inline void lock_check(struct lock *l) { };
static inline void unlock_check(struct lock *l) { };
static inline bool lock_timeout(unsigned long s) { return false; }
#endif /* DEBUG_LOCKS */
#if defined(DEADLOCK_CHECKER) && defined(DEBUG_LOCKS)
static struct lock dl_lock = {
.lock_val = 0,
.in_con_path = true,
.owner = LOCK_CALLER
};
/* Find circular dependencies in the lock requests. */
static __nomcount inline bool check_deadlock(void)
{
uint32_t lock_owner, start, i;
struct cpu_thread *next_cpu;
struct lock *next;
next = this_cpu()->requested_lock;
start = this_cpu()->pir;
i = 0;
while (i < cpu_max_pir) {
if (!next)
return false;
if (!(next->lock_val & 1) || next->in_con_path)
return false;
lock_owner = next->lock_val >> 32;
if (lock_owner == start)
return true;
next_cpu = find_cpu_by_pir_nomcount(lock_owner);
if (!next_cpu)
return false;
next = next_cpu->requested_lock;
i++;
}
return false;
}
static void add_lock_request(struct lock *l)
{
struct cpu_thread *curr = this_cpu();
bool dead;
if (curr->state != cpu_state_active &&
curr->state != cpu_state_os)
return;
/*
* For deadlock detection we must keep the lock states constant
* while doing the deadlock check. However we need to avoid
* clashing with the stack checker, so no mcount and use an
* inline implementation of the lock for the dl_lock
*/
for (;;) {
if (__try_lock(curr, &dl_lock))
break;
smt_lowest();
while (dl_lock.lock_val)
barrier();
smt_medium();
}
curr->requested_lock = l;
dead = check_deadlock();
lwsync();
dl_lock.lock_val = 0;
if (dead)
lock_error(l, "Deadlock detected", 0);
}
static void remove_lock_request(void)
{
this_cpu()->requested_lock = NULL;
}
#else
static inline void add_lock_request(struct lock *l) { };
static inline void remove_lock_request(void) { };
#endif /* #if defined(DEADLOCK_CHECKER) && defined(DEBUG_LOCKS) */
bool lock_held_by_me(struct lock *l)
{
uint64_t pir64 = this_cpu()->pir;
return l->lock_val == ((pir64 << 32) | 1);
}
bool try_lock_caller(struct lock *l, const char *owner)
{
struct cpu_thread *cpu = this_cpu();
if (bust_locks)
return true;
if (l->in_con_path)
cpu->con_suspend++;
if (__try_lock(cpu, l)) {
l->owner = owner;
#ifdef DEBUG_LOCKS_BACKTRACE
backtrace_create(l->bt_buf, LOCKS_BACKTRACE_MAX_ENTS,
&l->bt_metadata);
#endif
list_add(&cpu->locks_held, &l->list);
return true;
}
if (l->in_con_path)
cpu->con_suspend--;
return false;
}
void lock_caller(struct lock *l, const char *owner)
{
bool timeout_warn = false;
unsigned long start = 0;
if (bust_locks)
return;
lock_check(l);
if (try_lock_caller(l, owner))
return;
add_lock_request(l);
#ifdef DEBUG_LOCKS
/*
* Ensure that we get a valid start value
* as we may be handling TFMR errors and taking
* a lock to do so, so timebase could be garbage
*/
if( (mfspr(SPR_TFMR) & SPR_TFMR_TB_VALID))
start = tb_to_msecs(mftb());
#endif
for (;;) {
if (try_lock_caller(l, owner))
break;
smt_lowest();
while (l->lock_val)
barrier();
smt_medium();
if (start && !timeout_warn && lock_timeout(start)) {
/*
* Holding the lock request while printing a
* timeout and taking console locks can result
* in deadlock fals positive if the lock owner
* tries to take the console lock. So drop it.
*/
remove_lock_request();
prlog(PR_WARNING, "WARNING: Lock has been spinning for over %dms\n", LOCK_TIMEOUT_MS);
backtrace();
add_lock_request(l);
timeout_warn = true;
}
}
remove_lock_request();
}
void unlock(struct lock *l)
{
struct cpu_thread *cpu = this_cpu();
if (bust_locks)
return;
unlock_check(l);
l->owner = NULL;
list_del(&l->list);
lwsync();
l->lock_val = 0;
/* WARNING: On fast reboot, we can be reset right at that
* point, so the reset_lock in there cannot be in the con path
*/
if (l->in_con_path) {
cpu->con_suspend--;
if (cpu->con_suspend == 0 && cpu->con_need_flush)
flush_console();
}
}
bool lock_recursive_caller(struct lock *l, const char *caller)
{
if (bust_locks)
return false;
if (lock_held_by_me(l))
return false;
lock_caller(l, caller);
return true;
}
void init_locks(void)
{
bust_locks = false;
}
void dump_locks_list(void)
{
struct lock *l;
prlog(PR_ERR, "Locks held:\n");
list_for_each(&this_cpu()->locks_held, l, list) {
prlog(PR_ERR, " %s\n", l->owner);
#ifdef DEBUG_LOCKS_BACKTRACE
backtrace_print(l->bt_buf, &l->bt_metadata, NULL, NULL, true);
#endif
}
}
void drop_my_locks(bool warn)
{
struct lock *l;
disable_fast_reboot("Lock corruption");
while((l = list_top(&this_cpu()->locks_held, struct lock, list)) != NULL) {
if (warn) {
prlog(PR_ERR, " %s\n", l->owner);
#ifdef DEBUG_LOCKS_BACKTRACE
backtrace_print(l->bt_buf, &l->bt_metadata, NULL, NULL,
true);
#endif
}
unlock(l);
}
}