]> git.saurik.com Git - apple/libdispatch.git/blob - src/firehose/firehose_buffer.c
libdispatch-913.1.6.tar.gz
[apple/libdispatch.git] / src / firehose / firehose_buffer.c
1 /*
2 * Copyright (c) 2015 Apple Inc. All rights reserved.
3 *
4 * @APPLE_APACHE_LICENSE_HEADER_START@
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 *
18 * @APPLE_APACHE_LICENSE_HEADER_END@
19 */
20
21 #include <mach/vm_statistics.h> // VM_MEMORY_GENEALOGY
22 #ifdef KERNEL
23
24 #define OS_VOUCHER_ACTIVITY_SPI_TYPES 1
25 #define OS_FIREHOSE_SPI 1
26 #define __OS_EXPOSE_INTERNALS_INDIRECT__ 1
27
28 #define DISPATCH_PURE_C 1
29 #define _safe_cast_to_long(x) \
30 ({ _Static_assert(sizeof(typeof(x)) <= sizeof(long), \
31 "__builtin_expect doesn't support types wider than long"); \
32 (long)(x); })
33 #define fastpath(x) ((typeof(x))__builtin_expect(_safe_cast_to_long(x), ~0l))
34 #define slowpath(x) ((typeof(x))__builtin_expect(_safe_cast_to_long(x), 0l))
35 #define os_likely(x) __builtin_expect(!!(x), 1)
36 #define os_unlikely(x) __builtin_expect(!!(x), 0)
37 #define likely(x) __builtin_expect(!!(x), 1)
38 #define unlikely(x) __builtin_expect(!!(x), 0)
39
40 #ifndef OS_FALLTHROUGH
41 #define OS_FALLTHROUGH
42 #endif
43
44 #define DISPATCH_INTERNAL_CRASH(ac, msg) ({ panic(msg); __builtin_trap(); })
45
46 #if defined(__x86_64__) || defined(__i386__)
47 #define dispatch_hardware_pause() __asm__("pause")
48 #elif (defined(__arm__) && defined(_ARM_ARCH_7) && defined(__thumb__)) || \
49 defined(__arm64__)
50 #define dispatch_hardware_pause() __asm__("yield")
51 #define dispatch_hardware_wfe() __asm__("wfe")
52 #else
53 #define dispatch_hardware_pause() __asm__("")
54 #endif
55
56 #define _dispatch_wait_until(c) ({ \
57 typeof(c) _c; \
58 for (;;) { \
59 if (likely(_c = (c))) break; \
60 dispatch_hardware_pause(); \
61 } \
62 _c; })
63 #define dispatch_compiler_barrier() __asm__ __volatile__("" ::: "memory")
64
65 typedef uint32_t dispatch_lock;
66 typedef struct dispatch_gate_s {
67 dispatch_lock dgl_lock;
68 } dispatch_gate_s, *dispatch_gate_t;
69 #define DLOCK_LOCK_DATA_CONTENTION 0
70 static void _dispatch_gate_wait(dispatch_gate_t l, uint32_t flags);
71
72 #define fcp_quarntined fcp_quarantined
73
74 #include <kern/debug.h>
75 #include <machine/cpu_number.h>
76 #include <kern/thread.h>
77 #include <mach/port.h>
78 #include <stdbool.h>
79 #include <string.h>
80 #include <sys/param.h>
81 #include <sys/types.h>
82 #include <vm/vm_kern.h>
83 #include <internal/atomic.h> // os/internal/atomic.h
84 #include <firehose_types_private.h> // <firehose/firehose_types_private.h>
85 #include <tracepoint_private.h> // <firehose/tracepoint_private.h>
86 #include <chunk_private.h> // <firehose/chunk_private.h>
87 #include "os/firehose_buffer_private.h"
88 #include "firehose_buffer_internal.h"
89 #include "firehose_inline_internal.h"
90 #else
91 #include "internal.h"
92 #include "firehose.h" // MiG
93 #include "firehose_replyServer.h" // MiG
94 #endif
95
96 #if OS_FIREHOSE_SPI
97
98 #if __has_feature(c_static_assert)
99 _Static_assert(sizeof(((firehose_stream_state_u *)NULL)->fss_gate) ==
100 sizeof(((firehose_stream_state_u *)NULL)->fss_allocator),
101 "fss_gate and fss_allocator alias");
102 _Static_assert(offsetof(firehose_stream_state_u, fss_gate) ==
103 offsetof(firehose_stream_state_u, fss_allocator),
104 "fss_gate and fss_allocator alias");
105 _Static_assert(sizeof(struct firehose_buffer_header_s) ==
106 FIREHOSE_CHUNK_SIZE,
107 "firehose buffer header must be 4k");
108 _Static_assert(offsetof(struct firehose_buffer_header_s, fbh_unused) <=
109 FIREHOSE_CHUNK_SIZE - FIREHOSE_BUFFER_LIBTRACE_HEADER_SIZE,
110 "we must have enough space for the libtrace header");
111 _Static_assert(powerof2(FIREHOSE_BUFFER_CHUNK_COUNT),
112 "CHUNK_COUNT Must be a power of two");
113 _Static_assert(FIREHOSE_BUFFER_CHUNK_COUNT <= 64,
114 "CHUNK_COUNT must be less than 64 (bitmap in uint64_t)");
115 #ifdef FIREHOSE_BUFFER_MADVISE_CHUNK_COUNT
116 _Static_assert(powerof2(FIREHOSE_BUFFER_MADVISE_CHUNK_COUNT),
117 "madvise chunk count must be a power of two");
118 #endif
119 _Static_assert(sizeof(struct firehose_buffer_stream_s) == 128,
120 "firehose buffer stream must be small (single cacheline if possible)");
121 _Static_assert(sizeof(struct firehose_tracepoint_s) == 24,
122 "tracepoint header should be exactly 24 bytes");
123 #endif
124
125 #ifdef KERNEL
126 static firehose_buffer_t kernel_firehose_buffer = NULL;
127 #endif
128
129 #pragma mark -
130 #pragma mark Client IPC to the log daemon
131 #ifndef KERNEL
132
133 static mach_port_t
134 firehose_client_reconnect(firehose_buffer_t fb, mach_port_t oldsendp)
135 {
136 mach_port_t sendp = MACH_PORT_NULL;
137 mach_port_t mem_port = MACH_PORT_NULL, extra_info_port = MACH_PORT_NULL;
138 mach_vm_size_t extra_info_size = 0;
139 kern_return_t kr;
140
141 dispatch_assert(fb->fb_header.fbh_logd_port);
142 dispatch_assert(fb->fb_header.fbh_recvp);
143 dispatch_assert(fb->fb_header.fbh_uniquepid != 0);
144
145 _dispatch_unfair_lock_lock(&fb->fb_header.fbh_logd_lock);
146 sendp = fb->fb_header.fbh_sendp;
147 if (sendp != oldsendp || sendp == MACH_PORT_DEAD) {
148 // someone beat us to reconnecting or logd was unloaded, just go away
149 goto unlock;
150 }
151
152 if (oldsendp) {
153 // same trick as _xpc_pipe_dispose: keeping a send right
154 // maintains the name, so that we can destroy the receive right
155 // in case we still have it.
156 (void)firehose_mach_port_recv_dispose(oldsendp, fb);
157 firehose_mach_port_send_release(oldsendp);
158 fb->fb_header.fbh_sendp = MACH_PORT_NULL;
159 }
160
161 /* Create a memory port for the buffer VM region */
162 vm_prot_t flags = VM_PROT_READ | MAP_MEM_VM_SHARE;
163 memory_object_size_t size = sizeof(union firehose_buffer_u);
164 mach_vm_address_t addr = (vm_address_t)fb;
165
166 kr = mach_make_memory_entry_64(mach_task_self(), &size, addr,
167 flags, &mem_port, MACH_PORT_NULL);
168 if (size < sizeof(union firehose_buffer_u)) {
169 DISPATCH_CLIENT_CRASH(size, "Invalid size for the firehose buffer");
170 }
171 if (unlikely(kr)) {
172 // the client probably has some form of memory corruption
173 // and/or a port leak
174 DISPATCH_CLIENT_CRASH(kr, "Unable to make memory port");
175 }
176
177 /* Create a communication port to the logging daemon */
178 uint32_t opts = MPO_CONTEXT_AS_GUARD | MPO_TEMPOWNER | MPO_INSERT_SEND_RIGHT;
179 sendp = firehose_mach_port_allocate(opts, fb);
180
181 if (oldsendp && _voucher_libtrace_hooks->vah_get_reconnect_info) {
182 kr = _voucher_libtrace_hooks->vah_get_reconnect_info(&addr, &size);
183 if (likely(kr == KERN_SUCCESS) && addr && size) {
184 extra_info_size = size;
185 kr = mach_make_memory_entry_64(mach_task_self(), &size, addr,
186 flags, &extra_info_port, MACH_PORT_NULL);
187 if (unlikely(kr)) {
188 // the client probably has some form of memory corruption
189 // and/or a port leak
190 DISPATCH_CLIENT_CRASH(kr, "Unable to make memory port");
191 }
192 kr = mach_vm_deallocate(mach_task_self(), addr, size);
193 (void)dispatch_assume_zero(kr);
194 }
195 }
196
197 /* Call the firehose_register() MIG routine */
198 kr = firehose_send_register(fb->fb_header.fbh_logd_port, mem_port,
199 sizeof(union firehose_buffer_u), sendp, fb->fb_header.fbh_recvp,
200 extra_info_port, extra_info_size);
201 if (likely(kr == KERN_SUCCESS)) {
202 fb->fb_header.fbh_sendp = sendp;
203 } else if (unlikely(kr == MACH_SEND_INVALID_DEST)) {
204 // MACH_SEND_INVALID_DEST here means that logd's boostrap port
205 // turned into a dead name, which in turn means that logd has been
206 // unloaded. The only option here, is to give up permanently.
207 //
208 // same trick as _xpc_pipe_dispose: keeping a send right
209 // maintains the name, so that we can destroy the receive right
210 // in case we still have it.
211 (void)firehose_mach_port_recv_dispose(sendp, fb);
212 firehose_mach_port_send_release(sendp);
213 firehose_mach_port_send_release(mem_port);
214 if (extra_info_port) firehose_mach_port_send_release(extra_info_port);
215 sendp = fb->fb_header.fbh_sendp = MACH_PORT_DEAD;
216 } else {
217 // the client probably has some form of memory corruption
218 // and/or a port leak
219 DISPATCH_CLIENT_CRASH(kr, "Unable to register with logd");
220 }
221
222 unlock:
223 _dispatch_unfair_lock_unlock(&fb->fb_header.fbh_logd_lock);
224 return sendp;
225 }
226
227 static void
228 firehose_buffer_update_limits_unlocked(firehose_buffer_t fb)
229 {
230 firehose_bank_state_u old, new;
231 firehose_buffer_bank_t fbb = &fb->fb_header.fbh_bank;
232 unsigned long fbb_flags = fbb->fbb_flags;
233 uint16_t io_streams = 0, mem_streams = 0;
234 uint16_t total = 0;
235
236 for (size_t i = 0; i < countof(fb->fb_header.fbh_stream); i++) {
237 firehose_buffer_stream_t fbs = fb->fb_header.fbh_stream + i;
238
239 if (fbs->fbs_state.fss_current == FIREHOSE_STREAM_STATE_PRISTINE) {
240 continue;
241 }
242 if ((1UL << i) & firehose_stream_uses_io_bank) {
243 io_streams++;
244 } else {
245 mem_streams++;
246 }
247 }
248
249 if (fbb_flags & FIREHOSE_BUFFER_BANK_FLAG_LOW_MEMORY) {
250 if (fbb_flags & FIREHOSE_BUFFER_BANK_FLAG_HIGH_RATE) {
251 total = 1 + 4 * mem_streams + io_streams; // usually 10
252 } else {
253 total = 1 + 2 + mem_streams + io_streams; // usually 6
254 }
255 } else {
256 if (fbb_flags & FIREHOSE_BUFFER_BANK_FLAG_HIGH_RATE) {
257 total = 1 + 6 * mem_streams + 3 * io_streams; // usually 16
258 } else {
259 total = 1 + 2 * (mem_streams + io_streams); // usually 7
260 }
261 }
262
263 uint16_t ratio = (uint16_t)(PAGE_SIZE / FIREHOSE_CHUNK_SIZE);
264 if (ratio > 1) {
265 total = roundup(total, ratio);
266 }
267 total = MAX(total, FIREHOSE_BUFFER_CHUNK_PREALLOCATED_COUNT);
268 if (!(fbb_flags & FIREHOSE_BUFFER_BANK_FLAG_LOW_MEMORY)) {
269 total = MAX(total, TARGET_OS_EMBEDDED ? 8 : 12);
270 }
271
272 new.fbs_max_ref = total;
273 new.fbs_mem_bank = FIREHOSE_BANK_UNAVAIL_BIT - (total - 1);
274 new.fbs_io_bank = FIREHOSE_BANK_UNAVAIL_BIT -
275 MAX(3 * total / 8, 2 * io_streams);
276 new.fbs_unused = 0;
277
278 old = fbb->fbb_limits;
279 fbb->fbb_limits = new;
280 if (old.fbs_atomic_state == new.fbs_atomic_state) {
281 return;
282 }
283 os_atomic_add2o(&fb->fb_header, fbh_bank.fbb_state.fbs_atomic_state,
284 new.fbs_atomic_state - old.fbs_atomic_state, relaxed);
285 }
286 #endif // !KERNEL
287
288 firehose_buffer_t
289 firehose_buffer_create(mach_port_t logd_port, uint64_t unique_pid,
290 unsigned long bank_flags)
291 {
292 firehose_buffer_header_t fbh;
293 firehose_buffer_t fb;
294
295 #ifndef KERNEL
296 mach_vm_address_t vm_addr = 0;
297 kern_return_t kr;
298
299 vm_addr = vm_page_size;
300 const size_t madvise_bytes = FIREHOSE_BUFFER_MADVISE_CHUNK_COUNT *
301 FIREHOSE_CHUNK_SIZE;
302 if (slowpath(madvise_bytes % PAGE_SIZE)) {
303 DISPATCH_INTERNAL_CRASH(madvise_bytes,
304 "Invalid values for MADVISE_CHUNK_COUNT / CHUNK_SIZE");
305 }
306
307 kr = mach_vm_map(mach_task_self(), &vm_addr, sizeof(*fb), 0,
308 VM_FLAGS_ANYWHERE | VM_FLAGS_PURGABLE |
309 VM_MAKE_TAG(VM_MEMORY_GENEALOGY), MEMORY_OBJECT_NULL, 0, FALSE,
310 VM_PROT_DEFAULT, VM_PROT_ALL, VM_INHERIT_NONE);
311 if (slowpath(kr)) {
312 if (kr != KERN_NO_SPACE) dispatch_assume_zero(kr);
313 firehose_mach_port_send_release(logd_port);
314 return NULL;
315 }
316
317 uint32_t opts = MPO_CONTEXT_AS_GUARD | MPO_STRICT | MPO_INSERT_SEND_RIGHT;
318 #else
319 vm_offset_t vm_addr = 0;
320 vm_size_t size;
321
322 size = FIREHOSE_BUFFER_KERNEL_CHUNK_COUNT * FIREHOSE_CHUNK_SIZE;
323 __firehose_allocate(&vm_addr, size);
324
325 (void)logd_port; (void)unique_pid;
326 #endif // KERNEL
327
328 fb = (firehose_buffer_t)vm_addr;
329 fbh = &fb->fb_header;
330 #ifndef KERNEL
331 fbh->fbh_logd_port = logd_port;
332 fbh->fbh_pid = getpid();
333 fbh->fbh_uniquepid = unique_pid;
334 fbh->fbh_recvp = firehose_mach_port_allocate(opts, fb);
335 #endif // !KERNEL
336 fbh->fbh_spi_version = OS_FIREHOSE_SPI_VERSION;
337 fbh->fbh_bank.fbb_flags = bank_flags;
338
339 #ifndef KERNEL
340 for (size_t i = 0; i < countof(fbh->fbh_stream); i++) {
341 firehose_buffer_stream_t fbs = fbh->fbh_stream + i;
342 if (i != firehose_stream_metadata) {
343 fbs->fbs_state.fss_current = FIREHOSE_STREAM_STATE_PRISTINE;
344 }
345 }
346 firehose_buffer_update_limits_unlocked(fb);
347 #else
348 uint16_t total = FIREHOSE_BUFFER_CHUNK_PREALLOCATED_COUNT + 1;
349 const uint16_t num_kernel_io_pages = 8;
350 uint16_t io_pages = num_kernel_io_pages;
351 fbh->fbh_bank.fbb_state = (firehose_bank_state_u){
352 .fbs_max_ref = total,
353 .fbs_io_bank = FIREHOSE_BANK_UNAVAIL_BIT - io_pages,
354 .fbs_mem_bank = FIREHOSE_BANK_UNAVAIL_BIT - (total - io_pages - 1),
355 };
356 fbh->fbh_bank.fbb_limits = fbh->fbh_bank.fbb_state;
357 #endif // KERNEL
358
359 // now pre-allocate some chunks in the ring directly
360 #ifdef KERNEL
361 const uint16_t pre_allocated = FIREHOSE_BUFFER_CHUNK_PREALLOCATED_COUNT - 1;
362 #else
363 const uint16_t pre_allocated = FIREHOSE_BUFFER_CHUNK_PREALLOCATED_COUNT;
364 #endif
365
366 fbh->fbh_bank.fbb_bitmap = (1U << (1 + pre_allocated)) - 1;
367
368 for (uint16_t i = 0; i < pre_allocated; i++) {
369 fbh->fbh_mem_ring[i] = i + 1;
370 }
371 fbh->fbh_bank.fbb_mem_flushed = pre_allocated;
372 fbh->fbh_ring_mem_head = pre_allocated;
373
374
375 #ifdef KERNEL
376 // install the early boot page as the current one for persist
377 fbh->fbh_stream[firehose_stream_persist].fbs_state.fss_current =
378 FIREHOSE_BUFFER_CHUNK_PREALLOCATED_COUNT;
379 fbh->fbh_bank.fbb_state.fbs_io_bank += 1;
380 #endif
381
382 fbh->fbh_ring_tail = (firehose_ring_tail_u){
383 .frp_mem_flushed = pre_allocated,
384 };
385 return fb;
386 }
387
388 #ifndef KERNEL
389 static void
390 firehose_notify_source_invoke(mach_msg_header_t *hdr)
391 {
392 const size_t reply_size =
393 sizeof(union __ReplyUnion__firehose_client_firehoseReply_subsystem);
394
395 firehose_mig_server(firehoseReply_server, reply_size, hdr);
396 }
397
398 static void
399 firehose_client_register_for_notifications(firehose_buffer_t fb)
400 {
401 static const struct dispatch_continuation_s dc = {
402 .dc_func = (void *)firehose_notify_source_invoke,
403 };
404 firehose_buffer_header_t fbh = &fb->fb_header;
405
406 dispatch_once(&fbh->fbh_notifs_pred, ^{
407 dispatch_source_t ds = _dispatch_source_create_mach_msg_direct_recv(
408 fbh->fbh_recvp, &dc);
409 dispatch_set_context(ds, fb);
410 dispatch_activate(ds);
411 fbh->fbh_notifs_source = ds;
412 });
413 }
414
415 static void
416 firehose_client_send_push_async(firehose_buffer_t fb, qos_class_t qos,
417 bool for_io)
418 {
419 bool ask_for_notifs = fb->fb_header.fbh_notifs_source != NULL;
420 mach_port_t sendp = fb->fb_header.fbh_sendp;
421 kern_return_t kr = KERN_FAILURE;
422
423 if (!ask_for_notifs && _dispatch_is_multithreaded_inline()) {
424 firehose_client_register_for_notifications(fb);
425 ask_for_notifs = true;
426 }
427
428 if (slowpath(sendp == MACH_PORT_DEAD)) {
429 return;
430 }
431
432 if (fastpath(sendp)) {
433 kr = firehose_send_push_async(sendp, qos, for_io, ask_for_notifs);
434 if (likely(kr == KERN_SUCCESS)) {
435 return;
436 }
437 if (kr != MACH_SEND_INVALID_DEST) {
438 DISPATCH_VERIFY_MIG(kr);
439 dispatch_assume_zero(kr);
440 }
441 }
442
443 sendp = firehose_client_reconnect(fb, sendp);
444 if (fastpath(MACH_PORT_VALID(sendp))) {
445 kr = firehose_send_push_async(sendp, qos, for_io, ask_for_notifs);
446 if (likely(kr == KERN_SUCCESS)) {
447 return;
448 }
449 if (kr != MACH_SEND_INVALID_DEST) {
450 DISPATCH_VERIFY_MIG(kr);
451 dispatch_assume_zero(kr);
452 }
453 }
454 }
455
456 OS_NOINLINE
457 static void
458 firehose_client_start_quarantine(firehose_buffer_t fb)
459 {
460 if (_voucher_libtrace_hooks->vah_version < 5) return;
461 if (!_voucher_libtrace_hooks->vah_quarantine_starts) return;
462
463 _voucher_libtrace_hooks->vah_quarantine_starts();
464
465 fb->fb_header.fbh_quarantined = true;
466 firehose_buffer_stream_flush(fb, firehose_stream_special);
467 firehose_buffer_stream_flush(fb, firehose_stream_persist);
468 firehose_buffer_stream_flush(fb, firehose_stream_memory);
469 }
470 #endif // !KERNEL
471
472 static void
473 firehose_client_merge_updates(firehose_buffer_t fb, bool async_notif,
474 firehose_push_reply_t reply, bool quarantined,
475 firehose_bank_state_u *state_out)
476 {
477 firehose_buffer_header_t fbh = &fb->fb_header;
478 firehose_bank_state_u state;
479 firehose_ring_tail_u otail, ntail;
480 uint64_t old_flushed_pos, bank_updates;
481 uint16_t io_delta = 0;
482 uint16_t mem_delta = 0;
483
484 if (quarantined) {
485 #ifndef KERNEL
486 // this isn't a dispatch_once so that the upcall to libtrace
487 // can actually log itself without blocking on the gate.
488 if (async_notif) {
489 if (os_atomic_xchg(&fbh->fbh_quarantined_state,
490 FBH_QUARANTINE_STARTED, relaxed) !=
491 FBH_QUARANTINE_STARTED) {
492 firehose_client_start_quarantine(fb);
493 }
494 } else if (os_atomic_load(&fbh->fbh_quarantined_state, relaxed) ==
495 FBH_QUARANTINE_NONE) {
496 os_atomic_cmpxchg(&fbh->fbh_quarantined_state, FBH_QUARANTINE_NONE,
497 FBH_QUARANTINE_PENDING, relaxed);
498 }
499 #endif
500 }
501
502 if (firehose_atomic_maxv2o(fbh, fbh_bank.fbb_mem_flushed,
503 reply.fpr_mem_flushed_pos, &old_flushed_pos, relaxed)) {
504 mem_delta = (uint16_t)(reply.fpr_mem_flushed_pos - old_flushed_pos);
505 }
506 if (firehose_atomic_maxv2o(fbh, fbh_bank.fbb_io_flushed,
507 reply.fpr_io_flushed_pos, &old_flushed_pos, relaxed)) {
508 io_delta = (uint16_t)(reply.fpr_io_flushed_pos - old_flushed_pos);
509 }
510 #ifndef KERNEL
511 _dispatch_debug("client side: mem: +%d->%llx, io: +%d->%llx",
512 mem_delta, reply.fpr_mem_flushed_pos,
513 io_delta, reply.fpr_io_flushed_pos);
514 #endif
515
516 if (!mem_delta && !io_delta) {
517 if (state_out) {
518 state_out->fbs_atomic_state = os_atomic_load2o(fbh,
519 fbh_bank.fbb_state.fbs_atomic_state, relaxed);
520 }
521 return;
522 }
523
524 __firehose_critical_region_enter();
525 os_atomic_rmw_loop2o(fbh, fbh_ring_tail.frp_atomic_tail,
526 otail.frp_atomic_tail, ntail.frp_atomic_tail, relaxed, {
527 ntail = otail;
528 // overflow handles the generation wraps
529 ntail.frp_io_flushed += io_delta;
530 ntail.frp_mem_flushed += mem_delta;
531 });
532
533 bank_updates = ((uint64_t)mem_delta << FIREHOSE_BANK_SHIFT(0)) |
534 ((uint64_t)io_delta << FIREHOSE_BANK_SHIFT(1));
535 state.fbs_atomic_state = os_atomic_sub2o(fbh,
536 fbh_bank.fbb_state.fbs_atomic_state, bank_updates, release);
537 __firehose_critical_region_leave();
538
539 if (state_out) *state_out = state;
540
541 if (async_notif) {
542 if (io_delta) {
543 os_atomic_inc2o(fbh, fbh_bank.fbb_io_notifs, relaxed);
544 }
545 if (mem_delta) {
546 os_atomic_inc2o(fbh, fbh_bank.fbb_mem_notifs, relaxed);
547 }
548 }
549 }
550
551 #ifndef KERNEL
552 OS_NOT_TAIL_CALLED OS_NOINLINE
553 static void
554 firehose_client_send_push_and_wait(firehose_buffer_t fb, bool for_io,
555 firehose_bank_state_u *state_out)
556 {
557 mach_port_t sendp = fb->fb_header.fbh_sendp;
558 firehose_push_reply_t push_reply = { };
559 qos_class_t qos = qos_class_self();
560 boolean_t quarantined = false;
561 kern_return_t kr;
562
563 if (slowpath(sendp == MACH_PORT_DEAD)) {
564 return;
565 }
566 if (fastpath(sendp)) {
567 kr = firehose_send_push_and_wait(sendp, qos, for_io,
568 &push_reply, &quarantined);
569 if (likely(kr == KERN_SUCCESS)) {
570 goto success;
571 }
572 if (kr != MACH_SEND_INVALID_DEST) {
573 DISPATCH_VERIFY_MIG(kr);
574 dispatch_assume_zero(kr);
575 }
576 }
577
578 sendp = firehose_client_reconnect(fb, sendp);
579 if (fastpath(MACH_PORT_VALID(sendp))) {
580 kr = firehose_send_push_and_wait(sendp, qos, for_io,
581 &push_reply, &quarantined);
582 if (likely(kr == KERN_SUCCESS)) {
583 goto success;
584 }
585 if (kr != MACH_SEND_INVALID_DEST) {
586 DISPATCH_VERIFY_MIG(kr);
587 dispatch_assume_zero(kr);
588 }
589 }
590
591 if (state_out) {
592 state_out->fbs_atomic_state = os_atomic_load2o(&fb->fb_header,
593 fbh_bank.fbb_state.fbs_atomic_state, relaxed);
594 }
595 return;
596
597 success:
598 if (memcmp(&push_reply, &FIREHOSE_PUSH_REPLY_CORRUPTED,
599 sizeof(push_reply)) == 0) {
600 // TODO: find out the actual cause and log it
601 DISPATCH_CLIENT_CRASH(0, "Memory corruption in the logging buffers");
602 }
603
604 if (for_io) {
605 os_atomic_inc2o(&fb->fb_header, fbh_bank.fbb_io_sync_pushes, relaxed);
606 } else {
607 os_atomic_inc2o(&fb->fb_header, fbh_bank.fbb_mem_sync_pushes, relaxed);
608 }
609 // TODO <rdar://problem/22963876>
610 //
611 // use fbb_*_flushes and fbb_*_sync_pushes to decide to dynamically
612 // allow using more buffers, if not under memory pressure.
613 //
614 // There only is a point for multithreaded clients if:
615 // - enough samples (total_flushes above some limits)
616 // - the ratio is really bad (a push per cycle is definitely a problem)
617 return firehose_client_merge_updates(fb, false, push_reply, quarantined,
618 state_out);
619 }
620
621 OS_NOT_TAIL_CALLED OS_NOINLINE
622 static void
623 __FIREHOSE_CLIENT_THROTTLED_DUE_TO_HEAVY_LOGGING__(firehose_buffer_t fb,
624 bool for_io, firehose_bank_state_u *state_out)
625 {
626 firehose_client_send_push_and_wait(fb, for_io, state_out);
627 }
628
629 kern_return_t
630 firehose_client_push_reply(mach_port_t req_port OS_UNUSED,
631 kern_return_t rtc, firehose_push_reply_t push_reply OS_UNUSED,
632 boolean_t quarantined OS_UNUSED)
633 {
634 DISPATCH_INTERNAL_CRASH(rtc, "firehose_push_reply should never be sent "
635 "to the buffer receive port");
636 }
637
638 kern_return_t
639 firehose_client_push_notify_async(mach_port_t server_port OS_UNUSED,
640 firehose_push_reply_t push_reply, boolean_t quarantined)
641 {
642 // see _dispatch_source_merge_mach_msg_direct
643 dispatch_queue_t dq = _dispatch_queue_get_current();
644 firehose_buffer_t fb = dispatch_get_context(dq);
645 firehose_client_merge_updates(fb, true, push_reply, quarantined, NULL);
646 return KERN_SUCCESS;
647 }
648
649 #endif // !KERNEL
650 #pragma mark -
651 #pragma mark Buffer handling
652
653 #ifndef KERNEL
654 void
655 firehose_buffer_update_limits(firehose_buffer_t fb)
656 {
657 dispatch_unfair_lock_t fbb_lock = &fb->fb_header.fbh_bank.fbb_lock;
658 _dispatch_unfair_lock_lock(fbb_lock);
659 firehose_buffer_update_limits_unlocked(fb);
660 _dispatch_unfair_lock_unlock(fbb_lock);
661 }
662 #endif // !KERNEL
663
664 OS_ALWAYS_INLINE
665 static inline firehose_tracepoint_t
666 firehose_buffer_chunk_init(firehose_chunk_t fc,
667 firehose_tracepoint_query_t ask, uint8_t **privptr)
668 {
669 const uint16_t ft_size = offsetof(struct firehose_tracepoint_s, ft_data);
670
671 uint16_t pub_offs = offsetof(struct firehose_chunk_s, fc_data);
672 uint16_t priv_offs = FIREHOSE_CHUNK_SIZE;
673
674 pub_offs += roundup(ft_size + ask->pubsize, 8);
675 priv_offs -= ask->privsize;
676
677 if (fc->fc_pos.fcp_atomic_pos) {
678 // Needed for process death handling (recycle-reuse):
679 // No atomic fences required, we merely want to make sure the observers
680 // will see memory effects in program (asm) order.
681 // 1. the payload part of the chunk is cleared completely
682 // 2. the chunk is marked as reused
683 // This ensures that if we don't see a reference to a chunk in the ring
684 // and it is dirty, when crawling the chunk, we don't see remnants of
685 // other tracepoints
686 //
687 // We only do that when the fc_pos is non zero, because zero means
688 // we just faulted the chunk, and the kernel already bzero-ed it.
689 bzero(fc->fc_data, sizeof(fc->fc_data));
690 }
691 dispatch_compiler_barrier();
692 // <rdar://problem/23562733> boot starts mach absolute time at 0, and
693 // wrapping around to values above UINT64_MAX - FIREHOSE_STAMP_SLOP
694 // breaks firehose_buffer_stream_flush() assumptions
695 if (ask->stamp > FIREHOSE_STAMP_SLOP) {
696 fc->fc_timestamp = ask->stamp - FIREHOSE_STAMP_SLOP;
697 } else {
698 fc->fc_timestamp = 0;
699 }
700 fc->fc_pos = (firehose_chunk_pos_u){
701 .fcp_next_entry_offs = pub_offs,
702 .fcp_private_offs = priv_offs,
703 .fcp_refcnt = 1,
704 .fcp_qos = firehose_buffer_qos_bits_propagate(),
705 .fcp_stream = ask->stream,
706 .fcp_flag_io = ask->for_io,
707 .fcp_quarantined = ask->quarantined,
708 };
709
710 if (privptr) {
711 *privptr = fc->fc_start + priv_offs;
712 }
713 return (firehose_tracepoint_t)fc->fc_data;
714 }
715
716 OS_NOINLINE
717 static firehose_tracepoint_t
718 firehose_buffer_stream_chunk_install(firehose_buffer_t fb,
719 firehose_tracepoint_query_t ask, uint8_t **privptr, uint16_t ref)
720 {
721 firehose_stream_state_u state, new_state;
722 firehose_tracepoint_t ft;
723 firehose_buffer_header_t fbh = &fb->fb_header;
724 firehose_buffer_stream_t fbs = &fbh->fbh_stream[ask->stream];
725 uint64_t stamp_and_len;
726
727 if (fastpath(ref)) {
728 firehose_chunk_t fc = firehose_buffer_ref_to_chunk(fb, ref);
729 ft = firehose_buffer_chunk_init(fc, ask, privptr);
730 // Needed for process death handling (tracepoint-begin):
731 // write the length before making the chunk visible
732 stamp_and_len = ask->stamp - fc->fc_timestamp;
733 stamp_and_len |= (uint64_t)ask->pubsize << 48;
734 os_atomic_store2o(ft, ft_stamp_and_length, stamp_and_len, relaxed);
735 #ifdef KERNEL
736 ft->ft_thread = thread_tid(current_thread());
737 #else
738 ft->ft_thread = _pthread_threadid_self_np_direct();
739 #endif
740 if (ask->stream == firehose_stream_metadata) {
741 os_atomic_or2o(fbh, fbh_bank.fbb_metadata_bitmap,
742 1ULL << ref, relaxed);
743 }
744 // release barrier to make the chunk init visible
745 os_atomic_rmw_loop2o(fbs, fbs_state.fss_atomic_state,
746 state.fss_atomic_state, new_state.fss_atomic_state, release, {
747 // We use a generation counter to prevent a theoretical ABA problem:
748 // a thread could try to acquire a tracepoint in a chunk, fail to
749 // do so mark it as to be pushed, enqueue it, and then be preempted
750 //
751 // It sleeps for a long time, and then tries to acquire the
752 // allocator bit and uninstalling the chunk. Succeeds in doing so,
753 // but because the chunk actually happened to have cycled all the
754 // way back to being installed. That thread would effectively hide
755 // that unflushed chunk and leak it.
756 //
757 // Having a generation counter prevents the uninstallation of the
758 // chunk to spuriously succeed when it was a re-incarnation of it.
759 new_state = (firehose_stream_state_u){
760 .fss_current = ref,
761 .fss_generation = state.fss_generation + 1,
762 };
763 });
764 } else {
765 // the allocator gave up just clear the allocator + waiter bits
766 firehose_stream_state_u mask = { .fss_allocator = ~0u, };
767 state.fss_atomic_state = os_atomic_and_orig2o(fbs,
768 fbs_state.fss_atomic_state, ~mask.fss_atomic_state, relaxed);
769 ft = NULL;
770 }
771
772 // pairs with the one in firehose_buffer_tracepoint_reserve()
773 __firehose_critical_region_leave();
774
775 #ifndef KERNEL
776 if (unlikely(_dispatch_lock_is_locked_by_self(state.fss_gate.dgl_lock))) {
777 _dispatch_gate_broadcast_slow(&fbs->fbs_state.fss_gate,
778 state.fss_gate.dgl_lock);
779 }
780
781 if (unlikely(state.fss_current == FIREHOSE_STREAM_STATE_PRISTINE)) {
782 firehose_buffer_update_limits(fb);
783 }
784
785 if (unlikely(os_atomic_load2o(fbh, fbh_quarantined_state, relaxed) ==
786 FBH_QUARANTINE_PENDING)) {
787 if (os_atomic_cmpxchg2o(fbh, fbh_quarantined_state,
788 FBH_QUARANTINE_PENDING, FBH_QUARANTINE_STARTED, relaxed)) {
789 firehose_client_start_quarantine(fb);
790 }
791 }
792 #endif // KERNEL
793
794 return ft;
795 }
796
797 #ifndef KERNEL
798 OS_ALWAYS_INLINE
799 static inline uint16_t
800 firehose_buffer_ring_try_grow(firehose_buffer_bank_t fbb, uint16_t limit)
801 {
802 uint16_t ref = 0;
803 uint64_t bitmap;
804
805 _dispatch_unfair_lock_lock(&fbb->fbb_lock);
806 bitmap = ~(fbb->fbb_bitmap | (~0ULL << limit));
807 if (bitmap) {
808 ref = firehose_bitmap_first_set(bitmap);
809 fbb->fbb_bitmap |= 1U << ref;
810 }
811 _dispatch_unfair_lock_unlock(&fbb->fbb_lock);
812 return ref;
813 }
814
815 OS_ALWAYS_INLINE
816 static inline uint16_t
817 firehose_buffer_ring_shrink(firehose_buffer_t fb, uint16_t ref)
818 {
819 const size_t madv_size =
820 FIREHOSE_CHUNK_SIZE * FIREHOSE_BUFFER_MADVISE_CHUNK_COUNT;
821 const size_t madv_mask =
822 (1ULL << FIREHOSE_BUFFER_MADVISE_CHUNK_COUNT) - 1;
823
824 dispatch_unfair_lock_t fbb_lock = &fb->fb_header.fbh_bank.fbb_lock;
825 uint64_t bitmap;
826
827 _dispatch_unfair_lock_lock(fbb_lock);
828 if (ref < fb->fb_header.fbh_bank.fbb_limits.fbs_max_ref) {
829 goto done;
830 }
831
832 bitmap = (fb->fb_header.fbh_bank.fbb_bitmap &= ~(1UL << ref));
833 ref &= ~madv_mask;
834 if ((bitmap & (madv_mask << ref)) == 0) {
835 // if MADVISE_WIDTH consecutive chunks are free, madvise them free
836 madvise(firehose_buffer_ref_to_chunk(fb, ref), madv_size, MADV_FREE);
837 }
838 ref = 0;
839 done:
840 _dispatch_unfair_lock_unlock(fbb_lock);
841 return ref;
842 }
843 #endif // !KERNEL
844
845 OS_NOINLINE
846 void
847 firehose_buffer_ring_enqueue(firehose_buffer_t fb, uint16_t ref)
848 {
849 firehose_chunk_t fc = firehose_buffer_ref_to_chunk(fb, ref);
850 uint16_t volatile *fbh_ring;
851 uint16_t volatile *fbh_ring_head;
852 uint16_t head, gen, dummy, idx;
853 firehose_chunk_pos_u fc_pos = fc->fc_pos;
854 bool for_io = fc_pos.fcp_flag_io;
855
856 if (for_io) {
857 fbh_ring = fb->fb_header.fbh_io_ring;
858 fbh_ring_head = &fb->fb_header.fbh_ring_io_head;
859 } else {
860 fbh_ring = fb->fb_header.fbh_mem_ring;
861 fbh_ring_head = &fb->fb_header.fbh_ring_mem_head;
862 }
863
864 #ifdef KERNEL
865 // The algorithm in the kernel is simpler:
866 // 1. reserve a write position for the head
867 // 2. store the new reference at that position
868 // Enqueuers can't starve each other that way.
869 //
870 // However, the dequeuers now have to sometimes wait for the value written
871 // in the ring to appear and have to spin, which is okay since the kernel
872 // disables preemption around these two consecutive atomic operations.
873 // See firehose_client_drain.
874 __firehose_critical_region_enter();
875 head = os_atomic_inc_orig(fbh_ring_head, relaxed);
876 gen = head & FIREHOSE_RING_POS_GEN_MASK;
877 idx = head & FIREHOSE_RING_POS_IDX_MASK;
878
879 while (unlikely(!os_atomic_cmpxchgv(&fbh_ring[idx], gen, gen | ref, &dummy,
880 relaxed))) {
881 // can only ever happen if a recycler is slow, this requires having
882 // enough cores (>5 for I/O e.g.)
883 _dispatch_wait_until(fbh_ring[idx] == gen);
884 }
885 __firehose_critical_region_leave();
886 __firehose_buffer_push_to_logd(fb, for_io);
887 #else
888 // The algorithm is:
889 // 1. read the head position
890 // 2. cmpxchg head.gen with the (head.gen | ref) at head.idx
891 // 3. if it fails wait until either the head cursor moves,
892 // or the cell becomes free
893 //
894 // The most likely stall at (3) is because another enqueuer raced us
895 // and made the cell non empty.
896 //
897 // The alternative is to reserve the enqueue slot with an atomic inc.
898 // Then write the ref into the ring. This would be much simpler as the
899 // generation packing wouldn't be required (though setting the ring cell
900 // would still need a cmpxchg loop to avoid clobbering values of slow
901 // dequeuers)
902 //
903 // But then that means that flushers (logd) could be starved until that
904 // finishes, and logd cannot be held forever (that could even be a logd
905 // DoS from malicious programs). Meaning that logd would stop draining
906 // buffer queues when encountering that issue, leading the program to be
907 // stuck in firehose_client_push() apparently waiting on logd, while
908 // really it's waiting on itself. It's better for the scheduler if we
909 // make it clear that we're waiting on ourselves!
910
911 head = os_atomic_load(fbh_ring_head, relaxed);
912 for (;;) {
913 gen = head & FIREHOSE_RING_POS_GEN_MASK;
914 idx = head & FIREHOSE_RING_POS_IDX_MASK;
915
916 // a thread being preempted here for GEN_MASK worth of ring rotations,
917 // it could lead to the cmpxchg succeed, and have a bogus enqueue
918 // (confused enqueuer)
919 if (fastpath(os_atomic_cmpxchgv(&fbh_ring[idx], gen, gen | ref, &dummy,
920 relaxed))) {
921 if (fastpath(os_atomic_cmpxchgv(fbh_ring_head, head, head + 1,
922 &head, release))) {
923 __firehose_critical_region_leave();
924 break;
925 }
926 // this thread is a confused enqueuer, need to undo enqueue
927 os_atomic_store(&fbh_ring[idx], gen, relaxed);
928 continue;
929 }
930
931 _dispatch_wait_until(({
932 // wait until either the head moves (another enqueuer is done)
933 // or (not very likely) a recycler is very slow
934 // or (very unlikely) the confused thread undoes its enqueue
935 uint16_t old_head = head;
936 head = *fbh_ring_head;
937 head != old_head || fbh_ring[idx] == gen;
938 }));
939 }
940
941 pthread_priority_t pp = fc_pos.fcp_qos;
942 pp <<= _PTHREAD_PRIORITY_QOS_CLASS_SHIFT;
943 firehose_client_send_push_async(fb, _pthread_qos_class_decode(pp, NULL, NULL),
944 for_io);
945 #endif
946 }
947
948 #ifndef KERNEL
949 void
950 firehose_buffer_force_connect(firehose_buffer_t fb)
951 {
952 mach_port_t sendp = fb->fb_header.fbh_sendp;
953 if (sendp == MACH_PORT_NULL) firehose_client_reconnect(fb, MACH_PORT_NULL);
954 }
955 #endif
956
957 OS_ALWAYS_INLINE
958 static inline uint16_t
959 firehose_buffer_ring_try_recycle(firehose_buffer_t fb)
960 {
961 firehose_ring_tail_u pos, old;
962 uint16_t volatile *fbh_ring;
963 uint16_t gen, ref, entry, tail;
964 firehose_chunk_t fc;
965 bool for_io;
966
967 os_atomic_rmw_loop2o(&fb->fb_header, fbh_ring_tail.frp_atomic_tail,
968 old.frp_atomic_tail, pos.frp_atomic_tail, relaxed, {
969 pos = old;
970 if (fastpath(old.frp_mem_tail != old.frp_mem_flushed)) {
971 pos.frp_mem_tail++;
972 } else if (fastpath(old.frp_io_tail != old.frp_io_flushed)) {
973 pos.frp_io_tail++;
974 } else {
975 os_atomic_rmw_loop_give_up(return 0);
976 }
977 });
978
979 // there's virtually no chance that the lack of acquire barrier above
980 // lets us read a value from the ring so stale that it's still an Empty
981 // marker. For correctness purposes have a cheap loop that should never
982 // really loop, instead of an acquire barrier in the cmpxchg above.
983 for_io = (pos.frp_io_tail != old.frp_io_tail);
984 if (for_io) {
985 fbh_ring = fb->fb_header.fbh_io_ring;
986 tail = old.frp_io_tail & FIREHOSE_RING_POS_IDX_MASK;
987 } else {
988 fbh_ring = fb->fb_header.fbh_mem_ring;
989 tail = old.frp_mem_tail & FIREHOSE_RING_POS_IDX_MASK;
990 }
991 _dispatch_wait_until((entry = fbh_ring[tail]) & FIREHOSE_RING_POS_IDX_MASK);
992
993 // Needed for process death handling (recycle-dequeue):
994 // No atomic fences required, we merely want to make sure the observers
995 // will see memory effects in program (asm) order.
996 // 1. the chunk is marked as "void&full" (clobbering the pos with FULL_BIT)
997 // 2. then we remove any reference to the chunk from the ring
998 // This ensures that if we don't see a reference to a chunk in the ring
999 // and it is dirty, it is a chunk being written to that needs a flush
1000 gen = (entry & FIREHOSE_RING_POS_GEN_MASK) + FIREHOSE_RING_POS_GEN_INC;
1001 ref = entry & FIREHOSE_RING_POS_IDX_MASK;
1002 fc = firehose_buffer_ref_to_chunk(fb, ref);
1003
1004 if (!for_io && fc->fc_pos.fcp_stream == firehose_stream_metadata) {
1005 os_atomic_and2o(fb, fb_header.fbh_bank.fbb_metadata_bitmap,
1006 ~(1ULL << ref), relaxed);
1007 }
1008 os_atomic_store2o(fc, fc_pos.fcp_atomic_pos,
1009 FIREHOSE_CHUNK_POS_FULL_BIT, relaxed);
1010 dispatch_compiler_barrier();
1011 os_atomic_store(&fbh_ring[tail], gen | 0, relaxed);
1012 return ref;
1013 }
1014
1015 #ifndef KERNEL
1016 OS_NOINLINE
1017 static firehose_tracepoint_t
1018 firehose_buffer_tracepoint_reserve_wait_for_chunks_from_logd(firehose_buffer_t fb,
1019 firehose_tracepoint_query_t ask, uint8_t **privptr, uint16_t ref)
1020 {
1021 const uint64_t bank_unavail_mask = FIREHOSE_BANK_UNAVAIL_MASK(ask->for_io);
1022 const uint64_t bank_inc = FIREHOSE_BANK_INC(ask->for_io);
1023 firehose_buffer_bank_t const fbb = &fb->fb_header.fbh_bank;
1024 firehose_bank_state_u state;
1025 uint16_t fbs_max_ref;
1026
1027 // first wait for our bank to have space, if needed
1028 if (!fastpath(ask->is_bank_ok)) {
1029 state.fbs_atomic_state =
1030 os_atomic_load2o(fbb, fbb_state.fbs_atomic_state, relaxed);
1031 while ((state.fbs_atomic_state - bank_inc) & bank_unavail_mask) {
1032 if (ask->quarantined) {
1033 __FIREHOSE_CLIENT_THROTTLED_DUE_TO_HEAVY_LOGGING__(fb,
1034 ask->for_io, &state);
1035 } else {
1036 firehose_client_send_push_and_wait(fb, ask->for_io, &state);
1037 }
1038 if (slowpath(fb->fb_header.fbh_sendp == MACH_PORT_DEAD)) {
1039 // logd was unloaded, give up
1040 return NULL;
1041 }
1042 }
1043 ask->is_bank_ok = true;
1044 fbs_max_ref = state.fbs_max_ref;
1045 } else {
1046 fbs_max_ref = fbb->fbb_state.fbs_max_ref;
1047 }
1048
1049 // second, if we were passed a chunk, we may need to shrink
1050 if (slowpath(ref)) {
1051 goto try_shrink;
1052 }
1053
1054 // third, wait for a chunk to come up, and if not, wait on the daemon
1055 for (;;) {
1056 if (fastpath(ref = firehose_buffer_ring_try_recycle(fb))) {
1057 try_shrink:
1058 if (slowpath(ref >= fbs_max_ref)) {
1059 ref = firehose_buffer_ring_shrink(fb, ref);
1060 if (!ref) {
1061 continue;
1062 }
1063 }
1064 break;
1065 }
1066 if (fastpath(ref = firehose_buffer_ring_try_grow(fbb, fbs_max_ref))) {
1067 break;
1068 }
1069 if (ask->quarantined) {
1070 __FIREHOSE_CLIENT_THROTTLED_DUE_TO_HEAVY_LOGGING__(fb,
1071 ask->for_io, &state);
1072 } else {
1073 firehose_client_send_push_and_wait(fb, ask->for_io, NULL);
1074 }
1075 if (slowpath(fb->fb_header.fbh_sendp == MACH_PORT_DEAD)) {
1076 // logd was unloaded, give up
1077 break;
1078 }
1079 }
1080
1081 return firehose_buffer_stream_chunk_install(fb, ask, privptr, ref);
1082 }
1083 #else
1084 static inline dispatch_lock
1085 _dispatch_gate_lock_load_seq_cst(dispatch_gate_t l)
1086 {
1087 return os_atomic_load(&l->dgl_lock, seq_cst);
1088 }
1089 OS_NOINLINE
1090 static void
1091 _dispatch_gate_wait(dispatch_gate_t l, uint32_t flags)
1092 {
1093 (void)flags;
1094 _dispatch_wait_until(_dispatch_gate_lock_load_seq_cst(l) == 0);
1095 }
1096 #endif // KERNEL
1097
1098 firehose_tracepoint_t
1099 firehose_buffer_tracepoint_reserve_slow(firehose_buffer_t fb,
1100 firehose_tracepoint_query_t ask, uint8_t **privptr)
1101 {
1102 const unsigned for_io = ask->for_io;
1103 const firehose_buffer_bank_t fbb = &fb->fb_header.fbh_bank;
1104 firehose_bank_state_u state;
1105 uint16_t ref = 0;
1106
1107 uint64_t unavail_mask = FIREHOSE_BANK_UNAVAIL_MASK(for_io);
1108 #ifndef KERNEL
1109 state.fbs_atomic_state = os_atomic_add_orig2o(fbb,
1110 fbb_state.fbs_atomic_state, FIREHOSE_BANK_INC(for_io), acquire);
1111 if (fastpath(!(state.fbs_atomic_state & unavail_mask))) {
1112 ask->is_bank_ok = true;
1113 if (fastpath(ref = firehose_buffer_ring_try_recycle(fb))) {
1114 if (fastpath(ref < state.fbs_max_ref)) {
1115 return firehose_buffer_stream_chunk_install(fb, ask,
1116 privptr, ref);
1117 }
1118 }
1119 }
1120 return firehose_buffer_tracepoint_reserve_wait_for_chunks_from_logd(fb, ask,
1121 privptr, ref);
1122 #else
1123 firehose_bank_state_u value;
1124 ask->is_bank_ok = os_atomic_rmw_loop2o(fbb, fbb_state.fbs_atomic_state,
1125 state.fbs_atomic_state, value.fbs_atomic_state, acquire, {
1126 value = state;
1127 if (slowpath((value.fbs_atomic_state & unavail_mask) != 0)) {
1128 os_atomic_rmw_loop_give_up(break);
1129 }
1130 value.fbs_atomic_state += FIREHOSE_BANK_INC(for_io);
1131 });
1132 if (ask->is_bank_ok) {
1133 ref = firehose_buffer_ring_try_recycle(fb);
1134 if (slowpath(ref == 0)) {
1135 // the kernel has no overlap between I/O and memory chunks,
1136 // having an available bank slot means we should be able to recycle
1137 DISPATCH_INTERNAL_CRASH(0, "Unable to recycle a chunk");
1138 }
1139 }
1140 // rdar://25137005 installing `0` unlocks the allocator
1141 return firehose_buffer_stream_chunk_install(fb, ask, privptr, ref);
1142 #endif // KERNEL
1143 }
1144
1145 #ifdef KERNEL
1146 firehose_tracepoint_t
1147 __firehose_buffer_tracepoint_reserve(uint64_t stamp, firehose_stream_t stream,
1148 uint16_t pubsize, uint16_t privsize, uint8_t **privptr)
1149 {
1150 firehose_buffer_t fb = kernel_firehose_buffer;
1151 if (!fastpath(fb)) {
1152 return NULL;
1153 }
1154 return firehose_buffer_tracepoint_reserve(fb, stamp, stream, pubsize,
1155 privsize, privptr);
1156 }
1157
1158 firehose_buffer_t
1159 __firehose_buffer_create(size_t *size)
1160 {
1161 if (!kernel_firehose_buffer) {
1162 kernel_firehose_buffer = firehose_buffer_create(MACH_PORT_NULL, 0, 0);
1163 }
1164
1165 if (size) {
1166 *size = FIREHOSE_BUFFER_KERNEL_CHUNK_COUNT * FIREHOSE_CHUNK_SIZE;
1167 }
1168 return kernel_firehose_buffer;
1169 }
1170
1171 void
1172 __firehose_buffer_tracepoint_flush(firehose_tracepoint_t ft,
1173 firehose_tracepoint_id_u ftid)
1174 {
1175 return firehose_buffer_tracepoint_flush(kernel_firehose_buffer, ft, ftid);
1176 }
1177
1178 void
1179 __firehose_merge_updates(firehose_push_reply_t update)
1180 {
1181 firehose_buffer_t fb = kernel_firehose_buffer;
1182 if (fastpath(fb)) {
1183 firehose_client_merge_updates(fb, true, update, false, NULL);
1184 }
1185 }
1186 #endif // KERNEL
1187
1188 #endif // OS_FIREHOSE_SPI