1/*
2 * Copyright (c) 2000-2020 Apple Computer, 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 * Copyright (c) 2000 Apple Computer, Inc. All rights reserved.
30 *
31 * HISTORY
32 *
33 * 29 June 2000 (debo)
34 * Created.
35 */
36
37#include <mach/mach_types.h>
38#include <mach/mach_traps.h>
39#include <mach/mach_port_server.h>
40
41#include <mach/mk_timer.h>
42
43#include <ipc/port.h>
44#include <ipc/ipc_space.h>
45
46#include <kern/lock_group.h>
47#include <kern/thread_call.h>
48#include <ipc/ipc_kmsg.h>
49
50struct mk_timer {
51 decl_simple_lock_data(, lock);
52 thread_call_data_t mkt_thread_call;
53 uint32_t is_dead:1,
54 is_armed:1;
55 int active;
56 ipc_port_t port;
57};
58
59static ZONE_DEFINE_TYPE(mk_timer_zone, "mk_timer",
60 struct mk_timer, ZC_ZFREE_CLEARMEM);
61
62static void mk_timer_port_destroy(ipc_port_t);
63static void mk_timer_expire(void *p0, void *p1);
64
65IPC_KOBJECT_DEFINE(IKOT_TIMER,
66 .iko_op_destroy = mk_timer_port_destroy);
67
68__abortlike
69static void
70ipc_kobject_mktimer_require_panic(
71 ipc_port_t port)
72{
73 panic("port %p / mktimer %p: circularity check failed",
74 port, ipc_kobject_get_raw(port, IKOT_TIMER));
75}
76
77void
78ipc_kobject_mktimer_require_locked(
79 ipc_port_t port)
80{
81 struct mk_timer *timer;
82
83 timer = ipc_kobject_get_locked(port, type: IKOT_TIMER);
84 if (timer->port != port) {
85 ipc_kobject_mktimer_require_panic(port);
86 }
87}
88
89mach_port_name_t
90mk_timer_create_trap(
91 __unused struct mk_timer_create_trap_args *args)
92{
93 struct mk_timer* timer;
94 ipc_space_t myspace = current_space();
95 mach_port_name_t name = MACH_PORT_NULL;
96 ipc_port_init_flags_t init_flags;
97 ipc_port_t port;
98 kern_return_t result;
99 ipc_kmsg_t kmsg;
100
101 /* Allocate and initialize local state of a timer object */
102 timer = zalloc_flags(mk_timer_zone, Z_ZERO | Z_WAITOK | Z_NOFAIL);
103 simple_lock_init(&timer->lock, 0);
104 thread_call_setup(call: &timer->mkt_thread_call, func: mk_timer_expire, param0: timer);
105
106 /* Pre-allocate a kmsg for the timer messages */
107 kmsg = ipc_kmsg_alloc(msg_size: sizeof(mk_timer_expire_msg_t), aux_size: 0, desc_count: 0,
108 flags: IPC_KMSG_ALLOC_KERNEL | IPC_KMSG_ALLOC_ZERO |
109 IPC_KMSG_ALLOC_SAVED | IPC_KMSG_ALLOC_NOFAIL);
110 static_assert(sizeof(mk_timer_expire_msg_t) < IKM_SAVED_MSG_SIZE);
111
112 init_flags = IPC_PORT_INIT_MESSAGE_QUEUE;
113 result = ipc_port_alloc(space: myspace, flags: init_flags, namep: &name, portp: &port);
114 if (result != KERN_SUCCESS) {
115 zfree(mk_timer_zone, timer);
116 ipc_kmsg_free(kmsg);
117 return MACH_PORT_NULL;
118 }
119
120 /* Associate the pre-allocated kmsg with the port */
121 ipc_kmsg_set_prealloc(kmsg, port);
122
123 /* port locked, receive right at user-space */
124 ipc_kobject_upgrade_mktimer_locked(port, kobject: (ipc_kobject_t)timer);
125
126 /* make a (naked) send right for the timer to keep */
127 timer->port = ipc_port_make_send_any_locked(port);
128
129 ip_mq_unlock(port);
130
131 return name;
132}
133
134static void
135mk_timer_port_destroy(
136 ipc_port_t port)
137{
138 struct mk_timer *timer = NULL;
139
140 timer = ipc_kobject_disable(port, type: IKOT_TIMER);
141
142 simple_lock(&timer->lock, LCK_GRP_NULL);
143
144 if (thread_call_cancel(call: &timer->mkt_thread_call)) {
145 timer->active--;
146 }
147 timer->is_armed = FALSE;
148
149 timer->is_dead = TRUE;
150 if (timer->active == 0) {
151 simple_unlock(&timer->lock);
152 zfree(mk_timer_zone, timer);
153
154 ipc_port_release_send(port);
155 return;
156 }
157
158 simple_unlock(&timer->lock);
159}
160
161static void
162mk_timer_expire(
163 void *p0,
164 __unused void *p1)
165{
166 struct mk_timer* timer = p0;
167
168 simple_lock(&timer->lock, LCK_GRP_NULL);
169
170 if (timer->active > 1) {
171 timer->active--;
172 simple_unlock(&timer->lock);
173 return;
174 }
175
176 ipc_port_t port = timer->port;
177 assert(port != IP_NULL);
178 assert(timer->active == 1);
179
180 while (timer->is_armed && timer->active == 1) {
181 mk_timer_expire_msg_t msg;
182
183 timer->is_armed = FALSE;
184 simple_unlock(&timer->lock);
185
186 msg.header.msgh_bits =
187 MACH_MSGH_BITS_SET(MACH_MSG_TYPE_COPY_SEND, 0, 0, 0);
188 msg.header.msgh_remote_port = port;
189 msg.header.msgh_local_port = MACH_PORT_NULL;
190 msg.header.msgh_voucher_port = MACH_PORT_NULL;
191 msg.header.msgh_id = 0;
192
193 msg.unused[0] = msg.unused[1] = msg.unused[2] = 0;
194
195 (void) mach_msg_send_from_kernel_proper(msg: &msg.header, send_size: sizeof(msg));
196
197 simple_lock(&timer->lock, LCK_GRP_NULL);
198 }
199
200 if (--timer->active == 0 && timer->is_dead) {
201 simple_unlock(&timer->lock);
202 zfree(mk_timer_zone, timer);
203
204 ipc_port_release_send(port);
205 return;
206 }
207
208 simple_unlock(&timer->lock);
209}
210
211/*
212 * mk_timer_destroy_trap: Destroy the Mach port associated with a timer
213 *
214 * Parameters: args User argument descriptor (see below)
215 *
216 * Indirect: args->name Mach port name
217 *
218 *
219 * Returns: 0 Success
220 * !0 Not success
221 *
222 */
223kern_return_t
224mk_timer_destroy_trap(
225 struct mk_timer_destroy_trap_args *args)
226{
227 mach_port_name_t name = args->name;
228 ipc_space_t myspace = current_space();
229 ipc_port_t port;
230 kern_return_t kr;
231 ipc_entry_t entry;
232
233 kr = ipc_right_lookup_write(space: myspace, name, entryp: &entry);
234 if (kr != KERN_SUCCESS) {
235 return kr;
236 }
237
238 /* space is write-locked and active */
239
240 if ((IE_BITS_TYPE(entry->ie_bits) & MACH_PORT_TYPE_RECEIVE) == 0) {
241 is_write_unlock(myspace);
242 return KERN_INVALID_RIGHT;
243 }
244
245 port = ip_object_to_port(entry->ie_object);
246 if (ip_kotype(port) != IKOT_TIMER) {
247 is_write_unlock(myspace);
248 return KERN_INVALID_ARGUMENT;
249 }
250
251 /*
252 * This should have been a mach_mod_refs(RR, -1) but unfortunately,
253 * the fact this is a mach_port_destroy() is ABI now.
254 */
255 return ipc_right_destroy(space: myspace, name, entry, TRUE, guard: 0); /* unlocks space */
256}
257
258/*
259 * mk_timer_arm_trap: Start (arm) a timer
260 *
261 * Parameters: args User argument descriptor (see below)
262 *
263 * Indirect: args->name Mach port name
264 * args->expire_time Time when timer expires
265 *
266 *
267 * Returns: 0 Success
268 * !0 Not success
269 *
270 */
271
272static kern_return_t
273mk_timer_arm_trap_internal(mach_port_name_t name, uint64_t expire_time, uint64_t mk_leeway, uint64_t mk_timer_flags)
274{
275 struct mk_timer* timer;
276 ipc_space_t myspace = current_space();
277 ipc_port_t port;
278 kern_return_t result;
279
280 result = ipc_port_translate_receive(space: myspace, name, portp: &port);
281 if (result != KERN_SUCCESS) {
282 return result;
283 }
284
285 timer = ipc_kobject_get_locked(port, type: IKOT_TIMER);
286
287 if (timer) {
288
289 simple_lock(&timer->lock, LCK_GRP_NULL);
290 assert(timer->port == port);
291 ip_mq_unlock(port);
292
293 if (!timer->is_dead) {
294 timer->is_armed = TRUE;
295
296 if (expire_time > mach_absolute_time()) {
297 uint32_t tcflags = THREAD_CALL_DELAY_USER_NORMAL;
298
299 if (mk_timer_flags & MK_TIMER_CRITICAL) {
300 tcflags = THREAD_CALL_DELAY_USER_CRITICAL;
301 }
302
303 if (mk_leeway != 0) {
304 tcflags |= THREAD_CALL_DELAY_LEEWAY;
305 }
306
307 if (!thread_call_enter_delayed_with_leeway(
308 call: &timer->mkt_thread_call, NULL,
309 deadline: expire_time, leeway: mk_leeway, flags: tcflags)) {
310 timer->active++;
311 }
312 } else {
313 if (!thread_call_enter1(call: &timer->mkt_thread_call, NULL)) {
314 timer->active++;
315 }
316 }
317 }
318
319 simple_unlock(&timer->lock);
320 } else {
321 ip_mq_unlock(port);
322 result = KERN_INVALID_ARGUMENT;
323 }
324 return result;
325}
326
327kern_return_t
328mk_timer_arm_trap(struct mk_timer_arm_trap_args *args)
329{
330 return mk_timer_arm_trap_internal(name: args->name, expire_time: args->expire_time, mk_leeway: 0, MK_TIMER_NORMAL);
331}
332
333kern_return_t
334mk_timer_arm_leeway_trap(struct mk_timer_arm_leeway_trap_args *args)
335{
336 return mk_timer_arm_trap_internal(name: args->name, expire_time: args->expire_time, mk_leeway: args->mk_leeway, mk_timer_flags: args->mk_timer_flags);
337}
338
339/*
340 * mk_timer_cancel_trap: Cancel a timer
341 *
342 * Parameters: args User argument descriptor (see below)
343 *
344 * Indirect: args->name Mach port name
345 * args->result_time The armed time of the cancelled timer (return value)
346 *
347 *
348 * Returns: 0 Success
349 * !0 Not success
350 *
351 */
352kern_return_t
353mk_timer_cancel_trap(
354 struct mk_timer_cancel_trap_args *args)
355{
356 mach_port_name_t name = args->name;
357 mach_vm_address_t result_time_addr = args->result_time;
358 uint64_t armed_time = 0;
359 struct mk_timer* timer;
360 ipc_space_t myspace = current_space();
361 ipc_port_t port;
362 kern_return_t result;
363
364 result = ipc_port_translate_receive(space: myspace, name, portp: &port);
365 if (result != KERN_SUCCESS) {
366 return result;
367 }
368
369 timer = ipc_kobject_get_locked(port, type: IKOT_TIMER);
370 if (timer != NULL) {
371 simple_lock(&timer->lock, LCK_GRP_NULL);
372 assert(timer->port == port);
373 ip_mq_unlock(port);
374
375 if (timer->is_armed) {
376 armed_time = thread_call_get_armed_deadline(call: &timer->mkt_thread_call);
377 if (thread_call_cancel(call: &timer->mkt_thread_call)) {
378 timer->active--;
379 }
380 timer->is_armed = FALSE;
381 }
382
383 simple_unlock(&timer->lock);
384 } else {
385 ip_mq_unlock(port);
386 result = KERN_INVALID_ARGUMENT;
387 }
388
389 if (result == KERN_SUCCESS && result_time_addr != 0) {
390 if (copyout((void *)&armed_time, result_time_addr, sizeof(armed_time)) != 0) {
391 result = KERN_FAILURE;
392 }
393 }
394
395 return result;
396}
397