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