/*
 * \brief  Event-tracing support
 * \author Norman Feske
 * \date   2013-08-09
 */

/*
 * Copyright (C) 2013 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.
 */

/* Genode includes */
#include <base/env.h>
#include <base/thread.h>
#include <base/trace/policy.h>
#include <dataspace/client.h>

/* local includes */
#include <trace/control.h>

using namespace Genode;


namespace Genode { bool inhibit_tracing = true; /* cleared by '_main' */ }


/**
 * Return trace-control structure for specified thread
 */
static Trace::Control *trace_control(Cpu_session *cpu, Rm_session *rm,
                                     Thread_capability thread_cap)
{
	struct Area
	{
		Cpu_session           &cpu;
		Rm_session            &rm;
		Dataspace_capability   ds;
		size_t           const size;
		Trace::Control * const base;

		Area(Cpu_session &cpu, Rm_session &rm)
		:
			cpu(cpu), rm(rm),
			ds(cpu.trace_control()),
			size(ds.valid() ? Dataspace_client(ds).size() : 0),
			base(ds.valid() ? (Trace::Control * const)rm.attach(ds) : 0)
		{ }

		Trace::Control *slot(Thread_capability thread)
		{
			if (!thread.valid() || base == 0)
				return 0;

			unsigned const index = cpu.trace_control_index(thread);

			if ((index + 1)*sizeof(Trace::Control) > size) {
				PERR("thread control index is out of range");
				return 0;
			}

			return base + index;
		}
	};

	static Area area(*cpu, *rm);
	return area.slot(thread_cap);
}


/*******************
 ** Trace::Logger **
 *******************/

bool Trace::Logger::_evaluate_control()
{
	/* check process-global and thread-specific tracing condition */
	if (inhibit_tracing || !control || control->tracing_inhibited())
		return false;

	if (control->state_changed()) {

		/* suppress tracing during initialization */
		Control::Inhibit_guard guard(*control);

		if (control->to_be_disabled()) {

			/* unload policy */
			if (policy_module) {
				env()->rm_session()->detach(policy_module);
				policy_module = 0;
			}

			/* unmap trace buffer */
			if (buffer) {
				env()->rm_session()->detach(buffer);
				buffer = 0;
			}

			/* inhibit generation of trace events */
			enabled = false;
			control->acknowledge_disabled();
		}

		else if (control->to_be_enabled()) {

			control->acknowledge_enabled();
			enabled = true;
		}
	}

	if (enabled && (policy_version != control->policy_version())) {

		/* suppress tracing during policy change */
		Control::Inhibit_guard guard(*control);

		/* obtain policy */
		Dataspace_capability policy_ds = env()->cpu_session()->trace_policy(thread_cap);

		if (!policy_ds.valid()) {
			PWRN("could not obtain trace policy");
			control->error();
			enabled = false;
			return false;
		}

		try {
			max_event_size = 0;
			policy_module  = 0;

			policy_module = env()->rm_session()->attach(policy_ds);

			/* relocate function pointers of policy callback table */
			for (unsigned i = 0; i < sizeof(Trace::Policy_module)/sizeof(void *); i++) {
				((addr_t *)policy_module)[i] += (addr_t)(policy_module);
			}

			max_event_size = policy_module->max_event_size();

		} catch (...) { }

		/* obtain buffer */
		buffer = 0;
		Dataspace_capability buffer_ds = env()->cpu_session()->trace_buffer(thread_cap);

		if (!buffer_ds.valid()) {
			PWRN("could not obtain trace buffer");
			control->error();
			enabled = false;
			return false;
		}

		try {
			buffer = env()->rm_session()->attach(buffer_ds);
			buffer->init(Dataspace_client(buffer_ds).size());
		} catch (...) { }

		policy_version = control->policy_version();
	}

	return enabled && policy_module;
}


void Trace::Logger::log(char const *msg, size_t len)
{
	if (!this || !_evaluate_control()) return;

	memcpy(buffer->reserve(len), msg, len);
	buffer->commit(len);
}


void Trace::Logger::init(Thread_capability thread)
{
	thread_cap = thread;

	control = trace_control(env()->cpu_session(), env()->rm_session(), thread);
}


Trace::Logger::Logger()
:
	control(0),
	enabled(false),
	policy_version(0),
	policy_module(0),
	max_event_size(0),
	pending_init(false)
{ }


/*****************
 ** Thread_base **
 *****************/

/**
 * return logger instance for the main thread **
 */
static Trace::Logger *main_trace_logger()
{
	static Trace::Logger logger;
	return &logger;
}


Trace::Logger *Thread_base::_logger()
{
	if (inhibit_tracing)
		return 0;

	Thread_base * const myself = Thread_base::myself();

	Trace::Logger * const logger = myself ? &myself->_trace_logger
	                                      : main_trace_logger();

	/* logger is already being initialized */
	if (logger->is_init_pending())
		return logger;

	/* lazily initialize trace object */
	if (!logger->is_initialized()) {
		logger->init_pending(true);
		logger->init(myself ? myself->_thread_cap : env()->parent()->main_thread_cap());
	}

	return logger;
}

