summaryrefslogtreecommitdiffstats
path: root/kexec/arch/ppc64/kexec-elf-rel-ppc64.c
diff options
context:
space:
mode:
Diffstat (limited to 'kexec/arch/ppc64/kexec-elf-rel-ppc64.c')
-rw-r--r--kexec/arch/ppc64/kexec-elf-rel-ppc64.c204
1 files changed, 204 insertions, 0 deletions
diff --git a/kexec/arch/ppc64/kexec-elf-rel-ppc64.c b/kexec/arch/ppc64/kexec-elf-rel-ppc64.c
new file mode 100644
index 0000000..51b1354
--- /dev/null
+++ b/kexec/arch/ppc64/kexec-elf-rel-ppc64.c
@@ -0,0 +1,204 @@
+#include <stdio.h>
+#include <elf.h>
+#include <string.h>
+#include "../../kexec.h"
+#include "../../kexec-elf.h"
+#include "kexec-ppc64.h"
+
+#if defined(_CALL_ELF) && _CALL_ELF == 2
+#define STO_PPC64_LOCAL_BIT 5
+#define STO_PPC64_LOCAL_MASK (7 << STO_PPC64_LOCAL_BIT)
+#define PPC64_LOCAL_ENTRY_OFFSET(other) \
+ (((1 << (((other) & STO_PPC64_LOCAL_MASK) >> STO_PPC64_LOCAL_BIT)) >> 2) << 2)
+
+static unsigned int local_entry_offset(struct mem_sym *sym)
+{
+ /* If this symbol has a local entry point, use it. */
+ return PPC64_LOCAL_ENTRY_OFFSET(sym->st_other);
+}
+#else
+static unsigned int local_entry_offset(struct mem_sym *UNUSED(sym))
+{
+ return 0;
+}
+#endif
+
+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;
+}
+
+int machine_verify_elf_rel(struct mem_ehdr *ehdr)
+{
+ struct mem_shdr *toc;
+
+ if (ehdr->ei_class != ELFCLASS64) {
+ return 0;
+ }
+ if (ehdr->e_machine != EM_PPC64) {
+ return 0;
+ }
+
+ /* Ensure .toc is sufficiently aligned. */
+ toc = toc_section(ehdr);
+ if (toc && toc->sh_addralign < 256)
+ toc->sh_addralign = 256;
+ return 1;
+}
+
+/* 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, struct mem_sym *sym,
+ 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)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:
+ value += local_entry_offset(sym);
+ /* 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;
+
+ /* R_PPC64_REL16_HA and R_PPC64_REL16_LO are handled to support
+ * ABIv2 r2 assignment based on r12 for PIC executable.
+ * Here address is know so replace
+ * 0: addis 2,12,.TOC.-0b@ha
+ * addi 2,2,.TOC.-0b@l
+ * by
+ * lis 2,.TOC.@ha
+ * addi 2,2,.TOC.@l
+ */
+ case R_PPC64_REL16_HA:
+ /* check that we are dealing with the addis 2,12 instruction */
+ if (((*(uint32_t*)location) & 0xffff0000) != 0x3c4c0000)
+ die("Unexpected instruction for R_PPC64_REL16_HA");
+ value += my_r2(ehdr);
+ /* replacing by lis 2 */
+ *(uint32_t *)location = 0x3c400000 + ((value >> 16) & 0xffff);
+ break;
+
+ case R_PPC64_REL16_LO:
+ /* check that we are dealing with the addi 2,2 instruction */
+ if (((*(uint32_t*)location) & 0xffff0000) != 0x38420000)
+ die("Unexpected instruction for R_PPC64_REL16_LO");
+
+ value += my_r2(ehdr) - 4;
+ *(uint16_t *)location = value & 0xffff;
+ break;
+
+ default:
+ die("Unknown rela relocation: %lu\n", r_type);
+ break;
+ }
+}