xref: /xnu-11417.101.15/osfmk/kern/exclaves_sensor.c (revision e3723e1f17661b24996789d8afc084c0c3303b26)
1 /*
2  * Copyright (c) 2023 Apple Inc. All rights reserved.
3  *
4  * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5  *
6  * This file contains Original Code and/or Modifications of Original Code
7  * as defined in and that are subject to the Apple Public Source License
8  * Version 2.0 (the 'License'). You may not use this file except in
9  * compliance with the License. The rights granted to you under the License
10  * may not be used to create, or enable the creation or redistribution of,
11  * unlawful or unlicensed copies of an Apple operating system, or to
12  * circumvent, violate, or enable the circumvention or violation of, any
13  * terms of an Apple operating system software license agreement.
14  *
15  * Please obtain a copy of the License at
16  * http://www.opensource.apple.com/apsl/ and read it before using this file.
17  *
18  * The Original Code and all software distributed under the License are
19  * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21  * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23  * Please see the License for the specific language governing rights and
24  * limitations under the License.
25  *
26  * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27  */
28 
29 #include <stdint.h>
30 #include <mach/exclaves.h>
31 #include <mach/kern_return.h>
32 
33 #include "exclaves_boot.h"
34 #include "exclaves_debug.h"
35 #include "exclaves_resource.h"
36 #include "exclaves_sensor.h"
37 
38 #if CONFIG_EXCLAVES
39 
40 #include <kern/locks.h>
41 #include <kern/thread_call.h>
42 
43 #include "kern/exclaves.tightbeam.h"
44 
45 /* -------------------------------------------------------------------------- */
46 #pragma mark EIC
47 
48 #define EXCLAVES_EIC "com.apple.service.ExclaveIndicatorController"
49 
50 /* The minimum time a sensor is on. */
51 #define EXCLAVES_EIC_MIN_SENSOR_TIME (3100 * NSEC_PER_MSEC) /* 3.1 seconds */
52 
53 /* Default to 30Hz */
54 static uint64_t exclaves_display_healthcheck_rate_hz = 30;
55 
56 static exclaveindicatorcontroller_sensorrequest_s eic_client;
57 
58 static inline __unused exclaveindicatorcontroller_sensortype_s
sensor_type_to_eic_sensortype(exclaves_sensor_type_t type)59 sensor_type_to_eic_sensortype(exclaves_sensor_type_t type)
60 {
61 	assert3u(type, >, 0);
62 	assert3u(type, <=, EXCLAVES_SENSOR_MAX);
63 
64 	switch (type) {
65 	case EXCLAVES_SENSOR_CAM:
66 		return EXCLAVEINDICATORCONTROLLER_SENSORTYPE_SENSOR_CAM;
67 	case EXCLAVES_SENSOR_MIC:
68 		return EXCLAVEINDICATORCONTROLLER_SENSORTYPE_SENSOR_MIC;
69 	case EXCLAVES_SENSOR_CAM_ALT_FACEID:
70 		return EXCLAVEINDICATORCONTROLLER_SENSORTYPE_SENSOR_CAM_ALT_FACEID;
71 	case EXCLAVES_SENSOR_CAM_ALT_FACEID_DELAYED:
72 		return EXCLAVEINDICATORCONTROLLER_SENSORTYPE_SENSOR_CAM_ALT_FACEID_DELAYED;
73 	default:
74 		panic("unknown sensor type");
75 	}
76 }
77 
78 static inline exclaves_sensor_status_t
eic_sensorstatus_to_sensor_status(exclaveindicatorcontroller_sensorstatusresponse_s status)79 eic_sensorstatus_to_sensor_status(exclaveindicatorcontroller_sensorstatusresponse_s status)
80 {
81 	assert3u(status, >, 0);
82 	assert3u(status, <=, EXCLAVEINDICATORCONTROLLER_SENSORSTATUSRESPONSE_SENSOR_PENDING);
83 
84 	switch (status) {
85 	case EXCLAVEINDICATORCONTROLLER_SENSORSTATUSRESPONSE_SENSOR_ALLOWED:
86 		return EXCLAVES_SENSOR_STATUS_ALLOWED;
87 	case EXCLAVEINDICATORCONTROLLER_SENSORSTATUSRESPONSE_SENSOR_DENIED:
88 		return EXCLAVES_SENSOR_STATUS_DENIED;
89 	case EXCLAVEINDICATORCONTROLLER_SENSORSTATUSRESPONSE_SENSOR_CONTROL:
90 		return EXCLAVES_SENSOR_STATUS_CONTROL;
91 	case EXCLAVEINDICATORCONTROLLER_SENSORSTATUSRESPONSE_SENSOR_PENDING:
92 		return EXCLAVES_SENSOR_STATUS_PENDING;
93 	default:
94 		panic("unknown sensor status");
95 	}
96 }
97 
98 static kern_return_t
exclaves_eic_init(void)99 exclaves_eic_init(void)
100 {
101 	exclaves_id_t eic_id = exclaves_service_lookup(EXCLAVES_DOMAIN_KERNEL,
102 	    EXCLAVES_EIC);
103 
104 	if (eic_id == EXCLAVES_INVALID_ID) {
105 		exclaves_requirement_assert(EXCLAVES_R_EIC,
106 		    "exclaves indicator controller not found");
107 		return KERN_SUCCESS;
108 	}
109 
110 	tb_endpoint_t ep = tb_endpoint_create_with_value(
111 		TB_TRANSPORT_TYPE_XNU, eic_id, TB_ENDPOINT_OPTIONS_NONE);
112 
113 	tb_error_t ret =
114 	    exclaveindicatorcontroller_sensorrequest__init(&eic_client, ep);
115 
116 	return ret == TB_ERROR_SUCCESS ? KERN_SUCCESS : KERN_FAILURE;
117 }
118 
119 static kern_return_t
exclaves_eic_display_healthcheck_rate(uint64_t ns)120 exclaves_eic_display_healthcheck_rate(uint64_t ns)
121 {
122 	exclaveindicatorcontroller_requestedrefreshrate_s rate;
123 
124 	/* Convert time to frequency and round up to nearest supported value. */
125 	switch (NSEC_PER_SEC / ns) {
126 	case 0 ... 30:
127 		exclaves_display_healthcheck_rate_hz = 30;
128 		rate = EXCLAVEINDICATORCONTROLLER_REQUESTEDREFRESHRATE_HZ_30;
129 		break;
130 	case 31 ... 60:
131 		exclaves_display_healthcheck_rate_hz = 60;
132 		rate = EXCLAVEINDICATORCONTROLLER_REQUESTEDREFRESHRATE_HZ_60;
133 		break;
134 	default:
135 		exclaves_display_healthcheck_rate_hz = 120;
136 		rate = EXCLAVEINDICATORCONTROLLER_REQUESTEDREFRESHRATE_HZ_120;
137 		break;
138 	}
139 
140 	tb_error_t ret = exclaveindicatorcontroller_sensorrequest_requestdisplayhealthcheckrate(
141 		&eic_client, rate, ^(__unused exclaveindicatorcontroller_requestresponse_s result) {});
142 
143 	return ret == TB_ERROR_SUCCESS ? KERN_SUCCESS : KERN_FAILURE;
144 }
145 
146 static kern_return_t
exclaves_eic_sensor_start(exclaves_sensor_type_t __unused sensor_type,__assert_only uint64_t flags,exclaves_sensor_status_t * status)147 exclaves_eic_sensor_start(exclaves_sensor_type_t __unused sensor_type,
148     __assert_only uint64_t flags, exclaves_sensor_status_t *status)
149 {
150 	assert3p(status, !=, NULL);
151 	assert3u(flags, ==, 0);
152 
153 	*status = EXCLAVES_SENSOR_STATUS_ALLOWED;
154 	return KERN_SUCCESS;
155 }
156 
157 static kern_return_t
exclaves_eic_sensor_stop(exclaves_sensor_type_t __unused sensor_type)158 exclaves_eic_sensor_stop(exclaves_sensor_type_t __unused sensor_type)
159 {
160 	return KERN_SUCCESS;
161 }
162 
163 static kern_return_t
exclaves_eic_sensor_status(exclaves_sensor_type_t __unused sensor_type,__assert_only uint64_t flags,exclaves_sensor_status_t * status)164 exclaves_eic_sensor_status(exclaves_sensor_type_t __unused sensor_type,
165     __assert_only uint64_t flags, exclaves_sensor_status_t *status)
166 {
167 	assert3p(status, !=, NULL);
168 	assert3u(flags, ==, 0);
169 
170 	*status = EXCLAVES_SENSOR_STATUS_ALLOWED;
171 	return KERN_SUCCESS;
172 }
173 
174 /*
175  * It is intentional to keep "buffer" untyped here as it avoids xnu having to
176  * understand what those IDs are at all. They are simply passed through from the
177  * resource table as-is.
178  */
179 static kern_return_t
exclaves_eic_sensor_copy(uint32_t buffer,uint64_t size1,uint64_t offset1,uint64_t size2,uint64_t offset2,exclaves_sensor_status_t * status)180 exclaves_eic_sensor_copy(uint32_t buffer, uint64_t size1, uint64_t offset1,
181     uint64_t size2, uint64_t offset2, exclaves_sensor_status_t *status)
182 {
183 	assert3u(size1, >, 0);
184 	assert3p(status, !=, NULL);
185 
186 	tb_error_t ret = exclaveindicatorcontroller_sensorrequest_copybuffer(
187 		&eic_client, buffer, offset1, size1, offset2, size2,
188 		^(exclaveindicatorcontroller_sensorstatusresponse_s result) {
189 		*status = eic_sensorstatus_to_sensor_status(result);
190 	});
191 
192 	return ret == TB_ERROR_SUCCESS ? KERN_SUCCESS : KERN_FAILURE;
193 }
194 
195 /* -------------------------------------------------------------------------- */
196 #pragma mark sensor
197 
198 static LCK_GRP_DECLARE(sensor_lck_grp, "exclaves_sensor");
199 
200 typedef struct {
201 	/*
202 	 * Count of how many times sensor_start has been called on this sensor
203 	 * without a corresponding sensor_stop.
204 	 */
205 	uint64_t s_startcount;
206 
207 	/* Last start time. */
208 	uint64_t s_start_abs;
209 
210 	/* Last stop time. */
211 	uint64_t s_stop_abs;
212 
213 	/* mutex to protect updates to the above */
214 	lck_mtx_t s_mutex;
215 
216 	/* Keep track of whether this sensor was initialised or not. */
217 	bool s_initialised;
218 } exclaves_sensor_t;
219 
220 /**
221  * A reverse lookup table for the sensor resources,
222  * as the kpi uses sensor ids directly to access the same resources */
223 static exclaves_sensor_t sensors[EXCLAVES_SENSOR_MAX];
224 
225 /*
226  * A thread call used to periodically call "status" on any open sensors.
227  */
228 static thread_call_t sensor_healthcheck_tcall = NULL;
229 
230 static inline bool
valid_sensor(exclaves_sensor_type_t sensor_type)231 valid_sensor(exclaves_sensor_type_t sensor_type)
232 {
233 	switch (sensor_type) {
234 	case EXCLAVES_SENSOR_CAM:
235 	case EXCLAVES_SENSOR_MIC:
236 	case EXCLAVES_SENSOR_CAM_ALT_FACEID:
237 	case EXCLAVES_SENSOR_CAM_ALT_FACEID_DELAYED:
238 		return true;
239 	default:
240 		return false;
241 	}
242 }
243 
244 static inline exclaves_sensor_t *
sensor_type_to_sensor(exclaves_sensor_type_t sensor_type)245 sensor_type_to_sensor(exclaves_sensor_type_t sensor_type)
246 {
247 	assert(valid_sensor(sensor_type));
248 	return &sensors[sensor_type - 1];
249 }
250 
251 static inline exclaves_sensor_type_t
sensor_to_sensor_type(exclaves_sensor_t * sensor)252 sensor_to_sensor_type(exclaves_sensor_t *sensor)
253 {
254 	assert3p(sensor, <=, &sensors[EXCLAVES_SENSOR_MAX]);
255 	assert3p(sensor, >=, &sensors[0]);
256 
257 	return (exclaves_sensor_type_t)((sensor - &sensors[0]) + 1);
258 }
259 
260 /* Calculate the next healthcheck time. */
261 static void
healthcheck_deadline(uint64_t * deadline,uint64_t * leeway)262 healthcheck_deadline(uint64_t *deadline, uint64_t *leeway)
263 {
264 	const uint32_t interval =
265 	    NSEC_PER_SEC / exclaves_display_healthcheck_rate_hz;
266 	clock_interval_to_deadline(interval, 1, deadline);
267 	nanoseconds_to_absolutetime(interval / 2, leeway);
268 }
269 
270 /*
271  * Do a healthcheck status call. The status call may be skipped if certain conditions are met.
272  * Returns false is status call was skipped.
273  */
274 static bool
do_healthcheck(exclaves_sensor_t * sensor)275 do_healthcheck(exclaves_sensor_t *sensor)
276 {
277 	LCK_MTX_ASSERT(&sensor->s_mutex, LCK_MTX_ASSERT_OWNED);
278 
279 	/*
280 	 * If the sensor has not started, and the min on-time has been processed,
281 	 * skip health check.
282 	 */
283 	if (sensor->s_startcount == 0 && sensor->s_stop_abs == 0) {
284 		return false;
285 	}
286 
287 	exclaves_sensor_status_t status;
288 	(void) exclaves_sensor_status(sensor_to_sensor_type(sensor), 0, &status);
289 
290 	return true;
291 }
292 
293 /*
294  * For stopped sensors, see if the minimum on-time has been reached. If so, do a
295  * status call. If the minimum on-time has not been reached, return a deadline
296  * for when it will be.
297  */
298 static void
do_min_on_time(exclaves_sensor_t * sensor,uint64_t * deadline,uint64_t * leeway)299 do_min_on_time(exclaves_sensor_t *sensor, uint64_t *deadline,
300     uint64_t *leeway)
301 {
302 	LCK_MTX_ASSERT(&sensor->s_mutex, LCK_MTX_ASSERT_OWNED);
303 
304 	/*
305 	 * The sensor hasn't stopped yet or has already had its min on-time
306 	 * processed.
307 	 */
308 	if (sensor->s_startcount != 0 || sensor->s_stop_abs == 0) {
309 		*deadline = UINT64_MAX;
310 		return;
311 	}
312 
313 	uint64_t min_time = 0;
314 	nanoseconds_to_absolutetime(EXCLAVES_EIC_MIN_SENSOR_TIME, &min_time);
315 	nanoseconds_to_absolutetime(50 * NSEC_PER_MSEC, leeway);
316 
317 	*deadline = sensor->s_stop_abs + min_time;
318 
319 	if (*deadline <= mach_absolute_time()) {
320 		/* The minimum on-time has been hit. Call status. */
321 		exclaves_sensor_status_t status;
322 		(void) exclaves_sensor_status(sensor_to_sensor_type(sensor), 0,
323 		    &status);
324 
325 		sensor->s_stop_abs = 0;
326 		*deadline = UINT64_MAX;
327 		return;
328 	}
329 
330 	/* The minimum on-time is in the future. Need to reschedule.  */
331 }
332 
333 /*
334  * Called from the threadcall to call into exclaves with a status command for
335  * every started sensor. Re-arms itself so it runs at a frequency set by the
336  * display healthcheck rate. Exits when there are no longer any started sensors.
337  * A sensor has a minimum on-time. For stopped sensors, call back into exclaves
338  * until this minimum time has been reached.
339  */
340 static void
exclaves_sensor_healthcheck(__unused void * param0,__unused void * param1)341 exclaves_sensor_healthcheck(__unused void *param0, __unused void *param1)
342 {
343 	uint64_t leeway, deadline = UINT64_MAX;
344 	uint64_t hc_leeway, hc_deadline;
345 	uint64_t mot_leeway, mot_deadline;
346 
347 	/*
348 	 * Calculate the next deadline up-front so the overhead of calling into
349 	 * exclaves doesn't add to the period.
350 	 */
351 	healthcheck_deadline(&hc_deadline, &hc_leeway);
352 
353 	for (int i = 0; i < EXCLAVES_SENSOR_MAX; i++) {
354 		exclaves_sensor_t *sensor = &sensors[i];
355 
356 		if (!sensor->s_initialised) {
357 			continue;
358 		}
359 
360 		lck_mtx_lock(&sensor->s_mutex);
361 
362 		if (do_healthcheck(sensor) &&
363 		    hc_deadline < deadline) {
364 			deadline = hc_deadline;
365 			leeway = hc_leeway;
366 		}
367 
368 		do_min_on_time(sensor, &mot_deadline, &mot_leeway);
369 		if (mot_deadline < deadline) {
370 			deadline = mot_deadline;
371 			leeway = mot_leeway;
372 		}
373 
374 		lck_mtx_unlock(&sensor->s_mutex);
375 	}
376 
377 	if (deadline != UINT64_MAX) {
378 		thread_call_enter_delayed_with_leeway(sensor_healthcheck_tcall,
379 		    NULL, deadline, leeway, THREAD_CALL_DELAY_LEEWAY);
380 	}
381 }
382 
383 static kern_return_t
exclaves_sensor_init(void)384 exclaves_sensor_init(void)
385 {
386 	kern_return_t kr = exclaves_eic_init();
387 	if (kr != KERN_SUCCESS) {
388 		return kr;
389 	}
390 
391 	for (uint32_t i = 1; i <= EXCLAVES_SENSOR_MAX; i++) {
392 		exclaves_sensor_t *sensor = sensor_type_to_sensor(i);
393 
394 		lck_mtx_init(&sensor->s_mutex, &sensor_lck_grp, NULL);
395 
396 		sensor->s_startcount = 0;
397 		sensor->s_initialised = true;
398 	}
399 
400 	sensor_healthcheck_tcall =
401 	    thread_call_allocate_with_priority(exclaves_sensor_healthcheck,
402 	    NULL, THREAD_CALL_PRIORITY_KERNEL);
403 
404 	return KERN_SUCCESS;
405 }
406 EXCLAVES_BOOT_TASK(exclaves_sensor_init, EXCLAVES_BOOT_RANK_ANY);
407 
408 kern_return_t
exclaves_sensor_start(exclaves_sensor_type_t sensor_type,uint64_t flags,exclaves_sensor_status_t * status)409 exclaves_sensor_start(exclaves_sensor_type_t sensor_type, uint64_t flags,
410     exclaves_sensor_status_t *status)
411 {
412 	if (!valid_sensor(sensor_type)) {
413 		return KERN_INVALID_ARGUMENT;
414 	}
415 
416 	exclaves_sensor_t *sensor = sensor_type_to_sensor(sensor_type);
417 	if (!sensor->s_initialised) {
418 		return KERN_FAILURE;
419 	}
420 
421 	lck_mtx_lock(&sensor->s_mutex);
422 	kern_return_t kr;
423 
424 	if (sensor->s_startcount == UINT64_MAX) {
425 		lck_mtx_unlock(&sensor->s_mutex);
426 		return KERN_INVALID_ARGUMENT;
427 	}
428 
429 	if (sensor->s_startcount > 0) {
430 		kr = exclaves_eic_sensor_status(sensor_type, flags, status);
431 		if (kr == KERN_SUCCESS) {
432 			sensor->s_startcount += 1;
433 		}
434 		lck_mtx_unlock(&sensor->s_mutex);
435 		return kr;
436 	}
437 
438 	// call start iff startcount is 0
439 	kr = exclaves_eic_sensor_start(sensor_type, flags, status);
440 	if (kr != KERN_SUCCESS) {
441 		lck_mtx_unlock(&sensor->s_mutex);
442 		return kr;
443 	}
444 
445 	sensor->s_start_abs = mach_absolute_time();
446 	sensor->s_startcount += 1;
447 
448 	lck_mtx_unlock(&sensor->s_mutex);
449 
450 	/* Kick off the periodic status check. */
451 	(void)thread_call_enter(sensor_healthcheck_tcall);
452 
453 	return KERN_SUCCESS;
454 }
455 
456 kern_return_t
exclaves_sensor_stop(exclaves_sensor_type_t sensor_type,uint64_t flags,exclaves_sensor_status_t * status)457 exclaves_sensor_stop(exclaves_sensor_type_t sensor_type, uint64_t flags,
458     exclaves_sensor_status_t *status)
459 {
460 	if (!valid_sensor(sensor_type)) {
461 		return KERN_INVALID_ARGUMENT;
462 	}
463 
464 	exclaves_sensor_t *sensor = sensor_type_to_sensor(sensor_type);
465 	if (!sensor->s_initialised) {
466 		return KERN_FAILURE;
467 	}
468 
469 	kern_return_t kr;
470 
471 	lck_mtx_lock(&sensor->s_mutex);
472 
473 	if (sensor->s_startcount == 0) {
474 		lck_mtx_unlock(&sensor->s_mutex);
475 		return KERN_INVALID_ARGUMENT;
476 	}
477 
478 	if (sensor->s_startcount > 1) {
479 		kr = exclaves_eic_sensor_status(sensor_type, flags, status);
480 		if (kr == KERN_SUCCESS) {
481 			sensor->s_startcount -= 1;
482 		}
483 		lck_mtx_unlock(&sensor->s_mutex);
484 		return kr;
485 	}
486 
487 	// call stop iff startcount is going to go to 0
488 	kr = exclaves_eic_sensor_stop(sensor_type);
489 	if (kr != KERN_SUCCESS) {
490 		lck_mtx_unlock(&sensor->s_mutex);
491 		return kr;
492 	}
493 
494 	sensor->s_stop_abs = mach_absolute_time();
495 	sensor->s_startcount = 0;
496 
497 	kr = exclaves_eic_sensor_status(sensor_type, flags, status);
498 
499 	lck_mtx_unlock(&sensor->s_mutex);
500 
501 	(void)thread_call_enter(sensor_healthcheck_tcall);
502 
503 	return kr;
504 }
505 
506 kern_return_t
exclaves_sensor_status(exclaves_sensor_type_t sensor_type,uint64_t flags,exclaves_sensor_status_t * status)507 exclaves_sensor_status(exclaves_sensor_type_t sensor_type, uint64_t flags,
508     exclaves_sensor_status_t *status)
509 {
510 	if (!valid_sensor(sensor_type)) {
511 		return KERN_INVALID_ARGUMENT;
512 	}
513 
514 	exclaves_sensor_t *sensor = sensor_type_to_sensor(sensor_type);
515 	if (!sensor->s_initialised) {
516 		return KERN_FAILURE;
517 	}
518 
519 	return exclaves_eic_sensor_status(sensor_type, flags, status);
520 }
521 
522 kern_return_t
exclaves_display_healthcheck_rate(uint64_t ns)523 exclaves_display_healthcheck_rate(uint64_t ns)
524 {
525 	/*
526 	 * Make sure that the initialisation has taken place before calling into
527 	 * the EIC. Any sensor is sufficient.
528 	 */
529 	exclaves_sensor_t *sensor = sensor_type_to_sensor(EXCLAVES_SENSOR_CAM);
530 	if (!sensor->s_initialised) {
531 		return KERN_FAILURE;
532 	}
533 
534 	return exclaves_eic_display_healthcheck_rate(ns);
535 }
536 
537 kern_return_t
exclaves_sensor_copy(uint32_t buffer,uint64_t size1,uint64_t offset1,uint64_t size2,uint64_t offset2,exclaves_sensor_status_t * status)538 exclaves_sensor_copy(uint32_t buffer, uint64_t size1, uint64_t offset1,
539     uint64_t size2, uint64_t offset2, exclaves_sensor_status_t *status)
540 {
541 	/*
542 	 * Make sure that the initialisation has taken place before calling into
543 	 * the EIC. Any sensor is sufficient.
544 	 */
545 	exclaves_sensor_t *sensor = sensor_type_to_sensor(EXCLAVES_SENSOR_CAM);
546 	if (!sensor->s_initialised) {
547 		return KERN_FAILURE;
548 	}
549 
550 
551 	return exclaves_eic_sensor_copy(buffer, size1, offset1, size2, offset2,
552 	           status);
553 }
554 
555 #else /* CONFIG_EXCLAVES */
556 
557 kern_return_t
exclaves_display_healthcheck_rate(__unused uint64_t ns)558 exclaves_display_healthcheck_rate(__unused uint64_t ns)
559 {
560 	return KERN_NOT_SUPPORTED;
561 }
562 
563 #endif /* CONFIG_EXCLAVES */
564