1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Copyright (C) 2016 Red Hat
4  * Author: Rob Clark <robdclark@gmail.com>
5  */
6 
7 #include "msm_drv.h"
8 #include "msm_gem.h"
9 #include "msm_gpu.h"
10 #include "msm_gpu_trace.h"
11 
12 static unsigned long
13 msm_gem_shrinker_count(struct shrinker *shrinker, struct shrink_control *sc)
14 {
15 	struct msm_drm_private *priv =
16 		container_of(shrinker, struct msm_drm_private, shrinker);
17 	struct msm_gem_object *msm_obj;
18 	unsigned long count = 0;
19 
20 	mutex_lock(&priv->mm_lock);
21 
22 	list_for_each_entry(msm_obj, &priv->inactive_dontneed, mm_list) {
23 		if (!msm_gem_trylock(&msm_obj->base))
24 			continue;
25 		if (is_purgeable(msm_obj))
26 			count += msm_obj->base.size >> PAGE_SHIFT;
27 		msm_gem_unlock(&msm_obj->base);
28 	}
29 
30 	mutex_unlock(&priv->mm_lock);
31 
32 	return count;
33 }
34 
35 static unsigned long
36 msm_gem_shrinker_scan(struct shrinker *shrinker, struct shrink_control *sc)
37 {
38 	struct msm_drm_private *priv =
39 		container_of(shrinker, struct msm_drm_private, shrinker);
40 	struct msm_gem_object *msm_obj;
41 	unsigned long freed = 0;
42 
43 	mutex_lock(&priv->mm_lock);
44 
45 	list_for_each_entry(msm_obj, &priv->inactive_dontneed, mm_list) {
46 		if (freed >= sc->nr_to_scan)
47 			break;
48 		if (!msm_gem_trylock(&msm_obj->base))
49 			continue;
50 		if (is_purgeable(msm_obj)) {
51 			msm_gem_purge(&msm_obj->base);
52 			freed += msm_obj->base.size >> PAGE_SHIFT;
53 		}
54 		msm_gem_unlock(&msm_obj->base);
55 	}
56 
57 	mutex_unlock(&priv->mm_lock);
58 
59 	if (freed > 0)
60 		trace_msm_gem_purge(freed << PAGE_SHIFT);
61 
62 	return freed;
63 }
64 
65 /* since we don't know any better, lets bail after a few
66  * and if necessary the shrinker will be invoked again.
67  * Seems better than unmapping *everything*
68  */
69 static const int vmap_shrink_limit = 15;
70 
71 static unsigned
72 vmap_shrink(struct list_head *mm_list)
73 {
74 	struct msm_gem_object *msm_obj;
75 	unsigned unmapped = 0;
76 
77 	list_for_each_entry(msm_obj, mm_list, mm_list) {
78 		if (!msm_gem_trylock(&msm_obj->base))
79 			continue;
80 		if (is_vunmapable(msm_obj)) {
81 			msm_gem_vunmap(&msm_obj->base);
82 			unmapped++;
83 		}
84 		msm_gem_unlock(&msm_obj->base);
85 
86 		if (++unmapped >= vmap_shrink_limit)
87 			break;
88 	}
89 
90 	return unmapped;
91 }
92 
93 static int
94 msm_gem_shrinker_vmap(struct notifier_block *nb, unsigned long event, void *ptr)
95 {
96 	struct msm_drm_private *priv =
97 		container_of(nb, struct msm_drm_private, vmap_notifier);
98 	struct list_head *mm_lists[] = {
99 		&priv->inactive_dontneed,
100 		&priv->inactive_willneed,
101 		priv->gpu ? &priv->gpu->active_list : NULL,
102 		NULL,
103 	};
104 	unsigned idx, unmapped = 0;
105 
106 	mutex_lock(&priv->mm_lock);
107 
108 	for (idx = 0; mm_lists[idx]; idx++) {
109 		unmapped += vmap_shrink(mm_lists[idx]);
110 
111 		if (unmapped >= vmap_shrink_limit)
112 			break;
113 	}
114 
115 	mutex_unlock(&priv->mm_lock);
116 
117 	*(unsigned long *)ptr += unmapped;
118 
119 	if (unmapped > 0)
120 		trace_msm_gem_purge_vmaps(unmapped);
121 
122 	return NOTIFY_DONE;
123 }
124 
125 /**
126  * msm_gem_shrinker_init - Initialize msm shrinker
127  * @dev: drm device
128  *
129  * This function registers and sets up the msm shrinker.
130  */
131 void msm_gem_shrinker_init(struct drm_device *dev)
132 {
133 	struct msm_drm_private *priv = dev->dev_private;
134 	priv->shrinker.count_objects = msm_gem_shrinker_count;
135 	priv->shrinker.scan_objects = msm_gem_shrinker_scan;
136 	priv->shrinker.seeks = DEFAULT_SEEKS;
137 	WARN_ON(register_shrinker(&priv->shrinker));
138 
139 	priv->vmap_notifier.notifier_call = msm_gem_shrinker_vmap;
140 	WARN_ON(register_vmap_purge_notifier(&priv->vmap_notifier));
141 }
142 
143 /**
144  * msm_gem_shrinker_cleanup - Clean up msm shrinker
145  * @dev: drm device
146  *
147  * This function unregisters the msm shrinker.
148  */
149 void msm_gem_shrinker_cleanup(struct drm_device *dev)
150 {
151 	struct msm_drm_private *priv = dev->dev_private;
152 
153 	if (priv->shrinker.nr_deferred) {
154 		WARN_ON(unregister_vmap_purge_notifier(&priv->vmap_notifier));
155 		unregister_shrinker(&priv->shrinker);
156 	}
157 }
158