xref: /openbmc/linux/arch/s390/kernel/alternative.c (revision bbecb07f)
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