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