#include "SDL_internal.h"
#ifdef HAVE_LIBURING_H
#include "../SDL_sysasyncio.h"
#include <liburing.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
static SDL_InitState liburing_init;
static bool (*CreateAsyncIOQueue)(SDL_AsyncIOQueue *queue);
static void (*QuitAsyncIO)(void);
static bool (*AsyncIOFromFile)(const char *file, const char *mode, SDL_AsyncIO *asyncio);
#define SDL_DRIVER_LIBURING_DYNAMIC "liburing-ffi.so.2"
static const char *liburing_library = SDL_DRIVER_LIBURING_DYNAMIC;
static void *liburing_handle = NULL;
SDL_ELF_NOTE_DLOPEN(
"io-io_uring",
"Support for async IO through liburing",
SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
SDL_DRIVER_LIBURING_DYNAMIC
)
#define SDL_LIBURING_FUNCS \
SDL_LIBURING_FUNC(int, io_uring_queue_init, (unsigned entries, struct io_uring *ring, unsigned flags)) \
SDL_LIBURING_FUNC(struct io_uring_probe *,io_uring_get_probe,(void)) \
SDL_LIBURING_FUNC(void, io_uring_free_probe, (struct io_uring_probe *probe)) \
SDL_LIBURING_FUNC(int, io_uring_opcode_supported, (const struct io_uring_probe *p, int op)) \
SDL_LIBURING_FUNC(struct io_uring_sqe *, io_uring_get_sqe, (struct io_uring *ring)) \
SDL_LIBURING_FUNC(void, io_uring_prep_read,(struct io_uring_sqe *sqe, int fd, void *buf, unsigned nbytes, __u64 offset)) \
SDL_LIBURING_FUNC(void, io_uring_prep_write,(struct io_uring_sqe *sqe, int fd, const void *buf, unsigned nbytes, __u64 offset)) \
SDL_LIBURING_FUNC(void, io_uring_prep_close, (struct io_uring_sqe *sqe, int fd)) \
SDL_LIBURING_FUNC(void, io_uring_prep_fsync, (struct io_uring_sqe *sqe, int fd, unsigned fsync_flags)) \
SDL_LIBURING_FUNC(void, io_uring_prep_cancel, (struct io_uring_sqe *sqe, void *user_data, int flags)) \
SDL_LIBURING_FUNC(void, io_uring_prep_timeout, (struct io_uring_sqe *sqe, struct __kernel_timespec *ts, unsigned count, unsigned flags)) \
SDL_LIBURING_FUNC(void, io_uring_prep_nop, (struct io_uring_sqe *sqe)) \
SDL_LIBURING_FUNC(void, io_uring_sqe_set_data, (struct io_uring_sqe *sqe, void *data)) \
SDL_LIBURING_FUNC(void, io_uring_sqe_set_flags, (struct io_uring_sqe *sqe, unsigned flags)) \
SDL_LIBURING_FUNC(int, io_uring_submit, (struct io_uring *ring)) \
SDL_LIBURING_FUNC(int, io_uring_peek_cqe, (struct io_uring *ring, struct io_uring_cqe **cqe_ptr)) \
SDL_LIBURING_FUNC(int, io_uring_wait_cqe, (struct io_uring *ring, struct io_uring_cqe **cqe_ptr)) \
SDL_LIBURING_FUNC(int, io_uring_wait_cqe_timeout, (struct io_uring *ring, struct io_uring_cqe **cqe_ptr, struct __kernel_timespec *ts)) \
SDL_LIBURING_FUNC(void, io_uring_cqe_seen, (struct io_uring *ring, struct io_uring_cqe *cqe)) \
SDL_LIBURING_FUNC(void, io_uring_queue_exit, (struct io_uring *ring)) \
#define SDL_LIBURING_FUNC(ret, fn, args) typedef ret (*SDL_fntype_##fn) args;
SDL_LIBURING_FUNCS
#undef SDL_LIBURING_FUNC
typedef struct SDL_LibUringFunctions
{
#define SDL_LIBURING_FUNC(ret, fn, args) SDL_fntype_##fn fn;
SDL_LIBURING_FUNCS
#undef SDL_LIBURING_FUNC
} SDL_LibUringFunctions;
static SDL_LibUringFunctions liburing;
typedef struct LibUringAsyncIOQueueData
{
SDL_Mutex *sqe_lock;
SDL_Mutex *cqe_lock;
struct io_uring ring;
SDL_AtomicInt num_waiting;
} LibUringAsyncIOQueueData;
static void UnloadLibUringLibrary(void)
{
if (liburing_library) {
SDL_UnloadObject(liburing_handle);
liburing_library = NULL;
}
SDL_zero(liburing);
}
static bool LoadLibUringSyms(void)
{
#define SDL_LIBURING_FUNC(ret, fn, args) { \
liburing.fn = (SDL_fntype_##fn) SDL_LoadFunction(liburing_handle, #fn); \
if (!liburing.fn) { \
return false; \
} \
}
SDL_LIBURING_FUNCS
#undef SDL_LIBURING_FUNC
return true;
}
static bool LoadLibUring(void)
{
bool result = true;
if (!liburing_handle) {
liburing_handle = SDL_LoadObject(liburing_library);
if (!liburing_handle) {
result = false;
} else {
result = LoadLibUringSyms();
if (result) {
static const int needed_ops[] = {
IORING_OP_NOP,
IORING_OP_FSYNC,
IORING_OP_TIMEOUT,
IORING_OP_CLOSE,
IORING_OP_READ,
IORING_OP_WRITE,
IORING_OP_ASYNC_CANCEL
};
struct io_uring_probe *probe = liburing.io_uring_get_probe();
if (!probe) {
result = false;
} else {
for (int i = 0; i < SDL_arraysize(needed_ops); i++) {
if (!io_uring_opcode_supported(probe, needed_ops[i])) {
result = false;
break;
}
}
liburing.io_uring_free_probe(probe);
}
}
if (!result) {
UnloadLibUringLibrary();
}
}
}
return result;
}
static bool liburing_SetError(const char *what, int err)
{
SDL_assert(err <= 0);
return SDL_SetError("%s failed: %s", what, strerror(-err));
}
static Sint64 liburing_asyncio_size(void *userdata)
{
const int fd = (int) (intptr_t) userdata;
struct stat statbuf;
if (fstat(fd, &statbuf) < 0) {
SDL_SetError("fstat failed: %s", strerror(errno));
return -1;
}
return ((Sint64) statbuf.st_size);
}
static bool liburing_asyncioqueue_queue_task(void *userdata, SDL_AsyncIOTask *task)
{
LibUringAsyncIOQueueData *queuedata = (LibUringAsyncIOQueueData *) userdata;
const int rc = liburing.io_uring_submit(&queuedata->ring);
return (rc < 0) ? liburing_SetError("io_uring_submit", rc) : true;
}
static void liburing_asyncioqueue_cancel_task(void *userdata, SDL_AsyncIOTask *task)
{
SDL_AsyncIOTask *cancel_task = (SDL_AsyncIOTask *) SDL_calloc(1, sizeof (*cancel_task));
if (!cancel_task) {
return; }
LibUringAsyncIOQueueData *queuedata = (LibUringAsyncIOQueueData *) userdata;
SDL_LockMutex(queuedata->sqe_lock);
struct io_uring_sqe *sqe = liburing.io_uring_get_sqe(&queuedata->ring);
if (!sqe) {
SDL_UnlockMutex(queuedata->sqe_lock);
SDL_free(cancel_task); return;
}
cancel_task->app_userdata = task;
liburing.io_uring_prep_cancel(sqe, task, 0);
liburing.io_uring_sqe_set_data(sqe, cancel_task);
liburing_asyncioqueue_queue_task(userdata, task);
SDL_UnlockMutex(queuedata->sqe_lock);
}
static SDL_AsyncIOTask *ProcessCQE(LibUringAsyncIOQueueData *queuedata, struct io_uring_cqe *cqe)
{
if (!cqe) {
return NULL;
}
SDL_AsyncIOTask *task = (SDL_AsyncIOTask *) io_uring_cqe_get_data(cqe);
if (task) { if (!task->queue) { SDL_AsyncIOTask *cancel_task = task;
task = (SDL_AsyncIOTask *) cancel_task->app_userdata;
SDL_free(cancel_task);
if (cqe->res >= 0) { task->result = SDL_ASYNCIO_CANCELED;
} else {
task = NULL; }
} else if (cqe->res < 0) {
task->result = SDL_ASYNCIO_FAILURE;
} else {
if ((task->type == SDL_ASYNCIO_TASK_WRITE) && (((Uint64) cqe->res) < task->requested_size)) {
task->result = SDL_ASYNCIO_FAILURE; }
if ((task->type == SDL_ASYNCIO_TASK_READ) || (task->type == SDL_ASYNCIO_TASK_WRITE)) {
task->result_size = (Uint64) cqe->res;
}
}
if (task && (task->type == SDL_ASYNCIO_TASK_CLOSE) && task->flush) {
task->flush = false;
task = NULL; }
}
return task;
}
static SDL_AsyncIOTask *liburing_asyncioqueue_get_results(void *userdata)
{
LibUringAsyncIOQueueData *queuedata = (LibUringAsyncIOQueueData *) userdata;
SDL_LockMutex(queuedata->cqe_lock);
struct io_uring_cqe *cqe = NULL;
const int rc = liburing.io_uring_peek_cqe(&queuedata->ring, &cqe);
if (rc != 0) {
SDL_assert(rc == -EAGAIN); SDL_UnlockMutex(queuedata->cqe_lock);
return NULL;
}
struct io_uring_cqe cqe_copy;
SDL_copyp(&cqe_copy, cqe); liburing.io_uring_cqe_seen(&queuedata->ring, cqe); SDL_UnlockMutex(queuedata->cqe_lock);
return ProcessCQE(queuedata, &cqe_copy);
}
static SDL_AsyncIOTask *liburing_asyncioqueue_wait_results(void *userdata, Sint32 timeoutMS)
{
LibUringAsyncIOQueueData *queuedata = (LibUringAsyncIOQueueData *) userdata;
struct io_uring_cqe *cqe = NULL;
SDL_AddAtomicInt(&queuedata->num_waiting, 1);
if (timeoutMS < 0) {
liburing.io_uring_wait_cqe(&queuedata->ring, &cqe);
} else {
struct __kernel_timespec ts = { (Sint64) timeoutMS / SDL_MS_PER_SECOND, (Sint64) SDL_MS_TO_NS(timeoutMS % SDL_MS_PER_SECOND) };
liburing.io_uring_wait_cqe_timeout(&queuedata->ring, &cqe, &ts);
}
SDL_AddAtomicInt(&queuedata->num_waiting, -1);
return liburing_asyncioqueue_get_results(userdata); }
static void liburing_asyncioqueue_signal(void *userdata)
{
LibUringAsyncIOQueueData *queuedata = (LibUringAsyncIOQueueData *) userdata;
const int num_waiting = SDL_GetAtomicInt(&queuedata->num_waiting);
SDL_LockMutex(queuedata->sqe_lock);
for (int i = 0; i < num_waiting; i++) { struct io_uring_sqe *sqe = liburing.io_uring_get_sqe(&queuedata->ring);
if (sqe) {
static struct __kernel_timespec ts; liburing.io_uring_prep_timeout(sqe, &ts, 0, 0);
liburing.io_uring_sqe_set_data(sqe, NULL);
}
}
liburing.io_uring_submit(&queuedata->ring);
SDL_UnlockMutex(queuedata->sqe_lock);
}
static void liburing_asyncioqueue_destroy(void *userdata)
{
LibUringAsyncIOQueueData *queuedata = (LibUringAsyncIOQueueData *) userdata;
liburing.io_uring_queue_exit(&queuedata->ring);
SDL_DestroyMutex(queuedata->sqe_lock);
SDL_DestroyMutex(queuedata->cqe_lock);
SDL_free(queuedata);
}
static bool SDL_SYS_CreateAsyncIOQueue_liburing(SDL_AsyncIOQueue *queue)
{
LibUringAsyncIOQueueData *queuedata = (LibUringAsyncIOQueueData *) SDL_calloc(1, sizeof (*queuedata));
if (!queuedata) {
return false;
}
SDL_SetAtomicInt(&queuedata->num_waiting, 0);
queuedata->sqe_lock = SDL_CreateMutex();
if (!queuedata->sqe_lock) {
SDL_free(queuedata);
return false;
}
queuedata->cqe_lock = SDL_CreateMutex();
if (!queuedata->cqe_lock) {
SDL_DestroyMutex(queuedata->sqe_lock);
SDL_free(queuedata);
return false;
}
const int rc = liburing.io_uring_queue_init(128, &queuedata->ring, 0);
if (rc != 0) {
SDL_DestroyMutex(queuedata->sqe_lock);
SDL_DestroyMutex(queuedata->cqe_lock);
SDL_free(queuedata);
return liburing_SetError("io_uring_queue_init", rc);
}
static const SDL_AsyncIOQueueInterface SDL_AsyncIOQueue_liburing = {
liburing_asyncioqueue_queue_task,
liburing_asyncioqueue_cancel_task,
liburing_asyncioqueue_get_results,
liburing_asyncioqueue_wait_results,
liburing_asyncioqueue_signal,
liburing_asyncioqueue_destroy
};
SDL_copyp(&queue->iface, &SDL_AsyncIOQueue_liburing);
queue->userdata = queuedata;
return true;
}
static bool liburing_asyncio_read(void *userdata, SDL_AsyncIOTask *task)
{
LibUringAsyncIOQueueData *queuedata = (LibUringAsyncIOQueueData *) task->queue->userdata;
const int fd = (int) (intptr_t) userdata;
if (task->requested_size > ((Uint64) ~((unsigned) 0))) {
return SDL_SetError("io_uring: i/o task is too large");
}
SDL_LockMutex(queuedata->sqe_lock);
bool retval;
struct io_uring_sqe *sqe = liburing.io_uring_get_sqe(&queuedata->ring);
if (!sqe) {
retval = SDL_SetError("io_uring: submission queue is full");
} else {
liburing.io_uring_prep_read(sqe, fd, task->buffer, (unsigned) task->requested_size, task->offset);
liburing.io_uring_sqe_set_data(sqe, task);
retval = task->queue->iface.queue_task(task->queue->userdata, task);
}
SDL_UnlockMutex(queuedata->sqe_lock);
return retval;
}
static bool liburing_asyncio_write(void *userdata, SDL_AsyncIOTask *task)
{
LibUringAsyncIOQueueData *queuedata = (LibUringAsyncIOQueueData *) task->queue->userdata;
const int fd = (int) (intptr_t) userdata;
if (task->requested_size > ((Uint64) ~((unsigned) 0))) {
return SDL_SetError("io_uring: i/o task is too large");
}
SDL_LockMutex(queuedata->sqe_lock);
bool retval;
struct io_uring_sqe *sqe = liburing.io_uring_get_sqe(&queuedata->ring);
if (!sqe) {
retval = SDL_SetError("io_uring: submission queue is full");
} else {
liburing.io_uring_prep_write(sqe, fd, task->buffer, (unsigned) task->requested_size, task->offset);
liburing.io_uring_sqe_set_data(sqe, task);
retval = task->queue->iface.queue_task(task->queue->userdata, task);
}
SDL_UnlockMutex(queuedata->sqe_lock);
return retval;
}
static bool liburing_asyncio_close(void *userdata, SDL_AsyncIOTask *task)
{
LibUringAsyncIOQueueData *queuedata = (LibUringAsyncIOQueueData *) task->queue->userdata;
const int fd = (int) (intptr_t) userdata;
SDL_LockMutex(queuedata->sqe_lock);
bool retval;
struct io_uring_sqe *sqe = liburing.io_uring_get_sqe(&queuedata->ring);
if (!sqe) {
retval = SDL_SetError("io_uring: submission queue is full");
} else {
if (task->flush) {
struct io_uring_sqe *flush_sqe = sqe;
sqe = liburing.io_uring_get_sqe(&queuedata->ring); if (!sqe) {
liburing.io_uring_prep_nop(flush_sqe); liburing.io_uring_sqe_set_data(flush_sqe, NULL);
task->queue->iface.queue_task(task->queue->userdata, task);
SDL_UnlockMutex(queuedata->sqe_lock);
return SDL_SetError("io_uring: submission queue is full");
}
liburing.io_uring_prep_fsync(flush_sqe, fd, IORING_FSYNC_DATASYNC);
liburing.io_uring_sqe_set_data(flush_sqe, task);
liburing.io_uring_sqe_set_flags(flush_sqe, IOSQE_IO_HARDLINK); }
liburing.io_uring_prep_close(sqe, fd);
liburing.io_uring_sqe_set_data(sqe, task);
retval = task->queue->iface.queue_task(task->queue->userdata, task);
}
SDL_UnlockMutex(queuedata->sqe_lock);
return retval;
}
static void liburing_asyncio_destroy(void *userdata)
{
}
static int PosixOpenModeFromString(const char *mode)
{
static const struct { const char *str; int flags; } mappings[] = {
{ "rb", O_RDONLY },
{ "wb", O_WRONLY | O_CREAT | O_TRUNC },
{ "r+b", O_RDWR },
{ "w+b", O_RDWR | O_CREAT | O_TRUNC }
};
for (int i = 0; i < SDL_arraysize(mappings); i++) {
if (SDL_strcmp(mappings[i].str, mode) == 0) {
return mappings[i].flags;
}
}
SDL_assert(!"Shouldn't have reached this code");
return 0;
}
static bool SDL_SYS_AsyncIOFromFile_liburing(const char *file, const char *mode, SDL_AsyncIO *asyncio)
{
const int fd = open(file, PosixOpenModeFromString(mode), 0644);
if (fd == -1) {
return SDL_SetError("open failed: %s", strerror(errno));
}
static const SDL_AsyncIOInterface SDL_AsyncIOFile_liburing = {
liburing_asyncio_size,
liburing_asyncio_read,
liburing_asyncio_write,
liburing_asyncio_close,
liburing_asyncio_destroy
};
SDL_copyp(&asyncio->iface, &SDL_AsyncIOFile_liburing);
asyncio->userdata = (void *) (intptr_t) fd;
return true;
}
static void SDL_SYS_QuitAsyncIO_liburing(void)
{
UnloadLibUringLibrary();
}
static void MaybeInitializeLibUring(void)
{
if (SDL_ShouldInit(&liburing_init)) {
if (LoadLibUring()) {
SDL_DebugLogBackend("asyncio", "liburing");
CreateAsyncIOQueue = SDL_SYS_CreateAsyncIOQueue_liburing;
QuitAsyncIO = SDL_SYS_QuitAsyncIO_liburing;
AsyncIOFromFile = SDL_SYS_AsyncIOFromFile_liburing;
} else { SDL_DebugLogBackend("asyncio", "generic");
CreateAsyncIOQueue = SDL_SYS_CreateAsyncIOQueue_Generic;
QuitAsyncIO = SDL_SYS_QuitAsyncIO_Generic;
AsyncIOFromFile = SDL_SYS_AsyncIOFromFile_Generic;
}
SDL_SetInitialized(&liburing_init, true);
}
}
bool SDL_SYS_CreateAsyncIOQueue(SDL_AsyncIOQueue *queue)
{
MaybeInitializeLibUring();
return CreateAsyncIOQueue(queue);
}
bool SDL_SYS_AsyncIOFromFile(const char *file, const char *mode, SDL_AsyncIO *asyncio)
{
MaybeInitializeLibUring();
return AsyncIOFromFile(file, mode, asyncio);
}
void SDL_SYS_QuitAsyncIO(void)
{
if (SDL_ShouldQuit(&liburing_init)) {
QuitAsyncIO();
CreateAsyncIOQueue = NULL;
QuitAsyncIO = NULL;
AsyncIOFromFile = NULL;
SDL_SetInitialized(&liburing_init, false);
}
}
#endif