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
11static 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
41static unsigned long
42msm_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
65static unsigned long
66msm_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
96static int
97msm_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 */
138void 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 */
156void 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