1/*
2 * Copyright (c) 2015 Apple 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#include <kern/kalloc.h>
29#include <kern/locks.h>
30#include <sys/kernel.h>
31#include <sys/kernel_types.h>
32#include <kern/zalloc.h>
33#include <sys/reason.h>
34#include <string.h>
35#include <kern/assert.h>
36#include <kern/debug.h>
37
38#if OS_REASON_DEBUG
39#include <pexpert/pexpert.h>
40
41extern int os_reason_debug_disabled;
42#endif
43
44extern int maxproc;
45
46/*
47 * Lock group attributes for os_reason subsystem
48 */
49lck_grp_attr_t *os_reason_lock_grp_attr;
50lck_grp_t *os_reason_lock_grp;
51lck_attr_t *os_reason_lock_attr;
52
53#define OS_REASON_RESERVE_COUNT 100
54#define OS_REASON_MAX_COUNT (maxproc + 100)
55
56static struct zone *os_reason_zone;
57static int os_reason_alloc_buffer_internal(os_reason_t cur_reason, uint32_t osr_bufsize,
58 boolean_t can_block);
59
60void
61os_reason_init()
62{
63 int reasons_allocated = 0;
64
65 /*
66 * Initialize OS reason group and lock attributes
67 */
68 os_reason_lock_grp_attr = lck_grp_attr_alloc_init();
69 os_reason_lock_grp = lck_grp_alloc_init("os_reason_lock", os_reason_lock_grp_attr);
70 os_reason_lock_attr = lck_attr_alloc_init();
71
72 /*
73 * Create OS reason zone.
74 */
75 os_reason_zone = zinit(sizeof(struct os_reason), OS_REASON_MAX_COUNT * sizeof(struct os_reason),
76 OS_REASON_MAX_COUNT, "os reasons");
77 if (os_reason_zone == NULL) {
78 panic("failed to initialize os_reason_zone");
79 }
80
81 /*
82 * We pre-fill the OS reason zone to reduce the likelihood that
83 * the jetsam thread and others block when they create an exit
84 * reason. This pre-filled memory is not-collectable since it's
85 * foreign memory crammed in as part of zfill().
86 */
87 reasons_allocated = zfill(os_reason_zone, OS_REASON_RESERVE_COUNT);
88 assert(reasons_allocated > 0);
89}
90
91/*
92 * Creates a new reason and initializes it with the provided reason
93 * namespace and code. Also sets up the buffer and kcdata_descriptor
94 * associated with the reason. Returns a pointer to the newly created
95 * reason.
96 *
97 * Returns:
98 * REASON_NULL if unable to allocate a reason or initialize the nested buffer
99 * a pointer to the reason otherwise
100 */
101os_reason_t
102os_reason_create(uint32_t osr_namespace, uint64_t osr_code)
103{
104 os_reason_t new_reason = OS_REASON_NULL;
105
106 new_reason = (os_reason_t) zalloc(os_reason_zone);
107 if (new_reason == OS_REASON_NULL) {
108#if OS_REASON_DEBUG
109 /*
110 * We rely on OS reasons to communicate important things such
111 * as process exit reason information, we should be aware
112 * when issues prevent us from allocating them.
113 */
114 if (os_reason_debug_disabled) {
115 kprintf("os_reason_create: failed to allocate reason with namespace: %u, code : %llu\n",
116 osr_namespace, osr_code);
117 } else {
118 panic("os_reason_create: failed to allocate reason with namespace: %u, code: %llu\n",
119 osr_namespace, osr_code);
120 }
121#endif
122 return new_reason;
123 }
124
125 bzero(new_reason, sizeof(*new_reason));
126
127 new_reason->osr_namespace = osr_namespace;
128 new_reason->osr_code = osr_code;
129 new_reason->osr_flags = 0;
130 new_reason->osr_bufsize = 0;
131 new_reason->osr_kcd_buf = NULL;
132
133 lck_mtx_init(&new_reason->osr_lock, os_reason_lock_grp, os_reason_lock_attr);
134 new_reason->osr_refcount = 1;
135
136 return new_reason;
137}
138
139static void
140os_reason_dealloc_buffer(os_reason_t cur_reason)
141{
142 assert(cur_reason != OS_REASON_NULL);
143 LCK_MTX_ASSERT(&cur_reason->osr_lock, LCK_MTX_ASSERT_OWNED);
144
145 if (cur_reason->osr_kcd_buf != NULL && cur_reason->osr_bufsize != 0) {
146 kfree(cur_reason->osr_kcd_buf, cur_reason->osr_bufsize);
147 }
148
149 cur_reason->osr_bufsize = 0;
150 cur_reason->osr_kcd_buf = NULL;
151 bzero(&cur_reason->osr_kcd_descriptor, sizeof(cur_reason->osr_kcd_descriptor));
152
153 return;
154}
155
156/*
157 * Allocates and initializes a buffer of specified size for the reason. This function
158 * may block and should not be called from extremely performance sensitive contexts
159 * (i.e. jetsam). Also initializes the kcdata descriptor accordingly. If there is an
160 * existing buffer, we dealloc the buffer before allocating a new one and
161 * clear the associated kcdata descriptor. If osr_bufsize is passed as 0,
162 * we deallocate the existing buffer and then return.
163 *
164 * Returns:
165 * 0 on success
166 * EINVAL if the passed reason pointer is invalid or the requested size is
167 * larger than REASON_BUFFER_MAX_SIZE
168 * EIO if we fail to initialize the kcdata buffer
169 */
170int
171os_reason_alloc_buffer(os_reason_t cur_reason, uint32_t osr_bufsize)
172{
173 return os_reason_alloc_buffer_internal(cur_reason, osr_bufsize, TRUE);
174}
175
176/*
177 * Allocates and initializes a buffer of specified size for the reason. Also
178 * initializes the kcdata descriptor accordingly. If there is an existing
179 * buffer, we dealloc the buffer before allocating a new one and
180 * clear the associated kcdata descriptor. If osr_bufsize is passed as 0,
181 * we deallocate the existing buffer and then return.
182 *
183 * Returns:
184 * 0 on success
185 * EINVAL if the passed reason pointer is invalid or the requested size is
186 * larger than REASON_BUFFER_MAX_SIZE
187 * ENOMEM if unable to allocate memory for the buffer
188 * EIO if we fail to initialize the kcdata buffer
189 */
190int
191os_reason_alloc_buffer_noblock(os_reason_t cur_reason, uint32_t osr_bufsize)
192{
193 return os_reason_alloc_buffer_internal(cur_reason, osr_bufsize, FALSE);
194}
195
196static int
197os_reason_alloc_buffer_internal(os_reason_t cur_reason, uint32_t osr_bufsize,
198 boolean_t can_block)
199{
200 if (cur_reason == OS_REASON_NULL) {
201 return EINVAL;
202 }
203
204 if (osr_bufsize > OS_REASON_BUFFER_MAX_SIZE) {
205 return EINVAL;
206 }
207
208 lck_mtx_lock(&cur_reason->osr_lock);
209
210 os_reason_dealloc_buffer(cur_reason);
211
212 if (osr_bufsize == 0) {
213 lck_mtx_unlock(&cur_reason->osr_lock);
214 return 0;
215 }
216
217 if (can_block) {
218 cur_reason->osr_kcd_buf = kalloc_tag(osr_bufsize, VM_KERN_MEMORY_REASON);
219 assert(cur_reason->osr_kcd_buf != NULL);
220 } else {
221 cur_reason->osr_kcd_buf = kalloc_noblock_tag(osr_bufsize, VM_KERN_MEMORY_REASON);
222 if (cur_reason->osr_kcd_buf == NULL) {
223 lck_mtx_unlock(&cur_reason->osr_lock);
224 return ENOMEM;
225 }
226 }
227
228 bzero(cur_reason->osr_kcd_buf, osr_bufsize);
229
230 cur_reason->osr_bufsize = osr_bufsize;
231
232 if (kcdata_memory_static_init(&cur_reason->osr_kcd_descriptor, (mach_vm_address_t) cur_reason->osr_kcd_buf,
233 KCDATA_BUFFER_BEGIN_OS_REASON, osr_bufsize, KCFLAG_USE_MEMCOPY) != KERN_SUCCESS) {
234 os_reason_dealloc_buffer(cur_reason);
235
236 lck_mtx_unlock(&cur_reason->osr_lock);
237 return EIO;
238 }
239
240 lck_mtx_unlock(&cur_reason->osr_lock);
241
242 return 0;
243}
244
245/*
246 * Returns a pointer to the kcdata descriptor associated with the specified
247 * reason if there is a buffer allocated.
248 */
249struct kcdata_descriptor *
250os_reason_get_kcdata_descriptor(os_reason_t cur_reason)
251{
252 if (cur_reason == OS_REASON_NULL) {
253 return NULL;
254 }
255
256 if (cur_reason->osr_kcd_buf == NULL) {
257 return NULL;
258 }
259
260 assert(cur_reason->osr_kcd_descriptor.kcd_addr_begin == (mach_vm_address_t) cur_reason->osr_kcd_buf);
261 if (cur_reason->osr_kcd_descriptor.kcd_addr_begin != (mach_vm_address_t) cur_reason->osr_kcd_buf) {
262 return NULL;
263 }
264
265 return &cur_reason->osr_kcd_descriptor;
266}
267
268/*
269 * Takes a reference on the passed reason.
270 */
271void
272os_reason_ref(os_reason_t cur_reason)
273{
274 if (cur_reason == OS_REASON_NULL) {
275 return;
276 }
277
278 lck_mtx_lock(&cur_reason->osr_lock);
279
280 assert(cur_reason->osr_refcount > 0);
281 if (os_add_overflow(cur_reason->osr_refcount, 1, &cur_reason->osr_refcount)) {
282 panic("os reason refcount overflow");
283 }
284
285 lck_mtx_unlock(&cur_reason->osr_lock);
286
287 return;
288}
289
290/*
291 * Drops a reference on the passed reason, deallocates
292 * the reason if no references remain.
293 */
294void
295os_reason_free(os_reason_t cur_reason)
296{
297 if (cur_reason == OS_REASON_NULL) {
298 return;
299 }
300
301 lck_mtx_lock(&cur_reason->osr_lock);
302
303 if (cur_reason->osr_refcount == 0) {
304 panic("os_reason_free called on reason with zero refcount");
305 }
306
307 cur_reason->osr_refcount--;
308 if (cur_reason->osr_refcount != 0) {
309 lck_mtx_unlock(&cur_reason->osr_lock);
310 return;
311 }
312
313 os_reason_dealloc_buffer(cur_reason);
314
315 lck_mtx_unlock(&cur_reason->osr_lock);
316 lck_mtx_destroy(&cur_reason->osr_lock, os_reason_lock_grp);
317
318 zfree(os_reason_zone, cur_reason);
319}
320