| // 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); |
| } |
| } |
| |