#include <kern/assert.h>
#include <kern/cpu_data.h>
#include <mach/mach_host.h>
#include <vm/vm_kern.h>
#if defined(__i386__) || defined(__x86_64__)
#include <i386/mp.h>
#endif
#if defined (__arm__) || defined (__arm64__)
#include <arm/cpu_data_internal.h>
#endif
#define DEFAULT_MAGAZINE_SIZE 8
#define DEFAULT_DEPOT_SIZE 8
#define ZCC_MAX_CPU_CACHE_LINE_SIZE 64
lck_grp_t zcache_locks_grp;
zone_t magazine_zone;
uint16_t magazine_element_count = 0;
uint16_t depot_element_count = 0;
bool zone_cache_ready = FALSE;
uintptr_t zcache_canary = 0;
struct zcc_magazine {
uint32_t zcc_magazine_index;
uint32_t zcc_magazine_capacity;
void *zcc_elements[0];
};
struct zcc_per_cpu_cache {
struct zcc_magazine *current;
struct zcc_magazine *previous;
} __attribute__((aligned(ZCC_MAX_CPU_CACHE_LINE_SIZE)));
#define ZCACHE_DEPOT_INVALID -1
#define zcache_depot_available(zcache) (zcache->zcc_depot_index != ZCACHE_DEPOT_INVALID)
struct zone_cache {
lck_mtx_t zcc_depot_lock;
struct zcc_per_cpu_cache zcc_per_cpu_caches[MAX_CPUS];
int zcc_depot_index;
struct zcc_magazine *zcc_depot_list[0];
};
void zcache_init_marked_zones(void);
bool zcache_mag_fill(zone_t zone, struct zcc_magazine *mag);
void zcache_mag_drain(zone_t zone, struct zcc_magazine *mag);
void zcache_mag_init(struct zcc_magazine *mag, int count);
void *zcache_mag_pop(struct zcc_magazine *mag);
void zcache_mag_push(struct zcc_magazine *mag, void *elem);
bool zcache_mag_has_space(struct zcc_magazine *mag);
bool zcache_mag_has_elements(struct zcc_magazine *mag);
void zcache_swap_magazines(struct zcc_magazine **a, struct zcc_magazine **b);
void zcache_mag_depot_swap_for_alloc(struct zone_cache *depot, struct zcc_per_cpu_cache *cache);
void zcache_mag_depot_swap_for_free(struct zone_cache *depot, struct zcc_per_cpu_cache *cache);
void zcache_mag_depot_swap(struct zone_cache *depot, struct zcc_per_cpu_cache *cache, boolean_t load_full);
void zcache_canary_add(zone_t zone, void *addr);
void zcache_canary_validate(zone_t zone, void *addr);
bool
zcache_ready(void)
{
return zone_cache_ready;
}
void
zcache_init_marked_zones(void)
{
unsigned int i;
for (i = 0; i < num_zones; i++) {
if (zone_array[i].cpu_cache_enable_when_ready) {
zcache_init(&zone_array[i]);
zone_array[i].cpu_cache_enable_when_ready = FALSE;
}
}
}
void
zcache_bootstrap(void)
{
if (!PE_parse_boot_argn("zcc_magazine_element_count", &magazine_element_count, sizeof(uint16_t))) {
magazine_element_count = DEFAULT_MAGAZINE_SIZE;
}
int magazine_size = sizeof(struct zcc_magazine) + magazine_element_count * sizeof(void *);
magazine_zone = zinit(magazine_size, 100000 * magazine_size, magazine_size, "zcc_magazine_zone");
assert(magazine_zone != NULL);
if (!PE_parse_boot_argn("zcc_depot_element_count", &depot_element_count, sizeof(uint16_t))) {
depot_element_count = DEFAULT_DEPOT_SIZE;
}
lck_grp_init(&zcache_locks_grp, "zcc_depot_lock", LCK_GRP_ATTR_NULL);
zcache_canary = (uintptr_t) early_random();
zone_cache_ready = TRUE;
zcache_init_marked_zones();
}
void
zcache_init(zone_t zone)
{
int i;
vm_size_t total_size;
struct zone_cache *temp_cache;
total_size = sizeof(struct zone_cache) + (depot_element_count * sizeof(void *));
temp_cache = (struct zone_cache *) kalloc(total_size);
for (i = 0; i < MAX_CPUS; i++) {
temp_cache->zcc_per_cpu_caches[i].current = (struct zcc_magazine *)zalloc(magazine_zone);
temp_cache->zcc_per_cpu_caches[i].previous = (struct zcc_magazine *)zalloc(magazine_zone);
assert(temp_cache->zcc_per_cpu_caches[i].current != NULL && temp_cache->zcc_per_cpu_caches[i].previous != NULL);
zcache_mag_init(temp_cache->zcc_per_cpu_caches[i].current, magazine_element_count);
zcache_mag_init(temp_cache->zcc_per_cpu_caches[i].previous, magazine_element_count);
}
lck_mtx_init(&(temp_cache->zcc_depot_lock), &zcache_locks_grp, LCK_ATTR_NULL);
for (i = 0; i < depot_element_count; i++) {
temp_cache->zcc_depot_list[i] = (struct zcc_magazine *)zalloc(magazine_zone);
assert(temp_cache->zcc_depot_list[i] != NULL);
zcache_mag_init(temp_cache->zcc_depot_list[i], magazine_element_count);
}
temp_cache->zcc_depot_index = 0;
lock_zone(zone);
zone->zcache = temp_cache;
zone->cpu_cache_enabled = TRUE;
unlock_zone(zone);
return;
}
void
zcache_drain_depot(zone_t zone)
{
struct zone_cache *zcache = zone->zcache;
int drain_depot_index = 0;
lck_mtx_lock_spin_always(&(zcache->zcc_depot_lock));
if (!zcache_depot_available(zcache) || (zcache->zcc_depot_index == 0)) {
lck_mtx_unlock(&(zcache->zcc_depot_lock));
return;
}
drain_depot_index = zcache->zcc_depot_index;
zcache->zcc_depot_index = ZCACHE_DEPOT_INVALID;
lck_mtx_unlock(&(zcache->zcc_depot_lock));
for (int i = 0; i < drain_depot_index; i++) {
zcache_mag_drain(zone, zcache->zcc_depot_list[i]);
}
lck_mtx_lock_spin_always(&(zcache->zcc_depot_lock));
zcache->zcc_depot_index = 0;
lck_mtx_unlock(&(zcache->zcc_depot_lock));
}
bool
zcache_free_to_cpu_cache(zone_t zone, void *addr)
{
int curcpu;
struct zone_cache *zcache;
struct zcc_per_cpu_cache *per_cpu_cache;
disable_preemption();
curcpu = current_processor()->cpu_id;
zcache = zone->zcache;
per_cpu_cache = &zcache->zcc_per_cpu_caches[curcpu];
if (zcache_mag_has_space(per_cpu_cache->current)) {
goto free_to_current;
} else if (zcache_mag_has_space(per_cpu_cache->previous)) {
zcache_swap_magazines(&per_cpu_cache->previous, &per_cpu_cache->current);
goto free_to_current;
} else {
lck_mtx_lock_spin_always(&(zcache->zcc_depot_lock));
if (zcache_depot_available(zcache) && (zcache->zcc_depot_index < depot_element_count)) {
zcache_mag_depot_swap_for_free(zcache, per_cpu_cache);
lck_mtx_unlock(&(zcache->zcc_depot_lock));
goto free_to_current;
}
lck_mtx_unlock(&(zcache->zcc_depot_lock));
zcache_mag_drain(zone, per_cpu_cache->current);
if (zcache_mag_has_space(per_cpu_cache->current)) {
goto free_to_current;
}
}
enable_preemption();
return FALSE;
free_to_current:
assert(zcache_mag_has_space(per_cpu_cache->current));
zcache_canary_add(zone, addr);
zcache_mag_push(per_cpu_cache->current, addr);
#if KASAN_ZALLOC
kasan_poison_range((vm_offset_t)addr, zone->elem_size, ASAN_HEAP_FREED);
#endif
enable_preemption();
return TRUE;
}
vm_offset_t
zcache_alloc_from_cpu_cache(zone_t zone)
{
int curcpu;
void *ret = NULL;
struct zone_cache *zcache;
struct zcc_per_cpu_cache *per_cpu_cache;
disable_preemption();
curcpu = current_processor()->cpu_id;
zcache = zone->zcache;
per_cpu_cache = &zcache->zcc_per_cpu_caches[curcpu];
if (zcache_mag_has_elements(per_cpu_cache->current)) {
goto allocate_from_current;
} else if (zcache_mag_has_elements(per_cpu_cache->previous)) {
zcache_swap_magazines(&per_cpu_cache->previous, &per_cpu_cache->current);
goto allocate_from_current;
} else {
lck_mtx_lock_spin_always(&(zcache->zcc_depot_lock));
if (zcache_depot_available(zcache) && (zcache->zcc_depot_index > 0)) {
zcache_mag_depot_swap_for_alloc(zcache, per_cpu_cache);
lck_mtx_unlock(&(zcache->zcc_depot_lock));
goto allocate_from_current;
}
lck_mtx_unlock(&(zcache->zcc_depot_lock));
if (zcache_mag_fill(zone, per_cpu_cache->current)) {
goto allocate_from_current;
}
}
enable_preemption();
return (vm_offset_t) NULL;
allocate_from_current:
ret = zcache_mag_pop(per_cpu_cache->current);
assert(ret != NULL);
zcache_canary_validate(zone, ret);
#if KASAN_ZALLOC
kasan_poison_range((vm_offset_t)ret, zone->elem_size, ASAN_VALID);
#endif
enable_preemption();
return (vm_offset_t) ret;
}
void
zcache_mag_init(struct zcc_magazine *mag, int count)
{
mag->zcc_magazine_index = 0;
mag->zcc_magazine_capacity = count;
}
bool
zcache_mag_fill(zone_t zone, struct zcc_magazine *mag)
{
assert(mag->zcc_magazine_index == 0);
void* elem = NULL;
uint32_t i;
lock_zone(zone);
for (i = mag->zcc_magazine_index; i < mag->zcc_magazine_capacity; i++) {
elem = zalloc_attempt(zone);
if (elem) {
zcache_canary_add(zone, elem);
zcache_mag_push(mag, elem);
#if KASAN_ZALLOC
kasan_poison_range((vm_offset_t)elem, zone->elem_size, ASAN_HEAP_FREED);
#endif
} else {
break;
}
}
unlock_zone(zone);
if (i == 0) {
return FALSE;
}
return TRUE;
}
void
zcache_mag_drain(zone_t zone, struct zcc_magazine *mag)
{
assert(mag->zcc_magazine_index == mag->zcc_magazine_capacity);
lock_zone(zone);
while (mag->zcc_magazine_index > 0) {
uint32_t index = --mag->zcc_magazine_index;
zcache_canary_validate(zone, mag->zcc_elements[index]);
zfree_direct(zone, (vm_offset_t)mag->zcc_elements[index]);
mag->zcc_elements[mag->zcc_magazine_index] = 0;
}
unlock_zone(zone);
}
void *
zcache_mag_pop(struct zcc_magazine *mag)
{
void *elem;
assert(zcache_mag_has_elements(mag));
elem = mag->zcc_elements[--mag->zcc_magazine_index];
mag->zcc_elements[mag->zcc_magazine_index] = NULL;
assert(elem != NULL);
return elem;
}
void
zcache_mag_push(struct zcc_magazine *mag, void *elem)
{
assert(zcache_mag_has_space(mag));
mag->zcc_elements[mag->zcc_magazine_index++] = elem;
}
bool
zcache_mag_has_space(struct zcc_magazine *mag)
{
return mag->zcc_magazine_index < mag->zcc_magazine_capacity;
}
bool
zcache_mag_has_elements(struct zcc_magazine *mag)
{
return mag->zcc_magazine_index > 0;
}
void
zcache_swap_magazines(struct zcc_magazine **a, struct zcc_magazine **b)
{
struct zcc_magazine *temp = *a;
*a = *b;
*b = temp;
}
void
zcache_mag_depot_swap_for_alloc(struct zone_cache *zcache, struct zcc_per_cpu_cache *cache)
{
assert(zcache_depot_available(zcache));
assert(zcache->zcc_depot_index > 0);
zcache->zcc_depot_index--;
zcache_swap_magazines(&cache->current, &zcache->zcc_depot_list[zcache->zcc_depot_index]);
}
void
zcache_mag_depot_swap_for_free(struct zone_cache *zcache, struct zcc_per_cpu_cache *cache)
{
assert(zcache_depot_available(zcache));
assert(zcache->zcc_depot_index < depot_element_count);
zcache_swap_magazines(&cache->current, &zcache->zcc_depot_list[zcache->zcc_depot_index]);
zcache->zcc_depot_index++;
}
void
zcache_canary_add(zone_t zone, void *element)
{
vm_offset_t *primary = (vm_offset_t *)element;
vm_offset_t *backup = (vm_offset_t *)((vm_offset_t)primary + zone->elem_size - sizeof(vm_offset_t));
*primary = *backup = (zcache_canary ^ (uintptr_t)element);
}
void
zcache_canary_validate(zone_t zone, void *element)
{
vm_offset_t *primary = (vm_offset_t *)element;
vm_offset_t *backup = (vm_offset_t *)((vm_offset_t)primary + zone->elem_size - sizeof(vm_offset_t));
vm_offset_t primary_value = (*primary ^ (uintptr_t)element);
if (primary_value != zcache_canary) {
panic("Zone cache element was used after free! Element %p was corrupted at beginning; Expected %p but found %p; canary %p; zone %p (%s)",
element, (void *)(zcache_canary ^ (uintptr_t)element), (void *)(*primary), (void *)zcache_canary, zone, zone->zone_name);
}
vm_offset_t backup_value = (*backup ^ (uintptr_t)element);
if (backup_value != zcache_canary) {
panic("Zone cache element was used after free! Element %p was corrupted at end; Expected %p but found %p; canary %p; zone %p (%s)",
element, (void *)(zcache_canary ^ (uintptr_t)element), (void *)(*backup), (void *)zcache_canary, zone, zone->zone_name);
}
}