1 | // SPDX-License-Identifier: GPL-2.0 OR MIT |
2 | /************************************************************************** |
3 | * |
4 | * Copyright 2014-2022 VMware, Inc., Palo Alto, CA., USA |
5 | * |
6 | * Permission is hereby granted, free of charge, to any person obtaining a |
7 | * copy of this software and associated documentation files (the |
8 | * "Software"), to deal in the Software without restriction, including |
9 | * without limitation the rights to use, copy, modify, merge, publish, |
10 | * distribute, sub license, and/or sell copies of the Software, and to |
11 | * permit persons to whom the Software is furnished to do so, subject to |
12 | * the following conditions: |
13 | * |
14 | * The above copyright notice and this permission notice (including the |
15 | * next paragraph) shall be included in all copies or substantial portions |
16 | * of the Software. |
17 | * |
18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
20 | * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL |
21 | * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, |
22 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |
23 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE |
24 | * USE OR OTHER DEALINGS IN THE SOFTWARE. |
25 | * |
26 | **************************************************************************/ |
27 | |
28 | #include "vmwgfx_drv.h" |
29 | #include "vmwgfx_resource_priv.h" |
30 | |
31 | #include <linux/hashtable.h> |
32 | |
33 | #define VMW_CMDBUF_RES_MAN_HT_ORDER 12 |
34 | |
35 | /** |
36 | * struct vmw_cmdbuf_res - Command buffer managed resource entry. |
37 | * |
38 | * @res: Refcounted pointer to a struct vmw_resource. |
39 | * @hash: Hash entry for the manager hash table. |
40 | * @head: List head used either by the staging list or the manager list |
41 | * of committed resources. |
42 | * @state: Staging state of this resource entry. |
43 | * @man: Pointer to a resource manager for this entry. |
44 | */ |
45 | struct vmw_cmdbuf_res { |
46 | struct vmw_resource *res; |
47 | struct vmwgfx_hash_item hash; |
48 | struct list_head head; |
49 | enum vmw_cmdbuf_res_state state; |
50 | struct vmw_cmdbuf_res_manager *man; |
51 | }; |
52 | |
53 | /** |
54 | * struct vmw_cmdbuf_res_manager - Command buffer resource manager. |
55 | * |
56 | * @resources: Hash table containing staged and committed command buffer |
57 | * resources |
58 | * @list: List of committed command buffer resources. |
59 | * @dev_priv: Pointer to a device private structure. |
60 | * |
61 | * @resources and @list are protected by the cmdbuf mutex for now. |
62 | */ |
63 | struct vmw_cmdbuf_res_manager { |
64 | DECLARE_HASHTABLE(resources, VMW_CMDBUF_RES_MAN_HT_ORDER); |
65 | struct list_head list; |
66 | struct vmw_private *dev_priv; |
67 | }; |
68 | |
69 | |
70 | /** |
71 | * vmw_cmdbuf_res_lookup - Look up a command buffer resource |
72 | * |
73 | * @man: Pointer to the command buffer resource manager |
74 | * @res_type: The resource type, that combined with the user key |
75 | * identifies the resource. |
76 | * @user_key: The user key. |
77 | * |
78 | * Returns a valid refcounted struct vmw_resource pointer on success, |
79 | * an error pointer on failure. |
80 | */ |
81 | struct vmw_resource * |
82 | vmw_cmdbuf_res_lookup(struct vmw_cmdbuf_res_manager *man, |
83 | enum vmw_cmdbuf_res_type res_type, |
84 | u32 user_key) |
85 | { |
86 | struct vmwgfx_hash_item *hash; |
87 | unsigned long key = user_key | (res_type << 24); |
88 | |
89 | hash_for_each_possible_rcu(man->resources, hash, head, key) { |
90 | if (hash->key == key) |
91 | return hlist_entry(hash, struct vmw_cmdbuf_res, hash)->res; |
92 | } |
93 | return ERR_PTR(error: -EINVAL); |
94 | } |
95 | |
96 | /** |
97 | * vmw_cmdbuf_res_free - Free a command buffer resource. |
98 | * |
99 | * @man: Pointer to the command buffer resource manager |
100 | * @entry: Pointer to a struct vmw_cmdbuf_res. |
101 | * |
102 | * Frees a struct vmw_cmdbuf_res entry and drops its reference to the |
103 | * struct vmw_resource. |
104 | */ |
105 | static void vmw_cmdbuf_res_free(struct vmw_cmdbuf_res_manager *man, |
106 | struct vmw_cmdbuf_res *entry) |
107 | { |
108 | list_del(entry: &entry->head); |
109 | hash_del_rcu(node: &entry->hash.head); |
110 | vmw_resource_unreference(p_res: &entry->res); |
111 | kfree(objp: entry); |
112 | } |
113 | |
114 | /** |
115 | * vmw_cmdbuf_res_commit - Commit a list of command buffer resource actions |
116 | * |
117 | * @list: Caller's list of command buffer resource actions. |
118 | * |
119 | * This function commits a list of command buffer resource |
120 | * additions or removals. |
121 | * It is typically called when the execbuf ioctl call triggering these |
122 | * actions has committed the fifo contents to the device. |
123 | */ |
124 | void vmw_cmdbuf_res_commit(struct list_head *list) |
125 | { |
126 | struct vmw_cmdbuf_res *entry, *next; |
127 | |
128 | list_for_each_entry_safe(entry, next, list, head) { |
129 | list_del(entry: &entry->head); |
130 | if (entry->res->func->commit_notify) |
131 | entry->res->func->commit_notify(entry->res, |
132 | entry->state); |
133 | switch (entry->state) { |
134 | case VMW_CMDBUF_RES_ADD: |
135 | entry->state = VMW_CMDBUF_RES_COMMITTED; |
136 | list_add_tail(new: &entry->head, head: &entry->man->list); |
137 | break; |
138 | case VMW_CMDBUF_RES_DEL: |
139 | vmw_resource_unreference(p_res: &entry->res); |
140 | kfree(objp: entry); |
141 | break; |
142 | default: |
143 | BUG(); |
144 | break; |
145 | } |
146 | } |
147 | } |
148 | |
149 | /** |
150 | * vmw_cmdbuf_res_revert - Revert a list of command buffer resource actions |
151 | * |
152 | * @list: Caller's list of command buffer resource action |
153 | * |
154 | * This function reverts a list of command buffer resource |
155 | * additions or removals. |
156 | * It is typically called when the execbuf ioctl call triggering these |
157 | * actions failed for some reason, and the command stream was never |
158 | * submitted. |
159 | */ |
160 | void vmw_cmdbuf_res_revert(struct list_head *list) |
161 | { |
162 | struct vmw_cmdbuf_res *entry, *next; |
163 | |
164 | list_for_each_entry_safe(entry, next, list, head) { |
165 | switch (entry->state) { |
166 | case VMW_CMDBUF_RES_ADD: |
167 | vmw_cmdbuf_res_free(man: entry->man, entry); |
168 | break; |
169 | case VMW_CMDBUF_RES_DEL: |
170 | hash_add_rcu(entry->man->resources, &entry->hash.head, |
171 | entry->hash.key); |
172 | list_move_tail(list: &entry->head, head: &entry->man->list); |
173 | entry->state = VMW_CMDBUF_RES_COMMITTED; |
174 | break; |
175 | default: |
176 | BUG(); |
177 | break; |
178 | } |
179 | } |
180 | } |
181 | |
182 | /** |
183 | * vmw_cmdbuf_res_add - Stage a command buffer managed resource for addition. |
184 | * |
185 | * @man: Pointer to the command buffer resource manager. |
186 | * @res_type: The resource type. |
187 | * @user_key: The user-space id of the resource. |
188 | * @res: Valid (refcount != 0) pointer to a struct vmw_resource. |
189 | * @list: The staging list. |
190 | * |
191 | * This function allocates a struct vmw_cmdbuf_res entry and adds the |
192 | * resource to the hash table of the manager identified by @man. The |
193 | * entry is then put on the staging list identified by @list. |
194 | */ |
195 | int vmw_cmdbuf_res_add(struct vmw_cmdbuf_res_manager *man, |
196 | enum vmw_cmdbuf_res_type res_type, |
197 | u32 user_key, |
198 | struct vmw_resource *res, |
199 | struct list_head *list) |
200 | { |
201 | struct vmw_cmdbuf_res *cres; |
202 | |
203 | cres = kzalloc(size: sizeof(*cres), GFP_KERNEL); |
204 | if (unlikely(!cres)) |
205 | return -ENOMEM; |
206 | |
207 | cres->hash.key = user_key | (res_type << 24); |
208 | hash_add_rcu(man->resources, &cres->hash.head, cres->hash.key); |
209 | |
210 | cres->state = VMW_CMDBUF_RES_ADD; |
211 | cres->res = vmw_resource_reference(res); |
212 | cres->man = man; |
213 | list_add_tail(new: &cres->head, head: list); |
214 | |
215 | return 0; |
216 | } |
217 | |
218 | /** |
219 | * vmw_cmdbuf_res_remove - Stage a command buffer managed resource for removal. |
220 | * |
221 | * @man: Pointer to the command buffer resource manager. |
222 | * @res_type: The resource type. |
223 | * @user_key: The user-space id of the resource. |
224 | * @list: The staging list. |
225 | * @res_p: If the resource is in an already committed state, points to the |
226 | * struct vmw_resource on successful return. The pointer will be |
227 | * non ref-counted. |
228 | * |
229 | * This function looks up the struct vmw_cmdbuf_res entry from the manager |
230 | * hash table and, if it exists, removes it. Depending on its current staging |
231 | * state it then either removes the entry from the staging list or adds it |
232 | * to it with a staging state of removal. |
233 | */ |
234 | int vmw_cmdbuf_res_remove(struct vmw_cmdbuf_res_manager *man, |
235 | enum vmw_cmdbuf_res_type res_type, |
236 | u32 user_key, |
237 | struct list_head *list, |
238 | struct vmw_resource **res_p) |
239 | { |
240 | struct vmw_cmdbuf_res *entry = NULL; |
241 | struct vmwgfx_hash_item *hash; |
242 | unsigned long key = user_key | (res_type << 24); |
243 | |
244 | hash_for_each_possible_rcu(man->resources, hash, head, key) { |
245 | if (hash->key == key) { |
246 | entry = hlist_entry(hash, struct vmw_cmdbuf_res, hash); |
247 | break; |
248 | } |
249 | } |
250 | if (unlikely(!entry)) |
251 | return -EINVAL; |
252 | |
253 | switch (entry->state) { |
254 | case VMW_CMDBUF_RES_ADD: |
255 | vmw_cmdbuf_res_free(man, entry); |
256 | *res_p = NULL; |
257 | break; |
258 | case VMW_CMDBUF_RES_COMMITTED: |
259 | hash_del_rcu(node: &entry->hash.head); |
260 | list_del(entry: &entry->head); |
261 | entry->state = VMW_CMDBUF_RES_DEL; |
262 | list_add_tail(new: &entry->head, head: list); |
263 | *res_p = entry->res; |
264 | break; |
265 | default: |
266 | BUG(); |
267 | break; |
268 | } |
269 | |
270 | return 0; |
271 | } |
272 | |
273 | /** |
274 | * vmw_cmdbuf_res_man_create - Allocate a command buffer managed resource |
275 | * manager. |
276 | * |
277 | * @dev_priv: Pointer to a struct vmw_private |
278 | * |
279 | * Allocates and initializes a command buffer managed resource manager. Returns |
280 | * an error pointer on failure. |
281 | */ |
282 | struct vmw_cmdbuf_res_manager * |
283 | vmw_cmdbuf_res_man_create(struct vmw_private *dev_priv) |
284 | { |
285 | struct vmw_cmdbuf_res_manager *man; |
286 | |
287 | man = kzalloc(size: sizeof(*man), GFP_KERNEL); |
288 | if (!man) |
289 | return ERR_PTR(error: -ENOMEM); |
290 | |
291 | man->dev_priv = dev_priv; |
292 | INIT_LIST_HEAD(list: &man->list); |
293 | hash_init(man->resources); |
294 | return man; |
295 | } |
296 | |
297 | /** |
298 | * vmw_cmdbuf_res_man_destroy - Destroy a command buffer managed resource |
299 | * manager. |
300 | * |
301 | * @man: Pointer to the manager to destroy. |
302 | * |
303 | * This function destroys a command buffer managed resource manager and |
304 | * unreferences / frees all command buffer managed resources and -entries |
305 | * associated with it. |
306 | */ |
307 | void vmw_cmdbuf_res_man_destroy(struct vmw_cmdbuf_res_manager *man) |
308 | { |
309 | struct vmw_cmdbuf_res *entry, *next; |
310 | |
311 | list_for_each_entry_safe(entry, next, &man->list, head) |
312 | vmw_cmdbuf_res_free(man, entry); |
313 | |
314 | kfree(objp: man); |
315 | } |
316 | |
317 | |