1pub mod page_meta;
9
10use core::ptr;
11
12pub use page_meta::{PFN_NONE, PageFlags, PageMeta};
13use page_meta::{free_list_push, free_list_remove};
14
15use crate::{
16 align_up, eii,
17 error::{AllocError, AllocResult},
18 is_aligned,
19};
20
21pub const MAX_ORDER: usize = 20;
23
24const DMA32_LIMIT: usize = 0x1_0000_0000;
26
27fn normalize_region(
28 region_start: usize,
29 region_size: usize,
30 granule: usize,
31) -> Option<(usize, usize)> {
32 if region_size == 0 || !granule.is_power_of_two() {
33 return None;
34 }
35 let region_end = region_start.checked_add(region_size)?;
36 let usable_start = align_up(region_start, granule);
37 let usable_end = region_end & !(granule - 1);
38 if usable_end <= usable_start {
39 return None;
40 }
41 Some((usable_start, usable_end - usable_start))
42}
43
44pub(crate) struct RegionLayout {
45 pub(crate) section_start: usize,
46 pub(crate) meta_start: usize,
47 pub(crate) managed_heap_start: usize,
48 pub(crate) managed_heap_size: usize,
49}
50
51pub(crate) struct SectionInitSpec {
52 pub(crate) region_start: usize,
53 pub(crate) region_size: usize,
54 pub(crate) section_ptr: *mut BuddySection,
55 pub(crate) meta_ptr: *mut u8,
56 pub(crate) meta_size: usize,
57 pub(crate) heap_start: usize,
58 pub(crate) heap_size: usize,
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub struct ManagedSection {
64 pub start: usize,
65 pub size: usize,
66 pub free_pages: usize,
67 pub total_pages: usize,
68}
69
70#[repr(C)]
72pub(crate) struct BuddySection {
73 pub(crate) next: *mut BuddySection,
74 pub(crate) region_start: usize,
75 pub(crate) region_size: usize,
76 pub(crate) meta: *mut PageMeta,
77 pub(crate) max_pages: usize,
78 pub(crate) heap_start: usize,
79 pub(crate) heap_size: usize,
80 pub(crate) free_lists: [u32; MAX_ORDER + 1],
81 pub(crate) free_pages: usize,
82 pub(crate) total_pages: usize,
83}
84
85impl BuddySection {
86 const fn metadata_align() -> usize {
87 let section_align = core::mem::align_of::<BuddySection>();
88 let meta_align = core::mem::align_of::<PageMeta>();
89 if section_align > meta_align {
90 section_align
91 } else {
92 meta_align
93 }
94 }
95
96 fn metadata_layout_for_pages(pages: usize) -> Option<(usize, usize)> {
97 let meta_offset = align_up(
98 core::mem::size_of::<BuddySection>(),
99 core::mem::align_of::<PageMeta>(),
100 );
101 let page_meta_size = pages.checked_mul(core::mem::size_of::<PageMeta>())?;
102 let meta_size = meta_offset.checked_add(page_meta_size)?;
103 Some((meta_offset, meta_size))
104 }
105
106 fn available_heap_pages<const PAGE_SIZE: usize>(
107 region_end: usize,
108 section_start: usize,
109 meta_size: usize,
110 heap_align: usize,
111 ) -> Option<usize> {
112 let managed_heap_start = align_up(section_start.checked_add(meta_size)?, heap_align);
113 if managed_heap_start > region_end {
114 return Some(0);
115 }
116 Some((region_end - managed_heap_start) / PAGE_SIZE)
117 }
118
119 fn can_manage_pages<const PAGE_SIZE: usize>(
120 region_end: usize,
121 section_start: usize,
122 pages: usize,
123 heap_align: usize,
124 ) -> bool {
125 let Some((_, meta_size)) = Self::metadata_layout_for_pages(pages) else {
126 return false;
127 };
128 let Some(available_pages) = Self::available_heap_pages::<PAGE_SIZE>(
129 region_end,
130 section_start,
131 meta_size,
132 heap_align,
133 ) else {
134 return false;
135 };
136 available_pages >= pages
137 }
138
139 pub(crate) fn compute_region_layout_with_heap_align<const PAGE_SIZE: usize>(
140 region_start: usize,
141 region_size: usize,
142 heap_align: usize,
143 ) -> Option<RegionLayout> {
144 if region_size == 0 || !PAGE_SIZE.is_power_of_two() || !heap_align.is_power_of_two() {
145 return None;
146 }
147
148 let region_end = region_start.checked_add(region_size)?;
149 let section_start = align_up(region_start, Self::metadata_align());
150 if section_start >= region_end {
151 return None;
152 }
153
154 let heap_search_start = align_up(
155 section_start.checked_add(core::mem::size_of::<BuddySection>())?,
156 PAGE_SIZE,
157 );
158 let max_pages = if heap_search_start >= region_end {
159 0
160 } else {
161 (region_end - heap_search_start) / PAGE_SIZE
162 };
163
164 let mut low = 0usize;
165 let mut high = max_pages;
166 while low < high {
167 let mid = low + (high - low).div_ceil(2);
168 if Self::can_manage_pages::<PAGE_SIZE>(region_end, section_start, mid, heap_align) {
169 low = mid;
170 } else {
171 high = mid - 1;
172 }
173 }
174
175 if low == 0 {
176 return None;
177 }
178
179 let (meta_offset, meta_size) = Self::metadata_layout_for_pages(low)?;
180 let meta_start = section_start.checked_add(meta_offset)?;
181 let managed_heap_start = align_up(section_start.checked_add(meta_size)?, heap_align);
182 let managed_heap_size = low.checked_mul(PAGE_SIZE)?;
183
184 Some(RegionLayout {
185 section_start,
186 meta_start,
187 managed_heap_start,
188 managed_heap_size,
189 })
190 }
191
192 fn compute_region_layout<const PAGE_SIZE: usize>(
193 region_start: usize,
194 region_size: usize,
195 ) -> Option<RegionLayout> {
196 Self::compute_region_layout_with_heap_align::<PAGE_SIZE>(
197 region_start,
198 region_size,
199 PAGE_SIZE,
200 )
201 }
202
203 unsafe fn init_at<const PAGE_SIZE: usize>(
204 section_ptr: *mut BuddySection,
205 region_start: usize,
206 region_size: usize,
207 meta_ptr: *mut u8,
208 meta_size: usize,
209 heap_start: usize,
210 heap_size: usize,
211 ) -> AllocResult {
212 unsafe {
213 if !PAGE_SIZE.is_power_of_two() {
214 return Err(AllocError::InvalidParam);
215 }
216 if !is_aligned(heap_start, PAGE_SIZE) || heap_size == 0 {
217 return Err(AllocError::InvalidParam);
218 }
219
220 let total_pages = heap_size / PAGE_SIZE;
221 let required = BuddyAllocator::<PAGE_SIZE>::required_meta_size(heap_size);
222 if meta_size < required {
223 return Err(AllocError::InvalidParam);
224 }
225
226 let meta = meta_ptr as *mut PageMeta;
227 for i in 0..total_pages {
228 meta.add(i).write(PageMeta::new());
229 }
230
231 section_ptr.write(BuddySection {
232 next: ptr::null_mut(),
233 region_start,
234 region_size,
235 meta,
236 max_pages: total_pages,
237 heap_start,
238 heap_size,
239 free_lists: [PFN_NONE; MAX_ORDER + 1],
240 free_pages: 0,
241 total_pages,
242 });
243
244 let section = &mut *section_ptr;
245 let mut pfn: usize = 0;
246 while pfn < total_pages {
247 let mut order = MAX_ORDER;
248 loop {
249 let block_pages = 1usize << order;
250 if block_pages <= total_pages - pfn && (pfn & (block_pages - 1)) == 0 {
251 break;
252 }
253 if order == 0 {
254 break;
255 }
256 order -= 1;
257 }
258 let block_pages = 1usize << order;
259 let m = &mut *section.meta.add(pfn);
260 m.flags = PageFlags::Free;
261 m.order = order as u8;
262 free_list_push(section.meta, &mut section.free_lists, pfn as u32, order);
263 section.free_pages += block_pages;
264 pfn += block_pages;
265 }
266
267 Ok(())
268 }
269 }
270
271 #[inline]
272 fn contains_heap_addr(&self, addr: usize) -> bool {
273 addr >= self.heap_start && addr < self.heap_start + self.heap_size
274 }
275
276 #[inline]
277 fn summary(&self) -> ManagedSection {
278 ManagedSection {
279 start: self.heap_start,
280 size: self.heap_size,
281 free_pages: self.free_pages,
282 total_pages: self.total_pages,
283 }
284 }
285}
286
287pub struct BuddyAllocator<const PAGE_SIZE: usize = 0x1000> {
291 sections_head: *mut BuddySection,
292 sections_tail: *mut BuddySection,
293 section_count: usize,
294}
295
296unsafe impl<const PAGE_SIZE: usize> Send for BuddyAllocator<PAGE_SIZE> {}
299
300impl<const PAGE_SIZE: usize> BuddyAllocator<PAGE_SIZE> {
301 pub const fn required_meta_size(heap_size: usize) -> usize {
303 let pages = heap_size / PAGE_SIZE;
304 pages * core::mem::size_of::<PageMeta>()
305 }
306
307 pub const fn new() -> Self {
309 Self {
310 sections_head: ptr::null_mut(),
311 sections_tail: ptr::null_mut(),
312 section_count: 0,
313 }
314 }
315}
316
317impl<const PAGE_SIZE: usize> Default for BuddyAllocator<PAGE_SIZE> {
318 fn default() -> Self {
319 Self::new()
320 }
321}
322
323impl<const PAGE_SIZE: usize> BuddyAllocator<PAGE_SIZE> {
324 pub(crate) fn reset(&mut self) {
325 self.sections_head = ptr::null_mut();
326 self.sections_tail = ptr::null_mut();
327 self.section_count = 0;
328 }
329
330 pub unsafe fn init(&mut self, region: &mut [u8]) -> AllocResult {
336 unsafe {
337 self.reset();
338 self.add_region(region)
339 }
340 }
341
342 pub unsafe fn add_region(&mut self, region: &mut [u8]) -> AllocResult {
348 unsafe {
349 let region_start = region.as_mut_ptr() as usize;
350 let region_size = region.len();
351 let (region_start, region_size) =
352 normalize_region(region_start, region_size, PAGE_SIZE)
353 .ok_or(AllocError::InvalidParam)?;
354 let layout =
355 BuddySection::compute_region_layout::<PAGE_SIZE>(region_start, region_size)
356 .ok_or(AllocError::InvalidParam)?;
357 self.add_region_raw(SectionInitSpec {
358 region_start,
359 region_size,
360 section_ptr: layout.section_start as *mut BuddySection,
361 meta_ptr: layout.meta_start as *mut u8,
362 meta_size: Self::required_meta_size(layout.managed_heap_size),
363 heap_start: layout.managed_heap_start,
364 heap_size: layout.managed_heap_size,
365 })
366 }
367 }
368
369 pub(crate) unsafe fn add_region_raw(&mut self, spec: SectionInitSpec) -> AllocResult {
370 unsafe {
371 let region_size = spec.region_size;
372 let region_end = spec
373 .region_start
374 .checked_add(region_size)
375 .ok_or(AllocError::InvalidParam)?;
376 let heap_end = spec
377 .heap_start
378 .checked_add(spec.heap_size)
379 .ok_or(AllocError::InvalidParam)?;
380 if heap_end > region_end {
381 return Err(AllocError::InvalidParam);
382 }
383
384 let mut section = self.sections_head;
385 while !section.is_null() {
386 let existing = &*section;
387 let existing_end = existing
388 .region_start
389 .checked_add(existing.region_size)
390 .ok_or(AllocError::InvalidParam)?;
391 if spec.region_start < existing_end && existing.region_start < region_end {
392 return Err(AllocError::MemoryOverlap);
393 }
394 section = existing.next;
395 }
396
397 BuddySection::init_at::<PAGE_SIZE>(
398 spec.section_ptr,
399 spec.region_start,
400 spec.region_size,
401 spec.meta_ptr,
402 spec.meta_size,
403 spec.heap_start,
404 spec.heap_size,
405 )?;
406
407 if self.sections_head.is_null() {
408 self.sections_head = spec.section_ptr;
409 } else {
410 (*self.sections_tail).next = spec.section_ptr;
411 }
412 self.sections_tail = spec.section_ptr;
413 self.section_count += 1;
414
415 log::debug!(
416 "BuddyAllocator: add section region {:#x}+{:#x}, heap {:#x}..{:#x}, {} pages",
417 spec.region_start,
418 spec.region_size,
419 spec.heap_start,
420 heap_end,
421 spec.heap_size / PAGE_SIZE,
422 );
423
424 Ok(())
425 }
426 }
427
428 pub fn section_count(&self) -> usize {
430 self.section_count
431 }
432
433 pub fn section(&self, index: usize) -> Option<ManagedSection> {
435 let mut current = self.sections_head;
436 let mut i = 0usize;
437 while !current.is_null() {
438 if i == index {
439 return Some(unsafe { (&*current).summary() });
440 }
441 current = unsafe { (*current).next };
442 i += 1;
443 }
444 None
445 }
446
447 pub fn total_pages(&self) -> usize {
449 let mut total = 0usize;
450 let mut current = self.sections_head;
451 while !current.is_null() {
452 total += unsafe { (*current).total_pages };
453 current = unsafe { (*current).next };
454 }
455 total
456 }
457
458 pub fn managed_bytes(&self) -> usize {
462 let mut total = 0usize;
463 let mut current = self.sections_head;
464 while !current.is_null() {
465 total += unsafe { (*current).heap_size };
466 current = unsafe { (*current).next };
467 }
468 total
469 }
470
471 pub fn free_pages(&self) -> usize {
473 let mut total = 0usize;
474 let mut current = self.sections_head;
475 while !current.is_null() {
476 total += unsafe { (*current).free_pages };
477 current = unsafe { (*current).next };
478 }
479 total
480 }
481
482 pub fn allocated_bytes(&self) -> usize {
488 self.managed_bytes()
489 .saturating_sub(self.free_pages().saturating_mul(PAGE_SIZE))
490 }
491
492 pub fn alloc_pages(&mut self, count: usize, align: usize) -> AllocResult<usize> {
494 if count == 0 {
495 return Err(AllocError::InvalidParam);
496 }
497 let align = if align == 0 { PAGE_SIZE } else { align };
498 if !align.is_power_of_two() || align < PAGE_SIZE {
499 return Err(AllocError::InvalidParam);
500 }
501
502 let order = count.next_power_of_two().trailing_zeros() as usize;
503 if order > MAX_ORDER {
504 return Err(AllocError::InvalidParam);
505 }
506
507 let mut section = self.sections_head;
508 while !section.is_null() {
509 if let Ok(addr) =
510 unsafe { Self::alloc_from_section_aligned(&mut *section, order, align) }
511 {
512 return Ok(addr);
513 }
514 section = unsafe { (*section).next };
515 }
516
517 Err(AllocError::NoMemory)
518 }
519
520 fn alloc_from_section_aligned(
521 section: &mut BuddySection,
522 order: usize,
523 align: usize,
524 ) -> AllocResult<usize> {
525 for search_order in order..=MAX_ORDER {
526 let mut pfn_u32 = section.free_lists[search_order];
527 while pfn_u32 != PFN_NONE {
528 let block_pfn = pfn_u32 as usize;
529 if let Some(target_pfn) = Self::find_aligned_pfn_in_block(
530 section.heap_start,
531 block_pfn,
532 search_order,
533 order,
534 align,
535 ) {
536 unsafe {
537 free_list_remove(
538 section.meta,
539 &mut section.free_lists,
540 pfn_u32,
541 search_order,
542 );
543 }
544
545 let mut current_order = search_order;
546 let mut current_pfn = block_pfn;
547 while current_order > order {
548 current_order -= 1;
549 let left_pfn = current_pfn;
550 let right_pfn = current_pfn + (1 << current_order);
551 let (next_pfn, free_pfn) = if target_pfn >= right_pfn {
552 (right_pfn, left_pfn)
553 } else {
554 (left_pfn, right_pfn)
555 };
556 unsafe {
557 let bm = &mut *section.meta.add(free_pfn);
558 bm.flags = PageFlags::Free;
559 bm.order = current_order as u8;
560 free_list_push(
561 section.meta,
562 &mut section.free_lists,
563 free_pfn as u32,
564 current_order,
565 );
566 }
567 current_pfn = next_pfn;
568 }
569
570 unsafe {
571 let m = &mut *section.meta.add(current_pfn);
572 m.flags = PageFlags::Allocated;
573 m.order = order as u8;
574 }
575
576 section.free_pages -= 1 << order;
577 return Ok(section.heap_start + current_pfn * PAGE_SIZE);
578 }
579 pfn_u32 = unsafe { (*section.meta.add(pfn_u32 as usize)).next };
580 }
581 }
582
583 Err(AllocError::NoMemory)
584 }
585
586 fn find_aligned_pfn_in_block(
587 heap_start: usize,
588 block_pfn: usize,
589 block_order: usize,
590 alloc_order: usize,
591 align: usize,
592 ) -> Option<usize> {
593 let subblock_pages = 1usize << alloc_order;
594 let align_pages = align / PAGE_SIZE;
595 let heap_page_offset = (heap_start / PAGE_SIZE) & (align_pages - 1);
596 let offset = (align_pages - heap_page_offset) & (align_pages - 1);
597
598 let candidate = if align_pages <= subblock_pages {
599 if !heap_start.is_multiple_of(align) {
600 return None;
601 }
602 block_pfn
603 } else {
604 if !offset.is_multiple_of(subblock_pages) {
605 return None;
606 }
607 let rem = block_pfn & (align_pages - 1);
608 let delta = (offset + align_pages - rem) & (align_pages - 1);
609 block_pfn + delta
610 };
611
612 let block_pages = 1usize << block_order;
613 let last_start = block_pfn + block_pages - subblock_pages;
614 (candidate <= last_start).then_some(candidate)
615 }
616
617 pub fn alloc_pages_lowmem(&mut self, count: usize, align: usize) -> AllocResult<usize> {
619 if count == 0 {
620 return Err(AllocError::InvalidParam);
621 }
622 let align = if align == 0 { PAGE_SIZE } else { align };
623 if !align.is_power_of_two() || align < PAGE_SIZE {
624 return Err(AllocError::InvalidParam);
625 }
626
627 let order = count.next_power_of_two().trailing_zeros() as usize;
628 if order > MAX_ORDER {
629 return Err(AllocError::InvalidParam);
630 }
631
632 let mut section = self.sections_head;
633 while !section.is_null() {
634 if let Ok(addr) =
635 unsafe { Self::alloc_lowmem_from_section(&mut *section, order, align) }
636 {
637 return Ok(addr);
638 }
639 section = unsafe { (*section).next };
640 }
641
642 Err(AllocError::NoMemory)
643 }
644
645 fn alloc_lowmem_from_section(
646 section: &mut BuddySection,
647 alloc_order: usize,
648 align: usize,
649 ) -> AllocResult<usize> {
650 for search_order in alloc_order..=MAX_ORDER {
651 let mut pfn_u32 = section.free_lists[search_order];
652 while pfn_u32 != PFN_NONE {
653 let block_pfn = pfn_u32 as usize;
654 let Some(target_pfn) = Self::find_aligned_pfn_in_block(
655 section.heap_start,
656 block_pfn,
657 search_order,
658 alloc_order,
659 align,
660 ) else {
661 pfn_u32 = unsafe { (*section.meta.add(pfn_u32 as usize)).next };
662 continue;
663 };
664 let addr = section.heap_start + target_pfn * PAGE_SIZE;
665 let phys = eii::virt_to_phys(addr);
666 let block_bytes = (1usize << alloc_order) * PAGE_SIZE;
667 if phys + block_bytes <= DMA32_LIMIT && addr.is_multiple_of(align) {
668 unsafe {
669 free_list_remove(
670 section.meta,
671 &mut section.free_lists,
672 pfn_u32,
673 search_order,
674 );
675 }
676
677 let mut current_order = search_order;
678 let mut current_pfn = block_pfn;
679 while current_order > alloc_order {
680 current_order -= 1;
681 let left_pfn = current_pfn;
682 let right_pfn = current_pfn + (1 << current_order);
683 let (next_pfn, free_pfn) = if target_pfn >= right_pfn {
684 (right_pfn, left_pfn)
685 } else {
686 (left_pfn, right_pfn)
687 };
688 unsafe {
689 let bm = &mut *section.meta.add(free_pfn);
690 bm.flags = PageFlags::Free;
691 bm.order = current_order as u8;
692 free_list_push(
693 section.meta,
694 &mut section.free_lists,
695 free_pfn as u32,
696 current_order,
697 );
698 }
699 current_pfn = next_pfn;
700 }
701
702 unsafe {
703 let m = &mut *section.meta.add(current_pfn);
704 m.flags = PageFlags::Allocated;
705 m.order = alloc_order as u8;
706 }
707 section.free_pages -= 1 << alloc_order;
708 return Ok(addr);
709 }
710 pfn_u32 = unsafe { (*section.meta.add(pfn_u32 as usize)).next };
711 }
712 }
713
714 Err(AllocError::NoMemory)
715 }
716
717 pub fn dealloc_pages(&mut self, addr: usize, count: usize) {
723 let Some(section) = self.find_section_by_addr_mut(addr) else {
724 debug_assert!(
725 false,
726 "dealloc_pages called with address outside all sections"
727 );
728 return;
729 };
730
731 debug_assert!(is_aligned(addr, PAGE_SIZE));
732 debug_assert!(count > 0);
733
734 let pfn = (addr - section.heap_start) / PAGE_SIZE;
735 debug_assert!(pfn < section.max_pages);
736 let stored = unsafe { &*section.meta.add(pfn) };
737 debug_assert!(
738 stored.flags == PageFlags::Allocated || stored.flags == PageFlags::Slab,
739 "dealloc_pages called on non-allocated block"
740 );
741
742 let expected_order = count.next_power_of_two().trailing_zeros() as usize;
743 let order = stored.order as usize;
744 debug_assert!(
745 expected_order <= order,
746 "dealloc_pages count implies larger order than the allocated block"
747 );
748 Self::dealloc_in_section(section, pfn, order);
749 }
750
751 pub unsafe fn set_page_flags(&mut self, addr: usize, flags: PageFlags) -> AllocResult {
756 unsafe {
757 let section = self
758 .find_section_by_addr_mut(addr)
759 .ok_or(AllocError::NotFound)?;
760 let pfn = (addr - section.heap_start) / PAGE_SIZE;
761 (*section.meta.add(pfn)).flags = flags;
762 Ok(())
763 }
764 }
765
766 pub fn page_flags(&self, addr: usize) -> AllocResult<PageFlags> {
768 let section = self
769 .find_section_by_addr(addr)
770 .ok_or(AllocError::NotFound)?;
771 let pfn = (addr - section.heap_start) / PAGE_SIZE;
772 Ok(unsafe { (*section.meta.add(pfn)).flags })
773 }
774
775 fn dealloc_in_section(section: &mut BuddySection, mut pfn: usize, mut order: usize) {
776 let freed_pages = 1usize << order;
777
778 while order < MAX_ORDER {
779 let buddy_pfn = pfn ^ (1 << order);
780 if buddy_pfn >= section.max_pages {
781 break;
782 }
783 let buddy = unsafe { &*section.meta.add(buddy_pfn) };
784 if buddy.flags != PageFlags::Free || buddy.order as usize != order {
785 break;
786 }
787 unsafe {
788 free_list_remove(
789 section.meta,
790 &mut section.free_lists,
791 buddy_pfn as u32,
792 order,
793 );
794 }
795 pfn = pfn.min(buddy_pfn);
796 order += 1;
797 }
798
799 unsafe {
800 let m = &mut *section.meta.add(pfn);
801 m.flags = PageFlags::Free;
802 m.order = order as u8;
803 free_list_push(section.meta, &mut section.free_lists, pfn as u32, order);
804 }
805 section.free_pages += freed_pages;
806 }
807
808 fn find_section_by_addr(&self, addr: usize) -> Option<&BuddySection> {
809 let mut section = self.sections_head;
810 while !section.is_null() {
811 let current = unsafe { &*section };
812 if current.contains_heap_addr(addr) {
813 return Some(current);
814 }
815 section = current.next;
816 }
817 None
818 }
819
820 fn find_section_by_addr_mut(&mut self, addr: usize) -> Option<&mut BuddySection> {
821 let mut section = self.sections_head;
822 while !section.is_null() {
823 let current = unsafe { &mut *section };
824 if current.contains_heap_addr(addr) {
825 return Some(current);
826 }
827 section = current.next;
828 }
829 None
830 }
831}