mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-11 02:51:10 +00:00
fix(quest): answer denied KGSL timestamp reads so Steam starts on Quest 3
This commit is contained in:
1 parent
951270bc8f
commit
4e571f1f99
4 files changed
+146
-1
No files matched your search
+3
-1
@@ -41,7 +41,9 @@ Both the Android driver wrapper and the Linux session preload use
|
||||
`tools/quest/kgsl_compat.c`. Successful legacy allocation/free calls are
|
||||
unchanged. Only `EACCES`/`EPERM` failures retry through `GPUOBJ_ALLOC`,
|
||||
`GPUOBJ_INFO` and `GPUOBJ_FREE`. The Android wrapper depends on the original
|
||||
bundled Turnip binary. Unhandled controller ioctls forward to the next
|
||||
bundled Turnip binary. Quest's SELinux policy also denies `CMDSTREAM_READTIMESTAMP_CTXTID`, which Mesa 26's Turnip
|
||||
needs to wait on timeline semaphores; the adapter answers it from the last submitted timestamp
|
||||
and a zero-timeout `WAITTIMESTAMP`. Unhandled controller ioctls forward to the next
|
||||
preload hook rather than skipping it with a raw syscall.
|
||||
|
||||
`tools/build_local.sh` and CI build the wrapper from source using the pinned
|
||||
|
||||
@@ -29,10 +29,92 @@ static void report(const char *operation, int error) {
|
||||
errno = saved;
|
||||
}
|
||||
|
||||
#define TRACKED_CONTEXTS 32
|
||||
|
||||
static struct {
|
||||
unsigned int context, queued, retired;
|
||||
} tracked[TRACKED_CONTEXTS];
|
||||
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 (int i = 0; i < TRACKED_CONTEXTS; i++) {
|
||||
if (tracked[i].context == context) return i;
|
||||
if (free_slot < 0 && tracked[i].context == 0) free_slot = i;
|
||||
}
|
||||
if (!create) return -1;
|
||||
if (free_slot < 0) free_slot = (int)(context % TRACKED_CONTEXTS);
|
||||
tracked[free_slot].context = context;
|
||||
tracked[free_slot].queued = tracked[free_slot].retired = 0;
|
||||
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 ((int)(timestamp - tracked[slot].queued) < 0) tracked[slot].retired = 0;
|
||||
tracked[slot].queued = timestamp;
|
||||
tracked_release();
|
||||
}
|
||||
|
||||
static void track_destroy(unsigned int context) {
|
||||
tracked_acquire();
|
||||
int slot = tracked_find(context, 0);
|
||||
if (slot >= 0) tracked[slot].context = 0;
|
||||
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;
|
||||
tracked_release();
|
||||
if (slot < 0) { 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 && (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) {
|
||||
int rc = real(fd, request, arg);
|
||||
if (rc == 0 && arg != NULL) {
|
||||
if (request == IOCTL_KGSL_GPU_COMMAND) {
|
||||
const struct kgsl_gpu_command *submit = arg;
|
||||
track_submit(submit->context_id, submit->timestamp);
|
||||
} else if (request == IOCTL_KGSL_DRAWCTXT_DESTROY) {
|
||||
track_destroy(((const struct kgsl_drawctxt_destroy *)arg)->drawctxt_id);
|
||||
}
|
||||
}
|
||||
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};
|
||||
|
||||
@@ -43,7 +43,66 @@ static struct kgsl_gpumem_alloc_id reset(void) {
|
||||
address = 0x100000;
|
||||
return (struct kgsl_gpumem_alloc_id){.size = 123, .flags = 0x10000};
|
||||
}
|
||||
static int read_error, wait_error, waits;
|
||||
static unsigned int waited_for;
|
||||
static int timestamp_mock(int fd, unsigned long request, void *arg) {
|
||||
assert(fd == 42);
|
||||
if (request == IOCTL_KGSL_GPU_COMMAND || request == IOCTL_KGSL_DRAWCTXT_DESTROY) return 0;
|
||||
if (request == IOCTL_KGSL_CMDSTREAM_READTIMESTAMP_CTXTID) {
|
||||
errno = read_error;
|
||||
return -1;
|
||||
}
|
||||
if (request == IOCTL_KGSL_DEVICE_WAITTIMESTAMP_CTXTID) {
|
||||
const struct kgsl_device_waittimestamp_ctxtid *w = arg;
|
||||
assert(w->timeout == 0);
|
||||
waits++;
|
||||
waited_for = w->timestamp;
|
||||
if (wait_error) { errno = wait_error; return -1; }
|
||||
return 0;
|
||||
}
|
||||
errno = EACCES;
|
||||
return -1;
|
||||
}
|
||||
static void submit(unsigned int context, unsigned int timestamp) {
|
||||
struct kgsl_gpu_command cmd = {.context_id = context, .timestamp = timestamp};
|
||||
assert(quest_kgsl_ioctl(timestamp_mock, 42, IOCTL_KGSL_GPU_COMMAND, &cmd) == 0);
|
||||
}
|
||||
static int read_ts(unsigned int context, unsigned int type, unsigned int *timestamp) {
|
||||
struct kgsl_cmdstream_readtimestamp_ctxtid q = {.context_id = context, .type = type, .timestamp = 0xdead};
|
||||
int rc = quest_kgsl_ioctl(timestamp_mock, 42, IOCTL_KGSL_CMDSTREAM_READTIMESTAMP_CTXTID, &q);
|
||||
*timestamp = q.timestamp;
|
||||
return rc;
|
||||
}
|
||||
static void timestamp_scenarios(void) {
|
||||
unsigned int ts;
|
||||
read_error = EACCES;
|
||||
assert(read_ts(5, KGSL_TIMESTAMP_RETIRED, &ts) == -1 && errno == EACCES && ts == 0xdead);
|
||||
submit(5, 10);
|
||||
assert(read_ts(5, KGSL_TIMESTAMP_QUEUED, &ts) == 0 && ts == 10 && waits == 0);
|
||||
wait_error = ETIMEDOUT;
|
||||
assert(read_ts(5, KGSL_TIMESTAMP_RETIRED, &ts) == 0 && ts == 0 && waits == 1 && waited_for == 10);
|
||||
wait_error = 0;
|
||||
assert(read_ts(5, KGSL_TIMESTAMP_RETIRED, &ts) == 0 && ts == 10 && waits == 2);
|
||||
assert(read_ts(5, KGSL_TIMESTAMP_RETIRED, &ts) == 0 && ts == 10 && waits == 2);
|
||||
submit(5, 11);
|
||||
wait_error = ETIMEDOUT;
|
||||
assert(read_ts(5, KGSL_TIMESTAMP_RETIRED, &ts) == 0 && ts == 10 && waited_for == 11);
|
||||
wait_error = EIO;
|
||||
assert(read_ts(5, KGSL_TIMESTAMP_RETIRED, &ts) == -1 && errno == EIO);
|
||||
wait_error = 0;
|
||||
submit(5, 0xfffffffe);
|
||||
submit(5, 1);
|
||||
assert(read_ts(5, KGSL_TIMESTAMP_RETIRED, &ts) == 0 && ts == 1);
|
||||
struct kgsl_drawctxt_destroy gone = {.drawctxt_id = 5};
|
||||
assert(quest_kgsl_ioctl(timestamp_mock, 42, IOCTL_KGSL_DRAWCTXT_DESTROY, &gone) == 0);
|
||||
assert(read_ts(5, KGSL_TIMESTAMP_QUEUED, &ts) == -1 && errno == EACCES);
|
||||
read_error = EIO;
|
||||
submit(6, 3);
|
||||
assert(read_ts(6, KGSL_TIMESTAMP_QUEUED, &ts) == -1 && errno == EIO);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
timestamp_scenarios();
|
||||
struct kgsl_gpumem_alloc_id p = reset();
|
||||
assert(quest_kgsl_ioctl(mock, 42, IOCTL_KGSL_GPUMEM_ALLOC_ID, &p) == 0 && count == 1);
|
||||
p = reset(); old_error = EACCES;
|
||||
@@ -74,4 +133,5 @@ int main(void) {
|
||||
reset();
|
||||
assert(quest_kgsl_ioctl(mock, 42, IOCTL_KGSL_GPUMEM_FREE_ID, &release) == 0 && count == 1);
|
||||
puts("PASS: 12 GPUOBJ translation, passthrough, failure and cleanup scenarios");
|
||||
puts("PASS: denied timestamp reads answered from tracked submissions");
|
||||
}
|
||||
@@ -17,6 +17,7 @@ class QuestKgslTest(unittest.TestCase):
|
||||
str(QUEST / 'test_kgsl_compat.c'), str(QUEST / 'kgsl_compat.c')], check=True)
|
||||
result = subprocess.run([binary], capture_output=True, text=True, check=True, timeout=10)
|
||||
self.assertIn('PASS: 12 GPUOBJ', result.stdout)
|
||||
self.assertIn('PASS: denied timestamp reads', result.stdout)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
Reference in new issue
Block a user