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