/***************************************************************************
 *   Copyright (C) 2015 by Daniel Krebs                                    *
 *   Daniel Krebs - github@daniel-krebs.net                                *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
 ***************************************************************************/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <helper/time_support.h>
#include <jtag/jtag.h>
#include "target/target.h"
#include "target/target_type.h"
#include "rtos.h"
#include "helper/log.h"
#include "helper/types.h"
#include "target/armv7m.h"
#include "rtos_riot_stackings.h"

static bool riot_detect_rtos(struct target *target);
static int riot_create(struct target *target);
static int riot_update_threads(struct rtos *rtos);
static int riot_get_thread_reg_list(struct rtos *rtos, int64_t thread_id,
	struct rtos_reg **reg_list, int *num_regs);
static int riot_get_symbol_list_to_lookup(struct symbol_table_elem *symbol_list[]);

struct riot_thread_state {
	int value;
	const char *desc;
};

/* refer RIOT sched.h */
static const struct riot_thread_state riot_thread_states[] = {
	{ 0, "Stopped" },
	{ 1, "Zombie" },
	{ 2, "Sleeping" },
	{ 3, "Blocked mutex" },
	{ 4, "Blocked receive" },
	{ 5, "Blocked send" },
	{ 6, "Blocked reply" },
	{ 7, "Blocked any flag" },
	{ 8, "Blocked all flags" },
	{ 9, "Blocked mbox" },
	{ 10, "Blocked condition" },
	{ 11, "Running" },
	{ 12, "Pending" },
};
#define RIOT_NUM_STATES ARRAY_SIZE(riot_thread_states)

struct riot_params {
	const char *target_name;
	unsigned char thread_sp_offset;
	unsigned char thread_status_offset;
};

static const struct riot_params riot_params_list[] = {
	{
		"cortex_m",		/* target_name */
		0x00,					/* thread_sp_offset */
		0x04,					/* thread_status_offset */
	},
	{	/* STLink */
		"hla_target",		/* target_name */
		0x00,			/* thread_sp_offset */
		0x04,			/* thread_status_offset */
	}
};
#define RIOT_NUM_PARAMS ARRAY_SIZE(riot_params_list)

/* Initialize in riot_create() depending on architecture */
static const struct rtos_register_stacking *stacking_info;

enum riot_symbol_values {
	RIOT_THREADS_BASE = 0,
	RIOT_NUM_THREADS,
	RIOT_ACTIVE_PID,
	RIOT_MAX_THREADS,
	RIOT_NAME_OFFSET,
};

struct riot_symbol {
	const char *const name;
	bool optional;
};

/* refer RIOT core/sched.c */
static struct riot_symbol const riot_symbol_list[] = {
	{"sched_threads", false},
	{"sched_num_threads", false},
	{"sched_active_pid", false},
	{"max_threads", false},
	{"_tcb_name_offset", true},
	{NULL, false}
};

const struct rtos_type riot_rtos = {
	.name = "RIOT",
	.detect_rtos = riot_detect_rtos,
	.create = riot_create,
	.update_threads = riot_update_threads,
	.get_thread_reg_list = riot_get_thread_reg_list,
	.get_symbol_list_to_lookup = riot_get_symbol_list_to_lookup,
};

static int riot_update_threads(struct rtos *rtos)
{
	int retval;
	int tasks_found = 0;
	const struct riot_params *param;

	if (!rtos)
		return ERROR_FAIL;

	if (!rtos->rtos_specific_params)
		return ERROR_FAIL;

	param = (const struct riot_params *)rtos->rtos_specific_params;

	if (!rtos->symbols) {
		LOG_ERROR("No symbols for RIOT");
		return ERROR_FAIL;
	}

	if (rtos->symbols[RIOT_THREADS_BASE].address == 0) {
		LOG_ERROR("Can't find symbol `%s`",
			riot_symbol_list[RIOT_THREADS_BASE].name);
		return ERROR_FAIL;
	}

	/* wipe out previous thread details if any */
	rtos_free_threadlist(rtos);

	/* Reset values */
	rtos->current_thread = 0;
	rtos->thread_count = 0;

	/* read the current thread id */
	int16_t active_pid = 0;
	retval = target_read_u16(rtos->target,
			rtos->symbols[RIOT_ACTIVE_PID].address,
			(uint16_t *)&active_pid);
	if (retval != ERROR_OK) {
		LOG_ERROR("Can't read symbol `%s`",
			riot_symbol_list[RIOT_ACTIVE_PID].name);
		return retval;
	}
	rtos->current_thread = active_pid;

	/* read the current thread count
	 * It's `int` in RIOT, but this is Cortex M* only anyway */
	int32_t thread_count = 0;
	retval = target_read_u16(rtos->target,
			rtos->symbols[RIOT_NUM_THREADS].address,
			(uint16_t *)&thread_count);
	if (retval != ERROR_OK) {
		LOG_ERROR("Can't read symbol `%s`",
			riot_symbol_list[RIOT_NUM_THREADS].name);
		return retval;
	}

	/* read the maximum number of threads */
	uint8_t max_threads = 0;
	retval = target_read_u8(rtos->target,
			rtos->symbols[RIOT_MAX_THREADS].address,
			&max_threads);
	if (retval != ERROR_OK) {
		LOG_ERROR("Can't read symbol `%s`",
			riot_symbol_list[RIOT_MAX_THREADS].name);
		return retval;
	}
	if (thread_count > max_threads) {
		LOG_ERROR("Thread count is invalid");
		return ERROR_FAIL;
	}
	rtos->thread_count = thread_count;

	/* Base address of thread array */
	uint32_t threads_base = rtos->symbols[RIOT_THREADS_BASE].address;

	/* Try to get the offset of tcb_t::name, if absent RIOT wasn't compiled
	 * with DEVELHELP, so there are no thread names */
	uint8_t name_offset = 0;
	if (rtos->symbols[RIOT_NAME_OFFSET].address != 0) {
		retval = target_read_u8(rtos->target,
				rtos->symbols[RIOT_NAME_OFFSET].address,
				&name_offset);
		if (retval != ERROR_OK) {
			LOG_ERROR("Can't read symbol `%s`",
				riot_symbol_list[RIOT_NAME_OFFSET].name);
			return retval;
		}
	}

	/* Allocate memory for thread description */
	rtos->thread_details = calloc(thread_count, sizeof(struct thread_detail));
	if (!rtos->thread_details) {
		LOG_ERROR("RIOT: out of memory");
		return ERROR_FAIL;
	}

	/* Buffer for thread names, maximum to display is 32 */
	char buffer[32];

	for (unsigned int i = 0; i < max_threads; i++) {
		if (tasks_found == rtos->thread_count)
			break;

		/* get pointer to tcb_t */
		uint32_t tcb_pointer = 0;
		retval = target_read_u32(rtos->target,
				threads_base + (i * 4),
				&tcb_pointer);
		if (retval != ERROR_OK) {
			LOG_ERROR("Can't parse `%s`",
				riot_symbol_list[RIOT_THREADS_BASE].name);
			goto error;
		}

		if (tcb_pointer == 0) {
			/* PID unused */
			continue;
		}

		/* Index is PID */
		rtos->thread_details[tasks_found].threadid = i;

		/* read thread state */
		uint8_t status = 0;
		retval = target_read_u8(rtos->target,
				tcb_pointer + param->thread_status_offset,
				&status);
		if (retval != ERROR_OK) {
			LOG_ERROR("Can't parse `%s`",
				riot_symbol_list[RIOT_THREADS_BASE].name);
			goto error;
		}

		/* Search for state */
		unsigned int k;
		for (k = 0; k < RIOT_NUM_STATES; k++) {
			if (riot_thread_states[k].value == status)
				break;
		}

		/* Copy state string */
		if (k >= RIOT_NUM_STATES) {
			rtos->thread_details[tasks_found].extra_info_str =
			strdup("unknown state");
		} else {
			rtos->thread_details[tasks_found].extra_info_str =
			strdup(riot_thread_states[k].desc);
		}

		if (!rtos->thread_details[tasks_found].extra_info_str) {
			LOG_ERROR("RIOT: out of memory");
			retval = ERROR_FAIL;
			goto error;
		}

		/* Thread names are only available if compiled with DEVELHELP */
		if (name_offset != 0) {
			uint32_t name_pointer = 0;
			retval = target_read_u32(rtos->target,
					tcb_pointer + name_offset,
					&name_pointer);
			if (retval != ERROR_OK) {
				LOG_ERROR("Can't parse `%s`",
					riot_symbol_list[RIOT_THREADS_BASE].name);
				goto error;
			}

			/* read thread name */
			retval = target_read_buffer(rtos->target,
					name_pointer,
					sizeof(buffer),
					(uint8_t *)&buffer);
			if (retval != ERROR_OK) {
				LOG_ERROR("Can't parse `%s`",
					riot_symbol_list[RIOT_THREADS_BASE].name);
				goto error;
			}

			/* Make sure the string in the buffer terminates */
			if (buffer[sizeof(buffer) - 1] != 0)
				buffer[sizeof(buffer) - 1] = 0;

			/* Copy thread name */
			rtos->thread_details[tasks_found].thread_name_str =
			strdup(buffer);

		} else {
			rtos->thread_details[tasks_found].thread_name_str =
			strdup("Enable DEVELHELP to see task names");
		}

		if (!rtos->thread_details[tasks_found].thread_name_str) {
			LOG_ERROR("RIOT: out of memory");
			retval = ERROR_FAIL;
			goto error;
		}

		rtos->thread_details[tasks_found].exists = true;

		tasks_found++;
	}

	return ERROR_OK;

error:
	rtos_free_threadlist(rtos);
	return retval;
}

static int riot_get_thread_reg_list(struct rtos *rtos, int64_t thread_id,
		struct rtos_reg **reg_list, int *num_regs)
{
	int retval;
	const struct riot_params *param;

	if (!rtos)
		return ERROR_FAIL;

	if (thread_id == 0)
		return ERROR_FAIL;

	if (!rtos->rtos_specific_params)
		return ERROR_FAIL;

	param = (const struct riot_params *)rtos->rtos_specific_params;

	/* find the thread with given thread id */
	uint32_t threads_base = rtos->symbols[RIOT_THREADS_BASE].address;
	uint32_t tcb_pointer = 0;
	retval = target_read_u32(rtos->target,
			threads_base + (thread_id * 4),
			&tcb_pointer);
	if (retval != ERROR_OK) {
		LOG_ERROR("Can't parse `%s`", riot_symbol_list[RIOT_THREADS_BASE].name);
		return retval;
	}

	/* read stack pointer for that thread */
	uint32_t stackptr = 0;
	retval = target_read_u32(rtos->target,
			tcb_pointer + param->thread_sp_offset,
			&stackptr);
	if (retval != ERROR_OK) {
		LOG_ERROR("Can't parse `%s`", riot_symbol_list[RIOT_THREADS_BASE].name);
		return retval;
	}

	return rtos_generic_stack_read(rtos->target,
			stacking_info,
			stackptr,
			reg_list,
			num_regs);
}

static int riot_get_symbol_list_to_lookup(struct symbol_table_elem *symbol_list[])
{
	*symbol_list = calloc(ARRAY_SIZE(riot_symbol_list), sizeof(struct symbol_table_elem));

	if (!*symbol_list) {
		LOG_ERROR("RIOT: out of memory");
		return ERROR_FAIL;
	}

	for (unsigned int i = 0; i < ARRAY_SIZE(riot_symbol_list); i++) {
		(*symbol_list)[i].symbol_name = riot_symbol_list[i].name;
		(*symbol_list)[i].optional = riot_symbol_list[i].optional;
	}

	return ERROR_OK;
}

static bool riot_detect_rtos(struct target *target)
{
	if ((target->rtos->symbols) &&
		(target->rtos->symbols[RIOT_THREADS_BASE].address != 0)) {
		/* looks like RIOT */
		return true;
	}
	return false;
}

static int riot_create(struct target *target)
{
	unsigned int i = 0;

	/* lookup if target is supported by RIOT */
	while ((i < RIOT_NUM_PARAMS) &&
		(strcmp(riot_params_list[i].target_name, target->type->name) != 0)) {
		i++;
	}
	if (i >= RIOT_NUM_PARAMS) {
		LOG_ERROR("Could not find target in RIOT compatibility list");
		return ERROR_FAIL;
	}

	target->rtos->rtos_specific_params = (void *)&riot_params_list[i];
	target->rtos->current_thread = 0;
	target->rtos->thread_details = NULL;

	/* Stacking is different depending on architecture */
	struct armv7m_common *armv7m_target = target_to_armv7m(target);

	if (armv7m_target->arm.arch == ARM_ARCH_V6M)
		stacking_info = &rtos_riot_cortex_m0_stacking;
	else if (is_armv7m(armv7m_target))
		stacking_info = &rtos_riot_cortex_m34_stacking;
	else {
		LOG_ERROR("No stacking info for architecture");
		return ERROR_FAIL;
	}
	return ERROR_OK;
}
