2 * Copyright (c) 2015 Apple Inc. All rights reserved.
4 * @APPLE_APACHE_LICENSE_HEADER_START@
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
10 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 * @APPLE_APACHE_LICENSE_HEADER_END@
21 #ifndef __FIREHOSE_INLINE_INTERNAL__
22 #define __FIREHOSE_INLINE_INTERNAL__
24 #define firehose_atomic_maxv2o(p, f, v, o, m) \
25 os_atomic_rmw_loop2o(p, f, *(o), (v), m, { \
26 if (*(o) >= (v)) os_atomic_rmw_loop_give_up(break); \
29 #define firehose_atomic_max2o(p, f, v, m) ({ \
30 typeof((p)->f) _old; \
31 firehose_atomic_maxv2o(p, f, v, &_old, m); \
35 // caller must test for non zero first
37 static inline uint16_t
38 firehose_bitmap_first_set(uint64_t bitmap
)
40 dispatch_assert(bitmap
!= 0);
41 // this builtin returns 0 if bitmap is 0, or (first bit set + 1)
42 return (uint16_t)__builtin_ffsll((long long)bitmap
) - 1;
47 #pragma mark Mach Misc.
51 static inline mach_port_t
52 firehose_mach_port_allocate(uint32_t flags
, void *ctx
)
54 mach_port_t port
= MACH_PORT_NULL
;
55 mach_port_options_t opts
= {
61 kr
= mach_port_construct(mach_task_self(), &opts
,
62 (mach_port_context_t
)ctx
, &port
);
63 if (fastpath(kr
== KERN_SUCCESS
)) {
66 DISPATCH_VERIFY_MIG(kr
);
67 dispatch_assume_zero(kr
);
68 _dispatch_temporary_resource_shortage();
74 static inline kern_return_t
75 firehose_mach_port_recv_dispose(mach_port_t port
, void *ctx
)
78 kr
= mach_port_destruct(mach_task_self(), port
, 0,
79 (mach_port_context_t
)ctx
);
80 DISPATCH_VERIFY_MIG(kr
);
86 firehose_mach_port_send_release(mach_port_t port
)
88 kern_return_t kr
= mach_port_deallocate(mach_task_self(), port
);
89 DISPATCH_VERIFY_MIG(kr
);
90 dispatch_assume_zero(kr
);
95 firehose_mach_port_guard(mach_port_t port
, bool strict
, void *ctx
)
97 kern_return_t kr
= mach_port_guard(mach_task_self(), port
,
98 (mach_port_context_t
)ctx
, strict
);
99 DISPATCH_VERIFY_MIG(kr
);
100 dispatch_assume_zero(kr
);
105 firehose_mig_server(dispatch_mig_callback_t demux
, size_t maxmsgsz
,
106 mach_msg_header_t
*hdr
)
108 mig_reply_error_t
*msg_reply
= (mig_reply_error_t
*)alloca(maxmsgsz
);
109 kern_return_t rc
= KERN_SUCCESS
;
110 bool expects_reply
= false;
112 if (MACH_MSGH_BITS_REMOTE(hdr
->msgh_bits
) == MACH_MSG_TYPE_MOVE_SEND_ONCE
) {
113 expects_reply
= true;
116 if (!fastpath(demux(hdr
, &msg_reply
->Head
))) {
118 } else if (msg_reply
->Head
.msgh_bits
& MACH_MSGH_BITS_COMPLEX
) {
121 // if MACH_MSGH_BITS_COMPLEX is _not_ set, then msg_reply->RetCode
123 rc
= msg_reply
->RetCode
;
126 if (slowpath(rc
== KERN_SUCCESS
&& expects_reply
)) {
127 // if crashing here, some handler returned KERN_SUCCESS
128 // hoping for firehose_mig_server to perform the mach_msg()
129 // call to reply, and it doesn't know how to do that
130 DISPATCH_INTERNAL_CRASH(msg_reply
->Head
.msgh_id
,
131 "firehose_mig_server doesn't handle replies");
133 if (slowpath(rc
!= KERN_SUCCESS
&& rc
!= MIG_NO_REPLY
)) {
134 // destroy the request - but not the reply port
135 hdr
->msgh_remote_port
= 0;
136 mach_msg_destroy(hdr
);
142 #pragma mark firehose buffer
145 static inline firehose_buffer_chunk_t
146 firehose_buffer_chunk_for_address(void *addr
)
148 uintptr_t chunk_addr
= (uintptr_t)addr
& ~(FIREHOSE_BUFFER_CHUNK_SIZE
- 1);
149 return (firehose_buffer_chunk_t
)chunk_addr
;
153 static inline uint16_t
154 firehose_buffer_chunk_to_ref(firehose_buffer_t fb
, firehose_buffer_chunk_t fbc
)
156 return (uint16_t)(fbc
- fb
->fb_chunks
);
160 static inline firehose_buffer_chunk_t
161 firehose_buffer_ref_to_chunk(firehose_buffer_t fb
, uint16_t ref
)
163 return fb
->fb_chunks
+ ref
;
166 #ifndef FIREHOSE_SERVER
170 firehose_buffer_pos_fits(firehose_buffer_pos_u pos
, uint16_t size
)
172 return pos
.fbc_next_entry_offs
+ size
<= pos
.fbc_private_offs
;
178 static inline uint8_t
179 firehose_buffer_qos_bits_propagate(void)
182 pthread_priority_t pp
= _dispatch_priority_propagate();
184 pp
&= _PTHREAD_PRIORITY_QOS_CLASS_MASK
;
185 return (uint8_t)(pp
>> _PTHREAD_PRIORITY_QOS_CLASS_SHIFT
);
193 firehose_buffer_chunk_try_reserve(firehose_buffer_chunk_t fbc
, uint64_t stamp
,
194 firehose_stream_t stream
, uint16_t pubsize
,
195 uint16_t privsize
, uint8_t **privptr
)
197 const uint16_t ft_size
= offsetof(struct firehose_tracepoint_s
, ft_data
);
198 firehose_buffer_pos_u orig
, pos
;
199 uint8_t qos_bits
= firehose_buffer_qos_bits_propagate();
200 bool reservation_failed
, stamp_delta_fits
;
202 stamp_delta_fits
= ((stamp
- fbc
->fbc_timestamp
) >> 48) == 0;
204 // no acquire barrier because the returned space is written to only
205 os_atomic_rmw_loop2o(fbc
, fbc_pos
.fbc_atomic_pos
,
206 orig
.fbc_atomic_pos
, pos
.fbc_atomic_pos
, relaxed
, {
207 if (unlikely(orig
.fbc_atomic_pos
== 0)) {
208 // we acquired a really really old reference, and we probably
209 // just faulted in a new page
210 // FIXME: if/when we hit this we should try to madvise it back FREE
211 os_atomic_rmw_loop_give_up(return 0);
213 if (unlikely(!FIREHOSE_BUFFER_POS_USABLE_FOR_STREAM(orig
, stream
))) {
214 // nothing to do if the chunk is full, or the stream doesn't match,
215 // in which case the thread probably:
216 // - loaded the chunk ref
217 // - been suspended a long while
218 // - read the chunk to find a very old thing
219 os_atomic_rmw_loop_give_up(return 0);
222 pos
.fbc_qos_bits
|= qos_bits
;
223 if (unlikely(!firehose_buffer_pos_fits(orig
,
224 ft_size
+ pubsize
+ privsize
) || !stamp_delta_fits
)) {
225 pos
.fbc_flag_full
= true;
226 reservation_failed
= true;
228 // using these *_INC macros is so that the compiler generates better
229 // assembly: using the struct individual fields forces the compiler
230 // to handle carry propagations, and we know it won't happen
231 pos
.fbc_atomic_pos
+= roundup(ft_size
+ pubsize
, 8) *
232 FIREHOSE_BUFFER_POS_ENTRY_OFFS_INC
;
233 pos
.fbc_atomic_pos
-= privsize
*
234 FIREHOSE_BUFFER_POS_PRIVATE_OFFS_INC
;
235 pos
.fbc_atomic_pos
+= FIREHOSE_BUFFER_POS_REFCNT_INC
;
236 const uint16_t minimum_payload_size
= 16;
237 if (!firehose_buffer_pos_fits(pos
,
238 roundup(ft_size
+ minimum_payload_size
, 8))) {
239 // if we can't even have minimum_payload_size bytes of payload
240 // for the next tracepoint, just flush right away
241 pos
.fbc_flag_full
= true;
243 reservation_failed
= false;
247 if (reservation_failed
) {
248 if (pos
.fbc_refcnt
) {
249 // nothing to do, there is a thread writing that will pick up
250 // the "FULL" flag on flush and push as a consequence
253 // caller must enqueue chunk
257 *privptr
= fbc
->fbc_start
+ pos
.fbc_private_offs
;
259 return orig
.fbc_next_entry_offs
;
264 firehose_buffer_stream_flush(firehose_buffer_t fb
, firehose_stream_t stream
)
266 firehose_buffer_stream_t fbs
= &fb
->fb_header
.fbh_stream
[stream
];
267 firehose_stream_state_u old_state
, new_state
;
268 firehose_buffer_chunk_t fbc
;
269 uint64_t stamp
= UINT64_MAX
; // will cause the reservation to fail
273 old_state
.fss_atomic_state
=
274 os_atomic_load2o(fbs
, fbs_state
.fss_atomic_state
, relaxed
);
275 ref
= old_state
.fss_current
;
276 if (!ref
|| ref
== FIREHOSE_STREAM_STATE_PRISTINE
) {
277 // there is no installed page, nothing to flush, go away
281 fbc
= firehose_buffer_ref_to_chunk(fb
, old_state
.fss_current
);
282 result
= firehose_buffer_chunk_try_reserve(fbc
, stamp
, stream
, 1, 0, NULL
);
283 if (likely(result
< 0)) {
284 firehose_buffer_ring_enqueue(fb
, old_state
.fss_current
);
286 if (unlikely(result
> 0)) {
287 // because we pass a silly stamp that requires a flush
288 DISPATCH_INTERNAL_CRASH(result
, "Allocation should always fail");
291 // as a best effort try to uninstall the page we just flushed
292 // but failing is okay, let's not contend stupidly for something
293 // allocators know how to handle in the first place
294 new_state
= old_state
;
295 new_state
.fss_current
= 0;
296 (void)os_atomic_cmpxchg2o(fbs
, fbs_state
.fss_atomic_state
,
297 old_state
.fss_atomic_state
, new_state
.fss_atomic_state
, relaxed
);
301 * @function firehose_buffer_tracepoint_reserve
304 * Reserves space in the firehose buffer for the tracepoint with specified
308 * This returns a slot, with the length of the tracepoint already set, so
309 * that in case of a crash, we maximize our chance to be able to skip the
310 * tracepoint in case of a partial write.
312 * Once the tracepoint has been written, firehose_buffer_tracepoint_flush()
316 * The buffer to allocate from.
319 * The buffer stream to use.
322 * The size of the public data for this tracepoint, cannot be 0, doesn't
323 * take the size of the tracepoint header into account.
326 * The size of the private data for this tracepoint, can be 0.
329 * The pointer to the private buffer, can be NULL
332 * The pointer to the tracepoint.
335 static inline firehose_tracepoint_t
336 firehose_buffer_tracepoint_reserve(firehose_buffer_t fb
, uint64_t stamp
,
337 firehose_stream_t stream
, uint16_t pubsize
,
338 uint16_t privsize
, uint8_t **privptr
)
340 firehose_buffer_stream_t fbs
= &fb
->fb_header
.fbh_stream
[stream
];
341 firehose_stream_state_u old_state
, new_state
;
342 firehose_tracepoint_t ft
;
343 firehose_buffer_chunk_t fbc
;
345 bool failable
= false;
351 // cannot use os_atomic_rmw_loop2o, _page_try_reserve does a store
352 old_state
.fss_atomic_state
=
353 os_atomic_load2o(fbs
, fbs_state
.fss_atomic_state
, relaxed
);
355 new_state
= old_state
;
357 ref
= old_state
.fss_current
;
358 if (likely(ref
&& ref
!= FIREHOSE_STREAM_STATE_PRISTINE
)) {
359 fbc
= firehose_buffer_ref_to_chunk(fb
, ref
);
360 result
= firehose_buffer_chunk_try_reserve(fbc
, stamp
, stream
,
361 pubsize
, privsize
, privptr
);
362 if (likely(result
> 0)) {
363 ft
= (firehose_tracepoint_t
)(fbc
->fbc_start
+ result
);
364 stamp
-= fbc
->fbc_timestamp
;
365 stamp
|= (uint64_t)pubsize
<< 48;
366 // Needed for process death handling (tracepoint-begin)
367 // see firehose_buffer_stream_chunk_install
368 os_atomic_store2o(ft
, ft_stamp_and_length
, stamp
, relaxed
);
369 dispatch_compiler_barrier();
372 if (likely(result
< 0)) {
373 firehose_buffer_ring_enqueue(fb
, old_state
.fss_current
);
375 new_state
.fss_current
= 0;
383 if (unlikely(old_state
.fss_allocator
)) {
384 _dispatch_gate_wait(&fbs
->fbs_state
.fss_gate
,
385 DLOCK_LOCK_DATA_CONTENTION
);
386 old_state
.fss_atomic_state
=
387 os_atomic_load2o(fbs
, fbs_state
.fss_atomic_state
, relaxed
);
394 // if the thread doing the allocation is a low priority one
395 // we may starve high priority ones.
396 // so disable preemption before we become an allocator
397 // the reenabling of the preemption is in
398 // firehose_buffer_stream_chunk_install
399 __firehose_critical_region_enter();
401 new_state
.fss_allocator
= (uint32_t)cpu_number();
403 new_state
.fss_allocator
= _dispatch_tid_self();
405 success
= os_atomic_cmpxchgvw2o(fbs
, fbs_state
.fss_atomic_state
,
406 old_state
.fss_atomic_state
, new_state
.fss_atomic_state
,
407 &old_state
.fss_atomic_state
, relaxed
);
408 if (likely(success
)) {
411 __firehose_critical_region_leave();
414 struct firehose_tracepoint_query_s ask
= {
416 .privsize
= privsize
,
418 .for_io
= (firehose_stream_uses_io_bank
& (1UL << stream
)) != 0,
421 return firehose_buffer_tracepoint_reserve_slow(fb
, &ask
, privptr
);
425 * @function firehose_buffer_tracepoint_flush
428 * Flushes a firehose tracepoint, and sends the chunk to the daemon when full
429 * and this was the last tracepoint writer for this chunk.
432 * The buffer the tracepoint belongs to.
435 * The tracepoint to flush.
438 * The firehose tracepoint ID for that tracepoint.
439 * It is written last, preventing compiler reordering, so that its absence
440 * on crash recovery means the tracepoint is partial.
444 firehose_buffer_tracepoint_flush(firehose_buffer_t fb
,
445 firehose_tracepoint_t ft
, firehose_tracepoint_id_u ftid
)
447 firehose_buffer_chunk_t fbc
= firehose_buffer_chunk_for_address(ft
);
448 firehose_buffer_pos_u pos
;
450 // Needed for process death handling (tracepoint-flush):
451 // We want to make sure the observers
452 // will see memory effects in program (asm) order.
453 // 1. write all the data to the tracepoint
454 // 2. write the tracepoint ID, so that seeing it means the tracepoint
457 ft
->ft_thread
= thread_tid(current_thread());
459 ft
->ft_thread
= _pthread_threadid_self_np_direct();
461 // release barrier makes the log writes visible
462 os_atomic_store2o(ft
, ft_id
.ftid_value
, ftid
.ftid_value
, release
);
463 pos
.fbc_atomic_pos
= os_atomic_sub2o(fbc
, fbc_pos
.fbc_atomic_pos
,
464 FIREHOSE_BUFFER_POS_REFCNT_INC
, relaxed
);
465 if (pos
.fbc_refcnt
== 0 && pos
.fbc_flag_full
) {
466 firehose_buffer_ring_enqueue(fb
, firehose_buffer_chunk_to_ref(fb
, fbc
));
473 firehose_buffer_clear_bank_flags(firehose_buffer_t fb
, unsigned long bits
)
475 firehose_buffer_bank_t fbb
= &fb
->fb_header
.fbh_bank
;
476 unsigned long orig_flags
;
478 orig_flags
= os_atomic_and_orig2o(fbb
, fbb_flags
, ~bits
, relaxed
);
479 if (orig_flags
!= (orig_flags
& ~bits
)) {
480 firehose_buffer_update_limits(fb
);
486 firehose_buffer_set_bank_flags(firehose_buffer_t fb
, unsigned long bits
)
488 firehose_buffer_bank_t fbb
= &fb
->fb_header
.fbh_bank
;
489 unsigned long orig_flags
;
491 orig_flags
= os_atomic_or_orig2o(fbb
, fbb_flags
, bits
, relaxed
);
492 if (orig_flags
!= (orig_flags
| bits
)) {
493 firehose_buffer_update_limits(fb
);
498 #endif // !defined(FIREHOSE_SERVER)
500 #endif // DISPATCH_PURE_C
502 #endif // __FIREHOSE_INLINE_INTERNAL__