From 9cf305865b894b7aa15c26cff26278c88009922a Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sat, 21 Sep 2024 20:41:43 -0500 Subject: [PATCH] Add support for managing multiple SDL DRM FDs This is required for Vulkan+KMSDRM rendering. --- app/masterhook.c | 26 +++++----- app/masterhook_internal.c | 106 ++++++++++++++++++++++++++++++++------ 2 files changed, 101 insertions(+), 31 deletions(-) diff --git a/app/masterhook.c b/app/masterhook.c index 323c8ff6..eff42fb2 100644 --- a/app/masterhook.c +++ b/app/masterhook.c @@ -36,8 +36,8 @@ int g_QtDrmMasterFd = -1; struct stat g_DrmMasterStat; -// The DRM master FD created for SDL -int g_SdlDrmMasterFd = -1; +bool removeSdlFd(int fd); +bool createSdlFd(); int drmIsMaster(int fd) { @@ -111,23 +111,21 @@ int open64(const char *pathname, int flags, ...) // after SDL closes its DRM FD. int close(int fd) { + // Remove this entry from the SDL FD table + bool lastSdlFd = removeSdlFd(fd); + // Call the real thing int ret = ((typeof(close)*)dlsym(RTLD_NEXT, __FUNCTION__))(fd); - if (ret == 0) { - // If we just closed the SDL DRM master FD, restore master - // to the Qt DRM FD. This works because the Qt DRM master FD - // was master once before, so we can set it as master again - // using drmSetMaster() without CAP_SYS_ADMIN. - if (g_SdlDrmMasterFd != -1 && fd == g_SdlDrmMasterFd) { - if (drmSetMaster(g_QtDrmMasterFd) < 0) { - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, - "Failed to restore master to Qt DRM FD: %d", - errno); - } - g_SdlDrmMasterFd = -1; + // If we closed the last SDL FD, restore master to the Qt FD + if (ret == 0 && lastSdlFd) { + if (drmSetMaster(g_QtDrmMasterFd) < 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "Failed to restore master to Qt DRM FD: %d", + errno); } } + return ret; } diff --git a/app/masterhook_internal.c b/app/masterhook_internal.c index 5cc08b57..60e81490 100644 --- a/app/masterhook_internal.c +++ b/app/masterhook_internal.c @@ -28,7 +28,50 @@ extern int g_QtDrmMasterFd; extern struct stat g_DrmMasterStat; -extern int g_SdlDrmMasterFd; + +#define MAX_SDL_FD_COUNT 8 +int g_SdlDrmMasterFds[MAX_SDL_FD_COUNT]; +int g_SdlDrmMasterFdCount = 0; +SDL_SpinLock g_FdTableLock = 0; + +// Caller must hold g_FdTableLock +int getSdlFdEntryIndex(bool unused) +{ + for (int i = 0; i < MAX_SDL_FD_COUNT; i++) { + // We slightly bend the FD rules here by treating 0 + // as invalid since that's our global default value. + if (unused && g_SdlDrmMasterFds[i] <= 0) { + return i; + } + else if (!unused && g_SdlDrmMasterFds[i] > 0) { + return i; + } + } + + return -1; +} + +bool removeSdlFd(int fd) +{ + SDL_AtomicLock(&g_FdTableLock); + if (g_SdlDrmMasterFdCount != 0) { + // Clear the entry for this fd from the table + for (int i = 0; i < MAX_SDL_FD_COUNT; i++) { + if (fd == g_SdlDrmMasterFds[i]) { + g_SdlDrmMasterFds[i] = -1; + g_SdlDrmMasterFdCount--; + break; + } + } + + if (g_SdlDrmMasterFdCount == 0) { + SDL_AtomicUnlock(&g_FdTableLock); + return true; + } + } + SDL_AtomicUnlock(&g_FdTableLock); + return false; +} int openHook(const char *funcname, const char *pathname, int flags, va_list va) { @@ -55,32 +98,61 @@ int openHook(const char *funcname, const char *pathname, int flags, va_list va) fstat(fd, &fdstat); if (g_DrmMasterStat.st_dev == fdstat.st_dev && g_DrmMasterStat.st_ino == fdstat.st_ino) { + int freeFdIndex; + int allocatedFdIndex; + // It is our device. Time to do the magic! + SDL_AtomicLock(&g_FdTableLock); - // This code assumes SDL only ever opens a single FD - // for a given DRM device. - SDL_assert(g_SdlDrmMasterFd == -1); - - // Drop master on Qt's FD so we can pick it up for SDL. - if (drmDropMaster(g_QtDrmMasterFd) < 0) { + // Get a free index for us to put the new entry + freeFdIndex = getSdlFdEntryIndex(true); + if (freeFdIndex < 0) { + SDL_AtomicUnlock(&g_FdTableLock); + SDL_assert(freeFdIndex >= 0); SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, - "Failed to drop master on Qt DRM FD: %d", - errno); + "No unused SDL FD table entries!"); // Hope for the best return fd; } - // We are not allowed to call drmSetMaster() without CAP_SYS_ADMIN, - // but since we just dropped the master, we can become master by - // simply creating a new FD. Let's do it. - close(fd); - if (__OPEN_NEEDS_MODE(flags)) { - fd = ((typeof(open)*)dlsym(RTLD_NEXT, funcname))(pathname, flags, mode); + // Check if we have an allocated entry already + allocatedFdIndex = getSdlFdEntryIndex(false); + if (allocatedFdIndex >= 0) { + // Close fd that we opened earlier (skipping our close() hook) + ((typeof(close)*)dlsym(RTLD_NEXT, "close"))(fd); + + // dup() an existing FD into the unused slot + fd = dup(g_SdlDrmMasterFds[allocatedFdIndex]); } else { - fd = ((typeof(open)*)dlsym(RTLD_NEXT, funcname))(pathname, flags); + // Drop master on Qt's FD so we can pick it up for SDL. + if (drmDropMaster(g_QtDrmMasterFd) < 0) { + SDL_AtomicUnlock(&g_FdTableLock); + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "Failed to drop master on Qt DRM FD: %d", + errno); + // Hope for the best + return fd; + } + + // Close fd that we opened earlier (skipping our close() hook) + ((typeof(close)*)dlsym(RTLD_NEXT, "close"))(fd); + + // We are not allowed to call drmSetMaster() without CAP_SYS_ADMIN, + // but since we just dropped the master, we can become master by + // simply creating a new FD. Let's do it. + if (__OPEN_NEEDS_MODE(flags)) { + fd = ((typeof(open)*)dlsym(RTLD_NEXT, funcname))(pathname, flags, mode); + } + else { + fd = ((typeof(open)*)dlsym(RTLD_NEXT, funcname))(pathname, flags); + } } - g_SdlDrmMasterFd = fd; + + // Insert the FD into the table + g_SdlDrmMasterFds[freeFdIndex] = fd; + g_SdlDrmMasterFdCount++; + SDL_AtomicUnlock(&g_FdTableLock); } } }