xref: /openbmc/linux/arch/s390/kernel/alternative.c (revision 60772e48)
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 static int __init nobp_setup_early(char *str)
19 {
20 	bool enabled;
21 	int rc;
22 
23 	rc = kstrtobool(str, &enabled);
24 	if (rc)
25 		return rc;
26 	if (enabled && test_facility(82))
27 		__set_facility(82, S390_lowcore.alt_stfle_fac_list);
28 	else
29 		__clear_facility(82, S390_lowcore.alt_stfle_fac_list);
30 	return 0;
31 }
32 early_param("nobp", nobp_setup_early);
33 
34 static int __init nospec_setup_early(char *str)
35 {
36 	__clear_facility(82, S390_lowcore.alt_stfle_fac_list);
37 	return 0;
38 }
39 early_param("nospec", nospec_setup_early);
40 
41 struct brcl_insn {
42 	u16 opc;
43 	s32 disp;
44 } __packed;
45 
46 static u16 __initdata_or_module nop16 = 0x0700;
47 static u32 __initdata_or_module nop32 = 0x47000000;
48 static struct brcl_insn __initdata_or_module nop48 = {
49 	0xc004, 0
50 };
51 
52 static const void *nops[] __initdata_or_module = {
53 	&nop16,
54 	&nop32,
55 	&nop48
56 };
57 
58 static void __init_or_module add_jump_padding(void *insns, unsigned int len)
59 {
60 	struct brcl_insn brcl = {
61 		0xc0f4,
62 		len / 2
63 	};
64 
65 	memcpy(insns, &brcl, sizeof(brcl));
66 	insns += sizeof(brcl);
67 	len -= sizeof(brcl);
68 
69 	while (len > 0) {
70 		memcpy(insns, &nop16, 2);
71 		insns += 2;
72 		len -= 2;
73 	}
74 }
75 
76 static void __init_or_module add_padding(void *insns, unsigned int len)
77 {
78 	if (len > 6)
79 		add_jump_padding(insns, len);
80 	else if (len >= 2)
81 		memcpy(insns, nops[len / 2 - 1], len);
82 }
83 
84 static void __init_or_module __apply_alternatives(struct alt_instr *start,
85 						  struct alt_instr *end)
86 {
87 	struct alt_instr *a;
88 	u8 *instr, *replacement;
89 	u8 insnbuf[MAX_PATCH_LEN];
90 
91 	/*
92 	 * The scan order should be from start to end. A later scanned
93 	 * alternative code can overwrite previously scanned alternative code.
94 	 */
95 	for (a = start; a < end; a++) {
96 		int insnbuf_sz = 0;
97 
98 		instr = (u8 *)&a->instr_offset + a->instr_offset;
99 		replacement = (u8 *)&a->repl_offset + a->repl_offset;
100 
101 		if (!__test_facility(a->facility,
102 				     S390_lowcore.alt_stfle_fac_list))
103 			continue;
104 
105 		if (unlikely(a->instrlen % 2 || a->replacementlen % 2)) {
106 			WARN_ONCE(1, "cpu alternatives instructions length is "
107 				     "odd, skipping patching\n");
108 			continue;
109 		}
110 
111 		memcpy(insnbuf, replacement, a->replacementlen);
112 		insnbuf_sz = a->replacementlen;
113 
114 		if (a->instrlen > a->replacementlen) {
115 			add_padding(insnbuf + a->replacementlen,
116 				    a->instrlen - a->replacementlen);
117 			insnbuf_sz += a->instrlen - a->replacementlen;
118 		}
119 
120 		s390_kernel_write(instr, insnbuf, insnbuf_sz);
121 	}
122 }
123 
124 void __init_or_module apply_alternatives(struct alt_instr *start,
125 					 struct alt_instr *end)
126 {
127 	if (!alt_instr_disabled)
128 		__apply_alternatives(start, end);
129 }
130 
131 extern struct alt_instr __alt_instructions[], __alt_instructions_end[];
132 void __init apply_alternative_instructions(void)
133 {
134 	apply_alternatives(__alt_instructions, __alt_instructions_end);
135 }
136