summaryrefslogtreecommitdiff
path: root/arch/s390/kernel/alternative.c
blob: cce0ddee2d02d2731ef36635a18082f9b13f76c3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// SPDX-License-Identifier: GPL-2.0
#include <linux/module.h>
#include <linux/cpu.h>
#include <linux/smp.h>
#include <asm/text-patching.h>
#include <asm/alternative.h>
#include <asm/facility.h>
#include <asm/nospec-branch.h>

#define MAX_PATCH_LEN (255 - 1)

static int __initdata_or_module alt_instr_disabled;

static int __init disable_alternative_instructions(char *str)
{
	alt_instr_disabled = 1;
	return 0;
}

early_param("noaltinstr", disable_alternative_instructions);

struct brcl_insn {
	u16 opc;
	s32 disp;
} __packed;

static u16 __initdata_or_module nop16 = 0x0700;
static u32 __initdata_or_module nop32 = 0x47000000;
static struct brcl_insn __initdata_or_module nop48 = {
	0xc004, 0
};

static const void *nops[] __initdata_or_module = {
	&nop16,
	&nop32,
	&nop48
};

static void __init_or_module add_jump_padding(void *insns, unsigned int len)
{
	struct brcl_insn brcl = {
		0xc0f4,
		len / 2
	};

	memcpy(insns, &brcl, sizeof(brcl));
	insns += sizeof(brcl);
	len -= sizeof(brcl);

	while (len > 0) {
		memcpy(insns, &nop16, 2);
		insns += 2;
		len -= 2;
	}
}

static void __init_or_module add_padding(void *insns, unsigned int len)
{
	if (len > 6)
		add_jump_padding(insns, len);
	else if (len >= 2)
		memcpy(insns, nops[len / 2 - 1], len);
}

static void __init_or_module __apply_alternatives(struct alt_instr *start,
						  struct alt_instr *end)
{
	struct alt_instr *a;
	u8 *instr, *replacement;
	u8 insnbuf[MAX_PATCH_LEN];

	/*
	 * The scan order should be from start to end. A later scanned
	 * alternative code can overwrite previously scanned alternative code.
	 */
	for (a = start; a < end; a++) {
		int insnbuf_sz = 0;

		instr = (u8 *)&a->instr_offset + a->instr_offset;
		replacement = (u8 *)&a->repl_offset + a->repl_offset;

		if (!__test_facility(a->facility, alt_stfle_fac_list))
			continue;

		if (unlikely(a->instrlen % 2 || a->replacementlen % 2)) {
			WARN_ONCE(1, "cpu alternatives instructions length is "
				     "odd, skipping patching\n");
			continue;
		}

		memcpy(insnbuf, replacement, a->replacementlen);
		insnbuf_sz = a->replacementlen;

		if (a->instrlen > a->replacementlen) {
			add_padding(insnbuf + a->replacementlen,
				    a->instrlen - a->replacementlen);
			insnbuf_sz += a->instrlen - a->replacementlen;
		}

		s390_kernel_write(instr, insnbuf, insnbuf_sz);
	}
}

void __init_or_module apply_alternatives(struct alt_instr *start,
					 struct alt_instr *end)
{
	if (!alt_instr_disabled)
		__apply_alternatives(start, end);
}

extern struct alt_instr __alt_instructions[], __alt_instructions_end[];
void __init apply_alternative_instructions(void)
{
	apply_alternatives(__alt_instructions, __alt_instructions_end);
}

static void do_sync_core(void *info)
{
	sync_core();
}

void text_poke_sync(void)
{
	on_each_cpu(do_sync_core, NULL, 1);
}

void text_poke_sync_lock(void)
{
	cpus_read_lock();
	text_poke_sync();
	cpus_read_unlock();
}