xref: /xnu-8792.61.2/bsd/kern/sys_reason.c (revision 42e220869062b56f8d7d0726fd4c88954f87902c)
1 /*
2  * Copyright (c) 2015-2020 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 extern int maxproc;
39 
40 /*
41  * Lock group attributes for os_reason subsystem
42  */
43 static LCK_GRP_DECLARE(os_reason_lock_grp, "os_reason_lock");
44 static ZONE_DEFINE(os_reason_zone, "os reasons",
45     sizeof(struct os_reason), ZC_ZFREE_CLEARMEM);
46 
47 os_refgrp_decl(static, os_reason_refgrp, "os_reason", NULL);
48 
49 static int os_reason_alloc_buffer_internal(os_reason_t cur_reason, uint32_t osr_bufsize,
50     zalloc_flags_t flags);
51 
52 /*
53  * Creates a new reason and initializes it with the provided reason
54  * namespace and code. Also sets up the buffer and kcdata_descriptor
55  * associated with the reason. Returns a pointer to the newly created
56  * reason.
57  *
58  * Returns:
59  * REASON_NULL if unable to allocate a reason or initialize the nested buffer
60  * a pointer to the reason otherwise
61  */
62 os_reason_t
os_reason_create(uint32_t osr_namespace,uint64_t osr_code)63 os_reason_create(uint32_t osr_namespace, uint64_t osr_code)
64 {
65 	os_reason_t new_reason;
66 
67 	new_reason = zalloc_flags(os_reason_zone, Z_WAITOK | Z_ZERO);
68 	new_reason->osr_namespace = osr_namespace;
69 	new_reason->osr_code = osr_code;
70 	lck_mtx_init(&new_reason->osr_lock, &os_reason_lock_grp, LCK_ATTR_NULL);
71 	os_ref_init(&new_reason->osr_refcount, &os_reason_refgrp);
72 
73 	return new_reason;
74 }
75 
76 static void
os_reason_dealloc_buffer(os_reason_t cur_reason)77 os_reason_dealloc_buffer(os_reason_t cur_reason)
78 {
79 	assert(cur_reason != OS_REASON_NULL);
80 	LCK_MTX_ASSERT(&cur_reason->osr_lock, LCK_MTX_ASSERT_OWNED);
81 
82 	if (cur_reason->osr_kcd_buf != NULL && cur_reason->osr_bufsize != 0) {
83 		kfree_data(cur_reason->osr_kcd_buf, cur_reason->osr_bufsize);
84 	}
85 
86 	cur_reason->osr_bufsize = 0;
87 	cur_reason->osr_kcd_buf = NULL;
88 	bzero(&cur_reason->osr_kcd_descriptor, sizeof(cur_reason->osr_kcd_descriptor));
89 }
90 
91 /*
92  * Allocates and initializes a buffer of specified size for the reason. This function
93  * may block and should not be called from extremely performance sensitive contexts
94  * (i.e. jetsam). Also initializes the kcdata descriptor accordingly. If there is an
95  * existing buffer, we dealloc the buffer before allocating a new one and
96  * clear the associated kcdata descriptor. If osr_bufsize is passed as 0,
97  * we deallocate the existing buffer and then return.
98  *
99  * Returns:
100  * 0 on success
101  * EINVAL if the passed reason pointer is invalid or the requested size is
102  *        larger than REASON_BUFFER_MAX_SIZE
103  * EIO if we fail to initialize the kcdata buffer
104  */
105 int
os_reason_alloc_buffer(os_reason_t cur_reason,uint32_t osr_bufsize)106 os_reason_alloc_buffer(os_reason_t cur_reason, uint32_t osr_bufsize)
107 {
108 	return os_reason_alloc_buffer_internal(cur_reason, osr_bufsize, Z_WAITOK);
109 }
110 
111 /*
112  * Allocates and initializes a buffer of specified size for the reason. Also
113  * initializes the kcdata descriptor accordingly. If there is an existing
114  * buffer, we dealloc the buffer before allocating a new one and
115  * clear the associated kcdata descriptor. If osr_bufsize is passed as 0,
116  * we deallocate the existing buffer and then return.
117  *
118  * Returns:
119  * 0 on success
120  * EINVAL if the passed reason pointer is invalid or the requested size is
121  *        larger than REASON_BUFFER_MAX_SIZE
122  * ENOMEM if unable to allocate memory for the buffer
123  * EIO if we fail to initialize the kcdata buffer
124  */
125 int
os_reason_alloc_buffer_noblock(os_reason_t cur_reason,uint32_t osr_bufsize)126 os_reason_alloc_buffer_noblock(os_reason_t cur_reason, uint32_t osr_bufsize)
127 {
128 	return os_reason_alloc_buffer_internal(cur_reason, osr_bufsize, Z_NOWAIT);
129 }
130 
131 static int
os_reason_alloc_buffer_internal(os_reason_t cur_reason,uint32_t osr_bufsize,zalloc_flags_t flags)132 os_reason_alloc_buffer_internal(os_reason_t cur_reason, uint32_t osr_bufsize,
133     zalloc_flags_t flags)
134 {
135 	if (cur_reason == OS_REASON_NULL) {
136 		return EINVAL;
137 	}
138 
139 	if (osr_bufsize > OS_REASON_BUFFER_MAX_SIZE) {
140 		return EINVAL;
141 	}
142 
143 	lck_mtx_lock(&cur_reason->osr_lock);
144 
145 	os_reason_dealloc_buffer(cur_reason);
146 
147 	if (osr_bufsize == 0) {
148 		lck_mtx_unlock(&cur_reason->osr_lock);
149 		return 0;
150 	}
151 
152 	cur_reason->osr_kcd_buf = kalloc_data_tag(osr_bufsize, flags | Z_ZERO,
153 	    VM_KERN_MEMORY_REASON);
154 
155 	if (cur_reason->osr_kcd_buf == NULL) {
156 		lck_mtx_unlock(&cur_reason->osr_lock);
157 		return ENOMEM;
158 	}
159 
160 	cur_reason->osr_bufsize = osr_bufsize;
161 
162 	if (kcdata_memory_static_init(&cur_reason->osr_kcd_descriptor,
163 	    (mach_vm_address_t)cur_reason->osr_kcd_buf,
164 	    KCDATA_BUFFER_BEGIN_OS_REASON, osr_bufsize, KCFLAG_USE_MEMCOPY) !=
165 	    KERN_SUCCESS) {
166 		os_reason_dealloc_buffer(cur_reason);
167 
168 		lck_mtx_unlock(&cur_reason->osr_lock);
169 		return EIO;
170 	}
171 
172 	lck_mtx_unlock(&cur_reason->osr_lock);
173 
174 	return 0;
175 }
176 
177 /*
178  * Returns a pointer to the kcdata descriptor associated with the specified
179  * reason if there is a buffer allocated.
180  */
181 struct kcdata_descriptor *
os_reason_get_kcdata_descriptor(os_reason_t cur_reason)182 os_reason_get_kcdata_descriptor(os_reason_t cur_reason)
183 {
184 	if (cur_reason == OS_REASON_NULL) {
185 		return NULL;
186 	}
187 
188 	if (cur_reason->osr_kcd_buf == NULL) {
189 		return NULL;
190 	}
191 
192 	assert(cur_reason->osr_kcd_descriptor.kcd_addr_begin ==
193 	    (mach_vm_address_t)cur_reason->osr_kcd_buf);
194 	if (cur_reason->osr_kcd_descriptor.kcd_addr_begin !=
195 	    (mach_vm_address_t)cur_reason->osr_kcd_buf) {
196 		return NULL;
197 	}
198 
199 	return &cur_reason->osr_kcd_descriptor;
200 }
201 
202 /*
203  * Takes a reference on the passed reason.
204  */
205 void
os_reason_ref(os_reason_t cur_reason)206 os_reason_ref(os_reason_t cur_reason)
207 {
208 	if (cur_reason == OS_REASON_NULL) {
209 		return;
210 	}
211 
212 	lck_mtx_lock(&cur_reason->osr_lock);
213 	os_ref_retain_locked(&cur_reason->osr_refcount);
214 	lck_mtx_unlock(&cur_reason->osr_lock);
215 	return;
216 }
217 
218 /*
219  * Drops a reference on the passed reason, deallocates
220  * the reason if no references remain.
221  */
222 void
os_reason_free(os_reason_t cur_reason)223 os_reason_free(os_reason_t cur_reason)
224 {
225 	if (cur_reason == OS_REASON_NULL) {
226 		return;
227 	}
228 
229 	lck_mtx_lock(&cur_reason->osr_lock);
230 
231 	if (os_ref_release_locked(&cur_reason->osr_refcount) > 0) {
232 		lck_mtx_unlock(&cur_reason->osr_lock);
233 		return;
234 	}
235 
236 	os_reason_dealloc_buffer(cur_reason);
237 
238 	lck_mtx_unlock(&cur_reason->osr_lock);
239 	lck_mtx_destroy(&cur_reason->osr_lock, &os_reason_lock_grp);
240 
241 	zfree(os_reason_zone, cur_reason);
242 }
243 
244 /*
245  * Sets flags on the passed reason.
246  */
247 void
os_reason_set_flags(os_reason_t cur_reason,uint64_t flags)248 os_reason_set_flags(os_reason_t cur_reason, uint64_t flags)
249 {
250 	if (cur_reason == OS_REASON_NULL) {
251 		return;
252 	}
253 
254 	lck_mtx_lock(&cur_reason->osr_lock);
255 	cur_reason->osr_flags = flags;
256 	lck_mtx_unlock(&cur_reason->osr_lock);
257 }
258 
259 /*
260  * Allocates space and sets description data in kcd_descriptor on the passed reason.
261  */
262 void
os_reason_set_description_data(os_reason_t cur_reason,uint32_t type,void * reason_data,uint32_t reason_data_len)263 os_reason_set_description_data(os_reason_t cur_reason, uint32_t type, void *reason_data, uint32_t reason_data_len)
264 {
265 	mach_vm_address_t osr_data_addr = 0;
266 
267 	if (cur_reason == OS_REASON_NULL) {
268 		return;
269 	}
270 
271 	if (0 != os_reason_alloc_buffer(cur_reason, kcdata_estimate_required_buffer_size(1, reason_data_len))) {
272 		panic("os_reason failed to allocate");
273 	}
274 
275 	lck_mtx_lock(&cur_reason->osr_lock);
276 	if (KERN_SUCCESS != kcdata_get_memory_addr(&cur_reason->osr_kcd_descriptor, type, reason_data_len, &osr_data_addr)) {
277 		panic("os_reason failed to get data address");
278 	}
279 	if (KERN_SUCCESS != kcdata_memcpy(&cur_reason->osr_kcd_descriptor, osr_data_addr, reason_data, reason_data_len)) {
280 		panic("os_reason failed to copy description data");
281 	}
282 	lck_mtx_unlock(&cur_reason->osr_lock);
283 }
284