diff options
author | Angelos Mouzakitis <a.mouzakitis@virtualopensystems.com> | 2023-10-10 14:33:42 +0000 |
---|---|---|
committer | Angelos Mouzakitis <a.mouzakitis@virtualopensystems.com> | 2023-10-10 14:33:42 +0000 |
commit | af1a266670d040d2f4083ff309d732d648afba2a (patch) | |
tree | 2fc46203448ddcc6f81546d379abfaeb323575e9 /roms/skiboot/core/lock.c | |
parent | e02cda008591317b1625707ff8e115a4841aa889 (diff) |
Change-Id: Iaf8d18082d3991dec7c0ebbea540f092188eb4ec
Diffstat (limited to 'roms/skiboot/core/lock.c')
-rw-r--r-- | roms/skiboot/core/lock.c | 336 |
1 files changed, 336 insertions, 0 deletions
diff --git a/roms/skiboot/core/lock.c b/roms/skiboot/core/lock.c new file mode 100644 index 000000000..f0ab595b1 --- /dev/null +++ b/roms/skiboot/core/lock.c @@ -0,0 +1,336 @@ +// 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); + } +} + |