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