From ba104d3e923aeb14e4faf75db9c8a3e8313ecd48 Mon Sep 17 00:00:00 2001 From: Simon Perretta Date: Thu, 11 Sep 2025 13:54:11 +0100 Subject: [PATCH] pvr: add support for drm-shim Signed-off-by: Simon Perretta Reviewed-by: Frank Binns Part-of: --- src/imagination/drm-shim/meson.build | 13 + src/imagination/drm-shim/pvr_noop.c | 567 ++++++++++++++++++ src/imagination/include/hwdef/rogue_hw_defs.h | 2 + src/imagination/meson.build | 4 + 4 files changed, 586 insertions(+) create mode 100644 src/imagination/drm-shim/meson.build create mode 100644 src/imagination/drm-shim/pvr_noop.c diff --git a/src/imagination/drm-shim/meson.build b/src/imagination/drm-shim/meson.build new file mode 100644 index 00000000000..82562d45003 --- /dev/null +++ b/src/imagination/drm-shim/meson.build @@ -0,0 +1,13 @@ +# Copyright © 2026 Imagination Technologies Ltd. +# Copyright 2019 Broadcom +# SPDX-License-Identifier: MIT + +libpowervr_noop_drm_shim = shared_library( + 'powervr_noop_drm_shim', + 'pvr_noop.c', + link_with : [libpowervr_common], + include_directories: [inc_imagination, inc_include, inc_src], + dependencies: [dep_drm_shim, dep_valgrind, dep_csbgen], + gnu_symbol_visibility : 'hidden', + install : true, +) diff --git a/src/imagination/drm-shim/pvr_noop.c b/src/imagination/drm-shim/pvr_noop.c new file mode 100644 index 00000000000..5ce3ec6ef05 --- /dev/null +++ b/src/imagination/drm-shim/pvr_noop.c @@ -0,0 +1,567 @@ +/* + * Copyright © 2026 Imagination Technologies Ltd. + * Copyright 2018 Broadcom + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include + +#include "common/pvr_device_info.h" +#include "drm-shim/drm_shim.h" +#include "drm-uapi/pvr_drm.h" +#include "hwdef/rogue_hw_utils.h" +#include "util/macros.h" +#include "util/u_debug.h" + +bool drm_shim_driver_prefers_first_render_node = true; + + +#define DEFAULT_DEVICE_BVNC PVR_BVNC_PACK(36, 53, 104, 796) +static struct pvr_device_info device_info; + +static bool musthave_quirks = false; +static unsigned num_quirks = 0; +static uint32_t *quirks = NULL; + +static unsigned num_enhancements = 0; +static uint32_t *enhancements = NULL; + +static int +pvr_ioctl_noop(UNUSED int fd, UNUSED unsigned long request, UNUSED void *arg) +{ + return 0; +} + +static inline uint32_t +rogue_num_phantoms(const struct pvr_device_info *dev_info) +{ + return DIV_ROUND_UP(PVR_GET_FEATURE_VALUE(&device_info, num_clusters, 1U), + 4U); +} + +static inline uint32_t +rogue_common_store_partition_space_size(const struct pvr_device_info *dev_info) +{ + const uint32_t max_partitions = + PVR_GET_FEATURE_VALUE(dev_info, max_partitions, 0U); + const uint32_t tile_size_x = + PVR_GET_FEATURE_VALUE(dev_info, tile_size_x, 0U); + const uint32_t tile_size_y = + PVR_GET_FEATURE_VALUE(dev_info, tile_size_y, 0U); + + if (tile_size_x == 16U && tile_size_y == 16U) { + uint32_t usc_min_output_registers_per_pix = + PVR_GET_FEATURE_VALUE(dev_info, usc_min_output_registers_per_pix, 0U); + + return tile_size_x * tile_size_y * max_partitions * + usc_min_output_registers_per_pix; + } + + return max_partitions * 1024U; +} + +static inline uint32_t +rogue_common_store_alloc_region_size(const struct pvr_device_info *dev_info) +{ + uint32_t alloc_region_size; + + const uint32_t common_store_size_in_dwords = + PVR_GET_FEATURE_VALUE(dev_info, + common_store_size_in_dwords, + 512U * 4U * 4U); + + alloc_region_size = common_store_size_in_dwords - (256U * 4U) - + rogue_common_store_partition_space_size(dev_info); + + if (PVR_HAS_QUIRK(dev_info, 44079)) { + uint32_t common_store_split_point = (768U * 4U * 4U); + + return MIN2(common_store_split_point - (256U * 4U), alloc_region_size); + } + + return alloc_region_size; +} + +static inline uint32_t rogue_max_coeffs(const struct pvr_device_info *dev_info) +{ + uint32_t max_coeff_additional_portion = ROGUE_MAX_VERTEX_SHARED_REGISTERS; + uint32_t pending_allocation_shared_regs = 2U * 1024U; + uint32_t pending_allocation_coeff_regs = 0U; + uint32_t num_phantoms = rogue_num_phantoms(dev_info); + uint32_t tiles_in_flight = + PVR_GET_FEATURE_VALUE(dev_info, isp_max_tiles_in_flight, 0U); + uint32_t max_coeff_pixel_portion = + DIV_ROUND_UP(tiles_in_flight, num_phantoms); + max_coeff_pixel_portion *= ROGUE_MAX_PIXEL_SHARED_REGISTERS; + + /* + * Compute tasks on cores with BRN48492 and without compute overlap may lock + * up without two additional lines of coeffs. + */ + if (PVR_HAS_QUIRK(dev_info, 48492) && + !PVR_HAS_FEATURE(dev_info, compute_overlap)) + pending_allocation_coeff_regs = 2U * 1024U; + + if (PVR_HAS_ENHANCEMENT(dev_info, 38748)) + pending_allocation_shared_regs = 0; + + if (PVR_HAS_ENHANCEMENT(dev_info, 38020)) + max_coeff_additional_portion += ROGUE_MAX_COMPUTE_SHARED_REGISTERS; + + return rogue_common_store_alloc_region_size(dev_info) + + pending_allocation_coeff_regs - + (max_coeff_pixel_portion + max_coeff_additional_portion + + pending_allocation_shared_regs); +} + +static inline uint32_t +rogue_cdm_max_local_mem_size_regs(const struct pvr_device_info *dev_info) +{ + uint32_t available_coeffs_in_dwords = rogue_max_coeffs(dev_info); + + if (PVR_HAS_QUIRK(dev_info, 48492) && PVR_HAS_FEATURE(dev_info, roguexe) && + !PVR_HAS_FEATURE(dev_info, compute_overlap)) { + /* Driver must not use the 2 reserved lines. */ + available_coeffs_in_dwords -= ROGUE_CSRM_LINE_SIZE_IN_DWORDS * 2U; + } + + /* + * The maximum amount of local memory available to a kernel is the minimum + * of the total number of coefficient registers available and the max common + * store allocation size which can be made by the CDM. + * + * If any coeff lines are reserved for tessellation or pixel then we need to + * subtract those too. + */ + return MIN2(available_coeffs_in_dwords, + ROGUE_MAX_PER_KERNEL_LOCAL_MEM_SIZE_REGS); +} + +static int pvr_dev_query_gpu_info_get(struct drm_pvr_ioctl_dev_query_args *args) +{ + struct drm_pvr_dev_query_gpu_info *gpu_info = + (struct drm_pvr_dev_query_gpu_info *)args->pointer; + + if (!gpu_info) + goto dev_query_gpu_info_set_size; + + gpu_info->gpu_id = pvr_get_packed_bvnc(&device_info); + gpu_info->num_phantoms = rogue_num_phantoms(&device_info); + +dev_query_gpu_info_set_size: + args->size = sizeof(*gpu_info); + return 0; +} + +static int +pvr_dev_query_runtime_info_get(struct drm_pvr_ioctl_dev_query_args *args) +{ + struct drm_pvr_dev_query_runtime_info *runtime_info = + (struct drm_pvr_dev_query_runtime_info *)args->pointer; + + if (!runtime_info) + goto dev_query_runtime_info_set_size; + + if (PVR_HAS_FEATURE(&device_info, roguexe)) { + if (PVR_HAS_QUIRK(&device_info, 66011)) + runtime_info->free_list_min_pages = 40; + else + runtime_info->free_list_min_pages = 25; + } else { + runtime_info->free_list_min_pages = 50; + } + + runtime_info->free_list_max_pages = 0x80000; + + runtime_info->common_store_alloc_region_size = + rogue_common_store_alloc_region_size(&device_info); + + runtime_info->common_store_partition_space_size = + rogue_common_store_partition_space_size(&device_info); + + runtime_info->max_coeffs = rogue_max_coeffs(&device_info); + + runtime_info->cdm_max_local_mem_size_regs = + rogue_cdm_max_local_mem_size_regs(&device_info); + +dev_query_runtime_info_set_size: + args->size = sizeof(*runtime_info); + return 0; +} + +static int pvr_dev_query_quirks_get(struct drm_pvr_ioctl_dev_query_args *args) +{ + struct drm_pvr_dev_query_quirks *query_quirks = + (struct drm_pvr_dev_query_quirks *)args->pointer; + + if (!query_quirks) + goto dev_query_quirks_set_size; + + if (!query_quirks->quirks) { + query_quirks->count = num_quirks; + query_quirks->musthave_count = musthave_quirks ? num_quirks : 0; + goto dev_query_quirks_set_size; + } + + if (query_quirks->count > num_quirks) + query_quirks->count = num_quirks; + + memcpy((void *)query_quirks->quirks, + quirks, + sizeof(*quirks) * query_quirks->count); + +dev_query_quirks_set_size: + args->size = sizeof(*query_quirks); + return 0; +} + +static int +pvr_dev_query_enhancements_get(struct drm_pvr_ioctl_dev_query_args *args) +{ + struct drm_pvr_dev_query_enhancements *query_enhancements = + (struct drm_pvr_dev_query_enhancements *)args->pointer; + + if (!query_enhancements) + goto dev_query_enhancements_set_size; + + if (!query_enhancements->enhancements) { + query_enhancements->count = num_enhancements; + goto dev_query_enhancements_set_size; + } + + if (query_enhancements->count > num_enhancements) + query_enhancements->count = num_enhancements; + + memcpy((void *)query_enhancements->enhancements, + enhancements, + sizeof(*enhancements) * query_enhancements->count); + +dev_query_enhancements_set_size: + args->size = sizeof(*query_enhancements); + return 0; +} + +static const struct drm_pvr_heap pvr_heaps[DRM_PVR_HEAP_COUNT] = { + [DRM_PVR_HEAP_GENERAL] = { + .base = UINT64_C(0x8000000000), + .size = UINT64_C(0x2000000000), + .flags = 0, + .page_size_log2 = 12, + }, + [DRM_PVR_HEAP_PDS_CODE_DATA] = { + .base = UINT64_C(0xda00000000), + .size = UINT64_C(0x100000000), + .flags = 0, + .page_size_log2 = 12, + }, + [DRM_PVR_HEAP_USC_CODE] = { + .base = UINT64_C(0xe000000000), + .size = UINT64_C(0x100000000), + .flags = 0, + .page_size_log2 = 12, + }, + [DRM_PVR_HEAP_RGNHDR] = { + .base = UINT64_C(0xdbf0000000), + .size = 0x10000000, + .flags = 0, + .page_size_log2 = 12, + }, + [DRM_PVR_HEAP_VIS_TEST] = { + .base = UINT64_C(0xf200000000), + .size = 0x00200000, + .flags = 0, + .page_size_log2 = 12, + }, + [DRM_PVR_HEAP_TRANSFER_FRAG] = { + .base = UINT64_C(0xe400000000), + .size = UINT64_C(0x400000000), + .flags = 0, + .page_size_log2 = 12, + }, +}; + +static int +pvr_dev_query_heap_info_get(struct drm_pvr_ioctl_dev_query_args *args) +{ + struct drm_pvr_dev_query_heap_info *heap_info = + (struct drm_pvr_dev_query_heap_info *)args->pointer; + + if (!heap_info) + goto dev_query_heap_info_set_size; + + if (!heap_info->heaps.array) { + heap_info->heaps.count = ARRAY_SIZE(pvr_heaps); + heap_info->heaps.stride = sizeof(struct drm_pvr_heap); + goto dev_query_heap_info_set_size; + } + + if (heap_info->heaps.count > ARRAY_SIZE(pvr_heaps)) + heap_info->heaps.count = ARRAY_SIZE(pvr_heaps); + + memcpy((void *)heap_info->heaps.array, + pvr_heaps, + heap_info->heaps.stride * heap_info->heaps.count); + +dev_query_heap_info_set_size: + args->size = sizeof(*heap_info); + return 0; +} + +static const struct drm_pvr_static_data_area pvr_static_data_areas[] = { + { + .area_usage = DRM_PVR_STATIC_DATA_AREA_FENCE, + .location_heap_id = DRM_PVR_HEAP_GENERAL, + .offset = 0, + .size = 128, + }, + { + .area_usage = DRM_PVR_STATIC_DATA_AREA_YUV_CSC, + .location_heap_id = DRM_PVR_HEAP_GENERAL, + .offset = 128, + .size = 1024, + }, + { + .area_usage = DRM_PVR_STATIC_DATA_AREA_VDM_SYNC, + .location_heap_id = DRM_PVR_HEAP_PDS_CODE_DATA, + .offset = 0, + .size = 128, + }, + { + .area_usage = DRM_PVR_STATIC_DATA_AREA_EOT, + .location_heap_id = DRM_PVR_HEAP_PDS_CODE_DATA, + .offset = 128, + .size = 128, + }, + { + .area_usage = DRM_PVR_STATIC_DATA_AREA_VDM_SYNC, + .location_heap_id = DRM_PVR_HEAP_USC_CODE, + .offset = 0, + .size = 128, + }, +}; + +static int +pvr_dev_query_static_data_areas_get(struct drm_pvr_ioctl_dev_query_args *args) +{ + struct drm_pvr_dev_query_static_data_areas *static_data_areas = + (struct drm_pvr_dev_query_static_data_areas *)args->pointer; + + if (!static_data_areas) + goto dev_query_static_data_areas_set_size; + + if (!static_data_areas->static_data_areas.array) { + static_data_areas->static_data_areas.count = + ARRAY_SIZE(pvr_static_data_areas); + static_data_areas->static_data_areas.stride = + sizeof(struct drm_pvr_static_data_area); + goto dev_query_static_data_areas_set_size; + } + + if (static_data_areas->static_data_areas.count > + ARRAY_SIZE(pvr_static_data_areas)) { + static_data_areas->static_data_areas.count = + ARRAY_SIZE(pvr_static_data_areas); + } + + memcpy((void *)static_data_areas->static_data_areas.array, + pvr_static_data_areas, + static_data_areas->static_data_areas.stride * + static_data_areas->static_data_areas.count); + +dev_query_static_data_areas_set_size: + args->size = sizeof(*static_data_areas); + return 0; +} + +static int +pvr_ioctl_dev_query(UNUSED int fd, UNUSED unsigned long request, void *arg) +{ + struct drm_pvr_ioctl_dev_query_args *args = arg; + + switch ((enum drm_pvr_dev_query)args->type) { + case DRM_PVR_DEV_QUERY_GPU_INFO_GET: + return pvr_dev_query_gpu_info_get(args); + + case DRM_PVR_DEV_QUERY_RUNTIME_INFO_GET: + return pvr_dev_query_runtime_info_get(args); + + case DRM_PVR_DEV_QUERY_QUIRKS_GET: + return pvr_dev_query_quirks_get(args); + + case DRM_PVR_DEV_QUERY_ENHANCEMENTS_GET: + return pvr_dev_query_enhancements_get(args); + + case DRM_PVR_DEV_QUERY_HEAP_INFO_GET: + return pvr_dev_query_heap_info_get(args); + + case DRM_PVR_DEV_QUERY_STATIC_DATA_AREAS_GET: + return pvr_dev_query_static_data_areas_get(args); + + default: + break; + } + + return -EINVAL; +} + +static int pvr_ioctl_create_bo(int fd, UNUSED unsigned long request, void *arg) +{ + struct drm_pvr_ioctl_create_bo_args *args = arg; + + struct shim_fd *shim_fd = drm_shim_fd_lookup(fd); + struct shim_bo *bo = calloc(1, sizeof(*bo)); + + drm_shim_bo_init(bo, args->size); + + args->handle = drm_shim_bo_get_handle(shim_fd, bo); + + drm_shim_bo_put(bo); + + return 0; +} + +static int +pvr_ioctl_get_bo_mmap_offset(int fd, UNUSED unsigned long request, void *arg) +{ + struct drm_pvr_ioctl_get_bo_mmap_offset_args *args = arg; + + struct shim_fd *shim_fd = drm_shim_fd_lookup(fd); + struct shim_bo *bo = drm_shim_bo_lookup(shim_fd, args->handle); + + args->offset = drm_shim_bo_get_mmap_offset(shim_fd, bo); + + return 0; +} + +#define PVR_IOC_NR(_name) (_IOC_NR(DRM_IOCTL_PVR_##_name) - DRM_COMMAND_BASE) + +static ioctl_fn_t driver_ioctls[] = { + [PVR_IOC_NR(DEV_QUERY)] = pvr_ioctl_dev_query, + [PVR_IOC_NR(CREATE_BO)] = pvr_ioctl_create_bo, + [PVR_IOC_NR(GET_BO_MMAP_OFFSET)] = pvr_ioctl_get_bo_mmap_offset, + [PVR_IOC_NR(CREATE_VM_CONTEXT)] = pvr_ioctl_noop, + [PVR_IOC_NR(DESTROY_VM_CONTEXT)] = pvr_ioctl_noop, + [PVR_IOC_NR(VM_MAP)] = pvr_ioctl_noop, + [PVR_IOC_NR(VM_UNMAP)] = pvr_ioctl_noop, + [PVR_IOC_NR(CREATE_CONTEXT)] = pvr_ioctl_noop, + [PVR_IOC_NR(DESTROY_CONTEXT)] = pvr_ioctl_noop, + [PVR_IOC_NR(CREATE_FREE_LIST)] = pvr_ioctl_noop, + [PVR_IOC_NR(DESTROY_FREE_LIST)] = pvr_ioctl_noop, + [PVR_IOC_NR(CREATE_HWRT_DATASET)] = pvr_ioctl_noop, + [PVR_IOC_NR(DESTROY_HWRT_DATASET)] = pvr_ioctl_noop, + [PVR_IOC_NR(SUBMIT_JOBS)] = pvr_ioctl_noop, +}; + +static unsigned count_elems(const char *str, const char *delims) +{ + if (!str || !strlen(str)) + return 0; + + unsigned count = 1; + for (unsigned u = 0; u < strlen(str); ++u) { + for (unsigned d = 0; d < strlen(delims); ++d) { + if (str[u] == delims[d]) { + ++count; + break; + } + } + } + + return count; +} + +static void u32_str_list_to_array(const char *str_list, + const char *delims, + unsigned num_elems, + uint32_t *array) +{ + for (unsigned u = 0; u < num_elems; ++u) { + array[u] = strtol(str_list, NULL, 10); + str_list += strcspn(str_list, delims) + 1; + } +} + +static void init_overrides(void) +{ + const char *device_bvnc = debug_get_option("PVR_SHIM_DEVICE_BVNC", NULL); + static const char *delims = ",;"; + unsigned b, v, n, c; + uint64_t bvnc; + int ret; + + if (device_bvnc) { + ret = sscanf(device_bvnc, "%u.%u.%u.%u", &b, &v, &n, &c); + if (ret != 4) { + fprintf(stderr, + "pvr-drm-shim: Warning: Invalid device BVNC format \"%s\"; " + "reverting to default device BVNC.\n", + device_bvnc); + + bvnc = DEFAULT_DEVICE_BVNC; + } else { + bvnc = PVR_BVNC_PACK(b, v, n, c); + } + } + + ret = pvr_device_info_init(&device_info, bvnc); + if (ret) { + fprintf(stderr, + "pvr-drm-shim: Warning: Device BVNC provided \"%s\" not found; " + "reverting to default device BVNC.\n", + device_bvnc); + + ret = pvr_device_info_init(&device_info, DEFAULT_DEVICE_BVNC); + assert(!ret); + } + + musthave_quirks = debug_get_bool_option("PVR_SHIM_MUSTHAVE_QUIRKS", false); + const char *quirk_list = debug_get_option("PVR_SHIM_QUIRKS", NULL); + if (quirk_list) { + num_quirks = count_elems(quirk_list, delims); + quirks = malloc(num_quirks * sizeof(*quirks)); + u32_str_list_to_array(quirk_list, delims, num_quirks, quirks); + } + + const char *enhancement_list = + debug_get_option("PVR_SHIM_ENHANCEMENTS", NULL); + if (enhancement_list) { + num_enhancements = count_elems(enhancement_list, delims); + enhancements = malloc(num_enhancements * sizeof(*enhancements)); + u32_str_list_to_array(enhancement_list, + delims, + num_enhancements, + enhancements); + } +} + +static void drm_shim_driver_fini(void) +{ + free(enhancements); + free(quirks); +} + +void drm_shim_driver_init(void) +{ + init_overrides(); + + shim_device.bus_type = DRM_BUS_PLATFORM; + shim_device.driver_name = "powervr"; + shim_device.driver_ioctls = driver_ioctls; + shim_device.driver_ioctl_count = ARRAY_SIZE(driver_ioctls); + + shim_device.version_major = 1; + shim_device.version_minor = 0; + shim_device.version_patchlevel = 0; + + drm_shim_override_file("DRIVER=powervr\n" + "OF_FULLNAME=/soc/pvr\n" + "OF_COMPATIBLE_0=img,img-rogue\n" + "OF_COMPATIBLE_N=1\n", + "/sys/dev/char/%d:%d/device/uevent", + DRM_MAJOR, + render_node_minor); + + atexit(drm_shim_driver_fini); +} diff --git a/src/imagination/include/hwdef/rogue_hw_defs.h b/src/imagination/include/hwdef/rogue_hw_defs.h index 8b40f9b4656..61a6284867a 100644 --- a/src/imagination/include/hwdef/rogue_hw_defs.h +++ b/src/imagination/include/hwdef/rogue_hw_defs.h @@ -87,6 +87,8 @@ #define ROGUE_MAX_PIXEL_SHARED_REGISTERS 1024U +#define ROGUE_MAX_COMPUTE_SHARED_REGISTERS (2U * 1024U) + /* Number of CR_PDS_BGRND values that need setting up. */ #define ROGUE_NUM_CR_PDS_BGRND_WORDS 3U diff --git a/src/imagination/meson.build b/src/imagination/meson.build index c3b17bdcb46..0c666d37fb7 100644 --- a/src/imagination/meson.build +++ b/src/imagination/meson.build @@ -19,4 +19,8 @@ endif if with_imagination_vk subdir('vulkan') + + if with_tools.contains('drm-shim') + subdir('drm-shim') + endif endif