diff --git a/tools/linuxfs/preload/sysv.c b/tools/linuxfs/preload/sysv.c index 6b9a21dc8..36d7bdd70 100644 --- a/tools/linuxfs/preload/sysv.c +++ b/tools/linuxfs/preload/sysv.c @@ -7,7 +7,8 @@ * segment is a plain file that shmat maps. Objects are found by the inode of their file, which is * what the id is. semop applies its operations one by one (no all-or-nothing across a set) and * SEM_UNDO is accepted but not performed - enough for the client, which uses single-counter sets. - * Message queues stay unsupported. */ + * A message queue is a table of slots in its file: gamescope hands mangoapp (Deck mode's performance + * overlay) each frame's timings through one. */ #define _GNU_SOURCE #include #include @@ -15,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -23,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -396,6 +399,126 @@ int shmctl(int shmid, int cmd, struct shmid_ds *buf) { } } +/* ------------------------------------------------------------------ message queues */ + +/* Only what gamescope and mangoapp use: msgtyp 0 or > 0, IPC_NOWAIT and IPC_RMID. */ +#define MSG_MAGIC 0x574e4d51u /* "WNMQ" */ +#define MSG_SLOTS 32 + +struct msg_queue { + uint32_t magic; + _Atomic int32_t lock; /* 0 free, else the holder's pid */ + _Atomic int32_t changed; /* bumped on every send and receive; waiters sleep on it */ + uint64_t next_seq; + struct { uint64_t seq; long mtype; size_t len; char data[2048]; } slot[MSG_SLOTS]; /* seq 0: free */ +}; + +static struct msg_queue *msg_map(int id) { + int fd = open_by_id("msg", id); + if (fd < 0) return NULL; + struct msg_queue *q = mmap(NULL, sizeof(*q), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + close(fd); + if (q == MAP_FAILED) return NULL; + if (q->magic == MSG_MAGIC) return q; + munmap(q, sizeof(*q)); + errno = EINVAL; + return NULL; +} + +/* A process killed while holding the lock (Android ends sessions without warning) loses it. */ +static void msg_lock(struct msg_queue *q) { + for (int32_t owner = 0; !atomic_compare_exchange_strong(&q->lock, &owner, getpid()); owner = 0) { + if (kill(owner, 0) != 0 && errno == ESRCH) atomic_compare_exchange_strong(&q->lock, &owner, 0); + else futex(&q->lock, FUTEX_WAIT, owner, &(struct timespec){0, 20000000}); + } +} + +static void msg_unlock(struct msg_queue *q, int changed) { + atomic_store(&q->lock, 0); + futex(&q->lock, FUTEX_WAKE, 1, NULL); + if (changed) { + atomic_fetch_add(&q->changed, 1); + futex(&q->changed, FUTEX_WAKE, INT_MAX, NULL); + } +} + +int msgget(key_t key, int msgflg) { + int created; + int fd = open_object("msg", key, msgflg, &created); + if (fd < 0) return -1; + uint32_t magic = MSG_MAGIC; + if (created && (ftruncate(fd, sizeof(struct msg_queue)) != 0 || pwrite(fd, &magic, sizeof(magic), 0) != sizeof(magic))) { + close(fd); + return -1; + } + int id = object_id(fd); + close(fd); + return id; +} + +int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg) { + struct msg_queue *q = msg_map(msqid); + if (!q) return -1; + if (msgsz > sizeof(q->slot[0].data) || *(const long *)msgp < 1) { munmap(q, sizeof(*q)); errno = EINVAL; return -1; } + for (;;) { + msg_lock(q); + int32_t seen = atomic_load(&q->changed); + for (int i = 0; i < MSG_SLOTS; i++) { + if (q->slot[i].seq) continue; + q->slot[i].mtype = *(const long *)msgp; + q->slot[i].len = msgsz; + memcpy(q->slot[i].data, (const long *)msgp + 1, msgsz); + q->slot[i].seq = ++q->next_seq; + msg_unlock(q, 1); + munmap(q, sizeof(*q)); + return 0; + } + msg_unlock(q, 0); + if ((msgflg & IPC_NOWAIT) || (futex(&q->changed, FUTEX_WAIT, seen, NULL) != 0 && errno == EINTR)) { + munmap(q, sizeof(*q)); + if (msgflg & IPC_NOWAIT) errno = EAGAIN; + return -1; + } + } +} + +ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg) { + if (msgtyp < 0) { errno = EINVAL; return -1; } + struct msg_queue *q = msg_map(msqid); + if (!q) return -1; + for (;;) { + msg_lock(q); + int32_t seen = atomic_load(&q->changed); + int best = -1; + for (int i = 0; i < MSG_SLOTS; i++) + if (q->slot[i].seq && (!msgtyp || q->slot[i].mtype == msgtyp) && (best < 0 || q->slot[i].seq < q->slot[best].seq)) + best = i; + if (best >= 0) { + size_t n = q->slot[best].len < msgsz ? q->slot[best].len : msgsz; + *(long *)msgp = q->slot[best].mtype; + memcpy((long *)msgp + 1, q->slot[best].data, n); + q->slot[best].seq = 0; + msg_unlock(q, 1); + munmap(q, sizeof(*q)); + return (ssize_t)n; + } + msg_unlock(q, 0); + if ((msgflg & IPC_NOWAIT) || (futex(&q->changed, FUTEX_WAIT, seen, NULL) != 0 && errno == EINTR)) { + munmap(q, sizeof(*q)); + if (msgflg & IPC_NOWAIT) errno = ENOMSG; + return -1; + } + } +} + +int msgctl(int msqid, int cmd, struct msqid_ds *buf) { + if (cmd != IPC_RMID) { errno = EINVAL; return -1; } + pthread_mutex_lock(&g_lock); + int rc = unlink_by_id("msg", msqid); + pthread_mutex_unlock(&g_lock); + return rc; +} + /* * fork() carries over only the calling thread, so a lock another thread was holding at that * instant stays held in the child by a thread that is not there to release it. Taking it before diff --git a/tools/linuxfs/preload/tracefs.c b/tools/linuxfs/preload/tracefs.c new file mode 100644 index 000000000..0d0b8f36b --- /dev/null +++ b/tools/linuxfs/preload/tracefs.c @@ -0,0 +1,46 @@ +/* + * A trace instance for mangoapp where the kernel's tracefs is closed to the app. + * + * Valve's mangoapp (Deck mode's performance overlay) reads the GPU memory total on a Qualcomm + * display driver (msm_dpu / msm_drm) from the gpu_mem_total event, through a tracefs instance of + * its own - and aborts when tracefs_instance_create() fails, taking the whole overlay with it. + * Android mounts tracefs for root and the readtracefs group only, so it always fails here. + * + * The real calls run first. Only when they fail is the instance placed in an empty directory of + * the session's own, where it exists but has no events: the overlay runs, and the one reading it + * cannot take shows no GPU memory. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include + +#define STANDIN_DIR "/tmp/droiddeck-tracefs" + +struct tracefs_instance; +struct tep_handle; + +struct tracefs_instance *tracefs_instance_create(const char *name) { + struct tracefs_instance *(*real)(const char *) = dlsym(RTLD_NEXT, "tracefs_instance_create"); + struct tracefs_instance *instance = real ? real(name) : NULL; + if (instance || !name) return instance; + struct tracefs_instance *(*alloc)(const char *, const char *) = dlsym(RTLD_NEXT, "tracefs_instance_alloc"); + if (!alloc) return NULL; + char path[PATH_MAX]; + mkdir(STANDIN_DIR, 0700); + mkdir(STANDIN_DIR "/instances", 0700); + snprintf(path, sizeof(path), STANDIN_DIR "/instances/%s", name); + mkdir(path, 0700); + return alloc(STANDIN_DIR, name); +} + +/* The stand-in has no event formats to read, so it gets an empty handle rather than none. */ +struct tep_handle *tracefs_local_events(const char *tracing_dir) { + struct tep_handle *(*real)(const char *) = dlsym(RTLD_NEXT, "tracefs_local_events"); + struct tep_handle *tep = real ? real(tracing_dir) : NULL; + if (tep || !tracing_dir || strncmp(tracing_dir, STANDIN_DIR "/", sizeof(STANDIN_DIR))) return tep; + struct tep_handle *(*tep_alloc)(void) = dlsym(RTLD_DEFAULT, "tep_alloc"); + return tep_alloc ? tep_alloc() : NULL; +} diff --git a/tools/mangoapp/mangoapp b/tools/mangoapp/mangoapp index 6557ed559..4d541f73e 100644 --- a/tools/mangoapp/mangoapp +++ b/tools/mangoapp/mangoapp @@ -1,4 +1,8 @@ #!/bin/sh # gamescope --mangoapp runs `mangoapp` from PATH; this puts Valve's build beside the libraries the # runtime lacks (fmt 10, spdlog 1.13, glfw, libtracefs, libtraceevent), staged by the app. +# mangoapp reads its window through GLFW's X11 API. GLFW 3.4 takes XDG_SESSION_TYPE=wayland with a +# WAYLAND_DISPLAY as Wayland-only, and unset, libwayland falls back to wayland-0 - DroidDeck's own +# compositor, which shares XDG_RUNTIME_DIR - so both fail; x11 makes GLFW use gamescope's X server. +export XDG_SESSION_TYPE=x11 LD_LIBRARY_PATH=/usr/local/lib/mangoapp${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH} exec /usr/local/lib/mangoapp/mangoapp "$@"