xref: /xnu-12377.41.6/libkern/os/log_queue.c (revision bbb1b6f9e71b8cdde6e5cd6f4841f207dee3d828)
1 /*
2  * Copyright (c) 2020-2021 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  *           log_queue_failed_intr);
21  *
22  * @APPLE_LICENSE_HEADER_END@
23  */
24 
25 #include <kern/assert.h>
26 #include <kern/counter.h>
27 #include <kern/cpu_data.h>
28 #include <kern/percpu.h>
29 #include <kern/kalloc.h>
30 #include <kern/thread_call.h>
31 #include <libkern/libkern.h>
32 #include <sys/queue.h>
33 #include <vm/vm_kern.h>
34 #include <sys/sysctl.h>
35 
36 #include "log_queue.h"
37 #include "log_mem.h"
38 
39 #define LQ_DEFAULT_SZ_ORDER 15 // 32K per slot
40 #define LQ_DEFAULT_FREE_AFTER_CNT 15000 // Deallocate log queue after N logs
41 #define LQ_MAX_SZ_ORDER 20 // 1MB per CPU should really be enough and a hard cap
42 #define LQ_MIN_LOG_SZ_ORDER 5
43 #define LQ_MAX_LOG_SZ_ORDER 11
44 #define LQ_BATCH_SIZE 24
45 #define LQ_MAX_LM_SLOTS 9
46 #define LQ_LOW_MEM_SCALE 3
47 #define LQ_MIN_ALLOCATED_LM_SLOTS 2
48 #define LQ_METADATA_START_SLOT 0
49 #define LQ_OTHER_START_SLOT (LQ_MIN_ALLOCATED_LM_SLOTS - 1)
50 
51 #define LQ_MEM_ENABLE(q, i) ((q)->lq_mem_set |= (1 << (i)))
52 #define LQ_MEM_ENABLED(q, i) ((q)->lq_mem_set & (1 << (i)))
53 #define LQ_MEM_DISABLE(q, i) ((q)->lq_mem_set &= ~(1 << (i)))
54 
55 OS_ENUM(log_queue_entry_state, uint8_t,
56     LOG_QUEUE_ENTRY_STATE_INVALID = 0,
57     LOG_QUEUE_ENTRY_STATE_STORED,
58     LOG_QUEUE_ENTRY_STATE_DISPATCHED,
59     LOG_QUEUE_ENTRY_STATE_SENT,
60     LOG_QUEUE_ENTRY_STATE_FAILED
61     );
62 
63 OS_ENUM(lq_mem_state, uint8_t,
64     LQ_MEM_STATE_READY = 0,
65     LQ_MEM_STATE_ALLOCATING,
66     LQ_MEM_STATE_RELEASING
67     );
68 
69 OS_ENUM(lq_req_state, uint8_t,
70     LQ_REQ_STATE_INVALID = 0,
71     LQ_REQ_STATE_ALLOCATING,
72     LQ_REQ_STATE_RELEASING,
73     LQ_REQ_STATE_READY
74     );
75 
76 typedef struct log_queue_entry {
77 	STAILQ_ENTRY(log_queue_entry)   lqe_link;
78 	uint16_t                        lqe_size;
79 	uint16_t                        lqe_lm_id;
80 	_Atomic log_queue_entry_state_t lqe_state;
81 	log_payload_s                   lqe_payload;
82 } log_queue_entry_s, *log_queue_entry_t;
83 
84 typedef STAILQ_HEAD(, log_queue_entry) log_queue_list_s, *log_queue_list_t;
85 
86 typedef struct {
87 	log_queue_list_s        lq_log_list;
88 	log_queue_list_s        lq_dispatch_list;
89 	logmem_t                lq_mem[LQ_MAX_LM_SLOTS];
90 	size_t                  lq_mem_set;
91 	size_t                  lq_mem_size;
92 	size_t                  lq_mem_size_order;
93 	lq_mem_state_t          lq_mem_state;
94 	thread_call_t           lq_mem_handler;
95 	size_t                  lq_cnt_mem_active;
96 	size_t                  lq_cnt_mem_avail;
97 	size_t                  lq_cnt_mem_max;
98 	size_t                  lq_cnt_mem_meta_avail;
99 	_Atomic lq_req_state_t  lq_req_state;
100 	void                    *lq_req_mem;
101 	uint32_t                lq_ready : 1;
102 	uint32_t                lq_suspend : 1;
103 } log_queue_s, *log_queue_t;
104 
105 extern bool os_log_disabled(void);
106 
107 /*
108  * Log Queue
109  *
110  * Log queues are allocated and set up per cpu. When a firehose memory is full
111  * logs are stored in a log queue and sent into the firehose once it has a free
112  * space again. Each log queue (memory) can grow and shrink based on demand by
113  * adding/removing additional memory to/from its memory slots. There are
114  * LQ_MAX_LM_SLOTS memory slots available for every log queue to use. Memory
115  * slots are released when not needed, with one slot always allocated per queue
116  * as a minimum.
117  *
118  * Boot args:
119  *
120  * lq_size_order: Per slot memory size defined as a power of 2 exponent
121  *                (i.e. 2^lq_bootarg_size_order). Zero disables queues.
122  *
123  * lq_nslots: Number of allocated slots to boot with per each log queue.
124  *            Once initial log traffic decreases, log queues release
125  *            slots as needed.
126  *
127  * If extensive number of logs is expected, setting aforementioned boot-args as
128  * needed allows to capture the vast majority of logs and avoid drops.
129  */
130 TUNABLE(size_t, lq_bootarg_size_order, "lq_size_order", LQ_DEFAULT_SZ_ORDER);
131 TUNABLE(size_t, lq_bootarg_nslots, "lq_nslots", LQ_MAX_LM_SLOTS);
132 
133 atomic_size_t lq_max_slots = LQ_MAX_LM_SLOTS;
134 #if DEVELOPMENT || DEBUG
135 SYSCTL_UINT(_debug, OID_AUTO, log_queue_max_slots, CTLFLAG_RW, (unsigned int *)&lq_max_slots, 0, "");
136 #endif
137 
138 SCALABLE_COUNTER_DEFINE(log_queue_cnt_received);
139 SCALABLE_COUNTER_DEFINE(log_queue_cnt_rejected_fh);
140 SCALABLE_COUNTER_DEFINE(log_queue_cnt_queued);
141 SCALABLE_COUNTER_DEFINE(log_queue_cnt_sent);
142 SCALABLE_COUNTER_DEFINE(log_queue_cnt_dropped_nomem);
143 SCALABLE_COUNTER_DEFINE(log_queue_cnt_dropped_off);
144 SCALABLE_COUNTER_DEFINE(log_queue_cnt_mem_allocated);
145 SCALABLE_COUNTER_DEFINE(log_queue_cnt_mem_released);
146 SCALABLE_COUNTER_DEFINE(log_queue_cnt_mem_failed);
147 
148 static log_queue_s PERCPU_DATA(oslog_queue);
149 static size_t lq_low_mem_limit;
150 
151 static void *
log_queue_buffer_alloc(size_t amount)152 log_queue_buffer_alloc(size_t amount)
153 {
154 	return kalloc_data_tag(amount, Z_WAITOK_ZERO, VM_KERN_MEMORY_LOG);
155 }
156 
157 static void
log_queue_buffer_free(void * addr,size_t amount)158 log_queue_buffer_free(void *addr, size_t amount)
159 {
160 	kfree_data(addr, amount);
161 }
162 
163 static void
log_queue_increment_mem_avail(const log_queue_t lq,size_t idx,size_t size)164 log_queue_increment_mem_avail(const log_queue_t lq, size_t idx, size_t size)
165 {
166 	lq->lq_cnt_mem_avail += size;
167 	if (idx < LQ_OTHER_START_SLOT) {
168 		lq->lq_cnt_mem_meta_avail += size;
169 	}
170 }
171 
172 static void
log_queue_decrement_mem_avail(const log_queue_t lq,size_t idx,size_t size)173 log_queue_decrement_mem_avail(const log_queue_t lq, size_t idx, size_t size)
174 {
175 	lq->lq_cnt_mem_avail -= size;
176 	if (idx < LQ_OTHER_START_SLOT) {
177 		lq->lq_cnt_mem_meta_avail -= size;
178 	}
179 }
180 
181 #define log_queue_entry_size(p) (sizeof(log_queue_entry_s) + (p)->lp_data_size)
182 
183 #define publish(a, v) os_atomic_store((a), (v), release)
184 #define read_dependency(v) os_atomic_load((v), dependency)
185 #define read_dependent(v, t) os_atomic_load_with_dependency_on((v), (uintptr_t)(t))
186 #define read_dependent_w(v, t) ({ \
187 	__auto_type _v = os_atomic_inject_dependency((v), (uintptr_t)(t)); \
188 	os_atomic_load_wide(_v, dependency); \
189 })
190 
191 static log_queue_entry_state_t
log_queue_entry_state(const log_queue_entry_t lqe)192 log_queue_entry_state(const log_queue_entry_t lqe)
193 {
194 	log_queue_entry_state_t state = read_dependency(&lqe->lqe_state);
195 	assert(state != LOG_QUEUE_ENTRY_STATE_INVALID);
196 	return state;
197 }
198 
199 static log_queue_entry_t
log_queue_entry_alloc(log_queue_t lq,size_t lqe_size,firehose_stream_t stream_type)200 log_queue_entry_alloc(log_queue_t lq, size_t lqe_size, firehose_stream_t stream_type)
201 {
202 	// some slots are exclusively reserved for metadata stream
203 	short start = LQ_METADATA_START_SLOT;
204 	if (stream_type != firehose_stream_metadata) {
205 		start = LQ_OTHER_START_SLOT;
206 	}
207 
208 	for (short i = start; i < LQ_MAX_LM_SLOTS; i++) {
209 		if (!LQ_MEM_ENABLED(lq, i)) {
210 			continue;
211 		}
212 		log_queue_entry_t lqe = logmem_alloc(&lq->lq_mem[i], &lqe_size);
213 		if (lqe) {
214 			assert(lqe_size <= lq->lq_cnt_mem_avail);
215 			assert(lqe_size <= UINT16_MAX);
216 			log_queue_decrement_mem_avail(lq, i, lqe_size);
217 			lqe->lqe_size = (uint16_t)lqe_size;
218 			lqe->lqe_lm_id = i;
219 			return lqe;
220 		}
221 	}
222 
223 	return NULL;
224 }
225 
226 static void
log_queue_entry_free(log_queue_t lq,log_queue_entry_t lqe)227 log_queue_entry_free(log_queue_t lq, log_queue_entry_t lqe)
228 {
229 	const size_t lqe_size = lqe->lqe_size;
230 	const uint16_t lqe_lm_id = lqe->lqe_lm_id;
231 
232 	bzero(lqe, lqe_size);
233 	logmem_free(&lq->lq_mem[lqe_lm_id], lqe, lqe_size);
234 	log_queue_increment_mem_avail(lq, lqe_lm_id, lqe_size);
235 }
236 
237 static bool
log_queue_add_entry(log_queue_t lq,log_payload_t lp,const uint8_t * lp_data)238 log_queue_add_entry(log_queue_t lq, log_payload_t lp, const uint8_t *lp_data)
239 {
240 	log_queue_entry_t lqe = log_queue_entry_alloc(lq, log_queue_entry_size(lp), lp->lp_stream);
241 	if (!lqe) {
242 		counter_inc_preemption_disabled(&log_queue_cnt_dropped_nomem);
243 		return false;
244 	}
245 	assert(lqe->lqe_size >= lp->lp_data_size);
246 
247 	lqe->lqe_payload = *lp;
248 	(void) memcpy((uint8_t *)lqe + sizeof(*lqe), lp_data, lqe->lqe_payload.lp_data_size);
249 	STAILQ_INSERT_TAIL(&lq->lq_log_list, lqe, lqe_link);
250 	publish(&lqe->lqe_state, LOG_QUEUE_ENTRY_STATE_STORED);
251 
252 	counter_inc_preemption_disabled(&log_queue_cnt_queued);
253 
254 	return true;
255 }
256 
257 /*
258  * Remove successfully sent logs from a dispatch list and free them.
259  */
260 static size_t
dispatch_list_cleanup(log_queue_t lq)261 dispatch_list_cleanup(log_queue_t lq)
262 {
263 	log_queue_entry_t lqe, lqe_tmp;
264 	size_t freed = 0;
265 
266 	STAILQ_FOREACH_SAFE(lqe, &lq->lq_dispatch_list, lqe_link, lqe_tmp) {
267 		log_queue_entry_state_t lqe_state = log_queue_entry_state(lqe);
268 		assert(lqe_state != LOG_QUEUE_ENTRY_STATE_STORED);
269 
270 		if (lqe_state == LOG_QUEUE_ENTRY_STATE_SENT) {
271 			STAILQ_REMOVE(&lq->lq_dispatch_list, lqe, log_queue_entry, lqe_link);
272 			publish(&lqe->lqe_state, LOG_QUEUE_ENTRY_STATE_INVALID);
273 			log_queue_entry_free(lq, lqe);
274 			counter_dec_preemption_disabled(&log_queue_cnt_queued);
275 			freed++;
276 		}
277 	}
278 
279 	return freed;
280 }
281 
282 /*
283  * Walk and collect logs stored in the log queue suitable for dispatching.
284  * First, collect previously failed logs, then (if still enough space) grab new
285  * logs.
286  */
287 static size_t
log_dispatch_prepare(log_queue_t lq,size_t requested,log_queue_entry_t * buf)288 log_dispatch_prepare(log_queue_t lq, size_t requested, log_queue_entry_t *buf)
289 {
290 	log_queue_entry_t lqe, lqe_tmp;
291 	size_t collected = 0;
292 
293 	STAILQ_FOREACH(lqe, &lq->lq_dispatch_list, lqe_link) {
294 		log_queue_entry_state_t lqe_state = log_queue_entry_state(lqe);
295 		assert(lqe_state != LOG_QUEUE_ENTRY_STATE_STORED);
296 
297 		if (lqe_state == LOG_QUEUE_ENTRY_STATE_FAILED) {
298 			publish(&lqe->lqe_state, LOG_QUEUE_ENTRY_STATE_DISPATCHED);
299 			buf[collected++] = lqe;
300 		}
301 
302 		if (collected == requested) {
303 			return collected;
304 		}
305 	}
306 	assert(collected < requested);
307 
308 	STAILQ_FOREACH_SAFE(lqe, &lq->lq_log_list, lqe_link, lqe_tmp) {
309 		assert(log_queue_entry_state(lqe) == LOG_QUEUE_ENTRY_STATE_STORED);
310 
311 		STAILQ_REMOVE(&lq->lq_log_list, lqe, log_queue_entry, lqe_link);
312 		STAILQ_INSERT_TAIL(&lq->lq_dispatch_list, lqe, lqe_link);
313 		publish(&lqe->lqe_state, LOG_QUEUE_ENTRY_STATE_DISPATCHED);
314 
315 		buf[collected++] = lqe;
316 		if (collected == requested) {
317 			break;
318 		}
319 	}
320 
321 	return collected;
322 }
323 
324 /*
325  * Send dispatched logs to the firehose. Skip streaming when replaying.
326  * Streaming does not process timestamps and would therefore show logs out of
327  * order.
328  */
329 static void
log_queue_dispatch_logs(size_t logs_count,log_queue_entry_t * logs)330 log_queue_dispatch_logs(size_t logs_count, log_queue_entry_t *logs)
331 {
332 	for (size_t i = 0; i < logs_count; i++) {
333 		const log_queue_entry_t lqe = logs[i];
334 		log_queue_entry_state_t lqe_state = log_queue_entry_state(lqe);
335 
336 		if (lqe_state == LOG_QUEUE_ENTRY_STATE_DISPATCHED) {
337 			const log_payload_t lqe_lp = &lqe->lqe_payload;
338 
339 			log_payload_s lp = {
340 				.lp_ftid = read_dependent_w(&lqe_lp->lp_ftid, lqe_state),
341 				.lp_timestamp = read_dependent_w(&lqe_lp->lp_timestamp, lqe_state),
342 				.lp_stream = read_dependent(&lqe_lp->lp_stream, lqe_state),
343 				.lp_pub_data_size = read_dependent(&lqe_lp->lp_pub_data_size, lqe_state),
344 				.lp_data_size = read_dependent(&lqe_lp->lp_data_size, lqe_state)
345 			};
346 			const void *lp_data = (uint8_t *)lqe + sizeof(*lqe);
347 
348 			/*
349 			 * The log queue mechanism expects only the state to be
350 			 * modified here since we are likely running on a
351 			 * different cpu. Queue cleanup will be done safely
352 			 * later in dispatch_list_cleanup().
353 			 */
354 			if (log_payload_send(&lp, lp_data, false)) {
355 				publish(&lqe->lqe_state, LOG_QUEUE_ENTRY_STATE_SENT);
356 				counter_inc(&log_queue_cnt_sent);
357 			} else {
358 				publish(&lqe->lqe_state, LOG_QUEUE_ENTRY_STATE_FAILED);
359 			}
360 		}
361 	}
362 }
363 
364 static bool
log_queue_empty(const log_queue_t lq)365 log_queue_empty(const log_queue_t lq)
366 {
367 	return STAILQ_EMPTY(&lq->lq_log_list) && STAILQ_EMPTY(&lq->lq_dispatch_list);
368 }
369 
370 static boolean_t
log_queue_low_mem(const log_queue_t lq)371 log_queue_low_mem(const log_queue_t lq)
372 {
373 	size_t mem_avail = lq->lq_cnt_mem_avail - lq->lq_cnt_mem_meta_avail;
374 	size_t low_mem_threshold = (lq->lq_cnt_mem_active - LQ_OTHER_START_SLOT) * lq_low_mem_limit;
375 	return mem_avail < low_mem_threshold;
376 }
377 
378 static lq_req_state_t
log_queue_request_state(log_queue_t lq)379 log_queue_request_state(log_queue_t lq)
380 {
381 	lq_req_state_t req_state = read_dependency(&lq->lq_req_state);
382 	return req_state;
383 }
384 
385 static void
log_queue_mem_init(log_queue_t lq,size_t idx,void * buf,size_t buflen)386 log_queue_mem_init(log_queue_t lq, size_t idx, void *buf, size_t buflen)
387 {
388 	assert(buf);
389 	assert(buflen > 0);
390 	assert(idx < LQ_MAX_LM_SLOTS);
391 	assert(!LQ_MEM_ENABLED(lq, idx));
392 
393 	logmem_init(&lq->lq_mem[idx], buf, buflen, lq->lq_mem_size_order,
394 	    LQ_MIN_LOG_SZ_ORDER, LQ_MAX_LOG_SZ_ORDER);
395 }
396 
397 void
log_queue_set_max_slots(size_t max_slots)398 log_queue_set_max_slots(size_t max_slots)
399 {
400 	max_slots = MAX(max_slots, LQ_MIN_ALLOCATED_LM_SLOTS);
401 	assert(max_slots <= LQ_MAX_LM_SLOTS);
402 	assert(max_slots >= LQ_MIN_ALLOCATED_LM_SLOTS);
403 	atomic_store_explicit((atomic_size_t *)&lq_max_slots, max_slots, memory_order_relaxed);
404 }
405 
406 static size_t
log_queue_mem_max_slots(void)407 log_queue_mem_max_slots(void)
408 {
409 	size_t max_slots = atomic_load_explicit((atomic_size_t *)&lq_max_slots, memory_order_relaxed);
410 	max_slots = MAX(max_slots, LQ_MIN_ALLOCATED_LM_SLOTS);
411 	assert(max_slots <= LQ_MAX_LM_SLOTS);
412 	assert(max_slots >= LQ_MIN_ALLOCATED_LM_SLOTS);
413 	return max_slots;
414 }
415 
416 static int
log_queue_mem_free_slot(log_queue_t lq)417 log_queue_mem_free_slot(log_queue_t lq)
418 {
419 	assert(LQ_MEM_ENABLED(lq, 0));
420 	assert(lq->lq_cnt_mem_max <= LQ_MAX_LM_SLOTS);
421 
422 	for (int i = LQ_MIN_ALLOCATED_LM_SLOTS; i < lq->lq_cnt_mem_max; i++) {
423 		if (!LQ_MEM_ENABLED(lq, i)) {
424 			return i;
425 		}
426 	}
427 	return -1;
428 }
429 
430 static void
log_queue_memory_handler(thread_call_param_t a0,__unused thread_call_param_t a1)431 log_queue_memory_handler(thread_call_param_t a0, __unused thread_call_param_t a1)
432 {
433 	log_queue_t lq = (log_queue_t)a0;
434 	lq_req_state_t req_state = log_queue_request_state(lq);
435 
436 	assert(req_state != LQ_REQ_STATE_INVALID);
437 
438 	if (req_state == LQ_REQ_STATE_ALLOCATING) {
439 		lq->lq_req_mem = log_queue_buffer_alloc(lq->lq_mem_size);
440 		publish(&lq->lq_req_state, LQ_REQ_STATE_READY);
441 
442 		if (lq->lq_req_mem) {
443 			counter_inc(&log_queue_cnt_mem_allocated);
444 		} else {
445 			counter_inc(&log_queue_cnt_mem_failed);
446 		}
447 	} else if (req_state == LQ_REQ_STATE_RELEASING) {
448 		void *buf = read_dependent(&lq->lq_req_mem, req_state);
449 
450 		log_queue_buffer_free(buf, lq->lq_mem_size);
451 		lq->lq_req_mem = NULL;
452 		publish(&lq->lq_req_state, LQ_REQ_STATE_READY);
453 
454 		counter_inc(&log_queue_cnt_mem_released);
455 	}
456 }
457 
458 static void
log_queue_order_memory(log_queue_t lq)459 log_queue_order_memory(log_queue_t lq)
460 {
461 	boolean_t __assert_only running;
462 
463 	lq->lq_req_mem = NULL;
464 	publish(&lq->lq_req_state, LQ_REQ_STATE_ALLOCATING);
465 
466 	running = thread_call_enter(lq->lq_mem_handler);
467 	assert(!running);
468 }
469 
470 static void
log_queue_release_memory(log_queue_t lq,void * buf)471 log_queue_release_memory(log_queue_t lq, void *buf)
472 {
473 	boolean_t __assert_only running;
474 
475 	assert(buf);
476 	lq->lq_req_mem = buf;
477 	publish(&lq->lq_req_state, LQ_REQ_STATE_RELEASING);
478 
479 	running = thread_call_enter(lq->lq_mem_handler);
480 	assert(!running);
481 }
482 
483 static void
log_queue_mem_enable(log_queue_t lq,size_t i)484 log_queue_mem_enable(log_queue_t lq, size_t i)
485 {
486 	logmem_t *lm = &lq->lq_mem[i];
487 	assert(!LQ_MEM_ENABLED(lq, i));
488 
489 	LQ_MEM_ENABLE(lq, i);
490 	lq->lq_cnt_mem_active++;
491 	log_queue_increment_mem_avail(lq, i, lm->lm_cnt_free);
492 }
493 
494 static void
log_queue_mem_disable(log_queue_t lq,size_t i)495 log_queue_mem_disable(log_queue_t lq, size_t i)
496 {
497 	logmem_t *lm = &lq->lq_mem[i];
498 	assert(LQ_MEM_ENABLED(lq, i));
499 
500 	LQ_MEM_DISABLE(lq, i);
501 	lq->lq_cnt_mem_active--;
502 	log_queue_decrement_mem_avail(lq, i, lm->lm_cnt_free);
503 }
504 
505 static void *
log_queue_mem_reclaim(log_queue_t lq)506 log_queue_mem_reclaim(log_queue_t lq)
507 {
508 	for (int i = LQ_MIN_ALLOCATED_LM_SLOTS; i < LQ_MAX_LM_SLOTS; i++) {
509 		logmem_t *lm = &lq->lq_mem[i];
510 		if (LQ_MEM_ENABLED(lq, i) && logmem_empty(lm)) {
511 			assert(lm->lm_mem_size == lq->lq_mem_size);
512 			void *reclaimed = lm->lm_mem;
513 			log_queue_mem_disable(lq, i);
514 			/* Do not use bzero here, see rdar://116922009 */
515 			*lm = (logmem_t){ };
516 			return reclaimed;
517 		}
518 	}
519 	return NULL;
520 }
521 
522 static void
log_queue_mem_reconfigure(log_queue_t lq)523 log_queue_mem_reconfigure(log_queue_t lq)
524 {
525 	assert(lq->lq_mem_state == LQ_MEM_STATE_ALLOCATING ||
526 	    lq->lq_mem_state == LQ_MEM_STATE_RELEASING);
527 
528 	lq_req_state_t req_state = log_queue_request_state(lq);
529 
530 	if (req_state == LQ_REQ_STATE_READY) {
531 		if (lq->lq_mem_state == LQ_MEM_STATE_ALLOCATING) {
532 			void *buf = read_dependent(&lq->lq_req_mem, req_state);
533 			if (buf) {
534 				const int i = log_queue_mem_free_slot(lq);
535 				assert(i > 0);
536 				log_queue_mem_init(lq, i, buf, lq->lq_mem_size);
537 				log_queue_mem_enable(lq, i);
538 			}
539 		}
540 		lq->lq_mem_state = LQ_MEM_STATE_READY;
541 		publish(&lq->lq_req_state, LQ_REQ_STATE_INVALID);
542 	}
543 }
544 
545 static boolean_t
log_queue_needs_memory(log_queue_t lq,boolean_t new_suspend)546 log_queue_needs_memory(log_queue_t lq, boolean_t new_suspend)
547 {
548 	// Store the current upper bound before potentially growing the queue.
549 	lq->lq_cnt_mem_max = log_queue_mem_max_slots();
550 
551 	if (new_suspend || log_queue_low_mem(lq)) {
552 		return lq->lq_cnt_mem_active < lq->lq_cnt_mem_max;
553 	}
554 	return false;
555 }
556 
557 static boolean_t
log_queue_can_release_memory(log_queue_t lq)558 log_queue_can_release_memory(log_queue_t lq)
559 {
560 	assert(lq->lq_mem_state == LQ_MEM_STATE_READY);
561 
562 	if (lq->lq_cnt_mem_active > LQ_MIN_ALLOCATED_LM_SLOTS
563 	    && log_queue_empty(lq) && !lq->lq_suspend) {
564 		const uint64_t total_log_cnt = counter_load(&log_queue_cnt_received);
565 		return total_log_cnt > LQ_DEFAULT_FREE_AFTER_CNT;
566 	}
567 	return false;
568 }
569 
570 extern boolean_t tasks_suspend_state;
571 
572 static boolean_t
detect_new_suspend(log_queue_t lq)573 detect_new_suspend(log_queue_t lq)
574 {
575 	if (!tasks_suspend_state) {
576 		lq->lq_suspend = false;
577 		return false;
578 	}
579 
580 	if (!lq->lq_suspend) {
581 		lq->lq_suspend = true;
582 		return true;
583 	}
584 
585 	return false;
586 }
587 
588 static void
log_queue_dispatch(void)589 log_queue_dispatch(void)
590 {
591 	lq_mem_state_t new_mem_state = LQ_MEM_STATE_READY;
592 	void *reclaimed_memory = NULL;
593 
594 	disable_preemption();
595 
596 	log_queue_t lq = PERCPU_GET(oslog_queue);
597 	if (__improbable(!lq->lq_ready)) {
598 		enable_preemption();
599 		return;
600 	}
601 
602 	dispatch_list_cleanup(lq);
603 
604 	log_queue_entry_t logs[LQ_BATCH_SIZE];
605 	size_t logs_count = log_dispatch_prepare(lq, LQ_BATCH_SIZE, (log_queue_entry_t *)&logs);
606 
607 	boolean_t new_suspend = detect_new_suspend(lq);
608 
609 	if (__improbable(lq->lq_mem_state != LQ_MEM_STATE_READY)) {
610 		log_queue_mem_reconfigure(lq);
611 	} else if (logs_count == 0 && log_queue_can_release_memory(lq)) {
612 		reclaimed_memory = log_queue_mem_reclaim(lq);
613 		if (reclaimed_memory) {
614 			lq->lq_mem_state = LQ_MEM_STATE_RELEASING;
615 			new_mem_state = lq->lq_mem_state;
616 		}
617 	} else if (log_queue_needs_memory(lq, new_suspend)) {
618 		lq->lq_mem_state = LQ_MEM_STATE_ALLOCATING;
619 		new_mem_state = lq->lq_mem_state;
620 	}
621 
622 	enable_preemption();
623 
624 	switch (new_mem_state) {
625 	case LQ_MEM_STATE_RELEASING:
626 		assert(logs_count == 0);
627 		log_queue_release_memory(lq, reclaimed_memory);
628 		break;
629 	case LQ_MEM_STATE_ALLOCATING:
630 		log_queue_order_memory(lq);
631 	/* FALLTHROUGH */
632 	case LQ_MEM_STATE_READY:
633 		log_queue_dispatch_logs(logs_count, logs);
634 		break;
635 	default:
636 		panic("Invalid log memory state %u", new_mem_state);
637 		break;
638 	}
639 }
640 
641 static bool
log_queue_add(log_payload_t lp,const uint8_t * lp_data)642 log_queue_add(log_payload_t lp, const uint8_t *lp_data)
643 {
644 	boolean_t order_memory = false;
645 
646 	disable_preemption();
647 
648 	log_queue_t lq = PERCPU_GET(oslog_queue);
649 	if (__improbable(!lq->lq_ready)) {
650 		enable_preemption();
651 		counter_inc(&log_queue_cnt_dropped_off);
652 		return false;
653 	}
654 
655 	boolean_t new_suspend = detect_new_suspend(lq);
656 
657 	if (__improbable(lq->lq_mem_state != LQ_MEM_STATE_READY)) {
658 		log_queue_mem_reconfigure(lq);
659 	} else if (log_queue_needs_memory(lq, new_suspend)) {
660 		lq->lq_mem_state = LQ_MEM_STATE_ALLOCATING;
661 		order_memory = true;
662 	}
663 
664 	bool added = log_queue_add_entry(lq, lp, lp_data);
665 	enable_preemption();
666 
667 	if (order_memory) {
668 		log_queue_order_memory(lq);
669 	}
670 
671 	return added;
672 }
673 
674 __startup_func
675 static size_t
log_queue_init_memory(log_queue_t lq)676 log_queue_init_memory(log_queue_t lq)
677 {
678 	lq->lq_cnt_mem_max = log_queue_mem_max_slots();
679 	assert(lq->lq_cnt_mem_max <= LQ_MAX_LM_SLOTS);
680 
681 	for (size_t i = 0; i < lq->lq_cnt_mem_max; i++) {
682 		void *buf = log_queue_buffer_alloc(lq->lq_mem_size);
683 		if (!buf) {
684 			return i;
685 		}
686 		counter_inc(&log_queue_cnt_mem_allocated);
687 		log_queue_mem_init(lq, i, buf, lq->lq_mem_size);
688 		log_queue_mem_enable(lq, i);
689 	}
690 
691 	return lq->lq_cnt_mem_max;
692 }
693 
694 __startup_func
695 static void
oslog_init_log_queues(void)696 oslog_init_log_queues(void)
697 {
698 	if (os_log_disabled()) {
699 		printf("Log queues disabled: Logging disabled by ATM\n");
700 		return;
701 	}
702 
703 	if (lq_bootarg_size_order == 0) {
704 		printf("Log queues disabled: Zero lq_size_order boot argument\n");
705 		return;
706 	}
707 
708 	lq_bootarg_size_order = MAX(lq_bootarg_size_order, PAGE_SHIFT);
709 	lq_bootarg_size_order = MIN(lq_bootarg_size_order, LQ_MAX_SZ_ORDER);
710 
711 	lq_bootarg_nslots = MAX(lq_bootarg_nslots, LQ_MIN_ALLOCATED_LM_SLOTS);
712 	lq_bootarg_nslots = MIN(lq_bootarg_nslots, LQ_MAX_LM_SLOTS);
713 	log_queue_set_max_slots(lq_bootarg_nslots);
714 
715 	lq_low_mem_limit = MAX(1 << (lq_bootarg_size_order - LQ_LOW_MEM_SCALE), 1024);
716 
717 	unsigned int slot_count = 0;
718 
719 	percpu_foreach(lq, oslog_queue) {
720 		lq->lq_mem_size_order = lq_bootarg_size_order;
721 		lq->lq_mem_size = round_page(logmem_required_size(lq->lq_mem_size_order, LQ_MIN_LOG_SZ_ORDER));
722 		lq->lq_mem_handler = thread_call_allocate(log_queue_memory_handler, (thread_call_param_t)lq);
723 		slot_count += log_queue_init_memory(lq);
724 		STAILQ_INIT(&lq->lq_log_list);
725 		STAILQ_INIT(&lq->lq_dispatch_list);
726 		lq->lq_ready = true;
727 	}
728 
729 	printf("Log queues configured: slot count: %u, per-slot size: %u, total size: %u\n",
730 	    slot_count, (1 << lq_bootarg_size_order),
731 	    slot_count * (1 << lq_bootarg_size_order));
732 }
733 STARTUP(OSLOG, STARTUP_RANK_SECOND, oslog_init_log_queues);
734 
735 bool
log_queue_log(log_payload_t lp,const void * lp_data,bool stream)736 log_queue_log(log_payload_t lp, const void *lp_data, bool stream)
737 {
738 	assert(lp);
739 	assert(oslog_is_safe() || startup_phase < STARTUP_SUB_EARLY_BOOT);
740 
741 	counter_inc(&log_queue_cnt_received);
742 
743 	if (log_payload_send(lp, lp_data, stream)) {
744 		counter_inc(&log_queue_cnt_sent);
745 		log_queue_dispatch();
746 		return true;
747 	}
748 	counter_inc(&log_queue_cnt_rejected_fh);
749 
750 	if (!log_queue_add(lp, lp_data)) {
751 		return false;
752 	}
753 
754 	return true;
755 }
756