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 | |
67 | struct 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 | |
86 | static inline void |
87 | wi_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 | |
95 | static inline void |
96 | wi_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 | */ |
122 | static ipc_port_t |
123 | work_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 | */ |
158 | static struct work_interval * |
159 | work_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 | */ |
189 | static kern_return_t |
190 | port_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 | */ |
236 | void |
237 | work_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 | */ |
286 | static void |
287 | thread_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 | |
302 | void |
303 | work_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 | |
311 | kern_return_t |
312 | kern_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 */ |
348 | static _Atomic uint64_t unique_work_interval_id = 1; |
349 | |
350 | kern_return_t |
351 | kern_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 | |
429 | kern_return_t |
430 | kern_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 | |
446 | kern_return_t |
447 | kern_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 | |