18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci#include <linux/mm.h> 38c2ecf20Sopenharmony_ci#include <linux/module.h> 48c2ecf20Sopenharmony_ci#include <asm/alternative.h> 58c2ecf20Sopenharmony_ci#include <asm/cacheflush.h> 68c2ecf20Sopenharmony_ci#include <asm/inst.h> 78c2ecf20Sopenharmony_ci#include <asm/sections.h> 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ciint __read_mostly alternatives_patched; 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(alternatives_patched); 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci#define MAX_PATCH_SIZE (((u8)(-1)) / LOONGARCH_INSN_SIZE) 148c2ecf20Sopenharmony_ci 158c2ecf20Sopenharmony_cistatic int __initdata_or_module debug_alternative; 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_cistatic int __init debug_alt(char *str) 188c2ecf20Sopenharmony_ci{ 198c2ecf20Sopenharmony_ci debug_alternative = 1; 208c2ecf20Sopenharmony_ci return 1; 218c2ecf20Sopenharmony_ci} 228c2ecf20Sopenharmony_ci__setup("debug-alternative", debug_alt); 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci#define DPRINTK(fmt, args...) \ 258c2ecf20Sopenharmony_cido { \ 268c2ecf20Sopenharmony_ci if (debug_alternative) \ 278c2ecf20Sopenharmony_ci printk(KERN_DEBUG "%s: " fmt "\n", __func__, ##args); \ 288c2ecf20Sopenharmony_ci} while (0) 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci#define DUMP_WORDS(buf, count, fmt, args...) \ 318c2ecf20Sopenharmony_cido { \ 328c2ecf20Sopenharmony_ci if (unlikely(debug_alternative)) { \ 338c2ecf20Sopenharmony_ci int _j; \ 348c2ecf20Sopenharmony_ci union loongarch_instruction *_buf = buf; \ 358c2ecf20Sopenharmony_ci \ 368c2ecf20Sopenharmony_ci if (!(count)) \ 378c2ecf20Sopenharmony_ci break; \ 388c2ecf20Sopenharmony_ci \ 398c2ecf20Sopenharmony_ci printk(KERN_DEBUG fmt, ##args); \ 408c2ecf20Sopenharmony_ci for (_j = 0; _j < count - 1; _j++) \ 418c2ecf20Sopenharmony_ci printk(KERN_CONT "<%08x> ", _buf[_j].word); \ 428c2ecf20Sopenharmony_ci printk(KERN_CONT "<%08x>\n", _buf[_j].word); \ 438c2ecf20Sopenharmony_ci } \ 448c2ecf20Sopenharmony_ci} while (0) 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci/* Use this to add nops to a buffer, then text_poke the whole buffer. */ 478c2ecf20Sopenharmony_cistatic void __init_or_module add_nops(union loongarch_instruction *insn, int count) 488c2ecf20Sopenharmony_ci{ 498c2ecf20Sopenharmony_ci while (count--) { 508c2ecf20Sopenharmony_ci insn->word = INSN_NOP; 518c2ecf20Sopenharmony_ci insn++; 528c2ecf20Sopenharmony_ci } 538c2ecf20Sopenharmony_ci} 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci/* Is the jump addr in local .altinstructions */ 568c2ecf20Sopenharmony_cistatic inline bool in_alt_jump(unsigned long jump, void *start, void *end) 578c2ecf20Sopenharmony_ci{ 588c2ecf20Sopenharmony_ci return jump >= (unsigned long)start && jump < (unsigned long)end; 598c2ecf20Sopenharmony_ci} 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_cistatic void __init_or_module recompute_jump(union loongarch_instruction *buf, 628c2ecf20Sopenharmony_ci union loongarch_instruction *dest, union loongarch_instruction *src, 638c2ecf20Sopenharmony_ci void *start, void *end) 648c2ecf20Sopenharmony_ci{ 658c2ecf20Sopenharmony_ci unsigned int si, si_l, si_h; 668c2ecf20Sopenharmony_ci unsigned long cur_pc, jump_addr, pc; 678c2ecf20Sopenharmony_ci long offset; 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_ci cur_pc = (unsigned long)src; 708c2ecf20Sopenharmony_ci pc = (unsigned long)dest; 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci si_l = src->reg0i26_format.simmediate_l; 738c2ecf20Sopenharmony_ci si_h = src->reg0i26_format.simmediate_h; 748c2ecf20Sopenharmony_ci switch (src->reg0i26_format.opcode) { 758c2ecf20Sopenharmony_ci case b_op: 768c2ecf20Sopenharmony_ci case bl_op: 778c2ecf20Sopenharmony_ci jump_addr = bs_dest_26(cur_pc, si_h, si_l); 788c2ecf20Sopenharmony_ci if (in_alt_jump(jump_addr, start, end)) 798c2ecf20Sopenharmony_ci return; 808c2ecf20Sopenharmony_ci offset = jump_addr - pc; 818c2ecf20Sopenharmony_ci BUG_ON(offset < -SZ_128M || offset >= SZ_128M); 828c2ecf20Sopenharmony_ci offset >>= 2; 838c2ecf20Sopenharmony_ci buf->reg0i26_format.simmediate_h = offset >> 16; 848c2ecf20Sopenharmony_ci buf->reg0i26_format.simmediate_l = offset; 858c2ecf20Sopenharmony_ci return; 868c2ecf20Sopenharmony_ci } 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci si_l = src->reg1i21_format.simmediate_l; 898c2ecf20Sopenharmony_ci si_h = src->reg1i21_format.simmediate_h; 908c2ecf20Sopenharmony_ci switch (src->reg1i21_format.opcode) { 918c2ecf20Sopenharmony_ci case beqz_op: 928c2ecf20Sopenharmony_ci case bnez_op: 938c2ecf20Sopenharmony_ci case bceqz_op: 948c2ecf20Sopenharmony_ci jump_addr = bs_dest_21(cur_pc, si_h, si_l); 958c2ecf20Sopenharmony_ci if (in_alt_jump(jump_addr, start, end)) 968c2ecf20Sopenharmony_ci return; 978c2ecf20Sopenharmony_ci offset = jump_addr - pc; 988c2ecf20Sopenharmony_ci BUG_ON(offset < -SZ_4M || offset >= SZ_4M); 998c2ecf20Sopenharmony_ci offset >>= 2; 1008c2ecf20Sopenharmony_ci buf->reg1i21_format.simmediate_h = offset >> 16; 1018c2ecf20Sopenharmony_ci buf->reg1i21_format.simmediate_l = offset; 1028c2ecf20Sopenharmony_ci return; 1038c2ecf20Sopenharmony_ci } 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci si = src->reg2i16_format.simmediate; 1068c2ecf20Sopenharmony_ci switch (src->reg2i16_format.opcode) { 1078c2ecf20Sopenharmony_ci case beq_op: 1088c2ecf20Sopenharmony_ci case bne_op: 1098c2ecf20Sopenharmony_ci case blt_op: 1108c2ecf20Sopenharmony_ci case bge_op: 1118c2ecf20Sopenharmony_ci case bltu_op: 1128c2ecf20Sopenharmony_ci case bgeu_op: 1138c2ecf20Sopenharmony_ci jump_addr = bs_dest_16(cur_pc, si); 1148c2ecf20Sopenharmony_ci if (in_alt_jump(jump_addr, start, end)) 1158c2ecf20Sopenharmony_ci return; 1168c2ecf20Sopenharmony_ci offset = jump_addr - pc; 1178c2ecf20Sopenharmony_ci BUG_ON(offset < -SZ_128K || offset >= SZ_128K); 1188c2ecf20Sopenharmony_ci offset >>= 2; 1198c2ecf20Sopenharmony_ci buf->reg2i16_format.simmediate = offset; 1208c2ecf20Sopenharmony_ci return; 1218c2ecf20Sopenharmony_ci } 1228c2ecf20Sopenharmony_ci} 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_cistatic int __init_or_module copy_alt_insns(union loongarch_instruction *buf, 1258c2ecf20Sopenharmony_ci union loongarch_instruction *dest, union loongarch_instruction *src, int nr) 1268c2ecf20Sopenharmony_ci{ 1278c2ecf20Sopenharmony_ci int i; 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci for (i = 0; i < nr; i++) { 1308c2ecf20Sopenharmony_ci buf[i].word = src[i].word; 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ci if (is_pc_insn(src[i])) { 1338c2ecf20Sopenharmony_ci pr_err("Not support pcrel instruction at present!"); 1348c2ecf20Sopenharmony_ci return -EINVAL; 1358c2ecf20Sopenharmony_ci } 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci if (is_branch_insn(src[i]) && 1388c2ecf20Sopenharmony_ci src[i].reg2i16_format.opcode != jirl_op) { 1398c2ecf20Sopenharmony_ci recompute_jump(&buf[i], &dest[i], &src[i], src, src + nr); 1408c2ecf20Sopenharmony_ci } 1418c2ecf20Sopenharmony_ci } 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci return 0; 1448c2ecf20Sopenharmony_ci} 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci/* 1478c2ecf20Sopenharmony_ci * text_poke_early - Update instructions on a live kernel at boot time 1488c2ecf20Sopenharmony_ci * 1498c2ecf20Sopenharmony_ci * When you use this code to patch more than one byte of an instruction 1508c2ecf20Sopenharmony_ci * you need to make sure that other CPUs cannot execute this code in parallel. 1518c2ecf20Sopenharmony_ci * Also no thread must be currently preempted in the middle of these 1528c2ecf20Sopenharmony_ci * instructions. And on the local CPU you need to be protected again NMI or MCE 1538c2ecf20Sopenharmony_ci * handlers seeing an inconsistent instruction while you patch. 1548c2ecf20Sopenharmony_ci */ 1558c2ecf20Sopenharmony_cistatic void *__init_or_module text_poke_early(union loongarch_instruction *insn, 1568c2ecf20Sopenharmony_ci union loongarch_instruction *buf, unsigned int nr) 1578c2ecf20Sopenharmony_ci{ 1588c2ecf20Sopenharmony_ci int i; 1598c2ecf20Sopenharmony_ci unsigned long flags; 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci local_irq_save(flags); 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_ci for (i = 0; i < nr; i++) 1648c2ecf20Sopenharmony_ci insn[i].word = buf[i].word; 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci local_irq_restore(flags); 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci wbflush(); 1698c2ecf20Sopenharmony_ci flush_icache_range((unsigned long)insn, (unsigned long)(insn + nr)); 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci return insn; 1728c2ecf20Sopenharmony_ci} 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci/* 1758c2ecf20Sopenharmony_ci * Replace instructions with better alternatives for this CPU type. This runs 1768c2ecf20Sopenharmony_ci * before SMP is initialized to avoid SMP problems with self modifying code. 1778c2ecf20Sopenharmony_ci * This implies that asymmetric systems where APs have less capabilities than 1788c2ecf20Sopenharmony_ci * the boot processor are not handled. Tough. Make sure you disable such 1798c2ecf20Sopenharmony_ci * features by hand. 1808c2ecf20Sopenharmony_ci */ 1818c2ecf20Sopenharmony_civoid __init_or_module apply_alternatives(struct alt_instr *start, struct alt_instr *end) 1828c2ecf20Sopenharmony_ci{ 1838c2ecf20Sopenharmony_ci struct alt_instr *a; 1848c2ecf20Sopenharmony_ci unsigned int nr_instr, nr_repl, nr_insnbuf; 1858c2ecf20Sopenharmony_ci union loongarch_instruction *instr, *replacement; 1868c2ecf20Sopenharmony_ci union loongarch_instruction insnbuf[MAX_PATCH_SIZE]; 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci DPRINTK("alt table %px, -> %px", start, end); 1898c2ecf20Sopenharmony_ci /* 1908c2ecf20Sopenharmony_ci * The scan order should be from start to end. A later scanned 1918c2ecf20Sopenharmony_ci * alternative code can overwrite previously scanned alternative code. 1928c2ecf20Sopenharmony_ci * Some kernel functions (e.g. memcpy, memset, etc) use this order to 1938c2ecf20Sopenharmony_ci * patch code. 1948c2ecf20Sopenharmony_ci * 1958c2ecf20Sopenharmony_ci * So be careful if you want to change the scan order to any other 1968c2ecf20Sopenharmony_ci * order. 1978c2ecf20Sopenharmony_ci */ 1988c2ecf20Sopenharmony_ci for (a = start; a < end; a++) { 1998c2ecf20Sopenharmony_ci nr_insnbuf = 0; 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci instr = (void *)&a->instr_offset + a->instr_offset; 2028c2ecf20Sopenharmony_ci replacement = (void *)&a->replace_offset + a->replace_offset; 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci BUG_ON(a->instrlen > sizeof(insnbuf)); 2058c2ecf20Sopenharmony_ci BUG_ON(a->instrlen & 0x3); 2068c2ecf20Sopenharmony_ci BUG_ON(a->replacementlen & 0x3); 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci nr_instr = a->instrlen / LOONGARCH_INSN_SIZE; 2098c2ecf20Sopenharmony_ci nr_repl = a->replacementlen / LOONGARCH_INSN_SIZE; 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ci if (!cpu_has(a->feature)) { 2128c2ecf20Sopenharmony_ci DPRINTK("feat not exist: %d, old: (%px len: %d), repl: (%px, len: %d)", 2138c2ecf20Sopenharmony_ci a->feature, instr, a->instrlen, 2148c2ecf20Sopenharmony_ci replacement, a->replacementlen); 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ci continue; 2178c2ecf20Sopenharmony_ci } 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_ci DPRINTK("feat: %d, old: (%px len: %d), repl: (%px, len: %d)", 2208c2ecf20Sopenharmony_ci a->feature, instr, a->instrlen, 2218c2ecf20Sopenharmony_ci replacement, a->replacementlen); 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci DUMP_WORDS(instr, nr_instr, "%px: old_insn: ", instr); 2248c2ecf20Sopenharmony_ci DUMP_WORDS(replacement, nr_repl, "%px: rpl_insn: ", replacement); 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci copy_alt_insns(insnbuf, instr, replacement, nr_repl); 2278c2ecf20Sopenharmony_ci nr_insnbuf = nr_repl; 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_ci if (nr_instr > nr_repl) { 2308c2ecf20Sopenharmony_ci add_nops(insnbuf + nr_repl, nr_instr - nr_repl); 2318c2ecf20Sopenharmony_ci nr_insnbuf += nr_instr - nr_repl; 2328c2ecf20Sopenharmony_ci } 2338c2ecf20Sopenharmony_ci DUMP_WORDS(insnbuf, nr_insnbuf, "%px: final_insn: ", instr); 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_ci text_poke_early(instr, insnbuf, nr_insnbuf); 2368c2ecf20Sopenharmony_ci } 2378c2ecf20Sopenharmony_ci} 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_civoid __init alternative_instructions(void) 2408c2ecf20Sopenharmony_ci{ 2418c2ecf20Sopenharmony_ci apply_alternatives(__alt_instructions, __alt_instructions_end); 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_ci alternatives_patched = 1; 2448c2ecf20Sopenharmony_ci} 245