]> git.saurik.com Git - apple/xnu.git/blob - osfmk/kern/coalition.c
xnu-6153.81.5.tar.gz
[apple/xnu.git] / osfmk / kern / coalition.c
1 /*
2 * Copyright (c) 2019 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 <kern/kern_types.h>
30 #include <mach/mach_types.h>
31 #include <mach/boolean.h>
32
33 #include <kern/coalition.h>
34 #include <kern/exc_resource.h>
35 #include <kern/host.h>
36 #include <kern/kalloc.h>
37 #include <kern/ledger.h>
38 #include <kern/mach_param.h> /* for TASK_CHUNK */
39 #if MONOTONIC
40 #include <kern/monotonic.h>
41 #endif /* MONOTONIC */
42 #include <kern/policy_internal.h>
43 #include <kern/task.h>
44 #include <kern/thread_group.h>
45 #include <kern/zalloc.h>
46
47 #include <libkern/OSAtomic.h>
48
49 #include <mach/coalition_notification_server.h>
50 #include <mach/host_priv.h>
51 #include <mach/host_special_ports.h>
52
53 #include <os/log.h>
54
55 #include <sys/errno.h>
56
57 /*
58 * BSD interface functions
59 */
60 int coalitions_get_list(int type, struct procinfo_coalinfo *coal_list, int list_sz);
61 coalition_t task_get_coalition(task_t task, int type);
62 boolean_t coalition_is_leader(task_t task, coalition_t coal);
63 task_t coalition_get_leader(coalition_t coal);
64 int coalition_get_task_count(coalition_t coal);
65 uint64_t coalition_get_page_count(coalition_t coal, int *ntasks);
66 int coalition_get_pid_list(coalition_t coal, uint32_t rolemask, int sort_order,
67 int *pid_list, int list_sz);
68
69 /* defined in task.c */
70 extern ledger_template_t task_ledger_template;
71
72 /*
73 * Templates; task template is copied due to potential allocation limits on
74 * task ledgers.
75 */
76 ledger_template_t coalition_task_ledger_template = NULL;
77 ledger_template_t coalition_ledger_template = NULL;
78
79 extern int proc_selfpid(void);
80 /*
81 * Coalition zone needs limits. We expect there will be as many coalitions as
82 * tasks (same order of magnitude), so use the task zone's limits.
83 * */
84 #define CONFIG_COALITION_MAX CONFIG_TASK_MAX
85 #define COALITION_CHUNK TASK_CHUNK
86
87 int unrestrict_coalition_syscalls;
88 int merge_adaptive_coalitions;
89
90 lck_attr_t coalitions_lck_attr;
91 lck_grp_t coalitions_lck_grp;
92 lck_grp_attr_t coalitions_lck_grp_attr;
93
94 /* coalitions_list_lock protects coalition_count, coalitions queue, next_coalition_id. */
95 decl_lck_mtx_data(static, coalitions_list_lock);
96 static uint64_t coalition_count;
97 static uint64_t coalition_next_id = 1;
98 static queue_head_t coalitions_q;
99
100 coalition_t init_coalition[COALITION_NUM_TYPES];
101 coalition_t corpse_coalition[COALITION_NUM_TYPES];
102
103 zone_t coalition_zone;
104
105 static const char *
106 coal_type_str(int type)
107 {
108 switch (type) {
109 case COALITION_TYPE_RESOURCE:
110 return "RESOURCE";
111 case COALITION_TYPE_JETSAM:
112 return "JETSAM";
113 default:
114 return "<unknown>";
115 }
116 }
117
118 struct coalition_type {
119 int type;
120 int has_default;
121 /*
122 * init
123 * pre-condition: coalition just allocated (unlocked), unreferenced,
124 * type field set
125 */
126 kern_return_t (*init)(coalition_t coal, boolean_t privileged);
127
128 /*
129 * dealloc
130 * pre-condition: coalition unlocked
131 * pre-condition: coalition refcount=0, active_count=0,
132 * termrequested=1, terminated=1, reaped=1
133 */
134 void (*dealloc)(coalition_t coal);
135
136 /*
137 * adopt_task
138 * pre-condition: coalition locked
139 * pre-condition: coalition !repead and !terminated
140 */
141 kern_return_t (*adopt_task)(coalition_t coal, task_t task);
142
143 /*
144 * remove_task
145 * pre-condition: coalition locked
146 * pre-condition: task has been removed from coalition's task list
147 */
148 kern_return_t (*remove_task)(coalition_t coal, task_t task);
149
150 /*
151 * set_taskrole
152 * pre-condition: coalition locked
153 * pre-condition: task added to coalition's task list,
154 * active_count >= 1 (at least the given task is active)
155 */
156 kern_return_t (*set_taskrole)(coalition_t coal, task_t task, int role);
157
158 /*
159 * get_taskrole
160 * pre-condition: coalition locked
161 * pre-condition: task added to coalition's task list,
162 * active_count >= 1 (at least the given task is active)
163 */
164 int (*get_taskrole)(coalition_t coal, task_t task);
165
166 /*
167 * iterate_tasks
168 * pre-condition: coalition locked
169 */
170 void (*iterate_tasks)(coalition_t coal, void *ctx, void (*callback)(coalition_t, void *, task_t));
171 };
172
173 /*
174 * COALITION_TYPE_RESOURCE
175 */
176
177 static kern_return_t i_coal_resource_init(coalition_t coal, boolean_t privileged);
178 static void i_coal_resource_dealloc(coalition_t coal);
179 static kern_return_t i_coal_resource_adopt_task(coalition_t coal, task_t task);
180 static kern_return_t i_coal_resource_remove_task(coalition_t coal, task_t task);
181 static kern_return_t i_coal_resource_set_taskrole(coalition_t coal,
182 task_t task, int role);
183 static int i_coal_resource_get_taskrole(coalition_t coal, task_t task);
184 static void i_coal_resource_iterate_tasks(coalition_t coal, void *ctx,
185 void (*callback)(coalition_t, void *, task_t));
186
187 /*
188 * Ensure COALITION_NUM_THREAD_QOS_TYPES defined in mach/coalition.h still
189 * matches THREAD_QOS_LAST defined in mach/thread_policy.h
190 */
191 static_assert(COALITION_NUM_THREAD_QOS_TYPES == THREAD_QOS_LAST);
192
193 struct i_resource_coalition {
194 /*
195 * This keeps track of resource utilization of tasks that are no longer active
196 * in the coalition and is updated when a task is removed from the coalition.
197 */
198 ledger_t ledger;
199 uint64_t bytesread;
200 uint64_t byteswritten;
201 uint64_t energy;
202 uint64_t gpu_time;
203 uint64_t logical_immediate_writes;
204 uint64_t logical_deferred_writes;
205 uint64_t logical_invalidated_writes;
206 uint64_t logical_metadata_writes;
207 uint64_t logical_immediate_writes_to_external;
208 uint64_t logical_deferred_writes_to_external;
209 uint64_t logical_invalidated_writes_to_external;
210 uint64_t logical_metadata_writes_to_external;
211 uint64_t cpu_ptime;
212 uint64_t cpu_time_eqos[COALITION_NUM_THREAD_QOS_TYPES]; /* cpu time per effective QoS class */
213 uint64_t cpu_time_rqos[COALITION_NUM_THREAD_QOS_TYPES]; /* cpu time per requested QoS class */
214 uint64_t cpu_instructions;
215 uint64_t cpu_cycles;
216
217 uint64_t task_count; /* tasks that have started in this coalition */
218 uint64_t dead_task_count; /* tasks that have exited in this coalition;
219 * subtract from task_count to get count
220 * of "active" tasks */
221 /*
222 * Count the length of time this coalition had at least one active task.
223 * This can be a 'denominator' to turn e.g. cpu_time to %cpu.
224 * */
225 uint64_t last_became_nonempty_time;
226 uint64_t time_nonempty;
227
228 queue_head_t tasks; /* List of active tasks in the coalition */
229 /*
230 * This ledger is used for triggering resource exception. For the tracked resources, this is updated
231 * when the member tasks' resource usage changes.
232 */
233 ledger_t resource_monitor_ledger;
234 };
235
236 /*
237 * COALITION_TYPE_JETSAM
238 */
239
240 static kern_return_t i_coal_jetsam_init(coalition_t coal, boolean_t privileged);
241 static void i_coal_jetsam_dealloc(coalition_t coal);
242 static kern_return_t i_coal_jetsam_adopt_task(coalition_t coal, task_t task);
243 static kern_return_t i_coal_jetsam_remove_task(coalition_t coal, task_t task);
244 static kern_return_t i_coal_jetsam_set_taskrole(coalition_t coal,
245 task_t task, int role);
246 int i_coal_jetsam_get_taskrole(coalition_t coal, task_t task);
247 static void i_coal_jetsam_iterate_tasks(coalition_t coal, void *ctx,
248 void (*callback)(coalition_t, void *, task_t));
249
250 struct i_jetsam_coalition {
251 task_t leader;
252 queue_head_t extensions;
253 queue_head_t services;
254 queue_head_t other;
255 struct thread_group *thread_group;
256 };
257
258
259 /*
260 * main coalition structure
261 */
262 struct coalition {
263 uint64_t id; /* monotonically increasing */
264 uint32_t type;
265 uint32_t role; /* default task role (background, adaptive, interactive, etc) */
266 uint32_t ref_count; /* Number of references to the memory containing this struct */
267 uint32_t active_count; /* Number of members of (tasks in) the
268 * coalition, plus vouchers referring
269 * to the coalition */
270 uint32_t focal_task_count; /* Number of TASK_FOREGROUND_APPLICATION tasks in the coalition */
271 uint32_t nonfocal_task_count; /* Number of TASK_BACKGROUND_APPLICATION tasks in the coalition */
272
273 /* coalition flags */
274 uint32_t privileged : 1; /* Members of this coalition may create
275 * and manage coalitions and may posix_spawn
276 * processes into selected coalitions */
277 /* ast? */
278 /* voucher */
279 uint32_t termrequested : 1; /* launchd has requested termination when coalition becomes empty */
280 uint32_t terminated : 1; /* coalition became empty and spawns are now forbidden */
281 uint32_t reaped : 1; /* reaped, invisible to userspace, but waiting for ref_count to go to zero */
282 uint32_t notified : 1; /* no-more-processes notification was sent via special port */
283 uint32_t efficient : 1; /* launchd has marked the coalition as efficient */
284 #if DEVELOPMENT || DEBUG
285 uint32_t should_notify : 1; /* should this coalition send notifications (default: yes) */
286 #endif
287
288 queue_chain_t coalitions; /* global list of coalitions */
289
290 decl_lck_mtx_data(, lock); /* Coalition lock. */
291
292 /* put coalition type-specific structures here */
293 union {
294 struct i_resource_coalition r;
295 struct i_jetsam_coalition j;
296 };
297 };
298
299 /*
300 * register different coalition types:
301 * these must be kept in the order specified in coalition.h
302 */
303 static const struct coalition_type
304 s_coalition_types[COALITION_NUM_TYPES] = {
305 {
306 COALITION_TYPE_RESOURCE,
307 1,
308 i_coal_resource_init,
309 i_coal_resource_dealloc,
310 i_coal_resource_adopt_task,
311 i_coal_resource_remove_task,
312 i_coal_resource_set_taskrole,
313 i_coal_resource_get_taskrole,
314 i_coal_resource_iterate_tasks,
315 },
316 {
317 COALITION_TYPE_JETSAM,
318 1,
319 i_coal_jetsam_init,
320 i_coal_jetsam_dealloc,
321 i_coal_jetsam_adopt_task,
322 i_coal_jetsam_remove_task,
323 i_coal_jetsam_set_taskrole,
324 i_coal_jetsam_get_taskrole,
325 i_coal_jetsam_iterate_tasks,
326 },
327 };
328
329 #define coal_call(coal, func, ...) \
330 (s_coalition_types[(coal)->type].func)(coal, ## __VA_ARGS__)
331
332
333 #define coalition_lock(c) do{ lck_mtx_lock(&c->lock); }while(0)
334 #define coalition_unlock(c) do{ lck_mtx_unlock(&c->lock); }while(0)
335
336 /*
337 * Define the coalition type to track focal tasks.
338 * On embedded, track them using jetsam coalitions since they have associated thread
339 * groups which reflect this property as a flag (and pass it down to CLPC).
340 * On non-embedded platforms, since not all coalitions have jetsam coalitions
341 * track focal counts on the resource coalition.
342 */
343 #if CONFIG_EMBEDDED
344 #define COALITION_FOCAL_TASKS_ACCOUNTING COALITION_TYPE_JETSAM
345 #else /* CONFIG_EMBEDDED */
346 #define COALITION_FOCAL_TASKS_ACCOUNTING COALITION_TYPE_RESOURCE
347 #endif /* CONFIG_EMBEDDED */
348
349
350 /*
351 *
352 * Coalition ledger implementation
353 *
354 */
355
356 struct coalition_ledger_indices coalition_ledgers =
357 {.logical_writes = -1, };
358 void __attribute__((noinline)) SENDING_NOTIFICATION__THIS_COALITION_IS_CAUSING_TOO_MUCH_IO(int flavor);
359
360 ledger_t
361 coalition_ledger_get_from_task(task_t task)
362 {
363 ledger_t ledger = LEDGER_NULL;
364 coalition_t coal = task->coalition[COALITION_TYPE_RESOURCE];
365
366 if (coal != NULL && (!queue_empty(&task->task_coalition[COALITION_TYPE_RESOURCE]))) {
367 ledger = coal->r.resource_monitor_ledger;
368 ledger_reference(ledger);
369 }
370 return ledger;
371 }
372
373
374 enum {
375 COALITION_IO_LEDGER_ENABLE,
376 COALITION_IO_LEDGER_DISABLE
377 };
378
379 void
380 coalition_io_monitor_ctl(struct coalition *coalition, uint32_t flags, int64_t limit)
381 {
382 ledger_t ledger = coalition->r.resource_monitor_ledger;
383
384 if (flags == COALITION_IO_LEDGER_ENABLE) {
385 /* Configure the logical I/O ledger */
386 ledger_set_limit(ledger, coalition_ledgers.logical_writes, (limit * 1024 * 1024), 0);
387 ledger_set_period(ledger, coalition_ledgers.logical_writes, (COALITION_LEDGER_MONITOR_INTERVAL_SECS * NSEC_PER_SEC));
388 } else if (flags == COALITION_IO_LEDGER_DISABLE) {
389 ledger_disable_refill(ledger, coalition_ledgers.logical_writes);
390 ledger_disable_callback(ledger, coalition_ledgers.logical_writes);
391 }
392 }
393
394 int
395 coalition_ledger_set_logical_writes_limit(struct coalition *coalition, int64_t limit)
396 {
397 int error = 0;
398
399 /* limit = -1 will be used to disable the limit and the callback */
400 if (limit > COALITION_MAX_LOGICAL_WRITES_LIMIT || limit == 0 || limit < -1) {
401 error = EINVAL;
402 goto out;
403 }
404
405 coalition_lock(coalition);
406 if (limit == -1) {
407 coalition_io_monitor_ctl(coalition, COALITION_IO_LEDGER_DISABLE, limit);
408 } else {
409 coalition_io_monitor_ctl(coalition, COALITION_IO_LEDGER_ENABLE, limit);
410 }
411 coalition_unlock(coalition);
412 out:
413 return error;
414 }
415
416 void __attribute__((noinline))
417 SENDING_NOTIFICATION__THIS_COALITION_IS_CAUSING_TOO_MUCH_IO(int flavor)
418 {
419 int pid = proc_selfpid();
420 ledger_amount_t new_limit;
421 task_t task = current_task();
422 struct ledger_entry_info lei;
423 kern_return_t kr;
424 ledger_t ledger;
425 struct coalition *coalition = task->coalition[COALITION_TYPE_RESOURCE];
426
427 assert(coalition != NULL);
428 ledger = coalition->r.resource_monitor_ledger;
429
430 switch (flavor) {
431 case FLAVOR_IO_LOGICAL_WRITES:
432 ledger_get_entry_info(ledger, coalition_ledgers.logical_writes, &lei);
433 trace_resource_violation(RMON_LOGWRITES_VIOLATED, &lei);
434 break;
435 default:
436 goto Exit;
437 }
438
439 os_log(OS_LOG_DEFAULT, "Coalition [%lld] caught causing excessive I/O (flavor: %d). Task I/O: %lld MB. [Limit : %lld MB per %lld secs]. Triggered by process [%d]\n",
440 coalition->id, flavor, (lei.lei_balance / (1024 * 1024)), (lei.lei_limit / (1024 * 1024)),
441 (lei.lei_refill_period / NSEC_PER_SEC), pid);
442
443 kr = send_resource_violation(send_disk_writes_violation, task, &lei, kRNFlagsNone);
444 if (kr) {
445 os_log(OS_LOG_DEFAULT, "ERROR %#x returned from send_resource_violation(disk_writes, ...)\n", kr);
446 }
447
448 /*
449 * Continue to monitor the coalition after it hits the initital limit, but increase
450 * the limit exponentially so that we don't spam the listener.
451 */
452 new_limit = (lei.lei_limit / 1024 / 1024) * 4;
453 coalition_lock(coalition);
454 if (new_limit > COALITION_MAX_LOGICAL_WRITES_LIMIT) {
455 coalition_io_monitor_ctl(coalition, COALITION_IO_LEDGER_DISABLE, -1);
456 } else {
457 coalition_io_monitor_ctl(coalition, COALITION_IO_LEDGER_ENABLE, new_limit);
458 }
459 coalition_unlock(coalition);
460
461 Exit:
462 return;
463 }
464
465 void
466 coalition_io_rate_exceeded(int warning, const void *param0, __unused const void *param1)
467 {
468 if (warning == 0) {
469 SENDING_NOTIFICATION__THIS_COALITION_IS_CAUSING_TOO_MUCH_IO((int)param0);
470 }
471 }
472
473 void
474 init_coalition_ledgers(void)
475 {
476 ledger_template_t t;
477 assert(coalition_ledger_template == NULL);
478
479 if ((t = ledger_template_create("Per-coalition ledgers")) == NULL) {
480 panic("couldn't create coalition ledger template");
481 }
482
483 coalition_ledgers.logical_writes = ledger_entry_add(t, "logical_writes", "res", "bytes");
484
485 if (coalition_ledgers.logical_writes < 0) {
486 panic("couldn't create entries for coaliton ledger template");
487 }
488
489 ledger_set_callback(t, coalition_ledgers.logical_writes, coalition_io_rate_exceeded, (void *)FLAVOR_IO_LOGICAL_WRITES, NULL);
490 ledger_template_complete(t);
491
492 coalition_task_ledger_template = ledger_template_copy(task_ledger_template, "Coalition task ledgers");
493
494 if (coalition_task_ledger_template == NULL) {
495 panic("couldn't create coalition task ledger template");
496 }
497
498 ledger_template_complete(coalition_task_ledger_template);
499
500 coalition_ledger_template = t;
501 }
502
503 void
504 coalition_io_ledger_update(task_t task, int32_t flavor, boolean_t is_credit, uint32_t io_size)
505 {
506 ledger_t ledger;
507 coalition_t coal = task->coalition[COALITION_TYPE_RESOURCE];
508
509 assert(coal != NULL);
510 ledger = coal->r.resource_monitor_ledger;
511 if (LEDGER_VALID(ledger)) {
512 if (flavor == FLAVOR_IO_LOGICAL_WRITES) {
513 if (is_credit) {
514 ledger_credit(ledger, coalition_ledgers.logical_writes, io_size);
515 } else {
516 ledger_debit(ledger, coalition_ledgers.logical_writes, io_size);
517 }
518 }
519 }
520 }
521
522 static void
523 coalition_notify_user(uint64_t id, uint32_t flags)
524 {
525 mach_port_t user_port;
526 kern_return_t kr;
527
528 kr = host_get_coalition_port(host_priv_self(), &user_port);
529 if ((kr != KERN_SUCCESS) || !IPC_PORT_VALID(user_port)) {
530 return;
531 }
532
533 coalition_notification(user_port, id, flags);
534 ipc_port_release_send(user_port);
535 }
536
537 /*
538 *
539 * COALITION_TYPE_RESOURCE
540 *
541 */
542 static kern_return_t
543 i_coal_resource_init(coalition_t coal, boolean_t privileged)
544 {
545 (void)privileged;
546 assert(coal && coal->type == COALITION_TYPE_RESOURCE);
547 coal->r.ledger = ledger_instantiate(coalition_task_ledger_template,
548 LEDGER_CREATE_ACTIVE_ENTRIES);
549 if (coal->r.ledger == NULL) {
550 return KERN_RESOURCE_SHORTAGE;
551 }
552
553 coal->r.resource_monitor_ledger = ledger_instantiate(coalition_ledger_template,
554 LEDGER_CREATE_ACTIVE_ENTRIES);
555 if (coal->r.resource_monitor_ledger == NULL) {
556 return KERN_RESOURCE_SHORTAGE;
557 }
558
559 queue_init(&coal->r.tasks);
560
561 return KERN_SUCCESS;
562 }
563
564 static void
565 i_coal_resource_dealloc(coalition_t coal)
566 {
567 assert(coal && coal->type == COALITION_TYPE_RESOURCE);
568
569 ledger_dereference(coal->r.ledger);
570 ledger_dereference(coal->r.resource_monitor_ledger);
571 }
572
573 static kern_return_t
574 i_coal_resource_adopt_task(coalition_t coal, task_t task)
575 {
576 struct i_resource_coalition *cr;
577
578 assert(coal && coal->type == COALITION_TYPE_RESOURCE);
579 assert(queue_empty(&task->task_coalition[COALITION_TYPE_RESOURCE]));
580
581 cr = &coal->r;
582 cr->task_count++;
583
584 if (cr->task_count < cr->dead_task_count) {
585 panic("%s: coalition %p id:%llu type:%s task_count(%llu) < dead_task_count(%llu)",
586 __func__, coal, coal->id, coal_type_str(coal->type),
587 cr->task_count, cr->dead_task_count);
588 }
589
590 /* If moving from 0->1 active tasks */
591 if (cr->task_count - cr->dead_task_count == 1) {
592 cr->last_became_nonempty_time = mach_absolute_time();
593 }
594
595 /* put the task on the coalition's list of tasks */
596 enqueue_tail(&cr->tasks, &task->task_coalition[COALITION_TYPE_RESOURCE]);
597
598 coal_dbg("Added PID:%d to id:%llu, task_count:%llu, dead_count:%llu, nonempty_time:%llu",
599 task_pid(task), coal->id, cr->task_count, cr->dead_task_count,
600 cr->last_became_nonempty_time);
601
602 return KERN_SUCCESS;
603 }
604
605 static kern_return_t
606 i_coal_resource_remove_task(coalition_t coal, task_t task)
607 {
608 struct i_resource_coalition *cr;
609
610 assert(coal && coal->type == COALITION_TYPE_RESOURCE);
611 assert(task->coalition[COALITION_TYPE_RESOURCE] == coal);
612 assert(!queue_empty(&task->task_coalition[COALITION_TYPE_RESOURCE]));
613
614 /*
615 * handle resource coalition accounting rollup for dead tasks
616 */
617 cr = &coal->r;
618
619 cr->dead_task_count++;
620
621 if (cr->task_count < cr->dead_task_count) {
622 panic("%s: coalition %p id:%llu type:%s task_count(%llu) < dead_task_count(%llu)",
623 __func__, coal, coal->id, coal_type_str(coal->type), cr->task_count, cr->dead_task_count);
624 }
625
626 /* If moving from 1->0 active tasks */
627 if (cr->task_count - cr->dead_task_count == 0) {
628 uint64_t last_time_nonempty = mach_absolute_time() - cr->last_became_nonempty_time;
629 cr->last_became_nonempty_time = 0;
630 cr->time_nonempty += last_time_nonempty;
631 }
632
633 /* Do not roll up for exec'd task or exec copy task */
634 if (!task_is_exec_copy(task) && !task_did_exec(task)) {
635 ledger_rollup(cr->ledger, task->ledger);
636 cr->bytesread += task->task_io_stats->disk_reads.size;
637 cr->byteswritten += task->task_io_stats->total_io.size - task->task_io_stats->disk_reads.size;
638 #if !CONFIG_EMBEDDED
639 cr->gpu_time += task_gpu_utilisation(task);
640 #else
641 cr->energy += task_energy(task);
642 #endif
643 cr->logical_immediate_writes += task->task_writes_counters_internal.task_immediate_writes;
644 cr->logical_deferred_writes += task->task_writes_counters_internal.task_deferred_writes;
645 cr->logical_invalidated_writes += task->task_writes_counters_internal.task_invalidated_writes;
646 cr->logical_metadata_writes += task->task_writes_counters_internal.task_metadata_writes;
647 cr->logical_immediate_writes_to_external += task->task_writes_counters_external.task_immediate_writes;
648 cr->logical_deferred_writes_to_external += task->task_writes_counters_external.task_deferred_writes;
649 cr->logical_invalidated_writes_to_external += task->task_writes_counters_external.task_invalidated_writes;
650 cr->logical_metadata_writes_to_external += task->task_writes_counters_external.task_metadata_writes;
651 cr->cpu_ptime += task_cpu_ptime(task);
652 task_update_cpu_time_qos_stats(task, cr->cpu_time_eqos, cr->cpu_time_rqos);
653 #if MONOTONIC
654 uint64_t counts[MT_CORE_NFIXED] = {};
655 (void)mt_fixed_task_counts(task, counts);
656 cr->cpu_cycles += counts[MT_CORE_CYCLES];
657 #if defined(MT_CORE_INSTRS)
658 cr->cpu_instructions += counts[MT_CORE_INSTRS];
659 #endif /* defined(MT_CORE_INSTRS) */
660 #endif /* MONOTONIC */
661 }
662
663 /* remove the task from the coalition's list */
664 remqueue(&task->task_coalition[COALITION_TYPE_RESOURCE]);
665 queue_chain_init(task->task_coalition[COALITION_TYPE_RESOURCE]);
666
667 coal_dbg("removed PID:%d from id:%llu, task_count:%llu, dead_count:%llu",
668 task_pid(task), coal->id, cr->task_count, cr->dead_task_count);
669
670 return KERN_SUCCESS;
671 }
672
673 static kern_return_t
674 i_coal_resource_set_taskrole(__unused coalition_t coal,
675 __unused task_t task, __unused int role)
676 {
677 return KERN_SUCCESS;
678 }
679
680 static int
681 i_coal_resource_get_taskrole(__unused coalition_t coal, __unused task_t task)
682 {
683 task_t t;
684
685 assert(coal && coal->type == COALITION_TYPE_RESOURCE);
686
687 qe_foreach_element(t, &coal->r.tasks, task_coalition[COALITION_TYPE_RESOURCE]) {
688 if (t == task) {
689 return COALITION_TASKROLE_UNDEF;
690 }
691 }
692
693 return -1;
694 }
695
696 static void
697 i_coal_resource_iterate_tasks(coalition_t coal, void *ctx, void (*callback)(coalition_t, void *, task_t))
698 {
699 task_t t;
700 assert(coal && coal->type == COALITION_TYPE_RESOURCE);
701
702 qe_foreach_element(t, &coal->r.tasks, task_coalition[COALITION_TYPE_RESOURCE])
703 callback(coal, ctx, t);
704 }
705
706 kern_return_t
707 coalition_resource_usage_internal(coalition_t coal, struct coalition_resource_usage *cru_out)
708 {
709 kern_return_t kr;
710 ledger_amount_t credit, debit;
711 int i;
712
713 if (coal->type != COALITION_TYPE_RESOURCE) {
714 return KERN_INVALID_ARGUMENT;
715 }
716
717 /* Return KERN_INVALID_ARGUMENT for Corpse coalition */
718 for (i = 0; i < COALITION_NUM_TYPES; i++) {
719 if (coal == corpse_coalition[i]) {
720 return KERN_INVALID_ARGUMENT;
721 }
722 }
723
724 ledger_t sum_ledger = ledger_instantiate(coalition_task_ledger_template, LEDGER_CREATE_ACTIVE_ENTRIES);
725 if (sum_ledger == LEDGER_NULL) {
726 return KERN_RESOURCE_SHORTAGE;
727 }
728
729 coalition_lock(coal);
730
731 /*
732 * Start with the coalition's ledger, which holds the totals from all
733 * the dead tasks.
734 */
735 ledger_rollup(sum_ledger, coal->r.ledger);
736 uint64_t bytesread = coal->r.bytesread;
737 uint64_t byteswritten = coal->r.byteswritten;
738 uint64_t gpu_time = coal->r.gpu_time;
739 uint64_t energy = coal->r.energy;
740 uint64_t logical_immediate_writes = coal->r.logical_immediate_writes;
741 uint64_t logical_deferred_writes = coal->r.logical_deferred_writes;
742 uint64_t logical_invalidated_writes = coal->r.logical_invalidated_writes;
743 uint64_t logical_metadata_writes = coal->r.logical_metadata_writes;
744 uint64_t logical_immediate_writes_to_external = coal->r.logical_immediate_writes_to_external;
745 uint64_t logical_deferred_writes_to_external = coal->r.logical_deferred_writes_to_external;
746 uint64_t logical_invalidated_writes_to_external = coal->r.logical_invalidated_writes_to_external;
747 uint64_t logical_metadata_writes_to_external = coal->r.logical_metadata_writes_to_external;
748 int64_t cpu_time_billed_to_me = 0;
749 int64_t cpu_time_billed_to_others = 0;
750 int64_t energy_billed_to_me = 0;
751 int64_t energy_billed_to_others = 0;
752 uint64_t cpu_ptime = coal->r.cpu_ptime;
753 uint64_t cpu_time_eqos[COALITION_NUM_THREAD_QOS_TYPES];
754 memcpy(cpu_time_eqos, coal->r.cpu_time_eqos, sizeof(cpu_time_eqos));
755 uint64_t cpu_time_rqos[COALITION_NUM_THREAD_QOS_TYPES];
756 memcpy(cpu_time_rqos, coal->r.cpu_time_rqos, sizeof(cpu_time_rqos));
757 uint64_t cpu_instructions = coal->r.cpu_instructions;
758 uint64_t cpu_cycles = coal->r.cpu_cycles;
759
760 /*
761 * Add to that all the active tasks' ledgers. Tasks cannot deallocate
762 * out from under us, since we hold the coalition lock.
763 */
764 task_t task;
765 qe_foreach_element(task, &coal->r.tasks, task_coalition[COALITION_TYPE_RESOURCE]) {
766 /*
767 * Rolling up stats for exec copy task or exec'd task will lead to double accounting.
768 * Cannot take task lock after taking coaliton lock
769 */
770 if (task_is_exec_copy(task) || task_did_exec(task)) {
771 continue;
772 }
773
774 ledger_rollup(sum_ledger, task->ledger);
775 bytesread += task->task_io_stats->disk_reads.size;
776 byteswritten += task->task_io_stats->total_io.size - task->task_io_stats->disk_reads.size;
777 #if !CONFIG_EMBEDDED
778 gpu_time += task_gpu_utilisation(task);
779 #else
780 energy += task_energy(task);
781 #endif
782 logical_immediate_writes += task->task_writes_counters_internal.task_immediate_writes;
783 logical_deferred_writes += task->task_writes_counters_internal.task_deferred_writes;
784 logical_invalidated_writes += task->task_writes_counters_internal.task_invalidated_writes;
785 logical_metadata_writes += task->task_writes_counters_internal.task_metadata_writes;
786 logical_immediate_writes_to_external += task->task_writes_counters_external.task_immediate_writes;
787 logical_deferred_writes_to_external += task->task_writes_counters_external.task_deferred_writes;
788 logical_invalidated_writes_to_external += task->task_writes_counters_external.task_invalidated_writes;
789 logical_metadata_writes_to_external += task->task_writes_counters_external.task_metadata_writes;
790
791 cpu_ptime += task_cpu_ptime(task);
792 task_update_cpu_time_qos_stats(task, cpu_time_eqos, cpu_time_rqos);
793 #if MONOTONIC
794 uint64_t counts[MT_CORE_NFIXED] = {};
795 (void)mt_fixed_task_counts(task, counts);
796 cpu_cycles += counts[MT_CORE_CYCLES];
797 #if defined(MT_CORE_INSTRS)
798 cpu_instructions += counts[MT_CORE_INSTRS];
799 #endif /* defined(MT_CORE_INSTRS) */
800 #endif /* MONOTONIC */
801 }
802
803 kr = ledger_get_balance(sum_ledger, task_ledgers.cpu_time_billed_to_me, (int64_t *)&cpu_time_billed_to_me);
804 if (kr != KERN_SUCCESS || cpu_time_billed_to_me < 0) {
805 cpu_time_billed_to_me = 0;
806 }
807
808 kr = ledger_get_balance(sum_ledger, task_ledgers.cpu_time_billed_to_others, (int64_t *)&cpu_time_billed_to_others);
809 if (kr != KERN_SUCCESS || cpu_time_billed_to_others < 0) {
810 cpu_time_billed_to_others = 0;
811 }
812
813 kr = ledger_get_balance(sum_ledger, task_ledgers.energy_billed_to_me, (int64_t *)&energy_billed_to_me);
814 if (kr != KERN_SUCCESS || energy_billed_to_me < 0) {
815 energy_billed_to_me = 0;
816 }
817
818 kr = ledger_get_balance(sum_ledger, task_ledgers.energy_billed_to_others, (int64_t *)&energy_billed_to_others);
819 if (kr != KERN_SUCCESS || energy_billed_to_others < 0) {
820 energy_billed_to_others = 0;
821 }
822
823 /* collect information from the coalition itself */
824 cru_out->tasks_started = coal->r.task_count;
825 cru_out->tasks_exited = coal->r.dead_task_count;
826
827 uint64_t time_nonempty = coal->r.time_nonempty;
828 uint64_t last_became_nonempty_time = coal->r.last_became_nonempty_time;
829
830 coalition_unlock(coal);
831
832 /* Copy the totals out of sum_ledger */
833 kr = ledger_get_entries(sum_ledger, task_ledgers.cpu_time,
834 &credit, &debit);
835 if (kr != KERN_SUCCESS) {
836 credit = 0;
837 }
838 cru_out->cpu_time = credit;
839 cru_out->cpu_time_billed_to_me = (uint64_t)cpu_time_billed_to_me;
840 cru_out->cpu_time_billed_to_others = (uint64_t)cpu_time_billed_to_others;
841 cru_out->energy_billed_to_me = (uint64_t)energy_billed_to_me;
842 cru_out->energy_billed_to_others = (uint64_t)energy_billed_to_others;
843
844 kr = ledger_get_entries(sum_ledger, task_ledgers.interrupt_wakeups,
845 &credit, &debit);
846 if (kr != KERN_SUCCESS) {
847 credit = 0;
848 }
849 cru_out->interrupt_wakeups = credit;
850
851 kr = ledger_get_entries(sum_ledger, task_ledgers.platform_idle_wakeups,
852 &credit, &debit);
853 if (kr != KERN_SUCCESS) {
854 credit = 0;
855 }
856 cru_out->platform_idle_wakeups = credit;
857
858 cru_out->bytesread = bytesread;
859 cru_out->byteswritten = byteswritten;
860 cru_out->gpu_time = gpu_time;
861 cru_out->energy = energy;
862 cru_out->logical_immediate_writes = logical_immediate_writes;
863 cru_out->logical_deferred_writes = logical_deferred_writes;
864 cru_out->logical_invalidated_writes = logical_invalidated_writes;
865 cru_out->logical_metadata_writes = logical_metadata_writes;
866 cru_out->logical_immediate_writes_to_external = logical_immediate_writes_to_external;
867 cru_out->logical_deferred_writes_to_external = logical_deferred_writes_to_external;
868 cru_out->logical_invalidated_writes_to_external = logical_invalidated_writes_to_external;
869 cru_out->logical_metadata_writes_to_external = logical_metadata_writes_to_external;
870 cru_out->cpu_ptime = cpu_ptime;
871 cru_out->cpu_time_eqos_len = COALITION_NUM_THREAD_QOS_TYPES;
872 memcpy(cru_out->cpu_time_eqos, cpu_time_eqos, sizeof(cru_out->cpu_time_eqos));
873 cru_out->cpu_cycles = cpu_cycles;
874 cru_out->cpu_instructions = cpu_instructions;
875 ledger_dereference(sum_ledger);
876 sum_ledger = LEDGER_NULL;
877
878 if (last_became_nonempty_time) {
879 time_nonempty += mach_absolute_time() - last_became_nonempty_time;
880 }
881 absolutetime_to_nanoseconds(time_nonempty, &cru_out->time_nonempty);
882
883 return KERN_SUCCESS;
884 }
885
886 /*
887 *
888 * COALITION_TYPE_JETSAM
889 *
890 */
891 static kern_return_t
892 i_coal_jetsam_init(coalition_t coal, boolean_t privileged)
893 {
894 assert(coal && coal->type == COALITION_TYPE_JETSAM);
895 (void)privileged;
896
897 coal->j.leader = TASK_NULL;
898 queue_head_init(coal->j.extensions);
899 queue_head_init(coal->j.services);
900 queue_head_init(coal->j.other);
901
902 return KERN_SUCCESS;
903 }
904
905 static void
906 i_coal_jetsam_dealloc(__unused coalition_t coal)
907 {
908 assert(coal && coal->type == COALITION_TYPE_JETSAM);
909
910 /* the coalition should be completely clear at this point */
911 assert(queue_empty(&coal->j.extensions));
912 assert(queue_empty(&coal->j.services));
913 assert(queue_empty(&coal->j.other));
914 assert(coal->j.leader == TASK_NULL);
915
916 }
917
918 static kern_return_t
919 i_coal_jetsam_adopt_task(coalition_t coal, task_t task)
920 {
921 struct i_jetsam_coalition *cj;
922 assert(coal && coal->type == COALITION_TYPE_JETSAM);
923
924 cj = &coal->j;
925
926 assert(queue_empty(&task->task_coalition[COALITION_TYPE_JETSAM]));
927
928 /* put each task initially in the "other" list */
929 enqueue_tail(&cj->other, &task->task_coalition[COALITION_TYPE_JETSAM]);
930 coal_dbg("coalition %lld adopted PID:%d as UNDEF",
931 coal->id, task_pid(task));
932
933 return KERN_SUCCESS;
934 }
935
936 static kern_return_t
937 i_coal_jetsam_remove_task(coalition_t coal, task_t task)
938 {
939 assert(coal && coal->type == COALITION_TYPE_JETSAM);
940 assert(task->coalition[COALITION_TYPE_JETSAM] == coal);
941
942 coal_dbg("removing PID:%d from coalition id:%lld",
943 task_pid(task), coal->id);
944
945 if (task == coal->j.leader) {
946 coal->j.leader = NULL;
947 coal_dbg(" PID:%d was the leader!", task_pid(task));
948 } else {
949 assert(!queue_empty(&task->task_coalition[COALITION_TYPE_JETSAM]));
950 }
951
952 /* remove the task from the specific coalition role queue */
953 remqueue(&task->task_coalition[COALITION_TYPE_JETSAM]);
954 queue_chain_init(task->task_coalition[COALITION_TYPE_RESOURCE]);
955
956 return KERN_SUCCESS;
957 }
958
959 static kern_return_t
960 i_coal_jetsam_set_taskrole(coalition_t coal, task_t task, int role)
961 {
962 struct i_jetsam_coalition *cj;
963 queue_t q = NULL;
964 assert(coal && coal->type == COALITION_TYPE_JETSAM);
965 assert(task->coalition[COALITION_TYPE_JETSAM] == coal);
966
967 cj = &coal->j;
968
969 switch (role) {
970 case COALITION_TASKROLE_LEADER:
971 coal_dbg("setting PID:%d as LEADER of %lld",
972 task_pid(task), coal->id);
973 if (cj->leader != TASK_NULL) {
974 /* re-queue the exiting leader onto the "other" list */
975 coal_dbg(" re-queue existing leader (%d) as OTHER",
976 task_pid(cj->leader));
977 re_queue_tail(&cj->other, &cj->leader->task_coalition[COALITION_TYPE_JETSAM]);
978 }
979 /*
980 * remove the task from the "other" list
981 * (where it was put by default)
982 */
983 remqueue(&task->task_coalition[COALITION_TYPE_JETSAM]);
984 queue_chain_init(task->task_coalition[COALITION_TYPE_JETSAM]);
985
986 /* set the coalition leader */
987 cj->leader = task;
988 break;
989 case COALITION_TASKROLE_XPC:
990 coal_dbg("setting PID:%d as XPC in %lld",
991 task_pid(task), coal->id);
992 q = (queue_t)&cj->services;
993 break;
994 case COALITION_TASKROLE_EXT:
995 coal_dbg("setting PID:%d as EXT in %lld",
996 task_pid(task), coal->id);
997 q = (queue_t)&cj->extensions;
998 break;
999 case COALITION_TASKROLE_NONE:
1000 /*
1001 * Tasks with a role of "none" should fall through to an
1002 * undefined role so long as the task is currently a member
1003 * of the coalition. This scenario can happen if a task is
1004 * killed (usually via jetsam) during exec.
1005 */
1006 if (task->coalition[COALITION_TYPE_JETSAM] != coal) {
1007 panic("%s: task %p attempting to set role %d "
1008 "in coalition %p to which it does not belong!", __func__, task, role, coal);
1009 }
1010 /* fall through */
1011 case COALITION_TASKROLE_UNDEF:
1012 coal_dbg("setting PID:%d as UNDEF in %lld",
1013 task_pid(task), coal->id);
1014 q = (queue_t)&cj->other;
1015 break;
1016 default:
1017 panic("%s: invalid role(%d) for task", __func__, role);
1018 return KERN_INVALID_ARGUMENT;
1019 }
1020
1021 if (q != NULL) {
1022 re_queue_tail(q, &task->task_coalition[COALITION_TYPE_JETSAM]);
1023 }
1024
1025 return KERN_SUCCESS;
1026 }
1027
1028 int
1029 i_coal_jetsam_get_taskrole(coalition_t coal, task_t task)
1030 {
1031 struct i_jetsam_coalition *cj;
1032 task_t t;
1033
1034 assert(coal && coal->type == COALITION_TYPE_JETSAM);
1035 assert(task->coalition[COALITION_TYPE_JETSAM] == coal);
1036
1037 cj = &coal->j;
1038
1039 if (task == cj->leader) {
1040 return COALITION_TASKROLE_LEADER;
1041 }
1042
1043 qe_foreach_element(t, &cj->services, task_coalition[COALITION_TYPE_JETSAM]) {
1044 if (t == task) {
1045 return COALITION_TASKROLE_XPC;
1046 }
1047 }
1048
1049 qe_foreach_element(t, &cj->extensions, task_coalition[COALITION_TYPE_JETSAM]) {
1050 if (t == task) {
1051 return COALITION_TASKROLE_EXT;
1052 }
1053 }
1054
1055 qe_foreach_element(t, &cj->other, task_coalition[COALITION_TYPE_JETSAM]) {
1056 if (t == task) {
1057 return COALITION_TASKROLE_UNDEF;
1058 }
1059 }
1060
1061 /* task not in the coalition?! */
1062 return COALITION_TASKROLE_NONE;
1063 }
1064
1065 static void
1066 i_coal_jetsam_iterate_tasks(coalition_t coal, void *ctx, void (*callback)(coalition_t, void *, task_t))
1067 {
1068 struct i_jetsam_coalition *cj;
1069 task_t t;
1070
1071 assert(coal && coal->type == COALITION_TYPE_JETSAM);
1072
1073 cj = &coal->j;
1074
1075 if (cj->leader) {
1076 callback(coal, ctx, cj->leader);
1077 }
1078
1079 qe_foreach_element(t, &cj->services, task_coalition[COALITION_TYPE_JETSAM])
1080 callback(coal, ctx, t);
1081
1082 qe_foreach_element(t, &cj->extensions, task_coalition[COALITION_TYPE_JETSAM])
1083 callback(coal, ctx, t);
1084
1085 qe_foreach_element(t, &cj->other, task_coalition[COALITION_TYPE_JETSAM])
1086 callback(coal, ctx, t);
1087 }
1088
1089
1090 /*
1091 *
1092 * Main Coalition implementation
1093 *
1094 */
1095
1096 /*
1097 * coalition_create_internal
1098 * Returns: New coalition object, referenced for the caller and unlocked.
1099 * Condition: coalitions_list_lock must be UNLOCKED.
1100 */
1101 kern_return_t
1102 coalition_create_internal(int type, int role, boolean_t privileged, coalition_t *out)
1103 {
1104 kern_return_t kr;
1105 struct coalition *new_coal;
1106
1107 if (type < 0 || type > COALITION_TYPE_MAX) {
1108 return KERN_INVALID_ARGUMENT;
1109 }
1110
1111 new_coal = (struct coalition *)zalloc(coalition_zone);
1112 if (new_coal == COALITION_NULL) {
1113 return KERN_RESOURCE_SHORTAGE;
1114 }
1115 bzero(new_coal, sizeof(*new_coal));
1116
1117 new_coal->type = type;
1118 new_coal->role = role;
1119
1120 /* initialize type-specific resources */
1121 kr = coal_call(new_coal, init, privileged);
1122 if (kr != KERN_SUCCESS) {
1123 zfree(coalition_zone, new_coal);
1124 return kr;
1125 }
1126
1127 /* One for caller, one for coalitions list */
1128 new_coal->ref_count = 2;
1129
1130 new_coal->privileged = privileged ? TRUE : FALSE;
1131 #if DEVELOPMENT || DEBUG
1132 new_coal->should_notify = 1;
1133 #endif
1134
1135 lck_mtx_init(&new_coal->lock, &coalitions_lck_grp, &coalitions_lck_attr);
1136
1137 lck_mtx_lock(&coalitions_list_lock);
1138 new_coal->id = coalition_next_id++;
1139 coalition_count++;
1140 enqueue_tail(&coalitions_q, &new_coal->coalitions);
1141
1142 KDBG_RELEASE(MACHDBG_CODE(DBG_MACH_COALITION, MACH_COALITION_NEW),
1143 new_coal->id, new_coal->type);
1144 lck_mtx_unlock(&coalitions_list_lock);
1145
1146 coal_dbg("id:%llu, type:%s", new_coal->id, coal_type_str(new_coal->type));
1147
1148 *out = new_coal;
1149 return KERN_SUCCESS;
1150 }
1151
1152 /*
1153 * coalition_release
1154 * Condition: coalition must be UNLOCKED.
1155 * */
1156 void
1157 coalition_release(coalition_t coal)
1158 {
1159 /* TODO: This can be done with atomics. */
1160 coalition_lock(coal);
1161 coal->ref_count--;
1162
1163 #if COALITION_DEBUG
1164 uint32_t rc = coal->ref_count;
1165 uint32_t ac = coal->active_count;
1166 #endif /* COALITION_DEBUG */
1167
1168 coal_dbg("id:%llu type:%s ref_count:%u active_count:%u%s",
1169 coal->id, coal_type_str(coal->type), rc, ac,
1170 rc <= 0 ? ", will deallocate now" : "");
1171
1172 if (coal->ref_count > 0) {
1173 coalition_unlock(coal);
1174 return;
1175 }
1176
1177 assert(coal->termrequested);
1178 assert(coal->terminated);
1179 assert(coal->active_count == 0);
1180 assert(coal->reaped);
1181 assert(coal->focal_task_count == 0);
1182 assert(coal->nonfocal_task_count == 0);
1183 KDBG_RELEASE(MACHDBG_CODE(DBG_MACH_COALITION, MACH_COALITION_FREE),
1184 coal->id, coal->type);
1185
1186 coal_call(coal, dealloc);
1187
1188 coalition_unlock(coal);
1189
1190 lck_mtx_destroy(&coal->lock, &coalitions_lck_grp);
1191
1192 zfree(coalition_zone, coal);
1193 }
1194
1195 /*
1196 * coalition_find_by_id_internal
1197 * Returns: Coalition object with specified id, NOT referenced.
1198 * If not found, returns COALITION_NULL.
1199 * Condition: coalitions_list_lock must be LOCKED.
1200 */
1201 static coalition_t
1202 coalition_find_by_id_internal(uint64_t coal_id)
1203 {
1204 if (coal_id == 0) {
1205 return COALITION_NULL;
1206 }
1207
1208 lck_mtx_assert(&coalitions_list_lock, LCK_MTX_ASSERT_OWNED);
1209 coalition_t coal;
1210 qe_foreach_element(coal, &coalitions_q, coalitions) {
1211 if (coal->id == coal_id) {
1212 return coal;
1213 }
1214 }
1215 return COALITION_NULL;
1216 }
1217
1218 /*
1219 * coalition_find_by_id
1220 * Returns: Coalition object with specified id, referenced.
1221 * Condition: coalitions_list_lock must be UNLOCKED.
1222 */
1223 coalition_t
1224 coalition_find_by_id(uint64_t cid)
1225 {
1226 if (cid == 0) {
1227 return COALITION_NULL;
1228 }
1229
1230 lck_mtx_lock(&coalitions_list_lock);
1231
1232 coalition_t coal = coalition_find_by_id_internal(cid);
1233 if (coal == COALITION_NULL) {
1234 lck_mtx_unlock(&coalitions_list_lock);
1235 return COALITION_NULL;
1236 }
1237
1238 coalition_lock(coal);
1239
1240 if (coal->reaped) {
1241 coalition_unlock(coal);
1242 lck_mtx_unlock(&coalitions_list_lock);
1243 return COALITION_NULL;
1244 }
1245
1246 if (coal->ref_count == 0) {
1247 panic("resurrecting coalition %p id:%llu type:%s, active_count:%u\n",
1248 coal, coal->id, coal_type_str(coal->type), coal->active_count);
1249 }
1250 coal->ref_count++;
1251 #if COALITION_DEBUG
1252 uint32_t rc = coal->ref_count;
1253 #endif
1254
1255 coalition_unlock(coal);
1256 lck_mtx_unlock(&coalitions_list_lock);
1257
1258 coal_dbg("id:%llu type:%s ref_count:%u",
1259 coal->id, coal_type_str(coal->type), rc);
1260
1261 return coal;
1262 }
1263
1264 /*
1265 * coalition_find_and_activate_by_id
1266 * Returns: Coalition object with specified id, referenced, and activated.
1267 * Condition: coalitions_list_lock must be UNLOCKED.
1268 * This is the function to use when putting a 'new' thing into a coalition,
1269 * like posix_spawn of an XPC service by launchd.
1270 * See also coalition_extend_active.
1271 */
1272 coalition_t
1273 coalition_find_and_activate_by_id(uint64_t cid)
1274 {
1275 if (cid == 0) {
1276 return COALITION_NULL;
1277 }
1278
1279 lck_mtx_lock(&coalitions_list_lock);
1280
1281 coalition_t coal = coalition_find_by_id_internal(cid);
1282 if (coal == COALITION_NULL) {
1283 lck_mtx_unlock(&coalitions_list_lock);
1284 return COALITION_NULL;
1285 }
1286
1287 coalition_lock(coal);
1288
1289 if (coal->reaped || coal->terminated) {
1290 /* Too late to put something new into this coalition, it's
1291 * already on its way out the door */
1292 coalition_unlock(coal);
1293 lck_mtx_unlock(&coalitions_list_lock);
1294 return COALITION_NULL;
1295 }
1296
1297 if (coal->ref_count == 0) {
1298 panic("resurrecting coalition %p id:%llu type:%s, active_count:%u\n",
1299 coal, coal->id, coal_type_str(coal->type), coal->active_count);
1300 }
1301
1302 coal->ref_count++;
1303 coal->active_count++;
1304
1305 #if COALITION_DEBUG
1306 uint32_t rc = coal->ref_count;
1307 uint32_t ac = coal->active_count;
1308 #endif
1309
1310 coalition_unlock(coal);
1311 lck_mtx_unlock(&coalitions_list_lock);
1312
1313 coal_dbg("id:%llu type:%s ref_count:%u, active_count:%u",
1314 coal->id, coal_type_str(coal->type), rc, ac);
1315
1316 return coal;
1317 }
1318
1319 uint64_t
1320 coalition_id(coalition_t coal)
1321 {
1322 return coal->id;
1323 }
1324
1325 void
1326 task_coalition_ids(task_t task, uint64_t ids[COALITION_NUM_TYPES])
1327 {
1328 int i;
1329 for (i = 0; i < COALITION_NUM_TYPES; i++) {
1330 if (task->coalition[i]) {
1331 ids[i] = task->coalition[i]->id;
1332 } else {
1333 ids[i] = 0;
1334 }
1335 }
1336 }
1337
1338 void
1339 task_coalition_roles(task_t task, int roles[COALITION_NUM_TYPES])
1340 {
1341 int i;
1342 memset(roles, 0, COALITION_NUM_TYPES * sizeof(roles[0]));
1343
1344 for (i = 0; i < COALITION_NUM_TYPES; i++) {
1345 if (task->coalition[i]) {
1346 coalition_lock(task->coalition[i]);
1347 roles[i] = coal_call(task->coalition[i],
1348 get_taskrole, task);
1349 coalition_unlock(task->coalition[i]);
1350 } else {
1351 roles[i] = COALITION_TASKROLE_NONE;
1352 }
1353 }
1354 }
1355
1356
1357 int
1358 coalition_type(coalition_t coal)
1359 {
1360 return coal->type;
1361 }
1362
1363 boolean_t
1364 coalition_term_requested(coalition_t coal)
1365 {
1366 return coal->termrequested;
1367 }
1368
1369 boolean_t
1370 coalition_is_terminated(coalition_t coal)
1371 {
1372 return coal->terminated;
1373 }
1374
1375 boolean_t
1376 coalition_is_reaped(coalition_t coal)
1377 {
1378 return coal->reaped;
1379 }
1380
1381 boolean_t
1382 coalition_is_privileged(coalition_t coal)
1383 {
1384 return coal->privileged || unrestrict_coalition_syscalls;
1385 }
1386
1387 boolean_t
1388 task_is_in_privileged_coalition(task_t task, int type)
1389 {
1390 if (type < 0 || type > COALITION_TYPE_MAX) {
1391 return FALSE;
1392 }
1393 if (unrestrict_coalition_syscalls) {
1394 return TRUE;
1395 }
1396 if (!task->coalition[type]) {
1397 return FALSE;
1398 }
1399 return task->coalition[type]->privileged;
1400 }
1401
1402 void
1403 task_coalition_update_gpu_stats(task_t task, uint64_t gpu_ns_delta)
1404 {
1405 coalition_t coal;
1406
1407 assert(task != TASK_NULL);
1408 if (gpu_ns_delta == 0) {
1409 return;
1410 }
1411
1412 coal = task->coalition[COALITION_TYPE_RESOURCE];
1413 assert(coal != COALITION_NULL);
1414
1415 coalition_lock(coal);
1416 coal->r.gpu_time += gpu_ns_delta;
1417 coalition_unlock(coal);
1418 }
1419
1420 boolean_t
1421 task_coalition_adjust_focal_count(task_t task, int count, uint32_t *new_count)
1422 {
1423 coalition_t coal = task->coalition[COALITION_FOCAL_TASKS_ACCOUNTING];
1424 if (coal == COALITION_NULL) {
1425 return FALSE;
1426 }
1427
1428 *new_count = os_atomic_add(&coal->focal_task_count, count, relaxed);
1429 assert(*new_count != UINT32_MAX);
1430 return TRUE;
1431 }
1432
1433 uint32_t
1434 task_coalition_focal_count(task_t task)
1435 {
1436 coalition_t coal = task->coalition[COALITION_FOCAL_TASKS_ACCOUNTING];
1437 if (coal == COALITION_NULL) {
1438 return 0;
1439 }
1440
1441 return coal->focal_task_count;
1442 }
1443
1444 boolean_t
1445 task_coalition_adjust_nonfocal_count(task_t task, int count, uint32_t *new_count)
1446 {
1447 coalition_t coal = task->coalition[COALITION_FOCAL_TASKS_ACCOUNTING];
1448 if (coal == COALITION_NULL) {
1449 return FALSE;
1450 }
1451
1452 *new_count = os_atomic_add(&coal->nonfocal_task_count, count, relaxed);
1453 assert(*new_count != UINT32_MAX);
1454 return TRUE;
1455 }
1456
1457 uint32_t
1458 task_coalition_nonfocal_count(task_t task)
1459 {
1460 coalition_t coal = task->coalition[COALITION_FOCAL_TASKS_ACCOUNTING];
1461 if (coal == COALITION_NULL) {
1462 return 0;
1463 }
1464
1465 return coal->nonfocal_task_count;
1466 }
1467
1468 void
1469 coalition_set_efficient(coalition_t coal)
1470 {
1471 coalition_lock(coal);
1472 coal->efficient = TRUE;
1473 coalition_unlock(coal);
1474 }
1475
1476
1477 void
1478 coalition_for_each_task(coalition_t coal, void *ctx,
1479 void (*callback)(coalition_t, void *, task_t))
1480 {
1481 assert(coal != COALITION_NULL);
1482
1483 coal_dbg("iterating tasks in coalition %p id:%llu type:%s, active_count:%u",
1484 coal, coal->id, coal_type_str(coal->type), coal->active_count);
1485
1486 coalition_lock(coal);
1487
1488 coal_call(coal, iterate_tasks, ctx, callback);
1489
1490 coalition_unlock(coal);
1491 }
1492
1493
1494 void
1495 coalition_remove_active(coalition_t coal)
1496 {
1497 coalition_lock(coal);
1498
1499 assert(!coal->reaped);
1500 assert(coal->active_count > 0);
1501
1502 coal->active_count--;
1503
1504 boolean_t do_notify = FALSE;
1505 uint64_t notify_id = 0;
1506 uint32_t notify_flags = 0;
1507 if (coal->termrequested && coal->active_count == 0) {
1508 /* We only notify once, when active_count reaches zero.
1509 * We just decremented, so if it reached zero, we mustn't have
1510 * notified already.
1511 */
1512 assert(!coal->terminated);
1513 coal->terminated = TRUE;
1514
1515 assert(!coal->notified);
1516
1517 coal->notified = TRUE;
1518 #if DEVELOPMENT || DEBUG
1519 do_notify = coal->should_notify;
1520 #else
1521 do_notify = TRUE;
1522 #endif
1523 notify_id = coal->id;
1524 notify_flags = 0;
1525 }
1526
1527 #if COALITION_DEBUG
1528 uint64_t cid = coal->id;
1529 uint32_t rc = coal->ref_count;
1530 int ac = coal->active_count;
1531 int ct = coal->type;
1532 #endif
1533 coalition_unlock(coal);
1534
1535 coal_dbg("id:%llu type:%s ref_count:%u, active_count:%u,%s",
1536 cid, coal_type_str(ct), rc, ac, do_notify ? " NOTIFY" : " ");
1537
1538 if (do_notify) {
1539 coalition_notify_user(notify_id, notify_flags);
1540 }
1541 }
1542
1543 /* Used for kernel_task, launchd, launchd's early boot tasks... */
1544 kern_return_t
1545 coalitions_adopt_init_task(task_t task)
1546 {
1547 kern_return_t kr;
1548 kr = coalitions_adopt_task(init_coalition, task);
1549 if (kr != KERN_SUCCESS) {
1550 panic("failed to adopt task %p into default coalition: %d", task, kr);
1551 }
1552 return kr;
1553 }
1554
1555 /* Used for forked corpses. */
1556 kern_return_t
1557 coalitions_adopt_corpse_task(task_t task)
1558 {
1559 kern_return_t kr;
1560 kr = coalitions_adopt_task(corpse_coalition, task);
1561 if (kr != KERN_SUCCESS) {
1562 panic("failed to adopt task %p into corpse coalition: %d", task, kr);
1563 }
1564 return kr;
1565 }
1566
1567 /*
1568 * coalition_adopt_task_internal
1569 * Condition: Coalition must be referenced and unlocked. Will fail if coalition
1570 * is already terminated.
1571 */
1572 static kern_return_t
1573 coalition_adopt_task_internal(coalition_t coal, task_t task)
1574 {
1575 kern_return_t kr;
1576
1577 if (task->coalition[coal->type]) {
1578 return KERN_ALREADY_IN_SET;
1579 }
1580
1581 coalition_lock(coal);
1582
1583 if (coal->reaped || coal->terminated) {
1584 coalition_unlock(coal);
1585 return KERN_TERMINATED;
1586 }
1587
1588 kr = coal_call(coal, adopt_task, task);
1589 if (kr != KERN_SUCCESS) {
1590 goto out_unlock;
1591 }
1592
1593 coal->active_count++;
1594
1595 coal->ref_count++;
1596
1597 task->coalition[coal->type] = coal;
1598
1599 out_unlock:
1600 #if COALITION_DEBUG
1601 (void)coal; /* need expression after label */
1602 uint64_t cid = coal->id;
1603 uint32_t rc = coal->ref_count;
1604 uint32_t ct = coal->type;
1605 #endif
1606 if (get_task_uniqueid(task) != UINT64_MAX) {
1607 /* On 32-bit targets, uniqueid will get truncated to 32 bits */
1608 KDBG_RELEASE(MACHDBG_CODE(DBG_MACH_COALITION, MACH_COALITION_ADOPT),
1609 coal->id, get_task_uniqueid(task));
1610 }
1611
1612 coalition_unlock(coal);
1613
1614 coal_dbg("task:%d, id:%llu type:%s ref_count:%u, kr=%d",
1615 task_pid(task), cid, coal_type_str(ct), rc, kr);
1616 return kr;
1617 }
1618
1619 static kern_return_t
1620 coalition_remove_task_internal(task_t task, int type)
1621 {
1622 kern_return_t kr;
1623
1624 coalition_t coal = task->coalition[type];
1625
1626 if (!coal) {
1627 return KERN_SUCCESS;
1628 }
1629
1630 assert(coal->type == (uint32_t)type);
1631
1632 coalition_lock(coal);
1633
1634 kr = coal_call(coal, remove_task, task);
1635
1636 #if COALITION_DEBUG
1637 uint64_t cid = coal->id;
1638 uint32_t rc = coal->ref_count;
1639 int ac = coal->active_count;
1640 int ct = coal->type;
1641 #endif
1642 KDBG_RELEASE(MACHDBG_CODE(DBG_MACH_COALITION, MACH_COALITION_REMOVE),
1643 coal->id, get_task_uniqueid(task));
1644 coalition_unlock(coal);
1645
1646 coal_dbg("id:%llu type:%s ref_count:%u, active_count:%u, kr=%d",
1647 cid, coal_type_str(ct), rc, ac, kr);
1648
1649 coalition_remove_active(coal);
1650
1651 return kr;
1652 }
1653
1654 /*
1655 * coalitions_adopt_task
1656 * Condition: All coalitions must be referenced and unlocked.
1657 * Will fail if any coalition is already terminated.
1658 */
1659 kern_return_t
1660 coalitions_adopt_task(coalition_t *coals, task_t task)
1661 {
1662 int i;
1663 kern_return_t kr;
1664
1665 if (!coals || coals[COALITION_TYPE_RESOURCE] == COALITION_NULL) {
1666 return KERN_INVALID_ARGUMENT;
1667 }
1668
1669 /* verify that the incoming coalitions are what they say they are */
1670 for (i = 0; i < COALITION_NUM_TYPES; i++) {
1671 if (coals[i] && coals[i]->type != (uint32_t)i) {
1672 return KERN_INVALID_ARGUMENT;
1673 }
1674 }
1675
1676 for (i = 0; i < COALITION_NUM_TYPES; i++) {
1677 kr = KERN_SUCCESS;
1678 if (coals[i]) {
1679 kr = coalition_adopt_task_internal(coals[i], task);
1680 }
1681 if (kr != KERN_SUCCESS) {
1682 /* dis-associate any coalitions that just adopted this task */
1683 while (--i >= 0) {
1684 if (task->coalition[i]) {
1685 coalition_remove_task_internal(task, i);
1686 }
1687 }
1688 break;
1689 }
1690 }
1691 return kr;
1692 }
1693
1694 /*
1695 * coalitions_remove_task
1696 * Condition: task must be referenced and UNLOCKED; all task's coalitions must be UNLOCKED
1697 */
1698 kern_return_t
1699 coalitions_remove_task(task_t task)
1700 {
1701 kern_return_t kr;
1702 int i;
1703
1704 for (i = 0; i < COALITION_NUM_TYPES; i++) {
1705 kr = coalition_remove_task_internal(task, i);
1706 assert(kr == KERN_SUCCESS);
1707 }
1708
1709 return kr;
1710 }
1711
1712 /*
1713 * task_release_coalitions
1714 * helper function to release references to all coalitions in which
1715 * 'task' is a member.
1716 */
1717 void
1718 task_release_coalitions(task_t task)
1719 {
1720 int i;
1721 for (i = 0; i < COALITION_NUM_TYPES; i++) {
1722 if (task->coalition[i]) {
1723 coalition_release(task->coalition[i]);
1724 } else if (i == COALITION_TYPE_RESOURCE) {
1725 panic("deallocating task %p was not a member of a resource coalition", task);
1726 }
1727 }
1728 }
1729
1730 /*
1731 * coalitions_set_roles
1732 * for each type of coalition, if the task is a member of a coalition of
1733 * that type (given in the coalitions parameter) then set the role of
1734 * the task within that that coalition.
1735 */
1736 kern_return_t
1737 coalitions_set_roles(coalition_t coalitions[COALITION_NUM_TYPES],
1738 task_t task, int roles[COALITION_NUM_TYPES])
1739 {
1740 kern_return_t kr = KERN_SUCCESS;
1741 int i;
1742
1743 for (i = 0; i < COALITION_NUM_TYPES; i++) {
1744 if (!coalitions[i]) {
1745 continue;
1746 }
1747 coalition_lock(coalitions[i]);
1748 kr = coal_call(coalitions[i], set_taskrole, task, roles[i]);
1749 coalition_unlock(coalitions[i]);
1750 assert(kr == KERN_SUCCESS);
1751 }
1752
1753 return kr;
1754 }
1755
1756 /*
1757 * coalition_terminate_internal
1758 * Condition: Coalition must be referenced and UNLOCKED.
1759 */
1760 kern_return_t
1761 coalition_request_terminate_internal(coalition_t coal)
1762 {
1763 assert(coal->type >= 0 && coal->type <= COALITION_TYPE_MAX);
1764
1765 if (coal == init_coalition[coal->type]) {
1766 return KERN_DEFAULT_SET;
1767 }
1768
1769 coalition_lock(coal);
1770
1771 if (coal->reaped) {
1772 coalition_unlock(coal);
1773 return KERN_INVALID_NAME;
1774 }
1775
1776 if (coal->terminated || coal->termrequested) {
1777 coalition_unlock(coal);
1778 return KERN_TERMINATED;
1779 }
1780
1781 coal->termrequested = TRUE;
1782
1783 boolean_t do_notify = FALSE;
1784 uint64_t note_id = 0;
1785 uint32_t note_flags = 0;
1786
1787 if (coal->active_count == 0) {
1788 /*
1789 * We only notify once, when active_count reaches zero.
1790 * We just set termrequested to zero. If the active count
1791 * was already at zero (tasks died before we could request
1792 * a termination notification), we should notify.
1793 */
1794 assert(!coal->terminated);
1795 coal->terminated = TRUE;
1796
1797 assert(!coal->notified);
1798
1799 coal->notified = TRUE;
1800 #if DEVELOPMENT || DEBUG
1801 do_notify = coal->should_notify;
1802 #else
1803 do_notify = TRUE;
1804 #endif
1805 note_id = coal->id;
1806 note_flags = 0;
1807 }
1808
1809 coalition_unlock(coal);
1810
1811 if (do_notify) {
1812 coalition_notify_user(note_id, note_flags);
1813 }
1814
1815 return KERN_SUCCESS;
1816 }
1817
1818 /*
1819 * coalition_reap_internal
1820 * Condition: Coalition must be referenced and UNLOCKED.
1821 */
1822 kern_return_t
1823 coalition_reap_internal(coalition_t coal)
1824 {
1825 assert(coal->type <= COALITION_TYPE_MAX);
1826
1827 if (coal == init_coalition[coal->type]) {
1828 return KERN_DEFAULT_SET;
1829 }
1830
1831 coalition_lock(coal);
1832 if (coal->reaped) {
1833 coalition_unlock(coal);
1834 return KERN_TERMINATED;
1835 }
1836 if (!coal->terminated) {
1837 coalition_unlock(coal);
1838 return KERN_FAILURE;
1839 }
1840 assert(coal->termrequested);
1841 if (coal->active_count > 0) {
1842 coalition_unlock(coal);
1843 return KERN_FAILURE;
1844 }
1845
1846 coal->reaped = TRUE;
1847
1848 /* Caller, launchd, and coalitions list should each have a reference */
1849 assert(coal->ref_count > 2);
1850
1851 coalition_unlock(coal);
1852
1853 lck_mtx_lock(&coalitions_list_lock);
1854 coalition_count--;
1855 remqueue(&coal->coalitions);
1856 lck_mtx_unlock(&coalitions_list_lock);
1857
1858 /* Release the list's reference and launchd's reference. */
1859 coalition_release(coal);
1860 coalition_release(coal);
1861
1862 return KERN_SUCCESS;
1863 }
1864
1865 #if DEVELOPMENT || DEBUG
1866 int
1867 coalition_should_notify(coalition_t coal)
1868 {
1869 int should;
1870 if (!coal) {
1871 return -1;
1872 }
1873 coalition_lock(coal);
1874 should = coal->should_notify;
1875 coalition_unlock(coal);
1876
1877 return should;
1878 }
1879
1880 void
1881 coalition_set_notify(coalition_t coal, int notify)
1882 {
1883 if (!coal) {
1884 return;
1885 }
1886 coalition_lock(coal);
1887 coal->should_notify = !!notify;
1888 coalition_unlock(coal);
1889 }
1890 #endif
1891
1892 void
1893 coalitions_init(void)
1894 {
1895 kern_return_t kr;
1896 int i;
1897 const struct coalition_type *ctype;
1898
1899 coalition_zone = zinit(
1900 sizeof(struct coalition),
1901 CONFIG_COALITION_MAX * sizeof(struct coalition),
1902 COALITION_CHUNK * sizeof(struct coalition),
1903 "coalitions");
1904 zone_change(coalition_zone, Z_NOENCRYPT, TRUE);
1905 queue_head_init(coalitions_q);
1906
1907 if (!PE_parse_boot_argn("unrestrict_coalition_syscalls", &unrestrict_coalition_syscalls,
1908 sizeof(unrestrict_coalition_syscalls))) {
1909 unrestrict_coalition_syscalls = 0;
1910 }
1911
1912 if (!PE_parse_boot_argn("tg_adaptive", &merge_adaptive_coalitions,
1913 sizeof(merge_adaptive_coalitions))) {
1914 merge_adaptive_coalitions = 0;
1915 }
1916
1917 lck_grp_attr_setdefault(&coalitions_lck_grp_attr);
1918 lck_grp_init(&coalitions_lck_grp, "coalition", &coalitions_lck_grp_attr);
1919 lck_attr_setdefault(&coalitions_lck_attr);
1920 lck_mtx_init(&coalitions_list_lock, &coalitions_lck_grp, &coalitions_lck_attr);
1921
1922 init_task_ledgers();
1923
1924 init_coalition_ledgers();
1925
1926 for (i = 0, ctype = &s_coalition_types[0]; i < COALITION_NUM_TYPES; ctype++, i++) {
1927 /* verify the entry in the global coalition types array */
1928 if (ctype->type != i ||
1929 !ctype->init ||
1930 !ctype->dealloc ||
1931 !ctype->adopt_task ||
1932 !ctype->remove_task) {
1933 panic("%s: Malformed coalition type %s(%d) in slot for type:%s(%d)",
1934 __func__, coal_type_str(ctype->type), ctype->type, coal_type_str(i), i);
1935 }
1936 if (!ctype->has_default) {
1937 continue;
1938 }
1939 kr = coalition_create_internal(ctype->type, COALITION_ROLE_SYSTEM, TRUE, &init_coalition[ctype->type]);
1940 if (kr != KERN_SUCCESS) {
1941 panic("%s: could not create init %s coalition: kr:%d",
1942 __func__, coal_type_str(i), kr);
1943 }
1944 kr = coalition_create_internal(ctype->type, COALITION_ROLE_SYSTEM, FALSE, &corpse_coalition[ctype->type]);
1945 if (kr != KERN_SUCCESS) {
1946 panic("%s: could not create corpse %s coalition: kr:%d",
1947 __func__, coal_type_str(i), kr);
1948 }
1949 }
1950
1951 /* "Leak" our reference to the global object */
1952 }
1953
1954 /*
1955 * BSD Kernel interface functions
1956 *
1957 */
1958 static void
1959 coalition_fill_procinfo(struct coalition *coal,
1960 struct procinfo_coalinfo *coalinfo)
1961 {
1962 coalinfo->coalition_id = coal->id;
1963 coalinfo->coalition_type = coal->type;
1964 coalinfo->coalition_tasks = coalition_get_task_count(coal);
1965 }
1966
1967
1968 int
1969 coalitions_get_list(int type, struct procinfo_coalinfo *coal_list, int list_sz)
1970 {
1971 int ncoals = 0;
1972 struct coalition *coal;
1973
1974 lck_mtx_lock(&coalitions_list_lock);
1975 qe_foreach_element(coal, &coalitions_q, coalitions) {
1976 if (!coal->reaped && (type < 0 || type == (int)coal->type)) {
1977 if (coal_list && ncoals < list_sz) {
1978 coalition_fill_procinfo(coal, &coal_list[ncoals]);
1979 }
1980 ++ncoals;
1981 }
1982 }
1983 lck_mtx_unlock(&coalitions_list_lock);
1984
1985 return ncoals;
1986 }
1987
1988 /*
1989 * Return the coaltion of the given type to which the task belongs.
1990 */
1991 coalition_t
1992 task_get_coalition(task_t task, int coal_type)
1993 {
1994 coalition_t c;
1995
1996 if (task == NULL || coal_type > COALITION_TYPE_MAX) {
1997 return COALITION_NULL;
1998 }
1999
2000 c = task->coalition[coal_type];
2001 assert(c == COALITION_NULL || (int)c->type == coal_type);
2002 return c;
2003 }
2004
2005 /*
2006 * Report if the given task is the leader of the given jetsam coalition.
2007 */
2008 boolean_t
2009 coalition_is_leader(task_t task, coalition_t coal)
2010 {
2011 boolean_t ret = FALSE;
2012
2013 if (coal != COALITION_NULL) {
2014 coalition_lock(coal);
2015
2016 ret = (coal->type == COALITION_TYPE_JETSAM && coal->j.leader == task);
2017
2018 coalition_unlock(coal);
2019 }
2020
2021 return ret;
2022 }
2023
2024 kern_return_t
2025 coalition_iterate_stackshot(coalition_iterate_fn_t callout, void *arg, uint32_t coalition_type)
2026 {
2027 coalition_t coal;
2028 int i = 0;
2029
2030 qe_foreach_element(coal, &coalitions_q, coalitions) {
2031 if (coal == NULL || !ml_validate_nofault((vm_offset_t)coal, sizeof(struct coalition))) {
2032 return KERN_FAILURE;
2033 }
2034
2035 if (coalition_type == coal->type) {
2036 callout(arg, i++, coal);
2037 }
2038 }
2039
2040 return KERN_SUCCESS;
2041 }
2042
2043 task_t
2044 kdp_coalition_get_leader(coalition_t coal)
2045 {
2046 if (!coal) {
2047 return TASK_NULL;
2048 }
2049
2050 if (coal->type == COALITION_TYPE_JETSAM) {
2051 return coal->j.leader;
2052 }
2053 return TASK_NULL;
2054 }
2055
2056 task_t
2057 coalition_get_leader(coalition_t coal)
2058 {
2059 task_t leader = TASK_NULL;
2060
2061 if (!coal) {
2062 return TASK_NULL;
2063 }
2064
2065 coalition_lock(coal);
2066 if (coal->type != COALITION_TYPE_JETSAM) {
2067 goto out_unlock;
2068 }
2069
2070 leader = coal->j.leader;
2071 if (leader != TASK_NULL) {
2072 task_reference(leader);
2073 }
2074
2075 out_unlock:
2076 coalition_unlock(coal);
2077 return leader;
2078 }
2079
2080
2081 int
2082 coalition_get_task_count(coalition_t coal)
2083 {
2084 int ntasks = 0;
2085 struct queue_entry *qe;
2086 if (!coal) {
2087 return 0;
2088 }
2089
2090 coalition_lock(coal);
2091 switch (coal->type) {
2092 case COALITION_TYPE_RESOURCE:
2093 qe_foreach(qe, &coal->r.tasks)
2094 ntasks++;
2095 break;
2096 case COALITION_TYPE_JETSAM:
2097 if (coal->j.leader) {
2098 ntasks++;
2099 }
2100 qe_foreach(qe, &coal->j.other)
2101 ntasks++;
2102 qe_foreach(qe, &coal->j.extensions)
2103 ntasks++;
2104 qe_foreach(qe, &coal->j.services)
2105 ntasks++;
2106 break;
2107 default:
2108 break;
2109 }
2110 coalition_unlock(coal);
2111
2112 return ntasks;
2113 }
2114
2115
2116 static uint64_t
2117 i_get_list_footprint(queue_t list, int type, int *ntasks)
2118 {
2119 task_t task;
2120 uint64_t bytes = 0;
2121
2122 qe_foreach_element(task, list, task_coalition[type]) {
2123 bytes += get_task_phys_footprint(task);
2124 coal_dbg(" [%d] task_pid:%d, type:%d, footprint:%lld",
2125 *ntasks, task_pid(task), type, bytes);
2126 *ntasks += 1;
2127 }
2128
2129 return bytes;
2130 }
2131
2132 uint64_t
2133 coalition_get_page_count(coalition_t coal, int *ntasks)
2134 {
2135 uint64_t bytes = 0;
2136 int num_tasks = 0;
2137
2138 if (ntasks) {
2139 *ntasks = 0;
2140 }
2141 if (!coal) {
2142 return bytes;
2143 }
2144
2145 coalition_lock(coal);
2146
2147 switch (coal->type) {
2148 case COALITION_TYPE_RESOURCE:
2149 bytes += i_get_list_footprint(&coal->r.tasks, COALITION_TYPE_RESOURCE, &num_tasks);
2150 break;
2151 case COALITION_TYPE_JETSAM:
2152 if (coal->j.leader) {
2153 bytes += get_task_phys_footprint(coal->j.leader);
2154 num_tasks = 1;
2155 }
2156 bytes += i_get_list_footprint(&coal->j.extensions, COALITION_TYPE_JETSAM, &num_tasks);
2157 bytes += i_get_list_footprint(&coal->j.services, COALITION_TYPE_JETSAM, &num_tasks);
2158 bytes += i_get_list_footprint(&coal->j.other, COALITION_TYPE_JETSAM, &num_tasks);
2159 break;
2160 default:
2161 break;
2162 }
2163
2164 coalition_unlock(coal);
2165
2166 if (ntasks) {
2167 *ntasks = num_tasks;
2168 }
2169
2170 return bytes / PAGE_SIZE_64;
2171 }
2172
2173 struct coal_sort_s {
2174 int pid;
2175 int usr_order;
2176 uint64_t bytes;
2177 };
2178
2179 /*
2180 * return < 0 for a < b
2181 * 0 for a == b
2182 * > 0 for a > b
2183 */
2184 typedef int (*cmpfunc_t)(const void *a, const void *b);
2185
2186 extern void
2187 qsort(void *a, size_t n, size_t es, cmpfunc_t cmp);
2188
2189 static int
2190 dflt_cmp(const void *a, const void *b)
2191 {
2192 const struct coal_sort_s *csA = (const struct coal_sort_s *)a;
2193 const struct coal_sort_s *csB = (const struct coal_sort_s *)b;
2194
2195 /*
2196 * if both A and B are equal, use a memory descending sort
2197 */
2198 if (csA->usr_order == csB->usr_order) {
2199 return (int)((int64_t)csB->bytes - (int64_t)csA->bytes);
2200 }
2201
2202 /* otherwise, return the relationship between user specified orders */
2203 return csA->usr_order - csB->usr_order;
2204 }
2205
2206 static int
2207 mem_asc_cmp(const void *a, const void *b)
2208 {
2209 const struct coal_sort_s *csA = (const struct coal_sort_s *)a;
2210 const struct coal_sort_s *csB = (const struct coal_sort_s *)b;
2211
2212 return (int)((int64_t)csA->bytes - (int64_t)csB->bytes);
2213 }
2214
2215 static int
2216 mem_dec_cmp(const void *a, const void *b)
2217 {
2218 const struct coal_sort_s *csA = (const struct coal_sort_s *)a;
2219 const struct coal_sort_s *csB = (const struct coal_sort_s *)b;
2220
2221 return (int)((int64_t)csB->bytes - (int64_t)csA->bytes);
2222 }
2223
2224 static int
2225 usr_asc_cmp(const void *a, const void *b)
2226 {
2227 const struct coal_sort_s *csA = (const struct coal_sort_s *)a;
2228 const struct coal_sort_s *csB = (const struct coal_sort_s *)b;
2229
2230 return csA->usr_order - csB->usr_order;
2231 }
2232
2233 static int
2234 usr_dec_cmp(const void *a, const void *b)
2235 {
2236 const struct coal_sort_s *csA = (const struct coal_sort_s *)a;
2237 const struct coal_sort_s *csB = (const struct coal_sort_s *)b;
2238
2239 return csB->usr_order - csA->usr_order;
2240 }
2241
2242 /* avoid dynamic allocation in this path */
2243 #define MAX_SORTED_PIDS 80
2244
2245 static int
2246 coalition_get_sort_list(coalition_t coal, int sort_order, queue_t list,
2247 struct coal_sort_s *sort_array, int array_sz)
2248 {
2249 int ntasks = 0;
2250 task_t task;
2251
2252 assert(sort_array != NULL);
2253
2254 if (array_sz <= 0) {
2255 return 0;
2256 }
2257
2258 if (!list) {
2259 /*
2260 * this function will only be called with a NULL
2261 * list for JETSAM-type coalitions, and is intended
2262 * to investigate the leader process
2263 */
2264 if (coal->type != COALITION_TYPE_JETSAM ||
2265 coal->j.leader == TASK_NULL) {
2266 return 0;
2267 }
2268 sort_array[0].pid = task_pid(coal->j.leader);
2269 switch (sort_order) {
2270 case COALITION_SORT_DEFAULT:
2271 sort_array[0].usr_order = 0;
2272 /* fall-through */
2273 case COALITION_SORT_MEM_ASC:
2274 case COALITION_SORT_MEM_DEC:
2275 sort_array[0].bytes = get_task_phys_footprint(coal->j.leader);
2276 break;
2277 case COALITION_SORT_USER_ASC:
2278 case COALITION_SORT_USER_DEC:
2279 sort_array[0].usr_order = 0;
2280 break;
2281 default:
2282 break;
2283 }
2284 return 1;
2285 }
2286
2287 qe_foreach_element(task, list, task_coalition[coal->type]) {
2288 if (ntasks >= array_sz) {
2289 printf("WARNING: more than %d pids in coalition %llu\n",
2290 MAX_SORTED_PIDS, coal->id);
2291 break;
2292 }
2293
2294 sort_array[ntasks].pid = task_pid(task);
2295
2296 switch (sort_order) {
2297 case COALITION_SORT_DEFAULT:
2298 sort_array[ntasks].usr_order = 0;
2299 /* fall-through */
2300 case COALITION_SORT_MEM_ASC:
2301 case COALITION_SORT_MEM_DEC:
2302 sort_array[ntasks].bytes = get_task_phys_footprint(task);
2303 break;
2304 case COALITION_SORT_USER_ASC:
2305 case COALITION_SORT_USER_DEC:
2306 sort_array[ntasks].usr_order = 0;
2307 break;
2308 default:
2309 break;
2310 }
2311
2312 ntasks++;
2313 }
2314
2315 return ntasks;
2316 }
2317
2318 int
2319 coalition_get_pid_list(coalition_t coal, uint32_t rolemask, int sort_order,
2320 int *pid_list, int list_sz)
2321 {
2322 struct i_jetsam_coalition *cj;
2323 int ntasks = 0;
2324 cmpfunc_t cmp_func = NULL;
2325 struct coal_sort_s sort_array[MAX_SORTED_PIDS] = { {0, 0, 0} }; /* keep to < 2k */
2326
2327 if (!coal ||
2328 !(rolemask & COALITION_ROLEMASK_ALLROLES) ||
2329 !pid_list || list_sz < 1) {
2330 coal_dbg("Invalid parameters: coal:%p, type:%d, rolemask:0x%x, "
2331 "pid_list:%p, list_sz:%d", coal, coal ? coal->type : -1,
2332 rolemask, pid_list, list_sz);
2333 return -EINVAL;
2334 }
2335
2336 switch (sort_order) {
2337 case COALITION_SORT_NOSORT:
2338 cmp_func = NULL;
2339 break;
2340 case COALITION_SORT_DEFAULT:
2341 cmp_func = dflt_cmp;
2342 break;
2343 case COALITION_SORT_MEM_ASC:
2344 cmp_func = mem_asc_cmp;
2345 break;
2346 case COALITION_SORT_MEM_DEC:
2347 cmp_func = mem_dec_cmp;
2348 break;
2349 case COALITION_SORT_USER_ASC:
2350 cmp_func = usr_asc_cmp;
2351 break;
2352 case COALITION_SORT_USER_DEC:
2353 cmp_func = usr_dec_cmp;
2354 break;
2355 default:
2356 return -ENOTSUP;
2357 }
2358
2359 coalition_lock(coal);
2360
2361 if (coal->type == COALITION_TYPE_RESOURCE) {
2362 ntasks += coalition_get_sort_list(coal, sort_order, &coal->r.tasks,
2363 sort_array, MAX_SORTED_PIDS);
2364 goto unlock_coal;
2365 }
2366
2367 cj = &coal->j;
2368
2369 if (rolemask & COALITION_ROLEMASK_UNDEF) {
2370 ntasks += coalition_get_sort_list(coal, sort_order, &cj->other,
2371 sort_array + ntasks,
2372 MAX_SORTED_PIDS - ntasks);
2373 }
2374
2375 if (rolemask & COALITION_ROLEMASK_XPC) {
2376 ntasks += coalition_get_sort_list(coal, sort_order, &cj->services,
2377 sort_array + ntasks,
2378 MAX_SORTED_PIDS - ntasks);
2379 }
2380
2381 if (rolemask & COALITION_ROLEMASK_EXT) {
2382 ntasks += coalition_get_sort_list(coal, sort_order, &cj->extensions,
2383 sort_array + ntasks,
2384 MAX_SORTED_PIDS - ntasks);
2385 }
2386
2387 if (rolemask & COALITION_ROLEMASK_LEADER) {
2388 ntasks += coalition_get_sort_list(coal, sort_order, NULL,
2389 sort_array + ntasks,
2390 MAX_SORTED_PIDS - ntasks);
2391 }
2392
2393 unlock_coal:
2394 coalition_unlock(coal);
2395
2396 /* sort based on the chosen criterion (no sense sorting 1 item) */
2397 if (cmp_func && ntasks > 1) {
2398 qsort(sort_array, ntasks, sizeof(struct coal_sort_s), cmp_func);
2399 }
2400
2401 for (int i = 0; i < ntasks; i++) {
2402 if (i >= list_sz) {
2403 break;
2404 }
2405 coal_dbg(" [%d] PID:%d, footprint:%lld, usr_order:%d",
2406 i, sort_array[i].pid, sort_array[i].bytes,
2407 sort_array[i].usr_order);
2408 pid_list[i] = sort_array[i].pid;
2409 }
2410
2411 return ntasks;
2412 }