Files
android_kernel_samsung_sm86…/qcom/opensource/graphics-kernel/kgsl_reclaim.c
David Wronek 880d405719 Add 'qcom/opensource/graphics-kernel/' from commit 'b4fdc4c04295ac59109ae19d64747522740c3f14'
git-subtree-dir: qcom/opensource/graphics-kernel
git-subtree-mainline: 992813d9c1
git-subtree-split: b4fdc4c042
Change-Id:
repo: https://git.codelinaro.org/clo/la/platform/vendor/qcom/opensource/graphics-kernel
tag: GRAPHICS.LA.14.0.r1-07700-lanai.0
2024-10-06 16:44:56 +02:00

462 lines
12 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2022-2024 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/kthread.h>
#include <linux/notifier.h>
#include <linux/pagevec.h>
#include <linux/shmem_fs.h>
#include <linux/swap.h>
#include <linux/version.h>
#include "kgsl_reclaim.h"
#include "kgsl_sharedmem.h"
#include "kgsl_trace.h"
/*
* Reclaiming excessive number of pages from a process will impact launch
* latency for the subsequent launch of the process. After measuring the
* launch latencies by having various maximum limits, it has been decided
* that allowing 30MB (7680 pages) of relcaim per process will have little
* impact and the latency will be within acceptable limit.
*/
static u32 kgsl_reclaim_max_page_limit = 7680;
/* Setting this to 0 means we reclaim pages as specified in shrinker call */
static u32 kgsl_nr_to_scan;
struct work_struct reclaim_work;
static atomic_t kgsl_nr_to_reclaim;
static int kgsl_memdesc_get_reclaimed_pages(struct kgsl_mem_entry *entry)
{
struct kgsl_memdesc *memdesc = &entry->memdesc;
int i, ret;
struct page *page;
for (i = 0; i < memdesc->page_count; i++) {
if (memdesc->pages[i])
continue;
page = shmem_read_mapping_page_gfp(
memdesc->shmem_filp->f_mapping, i, kgsl_gfp_mask(0));
if (IS_ERR(page))
return PTR_ERR(page);
kgsl_page_sync(memdesc->dev, page, PAGE_SIZE, DMA_BIDIRECTIONAL);
/*
* Update the pages array only if vmfault has not
* updated it meanwhile
*/
spin_lock(&memdesc->lock);
if (!memdesc->pages[i]) {
memdesc->pages[i] = page;
atomic_dec(&entry->priv->unpinned_page_count);
} else
put_page(page);
spin_unlock(&memdesc->lock);
}
ret = kgsl_mmu_map(memdesc->pagetable, memdesc);
if (ret)
return ret;
trace_kgsl_reclaim_memdesc(entry, false);
memdesc->priv &= ~KGSL_MEMDESC_RECLAIMED;
memdesc->priv &= ~KGSL_MEMDESC_SKIP_RECLAIM;
return 0;
}
int kgsl_reclaim_to_pinned_state(
struct kgsl_process_private *process)
{
struct kgsl_mem_entry *entry, *valid_entry;
int next = 0, ret = 0, count;
mutex_lock(&process->reclaim_lock);
if (test_bit(KGSL_PROC_PINNED_STATE, &process->state))
goto done;
count = atomic_read(&process->unpinned_page_count);
for ( ; ; ) {
valid_entry = NULL;
spin_lock(&process->mem_lock);
entry = idr_get_next(&process->mem_idr, &next);
if (entry == NULL) {
spin_unlock(&process->mem_lock);
break;
}
if (entry->memdesc.priv & KGSL_MEMDESC_RECLAIMED)
valid_entry = kgsl_mem_entry_get(entry);
spin_unlock(&process->mem_lock);
if (valid_entry) {
ret = kgsl_memdesc_get_reclaimed_pages(entry);
kgsl_mem_entry_put(entry);
if (ret)
goto done;
}
next++;
}
trace_kgsl_reclaim_process(process, count, false);
set_bit(KGSL_PROC_PINNED_STATE, &process->state);
done:
mutex_unlock(&process->reclaim_lock);
return ret;
}
static void kgsl_reclaim_foreground_work(struct work_struct *work)
{
struct kgsl_process_private *process =
container_of(work, struct kgsl_process_private, fg_work);
if (test_bit(KGSL_PROC_STATE, &process->state))
kgsl_reclaim_to_pinned_state(process);
kgsl_process_private_put(process);
}
static ssize_t kgsl_proc_state_show(struct kobject *kobj,
struct kgsl_process_attribute *attr, char *buf)
{
struct kgsl_process_private *process =
container_of(kobj, struct kgsl_process_private, kobj);
if (test_bit(KGSL_PROC_STATE, &process->state))
return scnprintf(buf, PAGE_SIZE, "foreground\n");
else
return scnprintf(buf, PAGE_SIZE, "background\n");
}
static ssize_t kgsl_proc_state_store(struct kobject *kobj,
struct kgsl_process_attribute *attr, const char *buf, ssize_t count)
{
struct kgsl_process_private *process =
container_of(kobj, struct kgsl_process_private, kobj);
if (sysfs_streq(buf, "foreground")) {
if (!test_and_set_bit(KGSL_PROC_STATE, &process->state) &&
kgsl_process_private_get(process))
kgsl_schedule_work(&process->fg_work);
} else if (sysfs_streq(buf, "background")) {
clear_bit(KGSL_PROC_STATE, &process->state);
} else
return -EINVAL;
return count;
}
static ssize_t gpumem_reclaimed_show(struct kobject *kobj,
struct kgsl_process_attribute *attr, char *buf)
{
struct kgsl_process_private *process =
container_of(kobj, struct kgsl_process_private, kobj);
return scnprintf(buf, PAGE_SIZE, "%d\n",
atomic_read(&process->unpinned_page_count) << PAGE_SHIFT);
}
PROCESS_ATTR(state, 0644, kgsl_proc_state_show, kgsl_proc_state_store);
PROCESS_ATTR(gpumem_reclaimed, 0444, gpumem_reclaimed_show, NULL);
static const struct attribute *proc_reclaim_attrs[] = {
&attr_state.attr,
&attr_gpumem_reclaimed.attr,
NULL,
};
void kgsl_reclaim_proc_sysfs_init(struct kgsl_process_private *process)
{
WARN_ON(sysfs_create_files(&process->kobj, proc_reclaim_attrs));
}
ssize_t kgsl_proc_max_reclaim_limit_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int ret;
ret = kstrtou32(buf, 0, &kgsl_reclaim_max_page_limit);
return ret ? ret : count;
}
ssize_t kgsl_proc_max_reclaim_limit_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return scnprintf(buf, PAGE_SIZE, "%d\n", kgsl_reclaim_max_page_limit);
}
ssize_t kgsl_nr_to_scan_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int ret;
ret = kstrtou32(buf, 0, &kgsl_nr_to_scan);
return ret ? ret : count;
}
ssize_t kgsl_nr_to_scan_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return scnprintf(buf, PAGE_SIZE, "%d\n", kgsl_nr_to_scan);
}
static void kgsl_release_page_vec(struct pagevec *pvec)
{
check_move_unevictable_pages(pvec);
__pagevec_release(pvec);
}
static u32 kgsl_reclaim_process(struct kgsl_process_private *process,
u32 pages_to_reclaim)
{
struct kgsl_memdesc *memdesc;
struct kgsl_mem_entry *entry, *valid_entry;
u32 next = 0, remaining = pages_to_reclaim;
/*
* If we do not get the lock here, it means that the buffers are
* being pinned back. So do not keep waiting here as we would anyway
* return empty handed once the lock is acquired.
*/
if (!mutex_trylock(&process->reclaim_lock))
return 0;
while (remaining) {
if (atomic_read(&process->unpinned_page_count) >=
kgsl_reclaim_max_page_limit)
break;
/* Abort reclaim if process submitted work. */
if (atomic_read(&process->cmd_count))
break;
/* Abort reclaim if process foreground hint is received. */
if (test_bit(KGSL_PROC_STATE, &process->state))
break;
valid_entry = NULL;
spin_lock(&process->mem_lock);
entry = idr_get_next(&process->mem_idr, &next);
if (entry == NULL) {
spin_unlock(&process->mem_lock);
break;
}
memdesc = &entry->memdesc;
if (!entry->pending_free &&
(memdesc->priv & KGSL_MEMDESC_CAN_RECLAIM) &&
!(memdesc->priv & KGSL_MEMDESC_RECLAIMED) &&
!(memdesc->priv & KGSL_MEMDESC_SKIP_RECLAIM))
valid_entry = kgsl_mem_entry_get(entry);
spin_unlock(&process->mem_lock);
if (!valid_entry) {
next++;
continue;
}
/* Do not reclaim pages mapped into a VBO */
if (atomic_read(&valid_entry->vbo_count)) {
kgsl_mem_entry_put(entry);
next++;
continue;
}
if ((atomic_read(&process->unpinned_page_count) +
memdesc->page_count) > kgsl_reclaim_max_page_limit) {
kgsl_mem_entry_put(entry);
next++;
continue;
}
if (memdesc->page_count > remaining) {
kgsl_mem_entry_put(entry);
next++;
continue;
}
if (!kgsl_mmu_unmap(memdesc->pagetable, memdesc)) {
int i;
struct pagevec pvec;
/*
* Pages that are first allocated are by default added to
* unevictable list. To reclaim them, we first clear the
* AS_UNEVICTABLE flag of the shmem file address space thus
* check_move_unevictable_pages() places them on the
* evictable list.
*
* Once reclaim is done, hint that further shmem allocations
* will have to be on the unevictable list.
*/
mapping_clear_unevictable(memdesc->shmem_filp->f_mapping);
pagevec_init(&pvec);
for (i = 0; i < memdesc->page_count; i++) {
set_page_dirty_lock(memdesc->pages[i]);
spin_lock(&memdesc->lock);
pagevec_add(&pvec, memdesc->pages[i]);
memdesc->pages[i] = NULL;
atomic_inc(&process->unpinned_page_count);
spin_unlock(&memdesc->lock);
if (pagevec_count(&pvec) == PAGEVEC_SIZE)
kgsl_release_page_vec(&pvec);
remaining--;
}
if (pagevec_count(&pvec))
kgsl_release_page_vec(&pvec);
reclaim_shmem_address_space(memdesc->shmem_filp->f_mapping);
mapping_set_unevictable(memdesc->shmem_filp->f_mapping);
memdesc->priv |= KGSL_MEMDESC_RECLAIMED;
trace_kgsl_reclaim_memdesc(entry, true);
}
kgsl_mem_entry_put(entry);
next++;
}
if (next)
clear_bit(KGSL_PROC_PINNED_STATE, &process->state);
trace_kgsl_reclaim_process(process, pages_to_reclaim - remaining, true);
mutex_unlock(&process->reclaim_lock);
return (pages_to_reclaim - remaining);
}
static void kgsl_reclaim_background_work(struct work_struct *work)
{
u32 bg_proc = 0, nr_pages = atomic_read(&kgsl_nr_to_reclaim);
u64 pp_nr_pages;
struct list_head kgsl_reclaim_process_list;
struct kgsl_process_private *process, *next;
INIT_LIST_HEAD(&kgsl_reclaim_process_list);
read_lock(&kgsl_driver.proclist_lock);
list_for_each_entry(process, &kgsl_driver.process_list, list) {
if (test_bit(KGSL_PROC_STATE, &process->state) ||
!kgsl_process_private_get(process))
continue;
bg_proc++;
list_add(&process->reclaim_list, &kgsl_reclaim_process_list);
}
read_unlock(&kgsl_driver.proclist_lock);
list_for_each_entry(process, &kgsl_reclaim_process_list, reclaim_list) {
if (!nr_pages)
break;
pp_nr_pages = nr_pages;
do_div(pp_nr_pages, bg_proc--);
nr_pages -= kgsl_reclaim_process(process, pp_nr_pages);
}
list_for_each_entry_safe(process, next,
&kgsl_reclaim_process_list, reclaim_list) {
list_del(&process->reclaim_list);
kgsl_process_private_put(process);
}
}
/* Shrinker callback functions */
static unsigned long
kgsl_reclaim_shrink_scan_objects(struct shrinker *shrinker,
struct shrink_control *sc)
{
if (!current_is_kswapd())
return 0;
atomic_set(&kgsl_nr_to_reclaim, kgsl_nr_to_scan ?
kgsl_nr_to_scan : sc->nr_to_scan);
kgsl_schedule_work(&reclaim_work);
return atomic_read(&kgsl_nr_to_reclaim);
}
static unsigned long
kgsl_reclaim_shrink_count_objects(struct shrinker *shrinker,
struct shrink_control *sc)
{
struct kgsl_process_private *process;
unsigned long count_reclaimable = 0;
if (!current_is_kswapd())
return 0;
read_lock(&kgsl_driver.proclist_lock);
list_for_each_entry(process, &kgsl_driver.process_list, list) {
if (!test_bit(KGSL_PROC_STATE, &process->state))
count_reclaimable += kgsl_reclaim_max_page_limit -
atomic_read(&process->unpinned_page_count);
}
read_unlock(&kgsl_driver.proclist_lock);
return count_reclaimable;
}
/* Shrinker callback data*/
static struct shrinker kgsl_reclaim_shrinker = {
.count_objects = kgsl_reclaim_shrink_count_objects,
.scan_objects = kgsl_reclaim_shrink_scan_objects,
.seeks = DEFAULT_SEEKS,
.batch = 0,
};
void kgsl_reclaim_proc_private_init(struct kgsl_process_private *process)
{
mutex_init(&process->reclaim_lock);
INIT_WORK(&process->fg_work, kgsl_reclaim_foreground_work);
set_bit(KGSL_PROC_PINNED_STATE, &process->state);
set_bit(KGSL_PROC_STATE, &process->state);
atomic_set(&process->unpinned_page_count, 0);
}
int kgsl_reclaim_start(void)
{
int ret;
/* Initialize shrinker */
#if (KERNEL_VERSION(6, 0, 0) <= LINUX_VERSION_CODE)
ret = register_shrinker(&kgsl_reclaim_shrinker, "kgsl_reclaim_shrinker");
#else
ret = register_shrinker(&kgsl_reclaim_shrinker);
#endif
if (ret)
pr_err("kgsl: reclaim: Failed to register shrinker\n");
return ret;
}
int kgsl_reclaim_init(void)
{
int ret = kgsl_reclaim_start();
if (ret)
return ret;
INIT_WORK(&reclaim_work, kgsl_reclaim_background_work);
return 0;
}
void kgsl_reclaim_close(void)
{
/* Unregister shrinker */
unregister_shrinker(&kgsl_reclaim_shrinker);
cancel_work_sync(&reclaim_work);
}