Commit | Line | Data |
---|---|---|
c6603c74 DV |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Copyright (C) 2020 Intel | |
4 | * | |
5 | * Based on drivers/base/devres.c | |
6 | */ | |
7 | ||
8 | #include <drm/drm_managed.h> | |
9 | ||
10 | #include <linux/list.h> | |
11 | #include <linux/slab.h> | |
12 | #include <linux/spinlock.h> | |
13 | ||
14 | #include <drm/drm_device.h> | |
15 | #include <drm/drm_print.h> | |
16 | ||
3df6fad4 CW |
17 | #include "drm_internal.h" |
18 | ||
c6603c74 DV |
19 | /** |
20 | * DOC: managed resources | |
21 | * | |
22 | * Inspired by struct &device managed resources, but tied to the lifetime of | |
23 | * struct &drm_device, which can outlive the underlying physical device, usually | |
24 | * when userspace has some open files and other handles to resources still open. | |
9e1ed9fb DV |
25 | * |
26 | * Release actions can be added with drmm_add_action(), memory allocations can | |
27 | * be done directly with drmm_kmalloc() and the related functions. Everything | |
28 | * will be released on the final drm_dev_put() in reverse order of how the | |
29 | * release actions have been added and memory has been allocated since driver | |
4c8e84b8 | 30 | * loading started with devm_drm_dev_alloc(). |
9e1ed9fb DV |
31 | * |
32 | * Note that release actions and managed memory can also be added and removed | |
33 | * during the lifetime of the driver, all the functions are fully concurrent | |
34 | * safe. But it is recommended to use managed resources only for resources that | |
35 | * change rarely, if ever, during the lifetime of the &drm_device instance. | |
c6603c74 | 36 | */ |
9e1ed9fb | 37 | |
c6603c74 DV |
38 | struct drmres_node { |
39 | struct list_head entry; | |
40 | drmres_release_t release; | |
41 | const char *name; | |
42 | size_t size; | |
43 | }; | |
44 | ||
45 | struct drmres { | |
46 | struct drmres_node node; | |
47 | /* | |
48 | * Some archs want to perform DMA into kmalloc caches | |
49 | * and need a guaranteed alignment larger than | |
50 | * the alignment of a 64-bit integer. | |
51 | * Thus we use ARCH_KMALLOC_MINALIGN here and get exactly the same | |
52 | * buffer alignment as if it was allocated by plain kmalloc(). | |
53 | */ | |
54 | u8 __aligned(ARCH_KMALLOC_MINALIGN) data[]; | |
55 | }; | |
56 | ||
57 | static void free_dr(struct drmres *dr) | |
58 | { | |
59 | kfree_const(dr->node.name); | |
60 | kfree(dr); | |
61 | } | |
62 | ||
63 | void drm_managed_release(struct drm_device *dev) | |
64 | { | |
65 | struct drmres *dr, *tmp; | |
66 | ||
67 | drm_dbg_drmres(dev, "drmres release begin\n"); | |
68 | list_for_each_entry_safe(dr, tmp, &dev->managed.resources, node.entry) { | |
69 | drm_dbg_drmres(dev, "REL %p %s (%zu bytes)\n", | |
70 | dr, dr->node.name, dr->node.size); | |
71 | ||
72 | if (dr->node.release) | |
73 | dr->node.release(dev, dr->node.size ? *(void **)&dr->data : NULL); | |
74 | ||
75 | list_del(&dr->node.entry); | |
76 | free_dr(dr); | |
77 | } | |
78 | drm_dbg_drmres(dev, "drmres release end\n"); | |
79 | } | |
80 | ||
81 | /* | |
82 | * Always inline so that kmalloc_track_caller tracks the actual interesting | |
83 | * caller outside of drm_managed.c. | |
84 | */ | |
85 | static __always_inline struct drmres * alloc_dr(drmres_release_t release, | |
86 | size_t size, gfp_t gfp, int nid) | |
87 | { | |
88 | size_t tot_size; | |
89 | struct drmres *dr; | |
90 | ||
91 | /* We must catch any near-SIZE_MAX cases that could overflow. */ | |
92 | if (unlikely(check_add_overflow(sizeof(*dr), size, &tot_size))) | |
93 | return NULL; | |
94 | ||
95 | dr = kmalloc_node_track_caller(tot_size, gfp, nid); | |
96 | if (unlikely(!dr)) | |
97 | return NULL; | |
98 | ||
99 | memset(dr, 0, offsetof(struct drmres, data)); | |
100 | ||
101 | INIT_LIST_HEAD(&dr->node.entry); | |
102 | dr->node.release = release; | |
103 | dr->node.size = size; | |
104 | ||
105 | return dr; | |
106 | } | |
107 | ||
108 | static void del_dr(struct drm_device *dev, struct drmres *dr) | |
109 | { | |
110 | list_del_init(&dr->node.entry); | |
111 | ||
112 | drm_dbg_drmres(dev, "DEL %p %s (%lu bytes)\n", | |
113 | dr, dr->node.name, (unsigned long) dr->node.size); | |
114 | } | |
115 | ||
116 | static void add_dr(struct drm_device *dev, struct drmres *dr) | |
117 | { | |
118 | unsigned long flags; | |
119 | ||
120 | spin_lock_irqsave(&dev->managed.lock, flags); | |
121 | list_add(&dr->node.entry, &dev->managed.resources); | |
122 | spin_unlock_irqrestore(&dev->managed.lock, flags); | |
123 | ||
124 | drm_dbg_drmres(dev, "ADD %p %s (%lu bytes)\n", | |
125 | dr, dr->node.name, (unsigned long) dr->node.size); | |
126 | } | |
127 | ||
128 | void drmm_add_final_kfree(struct drm_device *dev, void *container) | |
129 | { | |
130 | WARN_ON(dev->managed.final_kfree); | |
131 | WARN_ON(dev < (struct drm_device *) container); | |
c7da606e | 132 | WARN_ON(dev + 1 > (struct drm_device *) (container + ksize(container))); |
c6603c74 DV |
133 | dev->managed.final_kfree = container; |
134 | } | |
c6603c74 DV |
135 | |
136 | int __drmm_add_action(struct drm_device *dev, | |
137 | drmres_release_t action, | |
138 | void *data, const char *name) | |
139 | { | |
140 | struct drmres *dr; | |
141 | void **void_ptr; | |
142 | ||
143 | dr = alloc_dr(action, data ? sizeof(void*) : 0, | |
144 | GFP_KERNEL | __GFP_ZERO, | |
145 | dev_to_node(dev->dev)); | |
146 | if (!dr) { | |
147 | drm_dbg_drmres(dev, "failed to add action %s for %p\n", | |
148 | name, data); | |
149 | return -ENOMEM; | |
150 | } | |
151 | ||
152 | dr->node.name = kstrdup_const(name, GFP_KERNEL); | |
153 | if (data) { | |
154 | void_ptr = (void **)&dr->data; | |
155 | *void_ptr = data; | |
156 | } | |
157 | ||
158 | add_dr(dev, dr); | |
159 | ||
160 | return 0; | |
161 | } | |
162 | EXPORT_SYMBOL(__drmm_add_action); | |
163 | ||
f96306f9 DV |
164 | int __drmm_add_action_or_reset(struct drm_device *dev, |
165 | drmres_release_t action, | |
166 | void *data, const char *name) | |
167 | { | |
168 | int ret; | |
169 | ||
170 | ret = __drmm_add_action(dev, action, data, name); | |
171 | if (ret) | |
172 | action(dev, data); | |
173 | ||
174 | return ret; | |
175 | } | |
176 | EXPORT_SYMBOL(__drmm_add_action_or_reset); | |
177 | ||
9e1ed9fb DV |
178 | /** |
179 | * drmm_kmalloc - &drm_device managed kmalloc() | |
180 | * @dev: DRM device | |
181 | * @size: size of the memory allocation | |
182 | * @gfp: GFP allocation flags | |
183 | * | |
184 | * This is a &drm_device managed version of kmalloc(). The allocated memory is | |
185 | * automatically freed on the final drm_dev_put(). Memory can also be freed | |
186 | * before the final drm_dev_put() by calling drmm_kfree(). | |
187 | */ | |
c6603c74 DV |
188 | void *drmm_kmalloc(struct drm_device *dev, size_t size, gfp_t gfp) |
189 | { | |
190 | struct drmres *dr; | |
191 | ||
192 | dr = alloc_dr(NULL, size, gfp, dev_to_node(dev->dev)); | |
193 | if (!dr) { | |
194 | drm_dbg_drmres(dev, "failed to allocate %zu bytes, %u flags\n", | |
195 | size, gfp); | |
196 | return NULL; | |
197 | } | |
198 | dr->node.name = kstrdup_const("kmalloc", GFP_KERNEL); | |
199 | ||
200 | add_dr(dev, dr); | |
201 | ||
202 | return dr->data; | |
203 | } | |
204 | EXPORT_SYMBOL(drmm_kmalloc); | |
205 | ||
9e1ed9fb DV |
206 | /** |
207 | * drmm_kstrdup - &drm_device managed kstrdup() | |
208 | * @dev: DRM device | |
209 | * @s: 0-terminated string to be duplicated | |
210 | * @gfp: GFP allocation flags | |
211 | * | |
212 | * This is a &drm_device managed version of kstrdup(). The allocated memory is | |
213 | * automatically freed on the final drm_dev_put() and works exactly like a | |
214 | * memory allocation obtained by drmm_kmalloc(). | |
215 | */ | |
a5c71fdb DV |
216 | char *drmm_kstrdup(struct drm_device *dev, const char *s, gfp_t gfp) |
217 | { | |
218 | size_t size; | |
219 | char *buf; | |
220 | ||
221 | if (!s) | |
222 | return NULL; | |
223 | ||
224 | size = strlen(s) + 1; | |
225 | buf = drmm_kmalloc(dev, size, gfp); | |
226 | if (buf) | |
227 | memcpy(buf, s, size); | |
228 | return buf; | |
229 | } | |
230 | EXPORT_SYMBOL_GPL(drmm_kstrdup); | |
231 | ||
9e1ed9fb DV |
232 | /** |
233 | * drmm_kfree - &drm_device managed kfree() | |
234 | * @dev: DRM device | |
235 | * @data: memory allocation to be freed | |
236 | * | |
237 | * This is a &drm_device managed version of kfree() which can be used to | |
238 | * release memory allocated through drmm_kmalloc() or any of its related | |
239 | * functions before the final drm_dev_put() of @dev. | |
240 | */ | |
c6603c74 DV |
241 | void drmm_kfree(struct drm_device *dev, void *data) |
242 | { | |
243 | struct drmres *dr_match = NULL, *dr; | |
244 | unsigned long flags; | |
245 | ||
246 | if (!data) | |
247 | return; | |
248 | ||
249 | spin_lock_irqsave(&dev->managed.lock, flags); | |
250 | list_for_each_entry(dr, &dev->managed.resources, node.entry) { | |
251 | if (dr->data == data) { | |
252 | dr_match = dr; | |
253 | del_dr(dev, dr_match); | |
254 | break; | |
255 | } | |
256 | } | |
257 | spin_unlock_irqrestore(&dev->managed.lock, flags); | |
258 | ||
259 | if (WARN_ON(!dr_match)) | |
260 | return; | |
261 | ||
262 | free_dr(dr_match); | |
263 | } | |
264 | EXPORT_SYMBOL(drmm_kfree); |