xref: /openbmc/linux/drivers/gpu/drm/msm/msm_gem_shrinker.c (revision 4ed91d48259d9ddd378424d008f2e6559f7e78f8)
1 /*
2  * Copyright (C) 2016 Red Hat
3  * Author: Rob Clark <robdclark@gmail.com>
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 as published by
7  * the Free Software Foundation.
8  *
9  * This program is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
12  * more details.
13  *
14  * You should have received a copy of the GNU General Public License along with
15  * this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include "msm_drv.h"
19 #include "msm_gem.h"
20 
21 static bool msm_gem_shrinker_lock(struct drm_device *dev, bool *unlock)
22 {
23 	switch (mutex_trylock_recursive(&dev->struct_mutex)) {
24 	case MUTEX_TRYLOCK_FAILED:
25 		return false;
26 
27 	case MUTEX_TRYLOCK_SUCCESS:
28 		*unlock = true;
29 		return true;
30 
31 	case MUTEX_TRYLOCK_RECURSIVE:
32 		*unlock = false;
33 		return true;
34 	}
35 
36 	BUG();
37 }
38 
39 static unsigned long
40 msm_gem_shrinker_count(struct shrinker *shrinker, struct shrink_control *sc)
41 {
42 	struct msm_drm_private *priv =
43 		container_of(shrinker, struct msm_drm_private, shrinker);
44 	struct drm_device *dev = priv->dev;
45 	struct msm_gem_object *msm_obj;
46 	unsigned long count = 0;
47 	bool unlock;
48 
49 	if (!msm_gem_shrinker_lock(dev, &unlock))
50 		return 0;
51 
52 	list_for_each_entry(msm_obj, &priv->inactive_list, mm_list) {
53 		if (is_purgeable(msm_obj))
54 			count += msm_obj->base.size >> PAGE_SHIFT;
55 	}
56 
57 	if (unlock)
58 		mutex_unlock(&dev->struct_mutex);
59 
60 	return count;
61 }
62 
63 static unsigned long
64 msm_gem_shrinker_scan(struct shrinker *shrinker, struct shrink_control *sc)
65 {
66 	struct msm_drm_private *priv =
67 		container_of(shrinker, struct msm_drm_private, shrinker);
68 	struct drm_device *dev = priv->dev;
69 	struct msm_gem_object *msm_obj;
70 	unsigned long freed = 0;
71 	bool unlock;
72 
73 	if (!msm_gem_shrinker_lock(dev, &unlock))
74 		return SHRINK_STOP;
75 
76 	list_for_each_entry(msm_obj, &priv->inactive_list, mm_list) {
77 		if (freed >= sc->nr_to_scan)
78 			break;
79 		if (is_purgeable(msm_obj)) {
80 			msm_gem_purge(&msm_obj->base);
81 			freed += msm_obj->base.size >> PAGE_SHIFT;
82 		}
83 	}
84 
85 	if (unlock)
86 		mutex_unlock(&dev->struct_mutex);
87 
88 	if (freed > 0)
89 		pr_info_ratelimited("Purging %lu bytes\n", freed << PAGE_SHIFT);
90 
91 	return freed;
92 }
93 
94 static int
95 msm_gem_shrinker_vmap(struct notifier_block *nb, unsigned long event, void *ptr)
96 {
97 	struct msm_drm_private *priv =
98 		container_of(nb, struct msm_drm_private, vmap_notifier);
99 	struct drm_device *dev = priv->dev;
100 	struct msm_gem_object *msm_obj;
101 	unsigned unmapped = 0;
102 	bool unlock;
103 
104 	if (!msm_gem_shrinker_lock(dev, &unlock))
105 		return NOTIFY_DONE;
106 
107 	list_for_each_entry(msm_obj, &priv->inactive_list, mm_list) {
108 		if (is_vunmapable(msm_obj)) {
109 			msm_gem_vunmap(&msm_obj->base);
110 			/* since we don't know any better, lets bail after a few
111 			 * and if necessary the shrinker will be invoked again.
112 			 * Seems better than unmapping *everything*
113 			 */
114 			if (++unmapped >= 15)
115 				break;
116 		}
117 	}
118 
119 	if (unlock)
120 		mutex_unlock(&dev->struct_mutex);
121 
122 	*(unsigned long *)ptr += unmapped;
123 
124 	if (unmapped > 0)
125 		pr_info_ratelimited("Purging %u vmaps\n", unmapped);
126 
127 	return NOTIFY_DONE;
128 }
129 
130 /**
131  * msm_gem_shrinker_init - Initialize msm shrinker
132  * @dev_priv: msm device
133  *
134  * This function registers and sets up the msm shrinker.
135  */
136 void msm_gem_shrinker_init(struct drm_device *dev)
137 {
138 	struct msm_drm_private *priv = dev->dev_private;
139 	priv->shrinker.count_objects = msm_gem_shrinker_count;
140 	priv->shrinker.scan_objects = msm_gem_shrinker_scan;
141 	priv->shrinker.seeks = DEFAULT_SEEKS;
142 	WARN_ON(register_shrinker(&priv->shrinker));
143 
144 	priv->vmap_notifier.notifier_call = msm_gem_shrinker_vmap;
145 	WARN_ON(register_vmap_purge_notifier(&priv->vmap_notifier));
146 }
147 
148 /**
149  * msm_gem_shrinker_cleanup - Clean up msm shrinker
150  * @dev_priv: msm device
151  *
152  * This function unregisters the msm shrinker.
153  */
154 void msm_gem_shrinker_cleanup(struct drm_device *dev)
155 {
156 	struct msm_drm_private *priv = dev->dev_private;
157 
158 	if (priv->shrinker.nr_deferred) {
159 		WARN_ON(unregister_vmap_purge_notifier(&priv->vmap_notifier));
160 		unregister_shrinker(&priv->shrinker);
161 	}
162 }
163