/*
 * \brief  Fiasco.OC(ARM)-specific helper functions for GDB server
 * \author Christian Prochaska
 * \date   2011-08-01
 */

/*
 * Copyright (C) 2011-2016 Genode Labs GmbH
 *
 * This file is part of the Genode OS framework, which is distributed
 * under the terms of the GNU General Public License version 2.
 */

extern "C" {
#define private _private
#include "genode-low.h"
#define _private private
}

#include <base/log.h>
#include "cpu_session_component.h"
#include "reg-arm.h"
#include "gdbserver_platform_helper.h"

#include "genode_child_resources.h"

using namespace Genode;

static bool in_syscall(Thread_state const &ts)
{
	try {
		/* looking for syscall pattern:
		 * (PC-8:  e1a0e00f mov lr, pc)
		 *  PC-4:  e3e0f00b mvn pc, #11
		 * (PC:    e1a02004 mov r2, r4)
		 */
		if ((genode_read_memory_byte((void*)(ts.ip - 1)) == 0xe3) &&
			(genode_read_memory_byte((void*)(ts.ip - 2)) == 0xe0) &&
			(genode_read_memory_byte((void*)(ts.ip - 3)) == 0xf0) &&
			(genode_read_memory_byte((void*)(ts.ip - 4)) == 0x0b))
			return true;
	} catch (No_memory_at_address) {
		return false;
	}
	return false;
}

extern "C" int genode_fetch_register(int regno, unsigned long *value)
{
	Thread_state ts;

	try { ts = get_current_thread_state(); }
	catch (...) {
		error(__PRETTY_FUNCTION__, ": could not get current thread state");
		return -1;
	}

	if (in_syscall(ts) || ts.unresolved_page_fault) {

		switch((enum reg_index)regno)
		{
			case R0:  cannot_fetch_register("R0");  return -1;
			case R1:  cannot_fetch_register("R1");  return -1;
			case R2:  cannot_fetch_register("R2");  return -1;
			case R3:  cannot_fetch_register("R3");  return -1;
			case R4:  cannot_fetch_register("R4");  return -1;
			case R5:  cannot_fetch_register("R5");  return -1;
			case R6:  cannot_fetch_register("R6");  return -1;
			case R7:  cannot_fetch_register("R7");  return -1;
			case R8:  cannot_fetch_register("R8");  return -1;
			case R9:  cannot_fetch_register("R9");  return -1;
			case R10: cannot_fetch_register("R10"); return -1;

			case R11:

				if (in_syscall(ts)) {
					/* R11 can be calculated from SP. The offset can be found in
			 	 	 * the disassembled 'Fiasco::l4_ipc()' function:
			 	 	 *   add	r11, sp, #8 -> r11 = sp + 8
			 	 	 *   sub	sp, sp, #20 -> r11 = (sp + 20) + 8
			 	 	 */
					*value = (ts.sp + 20) + 8;

				    /* for the debug output, if enabled */
				    fetch_register("R11", *value, *value);

					return 0;
				} else {

				   cannot_fetch_register("R11");

				   return -1;
				}

			case R12:  cannot_fetch_register("R12"); return -1;
			case SP:   fetch_register("SP ", ts.sp, *value); return 0;
			case LR:   cannot_fetch_register("LR"); return -1;
			case PC:   fetch_register("PC ", ts.ip, *value); return 0;
			case F0:   cannot_fetch_register("F0"); return -1;
			case F1:   cannot_fetch_register("F1"); return -1;
			case F2:   cannot_fetch_register("F2"); return -1;
			case F3:   cannot_fetch_register("F3"); return -1;
			case F4:   cannot_fetch_register("F4"); return -1;
			case F5:   cannot_fetch_register("F5"); return -1;
			case F6:   cannot_fetch_register("F6"); return -1;
			case F7:   cannot_fetch_register("F7"); return -1;
			case FPS:  cannot_fetch_register("FPS"); return -1;
			case CPSR: cannot_fetch_register("CPSR"); return -1;
			default:   error("unhandled register ", regno); return -1;
		}

	} else {

		switch((enum reg_index)regno)
		{
			case R0:   fetch_register("R0  ", ts.r0,  *value); return 0;
			case R1:   fetch_register("R1  ", ts.r1,  *value); return 0;
			case R2:   fetch_register("R2  ", ts.r2,  *value); return 0;
			case R3:   fetch_register("R3  ", ts.r3,  *value); return 0;
			case R4:   fetch_register("R4  ", ts.r4,  *value); return 0;
			case R5:   fetch_register("R5  ", ts.r5,  *value); return 0;
			case R6:   fetch_register("R6  ", ts.r6,  *value); return 0;
			case R7:   fetch_register("R7  ", ts.r7,  *value); return 0;
			case R8:   fetch_register("R8  ", ts.r8,  *value); return 0;
			case R9:   fetch_register("R9  ", ts.r9,  *value); return 0;
			case R10:  fetch_register("R10 ", ts.r10, *value); return 0;
			case R11:  fetch_register("R11 ", ts.r11, *value); return 0;
			case R12:  fetch_register("R12 ", ts.r12, *value); return 0;
			case SP:   fetch_register("SP  ", ts.sp,  *value); return 0;
			case LR:   fetch_register("LR  ", ts.lr,  *value); return 0;
			case PC:   fetch_register("PC  ", ts.ip,  *value); return 0;
			case F0:   cannot_fetch_register("F0"); return -1;
			case F1:   cannot_fetch_register("F1"); return -1;
			case F2:   cannot_fetch_register("F2"); return -1;
			case F3:   cannot_fetch_register("F3"); return -1;
			case F4:   cannot_fetch_register("F4"); return -1;
			case F5:   cannot_fetch_register("F5"); return -1;
			case F6:   cannot_fetch_register("F6"); return -1;
			case F7:   cannot_fetch_register("F7"); return -1;
			case FPS:  cannot_fetch_register("FPS"); return -1;
			case CPSR: fetch_register("CPSR", ts.cpsr,  *value); return 0;
			default:   error("unhandled register ", regno); return -1;
		}
	}

	return -1;
}


extern "C" void genode_store_register(int regno, unsigned long value)
{
	Thread_state ts;

	try { ts = get_current_thread_state(); }
	catch (...) {
		error(__PRETTY_FUNCTION__, ": could not get current thread state");
		return;
	}

	if (in_syscall(ts)) {
		log("cannot set registers while thread is in syscall");
		return;
	}

	switch((enum reg_index)regno)
	{
		case R0:   if (!store_register("R0  ", ts.r0,   value)) return; break;
		case R1:   if (!store_register("R1  ", ts.r1,   value)) return; break;
		case R2:   if (!store_register("R2  ", ts.r2,   value)) return; break;
		case R3:   if (!store_register("R3  ", ts.r3,   value)) return; break;
		case R4:   if (!store_register("R4  ", ts.r4,   value)) return; break;
		case R5:   if (!store_register("R5  ", ts.r5,   value)) return; break;
		case R6:   if (!store_register("R6  ", ts.r6,   value)) return; break;
		case R7:   if (!store_register("R7  ", ts.r7,   value)) return; break;
		case R8:   if (!store_register("R8  ", ts.r8,   value)) return; break;
		case R9:   if (!store_register("R9  ", ts.r9,   value)) return; break;
		case R10:  if (!store_register("R10 ", ts.r10,  value)) return; break;
		case R11:  if (!store_register("R11 ", ts.r11,  value)) return; break;
		case R12:  if (!store_register("R12 ", ts.r12,  value)) return; break;
		case SP:   if (!store_register("SP  ", ts.sp,   value)) return; break;
		case LR:   if (!store_register("LR  ", ts.lr,   value)) return; break;
		case PC:   if (!store_register("PC  ", ts.ip,   value)) return; break;
		case F0:   cannot_store_register("F0 ", value); return;
		case F1:   cannot_store_register("F1 ", value); return;
		case F2:   cannot_store_register("F2 ", value); return;
		case F3:   cannot_store_register("F3 ", value); return;
		case F4:   cannot_store_register("F4 ", value); return;
		case F5:   cannot_store_register("F5 ", value); return;
		case F6:   cannot_store_register("F6 ", value); return;
		case F7:   cannot_store_register("F7 ", value); return;
		case FPS:  cannot_store_register("FPS", value); return;
		case CPSR: if (!store_register("CPSR", ts.cpsr, value)) return; break;
	}

	set_current_thread_state(ts);
}

