diff options
Diffstat (limited to 'kexec/arch/ia64/kexec-elf-rel-ia64.c')
-rw-r--r-- | kexec/arch/ia64/kexec-elf-rel-ia64.c | 96 |
1 files changed, 91 insertions, 5 deletions
diff --git a/kexec/arch/ia64/kexec-elf-rel-ia64.c b/kexec/arch/ia64/kexec-elf-rel-ia64.c index eb5f662..6b2c388 100644 --- a/kexec/arch/ia64/kexec-elf-rel-ia64.c +++ b/kexec/arch/ia64/kexec-elf-rel-ia64.c @@ -1,8 +1,14 @@ +/* Most of the code in this file is + * based on arch/ia64/kernel/module.c in Linux kernel + */ + #include <stdio.h> #include <elf.h> #include "../../kexec.h" #include "../../kexec-elf.h" +#define MAX_LTOFF ((uint64_t) (1 << 22)) + int machine_verify_elf_rel(struct mem_ehdr *ehdr) { if (ehdr->ei_data != ELFDATA2LSB) { @@ -17,12 +23,40 @@ int machine_verify_elf_rel(struct mem_ehdr *ehdr) return 1; } +static void +ia64_patch (uint64_t insn_addr, uint64_t mask, uint64_t val) +{ + uint64_t m0, m1, v0, v1, b0, b1, *b = (uint64_t *) (insn_addr & -16); +# define insn_mask ((1UL << 41) - 1) + unsigned long shift; + + b0 = b[0]; b1 = b[1]; + shift = 5 + 41 * (insn_addr % 16); /* 5 bits of template, then 3 x 41-bit instructions */ + if (shift >= 64) { + m1 = mask << (shift - 64); + v1 = val << (shift - 64); + } else { + m0 = mask << shift; m1 = mask >> (64 - shift); + v0 = val << shift; v1 = val >> (64 - shift); + b[0] = (b0 & ~m0) | (v0 & m0); + } + b[1] = (b1 & ~m1) | (v1 & m1); +} + +static inline uint64_t +bundle (const uint64_t insn) +{ + return insn & ~0xfUL; +} + void machine_apply_elf_rel(struct mem_ehdr *ehdr, unsigned long r_type, void *location, unsigned long address, unsigned long value) { + uint64_t gp_value = ehdr->rel_addr + 0x200000; switch(r_type) { case R_IA64_NONE: break; + case R_IA64_SEGREL64LSB: case R_IA64_DIR64LSB: *((uint64_t *)location) = value; break; @@ -31,15 +65,67 @@ void machine_apply_elf_rel(struct mem_ehdr *ehdr, unsigned long r_type, if (value != *((uint32_t *)location)) goto overflow; break; - case R_IA64_PCREL21B: + case R_IA64_IMM64: + ia64_patch((uint64_t)location, 0x01fffefe000UL, + /* bit 63 -> 36 */ + (((value & 0x8000000000000000UL) >> 27) + /* bit 21 -> 21 */ + | ((value & 0x0000000000200000UL) << 0) + /* bit 16 -> 22 */ + | ((value & 0x00000000001f0000UL) << 6) + /* bit 7 -> 27 */ + | ((value & 0x000000000000ff80UL) << 20) + /* bit 0 -> 13 */ + | ((value & 0x000000000000007fUL) << 13))); + ia64_patch((uint64_t)location - 1, 0x1ffffffffffUL, value>>22); + break; + case R_IA64_IMM22: + if (value + (1 << 21) >= (1 << 22)) + die("value out of IMM22 range\n"); + ia64_patch((uint64_t)location, 0x01fffcfe000UL, + /* bit 21 -> 36 */ + (((value & 0x200000UL) << 15) + /* bit 16 -> 22 */ + | ((value & 0x1f0000UL) << 6) + /* bit 7 -> 27 */ + | ((value & 0x00ff80UL) << 20) + /* bit 0 -> 13 */ + | ((value & 0x00007fUL) << 13) )); + break; + case R_IA64_PCREL21B: { + uint64_t delta = ((int64_t)value - (int64_t)address)/16; + if (delta + (1 << 20) >= (1 << 21)) + die("value out of IMM21B range\n"); + value = ((int64_t)(value - bundle(address)))/16; + ia64_patch((uint64_t)location, 0x11ffffe000UL, + (((value & 0x100000UL) << 16) /* bit 20 -> 36 */ + | ((value & 0x0fffffUL) << 13) /* bit 0 -> 13 */)); + } + break; + case R_IA64_LTOFF22X: + if (value - gp_value + MAX_LTOFF/2 >= MAX_LTOFF) + die("value out of gp relative range"); + value -= gp_value; + ia64_patch((uint64_t)location, 0x01fffcfe000UL, + (((value & 0x200000UL) << 15) /* bit 21 -> 36 */ + |((value & 0x1f0000UL) << 6) /* bit 16 -> 22 */ + |((value & 0x00ff80UL) << 20) /* bit 7 -> 27 */ + |((value & 0x00007fUL) << 13) /* bit 0 -> 13 */)); + break; + case R_IA64_LDXMOV: + if (value - gp_value + MAX_LTOFF/2 >= MAX_LTOFF) + die("value out of gp relative range"); + ia64_patch((uint64_t)location, 0x1fff80fe000UL, 0x10000000000UL); + break; case R_IA64_LTOFF22: - case R_IA64_SEGREL64LSB: + default: - die("Unknown rela relocation: %lu\n", r_type); + die("Unknown rela relocation: 0x%lx 0x%lx\n", + r_type, address); break; } return; - overflow: +overflow: die("overflow in relocation type %lu val %Lx\n", - r_type, value); + r_type, value); } |