1 // SPDX-License-Identifier: GPL-2.0 2 #include <linux/module.h> 3 #include <asm/alternative.h> 4 #include <asm/facility.h> 5 6 #define MAX_PATCH_LEN (255 - 1) 7 8 static int __initdata_or_module alt_instr_disabled; 9 10 static int __init disable_alternative_instructions(char *str) 11 { 12 alt_instr_disabled = 1; 13 return 0; 14 } 15 16 early_param("noaltinstr", disable_alternative_instructions); 17 18 struct brcl_insn { 19 u16 opc; 20 s32 disp; 21 } __packed; 22 23 static u16 __initdata_or_module nop16 = 0x0700; 24 static u32 __initdata_or_module nop32 = 0x47000000; 25 static struct brcl_insn __initdata_or_module nop48 = { 26 0xc004, 0 27 }; 28 29 static const void *nops[] __initdata_or_module = { 30 &nop16, 31 &nop32, 32 &nop48 33 }; 34 35 static void __init_or_module add_jump_padding(void *insns, unsigned int len) 36 { 37 struct brcl_insn brcl = { 38 0xc0f4, 39 len / 2 40 }; 41 42 memcpy(insns, &brcl, sizeof(brcl)); 43 insns += sizeof(brcl); 44 len -= sizeof(brcl); 45 46 while (len > 0) { 47 memcpy(insns, &nop16, 2); 48 insns += 2; 49 len -= 2; 50 } 51 } 52 53 static void __init_or_module add_padding(void *insns, unsigned int len) 54 { 55 if (len > 6) 56 add_jump_padding(insns, len); 57 else if (len >= 2) 58 memcpy(insns, nops[len / 2 - 1], len); 59 } 60 61 static void __init_or_module __apply_alternatives(struct alt_instr *start, 62 struct alt_instr *end) 63 { 64 struct alt_instr *a; 65 u8 *instr, *replacement; 66 u8 insnbuf[MAX_PATCH_LEN]; 67 68 /* 69 * The scan order should be from start to end. A later scanned 70 * alternative code can overwrite previously scanned alternative code. 71 */ 72 for (a = start; a < end; a++) { 73 int insnbuf_sz = 0; 74 75 instr = (u8 *)&a->instr_offset + a->instr_offset; 76 replacement = (u8 *)&a->repl_offset + a->repl_offset; 77 78 if (!test_facility(a->facility)) 79 continue; 80 81 if (unlikely(a->instrlen % 2 || a->replacementlen % 2)) { 82 WARN_ONCE(1, "cpu alternatives instructions length is " 83 "odd, skipping patching\n"); 84 continue; 85 } 86 87 memcpy(insnbuf, replacement, a->replacementlen); 88 insnbuf_sz = a->replacementlen; 89 90 if (a->instrlen > a->replacementlen) { 91 add_padding(insnbuf + a->replacementlen, 92 a->instrlen - a->replacementlen); 93 insnbuf_sz += a->instrlen - a->replacementlen; 94 } 95 96 s390_kernel_write(instr, insnbuf, insnbuf_sz); 97 } 98 } 99 100 void __init_or_module apply_alternatives(struct alt_instr *start, 101 struct alt_instr *end) 102 { 103 if (!alt_instr_disabled) 104 __apply_alternatives(start, end); 105 } 106 107 extern struct alt_instr __alt_instructions[], __alt_instructions_end[]; 108 void __init apply_alternative_instructions(void) 109 { 110 apply_alternatives(__alt_instructions, __alt_instructions_end); 111 } 112