blob: 2ea83e3beb086ff03d669091cf1e1ededb9754bd [file] [log] [blame]
/* 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 <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;
#ifdef DEBUG_LOCKS
static struct lock dl_lock = LOCK_UNLOCKED;
static void 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 void 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 void 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);
}
/* Find circular dependencies in the lock requests. */
static 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(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();
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.
*/
for (;;) {
if (try_lock(&dl_lock))
break;
smt_lowest();
while (dl_lock.lock_val)
barrier();
smt_medium();
}
curr->requested_lock = l;
if (check_deadlock())
lock_error(l, "Deadlock detected", 0);
unlock(&dl_lock);
}
static void remove_lock_request(void)
{
this_cpu()->requested_lock = NULL;
}
#define LOCK_TIMEOUT_MS 5000
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;
prlog(PR_WARNING, "WARNING: Lock has been "\
"spinning for %lums\n", wait - start);
backtrace();
return true;
}
return false;
}
#else
static inline void lock_check(struct lock *l) { };
static inline void unlock_check(struct lock *l) { };
static inline void add_lock_request(struct lock *l) { };
static inline void remove_lock_request(void) { };
static inline bool lock_timeout(unsigned long s) { return false; }
#endif /* DEBUG_LOCKS */
bool lock_held_by_me(struct lock *l)
{
uint64_t pir64 = this_cpu()->pir;
return l->lock_val == ((pir64 << 32) | 1);
}
static inline bool __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;
}
bool try_lock_caller(struct lock *l, const char *owner)
{
struct cpu_thread *cpu = this_cpu();
if (bust_locks)
return true;
if (__try_lock(cpu, l)) {
l->owner = owner;
if (l->in_con_path)
cpu->con_suspend++;
list_add(&cpu->locks_held, &l->list);
return true;
}
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)
timeout_warn = lock_timeout(start);
}
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);
}
}