1/*
2 * Copyright (c) 2017 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
30#include <sys/work_interval.h>
31
32#include <kern/work_interval.h>
33
34#include <kern/thread.h>
35#include <kern/sched_prim.h>
36#include <kern/machine.h>
37#include <kern/thread_group.h>
38#include <kern/ipc_kobject.h>
39#include <kern/task.h>
40#include <kern/coalition.h>
41#include <kern/policy_internal.h>
42
43#include <mach/kern_return.h>
44#include <mach/notify.h>
45
46#include <stdatomic.h>
47
48/*
49 * Work Interval structs
50 *
51 * This struct represents a thread group and/or work interval context
52 * in a mechanism that is represented with a kobject.
53 *
54 * Every thread that has joined a WI has a +1 ref, and the port
55 * has a +1 ref as well.
56 *
57 * TODO: groups need to have a 'is for WI' flag
58 * and they need a flag to create that says 'for WI'
59 * This would allow CLPC to avoid allocating WI support
60 * data unless it is needed
61 *
62 * TODO: Enforce not having more than one non-group joinable work
63 * interval per thread group.
64 * CLPC only wants to see one WI-notify callout per group.
65 */
66
67struct work_interval {
68 uint64_t wi_id;
69 _Atomic uint32_t wi_ref_count;
70 uint32_t wi_create_flags;
71
72 /* for debugging purposes only, does not hold a ref on port */
73 ipc_port_t wi_port;
74
75 /*
76 * holds uniqueid and version of creating process,
77 * used to permission-gate notify
78 * TODO: you'd think there would be a better way to do this
79 */
80 uint64_t wi_creator_uniqueid;
81 uint32_t wi_creator_pid;
82 int wi_creator_pidversion;
83
84};
85
86static inline void
87wi_retain(struct work_interval *work_interval)
88{
89 uint32_t old_count;
90 old_count = atomic_fetch_add_explicit(&work_interval->wi_ref_count,
91 1, memory_order_relaxed);
92 assert(old_count > 0);
93}
94
95static inline void
96wi_release(struct work_interval *work_interval)
97{
98 uint32_t old_count;
99 old_count = atomic_fetch_sub_explicit(&work_interval->wi_ref_count,
100 1, memory_order_relaxed);
101 assert(old_count > 0);
102
103 if (old_count == 1) {
104
105
106 kfree(work_interval, sizeof(struct work_interval));
107 }
108}
109
110/*
111 * work_interval_port_alloc
112 *
113 * Description: Obtain a send right for the given work interval struct.
114 *
115 * Parameters: work_interval - A work_interval struct
116 * Consumes a +1 ref count on work_interval, now owned by the port.
117 *
118 * Returns: Port of type IKOT_WORK_INTERVAL with work_interval set as its kobject.
119 * Returned with a +1 send right and no-senders notification armed.
120 * Work interval struct reference is held by the port.
121 */
122static ipc_port_t
123work_interval_port_alloc(struct work_interval *work_interval)
124{
125 ipc_port_t work_interval_port = ipc_port_alloc_kernel();
126
127 if (work_interval_port == IP_NULL)
128 panic("failed to allocate work interval port");
129
130 assert(work_interval->wi_port == IP_NULL);
131
132 ip_lock(work_interval_port);
133 ipc_kobject_set_atomically(work_interval_port, (ipc_kobject_t)work_interval,
134 IKOT_WORK_INTERVAL);
135
136 ipc_port_t notify_port = ipc_port_make_sonce_locked(work_interval_port);
137 ipc_port_t old_notify_port = IP_NULL;
138 ipc_port_nsrequest(work_interval_port, 1, notify_port, &old_notify_port);
139 /* port unlocked */
140
141 assert(old_notify_port == IP_NULL);
142
143 /* This is the only make-send that will happen on this port */
144 ipc_port_t send_port = ipc_port_make_send(work_interval_port);
145 assert(IP_VALID(send_port));
146
147 work_interval->wi_port = work_interval_port;
148
149 return send_port;
150}
151
152/*
153 * work_interval_port_convert
154 *
155 * Called with port locked, returns reference to work interval
156 * if indeed the port is a work interval kobject port
157 */
158static struct work_interval *
159work_interval_port_convert_locked(ipc_port_t port)
160{
161 struct work_interval *work_interval = NULL;
162
163 if (!IP_VALID(port))
164 return NULL;
165
166 if (!ip_active(port))
167 return NULL;
168
169 if (IKOT_WORK_INTERVAL != ip_kotype(port))
170 return NULL;
171
172 work_interval = (struct work_interval *)port->ip_kobject;
173
174 wi_retain(work_interval);
175
176 return work_interval;
177}
178
179/*
180 * port_name_to_work_interval
181 *
182 * Description: Obtain a reference to the work_interval associated with a given port.
183 *
184 * Parameters: name A Mach port name to translate.
185 *
186 * Returns: NULL The given Mach port did not reference a work_interval.
187 * !NULL The work_interval that is associated with the Mach port.
188 */
189static kern_return_t
190port_name_to_work_interval(mach_port_name_t name,
191 struct work_interval **work_interval)
192{
193 if (!MACH_PORT_VALID(name))
194 return KERN_INVALID_NAME;
195
196 ipc_port_t port = IPC_PORT_NULL;
197 kern_return_t kr = KERN_SUCCESS;
198
199 kr = ipc_port_translate_send(current_space(), name, &port);
200 if (kr != KERN_SUCCESS)
201 return kr;
202 /* port is locked */
203
204 assert(IP_VALID(port));
205
206 struct work_interval *converted_work_interval;
207
208 converted_work_interval = work_interval_port_convert_locked(port);
209
210 /* the port is valid, but doesn't denote a work_interval */
211 if (converted_work_interval == NULL)
212 kr = KERN_INVALID_CAPABILITY;
213
214 ip_unlock(port);
215
216 if (kr == KERN_SUCCESS)
217 *work_interval = converted_work_interval;
218
219 return kr;
220
221}
222
223
224/*
225 * work_interval_port_notify
226 *
227 * Description: Handle a no-senders notification for a work interval port.
228 * Destroys the port and releases its reference on the work interval.
229 *
230 * Parameters: msg A Mach no-senders notification message.
231 *
232 * Note: This assumes that there is only one create-right-from-work-interval point,
233 * if the ability to extract another send right after creation is added,
234 * this will have to change to handle make-send counts correctly.
235 */
236void
237work_interval_port_notify(mach_msg_header_t *msg)
238{
239 mach_no_senders_notification_t *notification = (void *)msg;
240 ipc_port_t port = notification->not_header.msgh_remote_port;
241 struct work_interval *work_interval = NULL;
242
243 if (!IP_VALID(port))
244 panic("work_interval_port_notify(): invalid port");
245
246 ip_lock(port);
247
248 if (!ip_active(port))
249 panic("work_interval_port_notify(): inactive port %p", port);
250
251 if (ip_kotype(port) != IKOT_WORK_INTERVAL)
252 panic("work_interval_port_notify(): not the right kobject: %p, %d\n",
253 port, ip_kotype(port));
254
255 if (port->ip_mscount != notification->not_count)
256 panic("work_interval_port_notify(): unexpected make-send count: %p, %d, %d",
257 port, port->ip_mscount, notification->not_count);
258
259 if (port->ip_srights != 0)
260 panic("work_interval_port_notify(): unexpected send right count: %p, %d",
261 port, port->ip_srights);
262
263 work_interval = (struct work_interval *)port->ip_kobject;
264
265 if (work_interval == NULL)
266 panic("work_interval_port_notify(): missing kobject: %p", port);
267
268 ipc_kobject_set_atomically(port, IKO_NULL, IKOT_NONE);
269
270 work_interval->wi_port = MACH_PORT_NULL;
271
272 ip_unlock(port);
273
274 ipc_port_dealloc_kernel(port);
275 wi_release(work_interval);
276}
277
278/*
279 * Change thread's bound work interval to the passed-in work interval
280 * Consumes +1 ref on work_interval
281 *
282 * May also pass NULL to un-set work_interval on the thread
283 *
284 * Will deallocate any old work interval on the thread
285 */
286static void
287thread_set_work_interval(thread_t thread,
288 struct work_interval *work_interval)
289{
290 assert(thread == current_thread());
291
292 struct work_interval *old_th_wi = thread->th_work_interval;
293
294 /* transfer +1 ref to thread */
295 thread->th_work_interval = work_interval;
296
297
298 if (old_th_wi != NULL)
299 wi_release(old_th_wi);
300}
301
302void
303work_interval_thread_terminate(thread_t thread)
304{
305 if (thread->th_work_interval != NULL)
306 thread_set_work_interval(thread, NULL);
307}
308
309
310
311kern_return_t
312kern_work_interval_notify(thread_t thread, struct kern_work_interval_args* kwi_args)
313{
314 assert(thread == current_thread());
315 assert(kwi_args->work_interval_id != 0);
316
317 struct work_interval *work_interval = thread->th_work_interval;
318
319 if (work_interval == NULL ||
320 work_interval->wi_id != kwi_args->work_interval_id) {
321 /* This thread must have adopted the work interval to be able to notify */
322 return (KERN_INVALID_ARGUMENT);
323 }
324
325 task_t notifying_task = current_task();
326
327 if (work_interval->wi_creator_uniqueid != get_task_uniqueid(notifying_task) ||
328 work_interval->wi_creator_pidversion != get_task_version(notifying_task)) {
329 /* Only the creating task can do a notify */
330 return (KERN_INVALID_ARGUMENT);
331 }
332
333 spl_t s = splsched();
334
335
336 uint64_t urgency_param1, urgency_param2;
337 kwi_args->urgency = thread_get_urgency(thread, &urgency_param1, &urgency_param2);
338
339 splx(s);
340
341 /* called without interrupts disabled */
342 machine_work_interval_notify(thread, kwi_args);
343
344 return (KERN_SUCCESS);
345}
346
347/* Start at 1, 0 is not a valid work interval ID */
348static _Atomic uint64_t unique_work_interval_id = 1;
349
350kern_return_t
351kern_work_interval_create(thread_t thread,
352 struct kern_work_interval_create_args *create_params)
353{
354 assert(thread == current_thread());
355
356 if (thread->th_work_interval != NULL) {
357 /* already assigned a work interval */
358 return (KERN_FAILURE);
359 }
360
361 struct work_interval *work_interval = kalloc(sizeof(*work_interval));
362
363 if (work_interval == NULL)
364 panic("failed to allocate work_interval");
365
366 bzero(work_interval, sizeof(*work_interval));
367
368 uint64_t old_value = atomic_fetch_add_explicit(&unique_work_interval_id, 1,
369 memory_order_relaxed);
370
371 uint64_t work_interval_id = old_value + 1;
372
373 uint32_t create_flags = create_params->wica_create_flags;
374
375 task_t creating_task = current_task();
376 if ((create_flags & WORK_INTERVAL_TYPE_MASK) == WORK_INTERVAL_TYPE_CA_CLIENT) {
377 /*
378 * CA_CLIENT work intervals do not create new thread groups
379 * and are non-joinable.
380 * There can only be one CA_CLIENT work interval (created by UIKit)
381 * per each application task
382 */
383 if (create_flags & (WORK_INTERVAL_FLAG_JOINABLE | WORK_INTERVAL_FLAG_GROUP))
384 return (KERN_FAILURE);
385 if (!task_is_app(creating_task))
386 return (KERN_NOT_SUPPORTED);
387 if (task_set_ca_client_wi(creating_task, true) == false)
388 return (KERN_FAILURE);
389 }
390
391 *work_interval = (struct work_interval) {
392 .wi_id = work_interval_id,
393 .wi_ref_count = 1,
394 .wi_create_flags = create_flags,
395 .wi_creator_pid = pid_from_task(creating_task),
396 .wi_creator_uniqueid = get_task_uniqueid(creating_task),
397 .wi_creator_pidversion = get_task_version(creating_task),
398 };
399
400
401 if (create_flags & WORK_INTERVAL_FLAG_JOINABLE) {
402 /* work_interval has a +1 ref, moves to the port */
403 ipc_port_t port = work_interval_port_alloc(work_interval);
404 mach_port_name_t name = MACH_PORT_NULL;
405
406 name = ipc_port_copyout_send(port, current_space());
407
408 if (!MACH_PORT_VALID(name)) {
409 /*
410 * copyout failed (port is already deallocated)
411 * Because of the port-destroyed magic,
412 * the work interval is already deallocated too.
413 */
414 return KERN_RESOURCE_SHORTAGE;
415 }
416
417 create_params->wica_port = name;
418 } else {
419 /* work_interval has a +1 ref, moves to the thread */
420 thread_set_work_interval(thread, work_interval);
421 create_params->wica_port = MACH_PORT_NULL;
422 }
423
424 create_params->wica_id = work_interval_id;
425 return KERN_SUCCESS;
426}
427
428
429kern_return_t
430kern_work_interval_destroy(thread_t thread, uint64_t work_interval_id)
431{
432 if (work_interval_id == 0)
433 return KERN_INVALID_ARGUMENT;
434
435 if (thread->th_work_interval == NULL ||
436 thread->th_work_interval->wi_id != work_interval_id) {
437 /* work ID isn't valid or doesn't match joined work interval ID */
438 return (KERN_INVALID_ARGUMENT);
439 }
440
441 thread_set_work_interval(thread, NULL);
442
443 return KERN_SUCCESS;
444}
445
446kern_return_t
447kern_work_interval_join(thread_t thread,
448 mach_port_name_t port_name)
449{
450 struct work_interval *work_interval = NULL;
451 kern_return_t kr;
452
453 if (port_name == MACH_PORT_NULL) {
454 /* 'Un-join' the current work interval */
455 thread_set_work_interval(thread, NULL);
456 return KERN_SUCCESS;
457 }
458
459 kr = port_name_to_work_interval(port_name, &work_interval);
460 if (kr != KERN_SUCCESS)
461 return kr;
462 /* work_interval has a +1 ref */
463
464 assert(work_interval != NULL);
465
466 thread_set_work_interval(thread, work_interval);
467
468 /* ref was consumed by passing it to the thread */
469
470 return KERN_SUCCESS;
471}
472