1355961Sdim// SPDX-License-Identifier: GPL-2.0
2355961Sdim/*
3355961Sdim * (C) Copyright 2022 Advanced Micro Devices, Inc
4355961Sdim * Michal Simek <michal.simek@amd.com>
5355961Sdim */
6355961Sdim
7355961Sdim#include <elf.h>
8355961Sdim#include <log.h>
9355961Sdim#include <linux/types.h>
10355961Sdim
11355961Sdim#define R_MICROBLAZE_NONE	0
12355961Sdim#define R_MICROBLAZE_32		1
13355961Sdim#define R_MICROBLAZE_REL	16
14355961Sdim#define R_MICROBLAZE_GLOB_DAT	18
15355961Sdim
16355961Sdim/**
17355961Sdim * mb_fix_rela - update relocation to new address
18355961Sdim * @reloc_addr: new relocation address
19355961Sdim * @verbose: enable version messages
20355961Sdim * @rela_start: rela section start
21355961Sdim * @rela_end: rela section end
22355961Sdim * @dyn_start: dynamic section start
23355961Sdim * @origin_addr: address where u-boot starts(doesn't need to be CONFIG_TEXT_BASE)
24355961Sdim */
25355961Sdimvoid mb_fix_rela(u32 reloc_addr, u32 verbose, u32 rela_start,
26355961Sdim		 u32 rela_end, u32 dyn_start, u32 origin_addr)
27355961Sdim{
28355961Sdim	u32 num, type, mask, i, reloc_off;
29355961Sdim
30355961Sdim	/*
31355961Sdim	 * Return in case u-boot.elf is used directly.
32355961Sdim	 * Skip it when u-boot.bin is loaded to different address than
33355961Sdim	 * CONFIG_TEXT_BASE. In this case relocation is necessary to run.
34355961Sdim	 */
35355961Sdim	if (reloc_addr == CONFIG_TEXT_BASE) {
36355961Sdim		debug_cond(verbose,
37355961Sdim			   "Relocation address is the same - skip relocation\n");
38355961Sdim		return;
39355961Sdim	}
40355961Sdim
41355961Sdim	reloc_off = reloc_addr - origin_addr;
42355961Sdim
43355961Sdim	debug_cond(verbose, "Relocation address:\t0x%08x\n", reloc_addr);
44355961Sdim	debug_cond(verbose, "Relocation offset:\t0x%08x\n", reloc_off);
45355961Sdim	debug_cond(verbose, "Origin address:\t0x%08x\n", origin_addr);
46355961Sdim	debug_cond(verbose, "Rela start:\t0x%08x\n", rela_start);
47355961Sdim	debug_cond(verbose, "Rela end:\t0x%08x\n", rela_end);
48355961Sdim	debug_cond(verbose, "Dynsym start:\t0x%08x\n", dyn_start);
49355961Sdim
50355961Sdim	num = (rela_end - rela_start) / sizeof(Elf32_Rela);
51355961Sdim
52355961Sdim	debug_cond(verbose, "Number of entries:\t%u\n", num);
53355961Sdim
54355961Sdim	for (i = 0; i < num; i++) {
55355961Sdim		Elf32_Rela *rela;
56355961Sdim		u32 temp;
57355961Sdim
58355961Sdim		rela = (Elf32_Rela *)(rela_start + sizeof(Elf32_Rela) * i);
59355961Sdim
60355961Sdim		mask = 0xffULL; /* would be different on 32-bit */
61355961Sdim		type = rela->r_info & mask;
62355961Sdim
63355961Sdim		debug_cond(verbose, "\nRela possition:\t%d/0x%x\n",
64355961Sdim			   i, (u32)rela);
65355961Sdim
66355961Sdim		switch (type) {
67355961Sdim		case R_MICROBLAZE_REL:
68355961Sdim			temp = *(u32 *)rela->r_offset;
69355961Sdim
70360784Sdim			debug_cond(verbose, "Type:\tREL\n");
71355961Sdim			debug_cond(verbose, "Rela r_offset:\t\t0x%x\n", rela->r_offset);
72355961Sdim			debug_cond(verbose, "Rela r_info:\t\t0x%x\n", rela->r_info);
73355961Sdim			debug_cond(verbose, "Rela r_addend:\t\t0x%x\n", rela->r_addend);
74355961Sdim			debug_cond(verbose, "Value at r_offset:\t0x%x\n", temp);
75355961Sdim
76355961Sdim			rela->r_offset += reloc_off;
77355961Sdim			rela->r_addend += reloc_off;
78355961Sdim
79355961Sdim			temp = *(u32 *)rela->r_offset;
80355961Sdim			temp += reloc_off;
81355961Sdim			*(u32 *)rela->r_offset = temp;
82355961Sdim
83355961Sdim			debug_cond(verbose, "New:Rela r_offset:\t0x%x\n", rela->r_offset);
84355961Sdim			debug_cond(verbose, "New:Rela r_addend:\t0x%x\n", rela->r_addend);
85355961Sdim			debug_cond(verbose, "New:Value at r_offset:\t0x%x\n", temp);
86355961Sdim			break;
87355961Sdim		case R_MICROBLAZE_32:
88355961Sdim		case R_MICROBLAZE_GLOB_DAT:
89			debug_cond(verbose, "Type:\t(32/GLOB) %u\n", type);
90			debug_cond(verbose, "Rela r_offset:\t\t0x%x\n", rela->r_offset);
91			debug_cond(verbose, "Rela r_info:\t\t0x%x\n", rela->r_info);
92			debug_cond(verbose, "Rela r_addend:\t\t0x%x\n", rela->r_addend);
93			debug_cond(verbose, "Value at r_offset:\t0x%x\n", temp);
94
95			rela->r_offset += reloc_off;
96
97			temp = *(u32 *)rela->r_offset;
98			temp += reloc_off;
99			*(u32 *)rela->r_offset = temp;
100
101			debug_cond(verbose, "New:Rela r_offset:\t0x%x\n", rela->r_offset);
102			debug_cond(verbose, "New:Value at r_offset:\t0x%x\n", temp);
103			break;
104		case R_MICROBLAZE_NONE:
105			debug_cond(verbose, "R_MICROBLAZE_NONE - skip\n");
106			break;
107		default:
108			debug_cond(verbose, "warning: unsupported relocation type %d at %x\n",
109				   type, rela->r_offset);
110		}
111	}
112}
113