xref: /linux/drivers/gpu/drm/panfrost/panfrost_gem_shrinker.c (revision c532de5a67a70f8533d495f8f2aaa9a0491c3ad0)
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