hc
2024-12-19 9370bb92b2d16684ee45cf24e879c93c509162da
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
// SPDX-License-Identifier: GPL-2.0
/*
 * Deferred dmabuf freeing helper
 *
 * Copyright (C) 2020 Linaro, Ltd.
 *
 * Based on the ION page pool code
 * Copyright (C) 2011 Google, Inc.
 */
 
#include <linux/freezer.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/swap.h>
#include <linux/sched/signal.h>
 
#include "deferred-free-helper.h"
 
static LIST_HEAD(free_list);
static size_t list_nr_pages;
wait_queue_head_t freelist_waitqueue;
struct task_struct *freelist_task;
static DEFINE_SPINLOCK(free_list_lock);
 
void deferred_free(struct deferred_freelist_item *item,
          void (*free)(struct deferred_freelist_item*,
               enum df_reason),
          size_t nr_pages)
{
   unsigned long flags;
 
   INIT_LIST_HEAD(&item->list);
   item->nr_pages = nr_pages;
   item->free = free;
 
   spin_lock_irqsave(&free_list_lock, flags);
   list_add(&item->list, &free_list);
   list_nr_pages += nr_pages;
   spin_unlock_irqrestore(&free_list_lock, flags);
   wake_up(&freelist_waitqueue);
}
EXPORT_SYMBOL_GPL(deferred_free);
 
static size_t free_one_item(enum df_reason reason)
{
   unsigned long flags;
   size_t nr_pages;
   struct deferred_freelist_item *item;
 
   spin_lock_irqsave(&free_list_lock, flags);
   if (list_empty(&free_list)) {
       spin_unlock_irqrestore(&free_list_lock, flags);
       return 0;
   }
   item = list_first_entry(&free_list, struct deferred_freelist_item, list);
   list_del(&item->list);
   nr_pages = item->nr_pages;
   list_nr_pages -= nr_pages;
   spin_unlock_irqrestore(&free_list_lock, flags);
 
   item->free(item, reason);
   return nr_pages;
}
 
unsigned long get_freelist_nr_pages(void)
{
   unsigned long nr_pages;
   unsigned long flags;
 
   spin_lock_irqsave(&free_list_lock, flags);
   nr_pages = list_nr_pages;
   spin_unlock_irqrestore(&free_list_lock, flags);
   return nr_pages;
}
EXPORT_SYMBOL_GPL(get_freelist_nr_pages);
 
static unsigned long freelist_shrink_count(struct shrinker *shrinker,
                      struct shrink_control *sc)
{
   return get_freelist_nr_pages();
}
 
static unsigned long freelist_shrink_scan(struct shrinker *shrinker,
                     struct shrink_control *sc)
{
   unsigned long total_freed = 0;
 
   if (sc->nr_to_scan == 0)
       return 0;
 
   while (total_freed < sc->nr_to_scan) {
       size_t pages_freed = free_one_item(DF_UNDER_PRESSURE);
 
       if (!pages_freed)
           break;
 
       total_freed += pages_freed;
   }
 
   return total_freed;
}
 
static struct shrinker freelist_shrinker = {
   .count_objects = freelist_shrink_count,
   .scan_objects = freelist_shrink_scan,
   .seeks = DEFAULT_SEEKS,
   .batch = 0,
};
 
static int deferred_free_thread(void *data)
{
   while (true) {
       wait_event_freezable(freelist_waitqueue,
                    get_freelist_nr_pages() > 0);
 
       free_one_item(DF_NORMAL);
   }
 
   return 0;
}
 
static int deferred_freelist_init(void)
{
   list_nr_pages = 0;
 
   init_waitqueue_head(&freelist_waitqueue);
   freelist_task = kthread_run(deferred_free_thread, NULL,
                   "%s", "dmabuf-deferred-free-worker");
   if (IS_ERR(freelist_task)) {
       pr_err("Creating thread for deferred free failed\n");
       return -1;
   }
   sched_set_normal(freelist_task, 19);
 
   return register_shrinker(&freelist_shrinker);
}
module_init(deferred_freelist_init);
MODULE_LICENSE("GPL v2");