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 = shrinker->private_data; 22 struct drm_gem_shmem_object *shmem; 23 unsigned long count = 0; 24 25 if (!mutex_trylock(&pfdev->shrinker_lock)) 26 return 0; 27 28 list_for_each_entry(shmem, &pfdev->shrinker_list, madv_list) { 29 if (drm_gem_shmem_is_purgeable(shmem)) 30 count += shmem->base.size >> PAGE_SHIFT; 31 } 32 33 mutex_unlock(&pfdev->shrinker_lock); 34 35 return count; 36 } 37 38 static bool panfrost_gem_purge(struct drm_gem_object *obj) 39 { 40 struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(obj); 41 struct panfrost_gem_object *bo = to_panfrost_bo(obj); 42 bool ret = false; 43 44 if (atomic_read(&bo->gpu_usecount)) 45 return false; 46 47 if (!mutex_trylock(&bo->mappings.lock)) 48 return false; 49 50 if (!dma_resv_trylock(shmem->base.resv)) 51 goto unlock_mappings; 52 53 panfrost_gem_teardown_mappings_locked(bo); 54 drm_gem_shmem_purge(&bo->base); 55 ret = true; 56 57 dma_resv_unlock(shmem->base.resv); 58 59 unlock_mappings: 60 mutex_unlock(&bo->mappings.lock); 61 return ret; 62 } 63 64 static unsigned long 65 panfrost_gem_shrinker_scan(struct shrinker *shrinker, struct shrink_control *sc) 66 { 67 struct panfrost_device *pfdev = shrinker->private_data; 68 struct drm_gem_shmem_object *shmem, *tmp; 69 unsigned long freed = 0; 70 71 if (!mutex_trylock(&pfdev->shrinker_lock)) 72 return SHRINK_STOP; 73 74 list_for_each_entry_safe(shmem, tmp, &pfdev->shrinker_list, madv_list) { 75 if (freed >= sc->nr_to_scan) 76 break; 77 if (drm_gem_shmem_is_purgeable(shmem) && 78 panfrost_gem_purge(&shmem->base)) { 79 freed += shmem->base.size >> PAGE_SHIFT; 80 list_del_init(&shmem->madv_list); 81 } 82 } 83 84 mutex_unlock(&pfdev->shrinker_lock); 85 86 if (freed > 0) 87 pr_info_ratelimited("Purging %lu bytes\n", freed << PAGE_SHIFT); 88 89 return freed; 90 } 91 92 /** 93 * panfrost_gem_shrinker_init - Initialize panfrost shrinker 94 * @dev: DRM device 95 * 96 * This function registers and sets up the panfrost shrinker. 97 */ 98 int panfrost_gem_shrinker_init(struct drm_device *dev) 99 { 100 struct panfrost_device *pfdev = dev->dev_private; 101 102 pfdev->shrinker = shrinker_alloc(0, "drm-panfrost"); 103 if (!pfdev->shrinker) 104 return -ENOMEM; 105 106 pfdev->shrinker->count_objects = panfrost_gem_shrinker_count; 107 pfdev->shrinker->scan_objects = panfrost_gem_shrinker_scan; 108 pfdev->shrinker->private_data = pfdev; 109 110 shrinker_register(pfdev->shrinker); 111 112 return 0; 113 } 114 115 /** 116 * panfrost_gem_shrinker_cleanup - Clean up panfrost shrinker 117 * @dev: DRM device 118 * 119 * This function unregisters the panfrost shrinker. 120 */ 121 void panfrost_gem_shrinker_cleanup(struct drm_device *dev) 122 { 123 struct panfrost_device *pfdev = dev->dev_private; 124 125 if (pfdev->shrinker) 126 shrinker_free(pfdev->shrinker); 127 } 128