#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #define MAX_HANDLES 4096 struct gem_close { uint32_t handle; uint32_t pad; }; #define GEM_CLOSE _IOW('d', 0x09, struct gem_close) struct handle_entry { uint32_t handle; int dma_fd; int drm_fd; }; static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; static struct handle_entry handle_entries[MAX_HANDLES]; static uint32_t next_handle = 1; static dev_t kgsl_dev; static int kgsl_state; static int is_kgsl(int fd) { struct stat st; pthread_mutex_lock(&lock); if (kgsl_state == 0) { kgsl_state = stat("/dev/kgsl-3d0", &st) == 0 ? 1 : -1; kgsl_dev = st.st_rdev; } pthread_mutex_unlock(&lock); return kgsl_state > 0 && fstat(fd, &st) == 0 && S_ISCHR(st.st_mode) && st.st_rdev == kgsl_dev; } static void *real(const char *name) { void *fn = dlsym(RTLD_NEXT, name); if (!fn) { errno = ENOSYS; } return fn; } static int same_file_description(int a, int b) { if (a == b) { return 1; } #ifdef SYS_kcmp int saved_errno = errno; long ret = syscall(SYS_kcmp, getpid(), getpid(), 0 , a, b); errno = saved_errno; return ret == 0; #else return 0; #endif } static int find_handle_locked(int fd, uint32_t handle) { for (int i = 0; i < MAX_HANDLES; i++) { if (handle_entries[i].handle == handle && same_file_description(handle_entries[i].drm_fd, fd)) { return i; } } return -1; } static uint32_t alloc_handle_locked(void) { for (int tries = 0; tries <= MAX_HANDLES; tries++) { uint32_t candidate = next_handle++; if (next_handle == 0) { next_handle = 1; } if (candidate == 0) { continue; } int used = 0; for (int i = 0; i < MAX_HANDLES; i++) { if (handle_entries[i].handle == candidate) { used = 1; break; } } if (!used) { return candidate; } } return 0; } int drmPrimeFDToHandle(int fd, int prime_fd, uint32_t *handle) { if (!is_kgsl(fd)) { int (*fn)(int, int, uint32_t *) = (int (*)(int, int, uint32_t *)) real("drmPrimeFDToHandle"); return fn ? fn(fd, prime_fd, handle) : -ENOSYS; } int dup_fd = fcntl(prime_fd, F_DUPFD_CLOEXEC, 0); if (dup_fd < 0) { return -errno; } int ret = -ENOMEM; pthread_mutex_lock(&lock); for (int i = 0; i < MAX_HANDLES; i++) { if (handle_entries[i].handle == 0) { uint32_t token = alloc_handle_locked(); if (!token) { break; } handle_entries[i].handle = token; handle_entries[i].dma_fd = dup_fd; handle_entries[i].drm_fd = fd; *handle = token; ret = 0; break; } } pthread_mutex_unlock(&lock); if (ret) { close(dup_fd); } return ret; } int drmPrimeHandleToFD(int fd, uint32_t handle, uint32_t flags, int *prime_fd) { if (!is_kgsl(fd)) { int (*fn)(int, uint32_t, uint32_t, int *) = (int (*)(int, uint32_t, uint32_t, int *)) real("drmPrimeHandleToFD"); return fn ? fn(fd, handle, flags, prime_fd) : -ENOSYS; } int ret = -EINVAL; pthread_mutex_lock(&lock); int index = find_handle_locked(fd, handle); if (index >= 0) { *prime_fd = fcntl(handle_entries[index].dma_fd, F_DUPFD_CLOEXEC, 0); ret = *prime_fd < 0 ? -errno : 0; } pthread_mutex_unlock(&lock); return ret; } static int release_handle(int fd, uint32_t handle) { int found = 0; pthread_mutex_lock(&lock); int index = find_handle_locked(fd, handle); if (index >= 0) { close(handle_entries[index].dma_fd); handle_entries[index] = (struct handle_entry){0}; found = 1; } pthread_mutex_unlock(&lock); return found; } int drmCloseBufferHandle(int fd, uint32_t handle) { if (!is_kgsl(fd)) { int (*fn)(int, uint32_t) = (int (*)(int, uint32_t)) real("drmCloseBufferHandle"); return fn ? fn(fd, handle) : -ENOSYS; } return release_handle(fd, handle) ? 0 : -EINVAL; } __attribute__((visibility("hidden"))) int bl_drm_gem_close(int fd, unsigned long request, void *arg, int *rc) { if (request != GEM_CLOSE || !arg || !is_kgsl(fd)) { return 0; } if (release_handle(fd, ((struct gem_close *) arg)->handle)) { *rc = 0; } else { errno = EINVAL; *rc = -1; } return 1; } static void drm_lock_before_fork(void) { pthread_mutex_lock(&lock); } static void drm_unlock_after_fork(void) { pthread_mutex_unlock(&lock); } static void drm_reset_after_fork(void) { pthread_mutex_init(&lock, NULL); } __attribute__((constructor)) static void install_drm_fork_handlers(void) { pthread_atfork(drm_lock_before_fork, drm_unlock_after_fork, drm_reset_after_fork); }