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 if the sensor has been started.
272 */
273 static bool
do_healthcheck(exclaves_sensor_t * sensor)274 do_healthcheck(exclaves_sensor_t *sensor)
275 {
276 LCK_MTX_ASSERT(&sensor->s_mutex, LCK_MTX_ASSERT_OWNED);
277
278 /* The sensor isn't started. */
279 if (sensor->s_startcount == 0) {
280 return false;
281 }
282
283 exclaves_sensor_status_t status;
284 (void) exclaves_sensor_status(sensor_to_sensor_type(sensor), 0, &status);
285
286 return true;
287 }
288
289 /*
290 * For stopped sensors, see if the minimum on-time has been reached. If so, do a
291 * status call. If the minimum on-time has not been reached, return a deadline
292 * for when it will be.
293 */
294 static void
do_min_on_time(exclaves_sensor_t * sensor,uint64_t * deadline,uint64_t * leeway)295 do_min_on_time(exclaves_sensor_t *sensor, uint64_t *deadline,
296 uint64_t *leeway)
297 {
298 LCK_MTX_ASSERT(&sensor->s_mutex, LCK_MTX_ASSERT_OWNED);
299
300 /*
301 * The sensor hasn't stopped yet or has already had its min on-time
302 * processed.
303 */
304 if (sensor->s_startcount != 0 || sensor->s_stop_abs == 0) {
305 *deadline = UINT64_MAX;
306 return;
307 }
308
309 uint64_t min_time = 0;
310 nanoseconds_to_absolutetime(EXCLAVES_EIC_MIN_SENSOR_TIME, &min_time);
311 nanoseconds_to_absolutetime(50 * NSEC_PER_MSEC, leeway);
312
313 *deadline = sensor->s_start_abs + min_time;
314
315 if (*deadline <= mach_absolute_time()) {
316 /* The minimum on-time has been hit. Call status. */
317 exclaves_sensor_status_t status;
318 (void) exclaves_sensor_status(sensor_to_sensor_type(sensor), 0,
319 &status);
320
321 sensor->s_stop_abs = 0;
322 *deadline = UINT64_MAX;
323 return;
324 }
325
326 /* The minimum on-time is in the future. Need to reschedule. */
327 }
328
329 /*
330 * Called from the threadcall to call into exclaves with a status command for
331 * every started sensor. Re-arms itself so it runs at a frequency set by the
332 * display healthcheck rate. Exits when there are no longer any started sensors.
333 * A sensor has a minimum on-time. For stopped sensors, call back into exclaves
334 * after this minimum time has been reached.
335 */
336 static void
exclaves_sensor_healthcheck(__unused void * param0,__unused void * param1)337 exclaves_sensor_healthcheck(__unused void *param0, __unused void *param1)
338 {
339 uint64_t leeway, deadline = UINT64_MAX;
340 uint64_t hc_leeway, hc_deadline;
341 uint64_t mot_leeway, mot_deadline;
342
343 /*
344 * Calculate the next deadline up-front so the overhead of calling into
345 * exclaves doesn't add to the period.
346 */
347 healthcheck_deadline(&hc_deadline, &hc_leeway);
348
349 for (int i = 0; i < EXCLAVES_SENSOR_MAX; i++) {
350 exclaves_sensor_t *sensor = &sensors[i];
351
352 if (!sensor->s_initialised) {
353 continue;
354 }
355
356 lck_mtx_lock(&sensor->s_mutex);
357
358 if (do_healthcheck(sensor) &&
359 hc_deadline < deadline) {
360 deadline = hc_deadline;
361 leeway = hc_leeway;
362 }
363
364 do_min_on_time(sensor, &mot_deadline, &mot_leeway);
365 if (mot_deadline < deadline) {
366 deadline = mot_deadline;
367 leeway = mot_leeway;
368 }
369
370 lck_mtx_unlock(&sensor->s_mutex);
371 }
372
373 if (deadline != UINT64_MAX) {
374 thread_call_enter_delayed_with_leeway(sensor_healthcheck_tcall,
375 NULL, deadline, leeway, THREAD_CALL_DELAY_LEEWAY);
376 }
377 }
378
379 static kern_return_t
exclaves_sensor_init(void)380 exclaves_sensor_init(void)
381 {
382 kern_return_t kr = exclaves_eic_init();
383 if (kr != KERN_SUCCESS) {
384 return kr;
385 }
386
387 for (uint32_t i = 1; i <= EXCLAVES_SENSOR_MAX; i++) {
388 exclaves_sensor_t *sensor = sensor_type_to_sensor(i);
389
390 lck_mtx_init(&sensor->s_mutex, &sensor_lck_grp, NULL);
391
392 sensor->s_startcount = 0;
393 sensor->s_initialised = true;
394 }
395
396 sensor_healthcheck_tcall =
397 thread_call_allocate_with_priority(exclaves_sensor_healthcheck,
398 NULL, THREAD_CALL_PRIORITY_KERNEL);
399
400 return KERN_SUCCESS;
401 }
402 EXCLAVES_BOOT_TASK(exclaves_sensor_init, EXCLAVES_BOOT_RANK_ANY);
403
404 kern_return_t
exclaves_sensor_start(exclaves_sensor_type_t sensor_type,uint64_t flags,exclaves_sensor_status_t * status)405 exclaves_sensor_start(exclaves_sensor_type_t sensor_type, uint64_t flags,
406 exclaves_sensor_status_t *status)
407 {
408 if (!valid_sensor(sensor_type)) {
409 return KERN_INVALID_ARGUMENT;
410 }
411
412 exclaves_sensor_t *sensor = sensor_type_to_sensor(sensor_type);
413 if (!sensor->s_initialised) {
414 return KERN_FAILURE;
415 }
416
417 lck_mtx_lock(&sensor->s_mutex);
418 kern_return_t kr;
419
420 if (sensor->s_startcount == UINT64_MAX) {
421 lck_mtx_unlock(&sensor->s_mutex);
422 return KERN_INVALID_ARGUMENT;
423 }
424
425 if (sensor->s_startcount > 0) {
426 kr = exclaves_eic_sensor_status(sensor_type, flags, status);
427 if (kr == KERN_SUCCESS) {
428 sensor->s_startcount += 1;
429 }
430 lck_mtx_unlock(&sensor->s_mutex);
431 return kr;
432 }
433
434 // call start iff startcount is 0
435 kr = exclaves_eic_sensor_start(sensor_type, flags, status);
436 if (kr != KERN_SUCCESS) {
437 lck_mtx_unlock(&sensor->s_mutex);
438 return kr;
439 }
440
441 sensor->s_start_abs = mach_absolute_time();
442 sensor->s_startcount += 1;
443
444 lck_mtx_unlock(&sensor->s_mutex);
445
446 /* Kick off the periodic status check. */
447 (void)thread_call_enter(sensor_healthcheck_tcall);
448
449 return KERN_SUCCESS;
450 }
451
452 kern_return_t
exclaves_sensor_stop(exclaves_sensor_type_t sensor_type,uint64_t flags,exclaves_sensor_status_t * status)453 exclaves_sensor_stop(exclaves_sensor_type_t sensor_type, uint64_t flags,
454 exclaves_sensor_status_t *status)
455 {
456 if (!valid_sensor(sensor_type)) {
457 return KERN_INVALID_ARGUMENT;
458 }
459
460 exclaves_sensor_t *sensor = sensor_type_to_sensor(sensor_type);
461 if (!sensor->s_initialised) {
462 return KERN_FAILURE;
463 }
464
465 kern_return_t kr;
466
467 lck_mtx_lock(&sensor->s_mutex);
468
469 if (sensor->s_startcount == 0) {
470 lck_mtx_unlock(&sensor->s_mutex);
471 return KERN_INVALID_ARGUMENT;
472 }
473
474 if (sensor->s_startcount > 1) {
475 kr = exclaves_eic_sensor_status(sensor_type, flags, status);
476 if (kr == KERN_SUCCESS) {
477 sensor->s_startcount -= 1;
478 }
479 lck_mtx_unlock(&sensor->s_mutex);
480 return kr;
481 }
482
483 // call stop iff startcount is going to go to 0
484 kr = exclaves_eic_sensor_stop(sensor_type);
485 if (kr != KERN_SUCCESS) {
486 lck_mtx_unlock(&sensor->s_mutex);
487 return kr;
488 }
489
490 sensor->s_stop_abs = mach_absolute_time();
491 sensor->s_startcount = 0;
492
493 kr = exclaves_eic_sensor_status(sensor_type, flags, status);
494
495 lck_mtx_unlock(&sensor->s_mutex);
496
497 (void)thread_call_enter(sensor_healthcheck_tcall);
498
499 return kr;
500 }
501
502 kern_return_t
exclaves_sensor_status(exclaves_sensor_type_t sensor_type,uint64_t flags,exclaves_sensor_status_t * status)503 exclaves_sensor_status(exclaves_sensor_type_t sensor_type, uint64_t flags,
504 exclaves_sensor_status_t *status)
505 {
506 if (!valid_sensor(sensor_type)) {
507 return KERN_INVALID_ARGUMENT;
508 }
509
510 exclaves_sensor_t *sensor = sensor_type_to_sensor(sensor_type);
511 if (!sensor->s_initialised) {
512 return KERN_FAILURE;
513 }
514
515 return exclaves_eic_sensor_status(sensor_type, flags, status);
516 }
517
518 kern_return_t
exclaves_display_healthcheck_rate(uint64_t ns)519 exclaves_display_healthcheck_rate(uint64_t ns)
520 {
521 /*
522 * Make sure that the initialisation has taken place before calling into
523 * the EIC. Any sensor is sufficient.
524 */
525 exclaves_sensor_t *sensor = sensor_type_to_sensor(EXCLAVES_SENSOR_CAM);
526 if (!sensor->s_initialised) {
527 return KERN_FAILURE;
528 }
529
530 return exclaves_eic_display_healthcheck_rate(ns);
531 }
532
533 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)534 exclaves_sensor_copy(uint32_t buffer, uint64_t size1, uint64_t offset1,
535 uint64_t size2, uint64_t offset2, exclaves_sensor_status_t *status)
536 {
537 /*
538 * Make sure that the initialisation has taken place before calling into
539 * the EIC. Any sensor is sufficient.
540 */
541 exclaves_sensor_t *sensor = sensor_type_to_sensor(EXCLAVES_SENSOR_CAM);
542 if (!sensor->s_initialised) {
543 return KERN_FAILURE;
544 }
545
546
547 return exclaves_eic_sensor_copy(buffer, size1, offset1, size2, offset2,
548 status);
549 }
550
551 #else /* CONFIG_EXCLAVES */
552
553 kern_return_t
exclaves_display_healthcheck_rate(__unused uint64_t ns)554 exclaves_display_healthcheck_rate(__unused uint64_t ns)
555 {
556 return KERN_NOT_SUPPORTED;
557 }
558
559 #endif /* CONFIG_EXCLAVES */
560