Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
awilliam
GitHub Repository: awilliam/linux-vfio
Path: blob/master/drivers/gpu/drm/drm_mm.c
15111 views
1
/**************************************************************************
2
*
3
* Copyright 2006 Tungsten Graphics, Inc., Bismarck, ND., USA.
4
* All Rights Reserved.
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
29
/*
30
* Generic simple memory manager implementation. Intended to be used as a base
31
* class implementation for more advanced memory managers.
32
*
33
* Note that the algorithm used is quite simple and there might be substantial
34
* performance gains if a smarter free list is implemented. Currently it is just an
35
* unordered stack of free regions. This could easily be improved if an RB-tree
36
* is used instead. At least if we expect heavy fragmentation.
37
*
38
* Aligned allocations can also see improvement.
39
*
40
* Authors:
41
* Thomas Hellström <thomas-at-tungstengraphics-dot-com>
42
*/
43
44
#include "drmP.h"
45
#include "drm_mm.h"
46
#include <linux/slab.h>
47
#include <linux/seq_file.h>
48
49
#define MM_UNUSED_TARGET 4
50
51
static struct drm_mm_node *drm_mm_kmalloc(struct drm_mm *mm, int atomic)
52
{
53
struct drm_mm_node *child;
54
55
if (atomic)
56
child = kzalloc(sizeof(*child), GFP_ATOMIC);
57
else
58
child = kzalloc(sizeof(*child), GFP_KERNEL);
59
60
if (unlikely(child == NULL)) {
61
spin_lock(&mm->unused_lock);
62
if (list_empty(&mm->unused_nodes))
63
child = NULL;
64
else {
65
child =
66
list_entry(mm->unused_nodes.next,
67
struct drm_mm_node, node_list);
68
list_del(&child->node_list);
69
--mm->num_unused;
70
}
71
spin_unlock(&mm->unused_lock);
72
}
73
return child;
74
}
75
76
/* drm_mm_pre_get() - pre allocate drm_mm_node structure
77
* drm_mm: memory manager struct we are pre-allocating for
78
*
79
* Returns 0 on success or -ENOMEM if allocation fails.
80
*/
81
int drm_mm_pre_get(struct drm_mm *mm)
82
{
83
struct drm_mm_node *node;
84
85
spin_lock(&mm->unused_lock);
86
while (mm->num_unused < MM_UNUSED_TARGET) {
87
spin_unlock(&mm->unused_lock);
88
node = kzalloc(sizeof(*node), GFP_KERNEL);
89
spin_lock(&mm->unused_lock);
90
91
if (unlikely(node == NULL)) {
92
int ret = (mm->num_unused < 2) ? -ENOMEM : 0;
93
spin_unlock(&mm->unused_lock);
94
return ret;
95
}
96
++mm->num_unused;
97
list_add_tail(&node->node_list, &mm->unused_nodes);
98
}
99
spin_unlock(&mm->unused_lock);
100
return 0;
101
}
102
EXPORT_SYMBOL(drm_mm_pre_get);
103
104
static inline unsigned long drm_mm_hole_node_start(struct drm_mm_node *hole_node)
105
{
106
return hole_node->start + hole_node->size;
107
}
108
109
static inline unsigned long drm_mm_hole_node_end(struct drm_mm_node *hole_node)
110
{
111
struct drm_mm_node *next_node =
112
list_entry(hole_node->node_list.next, struct drm_mm_node,
113
node_list);
114
115
return next_node->start;
116
}
117
118
static void drm_mm_insert_helper(struct drm_mm_node *hole_node,
119
struct drm_mm_node *node,
120
unsigned long size, unsigned alignment)
121
{
122
struct drm_mm *mm = hole_node->mm;
123
unsigned long tmp = 0, wasted = 0;
124
unsigned long hole_start = drm_mm_hole_node_start(hole_node);
125
unsigned long hole_end = drm_mm_hole_node_end(hole_node);
126
127
BUG_ON(!hole_node->hole_follows || node->allocated);
128
129
if (alignment)
130
tmp = hole_start % alignment;
131
132
if (!tmp) {
133
hole_node->hole_follows = 0;
134
list_del_init(&hole_node->hole_stack);
135
} else
136
wasted = alignment - tmp;
137
138
node->start = hole_start + wasted;
139
node->size = size;
140
node->mm = mm;
141
node->allocated = 1;
142
143
INIT_LIST_HEAD(&node->hole_stack);
144
list_add(&node->node_list, &hole_node->node_list);
145
146
BUG_ON(node->start + node->size > hole_end);
147
148
if (node->start + node->size < hole_end) {
149
list_add(&node->hole_stack, &mm->hole_stack);
150
node->hole_follows = 1;
151
} else {
152
node->hole_follows = 0;
153
}
154
}
155
156
struct drm_mm_node *drm_mm_get_block_generic(struct drm_mm_node *hole_node,
157
unsigned long size,
158
unsigned alignment,
159
int atomic)
160
{
161
struct drm_mm_node *node;
162
163
node = drm_mm_kmalloc(hole_node->mm, atomic);
164
if (unlikely(node == NULL))
165
return NULL;
166
167
drm_mm_insert_helper(hole_node, node, size, alignment);
168
169
return node;
170
}
171
EXPORT_SYMBOL(drm_mm_get_block_generic);
172
173
/**
174
* Search for free space and insert a preallocated memory node. Returns
175
* -ENOSPC if no suitable free area is available. The preallocated memory node
176
* must be cleared.
177
*/
178
int drm_mm_insert_node(struct drm_mm *mm, struct drm_mm_node *node,
179
unsigned long size, unsigned alignment)
180
{
181
struct drm_mm_node *hole_node;
182
183
hole_node = drm_mm_search_free(mm, size, alignment, 0);
184
if (!hole_node)
185
return -ENOSPC;
186
187
drm_mm_insert_helper(hole_node, node, size, alignment);
188
189
return 0;
190
}
191
EXPORT_SYMBOL(drm_mm_insert_node);
192
193
static void drm_mm_insert_helper_range(struct drm_mm_node *hole_node,
194
struct drm_mm_node *node,
195
unsigned long size, unsigned alignment,
196
unsigned long start, unsigned long end)
197
{
198
struct drm_mm *mm = hole_node->mm;
199
unsigned long tmp = 0, wasted = 0;
200
unsigned long hole_start = drm_mm_hole_node_start(hole_node);
201
unsigned long hole_end = drm_mm_hole_node_end(hole_node);
202
203
BUG_ON(!hole_node->hole_follows || node->allocated);
204
205
if (hole_start < start)
206
wasted += start - hole_start;
207
if (alignment)
208
tmp = (hole_start + wasted) % alignment;
209
210
if (tmp)
211
wasted += alignment - tmp;
212
213
if (!wasted) {
214
hole_node->hole_follows = 0;
215
list_del_init(&hole_node->hole_stack);
216
}
217
218
node->start = hole_start + wasted;
219
node->size = size;
220
node->mm = mm;
221
node->allocated = 1;
222
223
INIT_LIST_HEAD(&node->hole_stack);
224
list_add(&node->node_list, &hole_node->node_list);
225
226
BUG_ON(node->start + node->size > hole_end);
227
BUG_ON(node->start + node->size > end);
228
229
if (node->start + node->size < hole_end) {
230
list_add(&node->hole_stack, &mm->hole_stack);
231
node->hole_follows = 1;
232
} else {
233
node->hole_follows = 0;
234
}
235
}
236
237
struct drm_mm_node *drm_mm_get_block_range_generic(struct drm_mm_node *hole_node,
238
unsigned long size,
239
unsigned alignment,
240
unsigned long start,
241
unsigned long end,
242
int atomic)
243
{
244
struct drm_mm_node *node;
245
246
node = drm_mm_kmalloc(hole_node->mm, atomic);
247
if (unlikely(node == NULL))
248
return NULL;
249
250
drm_mm_insert_helper_range(hole_node, node, size, alignment,
251
start, end);
252
253
return node;
254
}
255
EXPORT_SYMBOL(drm_mm_get_block_range_generic);
256
257
/**
258
* Search for free space and insert a preallocated memory node. Returns
259
* -ENOSPC if no suitable free area is available. This is for range
260
* restricted allocations. The preallocated memory node must be cleared.
261
*/
262
int drm_mm_insert_node_in_range(struct drm_mm *mm, struct drm_mm_node *node,
263
unsigned long size, unsigned alignment,
264
unsigned long start, unsigned long end)
265
{
266
struct drm_mm_node *hole_node;
267
268
hole_node = drm_mm_search_free_in_range(mm, size, alignment,
269
start, end, 0);
270
if (!hole_node)
271
return -ENOSPC;
272
273
drm_mm_insert_helper_range(hole_node, node, size, alignment,
274
start, end);
275
276
return 0;
277
}
278
EXPORT_SYMBOL(drm_mm_insert_node_in_range);
279
280
/**
281
* Remove a memory node from the allocator.
282
*/
283
void drm_mm_remove_node(struct drm_mm_node *node)
284
{
285
struct drm_mm *mm = node->mm;
286
struct drm_mm_node *prev_node;
287
288
BUG_ON(node->scanned_block || node->scanned_prev_free
289
|| node->scanned_next_free);
290
291
prev_node =
292
list_entry(node->node_list.prev, struct drm_mm_node, node_list);
293
294
if (node->hole_follows) {
295
BUG_ON(drm_mm_hole_node_start(node)
296
== drm_mm_hole_node_end(node));
297
list_del(&node->hole_stack);
298
} else
299
BUG_ON(drm_mm_hole_node_start(node)
300
!= drm_mm_hole_node_end(node));
301
302
if (!prev_node->hole_follows) {
303
prev_node->hole_follows = 1;
304
list_add(&prev_node->hole_stack, &mm->hole_stack);
305
} else
306
list_move(&prev_node->hole_stack, &mm->hole_stack);
307
308
list_del(&node->node_list);
309
node->allocated = 0;
310
}
311
EXPORT_SYMBOL(drm_mm_remove_node);
312
313
/*
314
* Remove a memory node from the allocator and free the allocated struct
315
* drm_mm_node. Only to be used on a struct drm_mm_node obtained by one of the
316
* drm_mm_get_block functions.
317
*/
318
void drm_mm_put_block(struct drm_mm_node *node)
319
{
320
321
struct drm_mm *mm = node->mm;
322
323
drm_mm_remove_node(node);
324
325
spin_lock(&mm->unused_lock);
326
if (mm->num_unused < MM_UNUSED_TARGET) {
327
list_add(&node->node_list, &mm->unused_nodes);
328
++mm->num_unused;
329
} else
330
kfree(node);
331
spin_unlock(&mm->unused_lock);
332
}
333
EXPORT_SYMBOL(drm_mm_put_block);
334
335
static int check_free_hole(unsigned long start, unsigned long end,
336
unsigned long size, unsigned alignment)
337
{
338
unsigned wasted = 0;
339
340
if (end - start < size)
341
return 0;
342
343
if (alignment) {
344
unsigned tmp = start % alignment;
345
if (tmp)
346
wasted = alignment - tmp;
347
}
348
349
if (end >= start + size + wasted) {
350
return 1;
351
}
352
353
return 0;
354
}
355
356
struct drm_mm_node *drm_mm_search_free(const struct drm_mm *mm,
357
unsigned long size,
358
unsigned alignment, int best_match)
359
{
360
struct drm_mm_node *entry;
361
struct drm_mm_node *best;
362
unsigned long best_size;
363
364
BUG_ON(mm->scanned_blocks);
365
366
best = NULL;
367
best_size = ~0UL;
368
369
list_for_each_entry(entry, &mm->hole_stack, hole_stack) {
370
BUG_ON(!entry->hole_follows);
371
if (!check_free_hole(drm_mm_hole_node_start(entry),
372
drm_mm_hole_node_end(entry),
373
size, alignment))
374
continue;
375
376
if (!best_match)
377
return entry;
378
379
if (entry->size < best_size) {
380
best = entry;
381
best_size = entry->size;
382
}
383
}
384
385
return best;
386
}
387
EXPORT_SYMBOL(drm_mm_search_free);
388
389
struct drm_mm_node *drm_mm_search_free_in_range(const struct drm_mm *mm,
390
unsigned long size,
391
unsigned alignment,
392
unsigned long start,
393
unsigned long end,
394
int best_match)
395
{
396
struct drm_mm_node *entry;
397
struct drm_mm_node *best;
398
unsigned long best_size;
399
400
BUG_ON(mm->scanned_blocks);
401
402
best = NULL;
403
best_size = ~0UL;
404
405
list_for_each_entry(entry, &mm->hole_stack, hole_stack) {
406
unsigned long adj_start = drm_mm_hole_node_start(entry) < start ?
407
start : drm_mm_hole_node_start(entry);
408
unsigned long adj_end = drm_mm_hole_node_end(entry) > end ?
409
end : drm_mm_hole_node_end(entry);
410
411
BUG_ON(!entry->hole_follows);
412
if (!check_free_hole(adj_start, adj_end, size, alignment))
413
continue;
414
415
if (!best_match)
416
return entry;
417
418
if (entry->size < best_size) {
419
best = entry;
420
best_size = entry->size;
421
}
422
}
423
424
return best;
425
}
426
EXPORT_SYMBOL(drm_mm_search_free_in_range);
427
428
/**
429
* Moves an allocation. To be used with embedded struct drm_mm_node.
430
*/
431
void drm_mm_replace_node(struct drm_mm_node *old, struct drm_mm_node *new)
432
{
433
list_replace(&old->node_list, &new->node_list);
434
list_replace(&old->hole_stack, &new->hole_stack);
435
new->hole_follows = old->hole_follows;
436
new->mm = old->mm;
437
new->start = old->start;
438
new->size = old->size;
439
440
old->allocated = 0;
441
new->allocated = 1;
442
}
443
EXPORT_SYMBOL(drm_mm_replace_node);
444
445
/**
446
* Initializa lru scanning.
447
*
448
* This simply sets up the scanning routines with the parameters for the desired
449
* hole.
450
*
451
* Warning: As long as the scan list is non-empty, no other operations than
452
* adding/removing nodes to/from the scan list are allowed.
453
*/
454
void drm_mm_init_scan(struct drm_mm *mm, unsigned long size,
455
unsigned alignment)
456
{
457
mm->scan_alignment = alignment;
458
mm->scan_size = size;
459
mm->scanned_blocks = 0;
460
mm->scan_hit_start = 0;
461
mm->scan_hit_size = 0;
462
mm->scan_check_range = 0;
463
mm->prev_scanned_node = NULL;
464
}
465
EXPORT_SYMBOL(drm_mm_init_scan);
466
467
/**
468
* Initializa lru scanning.
469
*
470
* This simply sets up the scanning routines with the parameters for the desired
471
* hole. This version is for range-restricted scans.
472
*
473
* Warning: As long as the scan list is non-empty, no other operations than
474
* adding/removing nodes to/from the scan list are allowed.
475
*/
476
void drm_mm_init_scan_with_range(struct drm_mm *mm, unsigned long size,
477
unsigned alignment,
478
unsigned long start,
479
unsigned long end)
480
{
481
mm->scan_alignment = alignment;
482
mm->scan_size = size;
483
mm->scanned_blocks = 0;
484
mm->scan_hit_start = 0;
485
mm->scan_hit_size = 0;
486
mm->scan_start = start;
487
mm->scan_end = end;
488
mm->scan_check_range = 1;
489
mm->prev_scanned_node = NULL;
490
}
491
EXPORT_SYMBOL(drm_mm_init_scan_with_range);
492
493
/**
494
* Add a node to the scan list that might be freed to make space for the desired
495
* hole.
496
*
497
* Returns non-zero, if a hole has been found, zero otherwise.
498
*/
499
int drm_mm_scan_add_block(struct drm_mm_node *node)
500
{
501
struct drm_mm *mm = node->mm;
502
struct drm_mm_node *prev_node;
503
unsigned long hole_start, hole_end;
504
unsigned long adj_start;
505
unsigned long adj_end;
506
507
mm->scanned_blocks++;
508
509
BUG_ON(node->scanned_block);
510
node->scanned_block = 1;
511
512
prev_node = list_entry(node->node_list.prev, struct drm_mm_node,
513
node_list);
514
515
node->scanned_preceeds_hole = prev_node->hole_follows;
516
prev_node->hole_follows = 1;
517
list_del(&node->node_list);
518
node->node_list.prev = &prev_node->node_list;
519
node->node_list.next = &mm->prev_scanned_node->node_list;
520
mm->prev_scanned_node = node;
521
522
hole_start = drm_mm_hole_node_start(prev_node);
523
hole_end = drm_mm_hole_node_end(prev_node);
524
if (mm->scan_check_range) {
525
adj_start = hole_start < mm->scan_start ?
526
mm->scan_start : hole_start;
527
adj_end = hole_end > mm->scan_end ?
528
mm->scan_end : hole_end;
529
} else {
530
adj_start = hole_start;
531
adj_end = hole_end;
532
}
533
534
if (check_free_hole(adj_start , adj_end,
535
mm->scan_size, mm->scan_alignment)) {
536
mm->scan_hit_start = hole_start;
537
mm->scan_hit_size = hole_end;
538
539
return 1;
540
}
541
542
return 0;
543
}
544
EXPORT_SYMBOL(drm_mm_scan_add_block);
545
546
/**
547
* Remove a node from the scan list.
548
*
549
* Nodes _must_ be removed in the exact same order from the scan list as they
550
* have been added, otherwise the internal state of the memory manager will be
551
* corrupted.
552
*
553
* When the scan list is empty, the selected memory nodes can be freed. An
554
* immediately following drm_mm_search_free with best_match = 0 will then return
555
* the just freed block (because its at the top of the free_stack list).
556
*
557
* Returns one if this block should be evicted, zero otherwise. Will always
558
* return zero when no hole has been found.
559
*/
560
int drm_mm_scan_remove_block(struct drm_mm_node *node)
561
{
562
struct drm_mm *mm = node->mm;
563
struct drm_mm_node *prev_node;
564
565
mm->scanned_blocks--;
566
567
BUG_ON(!node->scanned_block);
568
node->scanned_block = 0;
569
570
prev_node = list_entry(node->node_list.prev, struct drm_mm_node,
571
node_list);
572
573
prev_node->hole_follows = node->scanned_preceeds_hole;
574
INIT_LIST_HEAD(&node->node_list);
575
list_add(&node->node_list, &prev_node->node_list);
576
577
/* Only need to check for containement because start&size for the
578
* complete resulting free block (not just the desired part) is
579
* stored. */
580
if (node->start >= mm->scan_hit_start &&
581
node->start + node->size
582
<= mm->scan_hit_start + mm->scan_hit_size) {
583
return 1;
584
}
585
586
return 0;
587
}
588
EXPORT_SYMBOL(drm_mm_scan_remove_block);
589
590
int drm_mm_clean(struct drm_mm * mm)
591
{
592
struct list_head *head = &mm->head_node.node_list;
593
594
return (head->next->next == head);
595
}
596
EXPORT_SYMBOL(drm_mm_clean);
597
598
int drm_mm_init(struct drm_mm * mm, unsigned long start, unsigned long size)
599
{
600
INIT_LIST_HEAD(&mm->hole_stack);
601
INIT_LIST_HEAD(&mm->unused_nodes);
602
mm->num_unused = 0;
603
mm->scanned_blocks = 0;
604
spin_lock_init(&mm->unused_lock);
605
606
/* Clever trick to avoid a special case in the free hole tracking. */
607
INIT_LIST_HEAD(&mm->head_node.node_list);
608
INIT_LIST_HEAD(&mm->head_node.hole_stack);
609
mm->head_node.hole_follows = 1;
610
mm->head_node.scanned_block = 0;
611
mm->head_node.scanned_prev_free = 0;
612
mm->head_node.scanned_next_free = 0;
613
mm->head_node.mm = mm;
614
mm->head_node.start = start + size;
615
mm->head_node.size = start - mm->head_node.start;
616
list_add_tail(&mm->head_node.hole_stack, &mm->hole_stack);
617
618
return 0;
619
}
620
EXPORT_SYMBOL(drm_mm_init);
621
622
void drm_mm_takedown(struct drm_mm * mm)
623
{
624
struct drm_mm_node *entry, *next;
625
626
if (!list_empty(&mm->head_node.node_list)) {
627
DRM_ERROR("Memory manager not clean. Delaying takedown\n");
628
return;
629
}
630
631
spin_lock(&mm->unused_lock);
632
list_for_each_entry_safe(entry, next, &mm->unused_nodes, node_list) {
633
list_del(&entry->node_list);
634
kfree(entry);
635
--mm->num_unused;
636
}
637
spin_unlock(&mm->unused_lock);
638
639
BUG_ON(mm->num_unused != 0);
640
}
641
EXPORT_SYMBOL(drm_mm_takedown);
642
643
void drm_mm_debug_table(struct drm_mm *mm, const char *prefix)
644
{
645
struct drm_mm_node *entry;
646
unsigned long total_used = 0, total_free = 0, total = 0;
647
unsigned long hole_start, hole_end, hole_size;
648
649
hole_start = drm_mm_hole_node_start(&mm->head_node);
650
hole_end = drm_mm_hole_node_end(&mm->head_node);
651
hole_size = hole_end - hole_start;
652
if (hole_size)
653
printk(KERN_DEBUG "%s 0x%08lx-0x%08lx: %8lu: free\n",
654
prefix, hole_start, hole_end,
655
hole_size);
656
total_free += hole_size;
657
658
drm_mm_for_each_node(entry, mm) {
659
printk(KERN_DEBUG "%s 0x%08lx-0x%08lx: %8lu: used\n",
660
prefix, entry->start, entry->start + entry->size,
661
entry->size);
662
total_used += entry->size;
663
664
if (entry->hole_follows) {
665
hole_start = drm_mm_hole_node_start(entry);
666
hole_end = drm_mm_hole_node_end(entry);
667
hole_size = hole_end - hole_start;
668
printk(KERN_DEBUG "%s 0x%08lx-0x%08lx: %8lu: free\n",
669
prefix, hole_start, hole_end,
670
hole_size);
671
total_free += hole_size;
672
}
673
}
674
total = total_free + total_used;
675
676
printk(KERN_DEBUG "%s total: %lu, used %lu free %lu\n", prefix, total,
677
total_used, total_free);
678
}
679
EXPORT_SYMBOL(drm_mm_debug_table);
680
681
#if defined(CONFIG_DEBUG_FS)
682
int drm_mm_dump_table(struct seq_file *m, struct drm_mm *mm)
683
{
684
struct drm_mm_node *entry;
685
unsigned long total_used = 0, total_free = 0, total = 0;
686
unsigned long hole_start, hole_end, hole_size;
687
688
hole_start = drm_mm_hole_node_start(&mm->head_node);
689
hole_end = drm_mm_hole_node_end(&mm->head_node);
690
hole_size = hole_end - hole_start;
691
if (hole_size)
692
seq_printf(m, "0x%08lx-0x%08lx: 0x%08lx: free\n",
693
hole_start, hole_end, hole_size);
694
total_free += hole_size;
695
696
drm_mm_for_each_node(entry, mm) {
697
seq_printf(m, "0x%08lx-0x%08lx: 0x%08lx: used\n",
698
entry->start, entry->start + entry->size,
699
entry->size);
700
total_used += entry->size;
701
if (entry->hole_follows) {
702
hole_start = drm_mm_hole_node_start(entry);
703
hole_end = drm_mm_hole_node_end(entry);
704
hole_size = hole_end - hole_start;
705
seq_printf(m, "0x%08lx-0x%08lx: 0x%08lx: free\n",
706
hole_start, hole_end, hole_size);
707
total_free += hole_size;
708
}
709
}
710
total = total_free + total_used;
711
712
seq_printf(m, "total: %lu, used %lu free %lu\n", total, total_used, total_free);
713
return 0;
714
}
715
EXPORT_SYMBOL(drm_mm_dump_table);
716
#endif
717
718