// 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; }