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