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_trace.h"
10 
11 static bool msm_gem_shrinker_lock(struct drm_device *dev, bool *unlock)
12 {
13 	/* NOTE: we are *closer* to being able to get rid of
14 	 * mutex_trylock_recursive().. the msm_gem code itself does
15 	 * not need struct_mutex, although codepaths that can trigger
16 	 * shrinker are still called in code-paths that hold the
17 	 * struct_mutex.
18 	 *
19 	 * Also, msm_obj->madv is protected by struct_mutex.
20 	 *
21 	 * The next step is probably split out a seperate lock for
22 	 * protecting inactive_list, so that shrinker does not need
23 	 * struct_mutex.
24 	 */
25 	switch (mutex_trylock_recursive(&dev->struct_mutex)) {
26 	case MUTEX_TRYLOCK_FAILED:
27 		return false;
28 
29 	case MUTEX_TRYLOCK_SUCCESS:
30 		*unlock = true;
31 		return true;
32 
33 	case MUTEX_TRYLOCK_RECURSIVE:
34 		*unlock = false;
35 		return true;
36 	}
37 
38 	BUG();
39 }
40 
41 static unsigned long
42 msm_gem_shrinker_count(struct shrinker *shrinker, struct shrink_control *sc)
43 {
44 	struct msm_drm_private *priv =
45 		container_of(shrinker, struct msm_drm_private, shrinker);
46 	struct drm_device *dev = priv->dev;
47 	struct msm_gem_object *msm_obj;
48 	unsigned long count = 0;
49 	bool unlock;
50 
51 	if (!msm_gem_shrinker_lock(dev, &unlock))
52 		return 0;
53 
54 	list_for_each_entry(msm_obj, &priv->inactive_list, mm_list) {
55 		if (is_purgeable(msm_obj))
56 			count += msm_obj->base.size >> PAGE_SHIFT;
57 	}
58 
59 	if (unlock)
60 		mutex_unlock(&dev->struct_mutex);
61 
62 	return count;
63 }
64 
65 static unsigned long
66 msm_gem_shrinker_scan(struct shrinker *shrinker, struct shrink_control *sc)
67 {
68 	struct msm_drm_private *priv =
69 		container_of(shrinker, struct msm_drm_private, shrinker);
70 	struct drm_device *dev = priv->dev;
71 	struct msm_gem_object *msm_obj;
72 	unsigned long freed = 0;
73 	bool unlock;
74 
75 	if (!msm_gem_shrinker_lock(dev, &unlock))
76 		return SHRINK_STOP;
77 
78 	list_for_each_entry(msm_obj, &priv->inactive_list, mm_list) {
79 		if (freed >= sc->nr_to_scan)
80 			break;
81 		if (is_purgeable(msm_obj)) {
82 			msm_gem_purge(&msm_obj->base, OBJ_LOCK_SHRINKER);
83 			freed += msm_obj->base.size >> PAGE_SHIFT;
84 		}
85 	}
86 
87 	if (unlock)
88 		mutex_unlock(&dev->struct_mutex);
89 
90 	if (freed > 0)
91 		trace_msm_gem_purge(freed << PAGE_SHIFT);
92 
93 	return freed;
94 }
95 
96 static int
97 msm_gem_shrinker_vmap(struct notifier_block *nb, unsigned long event, void *ptr)
98 {
99 	struct msm_drm_private *priv =
100 		container_of(nb, struct msm_drm_private, vmap_notifier);
101 	struct drm_device *dev = priv->dev;
102 	struct msm_gem_object *msm_obj;
103 	unsigned unmapped = 0;
104 	bool unlock;
105 
106 	if (!msm_gem_shrinker_lock(dev, &unlock))
107 		return NOTIFY_DONE;
108 
109 	list_for_each_entry(msm_obj, &priv->inactive_list, mm_list) {
110 		if (is_vunmapable(msm_obj)) {
111 			msm_gem_vunmap(&msm_obj->base, OBJ_LOCK_SHRINKER);
112 			/* since we don't know any better, lets bail after a few
113 			 * and if necessary the shrinker will be invoked again.
114 			 * Seems better than unmapping *everything*
115 			 */
116 			if (++unmapped >= 15)
117 				break;
118 		}
119 	}
120 
121 	if (unlock)
122 		mutex_unlock(&dev->struct_mutex);
123 
124 	*(unsigned long *)ptr += unmapped;
125 
126 	if (unmapped > 0)
127 		trace_msm_gem_purge_vmaps(unmapped);
128 
129 	return NOTIFY_DONE;
130 }
131 
132 /**
133  * msm_gem_shrinker_init - Initialize msm shrinker
134  * @dev_priv: msm device
135  *
136  * This function registers and sets up the msm shrinker.
137  */
138 void msm_gem_shrinker_init(struct drm_device *dev)
139 {
140 	struct msm_drm_private *priv = dev->dev_private;
141 	priv->shrinker.count_objects = msm_gem_shrinker_count;
142 	priv->shrinker.scan_objects = msm_gem_shrinker_scan;
143 	priv->shrinker.seeks = DEFAULT_SEEKS;
144 	WARN_ON(register_shrinker(&priv->shrinker));
145 
146 	priv->vmap_notifier.notifier_call = msm_gem_shrinker_vmap;
147 	WARN_ON(register_vmap_purge_notifier(&priv->vmap_notifier));
148 }
149 
150 /**
151  * msm_gem_shrinker_cleanup - Clean up msm shrinker
152  * @dev_priv: msm device
153  *
154  * This function unregisters the msm shrinker.
155  */
156 void msm_gem_shrinker_cleanup(struct drm_device *dev)
157 {
158 	struct msm_drm_private *priv = dev->dev_private;
159 
160 	if (priv->shrinker.nr_deferred) {
161 		WARN_ON(unregister_vmap_purge_notifier(&priv->vmap_notifier));
162 		unregister_shrinker(&priv->shrinker);
163 	}
164 }
165