In addition to virgl_renderer_submit_cmd, vkr_ring can also be used to submit commands. Signed-off-by: Chia-I Wu <olvaffe@gmail.com> Reviewed-by: Ryan Neph <ryanneph@google.com> Reviewed-by: Gert Wollny <gert.wollny@collabora.com>macos/master
parent
a5149985ee
commit
2eefe15310
@ -0,0 +1,268 @@ |
||||
/*
|
||||
* Copyright 2021 Google LLC |
||||
* SPDX-License-Identifier: MIT |
||||
*/ |
||||
|
||||
#include "vkr_ring.h" |
||||
|
||||
#include <assert.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <time.h> |
||||
|
||||
#include "util/u_math.h" |
||||
|
||||
#include "virgl_context.h" |
||||
|
||||
enum vkr_ring_status_flag { |
||||
VKR_RING_STATUS_IDLE = 1u << 0, |
||||
}; |
||||
|
||||
static void |
||||
vkr_ring_store_head(struct vkr_ring *ring) |
||||
{ |
||||
/* the renderer is expected to load the head with memory_order_acquire,
|
||||
* forming a release-acquire ordering |
||||
*/ |
||||
atomic_store_explicit(ring->shared.head, ring->cur, memory_order_release); |
||||
} |
||||
|
||||
static uint32_t |
||||
vkr_ring_load_tail(const struct vkr_ring *ring) |
||||
{ |
||||
/* the driver is expected to store the tail with memory_order_release,
|
||||
* forming a release-acquire ordering |
||||
*/ |
||||
return atomic_load_explicit(ring->shared.tail, memory_order_acquire); |
||||
} |
||||
|
||||
static void |
||||
vkr_ring_store_status(struct vkr_ring *ring, uint32_t status) |
||||
{ |
||||
atomic_store_explicit(ring->shared.status, status, memory_order_seq_cst); |
||||
} |
||||
|
||||
static void |
||||
vkr_ring_read_buffer(struct vkr_ring *ring, void *data, size_t size) |
||||
{ |
||||
const size_t offset = ring->cur & ring->buffer_mask; |
||||
assert(size <= ring->buffer_size); |
||||
if (offset + size <= ring->buffer_size) { |
||||
memcpy(data, (const uint8_t *)ring->shared.buffer + offset, size); |
||||
} else { |
||||
const size_t s = ring->buffer_size - offset; |
||||
memcpy(data, (const uint8_t *)ring->shared.buffer + offset, s); |
||||
memcpy((uint8_t *)data + s, ring->shared.buffer, size - s); |
||||
} |
||||
|
||||
ring->cur += size; |
||||
} |
||||
|
||||
struct vkr_ring * |
||||
vkr_ring_create(const struct vkr_ring_layout *layout, |
||||
void *shared, |
||||
struct virgl_context *ctx, |
||||
uint64_t idle_timeout) |
||||
{ |
||||
struct vkr_ring *ring; |
||||
int ret; |
||||
|
||||
ring = calloc(1, sizeof(*ring)); |
||||
if (!ring) |
||||
return NULL; |
||||
|
||||
#define ring_attach_shared(member) \ |
||||
ring->shared.member = (void *)((uint8_t *)shared + layout->member##_offset) |
||||
ring_attach_shared(head); |
||||
ring_attach_shared(tail); |
||||
ring_attach_shared(status); |
||||
ring_attach_shared(buffer); |
||||
ring_attach_shared(extra); |
||||
#undef ring_attach_shared |
||||
|
||||
assert(layout->buffer_size && util_is_power_of_two(layout->buffer_size)); |
||||
ring->buffer_size = layout->buffer_size; |
||||
ring->buffer_mask = layout->buffer_size - 1; |
||||
ring->extra_size = layout->extra_size; |
||||
|
||||
/* we will manage head and status, and we expect them to be 0 initially */ |
||||
if (*ring->shared.head || *ring->shared.status) { |
||||
free(ring); |
||||
return NULL; |
||||
} |
||||
|
||||
ring->cmd = malloc(ring->buffer_size); |
||||
if (!ring->cmd) { |
||||
free(ring); |
||||
return NULL; |
||||
} |
||||
|
||||
ring->context = ctx; |
||||
ring->idle_timeout = idle_timeout; |
||||
|
||||
ret = mtx_init(&ring->mutex, mtx_plain); |
||||
if (ret != thrd_success) { |
||||
free(ring->cmd); |
||||
free(ring); |
||||
return NULL; |
||||
} |
||||
ret = cnd_init(&ring->cond); |
||||
if (ret != thrd_success) { |
||||
mtx_destroy(&ring->mutex); |
||||
free(ring->cmd); |
||||
free(ring); |
||||
return NULL; |
||||
} |
||||
|
||||
return ring; |
||||
} |
||||
|
||||
void |
||||
vkr_ring_destroy(struct vkr_ring *ring) |
||||
{ |
||||
assert(!ring->started); |
||||
mtx_destroy(&ring->mutex); |
||||
cnd_destroy(&ring->cond); |
||||
free(ring->cmd); |
||||
free(ring); |
||||
} |
||||
|
||||
static uint64_t |
||||
vkr_ring_now(void) |
||||
{ |
||||
const uint64_t ns_per_sec = 1000000000llu; |
||||
struct timespec now; |
||||
if (clock_gettime(CLOCK_MONOTONIC, &now)) |
||||
return 0; |
||||
return ns_per_sec * now.tv_sec + now.tv_nsec; |
||||
} |
||||
|
||||
static void |
||||
vkr_ring_relax(uint32_t *iter) |
||||
{ |
||||
/* TODO do better */ |
||||
const uint32_t busy_wait_order = 4; |
||||
const uint32_t base_sleep_us = 10; |
||||
|
||||
(*iter)++; |
||||
if (*iter < (1u << busy_wait_order)) { |
||||
thrd_yield(); |
||||
return; |
||||
} |
||||
|
||||
const uint32_t shift = util_last_bit(*iter) - busy_wait_order - 1; |
||||
const uint32_t us = base_sleep_us << shift; |
||||
const struct timespec ts = { |
||||
.tv_sec = us / 1000000, |
||||
.tv_nsec = (us % 1000000) * 1000, |
||||
}; |
||||
clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL); |
||||
} |
||||
|
||||
static int |
||||
vkr_ring_thread(void *arg) |
||||
{ |
||||
struct vkr_ring *ring = arg; |
||||
struct virgl_context *ctx = ring->context; |
||||
uint64_t last_submit = vkr_ring_now(); |
||||
uint32_t relax_iter = 0; |
||||
int ret = 0; |
||||
|
||||
while (ring->started) { |
||||
bool wait = false; |
||||
uint32_t cmd_size; |
||||
|
||||
if (vkr_ring_now() >= last_submit + ring->idle_timeout) { |
||||
ring->pending_notify = false; |
||||
vkr_ring_store_status(ring, VKR_RING_STATUS_IDLE); |
||||
wait = ring->cur == vkr_ring_load_tail(ring); |
||||
if (!wait) |
||||
vkr_ring_store_status(ring, 0); |
||||
} |
||||
|
||||
if (wait) { |
||||
mtx_lock(&ring->mutex); |
||||
if (ring->started && !ring->pending_notify) |
||||
cnd_wait(&ring->cond, &ring->mutex); |
||||
vkr_ring_store_status(ring, 0); |
||||
mtx_unlock(&ring->mutex); |
||||
|
||||
if (!ring->started) |
||||
break; |
||||
|
||||
last_submit = vkr_ring_now(); |
||||
relax_iter = 0; |
||||
} |
||||
|
||||
cmd_size = vkr_ring_load_tail(ring) - ring->cur; |
||||
if (cmd_size) { |
||||
if (cmd_size > ring->buffer_size) { |
||||
ret = -EINVAL; |
||||
break; |
||||
} |
||||
|
||||
vkr_ring_read_buffer(ring, ring->cmd, cmd_size); |
||||
ctx->submit_cmd(ctx, ring->cmd, cmd_size); |
||||
vkr_ring_store_head(ring); |
||||
|
||||
last_submit = vkr_ring_now(); |
||||
relax_iter = 0; |
||||
} else { |
||||
vkr_ring_relax(&relax_iter); |
||||
} |
||||
} |
||||
|
||||
return ret; |
||||
} |
||||
|
||||
void |
||||
vkr_ring_start(struct vkr_ring *ring) |
||||
{ |
||||
int ret; |
||||
|
||||
assert(!ring->started); |
||||
ring->started = true; |
||||
ret = thrd_create(&ring->thread, vkr_ring_thread, ring); |
||||
if (ret != thrd_success) |
||||
ring->started = false; |
||||
} |
||||
|
||||
bool |
||||
vkr_ring_stop(struct vkr_ring *ring) |
||||
{ |
||||
mtx_lock(&ring->mutex); |
||||
if (ring->thread == thrd_current()) { |
||||
mtx_unlock(&ring->mutex); |
||||
return false; |
||||
} |
||||
assert(ring->started); |
||||
ring->started = false; |
||||
cnd_signal(&ring->cond); |
||||
mtx_unlock(&ring->mutex); |
||||
|
||||
thrd_join(ring->thread, NULL); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
void |
||||
vkr_ring_notify(struct vkr_ring *ring) |
||||
{ |
||||
mtx_lock(&ring->mutex); |
||||
ring->pending_notify = true; |
||||
cnd_signal(&ring->cond); |
||||
mtx_unlock(&ring->mutex); |
||||
} |
||||
|
||||
bool |
||||
vkr_ring_write_extra(struct vkr_ring *ring, size_t offset, uint32_t val) |
||||
{ |
||||
if (offset > ring->extra_size || sizeof(val) > ring->extra_size - offset) |
||||
return false; |
||||
|
||||
volatile atomic_uint *dst = |
||||
(void *)((uint8_t *)ring->shared.extra + offset); |
||||
atomic_store_explicit(dst, val, memory_order_release); |
||||
|
||||
return true; |
||||
} |
@ -0,0 +1,93 @@ |
||||
/*
|
||||
* Copyright 2021 Google LLC |
||||
* SPDX-License-Identifier: MIT |
||||
*/ |
||||
|
||||
#ifndef VKR_RING_H |
||||
#define VKR_RING_H |
||||
|
||||
#include "config.h" |
||||
|
||||
#include <stdatomic.h> |
||||
#include <stdbool.h> |
||||
#include <stddef.h> |
||||
#include <stdint.h> |
||||
|
||||
#include "c11/threads.h" |
||||
#include "util/u_double_list.h" |
||||
|
||||
#include "vkr_object.h" |
||||
|
||||
struct virgl_context; |
||||
|
||||
/* the layout of a ring in a virgl_resource */ |
||||
struct vkr_ring_layout { |
||||
size_t head_offset; |
||||
size_t tail_offset; |
||||
size_t status_offset; |
||||
|
||||
size_t buffer_offset; |
||||
size_t buffer_size; |
||||
|
||||
size_t extra_offset; |
||||
size_t extra_size; |
||||
}; |
||||
|
||||
static_assert(ATOMIC_INT_LOCK_FREE == 2 && sizeof(atomic_uint) == 4, |
||||
"vkr_ring_shared requires lock-free 32-bit atomic_uint"); |
||||
|
||||
/* pointers to a ring in a virgl_resource */ |
||||
struct vkr_ring_shared { |
||||
volatile atomic_uint *head; |
||||
const volatile atomic_uint *tail; |
||||
volatile atomic_uint *status; |
||||
const void *buffer; |
||||
|
||||
void *extra; |
||||
}; |
||||
|
||||
struct vkr_ring { |
||||
/* used by the caller */ |
||||
vkr_object_id id; |
||||
struct list_head head; |
||||
|
||||
struct vkr_ring_shared shared; |
||||
uint32_t buffer_size; |
||||
uint32_t buffer_mask; |
||||
uint32_t cur; |
||||
void *cmd; |
||||
|
||||
size_t extra_size; |
||||
|
||||
struct virgl_context *context; |
||||
uint64_t idle_timeout; |
||||
|
||||
mtx_t mutex; |
||||
cnd_t cond; |
||||
thrd_t thread; |
||||
atomic_bool started; |
||||
atomic_bool pending_notify; |
||||
}; |
||||
|
||||
struct vkr_ring * |
||||
vkr_ring_create(const struct vkr_ring_layout *layout, |
||||
void *shared, |
||||
struct virgl_context *ctx, |
||||
uint64_t idle_timeout); |
||||
|
||||
void |
||||
vkr_ring_destroy(struct vkr_ring *ring); |
||||
|
||||
void |
||||
vkr_ring_start(struct vkr_ring *ring); |
||||
|
||||
bool |
||||
vkr_ring_stop(struct vkr_ring *ring); |
||||
|
||||
void |
||||
vkr_ring_notify(struct vkr_ring *ring); |
||||
|
||||
bool |
||||
vkr_ring_write_extra(struct vkr_ring *ring, size_t offset, uint32_t val); |
||||
|
||||
#endif /* VKR_RING_H */ |
Loading…
Reference in new issue