blob: e49dbe6d8062d181b75c0b19a2321b0bece4a757 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
//
//
#include "handle_pool.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "block_pool.h"
#include "common/macros.h"
#include "common/vk/assert.h"
#include "common/vk/barrier.h"
#include "core_vk.h"
#include "device.h"
#include "dispatch.h"
#include "queue_pool.h"
#include "ring.h"
#include "spinel_assert.h"
#include "vk.h"
#include "vk_target.h"
//
// The handle pool allocates host-side path and raster handles.
//
// The handles are reference counted and have both an external "host"
// reference count as well as an internal "device" reference count.
//
// The device reference count indicates that the handle is being
// processed by a sub-pipeline and ensures that the handle isn't
// reclaimed immediately after it is defined and before it's
// materialized.
//
// There are two large extents:
//
// - a host-side pool of available handles
// - a host-side array of handle reference counts
//
// The bulk size of the three extents is 8 bytes of overhead per
// number of host handles plus additional storage for storing blocks
// of handles.
//
// The number of host handles is usually less than the number of block
// ids in the block pool.
//
// Note that the maximum number of blocks is currently 2^27 -- the
// number of blocks is less than or equal to the sublocks count.
//
// A practical instantiation might provide a combined 2^20 path and
// raster host handles. This would occupy over 8 MB of host RAM for
// the 32-bit handles, the 32-bit reference counts and additional
// storage for the "blocks of handles".
//
// For 2^20 handles, the device would allocate 4 MB of memory to map
// handles to block pool ids.
//
// Notes:
//
// - All host-side handles are stringently validated before being
// retained or released. If any handle is invalid, the entire set
// of handles is rejected.
//
// - The handle reference count is defensive and will not allow the
// host to underflow a handle that's still retained by the
// pipeline.
//
// - The single reference counter is split into host and device
// counts.
//
typedef uint32_t spn_handle_refcnt_hd;
typedef uint32_t spn_handle_refcnt_h;
typedef uint32_t spn_handle_refcnt_d;
//
// clang-format off
//
#define SPN_HANDLE_REFCNT_DEVICE_BITS 16
#define SPN_HANDLE_REFCNT_HOST_BITS (32 - SPN_HANDLE_REFCNT_DEVICE_BITS)
#define SPN_HANDLE_REFCNT_DEVICE_MAX BITS_TO_MASK_MACRO(SPN_HANDLE_REFCNT_DEVICE_BITS)
#define SPN_HANDLE_REFCNT_HOST_MAX BITS_TO_MASK_MACRO(SPN_HANDLE_REFCNT_HOST_BITS)
//
// The reference count packs two counters in one 32-bit word
//
// 0 31
// | HOST | DEVICE |
// +------+--------+
// | 16 | 16 |
//
//
// TODO(allanmac): The number of bits allocated to the device might
// become much lower.
//
union spn_handle_refcnt
{
spn_handle_refcnt_hd hd; // host and device
struct
{
spn_handle_refcnt_h h : SPN_HANDLE_REFCNT_HOST_BITS;
spn_handle_refcnt_d d : SPN_HANDLE_REFCNT_DEVICE_BITS;
};
};
STATIC_ASSERT_MACRO_1(sizeof(union spn_handle_refcnt) == sizeof(spn_handle_refcnt_hd));
//
// make sure these sizes always match
//
STATIC_ASSERT_MACRO_1(sizeof(struct spn_path) == sizeof(spn_handle_t));
STATIC_ASSERT_MACRO_1(sizeof(struct spn_raster) == sizeof(spn_handle_t));
//
// simple type punning
//
union spn_paths_to_handles
{
struct spn_path const * const paths;
spn_handle_t const * const handles;
};
union spn_rasters_to_handles
{
struct spn_raster const * const rasters;
spn_handle_t const * const handles;
};
//
//
//
struct spn_handle_pool_vk_dbi_dm
{
VkDescriptorBufferInfo dbi;
VkDeviceMemory dm;
};
//
//
//
struct spn_handle_pool_handle_ring
{
spn_handle_t * extent;
struct spn_ring ring;
};
//
//
//
struct spn_handle_pool_dispatch
{
uint32_t head;
uint32_t span;
bool complete;
};
//
//
//
struct spn_handle_pool_dispatch_ring
{
struct spn_handle_pool_dispatch * extent;
struct spn_ring ring;
};
//
//
//
typedef void (*spn_handle_pool_reclaim_pfn_flush)(struct spn_device * const device);
struct spn_handle_pool_reclaim
{
spn_handle_pool_reclaim_pfn_flush pfn_flush;
struct spn_handle_pool_vk_dbi_dm vk;
struct spn_handle_pool_handle_ring mapped;
struct spn_handle_pool_dispatch_ring dispatches;
};
//
//
//
struct spn_handle_pool
{
// ring of handles
struct spn_handle_pool_handle_ring handles;
// array of reference counts indexed by a handle
union spn_handle_refcnt * refcnts;
struct spn_handle_pool_reclaim paths;
struct spn_handle_pool_reclaim rasters;
};
//
//
//
struct spn_handle_pool_reclaim_complete_payload
{
struct spn_device * device;
struct spn_handle_pool_reclaim * reclaim;
struct spn_handle_pool_dispatch * dispatch;
struct spn_vk_ds_reclaim_t ds_reclaim;
};
//
// clang-format on
//
static struct spn_handle_pool_dispatch *
spn_handle_pool_reclaim_dispatch_head(struct spn_handle_pool_reclaim * const reclaim)
{
return (reclaim->dispatches.extent + reclaim->dispatches.ring.head);
}
static struct spn_handle_pool_dispatch *
spn_handle_pool_reclaim_dispatch_tail(struct spn_handle_pool_reclaim * const reclaim)
{
return (reclaim->dispatches.extent + reclaim->dispatches.ring.tail);
}
//
//
//
static void
spn_handle_pool_reclaim_dispatch_init(struct spn_handle_pool_reclaim * const reclaim)
{
struct spn_handle_pool_dispatch * const wip = spn_handle_pool_reclaim_dispatch_head(reclaim);
*wip = (struct spn_handle_pool_dispatch){ .head = reclaim->mapped.ring.head,
.span = 0,
.complete = false };
}
//
//
//
static void
spn_handle_pool_reclaim_dispatch_drop(struct spn_handle_pool_reclaim * const reclaim)
{
struct spn_ring * const ring = &reclaim->dispatches.ring;
spn_ring_drop_1(ring);
}
//
//
//
static void
spn_handle_pool_reclaim_dispatch_acquire(struct spn_handle_pool_reclaim * const reclaim,
struct spn_device * const device)
{
struct spn_ring * const ring = &reclaim->dispatches.ring;
while (spn_ring_is_empty(ring))
{
spn(device_wait(device, __func__));
}
spn_handle_pool_reclaim_dispatch_init(reclaim);
}
//
// See Vulkan specification's "Required Limits" section.
//
// clang-format off
#define SPN_VK_MAX_NONCOHERENT_ATOM_SIZE 256
#define SPN_VK_MAX_NONCOHERENT_ATOM_HANDLES (SPN_VK_MAX_NONCOHERENT_ATOM_SIZE / sizeof(spn_handle_t))
// clang-format on
//
//
//
static void
spn_handle_pool_reclaim_create(struct spn_handle_pool_reclaim * const reclaim,
struct spn_device * const device,
uint32_t const count_handles,
uint32_t count_dispatches)
{
//
// allocate device ring
//
spn_ring_init(&reclaim->mapped.ring, count_handles);
uint32_t const count_handles_ru = ROUND_UP_POW2_MACRO(count_handles, //
SPN_VK_MAX_NONCOHERENT_ATOM_HANDLES);
VkDeviceSize const size_ru = sizeof(*reclaim->mapped.extent) * count_handles_ru;
spn_allocator_device_perm_alloc(&device->allocator.device.perm.hrw_dr,
&device->environment,
size_ru,
NULL,
&reclaim->vk.dbi,
&reclaim->vk.dm);
//
// map device ring
//
vk(MapMemory(device->environment.d,
reclaim->vk.dm,
0,
VK_WHOLE_SIZE,
0,
(void **)&reclaim->mapped.extent));
//
// allocate and init dispatch ring
//
spn_ring_init(&reclaim->dispatches.ring, count_dispatches);
size_t const size_dispatches = sizeof(*reclaim->dispatches.extent) * count_dispatches;
reclaim->dispatches.extent = spn_allocator_host_perm_alloc(&device->allocator.host.perm,
SPN_MEM_FLAGS_READ_WRITE,
size_dispatches);
//
// init first dispatch
//
spn_handle_pool_reclaim_dispatch_init(reclaim);
}
//
//
//
static void
spn_handle_pool_reclaim_dispose(struct spn_handle_pool_reclaim * const reclaim,
struct spn_device * const device)
{
//
// free host allocations
//
spn_allocator_host_perm_free(&device->allocator.host.perm, reclaim->dispatches.extent);
//
// free device allocations
//
spn_allocator_device_perm_free(&device->allocator.device.perm.hrw_dr,
&device->environment,
&reclaim->vk.dbi,
reclaim->vk.dm);
}
//
//
//
static void
spn_handle_pool_copy(struct spn_ring * const from_ring,
spn_handle_t const * const from,
struct spn_ring * const to_ring,
spn_handle_t * const to,
uint32_t span)
{
while (span > 0)
{
uint32_t from_nowrap = spn_ring_tail_nowrap(from_ring);
uint32_t to_nowrap = spn_ring_tail_nowrap(to_ring);
uint32_t min_nowrap = MIN_MACRO(uint32_t, from_nowrap, to_nowrap);
uint32_t span_nowrap = MIN_MACRO(uint32_t, min_nowrap, span);
memcpy(to + to_ring->tail, from + from_ring->tail, sizeof(*to) * span_nowrap);
spn_ring_release_n(from_ring, span_nowrap);
spn_ring_release_n(to_ring, span_nowrap);
span -= span_nowrap;
}
}
//
//
//
static void
spn_handle_pool_reclaim_complete(void * const pfn_payload)
{
struct spn_handle_pool_reclaim_complete_payload const * const payload = pfn_payload;
// immediately release descriptor set
spn_vk_ds_release_reclaim(payload->device->instance, payload->ds_reclaim);
struct spn_handle_pool * const handle_pool = payload->device->handle_pool;
struct spn_handle_pool_reclaim * const reclaim = payload->reclaim;
struct spn_handle_pool_dispatch * dispatch = payload->dispatch;
//
// If the dispatch is the tail of the ring then release as many
// completed dispatch records as possible.
//
// Note that kernels can complete in any order so the release records
// need to be added to release ring slots in order.
//
if (reclaim->mapped.ring.tail == dispatch->head)
{
while (true)
{
// copy from mapped to handles
spn_handle_pool_copy(&reclaim->mapped.ring,
reclaim->mapped.extent,
&handle_pool->handles.ring,
handle_pool->handles.extent,
dispatch->span);
// release the dispatch
spn_ring_release_n(&reclaim->dispatches.ring, 1);
// any dispatches in flight?
if (spn_ring_is_full(&reclaim->dispatches.ring))
break;
// get next dispatch
dispatch = spn_handle_pool_reclaim_dispatch_tail(reclaim);
// is this dispatch still in flight?
if (!dispatch->complete)
break;
}
}
else
{
// out-of-order completion
dispatch->complete = true;
}
}
//
// Flush the noncoherent mapped ring
//
static void
spn_handle_pool_reclaim_flush_mapped(VkDevice vk_d, //
VkDeviceMemory ring,
uint32_t const size,
uint32_t const head,
uint32_t const span)
{
uint32_t const idx_max = head + span;
uint32_t const idx_hi = MIN_MACRO(uint32_t, idx_max, size);
uint32_t const span_hi = idx_hi - head;
uint32_t const idx_rd = ROUND_DOWN_POW2_MACRO(head, SPN_VK_MAX_NONCOHERENT_ATOM_HANDLES);
uint32_t const idx_hi_ru = ROUND_UP_POW2_MACRO(idx_hi, SPN_VK_MAX_NONCOHERENT_ATOM_HANDLES);
VkMappedMemoryRange mmr[2];
mmr[0].sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
mmr[0].pNext = NULL;
mmr[0].memory = ring;
mmr[0].offset = sizeof(spn_handle_t) * idx_rd;
mmr[0].size = sizeof(spn_handle_t) * (idx_hi_ru - idx_rd);
if (span <= span_hi)
{
vk(FlushMappedMemoryRanges(vk_d, 1, mmr));
}
else
{
uint32_t const span_lo = span - span_hi;
uint32_t const span_lo_ru = ROUND_UP_POW2_MACRO(span_lo, SPN_VK_MAX_NONCOHERENT_ATOM_HANDLES);
mmr[1].sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
mmr[1].pNext = NULL;
mmr[1].memory = ring;
mmr[1].offset = 0;
mmr[1].size = sizeof(spn_handle_t) * span_lo_ru;
vk(FlushMappedMemoryRanges(vk_d, 2, mmr));
}
}
//
// NOTE: the flush_paths() and flush_rasters() functions are nearly
// indentical but they might diverge in the future so there is no need
// to refactor.
//
static void
spn_device_handle_pool_flush_paths(struct spn_device * const device)
{
struct spn_handle_pool * const handle_pool = device->handle_pool;
struct spn_handle_pool_reclaim * const reclaim = &handle_pool->paths;
struct spn_handle_pool_dispatch * const wip = spn_handle_pool_reclaim_dispatch_head(reclaim);
// anything to do?
if (wip->span == 0)
return;
//
// if ring is not coherent then flush
//
struct spn_vk * const instance = device->instance;
struct spn_vk_target_config const * const config = spn_vk_get_config(instance);
if ((config->allocator.device.hrw_dr.properties & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) == 0)
{
spn_handle_pool_reclaim_flush_mapped(device->environment.d,
reclaim->vk.dm,
reclaim->mapped.ring.size,
wip->head,
wip->span);
}
//
// acquire an id for this stage
//
spn_dispatch_id_t id;
spn(device_dispatch_acquire(device, SPN_DISPATCH_STAGE_RECLAIM_PATHS, &id));
//
// bind descriptor set, push constants and pipeline
//
VkCommandBuffer cb = spn_device_dispatch_get_cb(device, id);
// bind global BLOCK_POOL descriptor set
spn_vk_ds_bind_paths_reclaim_block_pool(instance, cb, spn_device_block_pool_get_ds(device));
// acquire RECLAIM descriptor set
struct spn_vk_ds_reclaim_t ds_r;
spn_vk_ds_acquire_reclaim(instance, device, &ds_r);
// store the dbi
*spn_vk_ds_get_reclaim_reclaim(instance, ds_r) = reclaim->vk.dbi;
// update RECLAIM descriptor set
spn_vk_ds_update_reclaim(instance, &device->environment, ds_r);
// bind RECLAIM descriptor set
spn_vk_ds_bind_paths_reclaim_reclaim(instance, cb, ds_r);
// init and bind push constants
struct spn_vk_push_paths_reclaim const push = {
.bp_mask = spn_device_block_pool_get_mask(device),
.ring_size = reclaim->mapped.ring.size,
.ring_head = wip->head,
.ring_span = wip->span
};
spn_vk_p_push_paths_reclaim(instance, cb, &push);
// bind the RECLAIM_PATHS pipeline
spn_vk_p_bind_paths_reclaim(instance, cb);
//
// FIXME(allanmac): dispatch based on workgroup size
//
// dispatch one workgroup per reclamation block
vkCmdDispatch(cb, wip->span, 1, 1);
//
// set completion routine
//
struct spn_handle_pool_reclaim_complete_payload * const payload =
spn_device_dispatch_set_completion(device,
id,
spn_handle_pool_reclaim_complete,
sizeof(*payload));
payload->device = device;
payload->ds_reclaim = ds_r;
payload->reclaim = reclaim;
payload->dispatch = wip;
//
// the current dispatch is now "in flight" so drop it
//
spn_handle_pool_reclaim_dispatch_drop(reclaim);
//
// submit the dispatch
//
spn_device_dispatch_submit(device, id);
//
// acquire and initialize the next
//
spn_handle_pool_reclaim_dispatch_acquire(reclaim, device);
}
//
// NOTE: the flush_paths() and flush_rasters() functions are nearly
// indentical but they might diverge in the future so there is no need
// to refactor.
//
static void
spn_device_handle_pool_flush_rasters(struct spn_device * const device)
{
struct spn_handle_pool * const handle_pool = device->handle_pool;
struct spn_handle_pool_reclaim * const reclaim = &handle_pool->rasters;
struct spn_handle_pool_dispatch * const wip = spn_handle_pool_reclaim_dispatch_head(reclaim);
// anything to do?
if (wip->span == 0)
return;
//
// if ring is not coherent then flush
//
struct spn_vk * const instance = device->instance;
struct spn_vk_target_config const * const config = spn_vk_get_config(instance);
if ((config->allocator.device.hrw_dr.properties & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) == 0)
{
spn_handle_pool_reclaim_flush_mapped(device->environment.d,
reclaim->vk.dm,
reclaim->mapped.ring.size,
wip->head,
wip->span);
}
//
// acquire an id for this stage
//
spn_dispatch_id_t id;
spn(device_dispatch_acquire(device, SPN_DISPATCH_STAGE_RECLAIM_RASTERS, &id));
//
// bind descriptor set, push constants and pipeline
//
VkCommandBuffer cb = spn_device_dispatch_get_cb(device, id);
// bind global BLOCK_POOL descriptor set
spn_vk_ds_bind_rasters_reclaim_block_pool(instance, cb, spn_device_block_pool_get_ds(device));
// acquire RECLAIM descriptor set
struct spn_vk_ds_reclaim_t ds_r;
spn_vk_ds_acquire_reclaim(instance, device, &ds_r);
// store the dbi
*spn_vk_ds_get_reclaim_reclaim(instance, ds_r) = reclaim->vk.dbi;
// update RECLAIM descriptor set
spn_vk_ds_update_reclaim(instance, &device->environment, ds_r);
// bind RECLAIM descriptor set
spn_vk_ds_bind_rasters_reclaim_reclaim(instance, cb, ds_r);
// init and bind push constants
struct spn_vk_push_rasters_reclaim const push = {
.bp_mask = spn_device_block_pool_get_mask(device),
.ring_size = reclaim->mapped.ring.size,
.ring_head = wip->head,
.ring_span = wip->span
};
spn_vk_p_push_rasters_reclaim(instance, cb, &push);
// bind the RECLAIM_RASTERS pipeline
spn_vk_p_bind_rasters_reclaim(instance, cb);
//
// FIXME(allanmac): dispatch based on workgroup size
//
// dispatch one workgroup per reclamation block
vkCmdDispatch(cb, wip->span, 1, 1);
//
// set completion routine
//
struct spn_handle_pool_reclaim_complete_payload * const payload =
spn_device_dispatch_set_completion(device,
id,
spn_handle_pool_reclaim_complete,
sizeof(*payload));
payload->device = device;
payload->reclaim = reclaim;
payload->dispatch = wip;
payload->ds_reclaim = ds_r;
//
// the current dispatch is now sealed so drop it
//
spn_handle_pool_reclaim_dispatch_drop(reclaim);
//
// submit the dispatch
//
spn_device_dispatch_submit(device, id);
//
// acquire and initialize the next dispatch
//
spn_handle_pool_reclaim_dispatch_acquire(reclaim, device);
}
//
//
//
void
spn_device_handle_pool_create(struct spn_device * const device, uint32_t const handle_count)
{
//
// allocate the structure
//
struct spn_handle_pool * const handle_pool =
spn_allocator_host_perm_alloc(&device->allocator.host.perm,
SPN_MEM_FLAGS_READ_WRITE,
sizeof(*handle_pool));
device->handle_pool = handle_pool;
//
//
// allocate and init handles
//
spn_ring_init(&handle_pool->handles.ring, handle_count);
size_t const size_handles = sizeof(*handle_pool->handles.extent) * handle_count;
handle_pool->handles.extent = spn_allocator_host_perm_alloc(&device->allocator.host.perm,
SPN_MEM_FLAGS_READ_WRITE,
size_handles);
for (uint32_t ii = 0; ii < handle_count; ii++)
{
handle_pool->handles.extent[ii] = ii;
}
//
// allocate and init refcnts
//
size_t const size_refcnts = sizeof(*handle_pool->refcnts) * handle_count;
handle_pool->refcnts = spn_allocator_host_perm_alloc(&device->allocator.host.perm,
SPN_MEM_FLAGS_READ_WRITE,
size_refcnts);
memset(handle_pool->refcnts, 0, size_refcnts);
//
// initialize the reclamation rings
//
struct spn_vk_target_config const * const config = spn_vk_get_config(device->instance);
spn_handle_pool_reclaim_create(&handle_pool->paths,
device,
config->reclaim.size.paths,
config->reclaim.size.dispatches);
spn_handle_pool_reclaim_create(&handle_pool->rasters,
device,
config->reclaim.size.rasters,
config->reclaim.size.dispatches);
//
// initialize the flush pfns
//
handle_pool->paths.pfn_flush = spn_device_handle_pool_flush_paths;
handle_pool->rasters.pfn_flush = spn_device_handle_pool_flush_rasters;
}
//
//
//
void
spn_device_handle_pool_dispose(struct spn_device * const device)
{
struct spn_handle_pool * const handle_pool = device->handle_pool;
//
// There is currently no need to drain the reclamation rings before
// disposal because the entire context is being disposed. Any
// in-flight submissions will be drained elsewhere.
//
// free reclamation rings
spn_handle_pool_reclaim_dispose(&handle_pool->rasters, device);
spn_handle_pool_reclaim_dispose(&handle_pool->paths, device);
// free host allocations
spn_allocator_host_perm_free(&device->allocator.host.perm, handle_pool->refcnts);
spn_allocator_host_perm_free(&device->allocator.host.perm, handle_pool->handles.extent);
spn_allocator_host_perm_free(&device->allocator.host.perm, handle_pool);
}
//
//
//
uint32_t
spn_device_handle_pool_get_handle_count(struct spn_device * const device)
{
return device->handle_pool->handles.ring.size;
}
//
//
//
static void
spn_device_handle_pool_reclaim_h(struct spn_device * const device,
struct spn_handle_pool_reclaim * const reclaim,
union spn_handle_refcnt * const refcnts,
spn_handle_t const * handles,
uint32_t count)
{
struct spn_vk_target_config const * const config = spn_vk_get_config(device->instance);
//
// add handles to linear ring spans until done
//
while (count > 0)
{
//
// how many ring slots are available?
//
uint32_t head_nowrap;
while ((head_nowrap = spn_ring_head_nowrap(&reclaim->mapped.ring)) == 0)
{
// no need to flush here -- a flush would've already occurred
spn(device_wait(device, __func__));
}
//
// copy all releasable handles to the linear ring span
//
spn_handle_t * extent = reclaim->mapped.extent + reclaim->mapped.ring.head;
uint32_t rem = head_nowrap;
do
{
count -= 1;
spn_handle_t const handle = *handles++;
union spn_handle_refcnt * const refcnt_ptr = refcnts + handle;
union spn_handle_refcnt refcnt = *refcnt_ptr;
refcnt.h--;
*refcnt_ptr = refcnt;
if (refcnt.hd == 0)
{
*extent++ = handle;
if (--rem == 0)
break;
}
} while (count > 0);
//
// were no handles appended?
//
uint32_t const span = head_nowrap - rem;
if (span == 0)
return;
//
// update ring
//
spn_ring_drop_n(&reclaim->mapped.ring, span);
struct spn_handle_pool_dispatch * const wip = spn_handle_pool_reclaim_dispatch_head(reclaim);
wip->span += span;
//
// flush?
//
if (wip->span >= config->reclaim.size.eager)
{
reclaim->pfn_flush(device);
}
}
}
//
//
//
static void
spn_device_handle_pool_reclaim_d(struct spn_device * const device,
struct spn_handle_pool_reclaim * const reclaim,
union spn_handle_refcnt * const refcnts,
spn_handle_t const * handles,
uint32_t count)
{
struct spn_vk_target_config const * const config = spn_vk_get_config(device->instance);
//
// add handles to linear ring spans until done
//
while (count > 0)
{
//
// how many ring slots are available?
//
uint32_t head_nowrap;
while ((head_nowrap = spn_ring_head_nowrap(&reclaim->mapped.ring)) == 0)
{
// no need to flush here -- a flush would've already occurred
spn(device_wait(device, __func__));
}
//
// copy all releasable handles to the linear ring span
//
spn_handle_t * extent = reclaim->mapped.extent + reclaim->mapped.ring.head;
uint32_t rem = head_nowrap;
do
{
count -= 1;
spn_handle_t const handle = *handles++;
union spn_handle_refcnt * const refcnt_ptr = refcnts + handle;
union spn_handle_refcnt refcnt = *refcnt_ptr;
refcnt.d--;
*refcnt_ptr = refcnt;
if (refcnt.hd == 0)
{
*extent++ = handle;
if (--rem == 0)
break;
}
} while (count > 0);
//
// were no handles appended?
//
uint32_t const span = head_nowrap - rem;
if (span == 0)
return;
//
// update ring
//
spn_ring_drop_n(&reclaim->mapped.ring, span);
struct spn_handle_pool_dispatch * const wip = spn_handle_pool_reclaim_dispatch_head(reclaim);
wip->span += span;
//
// flush?
//
if (wip->span >= config->reclaim.size.eager)
{
reclaim->pfn_flush(device);
}
}
}
//
// NOTE(allanmac): A batch-oriented version of this function will likely
// be required when the batch API is exposed. For now, the Spinel API
// is implicitly acquiring one handle at a time.
//
void
spn_device_handle_pool_acquire(struct spn_device * const device, spn_handle_t * const p_handle)
{
//
// FIXME(allanmac): Running out of handles usually implies the app is
// not reclaiming unused handles or the handle pool is too small.
// Either case can be considered fatal unless reclamations are in
// flight.
//
// This condition may need to be surfaced through the API ... or
// simply kill the device with spn_device_lost() and log the reason.
//
// A comprehensive solution can be surfaced *after* the block pool
// allocation becomes more precise.
//
struct spn_handle_pool * const handle_pool = device->handle_pool;
struct spn_ring * const ring = &handle_pool->handles.ring;
while (ring->rem == 0)
{
// flush both reclamation rings
bool const flushable_paths = !spn_ring_is_full(&handle_pool->paths.mapped.ring);
bool const flushable_rasters = !spn_ring_is_full(&handle_pool->rasters.mapped.ring);
if (!flushable_paths && !flushable_rasters)
{
spn_device_lost(device);
}
else
{
if (flushable_paths)
{
spn_device_handle_pool_flush_paths(device);
}
if (flushable_rasters)
{
spn_device_handle_pool_flush_rasters(device);
}
spn(device_wait(device, __func__));
}
}
uint32_t const idx = spn_ring_acquire_1(ring);
spn_handle_t const handle = handle_pool->handles.extent[idx];
handle_pool->refcnts[handle] = (union spn_handle_refcnt){ .h = 1, .d = 1 };
*p_handle = handle;
}
//
// Validate host-provided handles before retaining.
//
// Retain validation consists of:
//
// - correct handle type
// - handle is in range of pool
// - host refcnt is not zero
// - host refcnt is not at the maximum value
//
// After validation, go ahead and retain the handles for the host.
//
static spn_result_t
spn_device_handle_pool_validate_retain_h(struct spn_device * const device,
spn_handle_t const * const handles,
uint32_t count)
{
struct spn_handle_pool * const handle_pool = device->handle_pool;
union spn_handle_refcnt * const refcnts = handle_pool->refcnts;
uint32_t const handle_max = handle_pool->handles.ring.size;
for (uint32_t ii = 0; ii < count; ii++)
{
spn_handle_t const handle = handles[ii];
if (handle >= handle_max)
{
return SPN_ERROR_HANDLE_INVALID;
}
else
{
union spn_handle_refcnt const refcnt = refcnts[handle];
if (refcnt.h == 0)
{
return SPN_ERROR_HANDLE_INVALID;
}
else if (refcnt.h == SPN_HANDLE_REFCNT_HOST_MAX)
{
return SPN_ERROR_HANDLE_OVERFLOW;
}
}
}
//
// all the handles validated, so retain them all..
//
for (uint32_t ii = 0; ii < count; ii++)
{
spn_handle_t const handle = handles[ii];
refcnts[handle].h++;
}
return SPN_SUCCESS;
}
spn_result_t
spn_device_handle_pool_validate_retain_h_paths(struct spn_device * const device,
struct spn_path const * const paths,
uint32_t count)
{
union spn_paths_to_handles const p2h = { paths };
return spn_device_handle_pool_validate_retain_h(device, p2h.handles, count);
}
spn_result_t
spn_device_handle_pool_validate_retain_h_rasters(struct spn_device * const device,
struct spn_raster const * const rasters,
uint32_t count)
{
union spn_rasters_to_handles const r2h = { rasters };
return spn_device_handle_pool_validate_retain_h(device, r2h.handles, count);
}
//
// Validate host-provided handles before releasing.
//
// Release validation consists of:
//
// - handle is in range of pool
// - host refcnt is not zero
//
// After validation, release the handles for the host
//
static spn_result_t
spn_device_handle_pool_validate_release_h(struct spn_handle_pool * const handle_pool,
union spn_handle_refcnt * const refcnts,
spn_handle_t const * const handles,
uint32_t count)
{
uint32_t const handle_max = handle_pool->handles.ring.size;
//
// validate
//
for (uint32_t ii = 0; ii < count; ii++)
{
spn_handle_t const handle = handles[ii];
if (handle >= handle_max)
{
return SPN_ERROR_HANDLE_INVALID;
}
else
{
union spn_handle_refcnt const refcnt = refcnts[handle];
if (refcnt.h == 0)
{
return SPN_ERROR_HANDLE_INVALID;
}
}
}
//
// all the handles validated
//
return SPN_SUCCESS;
}
//
//
//
spn_result_t
spn_device_handle_pool_validate_release_h_paths(struct spn_device * const device,
struct spn_path const * paths,
uint32_t count)
{
struct spn_handle_pool * const handle_pool = device->handle_pool;
union spn_handle_refcnt * const refcnts = handle_pool->refcnts;
union spn_paths_to_handles const p2h = { paths };
spn_result_t const result = spn_device_handle_pool_validate_release_h(handle_pool, //
refcnts,
p2h.handles,
count);
if (result == SPN_SUCCESS)
{
spn_device_handle_pool_reclaim_h(device, &handle_pool->paths, refcnts, p2h.handles, count);
}
return result;
}
spn_result_t
spn_device_handle_pool_validate_release_h_rasters(struct spn_device * const device,
struct spn_raster const * rasters,
uint32_t count)
{
struct spn_handle_pool * const handle_pool = device->handle_pool;
union spn_handle_refcnt * const refcnts = handle_pool->refcnts;
union spn_rasters_to_handles const r2h = { rasters };
spn_result_t const result = spn_device_handle_pool_validate_release_h(handle_pool, //
refcnts,
r2h.handles,
count);
if (result == SPN_SUCCESS)
{
spn_device_handle_pool_reclaim_h(device, &handle_pool->rasters, refcnts, r2h.handles, count);
}
return result;
}
//
// Validate host-provided handles before retaining on the device.
//
// - handle is in range of pool
// - host refcnt is not zero
// - device refcnt is not at the maximum value
//
static spn_result_t
spn_device_handle_pool_validate_retain_d(struct spn_device * const device,
spn_handle_t const * const handles,
uint32_t const count)
{
struct spn_handle_pool * const handle_pool = device->handle_pool;
union spn_handle_refcnt * const refcnts = handle_pool->refcnts;
uint32_t const handle_max = handle_pool->handles.ring.size;
for (uint32_t ii = 0; ii < count; ii++)
{
spn_handle_t const handle = handles[ii];
if (handle >= handle_max)
{
return SPN_ERROR_HANDLE_INVALID;
}
else
{
union spn_handle_refcnt const refcnt = refcnts[handle];
if (refcnt.h == 0)
{
return SPN_ERROR_HANDLE_INVALID;
}
else if (refcnt.d == SPN_HANDLE_REFCNT_DEVICE_MAX)
{
return SPN_ERROR_HANDLE_OVERFLOW;
}
}
}
return SPN_SUCCESS;
}
spn_result_t
spn_device_handle_pool_validate_d_paths(struct spn_device * const device,
struct spn_path const * const paths,
uint32_t const count)
{
union spn_paths_to_handles const p2h = { paths };
return spn_device_handle_pool_validate_retain_d(device, p2h.handles, count);
}
spn_result_t
spn_device_handle_pool_validate_d_rasters(struct spn_device * const device,
struct spn_raster const * const rasters,
uint32_t const count)
{
union spn_rasters_to_handles const r2h = { rasters };
return spn_device_handle_pool_validate_retain_d(device, r2h.handles, count);
}
//
// After explicit validation, retain the handles for the device
//
static void
spn_device_handle_pool_retain_d(struct spn_device * const device,
spn_handle_t const * const handles,
uint32_t const count)
{
struct spn_handle_pool * const handle_pool = device->handle_pool;
union spn_handle_refcnt * const refcnts = handle_pool->refcnts;
for (uint32_t ii = 0; ii < count; ii++)
{
spn_handle_t const handle = handles[ii];
refcnts[handle].d++;
}
}
void
spn_device_handle_pool_retain_d_paths(struct spn_device * const device,
struct spn_path const * const paths,
uint32_t const count)
{
union spn_paths_to_handles const p2h = { paths };
spn_device_handle_pool_retain_d(device, p2h.handles, count);
}
void
spn_device_handle_pool_retain_d_rasters(struct spn_device * const device,
struct spn_raster const * const rasters,
uint32_t const count)
{
union spn_rasters_to_handles const r2h = { rasters };
spn_device_handle_pool_retain_d(device, r2h.handles, count);
}
//
// Release device-held spans of handles of known type
//
void
spn_device_handle_pool_release_d_paths(struct spn_device * const device,
spn_handle_t const * handles,
uint32_t count)
{
struct spn_handle_pool * const handle_pool = device->handle_pool;
struct spn_handle_pool_reclaim * const reclaim = &handle_pool->paths;
union spn_handle_refcnt * const refcnts = handle_pool->refcnts;
spn_device_handle_pool_reclaim_d(device, reclaim, refcnts, handles, count);
}
void
spn_device_handle_pool_release_d_rasters(struct spn_device * const device,
spn_handle_t const * handles,
uint32_t count)
{
struct spn_handle_pool * const handle_pool = device->handle_pool;
struct spn_handle_pool_reclaim * const reclaim = &handle_pool->rasters;
union spn_handle_refcnt * const refcnts = handle_pool->refcnts;
spn_device_handle_pool_reclaim_d(device, reclaim, refcnts, handles, count);
}
//
// Release handles on a ring -- up to two spans
//
void
spn_device_handle_pool_release_ring_d_paths(struct spn_device * const device,
spn_handle_t const * paths,
uint32_t const size,
uint32_t const head,
uint32_t const span)
{
struct spn_handle_pool * const handle_pool = device->handle_pool;
struct spn_handle_pool_reclaim * const reclaim = &handle_pool->paths;
union spn_handle_refcnt * const refcnts = handle_pool->refcnts;
uint32_t const head_max = head + span;
uint32_t count_lo = MIN_MACRO(uint32_t, head_max, size) - head;
spn_device_handle_pool_reclaim_d(device, reclaim, refcnts, paths + head, count_lo);
if (span > count_lo)
{
uint32_t count_hi = span - count_lo;
spn_device_handle_pool_reclaim_d(device, reclaim, refcnts, paths, count_hi);
}
}
void
spn_device_handle_pool_release_ring_d_rasters(struct spn_device * const device,
spn_handle_t const * rasters,
uint32_t const size,
uint32_t const head,
uint32_t const span)
{
struct spn_handle_pool * const handle_pool = device->handle_pool;
struct spn_handle_pool_reclaim * const reclaim = &handle_pool->rasters;
union spn_handle_refcnt * const refcnts = handle_pool->refcnts;
uint32_t const head_max = head + span;
uint32_t count_lo = MIN_MACRO(uint32_t, head_max, size) - head;
spn_device_handle_pool_reclaim_d(device, reclaim, refcnts, rasters + head, count_lo);
if (span > count_lo)
{
uint32_t count_hi = span - count_lo;
spn_device_handle_pool_reclaim_d(device, reclaim, refcnts, rasters, count_hi);
}
}
//
//
//