/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*-
 *
 * This file is part of PRoot.
 *
 * Copyright (C) 2014 STMicroelectronics
 *
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 */

#include <assert.h>

#include "syscall/sysnum.h"
#include "tracee/tracee.h"
#include "tracee/abi.h"
#include "tracee/reg.h"
#include "arch.h"
#include "cli/notice.h"

#include SYSNUMS_HEADER1

#ifdef SYSNUMS_HEADER2
#include SYSNUMS_HEADER2
#endif

#ifdef SYSNUMS_HEADER3
#include SYSNUMS_HEADER3
#endif

typedef struct {
	const Sysnum *table;
	word_t offset;
	word_t length;
} Sysnums;

/**
 * Update @sysnums' fields with the sysnum table for the given @abi.
 */
static void get_sysnums(Abi abi, Sysnums *sysnums)
{
	switch (abi) {
	case ABI_DEFAULT:
		sysnums->table  = SYSNUMS_ABI1;
		sysnums->length = sizeof(SYSNUMS_ABI1) / sizeof(Sysnum);
		sysnums->offset = 0;
		return;
#ifdef SYSNUMS_ABI2
	case ABI_2:
		sysnums->table  = SYSNUMS_ABI2;
		sysnums->length = sizeof(SYSNUMS_ABI2) / sizeof(Sysnum);
		sysnums->offset = 0;
		return;
#endif
#ifdef SYSNUMS_ABI3
	case ABI_3:
		sysnums->table  = SYSNUMS_ABI3;
		sysnums->length = sizeof(SYSNUMS_ABI3) / sizeof(Sysnum);
		sysnums->offset = 0x40000000; /* x32 */
		return;
#endif
	default:
		assert(0);
	}
}

/**
 * Return the neutral value of @sysnum from the given @abi.
 */
static Sysnum translate_sysnum(Abi abi, word_t sysnum)
{
	Sysnums sysnums;
	word_t index;

	get_sysnums(abi, &sysnums);

	/* Sanity checks.  */
	if (sysnum < sysnums.offset)
		return PR_void;

	index = sysnum - sysnums.offset;

	/* Sanity checks.  */
	if (index > sysnums.length)
		return PR_void;

	return sysnums.table[index];
}

/**
 * Return the architecture value of @sysnum for the given @abi.
 */
word_t detranslate_sysnum(Abi abi, Sysnum sysnum)
{
	Sysnums sysnums;
	size_t i;

	/* Very special case.  */
	if (sysnum == PR_void)
		return SYSCALL_AVOIDER;

	get_sysnums(abi, &sysnums);

	for (i = 0; i < sysnums.length; i++) {
		if (sysnums.table[i] != sysnum)
			continue;

		return i + sysnums.offset;
	}

	return SYSCALL_AVOIDER;
}

/**
 * Return the neutral value of the @tracee's current syscall number.
 */
Sysnum get_sysnum(const Tracee *tracee, RegVersion version)
{
	return translate_sysnum(get_abi(tracee), peek_reg(tracee, version, SYSARG_NUM));
}

/**
 * Overwrite the @tracee's current syscall number with @sysnum.  Note:
 * this neutral value is automatically converted into the architecture
 * value.
 */
void set_sysnum(Tracee *tracee, Sysnum sysnum)
{
	poke_reg(tracee, SYSARG_NUM, detranslate_sysnum(get_abi(tracee), sysnum));
}

/**
 * Return the human readable name of @sysnum.
 */
const char *stringify_sysnum(Sysnum sysnum)
{
	#define SYSNUM(item) [ PR_ ## item ] = #item,
	static const char *names[] = {
		#include "syscall/sysnums.list"
	};
	#undef SYSNUM

	if (sysnum == 0)
		return "void";

	if (sysnum >= PR_NB_SYSNUM)
		return "";

	return names[sysnum];
}
