summaryrefslogtreecommitdiffstats
path: root/arch/mips/lib/reloc.c
blob: 4b0e252352d683f278356780f60688f9b6344fdf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
// 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>

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);
	}
}

void relocate_code(void *fdt, u32 fdt_size, u32 ram_size)
{
	unsigned long addr, length, bss_len;
	u32 relocaddr, new_stack;
	uint8_t *buf;
	unsigned int type;
	long off;

	bss_len = (unsigned long)&__bss_stop - (unsigned long)__bss_start;
	memset(__bss_start, 0, bss_len);
	cpu_probe();

	length = __bss_stop - __image_start;
	relocaddr = ALIGN_DOWN(ram_size - length, 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, barebox_image_size);

	/* 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();

	 __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();
}