#include <kern/kalloc.h>
#include <kern/locks.h>
#include <sys/kernel.h>
#include <sys/kernel_types.h>
#include <kern/zalloc.h>
#include <sys/reason.h>
#include <string.h>
#include <kern/assert.h>
#include <kern/debug.h>
#if OS_REASON_DEBUG
#include <pexpert/pexpert.h>
extern int os_reason_debug_disabled;
#endif
extern int maxproc;
lck_grp_attr_t *os_reason_lock_grp_attr;
lck_grp_t *os_reason_lock_grp;
lck_attr_t *os_reason_lock_attr;
#define OS_REASON_RESERVE_COUNT 100
#define OS_REASON_MAX_COUNT (maxproc + 100)
static struct zone *os_reason_zone;
static int os_reason_alloc_buffer_internal(os_reason_t cur_reason, uint32_t osr_bufsize,
boolean_t can_block);
void
os_reason_init()
{
int reasons_allocated = 0;
os_reason_lock_grp_attr = lck_grp_attr_alloc_init();
os_reason_lock_grp = lck_grp_alloc_init("os_reason_lock", os_reason_lock_grp_attr);
os_reason_lock_attr = lck_attr_alloc_init();
os_reason_zone = zinit(sizeof(struct os_reason), OS_REASON_MAX_COUNT * sizeof(struct os_reason),
OS_REASON_MAX_COUNT, "os reasons");
if (os_reason_zone == NULL) {
panic("failed to initialize os_reason_zone");
}
reasons_allocated = zfill(os_reason_zone, OS_REASON_RESERVE_COUNT);
assert(reasons_allocated > 0);
}
os_reason_t
os_reason_create(uint32_t osr_namespace, uint64_t osr_code)
{
os_reason_t new_reason = OS_REASON_NULL;
new_reason = (os_reason_t) zalloc(os_reason_zone);
if (new_reason == OS_REASON_NULL) {
#if OS_REASON_DEBUG
if (os_reason_debug_disabled) {
kprintf("os_reason_create: failed to allocate reason with namespace: %u, code : %llu\n",
osr_namespace, osr_code);
} else {
panic("os_reason_create: failed to allocate reason with namespace: %u, code: %llu\n",
osr_namespace, osr_code);
}
#endif
return new_reason;
}
bzero(new_reason, sizeof(*new_reason));
new_reason->osr_namespace = osr_namespace;
new_reason->osr_code = osr_code;
new_reason->osr_flags = 0;
new_reason->osr_bufsize = 0;
new_reason->osr_kcd_buf = NULL;
lck_mtx_init(&new_reason->osr_lock, os_reason_lock_grp, os_reason_lock_attr);
new_reason->osr_refcount = 1;
return new_reason;
}
static void
os_reason_dealloc_buffer(os_reason_t cur_reason)
{
assert(cur_reason != OS_REASON_NULL);
LCK_MTX_ASSERT(&cur_reason->osr_lock, LCK_MTX_ASSERT_OWNED);
if (cur_reason->osr_kcd_buf != NULL && cur_reason->osr_bufsize != 0) {
kfree(cur_reason->osr_kcd_buf, cur_reason->osr_bufsize);
}
cur_reason->osr_bufsize = 0;
cur_reason->osr_kcd_buf = NULL;
bzero(&cur_reason->osr_kcd_descriptor, sizeof(cur_reason->osr_kcd_descriptor));
return;
}
int
os_reason_alloc_buffer(os_reason_t cur_reason, uint32_t osr_bufsize)
{
return os_reason_alloc_buffer_internal(cur_reason, osr_bufsize, TRUE);
}
int
os_reason_alloc_buffer_noblock(os_reason_t cur_reason, uint32_t osr_bufsize)
{
return os_reason_alloc_buffer_internal(cur_reason, osr_bufsize, FALSE);
}
static int
os_reason_alloc_buffer_internal(os_reason_t cur_reason, uint32_t osr_bufsize,
boolean_t can_block)
{
if (cur_reason == OS_REASON_NULL) {
return EINVAL;
}
if (osr_bufsize > OS_REASON_BUFFER_MAX_SIZE) {
return EINVAL;
}
lck_mtx_lock(&cur_reason->osr_lock);
os_reason_dealloc_buffer(cur_reason);
if (osr_bufsize == 0) {
lck_mtx_unlock(&cur_reason->osr_lock);
return 0;
}
if (can_block) {
cur_reason->osr_kcd_buf = kalloc_tag(osr_bufsize, VM_KERN_MEMORY_REASON);
assert(cur_reason->osr_kcd_buf != NULL);
} else {
cur_reason->osr_kcd_buf = kalloc_noblock_tag(osr_bufsize, VM_KERN_MEMORY_REASON);
if (cur_reason->osr_kcd_buf == NULL) {
lck_mtx_unlock(&cur_reason->osr_lock);
return ENOMEM;
}
}
bzero(cur_reason->osr_kcd_buf, osr_bufsize);
cur_reason->osr_bufsize = osr_bufsize;
if (kcdata_memory_static_init(&cur_reason->osr_kcd_descriptor, (mach_vm_address_t) cur_reason->osr_kcd_buf,
KCDATA_BUFFER_BEGIN_OS_REASON, osr_bufsize, KCFLAG_USE_MEMCOPY) != KERN_SUCCESS) {
os_reason_dealloc_buffer(cur_reason);
lck_mtx_unlock(&cur_reason->osr_lock);
return EIO;
}
lck_mtx_unlock(&cur_reason->osr_lock);
return 0;
}
struct kcdata_descriptor *
os_reason_get_kcdata_descriptor(os_reason_t cur_reason)
{
if (cur_reason == OS_REASON_NULL) {
return NULL;
}
if (cur_reason->osr_kcd_buf == NULL) {
return NULL;
}
assert(cur_reason->osr_kcd_descriptor.kcd_addr_begin == (mach_vm_address_t) cur_reason->osr_kcd_buf);
if (cur_reason->osr_kcd_descriptor.kcd_addr_begin != (mach_vm_address_t) cur_reason->osr_kcd_buf) {
return NULL;
}
return &cur_reason->osr_kcd_descriptor;
}
void
os_reason_ref(os_reason_t cur_reason)
{
if (cur_reason == OS_REASON_NULL) {
return;
}
lck_mtx_lock(&cur_reason->osr_lock);
assert(cur_reason->osr_refcount > 0);
cur_reason->osr_refcount++;
lck_mtx_unlock(&cur_reason->osr_lock);
return;
}
void
os_reason_free(os_reason_t cur_reason)
{
if (cur_reason == OS_REASON_NULL) {
return;
}
lck_mtx_lock(&cur_reason->osr_lock);
assert(cur_reason->osr_refcount > 0);
cur_reason->osr_refcount--;
if (cur_reason->osr_refcount != 0) {
lck_mtx_unlock(&cur_reason->osr_lock);
return;
}
os_reason_dealloc_buffer(cur_reason);
lck_mtx_unlock(&cur_reason->osr_lock);
lck_mtx_destroy(&cur_reason->osr_lock, os_reason_lock_grp);
zfree(os_reason_zone, cur_reason);
}