| /* |
| * Copyright (C) 2016 Red Hat |
| * Author: Rob Clark <robdclark@gmail.com> |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 as published by |
| * the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
| * more details. |
| * |
| * You should have received a copy of the GNU General Public License along with |
| * this program. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| #include "msm_drv.h" |
| #include "msm_gem.h" |
| |
| static bool msm_gem_shrinker_lock(struct drm_device *dev, bool *unlock) |
| { |
| /* NOTE: we are *closer* to being able to get rid of |
| * mutex_trylock_recursive().. the msm_gem code itself does |
| * not need struct_mutex, although codepaths that can trigger |
| * shrinker are still called in code-paths that hold the |
| * struct_mutex. |
| * |
| * Also, msm_obj->madv is protected by struct_mutex. |
| * |
| * The next step is probably split out a seperate lock for |
| * protecting inactive_list, so that shrinker does not need |
| * struct_mutex. |
| */ |
| switch (mutex_trylock_recursive(&dev->struct_mutex)) { |
| case MUTEX_TRYLOCK_FAILED: |
| return false; |
| |
| case MUTEX_TRYLOCK_SUCCESS: |
| *unlock = true; |
| return true; |
| |
| case MUTEX_TRYLOCK_RECURSIVE: |
| *unlock = false; |
| return true; |
| } |
| |
| BUG(); |
| } |
| |
| static unsigned long |
| msm_gem_shrinker_count(struct shrinker *shrinker, struct shrink_control *sc) |
| { |
| struct msm_drm_private *priv = |
| container_of(shrinker, struct msm_drm_private, shrinker); |
| struct drm_device *dev = priv->dev; |
| struct msm_gem_object *msm_obj; |
| unsigned long count = 0; |
| bool unlock; |
| |
| if (!msm_gem_shrinker_lock(dev, &unlock)) |
| return 0; |
| |
| list_for_each_entry(msm_obj, &priv->inactive_list, mm_list) { |
| if (is_purgeable(msm_obj)) |
| count += msm_obj->base.size >> PAGE_SHIFT; |
| } |
| |
| if (unlock) |
| mutex_unlock(&dev->struct_mutex); |
| |
| return count; |
| } |
| |
| static unsigned long |
| msm_gem_shrinker_scan(struct shrinker *shrinker, struct shrink_control *sc) |
| { |
| struct msm_drm_private *priv = |
| container_of(shrinker, struct msm_drm_private, shrinker); |
| struct drm_device *dev = priv->dev; |
| struct msm_gem_object *msm_obj; |
| unsigned long freed = 0; |
| bool unlock; |
| |
| if (!msm_gem_shrinker_lock(dev, &unlock)) |
| return SHRINK_STOP; |
| |
| list_for_each_entry(msm_obj, &priv->inactive_list, mm_list) { |
| if (freed >= sc->nr_to_scan) |
| break; |
| if (is_purgeable(msm_obj)) { |
| msm_gem_purge(&msm_obj->base, OBJ_LOCK_SHRINKER); |
| freed += msm_obj->base.size >> PAGE_SHIFT; |
| } |
| } |
| |
| if (unlock) |
| mutex_unlock(&dev->struct_mutex); |
| |
| if (freed > 0) |
| pr_info_ratelimited("Purging %lu bytes\n", freed << PAGE_SHIFT); |
| |
| return freed; |
| } |
| |
| static int |
| msm_gem_shrinker_vmap(struct notifier_block *nb, unsigned long event, void *ptr) |
| { |
| struct msm_drm_private *priv = |
| container_of(nb, struct msm_drm_private, vmap_notifier); |
| struct drm_device *dev = priv->dev; |
| struct msm_gem_object *msm_obj; |
| unsigned unmapped = 0; |
| bool unlock; |
| |
| if (!msm_gem_shrinker_lock(dev, &unlock)) |
| return NOTIFY_DONE; |
| |
| list_for_each_entry(msm_obj, &priv->inactive_list, mm_list) { |
| if (is_vunmapable(msm_obj)) { |
| msm_gem_vunmap(&msm_obj->base, OBJ_LOCK_SHRINKER); |
| /* since we don't know any better, lets bail after a few |
| * and if necessary the shrinker will be invoked again. |
| * Seems better than unmapping *everything* |
| */ |
| if (++unmapped >= 15) |
| break; |
| } |
| } |
| |
| if (unlock) |
| mutex_unlock(&dev->struct_mutex); |
| |
| *(unsigned long *)ptr += unmapped; |
| |
| if (unmapped > 0) |
| pr_info_ratelimited("Purging %u vmaps\n", unmapped); |
| |
| return NOTIFY_DONE; |
| } |
| |
| /** |
| * msm_gem_shrinker_init - Initialize msm shrinker |
| * @dev_priv: msm device |
| * |
| * This function registers and sets up the msm shrinker. |
| */ |
| void msm_gem_shrinker_init(struct drm_device *dev) |
| { |
| struct msm_drm_private *priv = dev->dev_private; |
| priv->shrinker.count_objects = msm_gem_shrinker_count; |
| priv->shrinker.scan_objects = msm_gem_shrinker_scan; |
| priv->shrinker.seeks = DEFAULT_SEEKS; |
| WARN_ON(register_shrinker(&priv->shrinker)); |
| |
| priv->vmap_notifier.notifier_call = msm_gem_shrinker_vmap; |
| WARN_ON(register_vmap_purge_notifier(&priv->vmap_notifier)); |
| } |
| |
| /** |
| * msm_gem_shrinker_cleanup - Clean up msm shrinker |
| * @dev_priv: msm device |
| * |
| * This function unregisters the msm shrinker. |
| */ |
| void msm_gem_shrinker_cleanup(struct drm_device *dev) |
| { |
| struct msm_drm_private *priv = dev->dev_private; |
| |
| if (priv->shrinker.nr_deferred) { |
| WARN_ON(unregister_vmap_purge_notifier(&priv->vmap_notifier)); |
| unregister_shrinker(&priv->shrinker); |
| } |
| } |