diff options
-rw-r--r-- | meson.build | 11 | ||||
-rw-r--r-- | meson_options.txt | 2 | ||||
-rw-r--r-- | src/broadcom/drm-shim/README.md | 17 | ||||
-rw-r--r-- | src/broadcom/drm-shim/meson.build | 62 | ||||
-rw-r--r-- | src/broadcom/drm-shim/v3d.c | 98 | ||||
-rw-r--r-- | src/broadcom/drm-shim/v3d.h | 70 | ||||
-rw-r--r-- | src/broadcom/drm-shim/v3d_noop.c | 158 | ||||
-rw-r--r-- | src/broadcom/drm-shim/v3dx.c | 370 | ||||
-rw-r--r-- | src/broadcom/meson.build | 4 | ||||
-rw-r--r-- | src/drm-shim/README.md | 34 | ||||
-rw-r--r-- | src/drm-shim/device.c | 339 | ||||
-rw-r--r-- | src/drm-shim/drm_shim.c | 532 | ||||
-rw-r--r-- | src/drm-shim/drm_shim.h | 83 | ||||
-rw-r--r-- | src/drm-shim/meson.build | 34 | ||||
-rw-r--r-- | src/meson.build | 3 |
15 files changed, 1815 insertions, 2 deletions
diff --git a/meson.build b/meson.build index 804b921b1a5..f4c013a5319 100644 --- a/meson.build +++ b/meson.build @@ -55,7 +55,16 @@ with_osmesa = get_option('osmesa') with_swr_arches = get_option('swr-arches') with_tools = get_option('tools') if with_tools.contains('all') - with_tools = ['etnaviv', 'freedreno', 'glsl', 'intel', 'nir', 'nouveau', 'xvmc'] + with_tools = [ + 'drm-shim', + 'etnaviv', + 'freedreno', + 'glsl', + 'intel', + 'nir', + 'nouveau', + 'xvmc', + ] endif dri_drivers_path = get_option('dri-drivers-path') diff --git a/meson_options.txt b/meson_options.txt index 5cbb85658a2..b768c15053c 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -310,7 +310,7 @@ option( 'tools', type : 'array', value : [], - choices : ['etnaviv', 'freedreno', 'glsl', 'intel', 'intel-ui', 'nir', 'nouveau', 'xvmc', 'lima', 'all'], + choices : ['drm-shim', 'etnaviv', 'freedreno', 'glsl', 'intel', 'intel-ui', 'nir', 'nouveau', 'xvmc', 'lima', 'all'], description : 'List of tools to build. (Note: `intel-ui` selects `intel`)', ) option( diff --git a/src/broadcom/drm-shim/README.md b/src/broadcom/drm-shim/README.md new file mode 100644 index 00000000000..dde21c1b838 --- /dev/null +++ b/src/broadcom/drm-shim/README.md @@ -0,0 +1,17 @@ +### v3d backend + +This implements some of v3d using the closed source v3dv3 tree's +C/C++-based simulator. All execution is synchronous. + +Export: `MESA_LOADER_DRIVER_OVERRIDE=v3d +LD_PRELOAD=$prefix/lib/libv3d_drm_shim.so`. The v3dv3 version exposed +will depend on the v3dv3 build -- 3.3, 4.1, and 4.2 are supported. + +### v3d_noop backend + +This implements the minimum of v3d in order to make shader-db work. +The submit ioctl is stubbed out to not execute anything. + +Export `MESA_LOADER_DRIVER_OVERRIDE=v3d +LD_PRELOAD=$prefix/lib/libv3d_noop_drm_shim.so`. This will be a V3D +4.2 device. diff --git a/src/broadcom/drm-shim/meson.build b/src/broadcom/drm-shim/meson.build new file mode 100644 index 00000000000..2c2e4c5eb2e --- /dev/null +++ b/src/broadcom/drm-shim/meson.build @@ -0,0 +1,62 @@ +# Copyright © 2019 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 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. + +libv3d_noop_drm_shim = shared_library( + ['v3d_noop_drm_shim'], + 'v3d_noop.c', + include_directories: inc_common, + dependencies: dep_drm_shim, + c_args : c_vis_args, + install : true, +) + +dep_v3dv3 = dependency('v3dv3', required: false) +if dep_v3dv3.found() + v3dv3_c_args = '-DUSE_V3D_SIMULATOR' + + inc_gallium_v3d = include_directories('../../gallium/drivers/v3d') + + per_version_libs = [] + foreach ver : v3d_versions + per_version_libs += static_library( + 'libv3d_drm_shim-v' + ver, + [ + 'v3dx.c', + v3d_xml_pack + ], + include_directories : [inc_common, inc_broadcom, inc_src, inc_gallium_v3d], + c_args : [c_vis_args, no_override_init_args, '-DV3D_VERSION=' + ver, v3dv3_c_args], + dependencies: [dep_valgrind, dep_thread, dep_v3dv3], + ) + endforeach + + libv3d_drm_shim = shared_library( + ['v3d_drm_shim'], + [ + 'v3d.c', + '../../gallium/drivers/v3d/v3d_simulator_wrapper.cpp', + ], + dependencies: [dep_dl, dep_drm_shim, dep_v3dv3], + link_with: [libmesa_util, per_version_libs], + include_directories : [inc_common, inc_broadcom, inc_gallium_v3d], + c_args : [c_vis_args, no_override_init_args, '-std=gnu99', v3dv3_c_args], + cpp_args : [v3dv3_c_args] + ) +endif diff --git a/src/broadcom/drm-shim/v3d.c b/src/broadcom/drm-shim/v3d.c new file mode 100644 index 00000000000..e75657f59f7 --- /dev/null +++ b/src/broadcom/drm-shim/v3d.c @@ -0,0 +1,98 @@ +/* + * 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 <stdio.h> +#include <sys/ioctl.h> +#include "drm-uapi/v3d_drm.h" +#include "drm-shim/drm_shim.h" +#include "v3d.h" +#include "v3d_simulator_wrapper.h" + +static struct v3d_device_info devinfo; +struct v3d_shim_device v3d = { + .devinfo = &devinfo +}; + +struct v3d_bo *v3d_bo_lookup(struct shim_fd *shim_fd, int handle) +{ + return v3d_bo(drm_shim_bo_lookup(shim_fd, handle)); +} + +int +v3d_ioctl_wait_bo(int fd, unsigned long request, void *arg) +{ + /* No need to wait on anything yet, given that we submit + * synchronously. + */ + return 0; +} + +int +v3d_ioctl_mmap_bo(int fd, unsigned long request, void *arg) +{ + struct shim_fd *shim_fd = drm_shim_fd_lookup(fd); + struct drm_v3d_mmap_bo *map = arg; + struct shim_bo *bo = drm_shim_bo_lookup(shim_fd, map->handle); + + map->offset = drm_shim_bo_get_mmap_offset(shim_fd, bo); + + drm_shim_bo_put(bo); + + return 0; +} + +int +v3d_ioctl_get_bo_offset(int fd, unsigned long request, void *arg) +{ + struct shim_fd *shim_fd = drm_shim_fd_lookup(fd); + struct drm_v3d_get_bo_offset *get = arg; + struct v3d_bo *bo = v3d_bo_lookup(shim_fd, get->handle); + + get->offset = bo->offset; + + drm_shim_bo_put(&bo->base); + + return 0; +} + +void +drm_shim_driver_init(void) +{ + shim_device.driver_name = "v3d"; + + drm_shim_override_file("OF_FULLNAME=/rdb/v3d\n" + "OF_COMPATIBLE_N=1\n" + "OF_COMPATIBLE_0=brcm,7278-v3d\n", + "/sys/dev/char/%d:%d/device/uevent", + DRM_MAJOR, render_node_minor); + + v3d.hw = v3d_hw_auto_new(NULL); + v3d.devinfo->ver = v3d_hw_get_version(v3d.hw); + + if (v3d.devinfo->ver >= 42) + v3d42_drm_shim_driver_init(); + else if (v3d.devinfo->ver >= 41) + v3d41_drm_shim_driver_init(); + else + v3d33_drm_shim_driver_init(); +} diff --git a/src/broadcom/drm-shim/v3d.h b/src/broadcom/drm-shim/v3d.h new file mode 100644 index 00000000000..0712b8b3f24 --- /dev/null +++ b/src/broadcom/drm-shim/v3d.h @@ -0,0 +1,70 @@ +/* + * 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. + */ + +#ifndef DRM_SHIM_V3D_H +#define DRM_SHIM_V3D_H + +#include "broadcom/common/v3d_device_info.h" +#include "util/vma.h" + +struct drm_shim_fd; + +struct v3d_shim_device { + struct v3d_hw *hw; + struct v3d_device_info *devinfo; + + /* Base virtual address of the heap. */ + void *mem; + /* Base hardware address of the heap. */ + uint32_t mem_base; + /* Size of the heap. */ + size_t mem_size; + + /* Allocator for the GPU virtual addresses. */ + struct util_vma_heap heap; +}; +extern struct v3d_shim_device v3d; + +struct v3d_bo { + struct shim_bo base; + uint64_t offset; + void *sim_vaddr; + void *gem_vaddr; +}; + +static inline struct v3d_bo * +v3d_bo(struct shim_bo *bo) +{ + return (struct v3d_bo *)bo; +} + +struct v3d_bo *v3d_bo_lookup(struct shim_fd *shim_fd, int handle); +int v3d_ioctl_wait_bo(int fd, unsigned long request, void *arg); +int v3d_ioctl_mmap_bo(int fd, unsigned long request, void *arg); +int v3d_ioctl_get_bo_offset(int fd, unsigned long request, void *arg); + +void v3d33_drm_shim_driver_init(void); +void v3d41_drm_shim_driver_init(void); +void v3d42_drm_shim_driver_init(void); + +#endif /* DRM_SHIM_V3D_H */ diff --git a/src/broadcom/drm-shim/v3d_noop.c b/src/broadcom/drm-shim/v3d_noop.c new file mode 100644 index 00000000000..7c7d751285d --- /dev/null +++ b/src/broadcom/drm-shim/v3d_noop.c @@ -0,0 +1,158 @@ +/* + * 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 <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/ioctl.h> +#include "drm-uapi/v3d_drm.h" +#include "drm-shim/drm_shim.h" + +struct v3d_bo { + struct shim_bo base; + uint32_t offset; +}; + +static struct v3d_bo * +v3d_bo(struct shim_bo *bo) +{ + return (struct v3d_bo *)bo; +} + +struct v3d_device { + uint32_t next_offset; +}; + +static struct v3d_device v3d = { + .next_offset = 0x1000, +}; + +static int +v3d_ioctl_noop(int fd, unsigned long request, void *arg) +{ + return 0; +} + +static int +v3d_ioctl_create_bo(int fd, unsigned long request, void *arg) +{ + struct shim_fd *shim_fd = drm_shim_fd_lookup(fd); + struct drm_v3d_create_bo *create = arg; + struct v3d_bo *bo = calloc(1, sizeof(*bo)); + + drm_shim_bo_init(&bo->base, create->size); + + assert(UINT_MAX - v3d.next_offset > create->size); + bo->offset = v3d.next_offset; + v3d.next_offset += create->size; + + create->offset = bo->offset; + create->handle = drm_shim_bo_get_handle(shim_fd, &bo->base); + + drm_shim_bo_put(&bo->base); + + return 0; +} + +static int +v3d_ioctl_get_bo_offset(int fd, unsigned long request, void *arg) +{ + struct shim_fd *shim_fd = drm_shim_fd_lookup(fd); + struct drm_v3d_get_bo_offset *args = arg; + struct shim_bo *bo = drm_shim_bo_lookup(shim_fd, args->handle); + + args->offset = v3d_bo(bo)->offset; + + drm_shim_bo_put(bo); + + return 0; +} + +static int +v3d_ioctl_mmap_bo(int fd, unsigned long request, void *arg) +{ + struct shim_fd *shim_fd = drm_shim_fd_lookup(fd); + struct drm_v3d_mmap_bo *map = arg; + struct shim_bo *bo = drm_shim_bo_lookup(shim_fd, map->handle); + + map->offset = drm_shim_bo_get_mmap_offset(shim_fd, bo); + + drm_shim_bo_put(bo); + + return 0; +} + +static int +v3d_ioctl_get_param(int fd, unsigned long request, void *arg) +{ + struct drm_v3d_get_param *gp = arg; + static const uint32_t v3d42_reg_map[] = { + [DRM_V3D_PARAM_V3D_UIFCFG] = 0x00000045, + [DRM_V3D_PARAM_V3D_HUB_IDENT1] = 0x000e1124, + [DRM_V3D_PARAM_V3D_HUB_IDENT2] = 0x00000100, + [DRM_V3D_PARAM_V3D_HUB_IDENT3] = 0x00000e00, + [DRM_V3D_PARAM_V3D_CORE0_IDENT0] = 0x04443356, + [DRM_V3D_PARAM_V3D_CORE0_IDENT1] = 0x81001422, + [DRM_V3D_PARAM_V3D_CORE0_IDENT2] = 0x40078121, + }; + + switch (gp->param) { + case DRM_V3D_PARAM_SUPPORTS_TFU: + gp->value = 1; + return 0; + default: + break; + } + + if (gp->param < ARRAY_SIZE(v3d42_reg_map) && v3d42_reg_map[gp->param]) { + gp->value = v3d42_reg_map[gp->param]; + return 0; + } + + fprintf(stderr, "Unknown DRM_IOCTL_V3D_GET_PARAM %d\n", gp->param); + return -1; +} + +static ioctl_fn_t driver_ioctls[] = { + [DRM_V3D_SUBMIT_CL] = v3d_ioctl_noop, + [DRM_V3D_SUBMIT_TFU] = v3d_ioctl_noop, + [DRM_V3D_WAIT_BO] = v3d_ioctl_noop, + [DRM_V3D_CREATE_BO] = v3d_ioctl_create_bo, + [DRM_V3D_GET_PARAM] = v3d_ioctl_get_param, + [DRM_V3D_GET_BO_OFFSET] = v3d_ioctl_get_bo_offset, + [DRM_V3D_MMAP_BO] = v3d_ioctl_mmap_bo, +}; + +void +drm_shim_driver_init(void) +{ + shim_device.driver_name = "v3d"; + shim_device.driver_ioctls = driver_ioctls; + shim_device.driver_ioctl_count = ARRAY_SIZE(driver_ioctls); + + drm_shim_override_file("OF_FULLNAME=/rdb/v3d\n" + "OF_COMPATIBLE_N=1\n" + "OF_COMPATIBLE_0=brcm,7278-v3d\n", + "/sys/dev/char/%d:%d/device/uevent", + DRM_MAJOR, render_node_minor); +} diff --git a/src/broadcom/drm-shim/v3dx.c b/src/broadcom/drm-shim/v3dx.c new file mode 100644 index 00000000000..a22550a03a5 --- /dev/null +++ b/src/broadcom/drm-shim/v3dx.c @@ -0,0 +1,370 @@ +/* + * Copyright © 2014-2017 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 + * + * v3d driver code interacting v3dv3 simulator/fpga library. + * + * This is compiled per V3D version we support, since the register definitions + * conflict. + */ + +#include <errno.h> +#include <stdbool.h> +#include <stdio.h> +#include <string.h> +#include <sys/mman.h> +#include "util/macros.h" +#include "util/u_mm.h" +#include "broadcom/common/v3d_macros.h" +#include "v3d_simulator_wrapper.h" +#include "drm-shim/drm_shim.h" +#include "drm-uapi/v3d_drm.h" +#include "v3d.h" + +#define HW_REGISTER_RO(x) (x) +#define HW_REGISTER_RW(x) (x) +#if V3D_VERSION >= 41 +#include "libs/core/v3d/registers/4.1.34.0/v3d.h" +#else +#include "libs/core/v3d/registers/3.3.0.0/v3d.h" +#endif + +#define V3D_WRITE(reg, val) v3d_hw_write_reg(v3d.hw, reg, val) +#define V3D_READ(reg) v3d_hw_read_reg(v3d.hw, reg) + +static void +v3d_flush_l3() +{ + if (!v3d_hw_has_gca(v3d.hw)) + return; + +#if V3D_VERSION < 40 + uint32_t gca_ctrl = V3D_READ(V3D_GCA_CACHE_CTRL); + + V3D_WRITE(V3D_GCA_CACHE_CTRL, gca_ctrl | V3D_GCA_CACHE_CTRL_FLUSH_SET); + V3D_WRITE(V3D_GCA_CACHE_CTRL, gca_ctrl & ~V3D_GCA_CACHE_CTRL_FLUSH_SET); +#endif +} + +/* Invalidates the L2 cache. This is a read-only cache. */ +static void +v3d_flush_l2(void) +{ + V3D_WRITE(V3D_CTL_0_L2CACTL, + V3D_CTL_0_L2CACTL_L2CCLR_SET | + V3D_CTL_0_L2CACTL_L2CENA_SET); +} + +/* Invalidates texture L2 cachelines */ +static void +v3d_flush_l2t(void) +{ + V3D_WRITE(V3D_CTL_0_L2TFLSTA, 0); + V3D_WRITE(V3D_CTL_0_L2TFLEND, ~0); + V3D_WRITE(V3D_CTL_0_L2TCACTL, + V3D_CTL_0_L2TCACTL_L2TFLS_SET | + (0 << V3D_CTL_0_L2TCACTL_L2TFLM_LSB)); +} + +/* Invalidates the slice caches. These are read-only caches. */ +static void +v3d_flush_slices(void) +{ + V3D_WRITE(V3D_CTL_0_SLCACTL, ~0); +} + +static void +v3d_flush_caches(void) +{ + v3d_flush_l3(); + v3d_flush_l2(); + v3d_flush_l2t(); + v3d_flush_slices(); +} + +static void +v3d_simulator_copy_in_handle(struct shim_fd *shim_fd, int handle) +{ + if (!handle) + return; + + struct v3d_bo *bo = v3d_bo_lookup(shim_fd, handle); + + memcpy(bo->sim_vaddr, bo->gem_vaddr, bo->base.size); +} + +static void +v3d_simulator_copy_out_handle(struct shim_fd *shim_fd, int handle) +{ + if (!handle) + return; + + struct v3d_bo *bo = v3d_bo_lookup(shim_fd, handle); + + memcpy(bo->gem_vaddr, bo->sim_vaddr, bo->base.size); +} + +static int +v3dX(v3d_ioctl_submit_cl)(int fd, unsigned long request, void *arg) +{ + struct shim_fd *shim_fd = drm_shim_fd_lookup(fd); + struct drm_v3d_submit_cl *submit = arg; + uint32_t *bo_handles = (uint32_t *)(uintptr_t)submit->bo_handles; + + for (int i = 0; i < submit->bo_handle_count; i++) + v3d_simulator_copy_in_handle(shim_fd, bo_handles[i]); + + v3d_flush_caches(); + + if (submit->qma) { + V3D_WRITE(V3D_CLE_0_CT0QMA, submit->qma); + V3D_WRITE(V3D_CLE_0_CT0QMS, submit->qms); + } +#if V3D_VERSION >= 41 + if (submit->qts) { + V3D_WRITE(V3D_CLE_0_CT0QTS, + V3D_CLE_0_CT0QTS_CTQTSEN_SET | + submit->qts); + } +#endif + + fprintf(stderr, "submit %x..%x!\n", submit->bcl_start, submit->bcl_end); + + V3D_WRITE(V3D_CLE_0_CT0QBA, submit->bcl_start); + V3D_WRITE(V3D_CLE_0_CT0QEA, submit->bcl_end); + + /* Wait for bin to complete before firing render, as it seems the + * simulator doesn't implement the semaphores. + */ + while (V3D_READ(V3D_CLE_0_CT0CA) != + V3D_READ(V3D_CLE_0_CT0EA)) { + v3d_hw_tick(v3d.hw); + } + + fprintf(stderr, "submit %x..%x!\n", submit->rcl_start, submit->rcl_end); + + v3d_flush_caches(); + + V3D_WRITE(V3D_CLE_0_CT1QBA, submit->rcl_start); + V3D_WRITE(V3D_CLE_0_CT1QEA, submit->rcl_end); + + while (V3D_READ(V3D_CLE_0_CT1CA) != + V3D_READ(V3D_CLE_0_CT1EA)) { + v3d_hw_tick(v3d.hw); + } + + for (int i = 0; i < submit->bo_handle_count; i++) + v3d_simulator_copy_out_handle(shim_fd, bo_handles[i]); + + return 0; +} + +static int +v3dX(v3d_ioctl_submit_tfu)(int fd, unsigned long request, void *arg) +{ + struct shim_fd *shim_fd = drm_shim_fd_lookup(fd); + struct drm_v3d_submit_tfu *submit = arg; + + v3d_simulator_copy_in_handle(shim_fd, submit->bo_handles[0]); + v3d_simulator_copy_in_handle(shim_fd, submit->bo_handles[1]); + v3d_simulator_copy_in_handle(shim_fd, submit->bo_handles[2]); + v3d_simulator_copy_in_handle(shim_fd, submit->bo_handles[3]); + + int last_vtct = V3D_READ(V3D_TFU_CS) & V3D_TFU_CS_CVTCT_SET; + + V3D_WRITE(V3D_TFU_IIA, submit->iia); + V3D_WRITE(V3D_TFU_IIS, submit->iis); + V3D_WRITE(V3D_TFU_ICA, submit->ica); + V3D_WRITE(V3D_TFU_IUA, submit->iua); + V3D_WRITE(V3D_TFU_IOA, submit->ioa); + V3D_WRITE(V3D_TFU_IOS, submit->ios); + V3D_WRITE(V3D_TFU_COEF0, submit->coef[0]); + V3D_WRITE(V3D_TFU_COEF1, submit->coef[1]); + V3D_WRITE(V3D_TFU_COEF2, submit->coef[2]); + V3D_WRITE(V3D_TFU_COEF3, submit->coef[3]); + + V3D_WRITE(V3D_TFU_ICFG, submit->icfg); + + while ((V3D_READ(V3D_TFU_CS) & V3D_TFU_CS_CVTCT_SET) == last_vtct) { + v3d_hw_tick(v3d.hw); + } + + v3d_simulator_copy_out_handle(shim_fd, submit->bo_handles[0]); + + return 0; +} + +static int +v3dX(v3d_ioctl_create_bo)(int fd, unsigned long request, void *arg) +{ + struct shim_fd *shim_fd = drm_shim_fd_lookup(fd); + struct drm_v3d_create_bo *create = arg; + struct v3d_bo *bo = calloc(1, sizeof(*bo)); + + drm_shim_bo_init(&bo->base, create->size); + bo->offset = util_vma_heap_alloc(&v3d.heap, create->size, 4096); + if (bo->offset == 0) + return -ENOMEM; + + bo->sim_vaddr = v3d.mem + bo->offset - v3d.mem_base; +#if 0 + /* Place a mapping of the BO inside of the simulator's address space + * for V3D memory. This lets us avoid copy in/out for simpenrose, but + * I'm betting we'll need something else for FPGA. + */ + void *sim_addr = v3d.mem + bo->block->ofs; + void *mmap_ret = mmap(sim_addr, create->size, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_FIXED, bo->base.fd, 0); + assert(mmap_ret == sim_addr); +#else + /* Make a simulator-private mapping of the shim GEM object. */ + bo->gem_vaddr = mmap(NULL, bo->base.size, + PROT_READ | PROT_WRITE, + MAP_SHARED, + bo->base.fd, 0); + if (bo->gem_vaddr == MAP_FAILED) { + fprintf(stderr, "v3d: mmap of shim bo failed\n"); + abort(); + } +#endif + + create->offset = bo->offset; + create->handle = drm_shim_bo_get_handle(shim_fd, &bo->base); + + drm_shim_bo_put(&bo->base); + + return 0; +} + +static int +v3dX(v3d_ioctl_get_param)(int fd, unsigned long request, void *arg) +{ + struct drm_v3d_get_param *gp = arg; + static const uint32_t reg_map[] = { + [DRM_V3D_PARAM_V3D_UIFCFG] = V3D_HUB_CTL_UIFCFG, + [DRM_V3D_PARAM_V3D_HUB_IDENT1] = V3D_HUB_CTL_IDENT1, + [DRM_V3D_PARAM_V3D_HUB_IDENT2] = V3D_HUB_CTL_IDENT2, + [DRM_V3D_PARAM_V3D_HUB_IDENT3] = V3D_HUB_CTL_IDENT3, + [DRM_V3D_PARAM_V3D_CORE0_IDENT0] = V3D_CTL_0_IDENT0, + [DRM_V3D_PARAM_V3D_CORE0_IDENT1] = V3D_CTL_0_IDENT1, + [DRM_V3D_PARAM_V3D_CORE0_IDENT2] = V3D_CTL_0_IDENT2, + }; + + switch (gp->param) { + case DRM_V3D_PARAM_SUPPORTS_TFU: + gp->value = 1; + return 0; + } + + if (gp->param < ARRAY_SIZE(reg_map) && reg_map[gp->param]) { + gp->value = V3D_READ(reg_map[gp->param]); + return 0; + } + + fprintf(stderr, "Unknown DRM_IOCTL_V3D_GET_PARAM %d\n", gp->param); + return -1; +} + +static ioctl_fn_t driver_ioctls[] = { + [DRM_V3D_SUBMIT_CL] = v3dX(v3d_ioctl_submit_cl), + [DRM_V3D_SUBMIT_TFU] = v3dX(v3d_ioctl_submit_tfu), + [DRM_V3D_WAIT_BO] = v3d_ioctl_wait_bo, + [DRM_V3D_CREATE_BO] = v3dX(v3d_ioctl_create_bo), + [DRM_V3D_GET_PARAM] = v3dX(v3d_ioctl_get_param), + [DRM_V3D_MMAP_BO] = v3d_ioctl_mmap_bo, + [DRM_V3D_GET_BO_OFFSET] = v3d_ioctl_get_bo_offset, +}; + +static void +v3d_isr(uint32_t hub_status) +{ + /* Check the per-core bits */ + if (hub_status & (1 << 0)) { + uint32_t core_status = V3D_READ(V3D_CTL_0_INT_STS); + + if (core_status & V3D_CTL_0_INT_STS_INT_GMPV_SET) { + fprintf(stderr, "GMP violation at 0x%08x\n", + V3D_READ(V3D_GMP_0_VIO_ADDR)); + abort(); + } else { + fprintf(stderr, + "Unexpected ISR with core status 0x%08x\n", + core_status); + } + abort(); + } + + return; +} + +static void +v3dX(simulator_init_regs)(void) +{ +#if V3D_VERSION == 33 + /* Set OVRTMUOUT to match kernel behavior. + * + * This means that the texture sampler uniform configuration's tmu + * output type field is used, instead of using the hardware default + * behavior based on the texture type. If you want the default + * behavior, you can still put "2" in the indirect texture state's + * output_type field. + */ + V3D_WRITE(V3D_CTL_0_MISCCFG, V3D_CTL_1_MISCCFG_OVRTMUOUT_SET); +#endif + + uint32_t core_interrupts = V3D_CTL_0_INT_STS_INT_GMPV_SET; + V3D_WRITE(V3D_CTL_0_INT_MSK_SET, ~core_interrupts); + V3D_WRITE(V3D_CTL_0_INT_MSK_CLR, core_interrupts); + + v3d_hw_set_isr(v3d.hw, v3d_isr); +} + +static void +v3d_bo_free(struct shim_bo *shim_bo) +{ + struct v3d_bo *bo = v3d_bo(shim_bo); + + if (bo->gem_vaddr) + munmap(bo->gem_vaddr, shim_bo->size); + + util_vma_heap_free(&v3d.heap, bo->offset, bo->base.size); +} + +void +v3dX(drm_shim_driver_init)(void) +{ + shim_device.driver_ioctls = driver_ioctls; + shim_device.driver_ioctl_count = ARRAY_SIZE(driver_ioctls); + + shim_device.driver_bo_free = v3d_bo_free; + + /* Allocate a gig of memory to play in. */ + v3d_hw_alloc_mem(v3d.hw, 1024 * 1024 * 1024); + v3d.mem_base = + v3d_hw_get_mem(v3d.hw, &v3d.mem_size, + &v3d.mem); + util_vma_heap_init(&v3d.heap, 4096, v3d.mem_size - 4096); + + v3dX(simulator_init_regs)(); +} diff --git a/src/broadcom/meson.build b/src/broadcom/meson.build index b744e7ed81b..57f0d889b25 100644 --- a/src/broadcom/meson.build +++ b/src/broadcom/meson.build @@ -30,6 +30,10 @@ if with_gallium_v3d subdir('qpu') endif +if with_tools.contains('drm-shim') + subdir('drm-shim') +endif + per_version_libs = [] foreach ver : v3d_versions per_version_libs += static_library( diff --git a/src/drm-shim/README.md b/src/drm-shim/README.md new file mode 100644 index 00000000000..031a4218229 --- /dev/null +++ b/src/drm-shim/README.md @@ -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. diff --git a/src/drm-shim/device.c b/src/drm-shim/device.c new file mode 100644 index 00000000000..fe1cbbba351 --- /dev/null +++ b/src/drm-shim/device.c @@ -0,0 +1,339 @@ +/* + * 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" + +static mtx_t handle_lock = _MTX_INITIALIZER_NP; + +#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; + +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); + + 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; + 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); + + _mesa_hash_table_insert(shim_device.fd_map, (void *)(uintptr_t)(fd + 1), 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"; + + 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_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: + 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(&handle_lock); + struct hash_entry *entry = + _mesa_hash_table_search(shim_fd->handles, (void *)(uintptr_t)c->handle); + if (!entry) { + mtx_unlock(&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(&handle_lock); + 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_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_stub, + [_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, +}; + +/** + * Implements the GEM core ioctls, and calls into driver-specific ioctls. + */ +int +drm_shim_ioctl(int fd, unsigned long request, void *arg) +{ + 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); + } + + abort(); +} + +void +drm_shim_bo_init(struct shim_bo *bo, size_t size) +{ + bo->size = size; + bo->fd = memfd_create("shim bo", MFD_CLOEXEC); + if (bo->fd == -1) { + fprintf(stderr, "Failed to create BO: %s\n", strerror(errno)); + abort(); + } + + if (ftruncate(bo->fd, size) == -1) { + fprintf(stderr, "Failed to size BO: %s\n", strerror(errno)); + abort(); + } +} + +struct shim_bo * +drm_shim_bo_lookup(struct shim_fd *shim_fd, int handle) +{ + if (!handle) + return NULL; + + mtx_lock(&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(&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); + close(bo->fd); + 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(&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(&handle_lock); + return new_handle; + } + } + mtx_unlock(&handle_lock); + + return 0; +} + +/* Creates an mmap offset for the BO in the DRM fd. + * + * XXX: We should be maintaining a u_mm allocator where the mmap offsets + * allocate the size of the BO and it can be used to look the BO back up. + * Instead, we just stuff the shim's pointer as the return value, and treat + * the incoming mmap offset on the DRM fd as a BO pointer. This doesn't work + * if someone tries to map a subset of the BO, but it's enough to get V3D + * working for now. + */ +uint64_t +drm_shim_bo_get_mmap_offset(struct shim_fd *shim_fd, struct shim_bo *bo) +{ + return (uintptr_t)bo; +} + +/* 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, off_t offset) +{ + struct shim_bo *bo = (void *)(uintptr_t)offset; + + return mmap(NULL, length, prot, flags, bo->fd, 0); +} diff --git a/src/drm-shim/drm_shim.c b/src/drm-shim/drm_shim.c new file mode 100644 index 00000000000..7c7d5f0f009 --- /dev/null +++ b/src/drm-shim/drm_shim.c @@ -0,0 +1,532 @@ +/* + * 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 +#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/set.h" +#include "util/u_debug.h" +#include "drm_shim.h" + +#define REAL_FUNCTION_POINTER(x) typeof(x) *real_##x + +static mtx_t shim_lock = _MTX_INITIALIZER_NP; +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; + +/* XXX: implement 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(open); +REAL_FUNCTION_POINTER(opendir); +REAL_FUNCTION_POINTER(readdir); +REAL_FUNCTION_POINTER(readdir64); +REAL_FUNCTION_POINTER(readlink); +REAL_FUNCTION_POINTER(__xstat); +REAL_FUNCTION_POINTER(__xstat64); +REAL_FUNCTION_POINTER(__fxstat); +REAL_FUNCTION_POINTER(__fxstat64); + +/* Full path of /dev/dri/renderD* */ +static char *render_node_path; +/* renderD* */ +static char *render_node_dirent_name; +/* /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; + +/* Come up with a filename for a render node that doesn't actually exist on + * the system. + */ +static void +get_dri_render_node_minor(void) +{ + for (int i = 0; i < 10; i++) { + int minor = 128 + i; + asprintf(&render_node_dirent_name, "renderD%d", minor); + asprintf(&render_node_path, "/dev/dri/%s", + render_node_dirent_name); + struct stat st; + if (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); + vasprintf(&path, path_format, ap); + va_end(ap); + + struct file_override *override = &file_overrides[file_overrides_count++]; + override->path = path; + override->contents = strdup(contents); +} + +static void +destroy_shim(void) +{ + _mesa_set_destroy(opendir_set, NULL); + free(render_node_path); + free(render_node_dirent_name); + free(subsystem_path); +} + +/* 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) +{ + static bool inited = false; + drm_shim_debug = debug_get_bool_option("DRM_SHIM_DEBUG", false); + + /* We can't lock this, because we recurse during initialization. */ + if (inited) + return; + + /* This comes first (and we're locked), to make sure we don't recurse + * during initialization. + */ + inited = true; + + opendir_set = _mesa_set_create(NULL, + _mesa_hash_string, + _mesa_key_string_equal); + + 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(open); + GET_FUNCTION_POINTER(opendir); + GET_FUNCTION_POINTER(readdir); + GET_FUNCTION_POINTER(readdir64); + GET_FUNCTION_POINTER(readlink); + GET_FUNCTION_POINTER(__xstat); + GET_FUNCTION_POINTER(__xstat64); + GET_FUNCTION_POINTER(__fxstat); + GET_FUNCTION_POINTER(__fxstat64); + + get_dri_render_node_minor(); + + if (drm_shim_debug) { + fprintf(stderr, "Initializing DRM shim on %s\n", + render_node_path); + } + + asprintf(&subsystem_path, + "/sys/dev/char/%d:%d/device/subsystem", + DRM_MAJOR, render_node_minor); + + drm_shim_device_init(); + + atexit(destroy_shim); +} + +/* Override libdrm's reading of various sysfs files for device enumeration. */ +PUBLIC FILE *fopen(const char *path, const char *mode) +{ + init_shim(); + + for (int i = 0; i < file_overrides_count; i++) { + if (strcmp(file_overrides[i].path, path) == 0) { + int fds[2]; + pipe(fds); + write(fds[1], file_overrides[i].contents, + strlen(file_overrides[i].contents)); + return fdopen(fds[0], "r"); + } + } + + return real_fopen(path, mode); +} +PUBLIC FILE *fopen64(const char *path, const char *mode) + __attribute__((alias("fopen"))); + +/* 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); + + if (strcmp(path, render_node_path) != 0) + return real_open(path, flags, mode); + + int 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"))); + +/* 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); + + /* Fool libdrm's probe of whether the /sys dir for this char dev is + * there. + */ + char *sys_dev_drm_dir; + asprintf(&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); + + /* Fool libdrm's probe of whether the /sys dir for this char dev is + * there. + */ + char *sys_dev_drm_dir; + asprintf(&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; +} + +/* 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; + } + + mtx_lock(&shim_lock); + _mesa_set_add(opendir_set, dir); + mtx_unlock(&shim_lock); + } + + return dir; +} + +/* If we've reached the end of the real directory list and we're + * looking at /dev/dri, add our render node to the list. + */ +PUBLIC struct dirent * +readdir(DIR *dir) +{ + init_shim(); + + struct dirent *ent = NULL; + + if (dir != fake_dev_dri) + ent = real_readdir(dir); + static struct dirent render_node_dirent = { 0 }; + + if (!ent) { + mtx_lock(&shim_lock); + if (_mesa_set_search(opendir_set, dir)) { + strcpy(render_node_dirent.d_name, + render_node_dirent_name); + ent = &render_node_dirent; + _mesa_set_remove_key(opendir_set, dir); + } + mtx_unlock(&shim_lock); + } + + return ent; +} + +/* If we've reached the end of the real directory list and we're + * looking at /dev/dri, add our render node to the list. + */ +PUBLIC struct dirent64 * +readdir64(DIR *dir) +{ + init_shim(); + + struct dirent64 *ent = NULL; + if (dir != fake_dev_dri) + ent = real_readdir64(dir); + static struct dirent64 render_node_dirent = { 0 }; + + if (!ent) { + mtx_lock(&shim_lock); + if (_mesa_set_search(opendir_set, dir)) { + strcpy(render_node_dirent.d_name, + render_node_dirent_name); + ent = &render_node_dirent; + _mesa_set_remove_key(opendir_set, dir); + } + mtx_unlock(&shim_lock); + } + + return ent; +} + +/* Cleans up tracking of opendir("/dev/dri") */ +PUBLIC int +closedir(DIR *dir) +{ + init_shim(); + + mtx_lock(&shim_lock); + _mesa_set_remove_key(opendir_set, dir); + 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 (strcmp(path, subsystem_path) != 0) + return real_readlink(path, buf, size); + strncpy(buf, "/platform", size); + buf[size - 1] = 0; + + return strlen(buf) + 1; +} + +/* 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 (shim_fd && (cmd == F_DUPFD || cmd == F_DUPFD_CLOEXEC)) + drm_shim_fd_register(ret, shim_fd); + + 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*, size_t, int, int, int, off_t) + __attribute__((alias("mmap"))); diff --git a/src/drm-shim/drm_shim.h b/src/drm-shim/drm_shim.h new file mode 100644 index 00000000000..b14353a7962 --- /dev/null +++ b/src/drm-shim/drm_shim.h @@ -0,0 +1,83 @@ +/* + * 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 "util/macros.h" +#include "util/hash_table.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; + + 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; +}; + +extern struct shim_device shim_device; + +struct shim_fd { + int fd; + /* mapping from int gem handle to struct shim_bo *. */ + struct hash_table *handles; +}; + +struct shim_bo { + int fd; + 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); +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, off_t offset); + +void 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); + +/* driver-specific hooks. */ +void drm_shim_driver_init(void); diff --git a/src/drm-shim/meson.build b/src/drm-shim/meson.build new file mode 100644 index 00000000000..1638c5ae728 --- /dev/null +++ b/src/drm-shim/meson.build @@ -0,0 +1,34 @@ +# Copyright © 2019 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 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. + +drm_shim = static_library( + ['drm_shim'], + [ + 'device.c', + 'drm_shim.c', + ], + link_with: libmesa_util, + include_directories: [inc_common], + dependencies: [dep_dl], + c_args : [c_vis_args, '-std=gnu99'], +) +dep_drm_shim = declare_dependency( + link_with: drm_shim +) diff --git a/src/meson.build b/src/meson.build index 0dcd7eef65d..d86c0ec21a6 100644 --- a/src/meson.build +++ b/src/meson.build @@ -51,6 +51,9 @@ subdir('util') subdir('mapi') # TODO: opengl subdir('compiler') +if with_tools.contains('drm-shim') + subdir('drm-shim') +endif subdir('imgui') if with_platform_wayland subdir('egl/wayland/wayland-drm') |