13d0407baSopenharmony_ci// SPDX-License-Identifier: GPL-2.0
23d0407baSopenharmony_ci/*
33d0407baSopenharmony_ci * Deferred dmabuf freeing helper
43d0407baSopenharmony_ci *
53d0407baSopenharmony_ci * Copyright (C) 2020 Linaro, Ltd.
63d0407baSopenharmony_ci *
73d0407baSopenharmony_ci * Based on the ION page pool code
83d0407baSopenharmony_ci * Copyright (C) 2011 Google, Inc.
93d0407baSopenharmony_ci */
103d0407baSopenharmony_ci
113d0407baSopenharmony_ci#include <linux/freezer.h>
123d0407baSopenharmony_ci#include <linux/list.h>
133d0407baSopenharmony_ci#include <linux/slab.h>
143d0407baSopenharmony_ci#include <linux/swap.h>
153d0407baSopenharmony_ci#include <linux/sched/signal.h>
163d0407baSopenharmony_ci
173d0407baSopenharmony_ci#include "deferred-free-helper.h"
183d0407baSopenharmony_ci
193d0407baSopenharmony_cistatic LIST_HEAD(free_list);
203d0407baSopenharmony_cistatic size_t list_nr_pages;
213d0407baSopenharmony_ciwait_queue_head_t freelist_waitqueue;
223d0407baSopenharmony_cistruct task_struct *freelist_task;
233d0407baSopenharmony_cistatic DEFINE_SPINLOCK(free_list_lock);
243d0407baSopenharmony_ci
253d0407baSopenharmony_civoid deferred_free(struct deferred_freelist_item *item,
263d0407baSopenharmony_ci		   void (*free)(struct deferred_freelist_item*,
273d0407baSopenharmony_ci				enum df_reason),
283d0407baSopenharmony_ci		   size_t nr_pages)
293d0407baSopenharmony_ci{
303d0407baSopenharmony_ci	unsigned long flags;
313d0407baSopenharmony_ci
323d0407baSopenharmony_ci	INIT_LIST_HEAD(&item->list);
333d0407baSopenharmony_ci	item->nr_pages = nr_pages;
343d0407baSopenharmony_ci	item->free = free;
353d0407baSopenharmony_ci
363d0407baSopenharmony_ci	spin_lock_irqsave(&free_list_lock, flags);
373d0407baSopenharmony_ci	list_add(&item->list, &free_list);
383d0407baSopenharmony_ci	list_nr_pages += nr_pages;
393d0407baSopenharmony_ci	spin_unlock_irqrestore(&free_list_lock, flags);
403d0407baSopenharmony_ci	wake_up(&freelist_waitqueue);
413d0407baSopenharmony_ci}
423d0407baSopenharmony_ciEXPORT_SYMBOL_GPL(deferred_free);
433d0407baSopenharmony_ci
443d0407baSopenharmony_cistatic size_t free_one_item(enum df_reason reason)
453d0407baSopenharmony_ci{
463d0407baSopenharmony_ci	unsigned long flags;
473d0407baSopenharmony_ci	size_t nr_pages;
483d0407baSopenharmony_ci	struct deferred_freelist_item *item;
493d0407baSopenharmony_ci
503d0407baSopenharmony_ci	spin_lock_irqsave(&free_list_lock, flags);
513d0407baSopenharmony_ci	if (list_empty(&free_list)) {
523d0407baSopenharmony_ci		spin_unlock_irqrestore(&free_list_lock, flags);
533d0407baSopenharmony_ci		return 0;
543d0407baSopenharmony_ci	}
553d0407baSopenharmony_ci	item = list_first_entry(&free_list, struct deferred_freelist_item, list);
563d0407baSopenharmony_ci	list_del(&item->list);
573d0407baSopenharmony_ci	nr_pages = item->nr_pages;
583d0407baSopenharmony_ci	list_nr_pages -= nr_pages;
593d0407baSopenharmony_ci	spin_unlock_irqrestore(&free_list_lock, flags);
603d0407baSopenharmony_ci
613d0407baSopenharmony_ci	item->free(item, reason);
623d0407baSopenharmony_ci	return nr_pages;
633d0407baSopenharmony_ci}
643d0407baSopenharmony_ci
653d0407baSopenharmony_ciunsigned long get_freelist_nr_pages(void)
663d0407baSopenharmony_ci{
673d0407baSopenharmony_ci	unsigned long nr_pages;
683d0407baSopenharmony_ci	unsigned long flags;
693d0407baSopenharmony_ci
703d0407baSopenharmony_ci	spin_lock_irqsave(&free_list_lock, flags);
713d0407baSopenharmony_ci	nr_pages = list_nr_pages;
723d0407baSopenharmony_ci	spin_unlock_irqrestore(&free_list_lock, flags);
733d0407baSopenharmony_ci	return nr_pages;
743d0407baSopenharmony_ci}
753d0407baSopenharmony_ciEXPORT_SYMBOL_GPL(get_freelist_nr_pages);
763d0407baSopenharmony_ci
773d0407baSopenharmony_cistatic unsigned long freelist_shrink_count(struct shrinker *shrinker,
783d0407baSopenharmony_ci					   struct shrink_control *sc)
793d0407baSopenharmony_ci{
803d0407baSopenharmony_ci	return get_freelist_nr_pages();
813d0407baSopenharmony_ci}
823d0407baSopenharmony_ci
833d0407baSopenharmony_cistatic unsigned long freelist_shrink_scan(struct shrinker *shrinker,
843d0407baSopenharmony_ci					  struct shrink_control *sc)
853d0407baSopenharmony_ci{
863d0407baSopenharmony_ci	unsigned long total_freed = 0;
873d0407baSopenharmony_ci
883d0407baSopenharmony_ci	if (sc->nr_to_scan == 0)
893d0407baSopenharmony_ci		return 0;
903d0407baSopenharmony_ci
913d0407baSopenharmony_ci	while (total_freed < sc->nr_to_scan) {
923d0407baSopenharmony_ci		size_t pages_freed = free_one_item(DF_UNDER_PRESSURE);
933d0407baSopenharmony_ci
943d0407baSopenharmony_ci		if (!pages_freed)
953d0407baSopenharmony_ci			break;
963d0407baSopenharmony_ci
973d0407baSopenharmony_ci		total_freed += pages_freed;
983d0407baSopenharmony_ci	}
993d0407baSopenharmony_ci
1003d0407baSopenharmony_ci	return total_freed;
1013d0407baSopenharmony_ci}
1023d0407baSopenharmony_ci
1033d0407baSopenharmony_cistatic struct shrinker freelist_shrinker = {
1043d0407baSopenharmony_ci	.count_objects = freelist_shrink_count,
1053d0407baSopenharmony_ci	.scan_objects = freelist_shrink_scan,
1063d0407baSopenharmony_ci	.seeks = DEFAULT_SEEKS,
1073d0407baSopenharmony_ci	.batch = 0,
1083d0407baSopenharmony_ci};
1093d0407baSopenharmony_ci
1103d0407baSopenharmony_cistatic int deferred_free_thread(void *data)
1113d0407baSopenharmony_ci{
1123d0407baSopenharmony_ci	while (true) {
1133d0407baSopenharmony_ci		wait_event_freezable(freelist_waitqueue,
1143d0407baSopenharmony_ci				     get_freelist_nr_pages() > 0);
1153d0407baSopenharmony_ci
1163d0407baSopenharmony_ci		free_one_item(DF_NORMAL);
1173d0407baSopenharmony_ci	}
1183d0407baSopenharmony_ci
1193d0407baSopenharmony_ci	return 0;
1203d0407baSopenharmony_ci}
1213d0407baSopenharmony_ci
1223d0407baSopenharmony_cistatic int deferred_freelist_init(void)
1233d0407baSopenharmony_ci{
1243d0407baSopenharmony_ci	list_nr_pages = 0;
1253d0407baSopenharmony_ci
1263d0407baSopenharmony_ci	init_waitqueue_head(&freelist_waitqueue);
1273d0407baSopenharmony_ci	freelist_task = kthread_run(deferred_free_thread, NULL,
1283d0407baSopenharmony_ci				    "%s", "dmabuf-deferred-free-worker");
1293d0407baSopenharmony_ci	if (IS_ERR(freelist_task)) {
1303d0407baSopenharmony_ci		pr_err("Creating thread for deferred free failed\n");
1313d0407baSopenharmony_ci		return -1;
1323d0407baSopenharmony_ci	}
1333d0407baSopenharmony_ci	sched_set_normal(freelist_task, 19);
1343d0407baSopenharmony_ci
1353d0407baSopenharmony_ci	return register_shrinker(&freelist_shrinker);
1363d0407baSopenharmony_ci}
1373d0407baSopenharmony_cimodule_init(deferred_freelist_init);
1383d0407baSopenharmony_ciMODULE_LICENSE("GPL v2");
1393d0407baSopenharmony_ci
140