/**
 * @file arch/sparc/kernel/module.c
 *
 * @ingroup sparc
 * @brief implements architecture-specific @ref kernel_module interfaces
 */

#include <kernel/module.h>
#include <kernel/printk.h>
#include <kernel/err.h>


/**
 * @brief apply relocation + addend
 *
 * @param m an ELF module
 * @param rel an ELF relocation entry
 * @param sym the address of the target symbol
 * @param sec_name the name of the section to apply the relocation in
 *
 * return 0 on success
 */

int apply_relocate_add(struct elf_module *m, Elf_Rela *rel, Elf_Addr sym, const char *sec_name)
{
	Elf_Addr rsym;

	uint8_t  *loc8;
	uint32_t *loc32;

	struct module_section *text;



	if (!m)
		return -EINVAL;

	if (!rel)
		return -EINVAL;

	if (!sym)
		return -EINVAL;

	if (!sec_name) {
		pr_err("\tsec_name empty!\n");
		return -EINVAL;
	}


	text = find_mod_sec(m, sec_name);

	loc8  = (uint8_t  *) (text->addr + rel->r_offset);
	loc32 = (uint32_t *) loc8;


	/* the symbol address it is referring to */
	rsym = sym + rel->r_addend;


	/* SPARC relocations are annoying, these are just the most common ones,
	 * add as needed
	 * http://docs.oracle.com/cd/E23824_01/html/819-0690/chapter6-54839.html
	 */

	switch (ELF_R_TYPE(rel->r_info) & 0xff) {
	case R_SPARC_DISP32:	/* S + A - P */
		pr_debug("\tREL type R_SPARC_DISP32\n");
		rsym -= (Elf_Addr) loc8;
		(*loc32) = rsym;
		break;

	case R_SPARC_32:	/* S + A */
	case R_SPARC_UA32:	/* L + A */
		pr_debug("\tREL type R_SPARC_(UA)32\n");
		loc8[0] = rsym >> 24;
		loc8[1] = rsym >> 16;
		loc8[2] = rsym >>  8;
		loc8[3] = rsym >>  0;
		break;

	case R_SPARC_WDISP30:	/* (S + A - P) >> 2 */
		pr_debug("\tREL type R_SPARC_WDISP30\n");
		rsym -= (Elf_Addr) loc8;
		(*loc32) = ((*loc32) & ~(((1 << 30)) - 1)) |
			((rsym >> 2) &   ((1 << 30)  - 1));
		break;

	case R_SPARC_WDISP22:
		pr_debug("\tREL type R_SPARC_WDISP22\n");
		rsym -= (Elf_Addr) loc8;
		(*loc32) = ((*loc32) & ~(((1 << 22)) - 1)) |
			((rsym >> 2) &   ((1 << 22)  - 1));
		break;

	case R_SPARC_HI22:
		pr_debug("\tREL type R_SPARC_HI22\n");
		(*loc32) = ((*loc32) & ~(((1 << 22)) - 1)) |
			((rsym >> 10) &   ((1 << 22)  - 1));
		break;

	case R_SPARC_LO10:
		pr_debug("\tREL type R_SPARC_LO10\n");
		(*loc32) = ((*loc32) & ~(((1 << 10)) - 1)) |
			(rsym &   ((1 << 10)  - 1));
		break;

	default:
		pr_err("\tUnsupported relocation type: %x\n",
		       (ELF_R_TYPE(rel->r_info) & 0xff));
		return -ENOEXEC;
	}

	return 0;
}