/*	$NetBSD: kobj_machdep.c,v 1.6 2024/01/18 03:36:24 msaitoh Exp $	*/

/*-
 * Copyright (c) 2014,2023 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Matt Thomas of 3am Software Foundry, and by Nick Hudson.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/cdefs.h>

__RCSID("$NetBSD: kobj_machdep.c,v 1.6 2024/01/18 03:36:24 msaitoh Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kobj_impl.h>
#include <sys/kobj.h>
#include <sys/exec.h>
#include <sys/exec_elf.h>

#include <riscv/locore.h>

struct hi20 {
	void *where;
	long addend;
	bool local;
} last_hi20;

static int
kobj_findhi20(kobj_t ko, uintptr_t relocbase, bool local, const Elf_Rela *lo12,
    Elf_Addr *hi20addr)
{
	const Elf_Rela *relalim;
	const Elf_Rela *rela;
	/*
	 * Find the relocation section.
	 */
	for (size_t i = 0; i < ko->ko_nrela; i++) {
		rela = ko->ko_relatab[i].rela;
		if (rela == NULL) {
			continue;
		}
		relalim = rela + ko->ko_relatab[i].nrela;

		/* Check that the relocation is in this section */
		if (lo12 < rela || relalim < lo12)
			continue;

		/*
		 * Now find the HI20 relocation from the LO12 relocation
		 * address (hi20addr)
		 */
		for (; rela < relalim; rela++) {
			Elf_Addr * const where =
			    (Elf_Addr *)(relocbase + rela->r_offset);

			if (where != hi20addr) {
				continue;
			}
			/* Found the referenced HI20 relocation */
			uintptr_t symidx = ELF_R_SYM(rela->r_info);
#if 0
			if (symidx >= ko->ko_symcnt) {
				continue;
			}
#endif
			/*
			 * If this symbol doesn't is global and we're doing
			 * the local symbols at this point then we can't
			 * resolve it yet, so tell our caller.
			 */
			const Elf_Sym *sym = kobj_symbol(ko, symidx);
			if (local && ELF_ST_BIND(sym->st_info) != STB_LOCAL) {
				last_hi20.local = false;
				return 1;
			}

			last_hi20.local = true;

			Elf_Addr addr;
			const int error = kobj_sym_lookup(ko, symidx, &addr);
			if (error)
				return -1;

			long addend = rela->r_addend;	/* needs to be signed */
			addend -= (intptr_t)where;

			last_hi20.where = where;
			last_hi20.addend = addend;
			addend += addr;

			return 0;
		}
	}
	return -1;
}


int
kobj_reloc(kobj_t ko, uintptr_t relocbase, const void *data, bool isrela,
    bool local)
{
	if (!isrela) {
		panic("kobj_reloc: REL relocations not supported");
	}

	const Elf_Rela * const rela = (const Elf_Rela *)data;
	Elf_Addr * const where = (Elf_Addr *)(relocbase + rela->r_offset);
	long addend = rela->r_addend;	/* needs to be signed */
	uint32_t *wwhere = (uint32_t *)where;
	uint16_t * const hwhere = (uint16_t *)where;
	uint8_t * const bwhere = (uint8_t *)where;
	const u_int rtype = ELF_R_TYPE(rela->r_info);
	const u_int symidx = ELF_R_SYM(rela->r_info);
	const Elf_Sym *sym = kobj_symbol(ko, symidx);

	/*
	 * Check that we need to process this relocation in this pass.
	 * All relocations to local symols can be done in the first pass
	 * apart from PCREL_LO12 that reference a HI20 to a global symbol.
	 */
	switch (rtype) {
	case R_RISCV_PCREL_LO12_I:
	case R_RISCV_PCREL_LO12_S:
		if (addend != 0) {
			printf("oops\n");
		}
	case R_RISCV_PCREL_HI20:
	case R_RISCV_HI20:
		break;
	default:
		/* Don't do other relocations again */
		if (!local && ELF_ST_BIND(sym->st_info) == STB_LOCAL) {
			return 0;
		}
	}

	/* Relocation calculation */
	switch (rtype) {
	case R_RISCV_NONE:
	case R_RISCV_RELAX:
		return 0;

	case R_RISCV_BRANCH:
	case R_RISCV_JAL:
		// XXXNH eh? what's with the symidx test?'
		if (symidx == 0)
			break;
		/* FALLTHROUGH */

	case R_RISCV_CALL_PLT:
	case R_RISCV_CALL:
	case R_RISCV_PCREL_HI20:
	case R_RISCV_RVC_BRANCH:
	case R_RISCV_RVC_JUMP:
	case R_RISCV_32_PCREL:
		addend -= (intptr_t)where;		/* A -= P */
		/* FALLTHROUGH */

#ifdef _LP64
	case R_RISCV_64:	/* doubleword64 S + A */
	case R_RISCV_ADD64:
	case R_RISCV_SUB64:
#endif
	case R_RISCV_HI20:
	case R_RISCV_PCREL_LO12_I:
	case R_RISCV_PCREL_LO12_S:
	case R_RISCV_LO12_I:
	case R_RISCV_LO12_S:
	case R_RISCV_ADD32:
	case R_RISCV_ADD16:
	case R_RISCV_ADD8:
	case R_RISCV_SUB32:
	case R_RISCV_SUB16:
	case R_RISCV_SUB8:
	case R_RISCV_SUB6:
	case R_RISCV_SET32:
	case R_RISCV_SET16:
	case R_RISCV_SET8:
	case R_RISCV_SET6: {
		Elf_Addr addr;
		const int error = kobj_sym_lookup(ko, symidx, &addr);
		if (error) {
			/*
			 * This is a fatal error in the 2nd pass, i.e.
			 * local == false
			 */
			if (!local)
				return -1;
			return 0;
		}

		switch (rtype) {
		case R_RISCV_PCREL_HI20:
		case R_RISCV_HI20: {

			// XXXNH why is this without middle?
			last_hi20.addend = addend + addr;
			last_hi20.where = where;
			last_hi20.local = ELF_ST_BIND(sym->st_info) == STB_LOCAL;

			/*
			 * If this is the 2nd pass then and the symbol is
			 * local then it's already been fixed up.
			 */
			if (!local && last_hi20.local) {
//				last_hi20.global = false;
				return 0;
			}
			const uint32_t lobits = 12;
			const uint32_t middle = 1U << (lobits - 1);

			addend += addr + middle;
			break;
		    }
		case R_RISCV_PCREL_LO12_I:
		case R_RISCV_PCREL_LO12_S: {
			if (last_hi20.where != (void *)addr) {
				int err = kobj_findhi20(ko, relocbase, local,
				    rela, (Elf_Addr *)addr);
				if (err < 0)
					return -1;
				else if (err > 0) {
					KASSERT(local);
					return 0;
				}
			}
			/*
			 * In the second pass if the HI20 symbol was local
			 * it was already processed.
			 */
			if (!local && last_hi20.local) {
				return 0;
			}
			addend = addend + (last_hi20.addend & __BITS(11,0));
			break;
		    }
		default:
			addend += addr;				/* A += S */
		}
		break;
	    }

	default:
		printf("%s: unexpected relocation type %u\n\n", __func__, rtype);
		return -1;
	}

	/* Relocation memory update */
	switch (rtype) {
	case R_RISCV_64:	/* word64 S + A */
		*where = addend;
		break;

	case R_RISCV_32:	/* word32 S + A */
	case R_RISCV_SET32:	/* word32 S + A */
	case R_RISCV_32_PCREL:	/* word32 S + A - P */
		*wwhere = addend;
		break;

	case R_RISCV_SET16:	/* word16 S + A */
		*hwhere = addend;
		break;

	case R_RISCV_SET8:	/* word8 S + A */
		*bwhere = addend;
		break;

	case R_RISCV_SET6: {	/* word6 S + A */
		const uint8_t mask = __BITS(5, 0);

		*bwhere = (*bwhere & ~mask) | __SHIFTIN(addend, mask);
		break;
	    }

	case R_RISCV_ADD64:	/* word64 V + S + A */
		*where += addend;
		break;

	case R_RISCV_ADD32:	/* word32 V + S + A */
		*wwhere += addend;
		break;

	case R_RISCV_ADD16:	/* word16 V + S + A */
		*hwhere += addend;
		break;

	case R_RISCV_ADD8:	/* word8 V + S + A */
		*bwhere += addend;
		break;

	case R_RISCV_SUB64:	/* word64 V - S - A */
		*where -= addend;
		break;

	case R_RISCV_SUB32:	/* word32 V - S - A */
		*wwhere -= addend;
		break;

	case R_RISCV_SUB16:	/* word16 V - S - A */
		*hwhere -= addend;
		break;

	case R_RISCV_SUB8:	/* word8 V - S - A */
		*bwhere -= addend;
		break;

	case R_RISCV_SUB6:	/* word6 V - S - A */
		// XXXNH
		*bwhere -= addend;
		break;

	case R_RISCV_BRANCH: {
		/*
		 * B-type
		 *   31    | 30     25 | ... | 11     8 |    7     | ...
		 * imm[12] | imm[10:5] | ... | imm[4:1] | imm[11]  | ...
		 */

		const uint32_t immA = __SHIFTOUT(addend, __BIT(12));
		const uint32_t immB = __SHIFTOUT(addend, __BITS(10,  5));
		const uint32_t immC = __SHIFTOUT(addend, __BITS( 4,  1));
		const uint32_t immD = __SHIFTOUT(addend, __BIT(11));
		addend =
		    __SHIFTIN(immA, __BIT(31)) |
		    __SHIFTIN(immB, __BITS(30, 25)) |
		    __SHIFTIN(immC, __BITS(11, 8)) |
		    __SHIFTIN(immD, __BIT(7));
		const uint32_t mask = __BITS(31, 25) | __BITS(11, 7);

		*wwhere = (*wwhere & ~mask) | addend;
		break;
	    }

	case R_RISCV_JAL: {
		/*
		 * J-type
		 *   31    | 30     21 |   20    | 19      12 | ...
		 * imm[20] | imm[10:1] | imm[11] | imm[19:12] | ...
		 */

		const uint32_t immA = __SHIFTOUT(addend,  __BIT(20));
		const uint32_t immB = __SHIFTOUT(addend, __BITS(10,  1));
		const uint32_t immC = __SHIFTOUT(addend,  __BIT(11));
		const uint32_t immD = __SHIFTOUT(addend, __BITS(19, 12));
		addend =
		    __SHIFTIN(immA,  __BIT(31)) |
		    __SHIFTIN(immB, __BITS(30, 21)) |
		    __SHIFTIN(immC,  __BIT(20)) |
		    __SHIFTIN(immD, __BITS(19,12));
		/* FALLTHROUGH */
	    }

	case R_RISCV_HI20:		/* LUI/AUIPC (S + A - P) */
	case R_RISCV_PCREL_HI20: {	/* LUI/AUIPC (S + A - P) */
		/*
		 * U-Type
		 * 31      12 | ...
		 * imm[31:12] | ...
		 */
		const uint32_t mask = __BITS(31, 12);
		*wwhere = (addend & mask) | (*wwhere & ~mask);
		break;
	    }
	case R_RISCV_PCREL_LO12_I:
	case R_RISCV_LO12_I: {		/* low12 I-type instructions */
		/*
		 * I-Type
		 * 31     20 | ...
		 * imm[11:0] | ...
		 */

		*wwhere += ((addend) << 20);
		break;
	    }
	case R_RISCV_PCREL_LO12_S:
	case R_RISCV_LO12_S: {	/* low12 S-type instructions */
		/*
		 * S-type
		 *
		 * 31     25 | ... | 11     7 | ...
		 * imm[11:5] | ... | imm[4:0] | ...
		 *
		 */
		const uint32_t immA = __SHIFTOUT(addend, __BITS(11,  5));
		const uint32_t immB = __SHIFTOUT(addend, __BITS( 4,  0));
		const uint32_t mask = __BITS(31, 25) | __BITS(11, 7);
		addend =
		    __SHIFTIN(immA, __BITS(31, 25)) |
		    __SHIFTIN(immB, __BITS(11,  7));
		*wwhere = (*wwhere & ~mask) | addend;
		break;
	    }
	case R_RISCV_CALL:
	case R_RISCV_CALL_PLT: {
		/*
		 * U-type
		 *
		 * 31      12 | ...
		 * imm[31:12] | ...
		 *
		 *
		 * I-type
		 *
		 * 31     25 | ...
		 * imm[11:0] | ...
		 */
		// XXXNH better name...
		const int32_t check = (int32_t)addend >> 20;
		if (check == 0 || check == -1) {
			// This falls into the range for the JAL instruction.
			/*
			* J-type
			*   31    | 30     21 |   20    | 19      12 | ...
			* imm[20] | imm[10:1] | imm[11] | imm[19:12] | ...
			*/

			const uint32_t immA = __SHIFTOUT(addend,  __BIT(20));
			const uint32_t immB = __SHIFTOUT(addend, __BITS(10,  1));
			const uint32_t immC = __SHIFTOUT(addend,  __BIT(11));
			const uint32_t immD = __SHIFTOUT(addend, __BITS(19, 12));
			addend =
			    __SHIFTIN(immA,  __BIT(31)) |
			    __SHIFTIN(immB, __BITS(30, 21)) |
			    __SHIFTIN(immC,  __BIT(20)) |
			    __SHIFTIN(immD, __BITS(19,12));

			// Convert the auipc/jalr to a jal/nop
			wwhere[0] = addend | (wwhere[1] & 0xf80) | 0x6f;
			wwhere[1] = 0x00000013;	// nop (addi x0, x0, 0)
			printf("%s: %s where (%p) [0] -> %x, [1] -> %x\n", __func__,
			    "R_RISCV_CALL", wwhere, wwhere[0], wwhere[1]);
			break;
		}
		wwhere[0] = ((addend + 0x800) & 0xfffff000)
		    | (wwhere[0] & 0xfff);
		wwhere[1] = (addend << 20) | (wwhere[1] & 0x000fffff);
		printf("%s: %s where (%p) [0] -> %x, [1] -> %x\n", __func__,
		    "R_RISCV_CALL", wwhere, wwhere[0], wwhere[1]);

		break;
	    }
	case R_RISCV_RVC_BRANCH: {
		/*
		 * CB-type
		 *
		 * ... | 12         10 | ... | 6        5   3 2  | ...
		 * ... | offset[8|4:3] | ... | offset[7:6|2:1|5] | ...
		 */

		const uint16_t immA = __SHIFTOUT(addend, __BIT(8));
		const uint16_t immB = __SHIFTOUT(addend, __BITS(4, 3));
		const uint16_t immC = __SHIFTOUT(addend, __BITS(7, 6));
		const uint16_t immD = __SHIFTOUT(addend, __BITS(2, 1));
		const uint16_t immE = __SHIFTOUT(addend, __BIT(5));
		const uint16_t mask = __BITS(12, 10) | __BITS(6, 2);
		addend =
		    __SHIFTIN(immA, __BIT(12)) |
		    __SHIFTIN(immB, __BITS(11, 10)) |
		    __SHIFTIN(immC, __BITS( 6,  5)) |
		    __SHIFTIN(immD, __BITS( 4,  3)) |
		    __SHIFTIN(immE, __BIT(2));
		*hwhere = (*hwhere & ~mask) | addend;
		break;
	    }

	case R_RISCV_RVC_JUMP: {
		/*
		 * CJ-type
		 *
		 * ... | 12                          2 | ...
		 * ... | offset[11|4|9:8|10|6|7|3:1|5] | ...
		 */

		const uint16_t immA = __SHIFTOUT(addend, __BIT(11));
		const uint16_t immB = __SHIFTOUT(addend, __BIT(4));
		const uint16_t immC = __SHIFTOUT(addend, __BITS(9, 8));
		const uint16_t immD = __SHIFTOUT(addend, __BIT(10));
		const uint16_t immE = __SHIFTOUT(addend, __BIT(6));
		const uint16_t immF = __SHIFTOUT(addend, __BIT(7));
		const uint16_t immG = __SHIFTOUT(addend, __BITS(3, 1));
		const uint16_t immH = __SHIFTOUT(addend, __BIT(5));
		const uint16_t mask = __BITS(12, 2);
		addend =
		    __SHIFTIN(immA, __BIT(12)) |
		    __SHIFTIN(immB, __BIT(11)) |
		    __SHIFTIN(immC, __BITS(10,  9)) |
		    __SHIFTIN(immD, __BIT(8)) |
		    __SHIFTIN(immE, __BIT(7)) |
		    __SHIFTIN(immF, __BIT(6)) |
		    __SHIFTIN(immG, __BITS(5, 3)) |
		    __SHIFTIN(immH, __BIT(2));
		*hwhere = (*hwhere & ~mask) | addend;
		break;
	    }

	case R_RISCV_RVC_LUI: {
		/*
		 * CI-type
		 *
		 * ... | 12         | ... | 6           2 | ...
		 * ... | offset[17] | ... | offset[16:12] | ...
		 */

		const uint16_t immA = __SHIFTOUT(addend, __BIT(17));
		const uint16_t immB = __SHIFTOUT(addend, __BITS(16,12));
		const uint16_t mask = __BIT(12) | __BITS(6,  2);
		addend =
		    __SHIFTIN(immA, __BIT(12)) |
		    __SHIFTIN(immB, __BITS(6, 2));
		*hwhere = (*hwhere & ~mask) | addend;
		break;
	    }

	default:
		printf("Unexpected relocation type %d\n", rtype);
	}

	return 0;
}

int
kobj_machdep(kobj_t ko, void *base, size_t size, bool load)
{
	if (load)
		__asm("fence rw,rw; fence.i");

	return 0;
}
