/* Copyright 2013-2018 IBM Corp. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include /* 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; 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); } 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); unlock(l); } }