Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/drivers/gpu/drm/drm_buddy.c
26428 views
1
// SPDX-License-Identifier: MIT
2
/*
3
* Copyright © 2021 Intel Corporation
4
*/
5
6
#include <kunit/test-bug.h>
7
8
#include <linux/export.h>
9
#include <linux/kmemleak.h>
10
#include <linux/module.h>
11
#include <linux/sizes.h>
12
13
#include <drm/drm_buddy.h>
14
15
static struct kmem_cache *slab_blocks;
16
17
static struct drm_buddy_block *drm_block_alloc(struct drm_buddy *mm,
18
struct drm_buddy_block *parent,
19
unsigned int order,
20
u64 offset)
21
{
22
struct drm_buddy_block *block;
23
24
BUG_ON(order > DRM_BUDDY_MAX_ORDER);
25
26
block = kmem_cache_zalloc(slab_blocks, GFP_KERNEL);
27
if (!block)
28
return NULL;
29
30
block->header = offset;
31
block->header |= order;
32
block->parent = parent;
33
34
BUG_ON(block->header & DRM_BUDDY_HEADER_UNUSED);
35
return block;
36
}
37
38
static void drm_block_free(struct drm_buddy *mm,
39
struct drm_buddy_block *block)
40
{
41
kmem_cache_free(slab_blocks, block);
42
}
43
44
static void list_insert_sorted(struct drm_buddy *mm,
45
struct drm_buddy_block *block)
46
{
47
struct drm_buddy_block *node;
48
struct list_head *head;
49
50
head = &mm->free_list[drm_buddy_block_order(block)];
51
if (list_empty(head)) {
52
list_add(&block->link, head);
53
return;
54
}
55
56
list_for_each_entry(node, head, link)
57
if (drm_buddy_block_offset(block) < drm_buddy_block_offset(node))
58
break;
59
60
__list_add(&block->link, node->link.prev, &node->link);
61
}
62
63
static void clear_reset(struct drm_buddy_block *block)
64
{
65
block->header &= ~DRM_BUDDY_HEADER_CLEAR;
66
}
67
68
static void mark_cleared(struct drm_buddy_block *block)
69
{
70
block->header |= DRM_BUDDY_HEADER_CLEAR;
71
}
72
73
static void mark_allocated(struct drm_buddy_block *block)
74
{
75
block->header &= ~DRM_BUDDY_HEADER_STATE;
76
block->header |= DRM_BUDDY_ALLOCATED;
77
78
list_del(&block->link);
79
}
80
81
static void mark_free(struct drm_buddy *mm,
82
struct drm_buddy_block *block)
83
{
84
block->header &= ~DRM_BUDDY_HEADER_STATE;
85
block->header |= DRM_BUDDY_FREE;
86
87
list_insert_sorted(mm, block);
88
}
89
90
static void mark_split(struct drm_buddy_block *block)
91
{
92
block->header &= ~DRM_BUDDY_HEADER_STATE;
93
block->header |= DRM_BUDDY_SPLIT;
94
95
list_del(&block->link);
96
}
97
98
static inline bool overlaps(u64 s1, u64 e1, u64 s2, u64 e2)
99
{
100
return s1 <= e2 && e1 >= s2;
101
}
102
103
static inline bool contains(u64 s1, u64 e1, u64 s2, u64 e2)
104
{
105
return s1 <= s2 && e1 >= e2;
106
}
107
108
static struct drm_buddy_block *
109
__get_buddy(struct drm_buddy_block *block)
110
{
111
struct drm_buddy_block *parent;
112
113
parent = block->parent;
114
if (!parent)
115
return NULL;
116
117
if (parent->left == block)
118
return parent->right;
119
120
return parent->left;
121
}
122
123
static unsigned int __drm_buddy_free(struct drm_buddy *mm,
124
struct drm_buddy_block *block,
125
bool force_merge)
126
{
127
struct drm_buddy_block *parent;
128
unsigned int order;
129
130
while ((parent = block->parent)) {
131
struct drm_buddy_block *buddy;
132
133
buddy = __get_buddy(block);
134
135
if (!drm_buddy_block_is_free(buddy))
136
break;
137
138
if (!force_merge) {
139
/*
140
* Check the block and its buddy clear state and exit
141
* the loop if they both have the dissimilar state.
142
*/
143
if (drm_buddy_block_is_clear(block) !=
144
drm_buddy_block_is_clear(buddy))
145
break;
146
147
if (drm_buddy_block_is_clear(block))
148
mark_cleared(parent);
149
}
150
151
list_del(&buddy->link);
152
if (force_merge && drm_buddy_block_is_clear(buddy))
153
mm->clear_avail -= drm_buddy_block_size(mm, buddy);
154
155
drm_block_free(mm, block);
156
drm_block_free(mm, buddy);
157
158
block = parent;
159
}
160
161
order = drm_buddy_block_order(block);
162
mark_free(mm, block);
163
164
return order;
165
}
166
167
static int __force_merge(struct drm_buddy *mm,
168
u64 start,
169
u64 end,
170
unsigned int min_order)
171
{
172
unsigned int order;
173
int i;
174
175
if (!min_order)
176
return -ENOMEM;
177
178
if (min_order > mm->max_order)
179
return -EINVAL;
180
181
for (i = min_order - 1; i >= 0; i--) {
182
struct drm_buddy_block *block, *prev;
183
184
list_for_each_entry_safe_reverse(block, prev, &mm->free_list[i], link) {
185
struct drm_buddy_block *buddy;
186
u64 block_start, block_end;
187
188
if (!block->parent)
189
continue;
190
191
block_start = drm_buddy_block_offset(block);
192
block_end = block_start + drm_buddy_block_size(mm, block) - 1;
193
194
if (!contains(start, end, block_start, block_end))
195
continue;
196
197
buddy = __get_buddy(block);
198
if (!drm_buddy_block_is_free(buddy))
199
continue;
200
201
WARN_ON(drm_buddy_block_is_clear(block) ==
202
drm_buddy_block_is_clear(buddy));
203
204
/*
205
* If the prev block is same as buddy, don't access the
206
* block in the next iteration as we would free the
207
* buddy block as part of the free function.
208
*/
209
if (prev == buddy)
210
prev = list_prev_entry(prev, link);
211
212
list_del(&block->link);
213
if (drm_buddy_block_is_clear(block))
214
mm->clear_avail -= drm_buddy_block_size(mm, block);
215
216
order = __drm_buddy_free(mm, block, true);
217
if (order >= min_order)
218
return 0;
219
}
220
}
221
222
return -ENOMEM;
223
}
224
225
/**
226
* drm_buddy_init - init memory manager
227
*
228
* @mm: DRM buddy manager to initialize
229
* @size: size in bytes to manage
230
* @chunk_size: minimum page size in bytes for our allocations
231
*
232
* Initializes the memory manager and its resources.
233
*
234
* Returns:
235
* 0 on success, error code on failure.
236
*/
237
int drm_buddy_init(struct drm_buddy *mm, u64 size, u64 chunk_size)
238
{
239
unsigned int i;
240
u64 offset;
241
242
if (size < chunk_size)
243
return -EINVAL;
244
245
if (chunk_size < SZ_4K)
246
return -EINVAL;
247
248
if (!is_power_of_2(chunk_size))
249
return -EINVAL;
250
251
size = round_down(size, chunk_size);
252
253
mm->size = size;
254
mm->avail = size;
255
mm->clear_avail = 0;
256
mm->chunk_size = chunk_size;
257
mm->max_order = ilog2(size) - ilog2(chunk_size);
258
259
BUG_ON(mm->max_order > DRM_BUDDY_MAX_ORDER);
260
261
mm->free_list = kmalloc_array(mm->max_order + 1,
262
sizeof(struct list_head),
263
GFP_KERNEL);
264
if (!mm->free_list)
265
return -ENOMEM;
266
267
for (i = 0; i <= mm->max_order; ++i)
268
INIT_LIST_HEAD(&mm->free_list[i]);
269
270
mm->n_roots = hweight64(size);
271
272
mm->roots = kmalloc_array(mm->n_roots,
273
sizeof(struct drm_buddy_block *),
274
GFP_KERNEL);
275
if (!mm->roots)
276
goto out_free_list;
277
278
offset = 0;
279
i = 0;
280
281
/*
282
* Split into power-of-two blocks, in case we are given a size that is
283
* not itself a power-of-two.
284
*/
285
do {
286
struct drm_buddy_block *root;
287
unsigned int order;
288
u64 root_size;
289
290
order = ilog2(size) - ilog2(chunk_size);
291
root_size = chunk_size << order;
292
293
root = drm_block_alloc(mm, NULL, order, offset);
294
if (!root)
295
goto out_free_roots;
296
297
mark_free(mm, root);
298
299
BUG_ON(i > mm->max_order);
300
BUG_ON(drm_buddy_block_size(mm, root) < chunk_size);
301
302
mm->roots[i] = root;
303
304
offset += root_size;
305
size -= root_size;
306
i++;
307
} while (size);
308
309
return 0;
310
311
out_free_roots:
312
while (i--)
313
drm_block_free(mm, mm->roots[i]);
314
kfree(mm->roots);
315
out_free_list:
316
kfree(mm->free_list);
317
return -ENOMEM;
318
}
319
EXPORT_SYMBOL(drm_buddy_init);
320
321
/**
322
* drm_buddy_fini - tear down the memory manager
323
*
324
* @mm: DRM buddy manager to free
325
*
326
* Cleanup memory manager resources and the freelist
327
*/
328
void drm_buddy_fini(struct drm_buddy *mm)
329
{
330
u64 root_size, size, start;
331
unsigned int order;
332
int i;
333
334
size = mm->size;
335
336
for (i = 0; i < mm->n_roots; ++i) {
337
order = ilog2(size) - ilog2(mm->chunk_size);
338
start = drm_buddy_block_offset(mm->roots[i]);
339
__force_merge(mm, start, start + size, order);
340
341
if (WARN_ON(!drm_buddy_block_is_free(mm->roots[i])))
342
kunit_fail_current_test("buddy_fini() root");
343
344
drm_block_free(mm, mm->roots[i]);
345
346
root_size = mm->chunk_size << order;
347
size -= root_size;
348
}
349
350
WARN_ON(mm->avail != mm->size);
351
352
kfree(mm->roots);
353
kfree(mm->free_list);
354
}
355
EXPORT_SYMBOL(drm_buddy_fini);
356
357
static int split_block(struct drm_buddy *mm,
358
struct drm_buddy_block *block)
359
{
360
unsigned int block_order = drm_buddy_block_order(block) - 1;
361
u64 offset = drm_buddy_block_offset(block);
362
363
BUG_ON(!drm_buddy_block_is_free(block));
364
BUG_ON(!drm_buddy_block_order(block));
365
366
block->left = drm_block_alloc(mm, block, block_order, offset);
367
if (!block->left)
368
return -ENOMEM;
369
370
block->right = drm_block_alloc(mm, block, block_order,
371
offset + (mm->chunk_size << block_order));
372
if (!block->right) {
373
drm_block_free(mm, block->left);
374
return -ENOMEM;
375
}
376
377
mark_free(mm, block->left);
378
mark_free(mm, block->right);
379
380
if (drm_buddy_block_is_clear(block)) {
381
mark_cleared(block->left);
382
mark_cleared(block->right);
383
clear_reset(block);
384
}
385
386
mark_split(block);
387
388
return 0;
389
}
390
391
/**
392
* drm_get_buddy - get buddy address
393
*
394
* @block: DRM buddy block
395
*
396
* Returns the corresponding buddy block for @block, or NULL
397
* if this is a root block and can't be merged further.
398
* Requires some kind of locking to protect against
399
* any concurrent allocate and free operations.
400
*/
401
struct drm_buddy_block *
402
drm_get_buddy(struct drm_buddy_block *block)
403
{
404
return __get_buddy(block);
405
}
406
EXPORT_SYMBOL(drm_get_buddy);
407
408
/**
409
* drm_buddy_reset_clear - reset blocks clear state
410
*
411
* @mm: DRM buddy manager
412
* @is_clear: blocks clear state
413
*
414
* Reset the clear state based on @is_clear value for each block
415
* in the freelist.
416
*/
417
void drm_buddy_reset_clear(struct drm_buddy *mm, bool is_clear)
418
{
419
u64 root_size, size, start;
420
unsigned int order;
421
int i;
422
423
size = mm->size;
424
for (i = 0; i < mm->n_roots; ++i) {
425
order = ilog2(size) - ilog2(mm->chunk_size);
426
start = drm_buddy_block_offset(mm->roots[i]);
427
__force_merge(mm, start, start + size, order);
428
429
root_size = mm->chunk_size << order;
430
size -= root_size;
431
}
432
433
for (i = 0; i <= mm->max_order; ++i) {
434
struct drm_buddy_block *block;
435
436
list_for_each_entry_reverse(block, &mm->free_list[i], link) {
437
if (is_clear != drm_buddy_block_is_clear(block)) {
438
if (is_clear) {
439
mark_cleared(block);
440
mm->clear_avail += drm_buddy_block_size(mm, block);
441
} else {
442
clear_reset(block);
443
mm->clear_avail -= drm_buddy_block_size(mm, block);
444
}
445
}
446
}
447
}
448
}
449
EXPORT_SYMBOL(drm_buddy_reset_clear);
450
451
/**
452
* drm_buddy_free_block - free a block
453
*
454
* @mm: DRM buddy manager
455
* @block: block to be freed
456
*/
457
void drm_buddy_free_block(struct drm_buddy *mm,
458
struct drm_buddy_block *block)
459
{
460
BUG_ON(!drm_buddy_block_is_allocated(block));
461
mm->avail += drm_buddy_block_size(mm, block);
462
if (drm_buddy_block_is_clear(block))
463
mm->clear_avail += drm_buddy_block_size(mm, block);
464
465
__drm_buddy_free(mm, block, false);
466
}
467
EXPORT_SYMBOL(drm_buddy_free_block);
468
469
static void __drm_buddy_free_list(struct drm_buddy *mm,
470
struct list_head *objects,
471
bool mark_clear,
472
bool mark_dirty)
473
{
474
struct drm_buddy_block *block, *on;
475
476
WARN_ON(mark_dirty && mark_clear);
477
478
list_for_each_entry_safe(block, on, objects, link) {
479
if (mark_clear)
480
mark_cleared(block);
481
else if (mark_dirty)
482
clear_reset(block);
483
drm_buddy_free_block(mm, block);
484
cond_resched();
485
}
486
INIT_LIST_HEAD(objects);
487
}
488
489
static void drm_buddy_free_list_internal(struct drm_buddy *mm,
490
struct list_head *objects)
491
{
492
/*
493
* Don't touch the clear/dirty bit, since allocation is still internal
494
* at this point. For example we might have just failed part of the
495
* allocation.
496
*/
497
__drm_buddy_free_list(mm, objects, false, false);
498
}
499
500
/**
501
* drm_buddy_free_list - free blocks
502
*
503
* @mm: DRM buddy manager
504
* @objects: input list head to free blocks
505
* @flags: optional flags like DRM_BUDDY_CLEARED
506
*/
507
void drm_buddy_free_list(struct drm_buddy *mm,
508
struct list_head *objects,
509
unsigned int flags)
510
{
511
bool mark_clear = flags & DRM_BUDDY_CLEARED;
512
513
__drm_buddy_free_list(mm, objects, mark_clear, !mark_clear);
514
}
515
EXPORT_SYMBOL(drm_buddy_free_list);
516
517
static bool block_incompatible(struct drm_buddy_block *block, unsigned int flags)
518
{
519
bool needs_clear = flags & DRM_BUDDY_CLEAR_ALLOCATION;
520
521
return needs_clear != drm_buddy_block_is_clear(block);
522
}
523
524
static struct drm_buddy_block *
525
__alloc_range_bias(struct drm_buddy *mm,
526
u64 start, u64 end,
527
unsigned int order,
528
unsigned long flags,
529
bool fallback)
530
{
531
u64 req_size = mm->chunk_size << order;
532
struct drm_buddy_block *block;
533
struct drm_buddy_block *buddy;
534
LIST_HEAD(dfs);
535
int err;
536
int i;
537
538
end = end - 1;
539
540
for (i = 0; i < mm->n_roots; ++i)
541
list_add_tail(&mm->roots[i]->tmp_link, &dfs);
542
543
do {
544
u64 block_start;
545
u64 block_end;
546
547
block = list_first_entry_or_null(&dfs,
548
struct drm_buddy_block,
549
tmp_link);
550
if (!block)
551
break;
552
553
list_del(&block->tmp_link);
554
555
if (drm_buddy_block_order(block) < order)
556
continue;
557
558
block_start = drm_buddy_block_offset(block);
559
block_end = block_start + drm_buddy_block_size(mm, block) - 1;
560
561
if (!overlaps(start, end, block_start, block_end))
562
continue;
563
564
if (drm_buddy_block_is_allocated(block))
565
continue;
566
567
if (block_start < start || block_end > end) {
568
u64 adjusted_start = max(block_start, start);
569
u64 adjusted_end = min(block_end, end);
570
571
if (round_down(adjusted_end + 1, req_size) <=
572
round_up(adjusted_start, req_size))
573
continue;
574
}
575
576
if (!fallback && block_incompatible(block, flags))
577
continue;
578
579
if (contains(start, end, block_start, block_end) &&
580
order == drm_buddy_block_order(block)) {
581
/*
582
* Find the free block within the range.
583
*/
584
if (drm_buddy_block_is_free(block))
585
return block;
586
587
continue;
588
}
589
590
if (!drm_buddy_block_is_split(block)) {
591
err = split_block(mm, block);
592
if (unlikely(err))
593
goto err_undo;
594
}
595
596
list_add(&block->right->tmp_link, &dfs);
597
list_add(&block->left->tmp_link, &dfs);
598
} while (1);
599
600
return ERR_PTR(-ENOSPC);
601
602
err_undo:
603
/*
604
* We really don't want to leave around a bunch of split blocks, since
605
* bigger is better, so make sure we merge everything back before we
606
* free the allocated blocks.
607
*/
608
buddy = __get_buddy(block);
609
if (buddy &&
610
(drm_buddy_block_is_free(block) &&
611
drm_buddy_block_is_free(buddy)))
612
__drm_buddy_free(mm, block, false);
613
return ERR_PTR(err);
614
}
615
616
static struct drm_buddy_block *
617
__drm_buddy_alloc_range_bias(struct drm_buddy *mm,
618
u64 start, u64 end,
619
unsigned int order,
620
unsigned long flags)
621
{
622
struct drm_buddy_block *block;
623
bool fallback = false;
624
625
block = __alloc_range_bias(mm, start, end, order,
626
flags, fallback);
627
if (IS_ERR(block))
628
return __alloc_range_bias(mm, start, end, order,
629
flags, !fallback);
630
631
return block;
632
}
633
634
static struct drm_buddy_block *
635
get_maxblock(struct drm_buddy *mm, unsigned int order,
636
unsigned long flags)
637
{
638
struct drm_buddy_block *max_block = NULL, *block = NULL;
639
unsigned int i;
640
641
for (i = order; i <= mm->max_order; ++i) {
642
struct drm_buddy_block *tmp_block;
643
644
list_for_each_entry_reverse(tmp_block, &mm->free_list[i], link) {
645
if (block_incompatible(tmp_block, flags))
646
continue;
647
648
block = tmp_block;
649
break;
650
}
651
652
if (!block)
653
continue;
654
655
if (!max_block) {
656
max_block = block;
657
continue;
658
}
659
660
if (drm_buddy_block_offset(block) >
661
drm_buddy_block_offset(max_block)) {
662
max_block = block;
663
}
664
}
665
666
return max_block;
667
}
668
669
static struct drm_buddy_block *
670
alloc_from_freelist(struct drm_buddy *mm,
671
unsigned int order,
672
unsigned long flags)
673
{
674
struct drm_buddy_block *block = NULL;
675
unsigned int tmp;
676
int err;
677
678
if (flags & DRM_BUDDY_TOPDOWN_ALLOCATION) {
679
block = get_maxblock(mm, order, flags);
680
if (block)
681
/* Store the obtained block order */
682
tmp = drm_buddy_block_order(block);
683
} else {
684
for (tmp = order; tmp <= mm->max_order; ++tmp) {
685
struct drm_buddy_block *tmp_block;
686
687
list_for_each_entry_reverse(tmp_block, &mm->free_list[tmp], link) {
688
if (block_incompatible(tmp_block, flags))
689
continue;
690
691
block = tmp_block;
692
break;
693
}
694
695
if (block)
696
break;
697
}
698
}
699
700
if (!block) {
701
/* Fallback method */
702
for (tmp = order; tmp <= mm->max_order; ++tmp) {
703
if (!list_empty(&mm->free_list[tmp])) {
704
block = list_last_entry(&mm->free_list[tmp],
705
struct drm_buddy_block,
706
link);
707
if (block)
708
break;
709
}
710
}
711
712
if (!block)
713
return ERR_PTR(-ENOSPC);
714
}
715
716
BUG_ON(!drm_buddy_block_is_free(block));
717
718
while (tmp != order) {
719
err = split_block(mm, block);
720
if (unlikely(err))
721
goto err_undo;
722
723
block = block->right;
724
tmp--;
725
}
726
return block;
727
728
err_undo:
729
if (tmp != order)
730
__drm_buddy_free(mm, block, false);
731
return ERR_PTR(err);
732
}
733
734
static int __alloc_range(struct drm_buddy *mm,
735
struct list_head *dfs,
736
u64 start, u64 size,
737
struct list_head *blocks,
738
u64 *total_allocated_on_err)
739
{
740
struct drm_buddy_block *block;
741
struct drm_buddy_block *buddy;
742
u64 total_allocated = 0;
743
LIST_HEAD(allocated);
744
u64 end;
745
int err;
746
747
end = start + size - 1;
748
749
do {
750
u64 block_start;
751
u64 block_end;
752
753
block = list_first_entry_or_null(dfs,
754
struct drm_buddy_block,
755
tmp_link);
756
if (!block)
757
break;
758
759
list_del(&block->tmp_link);
760
761
block_start = drm_buddy_block_offset(block);
762
block_end = block_start + drm_buddy_block_size(mm, block) - 1;
763
764
if (!overlaps(start, end, block_start, block_end))
765
continue;
766
767
if (drm_buddy_block_is_allocated(block)) {
768
err = -ENOSPC;
769
goto err_free;
770
}
771
772
if (contains(start, end, block_start, block_end)) {
773
if (drm_buddy_block_is_free(block)) {
774
mark_allocated(block);
775
total_allocated += drm_buddy_block_size(mm, block);
776
mm->avail -= drm_buddy_block_size(mm, block);
777
if (drm_buddy_block_is_clear(block))
778
mm->clear_avail -= drm_buddy_block_size(mm, block);
779
list_add_tail(&block->link, &allocated);
780
continue;
781
} else if (!mm->clear_avail) {
782
err = -ENOSPC;
783
goto err_free;
784
}
785
}
786
787
if (!drm_buddy_block_is_split(block)) {
788
err = split_block(mm, block);
789
if (unlikely(err))
790
goto err_undo;
791
}
792
793
list_add(&block->right->tmp_link, dfs);
794
list_add(&block->left->tmp_link, dfs);
795
} while (1);
796
797
if (total_allocated < size) {
798
err = -ENOSPC;
799
goto err_free;
800
}
801
802
list_splice_tail(&allocated, blocks);
803
804
return 0;
805
806
err_undo:
807
/*
808
* We really don't want to leave around a bunch of split blocks, since
809
* bigger is better, so make sure we merge everything back before we
810
* free the allocated blocks.
811
*/
812
buddy = __get_buddy(block);
813
if (buddy &&
814
(drm_buddy_block_is_free(block) &&
815
drm_buddy_block_is_free(buddy)))
816
__drm_buddy_free(mm, block, false);
817
818
err_free:
819
if (err == -ENOSPC && total_allocated_on_err) {
820
list_splice_tail(&allocated, blocks);
821
*total_allocated_on_err = total_allocated;
822
} else {
823
drm_buddy_free_list_internal(mm, &allocated);
824
}
825
826
return err;
827
}
828
829
static int __drm_buddy_alloc_range(struct drm_buddy *mm,
830
u64 start,
831
u64 size,
832
u64 *total_allocated_on_err,
833
struct list_head *blocks)
834
{
835
LIST_HEAD(dfs);
836
int i;
837
838
for (i = 0; i < mm->n_roots; ++i)
839
list_add_tail(&mm->roots[i]->tmp_link, &dfs);
840
841
return __alloc_range(mm, &dfs, start, size,
842
blocks, total_allocated_on_err);
843
}
844
845
static int __alloc_contig_try_harder(struct drm_buddy *mm,
846
u64 size,
847
u64 min_block_size,
848
struct list_head *blocks)
849
{
850
u64 rhs_offset, lhs_offset, lhs_size, filled;
851
struct drm_buddy_block *block;
852
struct list_head *list;
853
LIST_HEAD(blocks_lhs);
854
unsigned long pages;
855
unsigned int order;
856
u64 modify_size;
857
int err;
858
859
modify_size = rounddown_pow_of_two(size);
860
pages = modify_size >> ilog2(mm->chunk_size);
861
order = fls(pages) - 1;
862
if (order == 0)
863
return -ENOSPC;
864
865
list = &mm->free_list[order];
866
if (list_empty(list))
867
return -ENOSPC;
868
869
list_for_each_entry_reverse(block, list, link) {
870
/* Allocate blocks traversing RHS */
871
rhs_offset = drm_buddy_block_offset(block);
872
err = __drm_buddy_alloc_range(mm, rhs_offset, size,
873
&filled, blocks);
874
if (!err || err != -ENOSPC)
875
return err;
876
877
lhs_size = max((size - filled), min_block_size);
878
if (!IS_ALIGNED(lhs_size, min_block_size))
879
lhs_size = round_up(lhs_size, min_block_size);
880
881
/* Allocate blocks traversing LHS */
882
lhs_offset = drm_buddy_block_offset(block) - lhs_size;
883
err = __drm_buddy_alloc_range(mm, lhs_offset, lhs_size,
884
NULL, &blocks_lhs);
885
if (!err) {
886
list_splice(&blocks_lhs, blocks);
887
return 0;
888
} else if (err != -ENOSPC) {
889
drm_buddy_free_list_internal(mm, blocks);
890
return err;
891
}
892
/* Free blocks for the next iteration */
893
drm_buddy_free_list_internal(mm, blocks);
894
}
895
896
return -ENOSPC;
897
}
898
899
/**
900
* drm_buddy_block_trim - free unused pages
901
*
902
* @mm: DRM buddy manager
903
* @start: start address to begin the trimming.
904
* @new_size: original size requested
905
* @blocks: Input and output list of allocated blocks.
906
* MUST contain single block as input to be trimmed.
907
* On success will contain the newly allocated blocks
908
* making up the @new_size. Blocks always appear in
909
* ascending order
910
*
911
* For contiguous allocation, we round up the size to the nearest
912
* power of two value, drivers consume *actual* size, so remaining
913
* portions are unused and can be optionally freed with this function
914
*
915
* Returns:
916
* 0 on success, error code on failure.
917
*/
918
int drm_buddy_block_trim(struct drm_buddy *mm,
919
u64 *start,
920
u64 new_size,
921
struct list_head *blocks)
922
{
923
struct drm_buddy_block *parent;
924
struct drm_buddy_block *block;
925
u64 block_start, block_end;
926
LIST_HEAD(dfs);
927
u64 new_start;
928
int err;
929
930
if (!list_is_singular(blocks))
931
return -EINVAL;
932
933
block = list_first_entry(blocks,
934
struct drm_buddy_block,
935
link);
936
937
block_start = drm_buddy_block_offset(block);
938
block_end = block_start + drm_buddy_block_size(mm, block);
939
940
if (WARN_ON(!drm_buddy_block_is_allocated(block)))
941
return -EINVAL;
942
943
if (new_size > drm_buddy_block_size(mm, block))
944
return -EINVAL;
945
946
if (!new_size || !IS_ALIGNED(new_size, mm->chunk_size))
947
return -EINVAL;
948
949
if (new_size == drm_buddy_block_size(mm, block))
950
return 0;
951
952
new_start = block_start;
953
if (start) {
954
new_start = *start;
955
956
if (new_start < block_start)
957
return -EINVAL;
958
959
if (!IS_ALIGNED(new_start, mm->chunk_size))
960
return -EINVAL;
961
962
if (range_overflows(new_start, new_size, block_end))
963
return -EINVAL;
964
}
965
966
list_del(&block->link);
967
mark_free(mm, block);
968
mm->avail += drm_buddy_block_size(mm, block);
969
if (drm_buddy_block_is_clear(block))
970
mm->clear_avail += drm_buddy_block_size(mm, block);
971
972
/* Prevent recursively freeing this node */
973
parent = block->parent;
974
block->parent = NULL;
975
976
list_add(&block->tmp_link, &dfs);
977
err = __alloc_range(mm, &dfs, new_start, new_size, blocks, NULL);
978
if (err) {
979
mark_allocated(block);
980
mm->avail -= drm_buddy_block_size(mm, block);
981
if (drm_buddy_block_is_clear(block))
982
mm->clear_avail -= drm_buddy_block_size(mm, block);
983
list_add(&block->link, blocks);
984
}
985
986
block->parent = parent;
987
return err;
988
}
989
EXPORT_SYMBOL(drm_buddy_block_trim);
990
991
static struct drm_buddy_block *
992
__drm_buddy_alloc_blocks(struct drm_buddy *mm,
993
u64 start, u64 end,
994
unsigned int order,
995
unsigned long flags)
996
{
997
if (flags & DRM_BUDDY_RANGE_ALLOCATION)
998
/* Allocate traversing within the range */
999
return __drm_buddy_alloc_range_bias(mm, start, end,
1000
order, flags);
1001
else
1002
/* Allocate from freelist */
1003
return alloc_from_freelist(mm, order, flags);
1004
}
1005
1006
/**
1007
* drm_buddy_alloc_blocks - allocate power-of-two blocks
1008
*
1009
* @mm: DRM buddy manager to allocate from
1010
* @start: start of the allowed range for this block
1011
* @end: end of the allowed range for this block
1012
* @size: size of the allocation in bytes
1013
* @min_block_size: alignment of the allocation
1014
* @blocks: output list head to add allocated blocks
1015
* @flags: DRM_BUDDY_*_ALLOCATION flags
1016
*
1017
* alloc_range_bias() called on range limitations, which traverses
1018
* the tree and returns the desired block.
1019
*
1020
* alloc_from_freelist() called when *no* range restrictions
1021
* are enforced, which picks the block from the freelist.
1022
*
1023
* Returns:
1024
* 0 on success, error code on failure.
1025
*/
1026
int drm_buddy_alloc_blocks(struct drm_buddy *mm,
1027
u64 start, u64 end, u64 size,
1028
u64 min_block_size,
1029
struct list_head *blocks,
1030
unsigned long flags)
1031
{
1032
struct drm_buddy_block *block = NULL;
1033
u64 original_size, original_min_size;
1034
unsigned int min_order, order;
1035
LIST_HEAD(allocated);
1036
unsigned long pages;
1037
int err;
1038
1039
if (size < mm->chunk_size)
1040
return -EINVAL;
1041
1042
if (min_block_size < mm->chunk_size)
1043
return -EINVAL;
1044
1045
if (!is_power_of_2(min_block_size))
1046
return -EINVAL;
1047
1048
if (!IS_ALIGNED(start | end | size, mm->chunk_size))
1049
return -EINVAL;
1050
1051
if (end > mm->size)
1052
return -EINVAL;
1053
1054
if (range_overflows(start, size, mm->size))
1055
return -EINVAL;
1056
1057
/* Actual range allocation */
1058
if (start + size == end) {
1059
if (!IS_ALIGNED(start | end, min_block_size))
1060
return -EINVAL;
1061
1062
return __drm_buddy_alloc_range(mm, start, size, NULL, blocks);
1063
}
1064
1065
original_size = size;
1066
original_min_size = min_block_size;
1067
1068
/* Roundup the size to power of 2 */
1069
if (flags & DRM_BUDDY_CONTIGUOUS_ALLOCATION) {
1070
size = roundup_pow_of_two(size);
1071
min_block_size = size;
1072
/* Align size value to min_block_size */
1073
} else if (!IS_ALIGNED(size, min_block_size)) {
1074
size = round_up(size, min_block_size);
1075
}
1076
1077
pages = size >> ilog2(mm->chunk_size);
1078
order = fls(pages) - 1;
1079
min_order = ilog2(min_block_size) - ilog2(mm->chunk_size);
1080
1081
do {
1082
order = min(order, (unsigned int)fls(pages) - 1);
1083
BUG_ON(order > mm->max_order);
1084
BUG_ON(order < min_order);
1085
1086
do {
1087
block = __drm_buddy_alloc_blocks(mm, start,
1088
end,
1089
order,
1090
flags);
1091
if (!IS_ERR(block))
1092
break;
1093
1094
if (order-- == min_order) {
1095
/* Try allocation through force merge method */
1096
if (mm->clear_avail &&
1097
!__force_merge(mm, start, end, min_order)) {
1098
block = __drm_buddy_alloc_blocks(mm, start,
1099
end,
1100
min_order,
1101
flags);
1102
if (!IS_ERR(block)) {
1103
order = min_order;
1104
break;
1105
}
1106
}
1107
1108
/*
1109
* Try contiguous block allocation through
1110
* try harder method.
1111
*/
1112
if (flags & DRM_BUDDY_CONTIGUOUS_ALLOCATION &&
1113
!(flags & DRM_BUDDY_RANGE_ALLOCATION))
1114
return __alloc_contig_try_harder(mm,
1115
original_size,
1116
original_min_size,
1117
blocks);
1118
err = -ENOSPC;
1119
goto err_free;
1120
}
1121
} while (1);
1122
1123
mark_allocated(block);
1124
mm->avail -= drm_buddy_block_size(mm, block);
1125
if (drm_buddy_block_is_clear(block))
1126
mm->clear_avail -= drm_buddy_block_size(mm, block);
1127
kmemleak_update_trace(block);
1128
list_add_tail(&block->link, &allocated);
1129
1130
pages -= BIT(order);
1131
1132
if (!pages)
1133
break;
1134
} while (1);
1135
1136
/* Trim the allocated block to the required size */
1137
if (!(flags & DRM_BUDDY_TRIM_DISABLE) &&
1138
original_size != size) {
1139
struct list_head *trim_list;
1140
LIST_HEAD(temp);
1141
u64 trim_size;
1142
1143
trim_list = &allocated;
1144
trim_size = original_size;
1145
1146
if (!list_is_singular(&allocated)) {
1147
block = list_last_entry(&allocated, typeof(*block), link);
1148
list_move(&block->link, &temp);
1149
trim_list = &temp;
1150
trim_size = drm_buddy_block_size(mm, block) -
1151
(size - original_size);
1152
}
1153
1154
drm_buddy_block_trim(mm,
1155
NULL,
1156
trim_size,
1157
trim_list);
1158
1159
if (!list_empty(&temp))
1160
list_splice_tail(trim_list, &allocated);
1161
}
1162
1163
list_splice_tail(&allocated, blocks);
1164
return 0;
1165
1166
err_free:
1167
drm_buddy_free_list_internal(mm, &allocated);
1168
return err;
1169
}
1170
EXPORT_SYMBOL(drm_buddy_alloc_blocks);
1171
1172
/**
1173
* drm_buddy_block_print - print block information
1174
*
1175
* @mm: DRM buddy manager
1176
* @block: DRM buddy block
1177
* @p: DRM printer to use
1178
*/
1179
void drm_buddy_block_print(struct drm_buddy *mm,
1180
struct drm_buddy_block *block,
1181
struct drm_printer *p)
1182
{
1183
u64 start = drm_buddy_block_offset(block);
1184
u64 size = drm_buddy_block_size(mm, block);
1185
1186
drm_printf(p, "%#018llx-%#018llx: %llu\n", start, start + size, size);
1187
}
1188
EXPORT_SYMBOL(drm_buddy_block_print);
1189
1190
/**
1191
* drm_buddy_print - print allocator state
1192
*
1193
* @mm: DRM buddy manager
1194
* @p: DRM printer to use
1195
*/
1196
void drm_buddy_print(struct drm_buddy *mm, struct drm_printer *p)
1197
{
1198
int order;
1199
1200
drm_printf(p, "chunk_size: %lluKiB, total: %lluMiB, free: %lluMiB, clear_free: %lluMiB\n",
1201
mm->chunk_size >> 10, mm->size >> 20, mm->avail >> 20, mm->clear_avail >> 20);
1202
1203
for (order = mm->max_order; order >= 0; order--) {
1204
struct drm_buddy_block *block;
1205
u64 count = 0, free;
1206
1207
list_for_each_entry(block, &mm->free_list[order], link) {
1208
BUG_ON(!drm_buddy_block_is_free(block));
1209
count++;
1210
}
1211
1212
drm_printf(p, "order-%2d ", order);
1213
1214
free = count * (mm->chunk_size << order);
1215
if (free < SZ_1M)
1216
drm_printf(p, "free: %8llu KiB", free >> 10);
1217
else
1218
drm_printf(p, "free: %8llu MiB", free >> 20);
1219
1220
drm_printf(p, ", blocks: %llu\n", count);
1221
}
1222
}
1223
EXPORT_SYMBOL(drm_buddy_print);
1224
1225
static void drm_buddy_module_exit(void)
1226
{
1227
kmem_cache_destroy(slab_blocks);
1228
}
1229
1230
static int __init drm_buddy_module_init(void)
1231
{
1232
slab_blocks = KMEM_CACHE(drm_buddy_block, 0);
1233
if (!slab_blocks)
1234
return -ENOMEM;
1235
1236
return 0;
1237
}
1238
1239
module_init(drm_buddy_module_init);
1240
module_exit(drm_buddy_module_exit);
1241
1242
MODULE_DESCRIPTION("DRM Buddy Allocator");
1243
MODULE_LICENSE("Dual MIT/GPL");
1244
1245