/* Copyright 2013-2014 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 <processor.h>
#include <cpu.h>
#include <stack.h>
#include <mem_region.h>
#include <unistd.h>
#include <lock.h>

#define STACK_BUF_ENTRIES	60
static struct bt_entry bt_buf[STACK_BUF_ENTRIES];

/* Dumps backtrace to buffer */
void __nomcount backtrace_create(struct bt_entry *entries,
				 unsigned int max_ents,
				 struct bt_metadata *metadata)
{
	unsigned long *fp = __builtin_frame_address(0);
	unsigned long top_adj = top_of_ram;
	struct stack_frame *eframe = (struct stack_frame *)fp;

	/* Assume one stack for early backtraces */
	if (top_of_ram == SKIBOOT_BASE + SKIBOOT_SIZE)
		top_adj = top_of_ram + STACK_SIZE;

	metadata->ents = 0;
	while (max_ents) {
		fp = (unsigned long *)fp[0];
		if (!fp || (unsigned long)fp > top_adj)
			break;
		eframe = (struct stack_frame *)fp;
		entries->sp = (unsigned long)fp;
		entries->pc = fp[2];
		entries++;
		metadata->ents++;
		max_ents--;
	}

	metadata->r1_caller = eframe->gpr[1];

	if (fp)
		metadata->token = eframe->gpr[0];
	else
		metadata->token = -1UL;

	metadata->pir = mfspr(SPR_PIR);
}

void backtrace_print(struct bt_entry *entries, struct bt_metadata *metadata,
		     char *out_buf, unsigned int *len, bool symbols)
{
	static char bt_text_buf[4096];
	int i, l = 0, max;
	char *buf = out_buf;
	unsigned long bottom, top, normal_top, tbot, ttop;
	char mark;

	if (!out_buf) {
		buf = bt_text_buf;
		max = sizeof(bt_text_buf) - 16;
	} else
		max = *len - 1;

	bottom = cpu_stack_bottom(metadata->pir);
	normal_top = cpu_stack_top(metadata->pir);
	top = cpu_emergency_stack_top(metadata->pir);
	tbot = SKIBOOT_BASE;
	ttop = (unsigned long)&_etext;

	l += snprintf(buf, max, "CPU %04lx Backtrace:\n", metadata->pir);
	for (i = 0; i < metadata->ents && l < max; i++) {
		if (entries->sp < bottom || entries->sp > top)
			mark = '!';
		else if (entries->sp > normal_top)
			mark = 'E';
		else if (entries->pc < tbot || entries->pc > ttop)
			mark = '*';
		else
			mark = ' ';
		l += snprintf(buf + l, max - l,
			      " S: %016lx R: %016lx %c ",
			      entries->sp, entries->pc, mark);
		if (symbols)
			l += snprintf_symbol(buf + l, max - l, entries->pc);
		l += snprintf(buf + l, max - l, "\n");
		entries++;
	}
	if (metadata->token <= OPAL_LAST)
		l += snprintf(buf + l, max - l,
			      " --- OPAL call token: 0x%lx caller R1: 0x%016lx ---\n",
			      metadata->token, metadata->r1_caller);
	else if (metadata->token == -1UL)
		l += snprintf(buf + l, max - l, " --- OPAL boot ---\n");
	if (!out_buf)
		write(stdout->fd, bt_text_buf, l);
	buf[l++] = 0;
	if (len)
		*len = l;
}

/*
 * To ensure that we always get backtrace output we bypass the usual console
 * locking paths. The downside is that when multiple threads need to print
 * a backtrace they garble each other. To prevent this we use a seperate
 * lock to serialise printing of the dumps.
 */
struct lock bt_lock = LOCK_UNLOCKED;

void backtrace(void)
{
	struct bt_metadata metadata;

	lock(&bt_lock);

	backtrace_create(bt_buf, STACK_BUF_ENTRIES, &metadata);
	backtrace_print(bt_buf, &metadata, NULL, NULL, true);

	unlock(&bt_lock);
}

void __nomcount __stack_chk_fail(void);
void __nomcount __stack_chk_fail(void)
{
	static bool failed_once;

	if (failed_once)
		return;
	failed_once = true;
	prlog(PR_EMERG, "Stack corruption detected !\n");
	abort();
}

#ifdef STACK_CHECK_ENABLED

static int64_t lowest_stack_mark = LONG_MAX;
static struct lock stack_check_lock = LOCK_UNLOCKED;

void __nomcount __mcount_stack_check(uint64_t sp, uint64_t lr);
void __nomcount __mcount_stack_check(uint64_t sp, uint64_t lr)
{
	struct cpu_thread *c = this_cpu();
	uint64_t base = (uint64_t)c;
	uint64_t bot = base + sizeof(struct cpu_thread);
	int64_t mark = sp - bot;
	uint64_t top = base + NORMAL_STACK_SIZE;

	/*
	 * Don't check the emergency stack just yet.
	 */
	if (c->in_opal_call > 1)
		return;

	/*
	 * Don't re-enter on this CPU or don't enter at all if somebody
	 * has spotted an overflow
	 */
	if (c->in_mcount)
		return;
	c->in_mcount = true;

	/* Capture lowest stack for this thread */
	if (mark < c->stack_bot_mark) {
		lock(&stack_check_lock);
		c->stack_bot_mark = mark;
		c->stack_bot_pc = lr;
		c->stack_bot_tok = c->current_token;
		backtrace_create(c->stack_bot_bt, CPU_BACKTRACE_SIZE,
				 &c->stack_bot_bt_metadata);
		unlock(&stack_check_lock);
	}

	/* Stack is within bounds ? check for warning and bail */
	if (sp >= (bot + STACK_SAFETY_GAP) && sp < top) {
		if (mark < STACK_WARNING_GAP) {
			prlog(PR_EMERG, "CPU %04x Stack usage danger !"
			      " pc=%08llx sp=%08llx (gap=%lld) token=%lld\n",
			      c->pir, lr, sp, mark, c->current_token);
			backtrace();
		}
		c->in_mcount = false;
		return;
	}
	
	prlog(PR_EMERG, "CPU %04x Stack overflow detected !"
	      " pc=%08llx sp=%08llx (gap=%lld) token=%lld\n",
	      c->pir, lr, sp, mark, c->current_token);
	abort();
}

void check_stacks(void)
{
	struct cpu_thread *c, *lowest = NULL;

	/* We should never call that from mcount */
	assert(!this_cpu()->in_mcount);

	/* Mark ourselves "in_mcount" to avoid deadlock on stack
	 * check lock
	 */
	this_cpu()->in_mcount = true;

	for_each_cpu(c) {
		if (!c->stack_bot_mark ||
		    c->stack_bot_mark >= lowest_stack_mark)
			continue;
		lock(&stack_check_lock);
		if (c->stack_bot_mark < lowest_stack_mark) {
			lowest = c;
			lowest_stack_mark = c->stack_bot_mark;
		}
		unlock(&stack_check_lock);
	}
	if (lowest) {
		lock(&stack_check_lock);
		prlog(PR_NOTICE, "CPU %04x lowest stack mark %lld bytes left"
		      " pc=%08llx token=%lld\n",
		      lowest->pir, lowest->stack_bot_mark, lowest->stack_bot_pc,
		      lowest->stack_bot_tok);
		backtrace_print(lowest->stack_bot_bt,
				&lowest->stack_bot_bt_metadata,
				NULL, NULL, true);
		unlock(&stack_check_lock);
	}

	this_cpu()->in_mcount = false;
}
#endif /* STACK_CHECK_ENABLED */
