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