summaryrefslogtreecommitdiff
path: root/kexec/arch/ppc64/kexec-elf-rel-ppc64.c
blob: 54d506abd390822f440d82dc91ac9dff40720630 (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
#include <stdio.h>
#include <elf.h>
#include <string.h>
#include "../../kexec.h"
#include "../../kexec-elf.h"
#include "kexec-ppc64.h"

int machine_verify_elf_rel(struct mem_ehdr *ehdr)
{
	if (ehdr->ei_data != ELFDATA2MSB) {
		return 0;
	}
	if (ehdr->ei_class != ELFCLASS64) {
		return 0;
	}
	if (ehdr->e_machine != EM_PPC64) {
		return 0;
	}

	return 1;
}

static struct mem_shdr *toc_section(const struct mem_ehdr *ehdr)
{
	struct mem_shdr *shdr, *shdr_end;
	unsigned char *strtab;

	strtab = (unsigned char *)ehdr->e_shdr[ehdr->e_shstrndx].sh_data;
	shdr_end = &ehdr->e_shdr[ehdr->e_shnum];
	for (shdr = ehdr->e_shdr; shdr != shdr_end; shdr++) {
		if (shdr->sh_size &&
			strcmp((char *)&strtab[shdr->sh_name], ".toc") == 0) {
			return shdr;
		}
	}

	return NULL;
}

/* r2 is the TOC pointer: it actually points 0x8000 into the TOC (this
   gives the value maximum span in an instruction which uses a signed
   offset) */
unsigned long my_r2(const struct mem_ehdr *ehdr)
{
	struct mem_shdr *shdr;

	shdr = toc_section(ehdr);
	if (!shdr) {
		die("TOC reloc without a toc section?");
	}

	return shdr->sh_addr + 0x8000;
}

static void do_relative_toc(unsigned long value, uint16_t *location,
	unsigned long mask, int complain_signed)
{
	if (complain_signed && (value + 0x8000 > 0xffff)) {
		die("TOC16 relocation overflows (%lu)\n", value);
	}

	if ((~mask & 0xffff) & value) {
		die("bad TOC16 relocation (%lu)\n", value);
	}

	*location = (*location & ~mask) | (value & mask);
}

void machine_apply_elf_rel(struct mem_ehdr *ehdr, unsigned long r_type,
	void *location, unsigned long address, unsigned long value)
{
	switch(r_type) {
	case R_PPC64_ADDR32:
		/* Simply set it */
		*(uint32_t *)location = value;
		break;

	case R_PPC64_ADDR64:
	case R_PPC64_REL64:
		/* Simply set it */
		*(uint64_t *)location = value;
		break;

	case R_PPC64_REL32:
		*(uint32_t *)location = value - (uint32_t)(uint64_t)location;
		break;

	case R_PPC64_TOC:
		*(uint64_t *)location = my_r2(ehdr);
		break;

	case R_PPC64_TOC16:
		do_relative_toc(value - my_r2(ehdr), location, 0xffff, 1);
		break;

	case R_PPC64_TOC16_DS:
		do_relative_toc(value - my_r2(ehdr), location, 0xfffc, 1);
		break;

	case R_PPC64_TOC16_LO:
		do_relative_toc(value - my_r2(ehdr), location, 0xffff, 0);
		break;

	case R_PPC64_TOC16_LO_DS:
		do_relative_toc(value - my_r2(ehdr), location, 0xfffc, 0);
		break;

	case R_PPC64_TOC16_HI:
		do_relative_toc((value - my_r2(ehdr)) >> 16, location,
			0xffff, 0);
		break;

	case R_PPC64_TOC16_HA:
		do_relative_toc((value - my_r2(ehdr) + 0x8000) >> 16, location,
			0xffff, 0);
		break;

	case R_PPC64_REL24:
		/* Convert value to relative */
		value -= address;
		if (value + 0x2000000 > 0x3ffffff || (value & 3) != 0) {
			die("REL24 %li out of range!\n", (long int)value);
		}

		/* Only replace bits 2 through 26 */
		*(uint32_t *)location = (*(uint32_t *)location & ~0x03fffffc) |
					(value & 0x03fffffc);
		break;

	case R_PPC64_ADDR16_LO:
		*(uint16_t *)location = value & 0xffff;
		break;

	case R_PPC64_ADDR16_HI:
		*(uint16_t *)location = (value >> 16) & 0xffff;
		break;

	case R_PPC64_ADDR16_HA:
		*(uint16_t *)location = (((value + 0x8000) >> 16) & 0xffff);
		break;

	case R_PPC64_ADDR16_HIGHER:
		*(uint16_t *)location = (((uint64_t)value >> 32) & 0xffff);
		break;

	case R_PPC64_ADDR16_HIGHEST:
		*(uint16_t *)location = (((uint64_t)value >> 48) & 0xffff);
		break;

	default:
		die("Unknown rela relocation: %lu\n", r_type);
		break;
	}
}