Paul Burton | 703ec9d | 2017-06-19 11:53:47 -0700 | [diff] [blame] | 1 | /* |
| 2 | * MIPS Relocation |
| 3 | * |
| 4 | * Copyright (c) 2017 Imagination Technologies Ltd. |
| 5 | * |
| 6 | * SPDX-License-Identifier: GPL-2.0+ |
| 7 | * |
| 8 | * Relocation data, found in the .rel section, is generated by the mips-relocs |
| 9 | * tool & contains a record of all locations in the U-Boot binary that need to |
| 10 | * be fixed up during relocation. |
| 11 | * |
| 12 | * The data is a sequence of unsigned integers, which are of somewhat arbitrary |
| 13 | * size. This is achieved by encoding integers as a sequence of bytes, each of |
| 14 | * which contains 7 bits of data with the most significant bit indicating |
| 15 | * whether any further bytes need to be read. The least significant bits of the |
| 16 | * integer are found in the first byte - ie. it somewhat resembles little |
| 17 | * endian. |
| 18 | * |
| 19 | * Each pair of two integers represents a relocation that must be applied. The |
| 20 | * first integer represents the type of relocation as a standard ELF relocation |
| 21 | * type (ie. R_MIPS_*). The second integer represents the offset at which to |
| 22 | * apply the relocation, relative to the previous relocation or for the first |
| 23 | * relocation the start of the relocated .text section. |
| 24 | * |
| 25 | * The end of the relocation data is indicated when type R_MIPS_NONE (0) is |
| 26 | * read, at which point no further integers should be read. That is, the |
| 27 | * terminating R_MIPS_NONE reloc includes no offset. |
| 28 | */ |
| 29 | |
| 30 | #include <common.h> |
| 31 | #include <asm/relocs.h> |
| 32 | #include <asm/sections.h> |
| 33 | |
| 34 | /** |
| 35 | * read_uint() - Read an unsigned integer from the buffer |
| 36 | * @buf: pointer to a pointer to the reloc buffer |
| 37 | * |
| 38 | * Read one whole unsigned integer from the relocation data pointed to by @buf, |
| 39 | * advancing @buf past the bytes encoding the integer. |
| 40 | * |
| 41 | * Returns: the integer read from @buf |
| 42 | */ |
| 43 | static unsigned long read_uint(uint8_t **buf) |
| 44 | { |
| 45 | unsigned long val = 0; |
| 46 | unsigned int shift = 0; |
| 47 | uint8_t new; |
| 48 | |
| 49 | do { |
| 50 | new = *(*buf)++; |
| 51 | val |= (new & 0x7f) << shift; |
| 52 | shift += 7; |
| 53 | } while (new & 0x80); |
| 54 | |
| 55 | return val; |
| 56 | } |
| 57 | |
| 58 | /** |
| 59 | * apply_reloc() - Apply a single relocation |
| 60 | * @type: the type of reloc (R_MIPS_*) |
| 61 | * @addr: the address that the reloc should be applied to |
| 62 | * @off: the relocation offset, ie. number of bytes we're moving U-Boot by |
| 63 | * |
| 64 | * Apply a single relocation of type @type at @addr. This function is |
| 65 | * intentionally simple, and does the bare minimum needed to fixup the |
| 66 | * relocated U-Boot - in particular, it does not check for overflows. |
| 67 | */ |
| 68 | static void apply_reloc(unsigned int type, void *addr, long off) |
| 69 | { |
| 70 | uint32_t u32; |
| 71 | |
| 72 | switch (type) { |
| 73 | case R_MIPS_26: |
| 74 | u32 = *(uint32_t *)addr; |
| 75 | u32 = (u32 & GENMASK(31, 26)) | |
| 76 | ((u32 + (off >> 2)) & GENMASK(25, 0)); |
| 77 | *(uint32_t *)addr = u32; |
| 78 | break; |
| 79 | |
| 80 | case R_MIPS_32: |
| 81 | *(uint32_t *)addr += off; |
| 82 | break; |
| 83 | |
| 84 | case R_MIPS_64: |
| 85 | *(uint64_t *)addr += off; |
| 86 | break; |
| 87 | |
| 88 | case R_MIPS_HI16: |
| 89 | *(uint32_t *)addr += off >> 16; |
| 90 | break; |
| 91 | |
| 92 | default: |
| 93 | panic("Unhandled reloc type %u\n", type); |
| 94 | } |
| 95 | } |
| 96 | |
| 97 | /** |
| 98 | * relocate_code() - Relocate U-Boot, generally from flash to DDR |
| 99 | * @start_addr_sp: new stack pointer |
| 100 | * @new_gd: pointer to relocated global data |
| 101 | * @relocaddr: the address to relocate to |
| 102 | * |
| 103 | * Relocate U-Boot from its current location (generally in flash) to a new one |
| 104 | * (generally in DDR). This function will copy the U-Boot binary & apply |
| 105 | * relocations as necessary, then jump to board_init_r in the new build of |
| 106 | * U-Boot. As such, this function does not return. |
| 107 | */ |
| 108 | void relocate_code(ulong start_addr_sp, gd_t *new_gd, ulong relocaddr) |
| 109 | { |
| 110 | unsigned long addr, length, bss_len; |
| 111 | uint8_t *buf, *bss_start; |
| 112 | unsigned int type; |
| 113 | long off; |
| 114 | |
| 115 | /* |
| 116 | * Ensure that we're relocating by an offset which is a multiple of |
| 117 | * 64KiB, ie. doesn't change the least significant 16 bits of any |
| 118 | * addresses. This allows us to discard R_MIPS_LO16 relocs, saving |
| 119 | * space in the U-Boot binary & complexity in handling them. |
| 120 | */ |
| 121 | off = relocaddr - (unsigned long)__text_start; |
| 122 | if (off & 0xffff) |
| 123 | panic("Mis-aligned relocation\n"); |
| 124 | |
| 125 | /* Copy U-Boot to RAM */ |
| 126 | length = __image_copy_end - __text_start; |
| 127 | memcpy((void *)relocaddr, __text_start, length); |
| 128 | |
| 129 | /* Now apply relocations to the copy in RAM */ |
| 130 | buf = __rel_start; |
| 131 | addr = relocaddr; |
| 132 | while (true) { |
| 133 | type = read_uint(&buf); |
| 134 | if (type == R_MIPS_NONE) |
| 135 | break; |
| 136 | |
| 137 | addr += read_uint(&buf) << 2; |
| 138 | apply_reloc(type, (void *)addr, off); |
| 139 | } |
| 140 | |
| 141 | /* Ensure the icache is coherent */ |
| 142 | flush_cache(relocaddr, length); |
| 143 | |
| 144 | /* Clear the .bss section */ |
| 145 | bss_start = (uint8_t *)((unsigned long)__bss_start + off); |
| 146 | bss_len = (unsigned long)&__bss_end - (unsigned long)__bss_start; |
| 147 | memset(bss_start, 0, bss_len); |
| 148 | |
| 149 | /* Jump to the relocated U-Boot */ |
| 150 | asm volatile( |
| 151 | "move $29, %0\n" |
| 152 | " move $4, %1\n" |
| 153 | " move $5, %2\n" |
| 154 | " move $31, $0\n" |
| 155 | " jr %3" |
| 156 | : /* no outputs */ |
| 157 | : "r"(start_addr_sp), |
| 158 | "r"(new_gd), |
| 159 | "r"(relocaddr), |
| 160 | "r"((unsigned long)board_init_r + off)); |
| 161 | |
| 162 | /* Since we jumped to the new U-Boot above, we won't get here */ |
| 163 | unreachable(); |
| 164 | } |