xref: /xnu-11417.121.6/osfmk/kern/static_if_common.c (revision a1e26a70f38d1d7daa7b49b258e2f8538ad81650)
1 /* * Copyright (c) 2021 Apple Inc. All rights reserved.
2  *
3  * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
4  *
5  * This file contains Original Code and/or Modifications of Original Code
6  * as defined in and that are subject to the Apple Public Source License
7  * Version 2.0 (the 'License'). You may not use this file except in
8  * compliance with the License. The rights granted to you under the License
9  * may not be used to create, or enable the creation or redistribution of,
10  * unlawful or unlicensed copies of an Apple operating system, or to
11  * circumvent, violate, or enable the circumvention or violation of, any
12  * terms of an Apple operating system software license agreement.
13  *
14  * Please obtain a copy of the License at
15  * http://www.opensource.apple.com/apsl/ and read it before using this file.
16  *
17  * The Original Code and all software distributed under the License are
18  * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
19  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
20  * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
21  * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
22  * Please see the License for the specific language governing rights and
23  * limitations under the License.
24  *
25  * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
26  */
27 
28 #ifndef STATIC_IF_TEST
29 #include <kern/assert.h>
30 #include <kern/locks.h>
31 #include <kern/startup.h>
32 #include <machine/static_if.h>
33 #include <machine/machine_routines.h>
34 
35 
36 extern struct static_if_entry __static_if_entries[]
37 __SECTION_START_SYM(STATIC_IF_SEGMENT, STATIC_IF_SECTION);
38 
39 extern struct static_if_entry __static_if_entries_end[]
40 __SECTION_END_SYM(STATIC_IF_SEGMENT, STATIC_IF_SECTION);
41 
42 extern static_if_initializer __static_if_initializer_entries[]
43 __SECTION_START_SYM(STATIC_IF_SEGMENT, STATIC_IFINIT_SECTION);
44 
45 extern static_if_initializer __static_if_initializer_entries_end[]
46 __SECTION_END_SYM(STATIC_IF_SEGMENT, STATIC_IFINIT_SECTION);
47 
48 
49 #endif /* STATIC_IF_TEST */
50 #pragma mark boot-arg parsing
51 
52 /*
53  * On SPTM targets we can't use PE_parse_boot_argn() because it isn't part
54  * of __BOOT_TEXT, so we need to roll our own.
55  *
56  * We can't use TUNABLES() yet either because they won't have been initialized.
57  */
58 
59 __attribute__((always_inline))
60 static inline bool
isargsep(char c)61 isargsep(char c)
62 {
63 	return c == ' ' || c == '\0' || c == '\t';
64 }
65 
66 __attribute__((always_inline))
67 static const char *
skip_seps(const char * s)68 skip_seps(const char *s)
69 {
70 	while (*s && isargsep(*s)) {
71 		s++;
72 	}
73 	return s;
74 }
75 
76 __attribute__((always_inline))
77 static const char *
skip_to_sep(const char * s)78 skip_to_sep(const char *s)
79 {
80 	while (!isargsep(*s)) {
81 		s++;
82 	}
83 	return s;
84 }
85 
86 __attribute__((always_inline))
87 static inline bool
skip_prefix(const char * args,const char * key,const char ** argsout)88 skip_prefix(const char *args, const char *key, const char **argsout)
89 {
90 	while (*key) {
91 		if (*args != *key) {
92 			return false;
93 		}
94 		args++;
95 		key++;
96 	}
97 
98 	*argsout = args;
99 	return true;
100 }
101 
102 __attribute__((always_inline))
103 static inline const char *
get_val(const char * s,uint64_t * val)104 get_val(const char *s, uint64_t *val)
105 {
106 	uint64_t radix = 10;
107 	uint64_t v;
108 	int sign = 1;
109 
110 	if (isargsep(*s)) {
111 		/* "... key ..." is the same as "... key=1 ..." */
112 		*val = 1;
113 		return s;
114 	}
115 
116 	if (*s != '=') {
117 		/* if not followed by a = then this is garbage */
118 		return s;
119 	}
120 	s++;
121 
122 	if (*s == '-') {
123 		sign = -1;
124 		s++;
125 	}
126 
127 	if (isargsep(*s)) {
128 		/* "... key=- ..." is malfomed */
129 		return s;
130 	}
131 
132 	v = (*s++ - '0');
133 	if (v == 0) {
134 		switch (*s) {
135 		case 'x':
136 			radix = 16;
137 			s++;
138 			break;
139 
140 		case 'b':
141 			radix = 2;
142 			s++;
143 			break;
144 
145 		case '0' ... '7':
146 			radix = 8;
147 			break;
148 
149 		default:
150 			if (!isargsep(*s)) {
151 				return s;
152 			}
153 			break;
154 		}
155 	} else if (v > radix) {
156 		return s;
157 	}
158 
159 	for (;;) {
160 		if (*s >= '0' && *s <= '9' - (10 - radix)) {
161 			v = v * radix + *s - '0';
162 		} else if (radix == 16 && *s >= 'a' && *s <= 'f') {
163 			v = v * radix + 10 + *s - 'a';
164 		} else if (radix == 16 && *s >= 'A' && *s <= 'F') {
165 			v = v * radix + 10 + *s - 'A';
166 		} else {
167 			if (isargsep(*s)) {
168 				*val = v * sign;
169 			}
170 			return s;
171 		}
172 
173 		s++;
174 	}
175 }
176 
177 MARK_AS_FIXUP_TEXT uint64_t
static_if_boot_arg_uint64(const char * args,const char * key,uint64_t defval)178 static_if_boot_arg_uint64(const char *args, const char *key, uint64_t defval)
179 {
180 	uint64_t ret = defval;
181 
182 	args = skip_seps(args);
183 
184 	while (*args) {
185 		if (*args == '-' && skip_prefix(args + 1, key, &args)) {
186 			if (isargsep(*args)) {
187 				ret = TRUE;
188 			}
189 		} else if (skip_prefix(args, key, &args)) {
190 			args = get_val(args, &ret);
191 		}
192 
193 		args = skip_to_sep(args);
194 		args = skip_seps(args);
195 	}
196 
197 	return ret;
198 }
199 
200 
201 #pragma mark patching
202 #ifndef STATIC_IF_TEST
203 
204 /*
205  * static_if() is implemented using keys, which are data structures
206  * of type `struct static_if_key`.
207  *
208  * These come in two concrete variants:
209  * - struct static_if_key_true, for which the key starts enabled/true,
210  * - struct static_if_key_false, for which the key starts disabled/false.
211  *
212  * Usage of static_if() and its variants use the following pattern:
213  * (a) a result variable is initialized to 0 (resp 1),
214  * (b) an asm goto() statement might jump to a label or fall through depending on
215  *     the state of the key (implemented with STATIC_IF_{NOP,JUMP}),
216  * (c) in the fall through code, the variable is set to 1 (resp 0).
217  *
218  * As a result these macros implement a boolean return that depend on whether
219  * their assembly is currently a nop (in which case it will return the value
220  * from (c)) or a branch (in which case it will return the value from (a)).
221  *
222  * STATIC_IF_NOP() and STATIC_IF_ENTRY() are machine dependent macros that emit
223  * either a nop or a branch instruction, and generate a `struct static_if_entry`
224  * which denote where this patchable instruction lives, and for which key.
225  *
226  * static_if_init() will run early and chain all these entries onto their key,
227  * in order to enable static_if_key_{enable,disable} to be able to quickly patch
228  * these instructions between nops and jumps.
229  */
230 
231 __attribute__((always_inline))
232 static inline void *
__static_if_entry_next(static_if_entry_t sie)233 __static_if_entry_next(static_if_entry_t sie)
234 {
235 	return (void *)(sie->sie_link & ~3ul);
236 }
237 
238 __attribute__((always_inline))
239 static inline bool
__static_if_is_jump(static_if_entry_t sie)240 __static_if_is_jump(static_if_entry_t sie)
241 {
242 	return sie->sie_link & 1;
243 }
244 
245 
246 MARK_AS_FIXUP_TEXT void
static_if_init(const char * args)247 static_if_init(const char *args)
248 {
249 	struct static_if_entry *sie = __static_if_entries_end;
250 	unsigned long sie_flags;
251 	static_if_key_t sie_key;
252 
253 	while (--sie >= __static_if_entries) {
254 		sie_flags = sie->sie_link & 3ul;
255 		sie_key   = __static_if_entry_next(sie);
256 		sie->sie_link = (vm_offset_t)sie_key->sik_entries_head | sie_flags;
257 
258 		sie_key->sik_entries_head = sie;
259 		sie_key->sik_entries_count++;
260 	}
261 
262 	for (static_if_initializer *f = __static_if_initializer_entries;
263 	    f < __static_if_initializer_entries_end; f++) {
264 		(*f)(args);
265 	}
266 
267 	ml_static_if_flush_icache();
268 }
269 
270 MARK_AS_FIXUP_TEXT void
__static_if_key_delta(static_if_key_t key,int delta)271 __static_if_key_delta(static_if_key_t key, int delta)
272 {
273 	/*
274 	 * On SPTM configuration, static_if_init() is called by
275 	 * arm_static_if_init() during the XNU fixup phase,
276 	 * before the XNU kernel text is retyped to SPTM_XNU_CODE
277 	 * and can't be modified anymore.
278 	 *
279 	 * For other platforms, this is called from kernel_startup_bootstrap()
280 	 */
281 	if (startup_phase >= STARTUP_SUB_TUNABLES) {
282 		panic("static_if_key_{enable,disable} called too late");
283 	}
284 
285 	bool was_enabled = (key->sik_enable_count >= 0);
286 
287 	key->sik_enable_count += delta;
288 	if (was_enabled != (key->sik_enable_count >= 0)) {
289 		static_if_entry_t sie = key->sik_entries_head;
290 		bool init_enabled = key->sik_init_value >= 0;
291 
292 		while (sie) {
293 			ml_static_if_entry_patch(sie,
294 			    (was_enabled == init_enabled) ^
295 			    __static_if_is_jump(sie));
296 			sie = __static_if_entry_next(sie);
297 		}
298 	}
299 }
300 
301 #endif /* STATIC_IF_TEST */
302