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