mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-11 02:51:10 +00:00
fix(quest): gamescope no longer aborts at start with vk.WaitSemaphores -13 (#1938)
This commit is contained in:
5 files changed
+270
-16
No files matched your search
@@ -46,6 +46,13 @@ needs to wait on timeline semaphores; the adapter answers it from the last submi
|
||||
and a zero-timeout `WAITTIMESTAMP`. Unhandled controller ioctls forward to the next
|
||||
preload hook rather than skipping it with a raw syscall.
|
||||
|
||||
The Ubuntu runtime's Turnip polls timeline points with
|
||||
`CMDSTREAM_READTIMESTAMP_CTXTID` and reports any failure as `VK_ERROR_UNKNOWN`,
|
||||
which aborted gamescope with `vk.WaitSemaphores` result -13 on Quest. The Linux
|
||||
preload (`tools/linuxfs/preload/kgslpoll.c`) answers a failed read from one
|
||||
millisecond `WAITTIMESTAMP` calls over the context's submitted timestamps, and
|
||||
logs refused KGSL ioctls to `session.log` as `[QuestKGSL] ioctl 0x.. refused`.
|
||||
|
||||
`tools/build_local.sh` and CI build the wrapper from source using the pinned
|
||||
Android NDK; the normal preload builds include the shared Linux adapter.
|
||||
Runtime input verification checks the resulting driver archive and invalidates
|
||||
|
||||
@@ -1,28 +1,112 @@
|
||||
#define _GNU_SOURCE
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#define KGSL_IOC_TYPE 0x09
|
||||
#define KGSL_TIMESTAMP_RETIRED 0x00000002
|
||||
#include "../../quest/kgsl_compat.h"
|
||||
|
||||
struct kgsl_device_waittimestamp_ctxtid {
|
||||
unsigned int context_id;
|
||||
unsigned int timestamp;
|
||||
unsigned int timeout;
|
||||
};
|
||||
#define KGSL_CONTEXTS 1024
|
||||
|
||||
struct kgsl_cmdstream_readtimestamp_ctxtid {
|
||||
unsigned int context_id;
|
||||
unsigned int type;
|
||||
unsigned int timestamp;
|
||||
};
|
||||
static struct {
|
||||
uint32_t submitted;
|
||||
uint32_t retired;
|
||||
int owner;
|
||||
} contexts[KGSL_CONTEXTS];
|
||||
|
||||
#define IOCTL_KGSL_DEVICE_WAITTIMESTAMP_CTXTID \
|
||||
_IOW(KGSL_IOC_TYPE, 0x7, struct kgsl_device_waittimestamp_ctxtid)
|
||||
#define IOCTL_KGSL_CMDSTREAM_READTIMESTAMP_CTXTID \
|
||||
_IOWR(KGSL_IOC_TYPE, 0x16, struct kgsl_cmdstream_readtimestamp_ctxtid)
|
||||
static void report_denied(unsigned long request, int error) {
|
||||
static unsigned reports;
|
||||
int saved = errno;
|
||||
|
||||
if (__atomic_fetch_add(&reports, 1, __ATOMIC_RELAXED) < 8)
|
||||
fprintf(stderr, "[QuestKGSL] ioctl 0x%lx refused, errno=%d\n", request & 0xff, error);
|
||||
errno = saved;
|
||||
}
|
||||
|
||||
static void note_submit(unsigned int context_id, unsigned int timestamp) {
|
||||
uint32_t known;
|
||||
|
||||
if (context_id >= KGSL_CONTEXTS) return;
|
||||
known = __atomic_load_n(&contexts[context_id].submitted, __ATOMIC_RELAXED);
|
||||
while ((int32_t)(timestamp - known) > 0 &&
|
||||
!__atomic_compare_exchange_n(&contexts[context_id].submitted, &known, timestamp, 1,
|
||||
__ATOMIC_RELAXED, __ATOMIC_RELAXED)) {
|
||||
}
|
||||
}
|
||||
|
||||
static int wait_retired(int (*real)(int, unsigned long, void *), int fd, uint32_t id, uint32_t timestamp) {
|
||||
/* KGSL reads a timeout of 0 as wait forever. */
|
||||
struct kgsl_device_waittimestamp_ctxtid wait = {.context_id = id, .timestamp = timestamp, .timeout = 1};
|
||||
|
||||
if (real(fd, IOCTL_KGSL_DEVICE_WAITTIMESTAMP_CTXTID, &wait) == 0) return 0;
|
||||
return errno == ETIMEDOUT || errno == ETIME || errno == EINTR || errno == EAGAIN ? 1 : -1;
|
||||
}
|
||||
|
||||
/* shortcut: an unfinished timestamp sleeps a jiffy per poll, read the memstore instead if Quest's mmap of it is proven */
|
||||
static int read_retired(int (*real)(int, unsigned long, void *), int fd,
|
||||
struct kgsl_cmdstream_readtimestamp_ctxtid *read) {
|
||||
uint32_t id = read->context_id;
|
||||
uint32_t timestamp, submitted;
|
||||
int result;
|
||||
|
||||
if (read->type != KGSL_TIMESTAMP_RETIRED || id >= KGSL_CONTEXTS ||
|
||||
__atomic_load_n(&contexts[id].owner, __ATOMIC_RELAXED) != fd + 1)
|
||||
return -1;
|
||||
timestamp = __atomic_load_n(&contexts[id].retired, __ATOMIC_RELAXED);
|
||||
submitted = __atomic_load_n(&contexts[id].submitted, __ATOMIC_RELAXED);
|
||||
/* KGSL checks a context's owner on every wait, which catches one that died with a reused descriptor. */
|
||||
if (timestamp == submitted && wait_retired(real, fd, id, timestamp) < 0) return -1;
|
||||
while (timestamp != submitted) {
|
||||
result = wait_retired(real, fd, id, timestamp + 1);
|
||||
if (result < 0) return -1;
|
||||
if (result > 0) break;
|
||||
timestamp++;
|
||||
__atomic_store_n(&contexts[id].retired, timestamp, __ATOMIC_RELAXED);
|
||||
}
|
||||
read->timestamp = timestamp;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Quest refuses some KGSL ioctls, and Mesa 27 turns a failed timestamp read into VK_ERROR_UNKNOWN. */
|
||||
__attribute__((visibility("hidden"))) void bl_kgsl_track(int (*real)(int, unsigned long, void *),
|
||||
int fd, unsigned long request, void *arg,
|
||||
int *rc) {
|
||||
int saved = errno;
|
||||
|
||||
if (arg == NULL) return;
|
||||
if (*rc == 0) {
|
||||
if (request == IOCTL_KGSL_GPU_COMMAND) {
|
||||
const struct kgsl_gpu_command *submit = arg;
|
||||
note_submit(submit->context_id, submit->timestamp);
|
||||
} else if (request == IOCTL_KGSL_GPU_AUX_COMMAND) {
|
||||
const struct kgsl_gpu_aux_command *submit = arg;
|
||||
note_submit(submit->context_id, submit->timestamp);
|
||||
} else if (request == IOCTL_KGSL_DRAWCTXT_CREATE) {
|
||||
const struct kgsl_drawctxt_create *create = arg;
|
||||
if (create->drawctxt_id < KGSL_CONTEXTS) {
|
||||
__atomic_store_n(&contexts[create->drawctxt_id].submitted, 0, __ATOMIC_RELAXED);
|
||||
__atomic_store_n(&contexts[create->drawctxt_id].retired, 0, __ATOMIC_RELAXED);
|
||||
/* User timestamps can leave gaps that would take one wait per integer, so their reads stay failed. */
|
||||
__atomic_store_n(&contexts[create->drawctxt_id].owner,
|
||||
create->flags & KGSL_CONTEXT_USER_GENERATED_TS ? 0 : fd + 1, __ATOMIC_RELAXED);
|
||||
}
|
||||
} else if (request == IOCTL_KGSL_DRAWCTXT_DESTROY) {
|
||||
const struct kgsl_drawctxt_destroy *destroy = arg;
|
||||
if (destroy->drawctxt_id < KGSL_CONTEXTS)
|
||||
__atomic_store_n(&contexts[destroy->drawctxt_id].owner, 0, __ATOMIC_RELAXED);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (_IOC_TYPE(request) == KGSL_IOC_TYPE && (saved == EACCES || saved == EPERM ||
|
||||
request == IOCTL_KGSL_CMDSTREAM_READTIMESTAMP_CTXTID))
|
||||
report_denied(request, saved);
|
||||
if (request != IOCTL_KGSL_CMDSTREAM_READTIMESTAMP_CTXTID || saved == EBADF) return;
|
||||
if (read_retired(real, fd, arg) == 0)
|
||||
*rc = 0;
|
||||
else if (errno != saved)
|
||||
report_denied(IOCTL_KGSL_DEVICE_WAITTIMESTAMP_CTXTID, errno);
|
||||
}
|
||||
|
||||
static int enabled(void) {
|
||||
static int cached = -1;
|
||||
|
||||
@@ -404,6 +404,8 @@ void bl_kgsl_chip_id_fixup(unsigned long request, void *arg, int rc);
|
||||
#include "../../quest/kgsl_compat.h"
|
||||
int bl_kgsl_poll(int (*real)(int, unsigned long, void *), int fd, unsigned long request,
|
||||
void *arg, int *rc) __attribute__((visibility("hidden")));
|
||||
void bl_kgsl_track(int (*real)(int, unsigned long, void *), int fd, unsigned long request,
|
||||
void *arg, int *rc) __attribute__((visibility("hidden")));
|
||||
int bl_ntsync_ioctl(int fd, unsigned long request, void *arg, int *rc) __attribute__((visibility("hidden")));
|
||||
int bl_drm_gem_close(int fd, unsigned long request, void *arg, int *rc) __attribute__((visibility("hidden")));
|
||||
int bl_venc_ioctl(int fd, unsigned long request, void *arg, int *rc) __attribute__((visibility("hidden")));
|
||||
@@ -430,6 +432,7 @@ int ioctl(int fd, unsigned long request, ...) {
|
||||
if (bl_winebus_rumble_ioctl(real, fd, request, arg, __builtin_return_address(0), &rc)) return rc;
|
||||
if (bl_writewatch_ioctl(real, fd, request, arg, &rc)) return rc;
|
||||
if (!bl_kgsl_poll(real, fd, request, arg, &rc)) rc = quest_kgsl_ioctl(real, fd, request, arg);
|
||||
bl_kgsl_track(real, fd, request, arg, &rc);
|
||||
bl_kgsl_chip_id_fixup(request, arg, rc);
|
||||
if (arg == NULL || !is_link_query(request)) return rc;
|
||||
int saved = errno;
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "../quest/kgsl_compat.h"
|
||||
|
||||
void bl_kgsl_track(int (*real)(int, unsigned long, void *), int fd, unsigned long request,
|
||||
void *arg, int *rc);
|
||||
|
||||
static int read_error, wait_error, waits;
|
||||
static unsigned int retired;
|
||||
|
||||
static int mock(int fd, unsigned long request, void *arg) {
|
||||
assert(fd == 42 || fd == 43);
|
||||
if (request == IOCTL_KGSL_CMDSTREAM_READTIMESTAMP_CTXTID) {
|
||||
if (read_error) {
|
||||
errno = read_error;
|
||||
return -1;
|
||||
}
|
||||
((struct kgsl_cmdstream_readtimestamp_ctxtid *)arg)->timestamp = retired;
|
||||
return 0;
|
||||
}
|
||||
assert(request == IOCTL_KGSL_DEVICE_WAITTIMESTAMP_CTXTID);
|
||||
struct kgsl_device_waittimestamp_ctxtid *wait = arg;
|
||||
assert(wait->context_id == 3 && wait->timeout != 0);
|
||||
waits++;
|
||||
if (wait_error) {
|
||||
errno = wait_error;
|
||||
return -1;
|
||||
}
|
||||
if (wait->timestamp > retired) {
|
||||
errno = ETIMEDOUT;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int issue(unsigned long request, void *arg) {
|
||||
int rc = mock(42, request, arg);
|
||||
bl_kgsl_track(mock, 42, request, arg, &rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void submit(unsigned int timestamp) {
|
||||
struct kgsl_gpu_command command = {.context_id = 3, .timestamp = timestamp};
|
||||
int rc = 0;
|
||||
bl_kgsl_track(mock, 42, IOCTL_KGSL_GPU_COMMAND, &command, &rc);
|
||||
assert(rc == 0);
|
||||
}
|
||||
|
||||
static int read_timestamp(unsigned int context, unsigned int *timestamp) {
|
||||
struct kgsl_cmdstream_readtimestamp_ctxtid read = {
|
||||
.context_id = context, .type = KGSL_TIMESTAMP_RETIRED};
|
||||
int rc = issue(IOCTL_KGSL_CMDSTREAM_READTIMESTAMP_CTXTID, &read);
|
||||
*timestamp = read.timestamp;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void create_context(unsigned int context) {
|
||||
struct kgsl_drawctxt_create create = {.drawctxt_id = context};
|
||||
int rc = 0;
|
||||
bl_kgsl_track(mock, 42, IOCTL_KGSL_DRAWCTXT_CREATE, &create, &rc);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
unsigned int timestamp;
|
||||
|
||||
retired = 5;
|
||||
assert(read_timestamp(3, ×tamp) == 0 && timestamp == 5 && waits == 0);
|
||||
|
||||
read_error = EACCES;
|
||||
retired = 0;
|
||||
errno = EACCES;
|
||||
assert(read_timestamp(3, ×tamp) == -1 && errno == EACCES && waits == 0);
|
||||
|
||||
create_context(3);
|
||||
assert(read_timestamp(3, ×tamp) == 0 && timestamp == 0 && waits == 1);
|
||||
|
||||
wait_error = EINVAL;
|
||||
assert(read_timestamp(3, ×tamp) == -1 && errno == EINVAL);
|
||||
wait_error = 0;
|
||||
|
||||
waits = 0;
|
||||
read_error = EBADF;
|
||||
assert(read_timestamp(3, ×tamp) == -1 && errno == EBADF && waits == 0);
|
||||
read_error = EACCES;
|
||||
|
||||
struct kgsl_cmdstream_readtimestamp_ctxtid other = {.context_id = 3, .type = KGSL_TIMESTAMP_RETIRED};
|
||||
int other_rc = mock(43, IOCTL_KGSL_CMDSTREAM_READTIMESTAMP_CTXTID, &other);
|
||||
bl_kgsl_track(mock, 43, IOCTL_KGSL_CMDSTREAM_READTIMESTAMP_CTXTID, &other, &other_rc);
|
||||
assert(other_rc == -1 && errno == EACCES && waits == 0);
|
||||
|
||||
submit(1);
|
||||
submit(2);
|
||||
submit(3);
|
||||
assert(read_timestamp(3, ×tamp) == 0 && timestamp == 0 && waits == 1);
|
||||
|
||||
retired = 2;
|
||||
waits = 0;
|
||||
assert(read_timestamp(3, ×tamp) == 0 && timestamp == 2 && waits == 3);
|
||||
|
||||
waits = 0;
|
||||
assert(read_timestamp(3, ×tamp) == 0 && timestamp == 2 && waits == 1);
|
||||
|
||||
retired = 3;
|
||||
waits = 0;
|
||||
assert(read_timestamp(3, ×tamp) == 0 && timestamp == 3 && waits == 1);
|
||||
|
||||
waits = 0;
|
||||
assert(read_timestamp(3, ×tamp) == 0 && timestamp == 3 && waits == 1);
|
||||
|
||||
wait_error = EINVAL;
|
||||
assert(read_timestamp(3, ×tamp) == -1 && errno == EINVAL);
|
||||
wait_error = 0;
|
||||
|
||||
submit(4);
|
||||
retired = 4;
|
||||
wait_error = EDEADLK;
|
||||
assert(read_timestamp(3, ×tamp) == -1 && errno == EDEADLK);
|
||||
wait_error = 0;
|
||||
|
||||
struct kgsl_gpu_aux_command aux = {.context_id = 3, .timestamp = 6};
|
||||
int rc = 0;
|
||||
bl_kgsl_track(mock, 42, IOCTL_KGSL_GPU_AUX_COMMAND, &aux, &rc);
|
||||
retired = 6;
|
||||
assert(read_timestamp(3, ×tamp) == 0 && timestamp == 6);
|
||||
|
||||
submit(8);
|
||||
submit(7);
|
||||
retired = 8;
|
||||
assert(read_timestamp(3, ×tamp) == 0 && timestamp == 8);
|
||||
|
||||
create_context(3);
|
||||
retired = 0;
|
||||
waits = 0;
|
||||
assert(read_timestamp(3, ×tamp) == 0 && timestamp == 0 && waits == 1);
|
||||
|
||||
struct kgsl_drawctxt_destroy destroy = {.drawctxt_id = 3};
|
||||
bl_kgsl_track(mock, 42, IOCTL_KGSL_DRAWCTXT_DESTROY, &destroy, &rc);
|
||||
assert(read_timestamp(3, ×tamp) == -1 && errno == EACCES);
|
||||
|
||||
struct kgsl_drawctxt_create user = {.flags = KGSL_CONTEXT_USER_GENERATED_TS, .drawctxt_id = 3};
|
||||
bl_kgsl_track(mock, 42, IOCTL_KGSL_DRAWCTXT_CREATE, &user, &rc);
|
||||
waits = 0;
|
||||
assert(read_timestamp(3, ×tamp) == -1 && errno == EACCES && waits == 0);
|
||||
|
||||
errno = EACCES;
|
||||
assert(read_timestamp(5000, ×tamp) == -1 && errno == EACCES);
|
||||
|
||||
puts("PASS: refused timestamp reads answered from retired waits");
|
||||
}
|
||||
@@ -19,6 +19,15 @@ class QuestKgslTest(unittest.TestCase):
|
||||
self.assertIn('PASS: 12 GPUOBJ', result.stdout)
|
||||
self.assertIn('PASS: denied timestamp reads', result.stdout)
|
||||
|
||||
def test_refused_timestamp_read_is_answered_from_waits(self):
|
||||
with tempfile.TemporaryDirectory() as folder:
|
||||
binary = str(Path(folder) / 'test-kgsl-read')
|
||||
subprocess.run(['gcc', '-Wall', '-Wextra', '-Werror', '-O2', '-o', binary,
|
||||
str(QUEST.parent / 'tests/test_kgsl_read.c'),
|
||||
str(QUEST.parent / 'linuxfs/preload/kgslpoll.c')], check=True)
|
||||
result = subprocess.run([binary], capture_output=True, text=True, check=True, timeout=10)
|
||||
self.assertIn('PASS: refused timestamp reads', result.stdout)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user