mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-11 11:01:07 +00:00
197 lines
7.3 KiB
C
197 lines
7.3 KiB
C
/* SPDX-License-Identifier: GPL-3.0-or-later */
|
|
#include "kgsl_compat.h"
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#ifdef __ANDROID__
|
|
#include <android/log.h>
|
|
#endif
|
|
|
|
#if __SIZEOF_POINTER__ == 8
|
|
_Static_assert(sizeof(struct kgsl_gpumem_alloc_id) == 48, "KGSL alloc ABI");
|
|
_Static_assert(sizeof(struct kgsl_gpuobj_alloc) == 48, "GPUOBJ alloc ABI");
|
|
_Static_assert(sizeof(struct kgsl_gpuobj_free) == 32, "GPUOBJ free ABI");
|
|
_Static_assert(sizeof(struct kgsl_gpuobj_info) == 48, "GPUOBJ info ABI");
|
|
#endif
|
|
|
|
static void report(const char *operation, int error) {
|
|
static unsigned successes, failures;
|
|
unsigned *counter = error ? &failures : &successes;
|
|
if (__atomic_fetch_add(counter, 1, __ATOMIC_RELAXED) >= (error ? 8u : 1u)) return;
|
|
int saved = errno;
|
|
#ifdef __ANDROID__
|
|
__android_log_print(error ? ANDROID_LOG_ERROR : ANDROID_LOG_INFO, "QuestKGSL",
|
|
"%s: GPUOBJ compatibility path, errno=%d", operation, error);
|
|
#else
|
|
fprintf(stderr, "[QuestKGSL] %s: GPUOBJ compatibility path, errno=%d\n", operation, error);
|
|
#endif
|
|
errno = saved;
|
|
}
|
|
|
|
struct tracked_context {
|
|
unsigned int context, queued, retired, generation;
|
|
};
|
|
|
|
static struct tracked_context *tracked;
|
|
static unsigned int tracked_count, tracked_capacity;
|
|
static unsigned int next_generation;
|
|
static char tracked_lock;
|
|
|
|
static void tracked_acquire(void) {
|
|
while (__atomic_test_and_set(&tracked_lock, __ATOMIC_ACQUIRE)) {}
|
|
}
|
|
|
|
static void tracked_release(void) {
|
|
__atomic_clear(&tracked_lock, __ATOMIC_RELEASE);
|
|
}
|
|
|
|
static int tracked_find(unsigned int context, int create) {
|
|
int free_slot = -1;
|
|
for (unsigned int i = 0; i < tracked_count; i++) {
|
|
if (tracked[i].context == context) return (int)i;
|
|
if (free_slot < 0 && tracked[i].context == 0) free_slot = (int)i;
|
|
}
|
|
if (!create) return -1;
|
|
if (free_slot < 0) {
|
|
if (tracked_count == tracked_capacity) {
|
|
unsigned int capacity = tracked_capacity ? tracked_capacity * 2 : 32;
|
|
struct tracked_context *grown = realloc(tracked, capacity * sizeof *grown);
|
|
if (grown == NULL) return -1;
|
|
tracked = grown;
|
|
tracked_capacity = capacity;
|
|
}
|
|
free_slot = (int)tracked_count++;
|
|
}
|
|
tracked[free_slot].context = context;
|
|
tracked[free_slot].queued = tracked[free_slot].retired = 0;
|
|
tracked[free_slot].generation = ++next_generation;
|
|
return free_slot;
|
|
}
|
|
|
|
static void track_submit(unsigned int context, unsigned int timestamp) {
|
|
if (context == 0) return;
|
|
tracked_acquire();
|
|
int slot = tracked_find(context, 1);
|
|
if (slot < 0) { tracked_release(); return; }
|
|
if ((int)(timestamp - tracked[slot].queued) < 0) tracked[slot].retired = 0;
|
|
tracked[slot].queued = timestamp;
|
|
tracked_release();
|
|
}
|
|
|
|
static struct tracked_context track_detach(unsigned int context) {
|
|
struct tracked_context gone = {0};
|
|
tracked_acquire();
|
|
int slot = tracked_find(context, 0);
|
|
if (slot >= 0) {
|
|
gone = tracked[slot];
|
|
tracked[slot].context = 0;
|
|
}
|
|
tracked_release();
|
|
return gone;
|
|
}
|
|
|
|
static void track_restore(struct tracked_context entry) {
|
|
if (entry.context == 0) return;
|
|
tracked_acquire();
|
|
if (tracked_find(entry.context, 0) < 0) {
|
|
int slot = tracked_find(entry.context, 1);
|
|
if (slot >= 0) {
|
|
unsigned int generation = tracked[slot].generation;
|
|
tracked[slot] = entry;
|
|
tracked[slot].generation = generation;
|
|
}
|
|
}
|
|
tracked_release();
|
|
}
|
|
|
|
/* Quest's SELinux policy denies CMDSTREAM_READTIMESTAMP_CTXTID, which Mesa 26's Turnip needs to wait on
|
|
* timeline semaphores; a zero-timeout WAITTIMESTAMP on the last submitted timestamp answers it. */
|
|
static int read_timestamp(quest_ioctl_fn real, int fd, struct kgsl_cmdstream_readtimestamp_ctxtid *query) {
|
|
tracked_acquire();
|
|
int slot = tracked_find(query->context_id, 0);
|
|
unsigned int queued = slot < 0 ? 0 : tracked[slot].queued;
|
|
unsigned int retired = slot < 0 ? 0 : tracked[slot].retired;
|
|
unsigned int generation = slot < 0 ? 0 : tracked[slot].generation;
|
|
tracked_release();
|
|
if (slot < 0 || (query->type != KGSL_TIMESTAMP_RETIRED && query->type != KGSL_TIMESTAMP_QUEUED)) {
|
|
errno = EACCES;
|
|
return -1;
|
|
}
|
|
|
|
if (query->type == KGSL_TIMESTAMP_RETIRED && (int)(queued - retired) > 0) {
|
|
struct kgsl_device_waittimestamp_ctxtid wait = {
|
|
.context_id = query->context_id, .timestamp = queued, .timeout = 0};
|
|
if (real(fd, IOCTL_KGSL_DEVICE_WAITTIMESTAMP_CTXTID, &wait) == 0) {
|
|
retired = queued;
|
|
tracked_acquire();
|
|
slot = tracked_find(query->context_id, 0);
|
|
if (slot >= 0 && tracked[slot].generation == generation &&
|
|
(int)(retired - tracked[slot].retired) > 0) tracked[slot].retired = retired;
|
|
tracked_release();
|
|
} else if (errno != ETIMEDOUT) {
|
|
return -1;
|
|
}
|
|
}
|
|
query->timestamp = query->type == KGSL_TIMESTAMP_RETIRED ? retired : queued;
|
|
return 0;
|
|
}
|
|
|
|
int quest_kgsl_ioctl(quest_ioctl_fn real, int fd, unsigned long request, void *arg) {
|
|
struct tracked_context detached = {0};
|
|
if (request == IOCTL_KGSL_DRAWCTXT_DESTROY && arg != NULL)
|
|
detached = track_detach(((const struct kgsl_drawctxt_destroy *)arg)->drawctxt_id);
|
|
int rc = real(fd, request, arg);
|
|
if (request == IOCTL_KGSL_DRAWCTXT_DESTROY && rc != 0) {
|
|
int error = errno;
|
|
track_restore(detached);
|
|
errno = error;
|
|
}
|
|
if (rc == 0 && arg != NULL && request == IOCTL_KGSL_GPU_COMMAND) {
|
|
const struct kgsl_gpu_command *submit = arg;
|
|
track_submit(submit->context_id, submit->timestamp);
|
|
}
|
|
if (rc != -1 || (errno != EACCES && errno != EPERM) || arg == NULL) return rc;
|
|
|
|
if (request == IOCTL_KGSL_CMDSTREAM_READTIMESTAMP_CTXTID)
|
|
return read_timestamp(real, fd, arg);
|
|
|
|
if (request == IOCTL_KGSL_GPUMEM_ALLOC_ID) {
|
|
struct kgsl_gpumem_alloc_id *old = arg;
|
|
struct kgsl_gpuobj_alloc alloc = {.size = old->size, .flags = old->flags};
|
|
rc = real(fd, IOCTL_KGSL_GPUOBJ_ALLOC, &alloc);
|
|
if (rc != 0) { report("alloc failed", errno); return rc; }
|
|
struct kgsl_gpuobj_info info = {.id = alloc.id};
|
|
rc = real(fd, IOCTL_KGSL_GPUOBJ_INFO, &info);
|
|
int error = errno;
|
|
if (rc == 0 && (info.size > SIZE_MAX || alloc.mmapsize > SIZE_MAX ||
|
|
info.gpuaddr > ULONG_MAX || info.flags > UINT_MAX)) {
|
|
rc = -1;
|
|
error = EOVERFLOW;
|
|
}
|
|
if (rc != 0) {
|
|
struct kgsl_gpuobj_free release = {.id = alloc.id};
|
|
if (real(fd, IOCTL_KGSL_GPUOBJ_FREE, &release) != 0)
|
|
report("cleanup failed", errno);
|
|
errno = error;
|
|
report("info failed", error);
|
|
return rc;
|
|
}
|
|
old->id = alloc.id;
|
|
old->flags = (unsigned int)info.flags;
|
|
old->size = info.size;
|
|
old->mmapsize = alloc.mmapsize;
|
|
old->gpuaddr = info.gpuaddr;
|
|
report("allocation succeeded", 0);
|
|
return 0;
|
|
}
|
|
if (request == IOCTL_KGSL_GPUMEM_FREE_ID) {
|
|
const struct kgsl_gpumem_free_id *old = arg;
|
|
struct kgsl_gpuobj_free release = {.id = old->id};
|
|
rc = real(fd, IOCTL_KGSL_GPUOBJ_FREE, &release);
|
|
if (rc != 0) report("free failed", errno);
|
|
}
|
|
return rc;
|
|
}
|