summaryrefslogtreecommitdiff
path: root/arch/arc/lib/relocate.c
blob: 4ffba84eeb3ec2a3d022111f0b4f52182f947de7 (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
// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (C) 2013-2014 Synopsys, Inc. All rights reserved.
 */

#include <common.h>
#include <elf.h>
#include <asm-generic/sections.h>

extern ulong __image_copy_start;
extern ulong __ivt_start;
extern ulong __ivt_end;
extern ulong __text_end;

DECLARE_GLOBAL_DATA_PTR;

int copy_uboot_to_ram(void)
{
	size_t len = (size_t)&__image_copy_end - (size_t)&__image_copy_start;

	if (gd->flags & GD_FLG_SKIP_RELOC)
		return 0;

	memcpy((void *)gd->relocaddr, (void *)&__image_copy_start, len);

	return 0;
}

int clear_bss(void)
{
	ulong dst_addr = (ulong)&__bss_start + gd->reloc_off;
	size_t len = (size_t)&__bss_end - (size_t)&__bss_start;

	memset((void *)dst_addr, 0x00, len);

	return 0;
}

/*
 * Base functionality is taken from x86 version with added ARC-specifics
 */
int do_elf_reloc_fixups(void)
{
	Elf32_Rela *re_src = (Elf32_Rela *)(&__rel_dyn_start);
	Elf32_Rela *re_end = (Elf32_Rela *)(&__rel_dyn_end);

	if (gd->flags & GD_FLG_SKIP_RELOC)
		return 0;

	debug("Section .rela.dyn is located at %08x-%08x\n",
	      (unsigned int)re_src, (unsigned int)re_end);

	Elf32_Addr *offset_ptr_rom;
	Elf32_Addr *offset_ptr_ram;

	do {
		/* Get the location from the relocation entry */
		offset_ptr_rom = (Elf32_Addr *)re_src->r_offset;

		/* Check that the location of the relocation is in .text */
		if (offset_ptr_rom >= (Elf32_Addr *)&__image_copy_start &&
		    offset_ptr_rom < (Elf32_Addr *)&__image_copy_end) {
			unsigned int val, do_swap = 0;
			/* Switch to the in-RAM version */
			offset_ptr_ram = (Elf32_Addr *)((ulong)offset_ptr_rom +
							gd->reloc_off);

#ifdef __LITTLE_ENDIAN__
			/* If location in ".text" section swap value */
			if (((u32)offset_ptr_rom >= (u32)&__text_start &&
			     (u32)offset_ptr_rom <= (u32)&__text_end)
#if defined(__ARC700__) || defined(__ARC600__)
			    || ((u32)offset_ptr_rom >= (u32)&__ivt_start &&
				(u32)offset_ptr_rom <= (u32)&__ivt_end)
#endif
			   )
				do_swap = 1;
#endif

			debug("Patching value @ %08x (relocated to %08x)%s\n",
			      (unsigned int)offset_ptr_rom,
			      (unsigned int)offset_ptr_ram,
			      do_swap ? ", middle-endian encoded" : "");

			/*
			 * Use "memcpy" because target location might be
			 * 16-bit aligned on ARC so we may need to read
			 * byte-by-byte. On attempt to read entire word by
			 * CPU throws an exception
			 */
			memcpy(&val, offset_ptr_ram, sizeof(int));

			if (do_swap)
				val = (val << 16) | (val >> 16);

			/* Check that the target points into executable */
			if (val < (unsigned int)&__image_copy_start ||
			    val > (unsigned int)&__image_copy_end) {
				/* TODO: Use panic() instead of debug()
				 *
				 * For some reason GCC might generate
				 * fake relocation even for LD/SC of constant
				 * inderectly. See an example below:
				 * ----------------------->8--------------------
				 * static int setup_mon_len(void)
				 * {
				 *         gd->mon_len = (ulong)&__bss_end - CONFIG_SYS_MONITOR_BASE;
				 *         return 0;
				 * }
				 * ----------------------->8--------------------
				 *
				 * And that's what we get in the binary:
				 * ----------------------->8--------------------
				 * 10005cb4 <setup_mon_len>:
				 * 10005cb4:       193c 3f80 0003 2f80     st      0x32f80,[r25,60]
				 *                         10005cb8: R_ARC_RELATIVE        *ABS*-0x10000000
				 * 10005cbc:       7fe0                    j_s.d   [blink]
				 * 10005cbe:       700c                    mov_s   r0,0
				 * ----------------------->8--------------------
				 */
				debug("Relocation target %08x points outside of image\n",
				      val);
			}

			val += gd->reloc_off;

			if (do_swap)
				val = (val << 16) | (val >> 16);

			memcpy(offset_ptr_ram, &val, sizeof(int));
		}
	} while (++re_src < re_end);

	return 0;
}