xref: /openbmc/linux/arch/x86/kernel/static_call.c (revision 09717af7)
1 // SPDX-License-Identifier: GPL-2.0
2 #include <linux/static_call.h>
3 #include <linux/memory.h>
4 #include <linux/bug.h>
5 #include <asm/text-patching.h>
6 
7 enum insn_type {
8 	CALL = 0, /* site call */
9 	NOP = 1,  /* site cond-call */
10 	JMP = 2,  /* tramp / site tail-call */
11 	RET = 3,  /* tramp / site cond-tail-call */
12 };
13 
14 /*
15  * data16 data16 xorq %rax, %rax - a single 5 byte instruction that clears %rax
16  * The REX.W cancels the effect of any data16.
17  */
18 static const u8 xor5rax[] = { 0x66, 0x66, 0x48, 0x31, 0xc0 };
19 
20 static void __ref __static_call_transform(void *insn, enum insn_type type, void *func)
21 {
22 	const void *emulate = NULL;
23 	int size = CALL_INSN_SIZE;
24 	const void *code;
25 
26 	switch (type) {
27 	case CALL:
28 		code = text_gen_insn(CALL_INSN_OPCODE, insn, func);
29 		if (func == &__static_call_return0) {
30 			emulate = code;
31 			code = &xor5rax;
32 		}
33 
34 		break;
35 
36 	case NOP:
37 		code = x86_nops[5];
38 		break;
39 
40 	case JMP:
41 		code = text_gen_insn(JMP32_INSN_OPCODE, insn, func);
42 		break;
43 
44 	case RET:
45 		code = text_gen_insn(RET_INSN_OPCODE, insn, func);
46 		size = RET_INSN_SIZE;
47 		break;
48 	}
49 
50 	if (memcmp(insn, code, size) == 0)
51 		return;
52 
53 	if (unlikely(system_state == SYSTEM_BOOTING))
54 		return text_poke_early(insn, code, size);
55 
56 	text_poke_bp(insn, code, size, emulate);
57 }
58 
59 static void __static_call_validate(void *insn, bool tail, bool tramp)
60 {
61 	u8 opcode = *(u8 *)insn;
62 
63 	if (tramp && memcmp(insn+5, "SCT", 3)) {
64 		pr_err("trampoline signature fail");
65 		BUG();
66 	}
67 
68 	if (tail) {
69 		if (opcode == JMP32_INSN_OPCODE ||
70 		    opcode == RET_INSN_OPCODE)
71 			return;
72 	} else {
73 		if (opcode == CALL_INSN_OPCODE ||
74 		    !memcmp(insn, x86_nops[5], 5) ||
75 		    !memcmp(insn, xor5rax, 5))
76 			return;
77 	}
78 
79 	/*
80 	 * If we ever trigger this, our text is corrupt, we'll probably not live long.
81 	 */
82 	pr_err("unexpected static_call insn opcode 0x%x at %pS\n", opcode, insn);
83 	BUG();
84 }
85 
86 static inline enum insn_type __sc_insn(bool null, bool tail)
87 {
88 	/*
89 	 * Encode the following table without branches:
90 	 *
91 	 *	tail	null	insn
92 	 *	-----+-------+------
93 	 *	  0  |   0   |  CALL
94 	 *	  0  |   1   |  NOP
95 	 *	  1  |   0   |  JMP
96 	 *	  1  |   1   |  RET
97 	 */
98 	return 2*tail + null;
99 }
100 
101 void arch_static_call_transform(void *site, void *tramp, void *func, bool tail)
102 {
103 	mutex_lock(&text_mutex);
104 
105 	if (tramp) {
106 		__static_call_validate(tramp, true, true);
107 		__static_call_transform(tramp, __sc_insn(!func, true), func);
108 	}
109 
110 	if (IS_ENABLED(CONFIG_HAVE_STATIC_CALL_INLINE) && site) {
111 		__static_call_validate(site, tail, false);
112 		__static_call_transform(site, __sc_insn(!func, tail), func);
113 	}
114 
115 	mutex_unlock(&text_mutex);
116 }
117 EXPORT_SYMBOL_GPL(arch_static_call_transform);
118