Loading...
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 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 | // SPDX-License-Identifier: GPL-2.0-only #include <linux/mm.h> #include <linux/module.h> #include <asm/alternative.h> #include <asm/cacheflush.h> #include <asm/inst.h> #include <asm/sections.h> int __read_mostly alternatives_patched; EXPORT_SYMBOL_GPL(alternatives_patched); #define MAX_PATCH_SIZE (((u8)(-1)) / LOONGARCH_INSN_SIZE) static int __initdata_or_module debug_alternative; static int __init debug_alt(char *str) { debug_alternative = 1; return 1; } __setup("debug-alternative", debug_alt); #define DPRINTK(fmt, args...) \ do { \ if (debug_alternative) \ printk(KERN_DEBUG "%s: " fmt "\n", __func__, ##args); \ } while (0) #define DUMP_WORDS(buf, count, fmt, args...) \ do { \ if (unlikely(debug_alternative)) { \ int _j; \ union loongarch_instruction *_buf = buf; \ \ if (!(count)) \ break; \ \ printk(KERN_DEBUG fmt, ##args); \ for (_j = 0; _j < count - 1; _j++) \ printk(KERN_CONT "<%08x> ", _buf[_j].word); \ printk(KERN_CONT "<%08x>\n", _buf[_j].word); \ } \ } while (0) /* Use this to add nops to a buffer, then text_poke the whole buffer. */ static void __init_or_module add_nops(union loongarch_instruction *insn, int count) { while (count--) { insn->word = INSN_NOP; insn++; } } /* Is the jump addr in local .altinstructions */ static inline bool in_alt_jump(unsigned long jump, void *start, void *end) { return jump >= (unsigned long)start && jump < (unsigned long)end; } static void __init_or_module recompute_jump(union loongarch_instruction *buf, union loongarch_instruction *dest, union loongarch_instruction *src, void *start, void *end) { unsigned int si, si_l, si_h; unsigned long cur_pc, jump_addr, pc; long offset; cur_pc = (unsigned long)src; pc = (unsigned long)dest; si_l = src->reg0i26_format.immediate_l; si_h = src->reg0i26_format.immediate_h; switch (src->reg0i26_format.opcode) { case b_op: case bl_op: jump_addr = cur_pc + sign_extend64((si_h << 16 | si_l) << 2, 27); if (in_alt_jump(jump_addr, start, end)) return; offset = jump_addr - pc; BUG_ON(offset < -SZ_128M || offset >= SZ_128M); offset >>= 2; buf->reg0i26_format.immediate_h = offset >> 16; buf->reg0i26_format.immediate_l = offset; return; } si_l = src->reg1i21_format.immediate_l; si_h = src->reg1i21_format.immediate_h; switch (src->reg1i21_format.opcode) { case bceqz_op: /* bceqz_op = bcnez_op */ BUG_ON(buf->reg1i21_format.rj & BIT(4)); fallthrough; case beqz_op: case bnez_op: jump_addr = cur_pc + sign_extend64((si_h << 16 | si_l) << 2, 22); if (in_alt_jump(jump_addr, start, end)) return; offset = jump_addr - pc; BUG_ON(offset < -SZ_4M || offset >= SZ_4M); offset >>= 2; buf->reg1i21_format.immediate_h = offset >> 16; buf->reg1i21_format.immediate_l = offset; return; } si = src->reg2i16_format.immediate; switch (src->reg2i16_format.opcode) { case beq_op: case bne_op: case blt_op: case bge_op: case bltu_op: case bgeu_op: jump_addr = cur_pc + sign_extend64(si << 2, 17); if (in_alt_jump(jump_addr, start, end)) return; offset = jump_addr - pc; BUG_ON(offset < -SZ_128K || offset >= SZ_128K); offset >>= 2; buf->reg2i16_format.immediate = offset; return; } } static int __init_or_module copy_alt_insns(union loongarch_instruction *buf, union loongarch_instruction *dest, union loongarch_instruction *src, int nr) { int i; for (i = 0; i < nr; i++) { buf[i].word = src[i].word; if (is_pc_ins(&src[i])) { pr_err("Not support pcrel instruction at present!"); return -EINVAL; } if (is_branch_ins(&src[i]) && src[i].reg2i16_format.opcode != jirl_op) { recompute_jump(&buf[i], &dest[i], &src[i], src, src + nr); } } return 0; } /* * text_poke_early - Update instructions on a live kernel at boot time * * When you use this code to patch more than one byte of an instruction * you need to make sure that other CPUs cannot execute this code in parallel. * Also no thread must be currently preempted in the middle of these * instructions. And on the local CPU you need to be protected again NMI or MCE * handlers seeing an inconsistent instruction while you patch. */ static void *__init_or_module text_poke_early(union loongarch_instruction *insn, union loongarch_instruction *buf, unsigned int nr) { int i; unsigned long flags; local_irq_save(flags); for (i = 0; i < nr; i++) insn[i].word = buf[i].word; local_irq_restore(flags); wbflush(); flush_icache_range((unsigned long)insn, (unsigned long)(insn + nr)); return insn; } /* * Replace instructions with better alternatives for this CPU type. This runs * before SMP is initialized to avoid SMP problems with self modifying code. * This implies that asymmetric systems where APs have less capabilities than * the boot processor are not handled. Tough. Make sure you disable such * features by hand. */ void __init_or_module apply_alternatives(struct alt_instr *start, struct alt_instr *end) { struct alt_instr *a; unsigned int nr_instr, nr_repl, nr_insnbuf; union loongarch_instruction *instr, *replacement; union loongarch_instruction insnbuf[MAX_PATCH_SIZE]; DPRINTK("alt table %px, -> %px", start, end); /* * The scan order should be from start to end. A later scanned * alternative code can overwrite previously scanned alternative code. * Some kernel functions (e.g. memcpy, memset, etc) use this order to * patch code. * * So be careful if you want to change the scan order to any other * order. */ for (a = start; a < end; a++) { nr_insnbuf = 0; instr = (void *)&a->instr_offset + a->instr_offset; replacement = (void *)&a->replace_offset + a->replace_offset; BUG_ON(a->instrlen > sizeof(insnbuf)); BUG_ON(a->instrlen & 0x3); BUG_ON(a->replacementlen & 0x3); nr_instr = a->instrlen / LOONGARCH_INSN_SIZE; nr_repl = a->replacementlen / LOONGARCH_INSN_SIZE; if (!cpu_has(a->feature)) { DPRINTK("feat not exist: %d, old: (%px len: %d), repl: (%px, len: %d)", a->feature, instr, a->instrlen, replacement, a->replacementlen); continue; } DPRINTK("feat: %d, old: (%px len: %d), repl: (%px, len: %d)", a->feature, instr, a->instrlen, replacement, a->replacementlen); DUMP_WORDS(instr, nr_instr, "%px: old_insn: ", instr); DUMP_WORDS(replacement, nr_repl, "%px: rpl_insn: ", replacement); copy_alt_insns(insnbuf, instr, replacement, nr_repl); nr_insnbuf = nr_repl; if (nr_instr > nr_repl) { add_nops(insnbuf + nr_repl, nr_instr - nr_repl); nr_insnbuf += nr_instr - nr_repl; } DUMP_WORDS(insnbuf, nr_insnbuf, "%px: final_insn: ", instr); text_poke_early(instr, insnbuf, nr_insnbuf); } } void __init alternative_instructions(void) { apply_alternatives(__alt_instructions, __alt_instructions_end); alternatives_patched = 1; } |