Rework xgi_(pcie|fb)_free_all to prevent deadlock.
[platform/upstream/libdrm.git] / linux-core / xgi_fb.c
1
2 /****************************************************************************
3  * Copyright (C) 2003-2006 by XGI Technology, Taiwan.                   
4  *                                                                                                                                                      *
5  * All Rights Reserved.                                                                                                         *
6  *                                                                                                                                                      *
7  * Permission is hereby granted, free of charge, to any person obtaining
8  * a copy of this software and associated documentation files (the      
9  * "Software"), to deal in the Software without restriction, including  
10  * without limitation on the rights to use, copy, modify, merge,        
11  * publish, distribute, sublicense, and/or sell copies of the Software, 
12  * and to permit persons to whom the Software is furnished to do so,    
13  * subject to the following conditions:                                 
14  *                                                                                                                                                      *
15  * The above copyright notice and this permission notice (including the 
16  * next paragraph) shall be included in all copies or substantial       
17  * portions of the Software.                                            
18  *                                                                                                                                                      *
19  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,      
20  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF   
21  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND                
22  * NON-INFRINGEMENT.  IN NO EVENT SHALL XGI AND/OR                      
23  * ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,           
24  * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,           
25  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER                        
26  * DEALINGS IN THE SOFTWARE.                                                                                            
27  ***************************************************************************/
28
29 #include "xgi_drv.h"
30
31 #define XGI_FB_HEAP_START 0x1000000
32
33 struct kmem_cache *xgi_mem_block_cache = NULL;
34
35 static struct xgi_mem_block *xgi_mem_new_node(void);
36
37
38 int xgi_mem_heap_init(struct xgi_mem_heap *heap, unsigned int start,
39                       unsigned int end)
40 {
41         struct xgi_mem_block *block;
42
43         INIT_LIST_HEAD(&heap->free_list);
44         INIT_LIST_HEAD(&heap->used_list);
45         INIT_LIST_HEAD(&heap->sort_list);
46         heap->initialized = TRUE;
47
48         block = kmem_cache_alloc(xgi_mem_block_cache, GFP_KERNEL);
49         if (!block) {
50                 return DRM_ERR(ENOMEM);
51         }
52
53         block->offset = start;
54         block->size = end - start;
55
56         list_add(&block->list, &heap->free_list);
57
58         heap->max_freesize = end - start;
59
60         return 0;
61 }
62
63
64 void xgi_mem_heap_cleanup(struct xgi_mem_heap * heap)
65 {
66         struct list_head *free_list;
67         struct xgi_mem_block *block;
68         struct xgi_mem_block *next;
69         int i;
70
71         free_list = &heap->free_list;
72         for (i = 0; i < 3; i++, free_list++) {
73                 list_for_each_entry_safe(block, next, free_list, list) {
74                         DRM_INFO
75                                 ("No. %d block->offset: 0x%lx block->size: 0x%lx \n",
76                                  i, block->offset, block->size);
77                         kmem_cache_free(xgi_mem_block_cache, block);
78                         block = NULL;
79                 }
80         }
81         
82         heap->initialized = 0;
83 }
84
85
86 struct xgi_mem_block *xgi_mem_new_node(void)
87 {
88         struct xgi_mem_block *block =
89                 kmem_cache_alloc(xgi_mem_block_cache, GFP_KERNEL);
90
91         if (!block) {
92                 DRM_ERROR("kmem_cache_alloc failed\n");
93                 return NULL;
94         }
95
96         block->offset = 0;
97         block->size = 0;
98         block->owner = PCIE_INVALID;
99         block->filp = (DRMFILE) -1;
100
101         return block;
102 }
103
104
105 struct xgi_mem_block *xgi_mem_alloc(struct xgi_mem_heap * heap,
106                                     unsigned long originalSize,
107                                     enum PcieOwner owner)
108 {
109         struct xgi_mem_block *block, *free_block, *used_block;
110         unsigned long size = (originalSize + PAGE_SIZE - 1) & PAGE_MASK;
111
112
113         DRM_INFO("Original 0x%lx bytes requested, really 0x%lx allocated\n",
114                  originalSize, size);
115
116         if (size == 0) {
117                 DRM_ERROR("size == 0\n");
118                 return (NULL);
119         }
120         DRM_INFO("max_freesize: 0x%lx \n", heap->max_freesize);
121         if (size > heap->max_freesize) {
122                 DRM_ERROR
123                     ("size: 0x%lx is bigger than frame buffer total free size: 0x%lx !\n",
124                      size, heap->max_freesize);
125                 return (NULL);
126         }
127
128         list_for_each_entry(block, &heap->free_list, list) {
129                 DRM_INFO("block: 0x%px \n", block);
130                 if (size <= block->size) {
131                         break;
132                 }
133         }
134
135         if (&block->list == &heap->free_list) {
136                 DRM_ERROR
137                     ("Can't allocate %ldk size from frame buffer memory !\n",
138                      size / 1024);
139                 return (NULL);
140         }
141
142         free_block = block;
143         DRM_INFO("alloc size: 0x%lx from offset: 0x%lx size: 0x%lx \n",
144                  size, free_block->offset, free_block->size);
145
146         if (size == free_block->size) {
147                 used_block = free_block;
148                 DRM_INFO("size == free_block->size: free_block = 0x%p\n",
149                          free_block);
150                 list_del(&free_block->list);
151         } else {
152                 used_block = xgi_mem_new_node();
153
154                 if (used_block == NULL)
155                         return (NULL);
156
157                 if (used_block == free_block) {
158                         DRM_ERROR("used_block == free_block = 0x%p\n",
159                                   used_block);
160                 }
161
162                 used_block->offset = free_block->offset;
163                 used_block->size = size;
164
165                 free_block->offset += size;
166                 free_block->size -= size;
167         }
168
169         heap->max_freesize -= size;
170
171         list_add(&used_block->list, &heap->used_list);
172         used_block->owner = owner;
173
174         return (used_block);
175 }
176
177 int xgi_mem_free(struct xgi_mem_heap * heap, unsigned long offset,
178                  DRMFILE filp)
179 {
180         struct xgi_mem_block *used_block = NULL, *block;
181         struct xgi_mem_block *prev, *next;
182
183         unsigned long upper;
184         unsigned long lower;
185
186         list_for_each_entry(block, &heap->used_list, list) {
187                 if (block->offset == offset) {
188                         break;
189                 }
190         }
191
192         if (&block->list == &heap->used_list) {
193                 DRM_ERROR("can't find block: 0x%lx to free!\n", offset);
194                 return DRM_ERR(ENOENT);
195         }
196
197         if (block->filp != filp) {
198                 return DRM_ERR(EPERM);
199         }
200
201         used_block = block;
202         DRM_INFO("used_block: 0x%p, offset = 0x%lx, size = 0x%lx\n",
203                  used_block, used_block->offset, used_block->size);
204
205         heap->max_freesize += used_block->size;
206
207         prev = next = NULL;
208         upper = used_block->offset + used_block->size;
209         lower = used_block->offset;
210
211         list_for_each_entry(block, &heap->free_list, list) {
212                 if (block->offset == upper) {
213                         next = block;
214                 } else if ((block->offset + block->size) == lower) {
215                         prev = block;
216                 }
217         }
218
219         DRM_INFO("next = 0x%p, prev = 0x%p\n", next, prev);
220         list_del(&used_block->list);
221
222         if (prev && next) {
223                 prev->size += (used_block->size + next->size);
224                 list_del(&next->list);
225                 DRM_INFO("free node 0x%p\n", next);
226                 kmem_cache_free(xgi_mem_block_cache, next);
227                 kmem_cache_free(xgi_mem_block_cache, used_block);
228         }
229         else if (prev) {
230                 prev->size += used_block->size;
231                 DRM_INFO("free node 0x%p\n", used_block);
232                 kmem_cache_free(xgi_mem_block_cache, used_block);
233         }
234         else if (next) {
235                 next->size += used_block->size;
236                 next->offset = used_block->offset;
237                 DRM_INFO("free node 0x%p\n", used_block);
238                 kmem_cache_free(xgi_mem_block_cache, used_block);
239         }
240         else {
241                 list_add(&used_block->list, &heap->free_list);
242                 DRM_INFO("Recycled free node %p, offset = 0x%lx, size = 0x%lx\n",
243                          used_block, used_block->offset, used_block->size);
244         }
245
246         return 0;
247 }
248
249
250 int xgi_fb_alloc(struct xgi_info * info, struct xgi_mem_alloc * alloc,
251                  DRMFILE filp)
252 {
253         struct xgi_mem_block *block;
254
255         if (alloc->is_front) {
256                 alloc->location = XGI_MEMLOC_LOCAL;
257                 alloc->offset = 0;
258                 alloc->hw_addr = 0;
259                 DRM_INFO
260                     ("Video RAM allocation on front buffer successfully! \n");
261         } else {
262                 down(&info->fb_sem);
263                 block = xgi_mem_alloc(&info->fb_heap, alloc->size, PCIE_2D);
264                 up(&info->fb_sem);
265
266                 if (block == NULL) {
267                         alloc->location = XGI_MEMLOC_LOCAL;
268                         alloc->size = 0;
269                         DRM_ERROR("Video RAM allocation failed\n");
270                         return DRM_ERR(ENOMEM);
271                 } else {
272                         DRM_INFO("Video RAM allocation succeeded: 0x%p\n",
273                                  (char *)block->offset);
274                         alloc->location = XGI_MEMLOC_LOCAL;
275                         alloc->size = block->size;
276                         alloc->offset = block->offset;
277                         alloc->hw_addr = block->offset;
278
279                         block->filp = filp;
280                 }
281         }
282
283         return 0;
284 }
285
286
287 int xgi_fb_alloc_ioctl(DRM_IOCTL_ARGS)
288 {
289         DRM_DEVICE;
290         struct xgi_mem_alloc alloc;
291         struct xgi_info *info = dev->dev_private;
292         int err;
293
294         DRM_COPY_FROM_USER_IOCTL(alloc, (struct xgi_mem_alloc __user *) data,
295                                  sizeof(alloc));
296
297         err = xgi_fb_alloc(info, & alloc, filp);
298         if (err) {
299                 return err;
300         }
301
302         DRM_COPY_TO_USER_IOCTL((struct xgi_mem_alloc __user *) data,
303                                alloc, sizeof(alloc));
304
305         return 0;
306 }
307
308
309 int xgi_fb_free(struct xgi_info * info, unsigned long offset, DRMFILE filp)
310 {
311         int err = 0;
312
313         if (offset == 0) {
314                 DRM_INFO("free onscreen frame buffer successfully !\n");
315         } else {
316                 down(&info->fb_sem);
317                 err = xgi_mem_free(&info->fb_heap, offset, filp);
318                 up(&info->fb_sem);
319         }
320
321         return err;
322 }
323
324
325 int xgi_fb_free_ioctl(DRM_IOCTL_ARGS)
326 {
327         DRM_DEVICE;
328         struct xgi_info *info = dev->dev_private;
329         u32 offset;
330
331         DRM_COPY_FROM_USER_IOCTL(offset, (unsigned long __user *) data,
332                                  sizeof(offset));
333
334         return xgi_fb_free(info, offset, filp);
335 }
336
337
338 int xgi_fb_heap_init(struct xgi_info * info)
339 {
340         return xgi_mem_heap_init(&info->fb_heap, XGI_FB_HEAP_START,
341                                  info->fb.size);
342 }
343
344 /**
345  * Free all blocks associated with a particular file handle.
346  */
347 void xgi_fb_free_all(struct xgi_info * info, DRMFILE filp)
348 {
349         if (!info->fb_heap.initialized) {
350                 return;
351         }
352
353         down(&info->fb_sem);
354
355         do {
356                 struct xgi_mem_block *block;
357
358                 list_for_each_entry(block, &info->fb_heap.used_list, list) {
359                         if (block->filp == filp) {
360                                 break;
361                         }
362                 }
363
364                 if (&block->list == &info->fb_heap.used_list) {
365                         break;
366                 }
367
368                 (void) xgi_mem_free(&info->fb_heap, block->offset, filp);
369         } while(1);
370
371         up(&info->fb_sem);
372 }