summaryrefslogtreecommitdiffstats
path: root/arch/mips/lib/reloc.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/mips/lib/reloc.c')
-rw-r--r--arch/mips/lib/reloc.c182
1 files changed, 182 insertions, 0 deletions
diff --git a/arch/mips/lib/reloc.c b/arch/mips/lib/reloc.c
new file mode 100644
index 0000000000..9756d61666
--- /dev/null
+++ b/arch/mips/lib/reloc.c
@@ -0,0 +1,182 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * MIPS Relocation
+ *
+ * Copyright (c) 2017 Imagination Technologies Ltd.
+ *
+ * Relocation data, found in the .rel section, is generated by the mips-relocs
+ * tool & contains a record of all locations in the Barebox binary that need to
+ * be fixed up during relocation.
+ *
+ * The data is a sequence of unsigned integers, which are of somewhat arbitrary
+ * size. This is achieved by encoding integers as a sequence of bytes, each of
+ * which contains 7 bits of data with the most significant bit indicating
+ * whether any further bytes need to be read. The least significant bits of the
+ * integer are found in the first byte - ie. it somewhat resembles little
+ * endian.
+ *
+ * Each pair of two integers represents a relocation that must be applied. The
+ * first integer represents the type of relocation as a standard ELF relocation
+ * type (ie. R_MIPS_*). The second integer represents the offset at which to
+ * apply the relocation, relative to the previous relocation or for the first
+ * relocation the start of the relocated .text section.
+ *
+ * The end of the relocation data is indicated when type R_MIPS_NONE (0) is
+ * read, at which point no further integers should be read. That is, the
+ * terminating R_MIPS_NONE reloc includes no offset.
+ */
+
+#include <common.h>
+
+#include <asm/bitops.h>
+#include <asm/cache.h>
+#include <asm/cacheops.h>
+#include <asm/cpu.h>
+#include <asm/cpu-info.h>
+#include <asm/io.h>
+#include <asm/mipsregs.h>
+#include <asm/relocs.h>
+#include <asm/sections.h>
+#include <linux/sizes.h>
+#include <asm-generic/memory_layout.h>
+
+#define MAX_BSS_SIZE SZ_1M
+
+void main_entry(void *fdt, u32 fdt_size);
+void relocate_code(void *fdt, u32 fdt_size, u32 relocaddr);
+
+/**
+ * read_uint() - Read an unsigned integer from the buffer
+ * @buf: pointer to a pointer to the reloc buffer
+ *
+ * Read one whole unsigned integer from the relocation data pointed to by @buf,
+ * advancing @buf past the bytes encoding the integer.
+ *
+ * Returns: the integer read from @buf
+ */
+static unsigned long read_uint(uint8_t **buf)
+{
+ unsigned long val = 0;
+ unsigned int shift = 0;
+ uint8_t new;
+
+ do {
+ new = *(*buf)++;
+ val |= (new & 0x7f) << shift;
+ shift += 7;
+ } while (new & 0x80);
+
+ return val;
+}
+
+/**
+ * apply_reloc() - Apply a single relocation
+ * @type: the type of reloc (R_MIPS_*)
+ * @addr: the address that the reloc should be applied to
+ * @off: the relocation offset, ie. number of bytes we're moving Barebox by
+ *
+ * Apply a single relocation of type @type at @addr. This function is
+ * intentionally simple, and does the bare minimum needed to fixup the
+ * relocated Barebox - in particular, it does not check for overflows.
+ */
+static void apply_reloc(unsigned int type, void *addr, long off)
+{
+ uint32_t u32;
+
+ switch (type) {
+ case R_MIPS_26:
+ u32 = *(uint32_t *)addr;
+ u32 = (u32 & GENMASK(31, 26)) |
+ ((u32 + (off >> 2)) & GENMASK(25, 0));
+ *(uint32_t *)addr = u32;
+ break;
+
+ case R_MIPS_32:
+ *(uint32_t *)addr += off;
+ break;
+
+ case R_MIPS_64:
+ *(uint64_t *)addr += off;
+ break;
+
+ case R_MIPS_HI16:
+ *(uint32_t *)addr += off >> 16;
+ break;
+
+ default:
+ panic("Unhandled reloc type %u\n", type);
+ }
+}
+
+/**
+ * relocate_code() - Relocate Barebox, generally from flash to DDR
+ * @start_addr_sp: new stack pointer
+ * @new_gd: pointer to relocated global data
+ * @relocaddr: the address to relocate to
+ *
+ * Relocate Barebox from its current location (generally in flash) to a new one
+ * (generally in DDR). This function will copy the Barebox binary & apply
+ * relocations as necessary, then jump to board_init_r in the new build of
+ * Barebox. As such, this function does not return.
+ */
+void relocate_code(void *fdt, u32 fdt_size, u32 ram_size)
+{
+ unsigned long addr, length, bss_len;
+ u32 relocaddr, new_stack;
+ uint8_t *buf, *bss_start;
+ unsigned int type;
+ long off;
+
+ length = barebox_image_size + MAX_BSS_SIZE;
+ relocaddr = ALIGN_DOWN(ram_size - barebox_image_size, SZ_64K);
+ relocaddr = KSEG0ADDR(relocaddr);
+ new_stack = relocaddr - MALLOC_SIZE - 16;
+
+ /*
+ * Ensure that we're relocating by an offset which is a multiple of
+ * 64KiB, ie. doesn't change the least significant 16 bits of any
+ * addresses. This allows us to discard R_MIPS_LO16 relocs, saving
+ * space in the Barebox binary & complexity in handling them.
+ */
+ off = relocaddr - (unsigned long)__image_start;
+ if (off & 0xffff)
+ panic("Mis-aligned relocation\n");
+
+ /* Copy Barebox to RAM */
+ memcpy((void *)relocaddr, __image_start, length);
+
+ /* Now apply relocations to the copy in RAM */
+ buf = __rel_start;
+ addr = relocaddr;
+ while (true) {
+ type = read_uint(&buf);
+ if (type == R_MIPS_NONE)
+ break;
+
+ addr += read_uint(&buf) << 2;
+ apply_reloc(type, (void *)addr, off);
+ }
+
+ /* Ensure the icache is coherent */
+ flush_cache_all();
+
+ /* Clear the .bss section */
+ bss_start = (uint8_t *)((unsigned long)__bss_start + off);
+ bss_len = (unsigned long)&__bss_stop - (unsigned long)__bss_start;
+ memset(bss_start, 0, bss_len);
+
+ __asm__ __volatile__ (
+ "move $a0, %0\n"
+ " move $a1, %1\n"
+ " move $31, $0\n"
+ " move $sp, %2\n"
+ " jr %3\n"
+ : /* no outputs */
+ : "r"(fdt),
+ "r"(fdt_size),
+ "r"(new_stack),
+ "r"((unsigned long)main_entry + off));
+
+ /* Since we jumped to the new Barebox above, we won't get here */
+ unreachable();
+}