/*
 * \brief  Genode backend for GDBServer (C++)
 * \author Christian Prochaska
 * \author Norman Feske
 * \date   2011-05-06
 */

/*
 * Copyright (C) 2011-2012 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"
#include "linux-low.h"
#define _private private

int linux_detach_one_lwp (struct inferior_list_entry *entry, void *args);
}

#include <base/printf.h>
#include <dataspace/client.h>

#include "cpu_session_component.h"

#include "gdb_stub_thread.h"

static bool verbose = false;

using namespace Genode;
using namespace Gdb_monitor;


static Lock &main_thread_ready_lock()
{
	static Lock _main_thread_ready_lock(Lock::LOCKED);
	return _main_thread_ready_lock;
}


static Lock &gdbserver_ready_lock()
{
	static Lock _gdbserver_ready_lock(Lock::LOCKED);
	return _gdbserver_ready_lock;
}


Gdb_stub_thread *gdb_stub_thread()
{
	return (Gdb_stub_thread*)(current_process()->_private->gdb_stub_thread);
}


extern "C" int genode_signal_fd()
{
	return gdb_stub_thread()->signal_fd();
}


void genode_add_thread(unsigned long lwpid)
{
	if (lwpid == GENODE_LWP_BASE) {
		main_thread_ready_lock().unlock();
	} else {
		if (lwpid == GENODE_LWP_BASE + 1) {
			/* make sure gdbserver is ready to attach new threads */
			gdbserver_ready_lock().lock();
		}
		linux_attach_lwp(lwpid);
	}
}


void genode_remove_thread(unsigned long lwpid)
{
	int pid = GENODE_LWP_BASE;
	linux_detach_one_lwp((struct inferior_list_entry *)
		find_thread_ptid(ptid_build(GENODE_LWP_BASE, lwpid, 0)), &pid);
}


void genode_wait_for_target_main_thread()
{
	/* gdbserver is now ready to attach new threads */
	gdbserver_ready_lock().unlock();

	/* wait until the target's main thread has been created */
	main_thread_ready_lock().lock();
}


extern "C" void genode_detect_all_threads()
{
	Cpu_session_component *csc = gdb_stub_thread()->cpu_session_component();

	Thread_capability thread_cap = csc->next(csc->first()); /* second thread */

	while (thread_cap.valid()) {
		linux_attach_lwp(csc->lwpid(thread_cap));
		thread_cap = csc->next(thread_cap);
	}
}


extern "C" void genode_stop_all_threads()
{
	Cpu_session_component *csc = gdb_stub_thread()->cpu_session_component();

	Thread_capability thread_cap = csc->first();

	while (thread_cap.valid()) {
		csc->pause(thread_cap);
		thread_cap = csc->next(thread_cap);
	}
}


extern "C" void genode_resume_all_threads()
{
	Cpu_session_component *csc = gdb_stub_thread()->cpu_session_component();

	Thread_capability thread_cap = csc->first();

	while (thread_cap.valid()) {
		csc->resume(thread_cap);
		thread_cap = csc->next(thread_cap);
	}
}


int genode_detach(int pid)
{
    find_inferior (&all_threads, linux_detach_one_lwp, &pid);

    genode_resume_all_threads();

    return 0;
}


int genode_kill(int pid)
{
    /* TODO */
    if (verbose) PDBG("genode_kill() called - not implemented\n");

    return genode_detach(pid);
}


void genode_interrupt_thread(unsigned long lwpid)
{
	Cpu_session_component *csc = gdb_stub_thread()->cpu_session_component();

	Thread_capability thread_cap = csc->thread_cap(lwpid);

	if (!thread_cap.valid()) {
		PERR("could not find thread capability for lwpid %lu", lwpid);
		return;
	}

	csc->pause(thread_cap);
}


void genode_continue_thread(unsigned long lwpid, int single_step)
{
	Cpu_session_component *csc = gdb_stub_thread()->cpu_session_component();

	Thread_capability thread_cap = csc->thread_cap(lwpid);

	if (!thread_cap.valid()) {
		PERR("could not find thread capability for lwpid %lu", lwpid);
		return;
	}

	csc->single_step(thread_cap, single_step);
	csc->resume(thread_cap);
}



class Memory_model
{
	private:

		Lock _lock;

		Rm_session_component * const _address_space;

		/**
		 * Representation of a currently mapped region
		 */
		struct Mapped_region
		{
			Rm_session_component::Region *_region;
			unsigned char                *_local_base;

			Mapped_region() : _region(0), _local_base(0) { }

			bool valid() { return _region != 0; }

			bool is_loaded(Rm_session_component::Region const * region)
			{
				return _region == region;
			}

			void flush()
			{
				if (!valid()) return;
				env()->rm_session()->detach(_local_base);
				_local_base = 0;
				_region = 0;
			}

			void load(Rm_session_component::Region *region)
			{
				if (region == _region)
					return;

				if (!region || valid())
					flush();

				if (!region)
					return;

				try {
					_region     = region;
					_local_base = env()->rm_session()->attach(_region->ds_cap(),
					                                          0, _region->offset());
				} catch (Rm_session::Attach_failed) {
					flush();
					PERR("Memory_model: RM attach failed");
				}
			}

			unsigned char *local_base() { return _local_base; }
		};

		enum { NUM_MAPPED_REGIONS = 1 };

		Mapped_region _mapped_region[NUM_MAPPED_REGIONS];

		unsigned _evict_idx;

		/**
		 * Return local address of mapped region
		 *
		 * The function returns 0 if the mapping fails
		 */
		unsigned char *_update_curr_region(Rm_session_component::Region *region)
		{
			for (unsigned i = 0; i < NUM_MAPPED_REGIONS; i++) {
				if (_mapped_region[i].is_loaded(region))
					return _mapped_region[i].local_base();
			}

			/* flush one currently mapped region */
			_evict_idx++;
			if (_evict_idx == NUM_MAPPED_REGIONS)
				_evict_idx = 0;

			_mapped_region[_evict_idx].load(region);

			return _mapped_region[_evict_idx].local_base();
		}

	public:

		Memory_model(Rm_session_component *address_space)
		:
			_address_space(address_space), _evict_idx(0)
		{ }

		unsigned char read(void *addr)
		{
			Lock::Guard guard(_lock);

			addr_t offset_in_region = 0;

			Rm_session_component::Region *region =
				_address_space->find_region(addr, &offset_in_region);

			unsigned char *local_base = _update_curr_region(region);

			if (!local_base) {
				PWRN("Memory model: no memory at address %p", addr);
				return 0;
			}

			unsigned char value =
				local_base[offset_in_region];

			if (verbose)
				Genode::printf("read addr=%p, value=%x\n", addr, value);

			return value;
		}

		void write(void *addr, unsigned char value)
		{
			if (verbose)
			Genode::printf("write addr=%p, value=%x\n", addr, value);

			Lock::Guard guard(_lock);

			addr_t offset_in_region = 0;
			Rm_session_component::Region *region =
				_address_space->find_region(addr, &offset_in_region);

			unsigned char *local_base = _update_curr_region(region);

			if (!local_base) {
				PWRN("Memory model: no memory at address %p", addr);
				PWRN("(attempted to write %x)", (int)value);
				return;
			}

			local_base[offset_in_region] = value;
		}
};


/**
 * Return singleton instance of memory model
 */
static Memory_model *memory_model()
{
	static Memory_model inst(gdb_stub_thread()->rm_session_component());
	return &inst;
}


unsigned char genode_read_memory_byte(void *addr)
{
	return memory_model()->read(addr);
}


void genode_write_memory_byte(void *addr, unsigned char value)
{
	return memory_model()->write(addr, value);
}
