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_resource.h"
35#include "exclaves_sensor.h"
36
37#if CONFIG_EXCLAVES
38
39#include <kern/locks.h>
40#include <kern/thread_call.h>
41
42#include "kern/exclaves.tightbeam.h"
43
44/* -------------------------------------------------------------------------- */
45#pragma mark EIC
46
47#define EXCLAVES_EIC "com.apple.service.ExclaveIndicatorController"
48
49/* Default to 120Hz */
50static uint64_t exclaves_display_healthcheck_rate_hz =
51 EXCLAVEINDICATORCONTROLLER_REQUESTEDREFRESHRATE_HZ_120;
52
53static exclaveindicatorcontroller_sensorrequest_s eic_client;
54
55static inline __unused exclaveindicatorcontroller_sensortype_s
56sensor_type_to_eic_sensortype(exclaves_sensor_type_t type)
57{
58 assert3u(type, >, 0);
59 assert3u(type, <=, EXCLAVES_SENSOR_MAX);
60
61 switch (type) {
62 case EXCLAVES_SENSOR_CAM:
63 return EXCLAVEINDICATORCONTROLLER_SENSORTYPE_SENSOR_CAM;
64 case EXCLAVES_SENSOR_MIC:
65 return EXCLAVEINDICATORCONTROLLER_SENSORTYPE_SENSOR_MIC;
66 case EXCLAVES_SENSOR_CAM_ALT_FACEID:
67 return EXCLAVEINDICATORCONTROLLER_SENSORTYPE_SENSOR_CAM_ALT_FACEID;
68 default:
69 panic("unknown sensor type");
70 }
71}
72
73static inline exclaves_sensor_status_t
74eic_sensorstatus_to_sensor_status(exclaveindicatorcontroller_sensorstatusresponse_s status)
75{
76 assert3u(status, >, 0);
77 assert3u(status, <=, EXCLAVEINDICATORCONTROLLER_SENSORSTATUSRESPONSE_SENSOR_CONTROL);
78
79 switch (status) {
80 case EXCLAVEINDICATORCONTROLLER_SENSORSTATUSRESPONSE_SENSOR_ALLOWED:
81 return EXCLAVES_SENSOR_STATUS_ALLOWED;
82 case EXCLAVEINDICATORCONTROLLER_SENSORSTATUSRESPONSE_SENSOR_DENIED:
83 return EXCLAVES_SENSOR_STATUS_DENIED;
84 case EXCLAVEINDICATORCONTROLLER_SENSORSTATUSRESPONSE_SENSOR_CONTROL:
85 return EXCLAVES_SENSOR_STATUS_CONTROL;
86 default:
87 panic("unknown sensor status");
88 }
89}
90
91static kern_return_t
92exclaves_eic_init(void)
93{
94 exclaves_id_t eic_id = exclaves_service_lookup(EXCLAVES_DOMAIN_KERNEL,
95 EXCLAVES_EIC);
96 if (eic_id == UINT64_C(~0)) {
97 return KERN_FAILURE;
98 }
99
100 tb_endpoint_t ep = tb_endpoint_create_with_value(
101 TB_TRANSPORT_TYPE_XNU, eic_id, TB_ENDPOINT_OPTIONS_NONE);
102
103 tb_error_t ret =
104 exclaveindicatorcontroller_sensorrequest__init(&eic_client, ep);
105
106 return ret == TB_ERROR_SUCCESS ? KERN_SUCCESS : KERN_FAILURE;
107}
108
109static kern_return_t
110exclaves_eic_display_healthcheck_rate(uint64_t ns)
111{
112 exclaveindicatorcontroller_requestedrefreshrate_s rate;
113
114 /* Convert time to frequency and round up to nearest supported value. */
115 switch (NSEC_PER_SEC / ns) {
116 case 0 ... 30:
117 exclaves_display_healthcheck_rate_hz = 30;
118 rate = EXCLAVEINDICATORCONTROLLER_REQUESTEDREFRESHRATE_HZ_30;
119 break;
120 case 31 ... 60:
121 exclaves_display_healthcheck_rate_hz = 60;
122 rate = EXCLAVEINDICATORCONTROLLER_REQUESTEDREFRESHRATE_HZ_60;
123 break;
124 default:
125 exclaves_display_healthcheck_rate_hz = 120;
126 rate = EXCLAVEINDICATORCONTROLLER_REQUESTEDREFRESHRATE_HZ_120;
127 break;
128 }
129
130 tb_error_t ret = exclaveindicatorcontroller_sensorrequest_requestdisplayhealthcheckrate(
131 &eic_client, rate, ^(__unused exclaveindicatorcontroller_requestresponse_s result) {});
132
133 return ret == TB_ERROR_SUCCESS ? KERN_SUCCESS : KERN_FAILURE;
134}
135
136static kern_return_t
137exclaves_eic_sensor_start(exclaves_sensor_type_t __unused sensor_type,
138 __assert_only uint64_t flags, exclaves_sensor_status_t *status)
139{
140 assert3p(status, !=, NULL);
141 assert3u(flags, ==, 0);
142
143 *status = EXCLAVES_SENSOR_STATUS_ALLOWED;
144 return KERN_SUCCESS;
145}
146
147static kern_return_t
148exclaves_eic_sensor_stop(exclaves_sensor_type_t __unused sensor_type)
149{
150 return KERN_SUCCESS;
151}
152
153static kern_return_t
154exclaves_eic_sensor_status(exclaves_sensor_type_t __unused sensor_type,
155 __assert_only uint64_t flags, exclaves_sensor_status_t *status)
156{
157 assert3p(status, !=, NULL);
158 assert3u(flags, ==, 0);
159
160 *status = EXCLAVES_SENSOR_STATUS_ALLOWED;
161 return KERN_SUCCESS;
162}
163
164/*
165 * It is intentional to keep "buffer" untyped here as it avoids xnu having to
166 * understand what those IDs are at all. They are simply passed through from the
167 * resource table as-is.
168 */
169static kern_return_t
170exclaves_eic_sensor_copy(uint32_t buffer, uint64_t size1, uint64_t offset1,
171 uint64_t size2, uint64_t offset2, exclaves_sensor_status_t *status)
172{
173 assert3u(size1, >, 0);
174 assert3p(status, !=, NULL);
175
176 /*
177 * The plan in the near future is that this TB call will take both sets
178 * of size/offset. In the meantime call it twice here.
179 */
180 tb_error_t ret = exclaveindicatorcontroller_sensorrequest_copy(
181 &eic_client, buffer, 0, offset1, size1,
182 ^(exclaveindicatorcontroller_sensorstatusresponse_s result) {
183 *status = eic_sensorstatus_to_sensor_status(result);
184 });
185
186 if (ret != TB_ERROR_SUCCESS) {
187 return ret;
188 }
189
190 /* Return early if the status isn't EXCLAVES_SENSOR_STATUS_ALLOWED */
191 if (*status != EXCLAVES_SENSOR_STATUS_ALLOWED || size2 == 0) {
192 return KERN_SUCCESS;
193 }
194
195 ret = exclaveindicatorcontroller_sensorrequest_copy(
196 &eic_client, buffer, 0, offset2, size2,
197 ^(exclaveindicatorcontroller_sensorstatusresponse_s result) {
198 *status = eic_sensorstatus_to_sensor_status(result);
199 });
200
201 return ret == TB_ERROR_SUCCESS ? KERN_SUCCESS : KERN_FAILURE;
202}
203
204/* -------------------------------------------------------------------------- */
205#pragma mark sensor
206
207static LCK_GRP_DECLARE(sensor_lck_grp, "exclaves_sensor");
208
209typedef struct {
210 /*
211 * Count of how many times sensor_start has been called on this sensor
212 * without a corresponding sensor_stop.
213 */
214 uint64_t s_startcount;
215
216 /* mutex to protect updates to the above */
217 lck_mtx_t s_mutex;
218
219 /* Keep track of whether this sensor was initialised or not. */
220 bool s_initialised;
221} exclaves_sensor_t;
222
223/**
224 * A reverse lookup table for the sensor resources,
225 * as the kpi uses sensor ids directly to access the same resources */
226static exclaves_sensor_t sensors[EXCLAVES_SENSOR_MAX];
227
228/*
229 * A thread call used to periodically call "status" on any open sensors.
230 */
231static thread_call_t sensor_healthcheck_tcall = NULL;
232
233static inline bool
234valid_sensor(exclaves_sensor_type_t sensor_type)
235{
236 switch (sensor_type) {
237 case EXCLAVES_SENSOR_CAM:
238 case EXCLAVES_SENSOR_MIC:
239 case EXCLAVES_SENSOR_CAM_ALT_FACEID:
240 return true;
241 default:
242 return false;
243 }
244}
245
246static inline exclaves_sensor_t *
247sensor_type_to_sensor(exclaves_sensor_type_t sensor_type)
248{
249 assert(valid_sensor(sensor_type));
250 return &sensors[sensor_type - 1];
251}
252
253static inline exclaves_sensor_type_t
254sensor_to_sensor_type(exclaves_sensor_t *sensor)
255{
256 assert3p(sensor, <=, &sensors[EXCLAVES_SENSOR_MAX]);
257 assert3p(sensor, >=, &sensors[0]);
258
259 return (exclaves_sensor_type_t)((sensor - &sensors[0]) + 1);
260}
261
262/*
263 * Called from the threadcall to call into exclaves with a status command for
264 * every started sensor. Re-arms itself so it runs at a frequency set by the
265 * display healthcheck rate. Exits when there are no longer any started sensors.
266 */
267static void
268exclaves_sensor_healthcheck(__unused void *param0, __unused void *param1)
269{
270 bool reschedule = false;
271
272 /*
273 * Calculate the next deadline up-front so the overhead of calling into
274 * exclaves doesn't add to the period.
275 */
276 uint64_t deadline = 0;
277 uint64_t leeway = 0;
278 const uint32_t interval =
279 NSEC_PER_SEC / exclaves_display_healthcheck_rate_hz;
280 clock_interval_to_deadline(interval, 1, &deadline);
281 nanoseconds_to_absolutetime(interval / 2, &leeway);
282
283 for (int i = 0; i < EXCLAVES_SENSOR_MAX; i++) {
284 exclaves_sensor_t *sensor = &sensors[i];
285
286 if (!sensor->s_initialised) {
287 continue;
288 }
289
290 lck_mtx_lock(&sensor->s_mutex);
291
292 exclaves_sensor_status_t status;
293 if (sensor->s_startcount != 0) {
294 (void) exclaves_sensor_status(
295 sensor_to_sensor_type(sensor), 0, &status);
296 reschedule = true;
297 }
298
299 lck_mtx_unlock(&sensor->s_mutex);
300 }
301
302 if (reschedule) {
303 thread_call_enter_delayed_with_leeway(sensor_healthcheck_tcall,
304 NULL, deadline, leeway, THREAD_CALL_DELAY_LEEWAY);
305 }
306}
307
308static kern_return_t
309exclaves_sensor_init(void)
310{
311 kern_return_t kr = exclaves_eic_init();
312 if (kr != KERN_SUCCESS) {
313 return kr;
314 }
315
316 for (uint32_t i = 1; i <= EXCLAVES_SENSOR_MAX; i++) {
317 exclaves_sensor_t *sensor = sensor_type_to_sensor(i);
318
319 lck_mtx_init(&sensor->s_mutex, &sensor_lck_grp, NULL);
320
321 sensor->s_startcount = 0;
322 sensor->s_initialised = true;
323 }
324
325 sensor_healthcheck_tcall =
326 thread_call_allocate_with_priority(exclaves_sensor_healthcheck,
327 NULL, THREAD_CALL_PRIORITY_KERNEL);
328
329 return KERN_SUCCESS;
330}
331EXCLAVES_BOOT_TASK(exclaves_sensor_init, EXCLAVES_BOOT_RANK_ANY);
332
333kern_return_t
334exclaves_sensor_start(exclaves_sensor_type_t sensor_type, uint64_t flags,
335 exclaves_sensor_status_t *status)
336{
337 if (!valid_sensor(sensor_type)) {
338 return KERN_INVALID_ARGUMENT;
339 }
340
341 exclaves_sensor_t *sensor = sensor_type_to_sensor(sensor_type);
342 if (!sensor->s_initialised) {
343 return KERN_FAILURE;
344 }
345
346 lck_mtx_lock(&sensor->s_mutex);
347 kern_return_t kr;
348
349 if (sensor->s_startcount == UINT64_MAX) {
350 lck_mtx_unlock(&sensor->s_mutex);
351 return KERN_INVALID_ARGUMENT;
352 }
353
354 if (sensor->s_startcount > 0) {
355 kr = exclaves_eic_sensor_status(sensor_type, flags, status);
356 if (kr == KERN_SUCCESS) {
357 sensor->s_startcount += 1;
358 }
359 lck_mtx_unlock(&sensor->s_mutex);
360 return kr;
361 }
362
363 // call start iff startcount is 0
364 kr = exclaves_eic_sensor_start(sensor_type, flags, status);
365 if (kr != KERN_SUCCESS) {
366 lck_mtx_unlock(&sensor->s_mutex);
367 return kr;
368 }
369
370 sensor->s_startcount += 1;
371
372 lck_mtx_unlock(&sensor->s_mutex);
373
374 /* Kick off the periodic status check. */
375 (void)thread_call_enter(sensor_healthcheck_tcall);
376
377 return KERN_SUCCESS;
378}
379
380kern_return_t
381exclaves_sensor_stop(exclaves_sensor_type_t sensor_type, uint64_t flags,
382 exclaves_sensor_status_t *status)
383{
384 if (!valid_sensor(sensor_type)) {
385 return KERN_INVALID_ARGUMENT;
386 }
387
388 exclaves_sensor_t *sensor = sensor_type_to_sensor(sensor_type);
389 if (!sensor->s_initialised) {
390 return KERN_FAILURE;
391 }
392
393 kern_return_t kr;
394
395 lck_mtx_lock(&sensor->s_mutex);
396
397 if (sensor->s_startcount == 0) {
398 lck_mtx_unlock(&sensor->s_mutex);
399 return KERN_INVALID_ARGUMENT;
400 }
401
402 if (sensor->s_startcount > 1) {
403 kr = exclaves_eic_sensor_status(sensor_type, flags, status);
404 if (kr == KERN_SUCCESS) {
405 sensor->s_startcount -= 1;
406 }
407 lck_mtx_unlock(&sensor->s_mutex);
408 return kr;
409 }
410
411 // call stop iff startcount is going to go to 0
412 kr = exclaves_eic_sensor_stop(sensor_type);
413 if (kr != KERN_SUCCESS) {
414 lck_mtx_unlock(&sensor->s_mutex);
415 return kr;
416 }
417
418 sensor->s_startcount = 0;
419 kr = exclaves_eic_sensor_status(sensor_type, flags, status);
420
421 lck_mtx_unlock(&sensor->s_mutex);
422
423 return kr;
424}
425
426kern_return_t
427exclaves_sensor_status(exclaves_sensor_type_t sensor_type, uint64_t flags,
428 exclaves_sensor_status_t *status)
429{
430 if (!valid_sensor(sensor_type)) {
431 return KERN_INVALID_ARGUMENT;
432 }
433
434 exclaves_sensor_t *sensor = sensor_type_to_sensor(sensor_type);
435 if (!sensor->s_initialised) {
436 return KERN_FAILURE;
437 }
438
439 return exclaves_eic_sensor_status(sensor_type, flags, status);
440}
441
442kern_return_t
443exclaves_display_healthcheck_rate(uint64_t ns)
444{
445 /*
446 * Make sure that the initialisation has taken place before calling into
447 * the EIC. Any sensor is sufficient.
448 */
449 exclaves_sensor_t *sensor = sensor_type_to_sensor(EXCLAVES_SENSOR_CAM);
450 if (!sensor->s_initialised) {
451 return KERN_FAILURE;
452 }
453
454 return exclaves_eic_display_healthcheck_rate(ns);
455}
456
457kern_return_t
458exclaves_sensor_copy(uint32_t buffer, uint64_t size1, uint64_t offset1,
459 uint64_t size2, uint64_t offset2, exclaves_sensor_status_t *status)
460{
461 /*
462 * Make sure that the initialisation has taken place before calling into
463 * the EIC. Any sensor is sufficient.
464 */
465 exclaves_sensor_t *sensor = sensor_type_to_sensor(EXCLAVES_SENSOR_CAM);
466 if (!sensor->s_initialised) {
467 return KERN_FAILURE;
468 }
469
470
471 return exclaves_eic_sensor_copy(buffer, size1, offset1, size2, offset2,
472 status);
473}
474
475#else /* CONFIG_EXCLAVES */
476
477kern_return_t
478exclaves_display_healthcheck_rate(__unused uint64_t ns)
479{
480 return KERN_NOT_SUPPORTED;
481}
482
483#endif /* CONFIG_EXCLAVES */
484