mirror of
https://github.com/elwrcl/intel-hard-graphics.git
synced 2026-10-31 19:08:03 +03:00
init
This commit is contained in:
12511 files changed
+6156641
No files matched your search
@@ -0,0 +1,34 @@
|
||||
# DRM shim - Fake GEM kernel drivers in userspace for CI
|
||||
|
||||
On CI systems where we don't control the kernel, it would be nice to
|
||||
be able to present either no-op GEM devices (for shader-db runs) or
|
||||
simulator-backed GEM devices (for testing against a software simulator
|
||||
or FPGA). This lets us do that by intercepting libc calls and
|
||||
exposing render nodes.
|
||||
|
||||
## Limitations
|
||||
|
||||
- Doesn't know how to handle DRM fds getting passed over the wire from
|
||||
X11 (Could we use kmsro to support the X11 case?).
|
||||
- libc interception is rather glibc-specific and fragile.
|
||||
- Can easily break gdb if the libc interceptor code is what's broken.
|
||||
(ulimit -c unlimited and doing gdb on the core after the fact can
|
||||
help)
|
||||
|
||||
## Using
|
||||
|
||||
You choose the backend by setting `LD_PRELOAD` to the shim you want.
|
||||
Since this will effectively fake another DRM device to your system,
|
||||
you may need some work on your userspace to get your test application
|
||||
to use it if it's not the only DRM device present. Setting
|
||||
`DRM_SHIM_DEBUG=1` in the environment will print out what path the
|
||||
shim initialized on.
|
||||
|
||||
For piglit tests, you can set:
|
||||
|
||||
```
|
||||
PIGLIT_PLATFORM=gbm
|
||||
WAFFLE_GBM_DEVICE=<path from DRM_SHIM_DEBUG>
|
||||
```
|
||||
|
||||
See your drm-shim backend's README for details on how to use it.
|
||||
@@ -0,0 +1,457 @@
|
||||
/*
|
||||
* Copyright © 2018 Broadcom
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice (including the next
|
||||
* paragraph) shall be included in all copies or substantial portions of the
|
||||
* Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/** @file
|
||||
*
|
||||
* Implements core GEM support (particularly ioctls) underneath the libc ioctl
|
||||
* wrappers, and calls into the driver-specific code as necessary.
|
||||
*/
|
||||
|
||||
#include <c11/threads.h>
|
||||
#include <errno.h>
|
||||
#include <linux/memfd.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#include "drm-uapi/drm.h"
|
||||
#include "drm_shim.h"
|
||||
#include "util/hash_table.h"
|
||||
#include "util/u_atomic.h"
|
||||
|
||||
#define SHIM_MEM_SIZE (4ull * 1024 * 1024 * 1024)
|
||||
|
||||
#ifndef HAVE_MEMFD_CREATE
|
||||
#include <sys/syscall.h>
|
||||
|
||||
static inline int
|
||||
memfd_create(const char *name, unsigned int flags)
|
||||
{
|
||||
return syscall(SYS_memfd_create, name, flags);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Global state for the shim shared between libc, core, and driver. */
|
||||
struct shim_device shim_device;
|
||||
|
||||
long shim_page_size;
|
||||
|
||||
static uint32_t
|
||||
uint_key_hash(const void *key)
|
||||
{
|
||||
return (uintptr_t)key;
|
||||
}
|
||||
|
||||
static bool
|
||||
uint_key_compare(const void *a, const void *b)
|
||||
{
|
||||
return a == b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the first libc shim is called, to initialize GEM simulation
|
||||
* state (other than the shims themselves).
|
||||
*/
|
||||
void
|
||||
drm_shim_device_init(void)
|
||||
{
|
||||
shim_device.fd_map = _mesa_hash_table_create(NULL,
|
||||
uint_key_hash,
|
||||
uint_key_compare);
|
||||
|
||||
shim_device.offset_map = _mesa_hash_table_u64_create(NULL);
|
||||
|
||||
mtx_init(&shim_device.mem_lock, mtx_plain);
|
||||
|
||||
shim_device.mem_fd = memfd_create("shim mem", MFD_CLOEXEC);
|
||||
assert(shim_device.mem_fd != -1);
|
||||
|
||||
ASSERTED int ret = ftruncate(shim_device.mem_fd, SHIM_MEM_SIZE);
|
||||
assert(ret == 0);
|
||||
|
||||
/* The man page for mmap() says
|
||||
*
|
||||
* offset must be a multiple of the page size as returned by
|
||||
* sysconf(_SC_PAGE_SIZE).
|
||||
*
|
||||
* Depending on the configuration of the kernel, this may not be 4096. Get
|
||||
* this page size once and use it as the page size throughout, ensuring that
|
||||
* are offsets are page-size aligned as required. Otherwise, mmap will fail
|
||||
* with EINVAL.
|
||||
*/
|
||||
|
||||
shim_page_size = sysconf(_SC_PAGESIZE);
|
||||
|
||||
util_vma_heap_init(&shim_device.mem_heap, shim_page_size,
|
||||
SHIM_MEM_SIZE - shim_page_size);
|
||||
|
||||
drm_shim_driver_init();
|
||||
}
|
||||
|
||||
static struct shim_fd *
|
||||
drm_shim_file_create(int fd)
|
||||
{
|
||||
struct shim_fd *shim_fd = calloc(1, sizeof(*shim_fd));
|
||||
|
||||
shim_fd->fd = fd;
|
||||
p_atomic_set(&shim_fd->refcount, 1);
|
||||
mtx_init(&shim_fd->handle_lock, mtx_plain);
|
||||
shim_fd->handles = _mesa_hash_table_create(NULL,
|
||||
uint_key_hash,
|
||||
uint_key_compare);
|
||||
|
||||
return shim_fd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the libc shims have interposed an open or dup of our simulated
|
||||
* DRM device.
|
||||
*/
|
||||
void drm_shim_fd_register(int fd, struct shim_fd *shim_fd)
|
||||
{
|
||||
if (!shim_fd)
|
||||
shim_fd = drm_shim_file_create(fd);
|
||||
else
|
||||
p_atomic_inc(&shim_fd->refcount);
|
||||
|
||||
_mesa_hash_table_insert(shim_device.fd_map, (void *)(uintptr_t)(fd + 1), shim_fd);
|
||||
}
|
||||
|
||||
static void handle_delete_fxn(struct hash_entry *entry)
|
||||
{
|
||||
drm_shim_bo_put(entry->data);
|
||||
}
|
||||
|
||||
void drm_shim_fd_unregister(int fd)
|
||||
{
|
||||
if (fd == -1)
|
||||
return;
|
||||
|
||||
struct hash_entry *entry =
|
||||
_mesa_hash_table_search(shim_device.fd_map, (void *)(uintptr_t)(fd + 1));
|
||||
if (!entry)
|
||||
return;
|
||||
struct shim_fd *shim_fd = entry->data;
|
||||
_mesa_hash_table_remove(shim_device.fd_map, entry);
|
||||
|
||||
if (!p_atomic_dec_zero(&shim_fd->refcount))
|
||||
return;
|
||||
|
||||
_mesa_hash_table_destroy(shim_fd->handles, handle_delete_fxn);
|
||||
free(shim_fd);
|
||||
}
|
||||
|
||||
struct shim_fd *
|
||||
drm_shim_fd_lookup(int fd)
|
||||
{
|
||||
if (fd == -1)
|
||||
return NULL;
|
||||
|
||||
struct hash_entry *entry =
|
||||
_mesa_hash_table_search(shim_device.fd_map, (void *)(uintptr_t)(fd + 1));
|
||||
|
||||
if (!entry)
|
||||
return NULL;
|
||||
return entry->data;
|
||||
}
|
||||
|
||||
/* ioctl used by drmGetVersion() */
|
||||
static int
|
||||
drm_shim_ioctl_version(int fd, unsigned long request, void *arg)
|
||||
{
|
||||
struct drm_version *args = arg;
|
||||
const char *date = "20190320";
|
||||
const char *desc = "shim";
|
||||
|
||||
args->version_major = shim_device.version_major;
|
||||
args->version_minor = shim_device.version_minor;
|
||||
args->version_patchlevel = shim_device.version_patchlevel;
|
||||
|
||||
if (args->name)
|
||||
strncpy(args->name, shim_device.driver_name, args->name_len);
|
||||
if (args->date)
|
||||
strncpy(args->date, date, args->date_len);
|
||||
if (args->desc)
|
||||
strncpy(args->desc, desc, args->desc_len);
|
||||
args->name_len = strlen(shim_device.driver_name);
|
||||
args->date_len = strlen(date);
|
||||
args->desc_len = strlen(desc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
drm_shim_ioctl_get_unique(int fd, unsigned long request, void *arg)
|
||||
{
|
||||
struct drm_unique *gu = arg;
|
||||
|
||||
if (gu->unique && shim_device.unique)
|
||||
strncpy(gu->unique, shim_device.unique, gu->unique_len);
|
||||
gu->unique_len = shim_device.unique ? strlen(shim_device.unique) : 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
drm_shim_ioctl_get_cap(int fd, unsigned long request, void *arg)
|
||||
{
|
||||
struct drm_get_cap *gc = arg;
|
||||
|
||||
switch (gc->capability) {
|
||||
case DRM_CAP_PRIME:
|
||||
case DRM_CAP_SYNCOBJ:
|
||||
case DRM_CAP_SYNCOBJ_TIMELINE:
|
||||
case DRM_CAP_ADDFB2_MODIFIERS:
|
||||
gc->value = 1;
|
||||
return 0;
|
||||
|
||||
default:
|
||||
fprintf(stderr, "DRM_IOCTL_GET_CAP: unhandled 0x%x\n",
|
||||
(int)gc->capability);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
drm_shim_ioctl_gem_close(int fd, unsigned long request, void *arg)
|
||||
{
|
||||
struct shim_fd *shim_fd = drm_shim_fd_lookup(fd);
|
||||
struct drm_gem_close *c = arg;
|
||||
|
||||
if (!c->handle)
|
||||
return 0;
|
||||
|
||||
mtx_lock(&shim_fd->handle_lock);
|
||||
struct hash_entry *entry =
|
||||
_mesa_hash_table_search(shim_fd->handles, (void *)(uintptr_t)c->handle);
|
||||
if (!entry) {
|
||||
mtx_unlock(&shim_fd->handle_lock);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
struct shim_bo *bo = entry->data;
|
||||
_mesa_hash_table_remove(shim_fd->handles, entry);
|
||||
drm_shim_bo_put(bo);
|
||||
mtx_unlock(&shim_fd->handle_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
drm_shim_ioctl_syncobj_create(int fd, unsigned long request, void *arg)
|
||||
{
|
||||
struct drm_syncobj_create *create = arg;
|
||||
|
||||
create->handle = 1; /* 0 is invalid */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
drm_shim_ioctl_stub(int fd, unsigned long request, void *arg)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
ioctl_fn_t core_ioctls[] = {
|
||||
[_IOC_NR(DRM_IOCTL_VERSION)] = drm_shim_ioctl_version,
|
||||
[_IOC_NR(DRM_IOCTL_GET_UNIQUE)] = drm_shim_ioctl_get_unique,
|
||||
[_IOC_NR(DRM_IOCTL_GET_CAP)] = drm_shim_ioctl_get_cap,
|
||||
[_IOC_NR(DRM_IOCTL_GEM_CLOSE)] = drm_shim_ioctl_gem_close,
|
||||
[_IOC_NR(DRM_IOCTL_SYNCOBJ_CREATE)] = drm_shim_ioctl_syncobj_create,
|
||||
[_IOC_NR(DRM_IOCTL_SYNCOBJ_DESTROY)] = drm_shim_ioctl_stub,
|
||||
[_IOC_NR(DRM_IOCTL_SYNCOBJ_HANDLE_TO_FD)] = drm_shim_ioctl_stub,
|
||||
[_IOC_NR(DRM_IOCTL_SYNCOBJ_FD_TO_HANDLE)] = drm_shim_ioctl_stub,
|
||||
[_IOC_NR(DRM_IOCTL_SYNCOBJ_WAIT)] = drm_shim_ioctl_stub,
|
||||
[_IOC_NR(DRM_IOCTL_SYNCOBJ_TRANSFER)] = drm_shim_ioctl_stub,
|
||||
[_IOC_NR(DRM_IOCTL_SYNCOBJ_RESET)] = drm_shim_ioctl_stub,
|
||||
[_IOC_NR(DRM_IOCTL_SYNCOBJ_TIMELINE_SIGNAL)] = drm_shim_ioctl_stub,
|
||||
[_IOC_NR(DRM_IOCTL_SYNCOBJ_TIMELINE_WAIT)] = drm_shim_ioctl_stub,
|
||||
[_IOC_NR(DRM_IOCTL_SYNCOBJ_QUERY)] = drm_shim_ioctl_stub,
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements the GEM core ioctls, and calls into driver-specific ioctls.
|
||||
*/
|
||||
int
|
||||
drm_shim_ioctl(int fd, unsigned long request, void *arg)
|
||||
{
|
||||
ASSERTED int type = _IOC_TYPE(request);
|
||||
int nr = _IOC_NR(request);
|
||||
|
||||
assert(type == DRM_IOCTL_BASE);
|
||||
|
||||
if (nr >= DRM_COMMAND_BASE && nr < DRM_COMMAND_END) {
|
||||
int driver_nr = nr - DRM_COMMAND_BASE;
|
||||
|
||||
if (driver_nr < shim_device.driver_ioctl_count &&
|
||||
shim_device.driver_ioctls[driver_nr]) {
|
||||
return shim_device.driver_ioctls[driver_nr](fd, request, arg);
|
||||
}
|
||||
} else {
|
||||
if (nr < ARRAY_SIZE(core_ioctls) && core_ioctls[nr]) {
|
||||
return core_ioctls[nr](fd, request, arg);
|
||||
}
|
||||
}
|
||||
|
||||
if (nr >= DRM_COMMAND_BASE && nr < DRM_COMMAND_END) {
|
||||
fprintf(stderr,
|
||||
"DRM_SHIM: unhandled driver DRM ioctl %d (0x%08lx)\n",
|
||||
nr - DRM_COMMAND_BASE, request);
|
||||
} else {
|
||||
fprintf(stderr,
|
||||
"DRM_SHIM: unhandled core DRM ioctl 0x%X (0x%08lx)\n",
|
||||
nr, request);
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
int
|
||||
drm_shim_bo_init(struct shim_bo *bo, size_t size)
|
||||
{
|
||||
|
||||
mtx_lock(&shim_device.mem_lock);
|
||||
bo->mem_addr = util_vma_heap_alloc(&shim_device.mem_heap, size, shim_page_size);
|
||||
mtx_unlock(&shim_device.mem_lock);
|
||||
|
||||
if (!bo->mem_addr)
|
||||
return -ENOMEM;
|
||||
|
||||
bo->size = size;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct shim_bo *
|
||||
drm_shim_bo_lookup(struct shim_fd *shim_fd, int handle)
|
||||
{
|
||||
if (!handle)
|
||||
return NULL;
|
||||
|
||||
mtx_lock(&shim_fd->handle_lock);
|
||||
struct hash_entry *entry =
|
||||
_mesa_hash_table_search(shim_fd->handles, (void *)(uintptr_t)handle);
|
||||
struct shim_bo *bo = entry ? entry->data : NULL;
|
||||
mtx_unlock(&shim_fd->handle_lock);
|
||||
|
||||
if (bo)
|
||||
p_atomic_inc(&bo->refcount);
|
||||
|
||||
return bo;
|
||||
}
|
||||
|
||||
void
|
||||
drm_shim_bo_get(struct shim_bo *bo)
|
||||
{
|
||||
p_atomic_inc(&bo->refcount);
|
||||
}
|
||||
|
||||
void
|
||||
drm_shim_bo_put(struct shim_bo *bo)
|
||||
{
|
||||
if (p_atomic_dec_return(&bo->refcount) == 0)
|
||||
return;
|
||||
|
||||
if (shim_device.driver_bo_free)
|
||||
shim_device.driver_bo_free(bo);
|
||||
|
||||
mtx_lock(&shim_device.mem_lock);
|
||||
util_vma_heap_free(&shim_device.mem_heap, bo->mem_addr, bo->size);
|
||||
mtx_unlock(&shim_device.mem_lock);
|
||||
free(bo);
|
||||
}
|
||||
|
||||
int
|
||||
drm_shim_bo_get_handle(struct shim_fd *shim_fd, struct shim_bo *bo)
|
||||
{
|
||||
/* We should probably have some real datastructure for finding the free
|
||||
* number.
|
||||
*/
|
||||
mtx_lock(&shim_fd->handle_lock);
|
||||
for (int new_handle = 1; ; new_handle++) {
|
||||
void *key = (void *)(uintptr_t)new_handle;
|
||||
if (!_mesa_hash_table_search(shim_fd->handles, key)) {
|
||||
drm_shim_bo_get(bo);
|
||||
_mesa_hash_table_insert(shim_fd->handles, key, bo);
|
||||
mtx_unlock(&shim_fd->handle_lock);
|
||||
return new_handle;
|
||||
}
|
||||
}
|
||||
mtx_unlock(&shim_fd->handle_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Creates an mmap offset for the BO in the DRM fd.
|
||||
*/
|
||||
uint64_t
|
||||
drm_shim_bo_get_mmap_offset(struct shim_fd *shim_fd, struct shim_bo *bo)
|
||||
{
|
||||
mtx_lock(&shim_device.mem_lock);
|
||||
_mesa_hash_table_u64_insert(shim_device.offset_map, bo->mem_addr, bo);
|
||||
mtx_unlock(&shim_device.mem_lock);
|
||||
|
||||
/* reuse the buffer address as the mmap offset: */
|
||||
return bo->mem_addr;
|
||||
}
|
||||
|
||||
void
|
||||
drm_shim_init_iomem_region(off64_t offset, size_t size,
|
||||
void *(*mmap_handler)(size_t, int, int, off64_t))
|
||||
{
|
||||
shim_device.iomem_region.mmap = mmap_handler;
|
||||
shim_device.iomem_region.start = offset;
|
||||
shim_device.iomem_region.size = size;
|
||||
}
|
||||
|
||||
/* For mmap() on the DRM fd, look up the BO from the "offset" and map the BO's
|
||||
* fd.
|
||||
*/
|
||||
void *
|
||||
drm_shim_mmap(struct shim_fd *shim_fd, size_t length, int prot, int flags,
|
||||
int fd, off64_t offset)
|
||||
{
|
||||
if (shim_device.iomem_region.mmap &&
|
||||
offset >= shim_device.iomem_region.start &&
|
||||
offset + length <= shim_device.iomem_region.start + shim_device.iomem_region.size) {
|
||||
return shim_device.iomem_region.mmap(length, prot, flags, offset);
|
||||
}
|
||||
|
||||
mtx_lock(&shim_device.mem_lock);
|
||||
struct shim_bo *bo = _mesa_hash_table_u64_search(shim_device.offset_map, offset);
|
||||
mtx_unlock(&shim_device.mem_lock);
|
||||
|
||||
if (!bo)
|
||||
return MAP_FAILED;
|
||||
|
||||
if (length > bo->size)
|
||||
return MAP_FAILED;
|
||||
|
||||
/* The offset we pass to mmap must be aligned to the page size */
|
||||
assert((bo->mem_addr & (shim_page_size - 1)) == 0);
|
||||
|
||||
return mmap(NULL, length, prot, flags, shim_device.mem_fd, bo->mem_addr);
|
||||
}
|
||||
@@ -0,0 +1,866 @@
|
||||
/*
|
||||
* Copyright © 2018 Broadcom
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice (including the next
|
||||
* paragraph) shall be included in all copies or substantial portions of the
|
||||
* Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
*
|
||||
* Implements wrappers of libc functions to fake having a DRM device that
|
||||
* isn't actually present in the kernel.
|
||||
*/
|
||||
|
||||
/* Prevent glibc from defining open64 when we want to alias it. */
|
||||
#undef _FILE_OFFSET_BITS
|
||||
#undef _TIME_BITS
|
||||
#define _LARGEFILE64_SOURCE
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/sysmacros.h>
|
||||
#include <stdarg.h>
|
||||
#include <fcntl.h>
|
||||
#include <dlfcn.h>
|
||||
#include <dirent.h>
|
||||
#include <c11/threads.h>
|
||||
#include <drm-uapi/drm.h>
|
||||
|
||||
#include "util/anon_file.h"
|
||||
#include "util/set.h"
|
||||
#include "util/simple_mtx.h"
|
||||
#include "util/u_debug.h"
|
||||
#include "drm_shim.h"
|
||||
|
||||
#define REAL_FUNCTION_POINTER(x) __typeof__(x) *real_##x
|
||||
|
||||
static simple_mtx_t shim_lock = SIMPLE_MTX_INITIALIZER;
|
||||
struct set *opendir_set;
|
||||
bool drm_shim_debug;
|
||||
|
||||
/* If /dev/dri doesn't exist, we'll need an arbitrary pointer that wouldn't be
|
||||
* returned by any other opendir() call so we can return just our fake node.
|
||||
*/
|
||||
DIR *fake_dev_dri = (void *)&opendir_set;
|
||||
|
||||
REAL_FUNCTION_POINTER(access);
|
||||
REAL_FUNCTION_POINTER(close);
|
||||
REAL_FUNCTION_POINTER(closedir);
|
||||
REAL_FUNCTION_POINTER(dup);
|
||||
REAL_FUNCTION_POINTER(fcntl);
|
||||
REAL_FUNCTION_POINTER(fopen);
|
||||
REAL_FUNCTION_POINTER(ioctl);
|
||||
REAL_FUNCTION_POINTER(mmap);
|
||||
REAL_FUNCTION_POINTER(mmap64);
|
||||
REAL_FUNCTION_POINTER(open);
|
||||
REAL_FUNCTION_POINTER(opendir);
|
||||
REAL_FUNCTION_POINTER(readdir);
|
||||
REAL_FUNCTION_POINTER(readdir64);
|
||||
REAL_FUNCTION_POINTER(readlink);
|
||||
REAL_FUNCTION_POINTER(realpath);
|
||||
|
||||
#define HAS_XSTAT __GLIBC__ == 2 && __GLIBC_MINOR__ < 33
|
||||
|
||||
#if HAS_XSTAT
|
||||
REAL_FUNCTION_POINTER(__xstat);
|
||||
REAL_FUNCTION_POINTER(__xstat64);
|
||||
REAL_FUNCTION_POINTER(__fxstat);
|
||||
REAL_FUNCTION_POINTER(__fxstat64);
|
||||
#else
|
||||
REAL_FUNCTION_POINTER(stat);
|
||||
REAL_FUNCTION_POINTER(stat64);
|
||||
REAL_FUNCTION_POINTER(fstat);
|
||||
REAL_FUNCTION_POINTER(fstat64);
|
||||
#endif
|
||||
|
||||
static char render_node_dir[] = "/dev/dri/";
|
||||
/* Full path of /dev/dri/renderD* */
|
||||
static char *render_node_path;
|
||||
/* renderD* */
|
||||
static char *render_node_dirent_name;
|
||||
/* /sys/dev/char/major: */
|
||||
static int drm_device_path_len;
|
||||
static char *drm_device_path;
|
||||
/* /sys/dev/char/major:minor/device */
|
||||
static int device_path_len;
|
||||
static char *device_path;
|
||||
/* /sys/dev/char/major:minor/device/subsystem */
|
||||
static char *subsystem_path;
|
||||
int render_node_minor = -1;
|
||||
|
||||
struct file_override {
|
||||
const char *path;
|
||||
char *contents;
|
||||
};
|
||||
static struct file_override file_overrides[10];
|
||||
static int file_overrides_count;
|
||||
extern bool drm_shim_driver_prefers_first_render_node;
|
||||
|
||||
static int
|
||||
nfvasprintf(char **restrict strp, const char *restrict fmt, va_list ap)
|
||||
{
|
||||
int ret = vasprintf(strp, fmt, ap);
|
||||
assert(ret >= 0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
nfasprintf(char **restrict strp, const char *restrict fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
int ret = nfvasprintf(strp, fmt, ap);
|
||||
va_end(ap);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Pick the minor and filename for our shimmed render node. This can be
|
||||
* either a new one that didn't exist on the system, or if the driver wants,
|
||||
* it can replace the first render node.
|
||||
*/
|
||||
static void
|
||||
get_dri_render_node_minor(void)
|
||||
{
|
||||
for (int i = 0; i < 10; i++) {
|
||||
UNUSED int minor = 128 + i;
|
||||
nfasprintf(&render_node_dirent_name, "renderD%d", minor);
|
||||
nfasprintf(&render_node_path, "/dev/dri/%s",
|
||||
render_node_dirent_name);
|
||||
struct stat st;
|
||||
if (drm_shim_driver_prefers_first_render_node ||
|
||||
stat(render_node_path, &st) == -1) {
|
||||
|
||||
render_node_minor = minor;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stderr, "Couldn't find a spare render node slot\n");
|
||||
}
|
||||
|
||||
static void *get_function_pointer(const char *name)
|
||||
{
|
||||
void *func = dlsym(RTLD_NEXT, name);
|
||||
if (!func) {
|
||||
fprintf(stderr, "Failed to resolve %s\n", name);
|
||||
abort();
|
||||
}
|
||||
return func;
|
||||
}
|
||||
|
||||
#define GET_FUNCTION_POINTER(x) real_##x = get_function_pointer(#x)
|
||||
|
||||
void
|
||||
drm_shim_override_file(const char *contents, const char *path_format, ...)
|
||||
{
|
||||
assert(file_overrides_count < ARRAY_SIZE(file_overrides));
|
||||
|
||||
char *path;
|
||||
va_list ap;
|
||||
va_start(ap, path_format);
|
||||
nfvasprintf(&path, path_format, ap);
|
||||
va_end(ap);
|
||||
|
||||
struct file_override *override = &file_overrides[file_overrides_count++];
|
||||
override->path = path;
|
||||
override->contents = strdup(contents);
|
||||
}
|
||||
|
||||
static uint32_t inited = 0;
|
||||
|
||||
static void
|
||||
destroy_shim(void)
|
||||
{
|
||||
_mesa_set_destroy(opendir_set, NULL);
|
||||
free(render_node_path);
|
||||
free(render_node_dirent_name);
|
||||
free(subsystem_path);
|
||||
|
||||
render_node_minor = -1;
|
||||
file_overrides_count = 0;
|
||||
p_atomic_set(&inited, 0);
|
||||
}
|
||||
|
||||
/* Initialization, which will be called from the first general library call
|
||||
* that might need to be wrapped with the shim.
|
||||
*/
|
||||
static void
|
||||
init_shim(void)
|
||||
{
|
||||
drm_shim_debug = debug_get_bool_option("DRM_SHIM_DEBUG", false);
|
||||
|
||||
/* We can't lock this, because we recurse during initialization. */
|
||||
if (p_atomic_cmpxchg(&inited, 0, 1))
|
||||
return;
|
||||
|
||||
opendir_set = _mesa_set_create(NULL,
|
||||
_mesa_hash_string,
|
||||
_mesa_key_string_equal);
|
||||
|
||||
GET_FUNCTION_POINTER(access);
|
||||
GET_FUNCTION_POINTER(close);
|
||||
GET_FUNCTION_POINTER(closedir);
|
||||
GET_FUNCTION_POINTER(dup);
|
||||
GET_FUNCTION_POINTER(fcntl);
|
||||
GET_FUNCTION_POINTER(fopen);
|
||||
GET_FUNCTION_POINTER(ioctl);
|
||||
GET_FUNCTION_POINTER(mmap);
|
||||
GET_FUNCTION_POINTER(mmap64);
|
||||
GET_FUNCTION_POINTER(open);
|
||||
GET_FUNCTION_POINTER(opendir);
|
||||
GET_FUNCTION_POINTER(readdir);
|
||||
GET_FUNCTION_POINTER(readdir64);
|
||||
GET_FUNCTION_POINTER(readlink);
|
||||
GET_FUNCTION_POINTER(realpath);
|
||||
|
||||
#if HAS_XSTAT
|
||||
GET_FUNCTION_POINTER(__xstat);
|
||||
GET_FUNCTION_POINTER(__xstat64);
|
||||
GET_FUNCTION_POINTER(__fxstat);
|
||||
GET_FUNCTION_POINTER(__fxstat64);
|
||||
#else
|
||||
GET_FUNCTION_POINTER(stat);
|
||||
GET_FUNCTION_POINTER(stat64);
|
||||
GET_FUNCTION_POINTER(fstat);
|
||||
GET_FUNCTION_POINTER(fstat64);
|
||||
#endif
|
||||
|
||||
get_dri_render_node_minor();
|
||||
|
||||
if (drm_shim_debug) {
|
||||
fprintf(stderr, "Initializing DRM shim on %s\n",
|
||||
render_node_path);
|
||||
}
|
||||
|
||||
drm_device_path_len =
|
||||
nfasprintf(&drm_device_path, "/sys/dev/char/%d:", DRM_MAJOR);
|
||||
|
||||
device_path_len =
|
||||
nfasprintf(&device_path,
|
||||
"/sys/dev/char/%d:%d/device",
|
||||
DRM_MAJOR, render_node_minor);
|
||||
|
||||
nfasprintf(&subsystem_path,
|
||||
"/sys/dev/char/%d:%d/device/subsystem",
|
||||
DRM_MAJOR, render_node_minor);
|
||||
|
||||
drm_shim_device_init();
|
||||
|
||||
atexit(destroy_shim);
|
||||
}
|
||||
|
||||
static bool hide_drm_device_path(const char *path)
|
||||
{
|
||||
if (render_node_minor == -1)
|
||||
return false;
|
||||
|
||||
/* If the path looks like our fake render node device, then don't hide it.
|
||||
*/
|
||||
if (strncmp(path, device_path, device_path_len) == 0 ||
|
||||
strcmp(path, render_node_path) == 0)
|
||||
return false;
|
||||
|
||||
/* String starts with /sys/dev/char/226: but is not the fake render node.
|
||||
* We want to hide all other drm devices for the shim.
|
||||
*/
|
||||
if (strncmp(path, drm_device_path, drm_device_path_len) == 0)
|
||||
return true;
|
||||
|
||||
/* String starts with /dev/dri/ but is not the fake render node. We want to
|
||||
* hide all other drm devices for the shim.
|
||||
*/
|
||||
if (strncmp(path, render_node_dir, sizeof(render_node_dir) - 1) == 0)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int file_override_open(const char *path)
|
||||
{
|
||||
for (int i = 0; i < file_overrides_count; i++) {
|
||||
if (strcmp(file_overrides[i].path, path) == 0) {
|
||||
int fd = os_create_anonymous_file(0, "shim file");
|
||||
write(fd, file_overrides[i].contents,
|
||||
strlen(file_overrides[i].contents));
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
return fd;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Override libdrm's reading of various sysfs files for device enumeration. */
|
||||
PUBLIC FILE *fopen(const char *path, const char *mode)
|
||||
{
|
||||
init_shim();
|
||||
|
||||
int fd = file_override_open(path);
|
||||
if (fd >= 0)
|
||||
return fdopen(fd, "r");
|
||||
|
||||
return real_fopen(path, mode);
|
||||
}
|
||||
PUBLIC FILE *fopen64(const char *path, const char *mode)
|
||||
__attribute__((alias("fopen")));
|
||||
|
||||
/* Intercepts access(render_node_path) to trick drmGetMinorType */
|
||||
PUBLIC int access(const char *path, int mode)
|
||||
{
|
||||
init_shim();
|
||||
|
||||
if (hide_drm_device_path(path)) {
|
||||
errno = ENOENT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (strcmp(path, render_node_path) != 0)
|
||||
return real_access(path, mode);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Intercepts open(render_node_path) to redirect it to the simulator. */
|
||||
PUBLIC int open(const char *path, int flags, ...)
|
||||
{
|
||||
init_shim();
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, flags);
|
||||
mode_t mode = va_arg(ap, mode_t);
|
||||
va_end(ap);
|
||||
|
||||
int fd = file_override_open(path);
|
||||
if (fd >= 0)
|
||||
return fd;
|
||||
|
||||
if (hide_drm_device_path(path)) {
|
||||
errno = ENOENT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (strcmp(path, render_node_path) != 0)
|
||||
return real_open(path, flags, mode);
|
||||
|
||||
fd = real_open("/dev/null", O_RDWR, 0);
|
||||
|
||||
drm_shim_fd_register(fd, NULL);
|
||||
|
||||
return fd;
|
||||
}
|
||||
PUBLIC int open64(const char*, int, ...) __attribute__((alias("open")));
|
||||
|
||||
/* __open64_2 isn't declared unless _FORTIFY_SOURCE is defined. */
|
||||
PUBLIC int __open64_2(const char *path, int flags);
|
||||
PUBLIC int __open64_2(const char *path, int flags)
|
||||
{
|
||||
return open(path, flags, 0);
|
||||
}
|
||||
|
||||
PUBLIC int close(int fd)
|
||||
{
|
||||
init_shim();
|
||||
|
||||
drm_shim_fd_unregister(fd);
|
||||
|
||||
return real_close(fd);
|
||||
}
|
||||
|
||||
#if HAS_XSTAT
|
||||
/* Fakes stat to return character device stuff for our fake render node. */
|
||||
PUBLIC int __xstat(int ver, const char *path, struct stat *st)
|
||||
{
|
||||
init_shim();
|
||||
|
||||
/* Note: call real stat if we're in the process of probing for a free
|
||||
* render node!
|
||||
*/
|
||||
if (render_node_minor == -1)
|
||||
return real___xstat(ver, path, st);
|
||||
|
||||
if (hide_drm_device_path(path)) {
|
||||
errno = ENOENT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Fool libdrm's probe of whether the /sys dir for this char dev is
|
||||
* there.
|
||||
*/
|
||||
char *sys_dev_drm_dir;
|
||||
nfasprintf(&sys_dev_drm_dir,
|
||||
"/sys/dev/char/%d:%d/device/drm",
|
||||
DRM_MAJOR, render_node_minor);
|
||||
if (strcmp(path, sys_dev_drm_dir) == 0) {
|
||||
free(sys_dev_drm_dir);
|
||||
return 0;
|
||||
}
|
||||
free(sys_dev_drm_dir);
|
||||
|
||||
if (strcmp(path, render_node_path) != 0)
|
||||
return real___xstat(ver, path, st);
|
||||
|
||||
memset(st, 0, sizeof(*st));
|
||||
st->st_rdev = makedev(DRM_MAJOR, render_node_minor);
|
||||
st->st_mode = S_IFCHR;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Fakes stat to return character device stuff for our fake render node. */
|
||||
PUBLIC int __xstat64(int ver, const char *path, struct stat64 *st)
|
||||
{
|
||||
init_shim();
|
||||
|
||||
/* Note: call real stat if we're in the process of probing for a free
|
||||
* render node!
|
||||
*/
|
||||
if (render_node_minor == -1)
|
||||
return real___xstat64(ver, path, st);
|
||||
|
||||
if (hide_drm_device_path(path)) {
|
||||
errno = ENOENT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Fool libdrm's probe of whether the /sys dir for this char dev is
|
||||
* there.
|
||||
*/
|
||||
char *sys_dev_drm_dir;
|
||||
nfasprintf(&sys_dev_drm_dir,
|
||||
"/sys/dev/char/%d:%d/device/drm",
|
||||
DRM_MAJOR, render_node_minor);
|
||||
if (strcmp(path, sys_dev_drm_dir) == 0) {
|
||||
free(sys_dev_drm_dir);
|
||||
return 0;
|
||||
}
|
||||
free(sys_dev_drm_dir);
|
||||
|
||||
if (strcmp(path, render_node_path) != 0)
|
||||
return real___xstat64(ver, path, st);
|
||||
|
||||
memset(st, 0, sizeof(*st));
|
||||
st->st_rdev = makedev(DRM_MAJOR, render_node_minor);
|
||||
st->st_mode = S_IFCHR;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Fakes fstat to return character device stuff for our fake render node. */
|
||||
PUBLIC int __fxstat(int ver, int fd, struct stat *st)
|
||||
{
|
||||
init_shim();
|
||||
|
||||
struct shim_fd *shim_fd = drm_shim_fd_lookup(fd);
|
||||
|
||||
if (!shim_fd)
|
||||
return real___fxstat(ver, fd, st);
|
||||
|
||||
memset(st, 0, sizeof(*st));
|
||||
st->st_rdev = makedev(DRM_MAJOR, render_node_minor);
|
||||
st->st_mode = S_IFCHR;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
PUBLIC int __fxstat64(int ver, int fd, struct stat64 *st)
|
||||
{
|
||||
init_shim();
|
||||
|
||||
struct shim_fd *shim_fd = drm_shim_fd_lookup(fd);
|
||||
|
||||
if (!shim_fd)
|
||||
return real___fxstat64(ver, fd, st);
|
||||
|
||||
memset(st, 0, sizeof(*st));
|
||||
st->st_rdev = makedev(DRM_MAJOR, render_node_minor);
|
||||
st->st_mode = S_IFCHR;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
PUBLIC int stat(const char* path, struct stat* stat_buf)
|
||||
{
|
||||
init_shim();
|
||||
|
||||
/* Note: call real stat if we're in the process of probing for a free
|
||||
* render node!
|
||||
*/
|
||||
if (render_node_minor == -1)
|
||||
return real_stat(path, stat_buf);
|
||||
|
||||
if (hide_drm_device_path(path)) {
|
||||
errno = ENOENT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Fool libdrm's probe of whether the /sys dir for this char dev is
|
||||
* there.
|
||||
*/
|
||||
char *sys_dev_drm_dir;
|
||||
nfasprintf(&sys_dev_drm_dir,
|
||||
"/sys/dev/char/%d:%d/device/drm",
|
||||
DRM_MAJOR, render_node_minor);
|
||||
if (strcmp(path, sys_dev_drm_dir) == 0) {
|
||||
free(sys_dev_drm_dir);
|
||||
return 0;
|
||||
}
|
||||
free(sys_dev_drm_dir);
|
||||
|
||||
if (strcmp(path, render_node_path) != 0)
|
||||
return real_stat(path, stat_buf);
|
||||
|
||||
memset(stat_buf, 0, sizeof(*stat_buf));
|
||||
stat_buf->st_rdev = makedev(DRM_MAJOR, render_node_minor);
|
||||
stat_buf->st_mode = S_IFCHR;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
PUBLIC int stat64(const char* path, struct stat64* stat_buf)
|
||||
{
|
||||
init_shim();
|
||||
|
||||
/* Note: call real stat if we're in the process of probing for a free
|
||||
* render node!
|
||||
*/
|
||||
if (render_node_minor == -1)
|
||||
return real_stat64(path, stat_buf);
|
||||
|
||||
if (hide_drm_device_path(path)) {
|
||||
errno = ENOENT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Fool libdrm's probe of whether the /sys dir for this char dev is
|
||||
* there.
|
||||
*/
|
||||
char *sys_dev_drm_dir;
|
||||
nfasprintf(&sys_dev_drm_dir,
|
||||
"/sys/dev/char/%d:%d/device/drm",
|
||||
DRM_MAJOR, render_node_minor);
|
||||
if (strcmp(path, sys_dev_drm_dir) == 0) {
|
||||
free(sys_dev_drm_dir);
|
||||
return 0;
|
||||
}
|
||||
free(sys_dev_drm_dir);
|
||||
|
||||
if (strcmp(path, render_node_path) != 0)
|
||||
return real_stat64(path, stat_buf);
|
||||
|
||||
memset(stat_buf, 0, sizeof(*stat_buf));
|
||||
stat_buf->st_rdev = makedev(DRM_MAJOR, render_node_minor);
|
||||
stat_buf->st_mode = S_IFCHR;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
PUBLIC int fstat(int fd, struct stat* stat_buf)
|
||||
{
|
||||
init_shim();
|
||||
|
||||
struct shim_fd *shim_fd = drm_shim_fd_lookup(fd);
|
||||
|
||||
if (!shim_fd)
|
||||
return real_fstat(fd, stat_buf);
|
||||
|
||||
memset(stat_buf, 0, sizeof(*stat_buf));
|
||||
stat_buf->st_rdev = makedev(DRM_MAJOR, render_node_minor);
|
||||
stat_buf->st_mode = S_IFCHR;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
PUBLIC int fstat64(int fd, struct stat64* stat_buf)
|
||||
{
|
||||
init_shim();
|
||||
|
||||
struct shim_fd *shim_fd = drm_shim_fd_lookup(fd);
|
||||
|
||||
if (!shim_fd)
|
||||
return real_fstat64(fd, stat_buf);
|
||||
|
||||
memset(stat_buf, 0, sizeof(*stat_buf));
|
||||
stat_buf->st_rdev = makedev(DRM_MAJOR, render_node_minor);
|
||||
stat_buf->st_mode = S_IFCHR;
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Tracks if the opendir was on /dev/dri. */
|
||||
PUBLIC DIR *
|
||||
opendir(const char *name)
|
||||
{
|
||||
init_shim();
|
||||
|
||||
DIR *dir = real_opendir(name);
|
||||
if (strcmp(name, "/dev/dri") == 0) {
|
||||
if (!dir) {
|
||||
/* If /dev/dri didn't exist, we still want to be able to return our
|
||||
* fake /dev/dri/render* even though we probably can't
|
||||
* mkdir("/dev/dri"). Return a fake DIR pointer for that.
|
||||
*/
|
||||
dir = fake_dev_dri;
|
||||
}
|
||||
|
||||
simple_mtx_lock(&shim_lock);
|
||||
_mesa_set_add(opendir_set, dir);
|
||||
simple_mtx_unlock(&shim_lock);
|
||||
}
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
/* If we're looking at /dev/dri, add our render node to the list
|
||||
* before the real entries in the directory.
|
||||
*/
|
||||
PUBLIC struct dirent *
|
||||
readdir(DIR *dir)
|
||||
{
|
||||
init_shim();
|
||||
|
||||
struct dirent *ent = NULL;
|
||||
|
||||
static struct dirent render_node_dirent = { 0 };
|
||||
|
||||
simple_mtx_lock(&shim_lock);
|
||||
if (_mesa_set_search(opendir_set, dir)) {
|
||||
strcpy(render_node_dirent.d_name,
|
||||
render_node_dirent_name);
|
||||
render_node_dirent.d_type = DT_CHR;
|
||||
ent = &render_node_dirent;
|
||||
_mesa_set_remove_key(opendir_set, dir);
|
||||
}
|
||||
simple_mtx_unlock(&shim_lock);
|
||||
|
||||
if (!ent && dir != fake_dev_dri)
|
||||
ent = real_readdir(dir);
|
||||
|
||||
return ent;
|
||||
}
|
||||
|
||||
/* If we're looking at /dev/dri, add our render node to the list
|
||||
* before the real entries in the directory.
|
||||
*/
|
||||
PUBLIC struct dirent64 *
|
||||
readdir64(DIR *dir)
|
||||
{
|
||||
init_shim();
|
||||
|
||||
struct dirent64 *ent = NULL;
|
||||
|
||||
static struct dirent64 render_node_dirent = { 0 };
|
||||
|
||||
simple_mtx_lock(&shim_lock);
|
||||
if (_mesa_set_search(opendir_set, dir)) {
|
||||
strcpy(render_node_dirent.d_name,
|
||||
render_node_dirent_name);
|
||||
render_node_dirent.d_type = DT_CHR;
|
||||
ent = &render_node_dirent;
|
||||
_mesa_set_remove_key(opendir_set, dir);
|
||||
}
|
||||
simple_mtx_unlock(&shim_lock);
|
||||
|
||||
if (!ent && dir != fake_dev_dri)
|
||||
ent = real_readdir64(dir);
|
||||
|
||||
return ent;
|
||||
}
|
||||
|
||||
/* Cleans up tracking of opendir("/dev/dri") */
|
||||
PUBLIC int
|
||||
closedir(DIR *dir)
|
||||
{
|
||||
init_shim();
|
||||
|
||||
simple_mtx_lock(&shim_lock);
|
||||
_mesa_set_remove_key(opendir_set, dir);
|
||||
simple_mtx_unlock(&shim_lock);
|
||||
|
||||
if (dir != fake_dev_dri)
|
||||
return real_closedir(dir);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Handles libdrm's readlink to figure out what kind of device we have. */
|
||||
PUBLIC ssize_t
|
||||
readlink(const char *path, char *buf, size_t size)
|
||||
{
|
||||
init_shim();
|
||||
|
||||
if (hide_drm_device_path(path)) {
|
||||
errno = ENOENT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (strcmp(path, subsystem_path) != 0)
|
||||
return real_readlink(path, buf, size);
|
||||
|
||||
static const struct {
|
||||
const char *name;
|
||||
int bus_type;
|
||||
} bus_types[] = {
|
||||
{ "/pci", DRM_BUS_PCI },
|
||||
{ "/usb", DRM_BUS_USB },
|
||||
{ "/platform", DRM_BUS_PLATFORM },
|
||||
{ "/spi", DRM_BUS_PLATFORM },
|
||||
{ "/host1x", DRM_BUS_HOST1X },
|
||||
};
|
||||
|
||||
for (uint32_t i = 0; i < ARRAY_SIZE(bus_types); i++) {
|
||||
if (bus_types[i].bus_type != shim_device.bus_type)
|
||||
continue;
|
||||
|
||||
strncpy(buf, bus_types[i].name, size);
|
||||
buf[size - 1] = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
return strlen(buf) + 1;
|
||||
}
|
||||
|
||||
#if __USE_FORTIFY_LEVEL > 0 && !defined _CLANG_FORTIFY_DISABLE
|
||||
/* Identical to readlink, but with buffer overflow check */
|
||||
PUBLIC ssize_t
|
||||
__readlink_chk(const char *path, char *buf, size_t size, size_t buflen)
|
||||
{
|
||||
if (size > buflen)
|
||||
abort();
|
||||
return readlink(path, buf, size);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Handles libdrm's realpath to figure out what kind of device we have. */
|
||||
PUBLIC char *
|
||||
realpath(const char *path, char *resolved_path)
|
||||
{
|
||||
init_shim();
|
||||
|
||||
if (strcmp(path, device_path) != 0)
|
||||
return real_realpath(path, resolved_path);
|
||||
|
||||
strcpy(resolved_path, path);
|
||||
|
||||
return resolved_path;
|
||||
}
|
||||
|
||||
/* Main entrypoint to DRM drivers: the ioctl syscall. We send all ioctls on
|
||||
* our DRM fd to drm_shim_ioctl().
|
||||
*/
|
||||
PUBLIC int
|
||||
ioctl(int fd, unsigned long request, ...)
|
||||
{
|
||||
init_shim();
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, request);
|
||||
void *arg = va_arg(ap, void *);
|
||||
va_end(ap);
|
||||
|
||||
struct shim_fd *shim_fd = drm_shim_fd_lookup(fd);
|
||||
if (!shim_fd)
|
||||
return real_ioctl(fd, request, arg);
|
||||
|
||||
return drm_shim_ioctl(fd, request, arg);
|
||||
}
|
||||
|
||||
/* Gallium uses this to dup the incoming fd on gbm screen creation */
|
||||
PUBLIC int
|
||||
fcntl(int fd, int cmd, ...)
|
||||
{
|
||||
init_shim();
|
||||
|
||||
struct shim_fd *shim_fd = drm_shim_fd_lookup(fd);
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, cmd);
|
||||
void *arg = va_arg(ap, void *);
|
||||
va_end(ap);
|
||||
|
||||
int ret = real_fcntl(fd, cmd, arg);
|
||||
|
||||
if (cmd == F_DUPFD || cmd == F_DUPFD_CLOEXEC) {
|
||||
if (shim_fd) {
|
||||
drm_shim_fd_register(ret, shim_fd);
|
||||
} else {
|
||||
/* x11_dri3_open / xcb_dri3_open_reply_fds will end up here. */
|
||||
drmVersionPtr ver = drmGetVersion(fd);
|
||||
if (ver) {
|
||||
drm_shim_fd_register(fd, NULL);
|
||||
drm_shim_fd_register(ret, drm_shim_fd_lookup(fd));
|
||||
drmFree(ver);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
PUBLIC int fcntl64(int, int, ...)
|
||||
__attribute__((alias("fcntl")));
|
||||
|
||||
/* I wrote this when trying to fix gallium screen creation, leaving it around
|
||||
* since it's probably good to have.
|
||||
*/
|
||||
PUBLIC int
|
||||
dup(int fd)
|
||||
{
|
||||
init_shim();
|
||||
|
||||
int ret = real_dup(fd);
|
||||
|
||||
struct shim_fd *shim_fd = drm_shim_fd_lookup(fd);
|
||||
if (shim_fd && ret >= 0)
|
||||
drm_shim_fd_register(ret, shim_fd);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
PUBLIC void *
|
||||
mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset)
|
||||
{
|
||||
init_shim();
|
||||
|
||||
struct shim_fd *shim_fd = drm_shim_fd_lookup(fd);
|
||||
if (shim_fd)
|
||||
return drm_shim_mmap(shim_fd, length, prot, flags, fd, offset);
|
||||
|
||||
return real_mmap(addr, length, prot, flags, fd, offset);
|
||||
}
|
||||
|
||||
PUBLIC void *
|
||||
mmap64(void* addr, size_t length, int prot, int flags, int fd, off64_t offset)
|
||||
{
|
||||
init_shim();
|
||||
|
||||
struct shim_fd *shim_fd = drm_shim_fd_lookup(fd);
|
||||
if (shim_fd)
|
||||
return drm_shim_mmap(shim_fd, length, prot, flags, fd, offset);
|
||||
|
||||
return real_mmap64(addr, length, prot, flags, fd, offset);
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright © 2018 Broadcom
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice (including the next
|
||||
* paragraph) shall be included in all copies or substantial portions of the
|
||||
* Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <c11/threads.h>
|
||||
|
||||
#include "util/macros.h"
|
||||
#include "util/hash_table.h"
|
||||
#include "util/vma.h"
|
||||
|
||||
#include <xf86drm.h>
|
||||
|
||||
#ifdef __linux__
|
||||
#define DRM_MAJOR 226
|
||||
#endif
|
||||
|
||||
typedef int (*ioctl_fn_t)(int fd, unsigned long request, void *arg);
|
||||
|
||||
struct shim_bo;
|
||||
|
||||
struct shim_device {
|
||||
/* Mapping from int fd to struct shim_fd *. */
|
||||
struct hash_table *fd_map;
|
||||
|
||||
/* Mapping from mmap offset to shim_bo */
|
||||
struct hash_table_u64 *offset_map;
|
||||
|
||||
/* IOMEM region */
|
||||
struct {
|
||||
off64_t start;
|
||||
size_t size;
|
||||
void *(*mmap)(size_t length, int prot, int flags, off64_t offset);
|
||||
} iomem_region;
|
||||
|
||||
mtx_t mem_lock;
|
||||
/* Heap from which shim_bo are allocated */
|
||||
struct util_vma_heap mem_heap;
|
||||
|
||||
int mem_fd;
|
||||
|
||||
int (**driver_ioctls)(int fd, unsigned long request, void *arg);
|
||||
int driver_ioctl_count;
|
||||
|
||||
void (*driver_bo_free)(struct shim_bo *bo);
|
||||
|
||||
/* Returned by drmGetVersion(). */
|
||||
const char *driver_name;
|
||||
|
||||
/* Returned by drmGetBusid(). */
|
||||
const char *unique;
|
||||
|
||||
int version_major, version_minor, version_patchlevel;
|
||||
int bus_type;
|
||||
};
|
||||
|
||||
extern struct shim_device shim_device;
|
||||
|
||||
struct shim_fd {
|
||||
int fd;
|
||||
int refcount;
|
||||
mtx_t handle_lock;
|
||||
/* mapping from int gem handle to struct shim_bo *. */
|
||||
struct hash_table *handles;
|
||||
};
|
||||
|
||||
struct shim_bo {
|
||||
uint64_t mem_addr;
|
||||
void *map;
|
||||
int refcount;
|
||||
uint32_t size;
|
||||
};
|
||||
|
||||
/* Core support. */
|
||||
extern int render_node_minor;
|
||||
void drm_shim_device_init(void);
|
||||
void drm_shim_override_file(const char *contents,
|
||||
const char *path_format, ...) PRINTFLIKE(2, 3);
|
||||
void drm_shim_fd_register(int fd, struct shim_fd *shim_fd);
|
||||
void drm_shim_fd_unregister(int fd);
|
||||
struct shim_fd *drm_shim_fd_lookup(int fd);
|
||||
int drm_shim_ioctl(int fd, unsigned long request, void *arg);
|
||||
void *drm_shim_mmap(struct shim_fd *shim_fd, size_t length, int prot, int flags,
|
||||
int fd, off64_t offset);
|
||||
|
||||
int drm_shim_bo_init(struct shim_bo *bo, size_t size);
|
||||
void drm_shim_bo_get(struct shim_bo *bo);
|
||||
void drm_shim_bo_put(struct shim_bo *bo);
|
||||
struct shim_bo *drm_shim_bo_lookup(struct shim_fd *shim_fd, int handle);
|
||||
int drm_shim_bo_get_handle(struct shim_fd *shim_fd, struct shim_bo *bo);
|
||||
uint64_t drm_shim_bo_get_mmap_offset(struct shim_fd *shim_fd,
|
||||
struct shim_bo *bo);
|
||||
void drm_shim_init_iomem_region(off64_t offset, size_t size,
|
||||
void *(*mmap_handler)(size_t, int, int, off64_t));
|
||||
|
||||
/* driver-specific hooks. */
|
||||
void drm_shim_driver_init(void);
|
||||
extern bool drm_shim_driver_prefers_new_render_node;
|
||||
@@ -0,0 +1,18 @@
|
||||
# Copyright © 2019 Broadcom
|
||||
# Copyright © 2020 Intel Corporation
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
drm_shim = static_library(
|
||||
'drm_shim',
|
||||
[
|
||||
'device.c',
|
||||
'drm_shim.c',
|
||||
],
|
||||
include_directories: [inc_include, inc_src],
|
||||
dependencies: [dep_libdrm, idep_mesautil, dep_dl],
|
||||
gnu_symbol_visibility : 'hidden',
|
||||
)
|
||||
dep_drm_shim = declare_dependency(
|
||||
link_with: drm_shim,
|
||||
dependencies: dep_libdrm,
|
||||
)
|
||||
Reference in new issue
Block a user