Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

drm/panfrost: Add madvise and shrinker support

Add support for madvise and a shrinker similar to other drivers. This
allows userspace to mark BOs which can be freed when there is memory
pressure.

Unlike other implementations, we don't depend on struct_mutex. The
driver maintains a list of BOs which can be freed when the shrinker
is called. Access to the list is serialized with the shrinker_lock.

Cc: Tomeu Vizoso <tomeu.vizoso@collabora.com>
Cc: David Airlie <airlied@linux.ie>
Cc: Daniel Vetter <daniel@ffwll.ch>
Acked-by: Alyssa Rosenzweig <alyssa.rosenzweig@collabora.com>
Signed-off-by: Rob Herring <robh@kernel.org>
Link: https://patchwork.freedesktop.org/patch/msgid/20190805143358.21245-2-robh@kernel.org

+180 -2
+1
drivers/gpu/drm/panfrost/Makefile
··· 5 5 panfrost_device.o \ 6 6 panfrost_devfreq.o \ 7 7 panfrost_gem.o \ 8 + panfrost_gem_shrinker.o \ 8 9 panfrost_gpu.o \ 9 10 panfrost_job.o \ 10 11 panfrost_mmu.o \
-2
drivers/gpu/drm/panfrost/TODO
··· 14 14 15 15 - Support userspace controlled GPU virtual addresses. Needed for Vulkan. (Tomeu) 16 16 17 - - Support for madvise and a shrinker. 18 - 19 17 - Compute job support. So called 'compute only' jobs need to be plumbed up to 20 18 userspace.
+4
drivers/gpu/drm/panfrost/panfrost_device.h
··· 85 85 struct mutex sched_lock; 86 86 struct mutex reset_lock; 87 87 88 + struct mutex shrinker_lock; 89 + struct list_head shrinker_list; 90 + struct shrinker shrinker; 91 + 88 92 struct { 89 93 struct devfreq *devfreq; 90 94 struct thermal_cooling_device *cooling;
+38
drivers/gpu/drm/panfrost/panfrost_drv.c
··· 333 333 return 0; 334 334 } 335 335 336 + static int panfrost_ioctl_madvise(struct drm_device *dev, void *data, 337 + struct drm_file *file_priv) 338 + { 339 + struct drm_panfrost_madvise *args = data; 340 + struct panfrost_device *pfdev = dev->dev_private; 341 + struct drm_gem_object *gem_obj; 342 + 343 + gem_obj = drm_gem_object_lookup(file_priv, args->handle); 344 + if (!gem_obj) { 345 + DRM_DEBUG("Failed to look up GEM BO %d\n", args->handle); 346 + return -ENOENT; 347 + } 348 + 349 + args->retained = drm_gem_shmem_madvise(gem_obj, args->madv); 350 + 351 + if (args->retained) { 352 + struct panfrost_gem_object *bo = to_panfrost_bo(gem_obj); 353 + 354 + mutex_lock(&pfdev->shrinker_lock); 355 + 356 + if (args->madv == PANFROST_MADV_DONTNEED) 357 + list_add_tail(&bo->base.madv_list, &pfdev->shrinker_list); 358 + else if (args->madv == PANFROST_MADV_WILLNEED) 359 + list_del_init(&bo->base.madv_list); 360 + 361 + mutex_unlock(&pfdev->shrinker_lock); 362 + } 363 + 364 + drm_gem_object_put_unlocked(gem_obj); 365 + return 0; 366 + } 367 + 336 368 int panfrost_unstable_ioctl_check(void) 337 369 { 338 370 if (!unstable_ioctls) ··· 416 384 PANFROST_IOCTL(GET_BO_OFFSET, get_bo_offset, DRM_RENDER_ALLOW), 417 385 PANFROST_IOCTL(PERFCNT_ENABLE, perfcnt_enable, DRM_RENDER_ALLOW), 418 386 PANFROST_IOCTL(PERFCNT_DUMP, perfcnt_dump, DRM_RENDER_ALLOW), 387 + PANFROST_IOCTL(MADVISE, madvise, DRM_RENDER_ALLOW), 419 388 }; 420 389 421 390 DEFINE_DRM_GEM_SHMEM_FOPS(panfrost_drm_driver_fops); ··· 465 432 pfdev->ddev = ddev; 466 433 467 434 spin_lock_init(&pfdev->mm_lock); 435 + mutex_init(&pfdev->shrinker_lock); 436 + INIT_LIST_HEAD(&pfdev->shrinker_list); 468 437 469 438 /* 4G enough for now. can be 48-bit */ 470 439 drm_mm_init(&pfdev->mm, SZ_32M >> PAGE_SHIFT, (SZ_4G - SZ_32M) >> PAGE_SHIFT); ··· 497 462 if (err < 0) 498 463 goto err_out1; 499 464 465 + panfrost_gem_shrinker_init(ddev); 466 + 500 467 return 0; 501 468 502 469 err_out1: ··· 515 478 struct drm_device *ddev = pfdev->ddev; 516 479 517 480 drm_dev_unregister(ddev); 481 + panfrost_gem_shrinker_cleanup(ddev); 518 482 pm_runtime_get_sync(pfdev->dev); 519 483 pm_runtime_put_sync_autosuspend(pfdev->dev); 520 484 pm_runtime_disable(pfdev->dev);
+5
drivers/gpu/drm/panfrost/panfrost_gem.c
··· 26 26 drm_mm_remove_node(&bo->node); 27 27 spin_unlock(&pfdev->mm_lock); 28 28 29 + mutex_lock(&pfdev->shrinker_lock); 30 + if (!list_empty(&bo->base.madv_list)) 31 + list_del(&bo->base.madv_list); 32 + mutex_unlock(&pfdev->shrinker_lock); 33 + 29 34 drm_gem_shmem_free_object(obj); 30 35 } 31 36
+3
drivers/gpu/drm/panfrost/panfrost_gem.h
··· 27 27 struct dma_buf_attachment *attach, 28 28 struct sg_table *sgt); 29 29 30 + void panfrost_gem_shrinker_init(struct drm_device *dev); 31 + void panfrost_gem_shrinker_cleanup(struct drm_device *dev); 32 + 30 33 #endif /* __PANFROST_GEM_H__ */
+107
drivers/gpu/drm/panfrost/panfrost_gem_shrinker.c
··· 1 + /* SPDX-License-Identifier: GPL-2.0 */ 2 + /* Copyright (C) 2019 Arm Ltd. 3 + * 4 + * Based on msm_gem_freedreno.c: 5 + * Copyright (C) 2016 Red Hat 6 + * Author: Rob Clark <robdclark@gmail.com> 7 + */ 8 + 9 + #include <linux/list.h> 10 + 11 + #include <drm/drm_device.h> 12 + #include <drm/drm_gem_shmem_helper.h> 13 + 14 + #include "panfrost_device.h" 15 + #include "panfrost_gem.h" 16 + #include "panfrost_mmu.h" 17 + 18 + static unsigned long 19 + panfrost_gem_shrinker_count(struct shrinker *shrinker, struct shrink_control *sc) 20 + { 21 + struct panfrost_device *pfdev = 22 + container_of(shrinker, struct panfrost_device, shrinker); 23 + struct drm_gem_shmem_object *shmem; 24 + unsigned long count = 0; 25 + 26 + if (!mutex_trylock(&pfdev->shrinker_lock)) 27 + return 0; 28 + 29 + list_for_each_entry(shmem, &pfdev->shrinker_list, madv_list) { 30 + if (drm_gem_shmem_is_purgeable(shmem)) 31 + count += shmem->base.size >> PAGE_SHIFT; 32 + } 33 + 34 + mutex_unlock(&pfdev->shrinker_lock); 35 + 36 + return count; 37 + } 38 + 39 + static void panfrost_gem_purge(struct drm_gem_object *obj) 40 + { 41 + struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(obj); 42 + mutex_lock(&shmem->pages_lock); 43 + 44 + panfrost_mmu_unmap(to_panfrost_bo(obj)); 45 + drm_gem_shmem_purge_locked(obj); 46 + 47 + mutex_unlock(&shmem->pages_lock); 48 + } 49 + 50 + static unsigned long 51 + panfrost_gem_shrinker_scan(struct shrinker *shrinker, struct shrink_control *sc) 52 + { 53 + struct panfrost_device *pfdev = 54 + container_of(shrinker, struct panfrost_device, shrinker); 55 + struct drm_gem_shmem_object *shmem, *tmp; 56 + unsigned long freed = 0; 57 + 58 + if (!mutex_trylock(&pfdev->shrinker_lock)) 59 + return SHRINK_STOP; 60 + 61 + list_for_each_entry_safe(shmem, tmp, &pfdev->shrinker_list, madv_list) { 62 + if (freed >= sc->nr_to_scan) 63 + break; 64 + if (drm_gem_shmem_is_purgeable(shmem)) { 65 + panfrost_gem_purge(&shmem->base); 66 + freed += shmem->base.size >> PAGE_SHIFT; 67 + list_del_init(&shmem->madv_list); 68 + } 69 + } 70 + 71 + mutex_unlock(&pfdev->shrinker_lock); 72 + 73 + if (freed > 0) 74 + pr_info_ratelimited("Purging %lu bytes\n", freed << PAGE_SHIFT); 75 + 76 + return freed; 77 + } 78 + 79 + /** 80 + * panfrost_gem_shrinker_init - Initialize panfrost shrinker 81 + * @dev: DRM device 82 + * 83 + * This function registers and sets up the panfrost shrinker. 84 + */ 85 + void panfrost_gem_shrinker_init(struct drm_device *dev) 86 + { 87 + struct panfrost_device *pfdev = dev->dev_private; 88 + pfdev->shrinker.count_objects = panfrost_gem_shrinker_count; 89 + pfdev->shrinker.scan_objects = panfrost_gem_shrinker_scan; 90 + pfdev->shrinker.seeks = DEFAULT_SEEKS; 91 + WARN_ON(register_shrinker(&pfdev->shrinker)); 92 + } 93 + 94 + /** 95 + * panfrost_gem_shrinker_cleanup - Clean up panfrost shrinker 96 + * @dev: DRM device 97 + * 98 + * This function unregisters the panfrost shrinker. 99 + */ 100 + void panfrost_gem_shrinker_cleanup(struct drm_device *dev) 101 + { 102 + struct panfrost_device *pfdev = dev->dev_private; 103 + 104 + if (pfdev->shrinker.nr_deferred) { 105 + unregister_shrinker(&pfdev->shrinker); 106 + } 107 + }
+22
include/uapi/drm/panfrost_drm.h
··· 20 20 #define DRM_PANFROST_GET_BO_OFFSET 0x05 21 21 #define DRM_PANFROST_PERFCNT_ENABLE 0x06 22 22 #define DRM_PANFROST_PERFCNT_DUMP 0x07 23 + #define DRM_PANFROST_MADVISE 0x08 23 24 24 25 #define DRM_IOCTL_PANFROST_SUBMIT DRM_IOW(DRM_COMMAND_BASE + DRM_PANFROST_SUBMIT, struct drm_panfrost_submit) 25 26 #define DRM_IOCTL_PANFROST_WAIT_BO DRM_IOW(DRM_COMMAND_BASE + DRM_PANFROST_WAIT_BO, struct drm_panfrost_wait_bo) ··· 28 27 #define DRM_IOCTL_PANFROST_MMAP_BO DRM_IOWR(DRM_COMMAND_BASE + DRM_PANFROST_MMAP_BO, struct drm_panfrost_mmap_bo) 29 28 #define DRM_IOCTL_PANFROST_GET_PARAM DRM_IOWR(DRM_COMMAND_BASE + DRM_PANFROST_GET_PARAM, struct drm_panfrost_get_param) 30 29 #define DRM_IOCTL_PANFROST_GET_BO_OFFSET DRM_IOWR(DRM_COMMAND_BASE + DRM_PANFROST_GET_BO_OFFSET, struct drm_panfrost_get_bo_offset) 30 + #define DRM_IOCTL_PANFROST_MADVISE DRM_IOWR(DRM_COMMAND_BASE + DRM_PANFROST_MADVISE, struct drm_panfrost_madvise) 31 31 32 32 /* 33 33 * Unstable ioctl(s): only exposed when the unsafe unstable_ioctls module ··· 198 196 199 197 struct drm_panfrost_perfcnt_dump { 200 198 __u64 buf_ptr; 199 + }; 200 + 201 + /* madvise provides a way to tell the kernel in case a buffers contents 202 + * can be discarded under memory pressure, which is useful for userspace 203 + * bo cache where we want to optimistically hold on to buffer allocate 204 + * and potential mmap, but allow the pages to be discarded under memory 205 + * pressure. 206 + * 207 + * Typical usage would involve madvise(DONTNEED) when buffer enters BO 208 + * cache, and madvise(WILLNEED) if trying to recycle buffer from BO cache. 209 + * In the WILLNEED case, 'retained' indicates to userspace whether the 210 + * backing pages still exist. 211 + */ 212 + #define PANFROST_MADV_WILLNEED 0 /* backing pages are needed, status returned in 'retained' */ 213 + #define PANFROST_MADV_DONTNEED 1 /* backing pages not needed */ 214 + 215 + struct drm_panfrost_madvise { 216 + __u32 handle; /* in, GEM handle */ 217 + __u32 madv; /* in, PANFROST_MADV_x */ 218 + __u32 retained; /* out, whether backing store still exists */ 201 219 }; 202 220 203 221 #if defined(__cplusplus)