1// SPDX-License-Identifier: GPL-2.0
2/* Copyright (C) 2019 Arm Ltd.
3 *
4 * Based on msm_gem_freedreno.c:
5 * Copyright (C) 2016 Red Hat
6 * Author: Rob Clark <robdclark@gmail.com>
7 */
8
9#include <linux/list.h>
10
11#include <drm/drm_device.h>
12#include <drm/drm_gem_shmem_helper.h>
13
14#include "panfrost_device.h"
15#include "panfrost_gem.h"
16#include "panfrost_mmu.h"
17
18static unsigned long
19panfrost_gem_shrinker_count(struct shrinker *shrinker, struct shrink_control *sc)
20{
21	struct panfrost_device *pfdev = shrinker->private_data;
22	struct drm_gem_shmem_object *shmem;
23	unsigned long count = 0;
24
25	if (!mutex_trylock(&pfdev->shrinker_lock))
26		return 0;
27
28	list_for_each_entry(shmem, &pfdev->shrinker_list, madv_list) {
29		if (drm_gem_shmem_is_purgeable(shmem))
30			count += shmem->base.size >> PAGE_SHIFT;
31	}
32
33	mutex_unlock(&pfdev->shrinker_lock);
34
35	return count;
36}
37
38static bool panfrost_gem_purge(struct drm_gem_object *obj)
39{
40	struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(obj);
41	struct panfrost_gem_object *bo = to_panfrost_bo(obj);
42	bool ret = false;
43
44	if (atomic_read(&bo->gpu_usecount))
45		return false;
46
47	if (!mutex_trylock(&bo->mappings.lock))
48		return false;
49
50	if (!dma_resv_trylock(shmem->base.resv))
51		goto unlock_mappings;
52
53	panfrost_gem_teardown_mappings_locked(bo);
54	drm_gem_shmem_purge(&bo->base);
55	ret = true;
56
57	dma_resv_unlock(shmem->base.resv);
58
59unlock_mappings:
60	mutex_unlock(&bo->mappings.lock);
61	return ret;
62}
63
64static unsigned long
65panfrost_gem_shrinker_scan(struct shrinker *shrinker, struct shrink_control *sc)
66{
67	struct panfrost_device *pfdev = shrinker->private_data;
68	struct drm_gem_shmem_object *shmem, *tmp;
69	unsigned long freed = 0;
70
71	if (!mutex_trylock(&pfdev->shrinker_lock))
72		return SHRINK_STOP;
73
74	list_for_each_entry_safe(shmem, tmp, &pfdev->shrinker_list, madv_list) {
75		if (freed >= sc->nr_to_scan)
76			break;
77		if (drm_gem_shmem_is_purgeable(shmem) &&
78		    panfrost_gem_purge(&shmem->base)) {
79			freed += shmem->base.size >> PAGE_SHIFT;
80			list_del_init(&shmem->madv_list);
81		}
82	}
83
84	mutex_unlock(&pfdev->shrinker_lock);
85
86	if (freed > 0)
87		pr_info_ratelimited("Purging %lu bytes\n", freed << PAGE_SHIFT);
88
89	return freed;
90}
91
92/**
93 * panfrost_gem_shrinker_init - Initialize panfrost shrinker
94 * @dev: DRM device
95 *
96 * This function registers and sets up the panfrost shrinker.
97 */
98int panfrost_gem_shrinker_init(struct drm_device *dev)
99{
100	struct panfrost_device *pfdev = dev->dev_private;
101
102	pfdev->shrinker = shrinker_alloc(0, "drm-panfrost");
103	if (!pfdev->shrinker)
104		return -ENOMEM;
105
106	pfdev->shrinker->count_objects = panfrost_gem_shrinker_count;
107	pfdev->shrinker->scan_objects = panfrost_gem_shrinker_scan;
108	pfdev->shrinker->private_data = pfdev;
109
110	shrinker_register(pfdev->shrinker);
111
112	return 0;
113}
114
115/**
116 * panfrost_gem_shrinker_cleanup - Clean up panfrost shrinker
117 * @dev: DRM device
118 *
119 * This function unregisters the panfrost shrinker.
120 */
121void panfrost_gem_shrinker_cleanup(struct drm_device *dev)
122{
123	struct panfrost_device *pfdev = dev->dev_private;
124
125	if (pfdev->shrinker)
126		shrinker_free(pfdev->shrinker);
127}
128