blob: 85cde3e5681ff2cf2ac3fa1a90c81e2411372937 [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 <stdlib.h>
#include <string.h>
#include "common/macros.h"
#include "common/vk/assert.h"
#include "common/vk/barrier.h"
#include "flush.h"
#include "ring.h"
#include "shaders/push.h"
#include "spinel/spinel_assert.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 several large host extents:
//
// - a host-side pool of available handles -- 4 bytes
// - a host-side array of handle reference counts -- 4 bytes
// - a host-side array of handle semaphore indices -- 1-2 bytes
//
// And one large device extent:
//
// - a device-side handle-to-block id map -- 4 bytes
//
// The aggregate size of the three host extents is ~9-10 bytes of overhead per
// number of host handles plus additional storage for storing blocks of handles.
//
// The device extent allocates ~4 bytes per handle.
//
// The number of host handles should be 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 9-10 MB of host RAM for the 32-bit handles, the
// 32-bit reference counts and additional storage for the "blocks of handles".
//
// 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.
//
// - There is currently a reference count limit of USHORT_MAX on both host and
// device. If this is deemed to be too small, then increase the reference
// count union to 64 bits.
//
typedef uint32_t spinel_handle_refcnt_hd;
typedef uint32_t spinel_handle_refcnt_h;
typedef uint32_t spinel_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 |
//
union spinel_handle_refcnt
{
spinel_handle_refcnt_hd hd; // host and device
struct
{
spinel_handle_refcnt_h h : SPN_HANDLE_REFCNT_HOST_BITS;
spinel_handle_refcnt_d d : SPN_HANDLE_REFCNT_DEVICE_BITS;
};
};
//
// Doublecheck some size assumptions in case modifications are made.
//
STATIC_ASSERT_MACRO_1(sizeof(union spinel_handle_refcnt) == sizeof(spinel_handle_refcnt_hd));
STATIC_ASSERT_MACRO_1(sizeof(struct spinel_path) == sizeof(spinel_handle_t));
STATIC_ASSERT_MACRO_1(sizeof(struct spinel_raster) == sizeof(spinel_handle_t));
//
// Type punning unions
//
union spinel_paths_to_handles
{
struct spinel_path const * const paths;
spinel_handle_t const * const handles;
};
union spinel_rasters_to_handles
{
struct spinel_raster const * const rasters;
spinel_handle_t const * const handles;
};
// clang-format on
//
// Handle ring allocator
//
struct spinel_handle_pool_handle_ring
{
spinel_handle_t * extent;
struct spinel_ring ring;
};
//
// Dispatch states
//
typedef enum spinel_hp_dispatch_state_e
{
SPN_HP_DISPATCH_STATE_INVALID,
SPN_HP_DISPATCH_STATE_RECORDING,
SPN_HP_DISPATCH_STATE_PENDING,
SPN_HP_DISPATCH_STATE_COMPLETE,
} spinel_hp_dispatch_state_e;
//
// Dispatches can complete in any order but are reclaimed in ring order.
//
struct spinel_handle_pool_dispatch
{
struct
{
uint32_t head;
uint32_t span;
} ring;
spinel_hp_dispatch_state_e state;
};
//
// Vulkan dispatch pool
//
struct spinel_handle_pool_dispatch_ring
{
struct spinel_handle_pool_dispatch * extent;
struct spinel_ring ring;
};
//
//
//
typedef void (*spinel_handle_pool_reclaim_flush_pfn)(struct spinel_device * device);
//
//
//
struct spinel_handle_pool_reclaim
{
struct spinel_dbi_dm_devaddr vk;
struct spinel_handle_pool_handle_ring mapped;
struct spinel_handle_pool_dispatch_ring dispatches;
};
//
//
//
struct spinel_handle_pool
{
//
// The handles and their refcnts
//
struct spinel_handle_pool_handle_ring handles;
union spinel_handle_refcnt * refcnts;
//
// Separate reclamation accounting for paths and rasters
//
struct spinel_handle_pool_reclaim paths;
struct spinel_handle_pool_reclaim rasters;
};
//
// The handle pool is reentrant. This means that a handle pool completion
// routine could invoke a handle pool flush and/or submission.
//
// Delaying acquisition and initialization until actually needing the head
// dispatch dodges a lot of complexity.
//
static struct spinel_handle_pool_dispatch *
spinel_handle_pool_reclaim_dispatch_head(struct spinel_handle_pool_reclaim * reclaim,
struct spinel_device * device)
{
//
// Wait for an available dispatch
//
struct spinel_ring * const ring = &reclaim->dispatches.ring;
while (spinel_ring_is_empty(ring))
{
spinel_deps_drain_1(device->deps, &device->vk);
}
//
// Get "work in progress" (wip) dispatch. This implicitly the head dispatch.
//
struct spinel_handle_pool_dispatch * const wip = reclaim->dispatches.extent + ring->head;
assert(wip->state != SPN_HP_DISPATCH_STATE_PENDING);
assert(wip->state != SPN_HP_DISPATCH_STATE_COMPLETE);
//
// Acquiring and initializing a dispatch is reentrant so we track
// initialization.
//
if (wip->state == SPN_HP_DISPATCH_STATE_INVALID)
{
*wip = (struct spinel_handle_pool_dispatch){
.ring = {
.head = reclaim->mapped.ring.head,
.span = 0,
},
.state = SPN_HP_DISPATCH_STATE_RECORDING,
};
}
return wip;
}
//
//
//
static struct spinel_handle_pool_dispatch *
spinel_handle_pool_reclaim_dispatch_tail(struct spinel_handle_pool_reclaim * reclaim)
{
assert(!spinel_ring_is_full(&reclaim->dispatches.ring));
return (reclaim->dispatches.extent + reclaim->dispatches.ring.tail);
}
//
//
//
static void
spinel_handle_pool_reclaim_dispatch_drop(struct spinel_handle_pool_reclaim * reclaim)
{
struct spinel_ring * const ring = &reclaim->dispatches.ring;
spinel_ring_drop_1(ring);
}
//
//
//
static void
spinel_handle_pool_reclaim_create(struct spinel_handle_pool_reclaim * reclaim,
struct spinel_device * device,
uint32_t const count_handles,
uint32_t count_dispatches)
{
//
// allocate device ring
//
spinel_ring_init(&reclaim->mapped.ring, count_handles);
VkDeviceSize const extent_size = sizeof(*reclaim->mapped.extent) * count_handles;
VkDeviceSize const extent_size_ru = ROUND_UP_POW2_MACRO(extent_size, //
device->vk.limits.noncoherent_atom_size);
spinel_allocator_alloc_dbi_dm_devaddr(&device->allocator.device.perm.hrw_dr,
device->vk.pd,
device->vk.d,
device->vk.ac,
extent_size_ru,
NULL,
&reclaim->vk);
//
// map device ring
//
vk(MapMemory(device->vk.d, //
reclaim->vk.dbi_dm.dm,
0,
VK_WHOLE_SIZE,
0,
(void **)&reclaim->mapped.extent));
//
// allocate and init dispatch ring
//
// implicitly sets .state to SPN_HP_DISPATCH_STATE_INVALID
//
spinel_ring_init(&reclaim->dispatches.ring, count_dispatches);
reclaim->dispatches.extent = CALLOC_MACRO(count_dispatches, sizeof(*reclaim->dispatches.extent));
}
//
//
//
static void
spinel_handle_pool_reclaim_dispose(struct spinel_handle_pool_reclaim * reclaim,
struct spinel_device * device)
{
//
// free host allocations
//
free(reclaim->dispatches.extent);
//
// free device allocations
//
vkUnmapMemory(device->vk.d, reclaim->vk.dbi_dm.dm); // not necessary
spinel_allocator_free_dbi_dm(&device->allocator.device.perm.hrw_dr,
device->vk.d,
device->vk.ac,
&reclaim->vk.dbi_dm);
}
//
//
//
static void
spinel_handle_pool_copy(struct spinel_ring * from_ring,
spinel_handle_t const * from,
struct spinel_ring * to_ring,
spinel_handle_t * to,
uint32_t span)
{
while (span > 0)
{
uint32_t from_nowrap = spinel_ring_tail_nowrap(from_ring);
uint32_t to_nowrap = spinel_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);
spinel_ring_release_n(from_ring, span_nowrap);
spinel_ring_release_n(to_ring, span_nowrap);
span -= span_nowrap;
}
}
//
//
//
static void
spinel_handle_pool_reclaim_flush_complete(struct spinel_handle_pool * handle_pool,
struct spinel_handle_pool_reclaim * reclaim,
struct spinel_handle_pool_dispatch * 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.
//
// FIXME(allanmac): The handles can be returned early.
//
dispatch->state = SPN_HP_DISPATCH_STATE_COMPLETE;
struct spinel_handle_pool_dispatch * tail = spinel_handle_pool_reclaim_dispatch_tail(reclaim);
while (tail->state == SPN_HP_DISPATCH_STATE_COMPLETE)
{
// will always be true
assert(reclaim->mapped.ring.tail == tail->ring.head);
// copy from mapped to handles and release slots
spinel_handle_pool_copy(&reclaim->mapped.ring,
reclaim->mapped.extent,
&handle_pool->handles.ring,
handle_pool->handles.extent,
tail->ring.span);
// release the dispatch
spinel_ring_release_n(&reclaim->dispatches.ring, 1);
// mark as invalid
tail->state = SPN_HP_DISPATCH_STATE_INVALID;
// any remaining dispatches in flight?
if (spinel_ring_is_full(&reclaim->dispatches.ring))
{
break;
}
// get next dispatch
tail = spinel_handle_pool_reclaim_dispatch_tail(reclaim);
}
}
//
//
//
static void
spinel_handle_pool_reclaim_flush_paths_complete(void * data0, void * data1)
{
struct spinel_device * device = data0;
struct spinel_handle_pool * handle_pool = device->handle_pool;
struct spinel_handle_pool_reclaim * reclaim = &handle_pool->paths;
struct spinel_handle_pool_dispatch * dispatch = data1;
spinel_handle_pool_reclaim_flush_complete(handle_pool, reclaim, dispatch);
}
static void
spinel_handle_pool_reclaim_flush_rasters_complete(void * data0, void * data1)
{
struct spinel_device * device = data0;
struct spinel_handle_pool * handle_pool = device->handle_pool;
struct spinel_handle_pool_reclaim * reclaim = &handle_pool->rasters;
struct spinel_handle_pool_dispatch * dispatch = data1;
spinel_handle_pool_reclaim_flush_complete(handle_pool, reclaim, dispatch);
}
//
// Record path reclamation commands
//
static VkPipelineStageFlags
spinel_handle_pool_reclaim_flush_paths_record(VkCommandBuffer cb, void * data0, void * data1)
{
// clang-format off
struct spinel_device * const device = data0;
struct spinel_handle_pool * const handle_pool = device->handle_pool;
struct spinel_handle_pool_reclaim * const reclaim = &handle_pool->paths;
struct spinel_handle_pool_dispatch * const wip = data1;
// clang-format on
assert(wip->ring.span > 0);
//
// If ring is not coherent then flush
//
if (!spinel_allocator_is_coherent(&device->allocator.device.perm.hrw_dr))
{
spinel_ring_flush(&device->vk,
reclaim->vk.dbi_dm.dm,
0UL,
reclaim->mapped.ring.size,
wip->ring.head,
wip->ring.span,
sizeof(spinel_handle_t));
}
//
// Record commands
//
struct spinel_block_pool * const block_pool = &device->block_pool;
struct spinel_push_reclaim const push = {
.devaddr_reclaim = handle_pool->paths.vk.devaddr,
.devaddr_block_pool_ids = block_pool->vk.dbi_devaddr.ids.devaddr,
.devaddr_block_pool_blocks = block_pool->vk.dbi_devaddr.blocks.devaddr,
.devaddr_block_pool_host_map = block_pool->vk.dbi_devaddr.host_map.devaddr,
.ring_size = reclaim->mapped.ring.size,
.ring_head = wip->ring.head,
.ring_span = wip->ring.span,
.bp_mask = block_pool->bp_mask,
};
vkCmdPushConstants(cb,
device->ti.pipeline_layouts.named.paths_reclaim,
VK_SHADER_STAGE_COMPUTE_BIT,
0,
sizeof(push),
&push);
vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_COMPUTE, device->ti.pipelines.named.paths_reclaim);
//
// Dispatch a subgroup per span element
//
struct spinel_target_config const * const config = &device->ti.config;
uint32_t const sgs_per_wg = config->group_sizes.named.paths_reclaim.workgroup >>
config->group_sizes.named.paths_reclaim.subgroup_log2;
uint32_t const span_wgs = (wip->ring.span + sgs_per_wg - 1) / sgs_per_wg;
vkCmdDispatch(cb, span_wgs, 1, 1);
//
// This command buffer ends with a compute shader
//
return VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
}
//
// NOTE: the flush_paths() and flush_rasters() functions are nearly identical
// but they might diverge in the future so there is no need to refactor.
//
static void
spinel_handle_pool_reclaim_flush_paths(struct spinel_device * device)
{
// clang-format off
struct spinel_handle_pool * const handle_pool = device->handle_pool;
struct spinel_handle_pool_reclaim * const reclaim = &handle_pool->paths;
struct spinel_handle_pool_dispatch * const wip = spinel_handle_pool_reclaim_dispatch_head(reclaim, device);
// clang-format on
//
// Anything to do?
//
if (wip->ring.span == 0)
{
return;
}
//
// Acquire an immediate semaphore
//
struct spinel_deps_immediate_submit_info const disi = {
.record = {
.pfn = spinel_handle_pool_reclaim_flush_paths_record,
.data0 = device,
.data1 = wip,
},
.completion = {
.pfn = spinel_handle_pool_reclaim_flush_paths_complete,
.data0 = device,
.data1 = wip,
},
};
//
// The current dispatch is now "in flight" so drop it
//
// Note that usually it doesn't matter if you drop the dispatch before or
// after submission but because handle reclamation is reentrant it does matter
// and instead a submission will simply work on the head dispatch and any
// prior submissions may potentially submit smaller than "eager" sized or
// empty dispatches.
//
spinel_handle_pool_reclaim_dispatch_drop(reclaim);
//
// Move to pending state
//
wip->state = SPN_HP_DISPATCH_STATE_PENDING;
//
// Submit!
//
spinel_deps_immediate_semaphore_t immediate;
spinel_deps_immediate_submit(device->deps, &device->vk, &disi, &immediate);
}
//
// Record raster reclamation commands
//
static VkPipelineStageFlags
spinel_handle_pool_reclaim_flush_rasters_record(VkCommandBuffer cb, void * data0, void * data1)
{
// clang-format off
struct spinel_device * const device = data0;
struct spinel_handle_pool * const handle_pool = device->handle_pool;
struct spinel_handle_pool_reclaim * const reclaim = &handle_pool->rasters;
struct spinel_handle_pool_dispatch * const wip = data1;
// clang-format on
assert(wip->ring.span > 0);
//
// If ring is not coherent then flush
//
if (!spinel_allocator_is_coherent(&device->allocator.device.perm.hrw_dr))
{
spinel_ring_flush(&device->vk,
reclaim->vk.dbi_dm.dm,
0UL,
reclaim->mapped.ring.size,
wip->ring.head,
wip->ring.span,
sizeof(spinel_handle_t));
}
//
// Record commands
//
struct spinel_block_pool const * block_pool = &device->block_pool;
struct spinel_push_reclaim const push = {
.devaddr_reclaim = handle_pool->rasters.vk.devaddr,
.devaddr_block_pool_ids = block_pool->vk.dbi_devaddr.ids.devaddr,
.devaddr_block_pool_blocks = block_pool->vk.dbi_devaddr.blocks.devaddr,
.devaddr_block_pool_host_map = block_pool->vk.dbi_devaddr.host_map.devaddr,
.ring_size = reclaim->mapped.ring.size,
.ring_head = wip->ring.head,
.ring_span = wip->ring.span,
.bp_mask = block_pool->bp_mask,
};
vkCmdPushConstants(cb,
device->ti.pipeline_layouts.named.rasters_reclaim,
VK_SHADER_STAGE_COMPUTE_BIT,
0,
sizeof(push),
&push);
vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_COMPUTE, device->ti.pipelines.named.rasters_reclaim);
//
// Dispatch a subgroup per span element
//
struct spinel_target_config const * const config = &device->ti.config;
uint32_t const sgs_per_wg = config->group_sizes.named.rasters_reclaim.workgroup >>
config->group_sizes.named.rasters_reclaim.subgroup_log2;
uint32_t const span_wgs = (wip->ring.span + sgs_per_wg - 1) / sgs_per_wg;
vkCmdDispatch(cb, span_wgs, 1, 1);
//
// This command buffer ends with a compute shader
//
return VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
}
//
// NOTE: the flush_paths() and flush_rasters() functions are nearly identical
// but they might diverge in the future so there is no need to refactor.
//
static void
spinel_handle_pool_reclaim_flush_rasters(struct spinel_device * device)
{
// clang-format off
struct spinel_handle_pool * const handle_pool = device->handle_pool;
struct spinel_handle_pool_reclaim * const reclaim = &handle_pool->rasters;
struct spinel_handle_pool_dispatch * const wip = spinel_handle_pool_reclaim_dispatch_head(reclaim, device);
// clang-format on
//
// Anything to do?
//
if (wip->ring.span == 0)
{
return;
}
//
// Acquire an immediate semaphore
//
struct spinel_deps_immediate_submit_info const disi = {
.record = {
.pfn = spinel_handle_pool_reclaim_flush_rasters_record,
.data0 = device,
.data1 = wip,
},
.completion = {
.pfn = spinel_handle_pool_reclaim_flush_rasters_complete,
.data0 = device,
.data1 = wip,
},
};
//
// The current dispatch is now "in flight" so drop it
//
// Note that usually it doesn't matter if you drop the dispatch before or
// after submission but because handle reclamation is reentrant it does matter
// and instead a submission will simply work on the head dispatch and any
// prior submissions may potentially submit smaller than "eager" sized or
// empty dispatches.
//
spinel_handle_pool_reclaim_dispatch_drop(reclaim);
//
// Move to pending state
//
wip->state = SPN_HP_DISPATCH_STATE_PENDING;
//
// Submit!
//
spinel_deps_immediate_semaphore_t immediate;
spinel_deps_immediate_submit(device->deps, &device->vk, &disi, &immediate);
}
//
//
//
void
spinel_device_handle_pool_create(struct spinel_device * device, uint32_t handle_count)
{
//
//
//
struct spinel_handle_pool * const handle_pool = MALLOC_MACRO(sizeof(*handle_pool));
device->handle_pool = handle_pool;
//
// allocate and init handles
//
spinel_ring_init(&handle_pool->handles.ring, handle_count);
size_t const size_handles = sizeof(*handle_pool->handles.extent) * handle_count;
handle_pool->handles.extent = MALLOC_MACRO(size_handles);
for (uint32_t ii = 0; ii < handle_count; ii++)
{
handle_pool->handles.extent[ii] = ii;
}
//
// allocate and init refcnts
//
handle_pool->refcnts = CALLOC_MACRO(handle_count, sizeof(*handle_pool->refcnts));
//
// some target invariants
//
struct spinel_target_config const * const config = &device->ti.config;
//
// A single reclamation invocation must never fill the entire reclamation ring.
//
assert(config->reclaim.size.paths > config->reclaim.size.eager);
assert(config->reclaim.size.rasters > config->reclaim.size.eager);
//
// initialize the reclamation rings
//
spinel_handle_pool_reclaim_create(&handle_pool->paths,
device,
config->reclaim.size.paths,
config->reclaim.size.dispatches);
spinel_handle_pool_reclaim_create(&handle_pool->rasters,
device,
config->reclaim.size.rasters,
config->reclaim.size.dispatches);
}
//
// All in-flight submissions will have been drained.
//
void
spinel_device_handle_pool_dispose(struct spinel_device * device)
{
struct spinel_handle_pool * const handle_pool = device->handle_pool;
//
// There is no reason to reclaim undispatched handles in the reclamation rings
// because we're about to drop the entire block pool.
//
// So don't do this:
//
// spinel_handle_pool_reclaim_flush_paths(device);
// spinel_handle_pool_reclaim_flush_rasters(device);
//
// But we do need to drain all in-flight reclamation dispatches.
//
spinel_deps_drain_all(device->deps, &device->vk);
// free reclamation rings
spinel_handle_pool_reclaim_dispose(&handle_pool->rasters, device);
spinel_handle_pool_reclaim_dispose(&handle_pool->paths, device);
// free host allocations
free(handle_pool->refcnts);
free(handle_pool->handles.extent);
// free handle pool
free(handle_pool);
}
//
//
//
uint32_t
spinel_handle_pool_get_handle_count(struct spinel_handle_pool const * handle_pool)
{
return handle_pool->handles.ring.size;
}
//
// Reclaim host ref-counted handles.
//
// Note that spinel_handle_pool_reclaim_[h|d] are invoked in path, raster and
// composition completion routines.
//
// For this reason, the function needs to be reentrant.
//
// This simply requires a check to see a "dispatch" is available before
// proceeding in each iteration because the `flush_pfn()` may have kicked off
// additional reclamations.
//
static void
spinel_handle_pool_reclaim_h(struct spinel_handle_pool_reclaim * reclaim,
spinel_handle_pool_reclaim_flush_pfn flush_pfn,
struct spinel_device * device,
union spinel_handle_refcnt * refcnts,
spinel_handle_t const * handles,
uint32_t count)
{
struct spinel_target_config const * const config = &device->ti.config;
//
// Append handles to linear ring spans until done
//
while (count > 0)
{
//
// How many linear ring slots are available in the reclaim ring?
//
uint32_t head_nowrap;
while ((head_nowrap = spinel_ring_head_nowrap(&reclaim->mapped.ring)) == 0)
{
// no need to flush here -- a flush would've already occurred
spinel_deps_drain_1(device->deps, &device->vk);
}
//
// What is the maximum linear span that can be copied to the ring's head?
//
uint32_t const span_max = MIN_MACRO(uint32_t, count, head_nowrap);
//
// Always scan the full linear span of handles
//
count -= span_max;
//
// We have to reload wip in case it was flushed by a reentrant
// reclamation. We know the span is less than eager or else it would've
// already been flushed.
//
// clang-format off
struct spinel_handle_pool_dispatch * const wip = spinel_handle_pool_reclaim_dispatch_head(reclaim, device);
// clang-format on
//
// Append to reclaim extent and update wip dispatch
//
spinel_handle_t * extent = reclaim->mapped.extent + reclaim->mapped.ring.head;
uint32_t reclaimed = 0;
//
// Copy all releasable handles to a linear ring span
//
for (uint32_t ii = 0; ii < span_max; ii++)
{
spinel_handle_t const handle = *handles++;
union spinel_handle_refcnt * const refcnt_ptr = refcnts + handle;
union spinel_handle_refcnt refcnt = *refcnt_ptr;
refcnt.h -= 1;
*refcnt_ptr = refcnt;
if (refcnt.hd == 0)
{
*extent++ = handle;
reclaimed += 1;
}
}
//
// How many handles were reclaimed in this iteration?
//
if (reclaimed > 0)
{
//
// Drop entries from head of reclamation ring
//
spinel_ring_drop_n(&reclaim->mapped.ring, reclaimed);
wip->ring.span += reclaimed;
if (wip->ring.span >= config->reclaim.size.eager)
{
flush_pfn(device);
}
}
}
}
//
// Reclaim device ref-counted handles.
//
// Note that spinel_handle_pool_reclaim_[h|d] are invoked in path, raster and
// composition completion routines.
//
// For this reason, the function needs to be reentrant.
//
// This simply requires a check to see a "dispatch" is available before
// proceeding in each iteration because the `flush_pfn()` may have kicked off
// additional reclamations.
//
static void
spinel_handle_pool_reclaim_d(struct spinel_handle_pool_reclaim * reclaim,
spinel_handle_pool_reclaim_flush_pfn flush_pfn,
struct spinel_device * device,
spinel_handle_t const * handles,
uint32_t count)
{
struct spinel_handle_pool * const handle_pool = device->handle_pool;
union spinel_handle_refcnt * const refcnts = handle_pool->refcnts;
struct spinel_target_config const * const config = &device->ti.config;
//
// Add handles to linear ring spans until done
//
while (count > 0)
{
//
// How many linear ring slots are available in the reclaim ring?
//
uint32_t head_nowrap;
while ((head_nowrap = spinel_ring_head_nowrap(&reclaim->mapped.ring)) == 0)
{
// no need to flush here -- a flush would have already occurred
spinel_deps_drain_1(device->deps, &device->vk);
}
//
// What is the maximum linear span that can be copied to the ring's head?
//
uint32_t const span_max = MIN_MACRO(uint32_t, count, head_nowrap);
//
// Always scan the full linear span of handles
//
count -= span_max;
//
// We have to reload wip in case it was flushed by a reentrant
// reclamation. We know the span is less than eager or else it would've
// already been flushed.
//
// clang-format off
struct spinel_handle_pool_dispatch * const wip = spinel_handle_pool_reclaim_dispatch_head(reclaim, device);
// clang-format on
//
// Append to reclaim extent and update wip dispatch
//
spinel_handle_t * extent = reclaim->mapped.extent + reclaim->mapped.ring.head;
uint32_t reclaimed = 0;
//
// Copy all releasable handles to a linear ring span
//
for (uint32_t ii = 0; ii < span_max; ii++)
{
spinel_handle_t const handle = *handles++;
union spinel_handle_refcnt * const refcnt_ptr = refcnts + handle;
union spinel_handle_refcnt refcnt = *refcnt_ptr;
refcnt.d -= 1;
*refcnt_ptr = refcnt;
if (refcnt.hd == 0)
{
*extent++ = handle;
reclaimed += 1;
}
}
//
// How many handles were reclaimed in this iteration?
//
if (reclaimed > 0)
{
//
// Drop entries from head of reclamation ring
//
spinel_ring_drop_n(&reclaim->mapped.ring, reclaimed);
wip->ring.span += reclaimed;
if (wip->ring.span >= config->reclaim.size.eager)
{
flush_pfn(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.
//
spinel_handle_t
spinel_device_handle_acquire(struct spinel_device * device)
{
//
// 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 spinel_device_lost() and log the reason.
//
// A comprehensive solution can be surfaced *after* the block pool allocation
// becomes more precise.
//
struct spinel_handle_pool * const handle_pool = device->handle_pool;
struct spinel_ring * const ring = &handle_pool->handles.ring;
while (ring->rem == 0)
{
//
// Drain all submissions
//
spinel_deps_drain_all(device->deps, &device->vk);
//
// Are there unreclaimed handles in the reclamation rings?
//
bool const no_unreclaimed_paths = spinel_ring_is_full(&handle_pool->paths.mapped.ring);
bool const no_unreclaimed_rasters = spinel_ring_is_full(&handle_pool->rasters.mapped.ring);
if (no_unreclaimed_paths && no_unreclaimed_rasters)
{
//
// FIXME(allanmac): Harmonize "device lost" handling
//
spinel_device_lost(device);
}
else
{
if (!no_unreclaimed_paths)
{
spinel_handle_pool_reclaim_flush_paths(device);
}
if (!no_unreclaimed_rasters)
{
spinel_handle_pool_reclaim_flush_rasters(device);
}
}
}
uint32_t const idx = spinel_ring_acquire_1(ring);
spinel_handle_t const handle = handle_pool->handles.extent[idx];
handle_pool->refcnts[handle] = (union spinel_handle_refcnt){ .h = 1, .d = 1 };
return 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 spinel_result_t
spinel_device_validate_retain_h(struct spinel_device * device,
spinel_handle_t const * handles,
uint32_t count)
{
struct spinel_handle_pool * const handle_pool = device->handle_pool;
union spinel_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++)
{
spinel_handle_t const handle = handles[ii];
if (handle >= handle_max)
{
return SPN_ERROR_HANDLE_INVALID;
}
else
{
union spinel_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++)
{
spinel_handle_t const handle = handles[ii];
refcnts[handle].h++;
}
return SPN_SUCCESS;
}
//
// Path and raster-specific functions
//
spinel_result_t
spinel_device_validate_retain_h_paths(struct spinel_device * device,
struct spinel_path const * paths,
uint32_t count)
{
if (count == 0)
{
return SPN_SUCCESS;
}
union spinel_paths_to_handles const p2h = { paths };
return spinel_device_validate_retain_h(device, p2h.handles, count);
}
spinel_result_t
spinel_device_validate_retain_h_rasters(struct spinel_device * device,
struct spinel_raster const * rasters,
uint32_t count)
{
if (count == 0)
{
return SPN_SUCCESS;
}
union spinel_rasters_to_handles const r2h = { rasters };
return spinel_device_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 spinel_result_t
spinel_handle_pool_validate_release_h(struct spinel_handle_pool * handle_pool,
union spinel_handle_refcnt * refcnts,
spinel_handle_t const * handles,
uint32_t count)
{
uint32_t const handle_max = handle_pool->handles.ring.size;
//
// validate
//
for (uint32_t ii = 0; ii < count; ii++)
{
spinel_handle_t const handle = handles[ii];
if (handle >= handle_max)
{
return SPN_ERROR_HANDLE_INVALID;
}
else
{
union spinel_handle_refcnt const refcnt = refcnts[handle];
if (refcnt.h == 0)
{
return SPN_ERROR_HANDLE_INVALID;
}
}
}
//
// all the handles validated
//
return SPN_SUCCESS;
}
//
// Path and raster-specific functions
//
spinel_result_t
spinel_device_validate_release_h_paths(struct spinel_device * device,
struct spinel_path const * paths,
uint32_t count)
{
if (count == 0)
{
return SPN_SUCCESS;
}
struct spinel_handle_pool * const handle_pool = device->handle_pool;
union spinel_handle_refcnt * const refcnts = handle_pool->refcnts;
union spinel_paths_to_handles const p2h = { paths };
spinel_result_t const result = spinel_handle_pool_validate_release_h(handle_pool, //
refcnts,
p2h.handles,
count);
if (result == SPN_SUCCESS)
{
spinel_handle_pool_reclaim_h(&handle_pool->paths,
spinel_handle_pool_reclaim_flush_paths,
device,
refcnts,
p2h.handles,
count);
}
return result;
}
spinel_result_t
spinel_device_validate_release_h_rasters(struct spinel_device * device,
struct spinel_raster const * rasters,
uint32_t count)
{
if (count == 0)
{
return SPN_SUCCESS;
}
struct spinel_handle_pool * const handle_pool = device->handle_pool;
union spinel_handle_refcnt * const refcnts = handle_pool->refcnts;
union spinel_rasters_to_handles const r2h = { rasters };
spinel_result_t const result = spinel_handle_pool_validate_release_h(handle_pool, //
refcnts,
r2h.handles,
count);
if (result == SPN_SUCCESS)
{
spinel_handle_pool_reclaim_h(&handle_pool->rasters,
spinel_handle_pool_reclaim_flush_rasters,
device,
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 spinel_result_t
spinel_device_validate_retain_d(struct spinel_device * device,
spinel_handle_t const * handles,
uint32_t const count)
{
assert(count > 0);
struct spinel_handle_pool * const handle_pool = device->handle_pool;
union spinel_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++)
{
spinel_handle_t const handle = handles[ii];
if (handle >= handle_max)
{
return SPN_ERROR_HANDLE_INVALID;
}
else
{
union spinel_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;
}
spinel_result_t
spinel_device_validate_d_paths(struct spinel_device * device,
struct spinel_path const * paths,
uint32_t const count)
{
assert(count > 0);
union spinel_paths_to_handles const p2h = { paths };
return spinel_device_validate_retain_d(device, p2h.handles, count);
}
spinel_result_t
spinel_device_validate_d_rasters(struct spinel_device * device,
struct spinel_raster const * rasters,
uint32_t const count)
{
assert(count > 0);
union spinel_rasters_to_handles const r2h = { rasters };
return spinel_device_validate_retain_d(device, r2h.handles, count);
}
//
// After explicit validation, retain the handles for the device
//
static void
spinel_device_retain_d(struct spinel_device * device,
spinel_handle_t const * handles,
uint32_t const count)
{
assert(count > 0);
struct spinel_handle_pool * const handle_pool = device->handle_pool;
union spinel_handle_refcnt * const refcnts = handle_pool->refcnts;
for (uint32_t ii = 0; ii < count; ii++)
{
spinel_handle_t const handle = handles[ii];
refcnts[handle].d++;
}
}
void
spinel_device_retain_d_paths(struct spinel_device * device,
struct spinel_path const * paths,
uint32_t const count)
{
assert(count > 0);
union spinel_paths_to_handles const p2h = { paths };
spinel_device_retain_d(device, p2h.handles, count);
}
void
spinel_device_retain_d_rasters(struct spinel_device * device,
struct spinel_raster const * rasters,
uint32_t const count)
{
assert(count > 0);
union spinel_rasters_to_handles const r2h = { rasters };
spinel_device_retain_d(device, r2h.handles, count);
}
//
// Release device-held spans of handles of known type
//
void
spinel_device_release_d_paths(struct spinel_device * device,
spinel_handle_t const * handles,
uint32_t count)
{
assert(count > 0);
spinel_handle_pool_reclaim_d(&device->handle_pool->paths,
spinel_handle_pool_reclaim_flush_paths,
device,
handles,
count);
}
void
spinel_device_release_d_rasters(struct spinel_device * device,
spinel_handle_t const * handles,
uint32_t count)
{
assert(count > 0);
spinel_handle_pool_reclaim_d(&device->handle_pool->rasters,
spinel_handle_pool_reclaim_flush_rasters,
device,
handles,
count);
}
//
// Release handles on a ring -- up to two spans
//
void
spinel_device_release_d_paths_ring(struct spinel_device * device,
spinel_handle_t const * paths,
uint32_t const size,
uint32_t const head,
uint32_t const span)
{
assert(span > 0);
uint32_t const head_max = head + span;
uint32_t const head_clamp = MIN_MACRO(uint32_t, head_max, size);
uint32_t const count_lo = head_clamp - head;
spinel_device_release_d_paths(device, paths + head, count_lo);
if (span > count_lo)
{
uint32_t const count_hi = span - count_lo;
spinel_device_release_d_paths(device, paths, count_hi);
}
}
void
spinel_device_release_d_rasters_ring(struct spinel_device * device,
spinel_handle_t const * rasters,
uint32_t const size,
uint32_t const head,
uint32_t const span)
{
assert(span > 0);
uint32_t const head_max = head + span;
uint32_t const head_clamp = MIN_MACRO(uint32_t, head_max, size);
uint32_t const count_lo = head_clamp - head;
spinel_device_release_d_rasters(device, rasters + head, count_lo);
if (span > count_lo)
{
uint32_t const count_hi = span - count_lo;
spinel_device_release_d_rasters(device, rasters, count_hi);
}
}
//
//
//