1 /*
2  * Copyright (c) 2015 Apple Inc. All rights reserved.
3  *
4  * @APPLE_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. Please obtain a copy of the License at
10  * http://www.opensource.apple.com/apsl/ and read it before using this
11  * file.
12  *
13  * The Original Code and all software distributed under the License are
14  * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16  * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18  * Please see the License for the specific language governing rights and
19  * limitations under the License.
20  *
21  * @APPLE_LICENSE_HEADER_END@
22  */
23 #include <sys/cdefs.h>
24 #include <sys/types.h>
25 #include <sys/work_interval.h>
26 
27 #include <mach/mach.h>
28 #include <mach/mach_time.h>
29 #include <mach/port.h>
30 
31 #include <sys/errno.h>
32 #include <stdlib.h>
33 #include <strings.h>
34 
35 struct work_interval {
36 	uint64_t thread_id;
37 	uint64_t work_interval_id;
38 	uint32_t create_flags;
39 	mach_port_t wi_port;
40 };
41 
42 extern uint64_t __thread_selfid(void);
43 
44 /* Create a new work interval handle (currently for the current thread only). */
45 int
work_interval_create(work_interval_t * interval_handle,uint32_t create_flags)46 work_interval_create(work_interval_t *interval_handle, uint32_t create_flags)
47 {
48 	int ret;
49 	work_interval_t handle;
50 
51 	if (interval_handle == NULL) {
52 		errno = EINVAL;
53 		return -1;
54 	}
55 
56 	struct work_interval_create_params create_params = {
57 		.wicp_create_flags = create_flags,
58 	};
59 
60 	ret = __work_interval_ctl(WORK_INTERVAL_OPERATION_CREATE2, 0,
61 	    &create_params, sizeof(create_params));
62 	if (ret == -1) {
63 		return ret;
64 	}
65 
66 	handle = malloc(sizeof(*handle));
67 	if (handle == NULL) {
68 		errno = ENOMEM;
69 		return -1;
70 	}
71 
72 	handle->thread_id = __thread_selfid();
73 	handle->work_interval_id = create_params.wicp_id;
74 	handle->create_flags = create_params.wicp_create_flags;
75 	handle->wi_port = create_params.wicp_port;
76 
77 	*interval_handle = handle;
78 	return 0;
79 }
80 
81 int
work_interval_get_flags_from_port(mach_port_t port,uint32_t * flags)82 work_interval_get_flags_from_port(mach_port_t port, uint32_t *flags)
83 {
84 	if (!MACH_PORT_VALID(port) || flags == NULL) {
85 		errno = EINVAL;
86 		return -1;
87 	}
88 
89 	struct work_interval_create_params create_params = { 0 };
90 
91 	int ret = __work_interval_ctl(WORK_INTERVAL_OPERATION_GET_FLAGS, port,
92 	    &create_params, sizeof(create_params));
93 	if (ret == -1) {
94 		return ret;
95 	}
96 
97 	*flags = create_params.wicp_create_flags;
98 	return 0;
99 }
100 
101 int
work_interval_notify(work_interval_t interval_handle,uint64_t start,uint64_t finish,uint64_t deadline,uint64_t next_start,uint32_t notify_flags)102 work_interval_notify(work_interval_t interval_handle, uint64_t start,
103     uint64_t finish, uint64_t deadline, uint64_t next_start,
104     uint32_t notify_flags)
105 {
106 	int ret;
107 	uint64_t work_interval_id;
108 	struct work_interval_notification notification = {
109 		.start = start,
110 		.finish = finish,
111 		.deadline = deadline,
112 		.next_start = next_start,
113 		.notify_flags = notify_flags
114 	};
115 
116 	if (interval_handle == NULL) {
117 		errno = EINVAL;
118 		return -1;
119 	}
120 
121 	if (interval_handle->create_flags & WORK_INTERVAL_FLAG_IGNORED) {
122 		return 0;
123 	}
124 
125 	notification.create_flags = interval_handle->create_flags;
126 	work_interval_id = interval_handle->work_interval_id;
127 
128 	ret = __work_interval_ctl(WORK_INTERVAL_OPERATION_NOTIFY, work_interval_id,
129 	    &notification, sizeof(notification));
130 	return ret;
131 }
132 
133 int
work_interval_notify_simple(work_interval_t interval_handle,uint64_t start,uint64_t deadline,uint64_t next_start)134 work_interval_notify_simple(work_interval_t interval_handle, uint64_t start,
135     uint64_t deadline, uint64_t next_start)
136 {
137 	return work_interval_notify(interval_handle, start, mach_absolute_time(),
138 	           deadline, next_start, 0);
139 }
140 
141 
142 int
work_interval_destroy(work_interval_t interval_handle)143 work_interval_destroy(work_interval_t interval_handle)
144 {
145 	if (interval_handle == NULL) {
146 		errno = EINVAL;
147 		return -1;
148 	}
149 
150 	if (interval_handle->create_flags & WORK_INTERVAL_FLAG_JOINABLE) {
151 		mach_port_t wi_port = interval_handle->wi_port;
152 
153 		/*
154 		 * A joinable work interval's lifetime is tied to the port lifetime.
155 		 * When the last port reference is destroyed, the work interval
156 		 * is destroyed via no-senders notification.
157 		 *
158 		 * Note however that after destroy it can no longer be notified
159 		 * because the userspace token is gone.
160 		 *
161 		 * Additionally, this function does not cause the thread to un-join
162 		 * the interval.
163 		 */
164 		kern_return_t kr = mach_port_deallocate(mach_task_self(), wi_port);
165 
166 		if (kr != KERN_SUCCESS) {
167 			/*
168 			 * If the deallocate fails, then someone got their port
169 			 * lifecycle wrong and over-released a port right.
170 			 *
171 			 * Return an error so the client can assert on this,
172 			 * and still find the port name in the interval handle.
173 			 */
174 			errno = EINVAL;
175 			return -1;
176 		}
177 
178 		interval_handle->wi_port = MACH_PORT_NULL;
179 		interval_handle->work_interval_id = 0;
180 
181 		free(interval_handle);
182 		return 0;
183 	} else {
184 		uint64_t work_interval_id = interval_handle->work_interval_id;
185 
186 		int ret = __work_interval_ctl(WORK_INTERVAL_OPERATION_DESTROY,
187 		    work_interval_id, NULL, 0);
188 
189 		interval_handle->work_interval_id = 0;
190 
191 		int saved_errno = errno;
192 		free(interval_handle);
193 		errno = saved_errno;
194 		return ret;
195 	}
196 }
197 
198 int
work_interval_join(work_interval_t interval_handle)199 work_interval_join(work_interval_t interval_handle)
200 {
201 	if (interval_handle == NULL) {
202 		errno = EINVAL;
203 		return -1;
204 	}
205 
206 	if ((interval_handle->create_flags & WORK_INTERVAL_FLAG_JOINABLE) == 0) {
207 		errno = EINVAL;
208 		return -1;
209 	}
210 
211 	mach_port_t wi_port = interval_handle->wi_port;
212 
213 	if (!MACH_PORT_VALID(wi_port)) {
214 		errno = EINVAL;
215 		return -1;
216 	}
217 
218 	return work_interval_join_port(wi_port);
219 }
220 
221 int
work_interval_join_port(mach_port_t port)222 work_interval_join_port(mach_port_t port)
223 {
224 	if (port == MACH_PORT_NULL) {
225 		errno = EINVAL;
226 		return -1;
227 	}
228 
229 	return __work_interval_ctl(WORK_INTERVAL_OPERATION_JOIN,
230 	           (uint64_t)port, NULL, 0);
231 }
232 
233 int
work_interval_leave(void)234 work_interval_leave(void)
235 {
236 	return __work_interval_ctl(WORK_INTERVAL_OPERATION_JOIN,
237 	           (uint64_t)MACH_PORT_NULL, NULL, 0);
238 }
239 
240 int
work_interval_copy_port(work_interval_t interval_handle,mach_port_t * port)241 work_interval_copy_port(work_interval_t interval_handle, mach_port_t *port)
242 {
243 	if (port == NULL) {
244 		errno = EINVAL;
245 		return -1;
246 	}
247 
248 	if (interval_handle == NULL) {
249 		*port = MACH_PORT_NULL;
250 		errno = EINVAL;
251 		return -1;
252 	}
253 
254 	if ((interval_handle->create_flags & WORK_INTERVAL_FLAG_JOINABLE) == 0) {
255 		*port = MACH_PORT_NULL;
256 		errno = EINVAL;
257 		return -1;
258 	}
259 
260 	mach_port_t wi_port = interval_handle->wi_port;
261 
262 	kern_return_t kr = mach_port_mod_refs(mach_task_self(), wi_port,
263 	    MACH_PORT_RIGHT_SEND, 1);
264 
265 	if (kr != KERN_SUCCESS) {
266 		*port = MACH_PORT_NULL;
267 		errno = EINVAL;
268 		return -1;
269 	}
270 
271 	*port = wi_port;
272 
273 	return 0;
274 }
275