Files
2026-10-13 21:27:42 -04:00

188 lines
4.6 KiB
C

#define _GNU_SOURCE
#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <unistd.h>
#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);
}