1use crate::{
15 boot::entry::{MemoryKind, MemoryRegion},
16 memory::{
17 boot_alloc,
18 frame::{
19 frame_flags, get_meta, AllocError, FrameAllocator, PhysFrame, FRAME_META_LINK_NONE,
20 },
21 hhdm_offset, phys_to_virt,
22 zone::{
23 BuddyBitmap, Migratetype, Zone, ZoneSegment, ZoneType, MAX_ORDER, PAGEBLOCK_ORDER,
24 PAGEBLOCK_PAGES,
25 },
26 },
27 serial_println,
28 sync::{guardian::PreemptDisabled, IrqDisabledToken, SpinLock, SpinLockGuard},
29};
30use core::{
31 mem, ptr,
32 sync::atomic::{AtomicUsize, Ordering as AtomicOrdering},
33};
34use x86_64::PhysAddr;
35
36const PAGE_SIZE: u64 = 4096;
37const DMA_MAX: u64 = 16 * 1024 * 1024;
38const NORMAL_MAX: u64 = 896 * 1024 * 1024;
39
40const LOCAL_CACHE_CAPACITY: usize = 256;
41const LOCAL_CACHE_REFILL_ORDER: u8 = 4;
42const LOCAL_CACHE_REFILL_FRAMES: usize = 1 << (LOCAL_CACHE_REFILL_ORDER as usize);
43const LOCAL_CACHE_FLUSH_BATCH: usize = 64;
44const LOCAL_CACHE_SLOTS: usize = Migratetype::COUNT * crate::arch::x86_64::percpu::MAX_CPUS;
45const LOCAL_CACHED_ZONE_MIGRATETYPE_SLOTS: usize = Migratetype::COUNT * ZoneType::COUNT;
46const COMPACTION_FRAGMENTATION_THRESHOLD: usize = 35;
47const COMPACTION_SNAPSHOT_NONE: usize = usize::MAX;
48const UNMOVABLE_ZONE_ORDER: [usize; ZoneType::COUNT] = [
49 ZoneType::Normal as usize,
50 ZoneType::HighMem as usize,
51 ZoneType::DMA as usize,
52];
53const MOVABLE_ZONE_ORDER: [usize; ZoneType::COUNT] = [
54 ZoneType::HighMem as usize,
55 ZoneType::Normal as usize,
56 ZoneType::DMA as usize,
57];
58
59#[cfg(feature = "selftest")]
60macro_rules! buddy_dbg {
61 ($($arg:tt)*) => {
62 serial_println!($($arg)*);
63 };
64}
65
66#[cfg(not(feature = "selftest"))]
67macro_rules! buddy_dbg {
68 ($($arg:tt)*) => {};
69}
70
71pub struct BuddyAllocator {
72 zones: [Zone; ZoneType::COUNT],
73 bitmap_pool: [(u64, u64); ZoneType::COUNT],
75}
76
77#[derive(Clone, Copy, Debug)]
78struct CompactionCandidate {
79 zone_idx: usize,
80 zone_type: ZoneType,
81 order: u8,
82 migratetype: Migratetype,
83 pressure: ZonePressure,
84 fragmentation_score: usize,
85 requested_pages: usize,
86 available_pages: usize,
87 usable_pages: usize,
88 cached_pages: usize,
89 pageblock_count: usize,
90 matching_pageblocks: usize,
91}
92
93impl BuddyAllocator {
94 pub const fn new() -> Self {
96 BuddyAllocator {
97 zones: [
98 Zone::new(ZoneType::DMA),
99 Zone::new(ZoneType::Normal),
100 Zone::new(ZoneType::HighMem),
101 ],
102 bitmap_pool: [(0, 0); ZoneType::COUNT],
103 }
104 }
105
106 pub fn init(&mut self, memory_regions: &[MemoryRegion]) {
108 #[cfg(debug_assertions)]
109 debug_assert!(
110 hhdm_offset() != u64::MAX,
111 "HHDM offset sanity check failed unexpectedly"
112 );
113
114 serial_println!(
115 "Buddy allocator: initializing with {} memory regions",
116 memory_regions.len()
117 );
118
119 for (i, region) in memory_regions.iter().enumerate() {
121 let kind_str = match region.kind {
122 crate::boot::entry::MemoryKind::Free => "FREE",
123 crate::boot::entry::MemoryKind::Reclaim => "RECLAIM",
124 crate::boot::entry::MemoryKind::Reserved => "RESERVED",
125 crate::boot::entry::MemoryKind::Null => "NULL",
126 _ => "UNKNOWN",
127 };
128 serial_println!(
129 " [buddy] MMAP[{:2}]: phys={:#018x}..{:#018x} size={:#x} ({})",
130 i,
131 region.base,
132 region.base.saturating_add(region.size),
133 region.size,
134 kind_str
135 );
136 }
137
138 for (_protected_base, _protected_size) in
139 Self::protected_module_ranges().into_iter().flatten()
140 {
141 buddy_dbg!(
142 " Protected module range: phys=0x{:x}..0x{:x}",
143 Self::align_down(_protected_base, PAGE_SIZE),
144 Self::align_up(_protected_base.saturating_add(_protected_size), PAGE_SIZE)
145 );
146 }
147
148 self.pass_count(memory_regions);
150
151 for zone in &self.zones {
153 serial_println!(
154 " [buddy] Zone {:?}: base={:#x} span={} pages ({} MB span)",
155 zone.zone_type,
156 zone.base.as_u64(),
157 zone.span_pages,
158 (zone.span_pages * 4096) / (1024 * 1024)
159 );
160 }
161
162 let mut candidates = [MemoryRegion {
165 base: 0,
166 size: 0,
167 kind: MemoryKind::Reserved,
168 }; boot_alloc::MAX_BOOT_ALLOC_REGIONS];
169 let candidate_len = boot_alloc::snapshot_free_regions(&mut candidates);
170 self.pass_reserve_bitmap_pools(&candidates[..candidate_len]);
171
172 let mut remaining = [MemoryRegion {
185 base: 0,
186 size: 0,
187 kind: MemoryKind::Reserved,
188 }; boot_alloc::MAX_BOOT_ALLOC_REGIONS];
189
190 let remaining_len = boot_alloc::snapshot_free_regions(&mut remaining);
191 self.pass_reserve_segment_storage(&remaining[..remaining_len]);
192
193 let remaining_len = boot_alloc::snapshot_free_regions(&mut remaining);
196
197 self.pass_build_segments(&remaining[..remaining_len]);
198 self.pass_finalize_zone_accounting();
199 self.pass_setup_segment_bitmaps();
200 self.pass_populate();
201
202 boot_alloc::seal();
206
207 for zone in &self.zones {
208 let hole_pages = zone.span_pages.saturating_sub(zone.page_count);
209 let efficiency = if zone.span_pages > 0 {
210 (zone.page_count * 100) / zone.span_pages
211 } else {
212 0
213 };
214 serial_println!(
215 " [buddy] Zone {:?}: segments={}/{} managed={} present={} reserved={} span={} holes={} min/low/high={}/{}/{} reserve={} ({}% utilized, {} MB managed)",
216 zone.zone_type,
217 zone.segment_count,
218 zone.segment_capacity,
219 zone.page_count,
220 zone.present_pages,
221 zone.reserved_pages,
222 zone.span_pages,
223 hole_pages,
224 zone.watermark_min,
225 zone.watermark_low,
226 zone.watermark_high,
227 zone.lowmem_reserve_pages,
228 efficiency,
229 (zone.page_count * 4096) / (1024 * 1024)
230 );
231 if zone.span_pages > 0 && efficiency < 70 {
232 serial_println!(
233 " [buddy] WARNING: Zone {:?} has large holes ({}% wasted). This may indicate VMware memory fragmentation.",
234 zone.zone_type,
235 100 - efficiency
236 );
237 }
238 }
239 }
240
241 fn pass_count(&mut self, memory_regions: &[MemoryRegion]) {
243 let mut min_base = [u64::MAX; ZoneType::COUNT];
244 let mut max_end = [0u64; ZoneType::COUNT];
245 let mut present_pages = [0usize; ZoneType::COUNT];
246
247 for region in memory_regions {
248 for zi in 0..ZoneType::COUNT {
249 if let Some((start, end)) = Self::zone_intersection_aligned(region, zi) {
250 present_pages[zi] =
251 present_pages[zi].saturating_add(((end - start) / PAGE_SIZE) as usize);
252 if start < min_base[zi] {
253 min_base[zi] = start;
254 }
255 if end > max_end[zi] {
256 max_end[zi] = end;
257 }
258 }
259 }
260 }
261
262 for zi in 0..ZoneType::COUNT {
263 let zone = &mut self.zones[zi];
264 zone.base = PhysAddr::new(0);
265 zone.page_count = 0;
266 zone.present_pages = present_pages[zi];
267 zone.span_pages = 0;
268 zone.allocated = 0;
269 zone.reserved_pages = 0;
270 zone.lowmem_reserve_pages = 0;
271 zone.watermark_min = 0;
272 zone.watermark_low = 0;
273 zone.watermark_high = 0;
274 zone.clear_segments();
275
276 if min_base[zi] == u64::MAX || max_end[zi] <= min_base[zi] {
277 continue;
278 }
279
280 zone.base = PhysAddr::new(min_base[zi]);
281 zone.span_pages = ((max_end[zi] - min_base[zi]) / PAGE_SIZE) as usize;
282 }
283 }
284
285 fn pass_reserve_segment_storage(&mut self, memory_regions: &[MemoryRegion]) {
287 let mut segment_counts = [0usize; ZoneType::COUNT];
288
289 for region in memory_regions {
290 for (zi, count) in segment_counts.iter_mut().enumerate() {
291 if Self::zone_intersection_aligned(region, zi).is_some() {
292 *count = count.saturating_add(1);
293 }
294 }
295 }
296
297 for (zi, &segment_count) in segment_counts.iter().enumerate() {
298 let zone = &mut self.zones[zi];
299 zone.clear_segments();
300
301 if segment_count == 0 {
302 continue;
303 }
304
305 let bytes = segment_count.saturating_mul(mem::size_of::<ZoneSegment>());
306 let storage_phys =
307 boot_alloc::alloc_bytes_accessible(bytes, mem::align_of::<ZoneSegment>())
308 .unwrap_or_else(|| {
309 panic!(
310 "Buddy allocator: unable to reserve {} bytes for {:?} segment table",
311 bytes, zone.zone_type
312 )
313 })
314 .as_u64();
315 unsafe {
316 ptr::write_bytes(phys_to_virt(storage_phys) as *mut u8, 0, bytes);
317 }
318
319 zone.segment_capacity = segment_count;
320 zone.segments = phys_to_virt(storage_phys) as *mut ZoneSegment;
321 }
322 }
323
324 fn pass_reserve_bitmap_pools(&mut self, memory_regions: &[MemoryRegion]) {
326 for zi in 0..ZoneType::COUNT {
327 let managed_pages = memory_regions
328 .iter()
329 .filter_map(|region| Self::zone_intersection_aligned(region, zi))
330 .map(|(start, end)| ((end - start) / PAGE_SIZE) as usize)
331 .sum::<usize>();
332 let needed_bytes = Self::bitmap_bytes_upper_bound_for_pages(managed_pages);
333 let reserved_bytes = Self::align_up(needed_bytes as u64, PAGE_SIZE);
334
335 if reserved_bytes == 0 {
336 self.bitmap_pool[zi] = (0, 0);
337 continue;
338 }
339
340 let pool_start = boot_alloc::alloc_bytes_accessible(needed_bytes, PAGE_SIZE as usize)
341 .unwrap_or_else(|| {
342 panic!(
343 "Buddy allocator: unable to reserve {} bytes for zone {:?} bitmaps",
344 needed_bytes, self.zones[zi].zone_type
345 )
346 })
347 .as_u64();
348 let pool_end = pool_start.saturating_add(reserved_bytes);
349 self.bitmap_pool[zi] = (pool_start, pool_end);
350 buddy_dbg!(
351 " Zone {:?}: bitmap pool phys=0x{:x}..0x{:x} ({} bytes)",
352 self.zones[zi].zone_type,
353 pool_start,
354 pool_end,
355 needed_bytes
356 );
357
358 unsafe {
360 core::ptr::write_bytes(
361 phys_to_virt(pool_start) as *mut u8,
362 0,
363 (pool_end - pool_start) as usize,
364 );
365 }
366 }
367 }
368
369 fn pass_finalize_zone_accounting(&mut self) {
371 for zone in &mut self.zones {
372 zone.reserved_pages = zone.present_pages.saturating_sub(zone.page_count);
373 zone.lowmem_reserve_pages =
374 Self::lowmem_reserve_target_pages(zone.zone_type, zone.page_count);
375 zone.watermark_min = Self::watermark_target_pages(zone.page_count, 256, 16, 2048);
376
377 let delta = Self::watermark_target_pages(zone.page_count, 512, 16, 2048);
378 zone.watermark_low = zone
379 .watermark_min
380 .saturating_add(delta)
381 .min(zone.page_count);
382 zone.watermark_high = zone
383 .watermark_low
384 .saturating_add(delta)
385 .min(zone.page_count);
386 }
387 }
388
389 fn watermark_target_pages(
391 managed_pages: usize,
392 divisor: usize,
393 floor: usize,
394 cap: usize,
395 ) -> usize {
396 Self::bounded_zone_target(managed_pages, divisor, floor, cap, 8)
397 }
398
399 fn lowmem_reserve_target_pages(zone_type: ZoneType, managed_pages: usize) -> usize {
401 match zone_type {
402 ZoneType::DMA => Self::bounded_zone_target(managed_pages, 8, 16, 512, 4),
403 ZoneType::Normal => Self::bounded_zone_target(managed_pages, 64, 64, 2048, 8),
404 ZoneType::HighMem => 0,
405 }
406 }
407
408 fn bounded_zone_target(
410 managed_pages: usize,
411 divisor: usize,
412 floor: usize,
413 cap: usize,
414 max_fraction_divisor: usize,
415 ) -> usize {
416 if managed_pages == 0 {
417 return 0;
418 }
419
420 let scaled = core::cmp::max(managed_pages / divisor, floor);
421 let capped = core::cmp::min(scaled, cap);
422 let max_for_zone = core::cmp::max(1, managed_pages / max_fraction_divisor);
423 core::cmp::min(capped, max_for_zone)
424 }
425
426 fn pass_build_segments(&mut self, memory_regions: &[MemoryRegion]) {
428 for region in memory_regions {
429 for zi in 0..ZoneType::COUNT {
430 let Some((start, end)) = Self::zone_intersection_aligned(region, zi) else {
431 continue;
432 };
433 let zone = &mut self.zones[zi];
434 if zone.segment_count >= zone.segment_capacity {
435 panic!(
436 "Buddy allocator: zone {:?} exceeded reserved segment capacity={} while processing phys=0x{:x}..0x{:x}",
437 zone.zone_type,
438 zone.segment_capacity,
439 start,
440 end,
441 );
442 }
443
444 let slot = zone.segment_count;
445 zone.segments_mut()[slot] = ZoneSegment {
446 base: PhysAddr::new(start),
447 page_count: ((end - start) / PAGE_SIZE) as usize,
448 free_lists: [[0; MAX_ORDER + 1]; Migratetype::COUNT],
449 buddy_bitmaps: [BuddyBitmap::empty(); MAX_ORDER + 1],
450 pageblock_tags: ptr::null_mut(),
451 pageblock_count: 0,
452 #[cfg(debug_assertions)]
453 alloc_bitmap: BuddyBitmap::empty(),
454 };
455 zone.segment_count = slot + 1;
456 zone.page_count = zone
457 .page_count
458 .saturating_add(((end - start) / PAGE_SIZE) as usize);
459
460 buddy_dbg!(
461 " Zone {:?}: segment phys=0x{:x}..0x{:x} pages={}",
462 zone.zone_type,
463 start,
464 end,
465 ((end - start) / PAGE_SIZE) as usize,
466 );
467 }
468 }
469 }
470
471 fn pass_setup_segment_bitmaps(&mut self) {
473 for zi in 0..ZoneType::COUNT {
474 let (pool_start, pool_end) = self.bitmap_pool[zi];
475 if pool_start == 0 || pool_end <= pool_start {
476 continue;
477 }
478
479 let zone = &mut self.zones[zi];
480 let default_pageblock_migratetype = Self::default_pageblock_migratetype(zone.zone_type);
481 let mut cursor = pool_start;
482 let segment_count = zone.segment_count;
483 for segment in zone.segments_mut().iter_mut().take(segment_count) {
484 let _exact_bitmap_bytes = Self::bitmap_bytes_for_span(segment.page_count);
485 for order in 0..=MAX_ORDER {
486 let num_bits = Self::pairs_for_order(segment.page_count, order as u8);
487 let num_bytes = Self::bits_to_bytes(num_bits) as u64;
488 if num_bits == 0 {
489 segment.buddy_bitmaps[order] = BuddyBitmap::empty();
490 continue;
491 }
492
493 debug_assert!(cursor + num_bytes <= pool_end);
494 segment.buddy_bitmaps[order] = BuddyBitmap {
495 data: phys_to_virt(cursor) as *mut u8,
496 num_bits,
497 };
498 cursor += num_bytes;
499 }
500
501 #[cfg(debug_assertions)]
502 {
503 let num_bits = segment.page_count;
504 let num_bytes = Self::bits_to_bytes(num_bits) as u64;
505 if num_bits == 0 {
506 segment.alloc_bitmap = BuddyBitmap::empty();
507 } else {
508 debug_assert!(cursor + num_bytes <= pool_end);
509 segment.alloc_bitmap = BuddyBitmap {
510 data: phys_to_virt(cursor) as *mut u8,
511 num_bits,
512 };
513 cursor += num_bytes;
514 }
515 }
516
517 let pageblock_count = segment.page_count.div_ceil(PAGEBLOCK_PAGES);
518 segment.pageblock_count = pageblock_count;
519 if pageblock_count == 0 {
520 segment.pageblock_tags = ptr::null_mut();
521 } else {
522 let num_bytes = pageblock_count as u64;
523 debug_assert!(cursor + num_bytes <= pool_end);
524 segment.pageblock_tags = phys_to_virt(cursor) as *mut u8;
525 unsafe {
526 ptr::write_bytes(
527 segment.pageblock_tags,
528 default_pageblock_migratetype as u8,
529 pageblock_count,
530 );
531 }
532 cursor += num_bytes;
533 }
534 }
535
536 debug_assert!(cursor <= pool_end);
537 }
538 }
539
540 fn pass_populate(&mut self) {
542 for zi in 0..ZoneType::COUNT {
543 let zone_type = self.zones[zi].zone_type;
544 let segment_count = self.zones[zi].segment_count;
545 for si in 0..segment_count {
546 let (start, end) = {
547 let segments = self.zones[zi].segments();
548 let segment = &segments[si];
549 (segment.base.as_u64(), segment.end_address())
550 };
551 let segment = &mut self.zones[zi].segments_mut()[si];
552 Self::seed_range_as_free(zone_type, segment, start, end);
553 }
554 }
555 }
556
557 fn seed_range_as_free(zone_type: ZoneType, segment: &mut ZoneSegment, start: u64, end: u64) {
563 let _ = zone_type;
564 if start >= end {
565 return;
566 }
567 let mut addr = start;
568
569 'seed: while addr < end {
570 if !segment.contains_address(PhysAddr::new(addr)) {
571 break;
572 }
573
574 if let Some(protected_end) = Self::protected_overlap_end(addr, addr + PAGE_SIZE) {
575 buddy_dbg!(
576 " Zone {:?}: skip protected range 0x{:x}..0x{:x}",
577 zone_type,
578 addr,
579 protected_end
580 );
581 addr = core::cmp::min(protected_end, end);
582 continue;
583 }
584
585 let remaining_pages = ((end - addr) / PAGE_SIZE) as usize;
586 debug_assert!(remaining_pages != 0);
587 let mut order = ((remaining_pages.ilog2()) as u8).min(MAX_ORDER as u8);
588
589 while order > 0 {
590 let block_size = PAGE_SIZE << order;
591 if addr & (block_size - 1) == 0 {
592 break;
593 }
594 order -= 1;
595 }
596
597 loop {
598 let block_size = PAGE_SIZE << order;
599 let block_end = addr.saturating_add(block_size);
600 if block_end > end {
601 debug_assert!(order != 0);
602 order -= 1;
603 continue;
604 }
605
606 if Self::protected_overlap_end(addr, block_end).is_some() {
607 if order == 0 {
608 if let Some(skip_to) = Self::protected_overlap_end(addr, block_end) {
609 buddy_dbg!(" Zone {:?}: skip protected page 0x{:x}", zone_type, addr);
610 addr = core::cmp::min(skip_to, end);
611 continue 'seed;
612 }
613 }
614 order -= 1;
615 continue;
616 }
617
618 let migratetype = Self::pageblock_migratetype(
619 segment,
620 addr,
621 Self::default_pageblock_migratetype(zone_type),
622 );
623 Self::insert_free_block(segment, addr, order, migratetype);
624 addr = block_end;
625 continue 'seed;
626 }
627 }
628 }
629
630 fn alloc_from_zone(
632 zone: &mut Zone,
633 zone_idx: usize,
634 order: u8,
635 migratetype: Migratetype,
636 honor_watermarks: bool,
637 token: &IrqDisabledToken,
638 ) -> Option<PhysFrame> {
639 if !Self::zone_allows_allocation(zone, zone_idx, order, honor_watermarks) {
640 return None;
641 }
642
643 for si in 0..zone.segment_count {
644 let frame_phys = {
645 let segment = &mut zone.segments_mut()[si];
646 Self::alloc_from_segment(segment, order, migratetype, token)
647 };
648 if let Some(frame_phys) = frame_phys {
649 zone.allocated += 1usize << order;
650 return PhysFrame::from_start_address(PhysAddr::new(frame_phys)).ok();
651 }
652 }
653 None
654 }
655
656 fn alloc_from_segment(
658 segment: &mut ZoneSegment,
659 order: u8,
660 requested_migratetype: Migratetype,
661 _token: &IrqDisabledToken,
662 ) -> Option<u64> {
663 for cur_order in order..=MAX_ORDER as u8 {
664 for donor_migratetype in requested_migratetype.fallback_order() {
665 let Some(frame_phys) = Self::free_list_pop(segment, cur_order, donor_migratetype)
666 else {
667 continue;
668 };
669 debug_assert!(
670 !crate::memory::frame::block_phys_has_poison_guard(frame_phys, cur_order),
671 "buddy: poisoned block on free list (order {})",
672 cur_order
673 );
674 let block_size = PAGE_SIZE << cur_order;
675 let block_end = frame_phys.saturating_add(block_size);
676 if Self::protected_overlap_end(frame_phys, block_end).is_some() {
677 panic!(
678 "Buddy allocator inconsistency: free block 0x{:x} order {} overlaps protected memory",
679 frame_phys, cur_order
680 );
681 }
682
683 let _ = Self::toggle_pair(segment, frame_phys, cur_order);
684
685 let mut split_order = cur_order;
686 while split_order > order {
687 split_order -= 1;
688 Self::retag_pageblock_range(
689 segment,
690 frame_phys,
691 split_order,
692 requested_migratetype,
693 );
694 let buddy_phys = frame_phys + ((1u64 << split_order) * PAGE_SIZE);
695 let buddy_migratetype =
696 Self::pageblock_migratetype(segment, buddy_phys, donor_migratetype);
697 Self::mark_block_free(buddy_phys, split_order, buddy_migratetype);
698 Self::free_list_push(segment, buddy_phys, split_order, buddy_migratetype);
699 let _ = Self::toggle_pair(segment, frame_phys, split_order);
700 }
701 Self::retag_pageblock_range(segment, frame_phys, order, requested_migratetype);
702 Self::mark_block_allocated(frame_phys, order, requested_migratetype);
703
704 #[cfg(debug_assertions)]
705 Self::mark_allocated(segment, frame_phys, order, true);
706
707 return Some(frame_phys);
708 }
709 }
710 None
711 }
712
713 #[inline]
714 fn find_segment_index(zone: &Zone, phys: u64, order: u8) -> Option<usize> {
715 zone.segments()
716 .iter()
717 .take(zone.segment_count)
718 .position(|segment| Self::segment_contains_block(segment, phys, order))
719 }
720
721 #[inline]
722 fn segment_contains_block(segment: &ZoneSegment, phys: u64, order: u8) -> bool {
723 if !segment.contains_address(PhysAddr::new(phys)) {
724 return false;
725 }
726 let block_end = phys.saturating_add(PAGE_SIZE << order);
727 block_end <= segment.end_address()
728 }
729
730 fn free_to_zone(zone: &mut Zone, frame: PhysFrame, order: u8, _token: &IrqDisabledToken) {
732 let frame_phys = frame.start_address.as_u64();
733 let block_size = PAGE_SIZE << order;
734 let block_end = frame_phys.saturating_add(block_size);
735 let migratetype = Self::block_migratetype(frame_phys);
736 let Some(segment_idx) = Self::find_segment_index(zone, frame_phys, order) else {
737 serial_println!(
742 "[buddy] CRITICAL: frame 0x{:x} order {} not found in zone {:?} segments.",
743 frame_phys,
744 order,
745 zone.zone_type,
746 );
747 serial_println!(
748 " segments={}/{} span_pages={} page_count={} allocated={}",
749 zone.segment_count,
750 zone.segment_capacity,
751 zone.span_pages,
752 zone.page_count,
753 zone.allocated,
754 );
755 for si in 0..zone.segment_count {
756 let seg = &zone.segments()[si];
757 serial_println!(
758 " segment[{}]: base=0x{:x} pages={} end=0x{:x}",
759 si,
760 seg.base.as_u64(),
761 seg.page_count,
762 seg.end_address(),
763 );
764 }
765 panic!(
769 "buddy free: frame 0x{:x} order {} does not belong to any segment in zone {:?}",
770 frame_phys, order, zone.zone_type,
771 );
772 };
773
774 debug_assert!(order <= MAX_ORDER as u8);
775 debug_assert!(frame.start_address.is_aligned(PAGE_SIZE << order));
776 debug_assert!(zone.contains_address(frame.start_address));
777
778 if Self::protected_overlap_end(frame_phys, block_end).is_some() {
779 serial_println!(
780 "[buddy] WARNING: free_to_zone: frame 0x{:x} order {} in zone {:?} overlaps protected memory 0x{:x}..0x{:x}",
781 frame_phys,
782 order,
783 zone.zone_type,
784 frame_phys,
785 block_end,
786 );
787 #[cfg(not(feature = "selftest"))]
788 panic!(
789 "buddy free: frame 0x{:x} order {} overlaps protected memory in zone {:?}",
790 frame_phys, order, zone.zone_type,
791 );
792 return;
793 }
794
795 #[cfg(debug_assertions)]
796 {
797 let segment = &mut zone.segments_mut()[segment_idx];
798 Self::mark_allocated(segment, frame_phys, order, false);
799 }
800
801 {
802 let segment = &mut zone.segments_mut()[segment_idx];
803 if order as usize >= PAGEBLOCK_ORDER {
804 Self::retag_pageblock_range(segment, frame_phys, order, migratetype);
805 }
806 let free_migratetype = Self::pageblock_migratetype(segment, frame_phys, migratetype);
807 Self::mark_block_free(frame_phys, order, free_migratetype);
808 Self::insert_free_block(segment, frame_phys, order, free_migratetype);
809 }
810 zone.allocated = zone.allocated.saturating_sub(1usize << order);
811 }
812
813 fn quarantine_poisoned_block_in_zone(
820 zone: &mut Zone,
821 frame: PhysFrame,
822 order: u8,
823 _token: &IrqDisabledToken,
824 ) {
825 let frame_phys = frame.start_address.as_u64();
826 let block_size = PAGE_SIZE << order;
827 let block_end = frame_phys.saturating_add(block_size);
828 let Some(segment_idx) = Self::find_segment_index(zone, frame_phys, order) else {
829 panic!(
830 "buddy quarantine: frame 0x{:x} order {} does not belong to any segment in zone {:?}",
831 frame_phys,
832 order,
833 zone.zone_type,
834 );
835 };
836
837 debug_assert!(order <= MAX_ORDER as u8);
838 debug_assert!(frame.start_address.is_aligned(PAGE_SIZE << order));
839 debug_assert!(zone.contains_address(frame.start_address));
840 debug_assert!(Self::segment_contains_block(
841 &zone.segments()[segment_idx],
842 frame_phys,
843 order
844 ));
845
846 if Self::protected_overlap_end(frame_phys, block_end).is_some() {
847 return;
848 }
849
850 zone.allocated = zone.allocated.saturating_sub(1usize << order);
854 POISON_QUARANTINE_PAGES.fetch_add(1usize << order, AtomicOrdering::Relaxed);
855 }
856
857 fn insert_free_block(
862 segment: &mut ZoneSegment,
863 frame_phys: u64,
864 initial_order: u8,
865 migratetype: Migratetype,
866 ) {
867 let mut current = frame_phys;
868 let mut order = initial_order;
869
870 loop {
871 let bit_is_set = Self::toggle_pair(segment, current, order);
872 if bit_is_set || order == MAX_ORDER as u8 {
873 Self::mark_block_free(current, order, migratetype);
874 Self::free_list_push(segment, current, order, migratetype);
875 break;
876 }
877
878 let Some(buddy) = Self::buddy_phys(segment, current, order) else {
879 Self::mark_block_free(current, order, migratetype);
880 Self::free_list_push(segment, current, order, migratetype);
881 break;
882 };
883
884 if !Self::can_merge_with_buddy(buddy, order, migratetype) {
885 Self::mark_block_free(current, order, migratetype);
886 Self::free_list_push(segment, current, order, migratetype);
887 break;
888 }
889
890 let removed = Self::free_list_remove(segment, buddy, order, migratetype);
891 if !removed {
892 debug_assert!(false, "buddy bitmap/list inconsistency while freeing");
893 Self::mark_block_free(current, order, migratetype);
894 Self::free_list_push(segment, current, order, migratetype);
895 break;
896 }
897
898 current = core::cmp::min(current, buddy);
899 order += 1;
900 }
901 }
902
903 #[inline]
905 fn page_index(segment: &ZoneSegment, phys: u64) -> usize {
906 debug_assert!(segment.page_count > 0);
907 let base = segment.base.as_u64();
908 debug_assert!(phys >= base);
909 debug_assert!((phys - base).is_multiple_of(PAGE_SIZE));
910 ((phys - base) / PAGE_SIZE) as usize
911 }
912
913 #[inline]
915 fn pair_index(segment: &ZoneSegment, phys: u64, order: u8) -> usize {
916 Self::page_index(segment, phys) >> (order as usize + 1)
917 }
918
919 #[inline]
921 fn toggle_pair(segment: &mut ZoneSegment, phys: u64, order: u8) -> bool {
922 let bitmap = segment.buddy_bitmaps[order as usize];
923 if bitmap.is_empty() {
924 return true;
925 }
926 let idx = Self::pair_index(segment, phys, order);
927 debug_assert!(idx < bitmap.num_bits);
928 bitmap.toggle(idx)
929 }
930
931 #[inline]
933 fn buddy_phys(segment: &ZoneSegment, phys: u64, order: u8) -> Option<u64> {
934 let base = segment.base.as_u64();
935 if phys < base {
936 return None;
937 }
938 let offset = phys - base;
939 let block_size = PAGE_SIZE << order;
940 let buddy_offset = offset ^ block_size;
941 let buddy_page = (buddy_offset / PAGE_SIZE) as usize;
942 if buddy_page >= segment.page_count {
943 return None;
944 }
945 Some(base + buddy_offset)
946 }
947
948 #[cfg(debug_assertions)]
950 fn mark_allocated(segment: &mut ZoneSegment, frame_phys: u64, order: u8, allocated: bool) {
951 if segment.alloc_bitmap.is_empty() {
952 return;
953 }
954 let start = Self::page_index(segment, frame_phys);
955 let count = 1usize << order;
956 for i in 0..count {
957 let bit = start + i;
958 debug_assert!(bit < segment.alloc_bitmap.num_bits);
959 if allocated {
960 debug_assert!(
961 !segment.alloc_bitmap.test(bit),
962 "double allocation detected"
963 );
964 segment.alloc_bitmap.set(bit);
965 } else {
966 debug_assert!(segment.alloc_bitmap.test(bit), "double free detected");
967 segment.alloc_bitmap.clear(bit);
968 }
969 }
970 }
971
972 fn free_list_push(segment: &mut ZoneSegment, phys: u64, order: u8, migratetype: Migratetype) {
974 debug_assert!(
975 !crate::memory::frame::block_phys_has_poison_guard(phys, order),
976 "buddy: refusing to push poisoned block to free list"
977 );
978 let head = segment.free_lists[migratetype.index()][order as usize];
979 Self::write_free_prev(phys, 0);
980 Self::write_free_next(phys, head);
981 if head != 0 {
982 Self::write_free_prev(head, phys);
983 }
984 segment.free_lists[migratetype.index()][order as usize] = phys;
985 }
986
987 fn free_list_pop(
989 segment: &mut ZoneSegment,
990 order: u8,
991 migratetype: Migratetype,
992 ) -> Option<u64> {
993 let head = segment.free_lists[migratetype.index()][order as usize];
994 if head == 0 {
995 return None;
996 }
997 let next = Self::read_free_next(head);
998 segment.free_lists[migratetype.index()][order as usize] = next;
999 if next != 0 {
1000 Self::write_free_prev(next, 0);
1001 }
1002 Self::write_free_next(head, 0);
1003 Self::write_free_prev(head, 0);
1004 Some(head)
1005 }
1006
1007 fn free_list_remove(
1009 segment: &mut ZoneSegment,
1010 phys: u64,
1011 order: u8,
1012 migratetype: Migratetype,
1013 ) -> bool {
1014 let prev = Self::read_free_prev(phys);
1015 let next = Self::read_free_next(phys);
1016
1017 if prev == 0 {
1018 if segment.free_lists[migratetype.index()][order as usize] != phys {
1019 return false;
1020 }
1021 segment.free_lists[migratetype.index()][order as usize] = next;
1022 } else {
1023 Self::write_free_next(prev, next);
1024 }
1025
1026 if next != 0 {
1027 Self::write_free_prev(next, prev);
1028 }
1029
1030 Self::write_free_next(phys, 0);
1031 Self::write_free_prev(phys, 0);
1032 true
1033 }
1034
1035 #[inline]
1037 fn read_free_next(phys: u64) -> u64 {
1038 let next = get_meta(PhysAddr::new(phys)).next();
1039 if next == FRAME_META_LINK_NONE {
1040 0
1041 } else {
1042 next
1043 }
1044 }
1045
1046 #[inline]
1048 fn write_free_next(phys: u64, next: u64) {
1049 get_meta(PhysAddr::new(phys)).set_next(if next == 0 {
1050 FRAME_META_LINK_NONE
1051 } else {
1052 next
1053 });
1054 }
1055
1056 #[inline]
1058 fn read_free_prev(phys: u64) -> u64 {
1059 let prev = get_meta(PhysAddr::new(phys)).prev();
1060 if prev == FRAME_META_LINK_NONE {
1061 0
1062 } else {
1063 prev
1064 }
1065 }
1066
1067 #[inline]
1069 fn write_free_prev(phys: u64, prev: u64) {
1070 get_meta(PhysAddr::new(phys)).set_prev(if prev == 0 {
1071 FRAME_META_LINK_NONE
1072 } else {
1073 prev
1074 });
1075 }
1076
1077 fn zone_index_for_addr(addr: u64) -> usize {
1079 if addr < DMA_MAX {
1080 ZoneType::DMA as usize
1081 } else if addr < NORMAL_MAX {
1082 ZoneType::Normal as usize
1083 } else {
1084 ZoneType::HighMem as usize
1085 }
1086 }
1087
1088 fn zone_bounds(zone_idx: usize) -> (u64, u64) {
1090 match zone_idx {
1091 x if x == ZoneType::DMA as usize => (0, DMA_MAX),
1092 x if x == ZoneType::Normal as usize => (DMA_MAX, NORMAL_MAX),
1093 _ => (NORMAL_MAX, u64::MAX),
1094 }
1095 }
1096
1097 fn zone_intersection_aligned(region: &MemoryRegion, zone_idx: usize) -> Option<(u64, u64)> {
1099 if !matches!(region.kind, MemoryKind::Free | MemoryKind::Reclaim) {
1100 return None;
1101 }
1102
1103 let region_start = region.base;
1104 let region_end = region.base.saturating_add(region.size);
1105 let (zone_start, zone_end) = Self::zone_bounds(zone_idx);
1106
1107 let start = core::cmp::max(region_start, zone_start);
1108 let end = core::cmp::min(region_end, zone_end);
1109 if start >= end {
1110 return None;
1111 }
1112
1113 let start = Self::align_up(core::cmp::max(start, PAGE_SIZE), PAGE_SIZE);
1115 let end = Self::align_down(end, PAGE_SIZE);
1116 if start >= end {
1117 None
1118 } else {
1119 Some((start, end))
1120 }
1121 }
1122
1123 fn protected_overlap_end(start: u64, end: u64) -> Option<u64> {
1125 for (base, size) in Self::protected_module_ranges().into_iter().flatten() {
1126 if size == 0 {
1127 continue;
1128 }
1129 let pstart = Self::align_down(base, PAGE_SIZE);
1130 let pend = Self::align_up(base.saturating_add(size), PAGE_SIZE);
1131 if end <= pstart || start >= pend {
1132 continue;
1133 }
1134 return Some(pend);
1135 }
1136 None
1137 }
1138
1139 fn protected_module_ranges() -> [Option<(u64, u64)>; boot_alloc::MAX_PROTECTED_RANGES] {
1141 boot_alloc::protected_ranges_snapshot()
1142 }
1143
1144 #[inline]
1146 fn pairs_for_order(span_pages: usize, order: u8) -> usize {
1147 let pair_span = 1usize << (order as usize + 1);
1148 span_pages.div_ceil(pair_span)
1149 }
1150
1151 #[inline]
1153 fn bits_to_bytes(bits: usize) -> usize {
1154 bits.div_ceil(8)
1155 }
1156
1157 fn bitmap_bytes_for_span(span_pages: usize) -> usize {
1159 let mut bytes = 0usize;
1160 for order in 0..=MAX_ORDER as u8 {
1161 bytes += Self::bits_to_bytes(Self::pairs_for_order(span_pages, order));
1162 }
1163 #[cfg(debug_assertions)]
1164 {
1165 bytes += Self::bits_to_bytes(span_pages);
1166 }
1167 bytes += Self::pageblock_tag_bytes_for_span(span_pages);
1168 bytes
1169 }
1170
1171 fn bitmap_bytes_upper_bound_for_pages(page_count: usize) -> usize {
1177 let mut bits = page_count.saturating_mul(MAX_ORDER + 1);
1178 #[cfg(debug_assertions)]
1179 {
1180 bits = bits.saturating_add(page_count);
1181 }
1182 Self::bits_to_bytes(bits)
1183 .saturating_add(Self::pageblock_tag_bytes_upper_bound_for_pages(page_count))
1184 }
1185
1186 #[inline]
1188 fn pageblock_tag_bytes_for_span(span_pages: usize) -> usize {
1189 span_pages.div_ceil(PAGEBLOCK_PAGES)
1190 }
1191
1192 #[inline]
1194 fn pageblock_tag_bytes_upper_bound_for_pages(page_count: usize) -> usize {
1195 page_count
1196 }
1197
1198 #[inline]
1200 fn align_up(value: u64, align: u64) -> u64 {
1201 debug_assert!(align.is_power_of_two());
1202 (value + align - 1) & !(align - 1)
1203 }
1204
1205 #[inline]
1207 fn align_down(value: u64, align: u64) -> u64 {
1208 debug_assert!(align.is_power_of_two());
1209 value & !(align - 1)
1210 }
1211
1212 #[inline]
1214 fn default_pageblock_migratetype(zone_type: ZoneType) -> Migratetype {
1215 match zone_type {
1216 ZoneType::HighMem => Migratetype::Movable,
1217 ZoneType::DMA | ZoneType::Normal => Migratetype::Unmovable,
1218 }
1219 }
1220
1221 #[inline]
1223 fn pageblock_index(segment: &ZoneSegment, phys: u64) -> usize {
1224 Self::page_index(segment, phys) / PAGEBLOCK_PAGES
1225 }
1226
1227 #[inline]
1229 fn decode_pageblock_tag(tag: u8) -> Migratetype {
1230 match tag {
1231 x if x == Migratetype::Movable as u8 => Migratetype::Movable,
1232 _ => Migratetype::Unmovable,
1233 }
1234 }
1235
1236 #[inline]
1238 fn pageblock_migratetype(
1239 segment: &ZoneSegment,
1240 phys: u64,
1241 fallback: Migratetype,
1242 ) -> Migratetype {
1243 if segment.pageblock_count == 0 || segment.pageblock_tags.is_null() {
1244 return fallback;
1245 }
1246 let idx = Self::pageblock_index(segment, phys);
1247 debug_assert!(idx < segment.pageblock_count);
1248 unsafe { Self::decode_pageblock_tag(*segment.pageblock_tags.add(idx)) }
1249 }
1250
1251 fn retag_pageblock_range(
1253 segment: &mut ZoneSegment,
1254 phys: u64,
1255 order: u8,
1256 migratetype: Migratetype,
1257 ) {
1258 if segment.pageblock_count == 0 || segment.pageblock_tags.is_null() {
1259 return;
1260 }
1261
1262 let start_page = Self::page_index(segment, phys);
1263 let end_page_exclusive = start_page.saturating_add(1usize << order);
1264 let start_idx = start_page / PAGEBLOCK_PAGES;
1265 let end_idx = end_page_exclusive.saturating_sub(1) / PAGEBLOCK_PAGES;
1266 debug_assert!(end_idx < segment.pageblock_count);
1267
1268 for idx in start_idx..=end_idx {
1269 unsafe {
1270 *segment.pageblock_tags.add(idx) = migratetype as u8;
1271 }
1272 }
1273 }
1274
1275 fn zone_pageblock_counts(zone: &Zone) -> [usize; Migratetype::COUNT] {
1277 let mut counts = [0usize; Migratetype::COUNT];
1278 for segment in zone.segments().iter().take(zone.segment_count) {
1279 if segment.pageblock_count == 0 || segment.pageblock_tags.is_null() {
1280 continue;
1281 }
1282 for idx in 0..segment.pageblock_count {
1283 let migratetype =
1284 unsafe { Self::decode_pageblock_tag(*segment.pageblock_tags.add(idx)) };
1285 counts[migratetype.index()] = counts[migratetype.index()].saturating_add(1);
1286 }
1287 }
1288 counts
1289 }
1290
1291 fn zone_effective_free_pages(zone: &Zone, zone_idx: usize) -> usize {
1292 zone.available_pages()
1293 .saturating_add(LOCAL_CACHED_ZONE_FRAMES[zone_idx].load(AtomicOrdering::Relaxed))
1294 }
1295
1296 fn zone_allows_allocation(
1298 zone: &Zone,
1299 zone_idx: usize,
1300 order: u8,
1301 honor_watermarks: bool,
1302 ) -> bool {
1303 if zone.page_count == 0 {
1304 return false;
1305 }
1306
1307 if !honor_watermarks {
1308 return true;
1309 }
1310
1311 let requested_pages = 1usize << order;
1312 let floor = zone.watermark_min.saturating_add(zone.lowmem_reserve_pages);
1313 Self::zone_effective_free_pages(zone, zone_idx) >= requested_pages.saturating_add(floor)
1314 }
1315
1316 fn can_merge_with_buddy(phys: u64, order: u8, migratetype: Migratetype) -> bool {
1318 let meta = get_meta(PhysAddr::new(phys));
1319 let flags = meta.get_flags();
1320 flags & frame_flags::FREE != 0
1321 && meta.get_order() == order
1322 && Self::migratetype_from_flags(flags) == migratetype
1323 && !crate::memory::frame::block_phys_has_poison_guard(phys, order)
1324 }
1325
1326 fn block_migratetype(frame_phys: u64) -> Migratetype {
1328 Self::migratetype_from_flags(get_meta(PhysAddr::new(frame_phys)).get_flags())
1329 }
1330
1331 #[inline]
1333 fn migratetype_from_flags(flags: u32) -> Migratetype {
1334 if flags & frame_flags::MOVABLE != 0 {
1335 Migratetype::Movable
1336 } else {
1337 Migratetype::Unmovable
1338 }
1339 }
1340
1341 #[inline]
1343 fn free_flags_for(migratetype: Migratetype) -> u32 {
1344 match migratetype {
1345 Migratetype::Unmovable => frame_flags::FREE,
1346 Migratetype::Movable => frame_flags::FREE | frame_flags::MOVABLE,
1347 }
1348 }
1349
1350 #[inline]
1352 fn allocated_flags_for(migratetype: Migratetype) -> u32 {
1353 match migratetype {
1354 Migratetype::Unmovable => frame_flags::ALLOCATED,
1355 Migratetype::Movable => frame_flags::ALLOCATED | frame_flags::MOVABLE,
1356 }
1357 }
1358
1359 fn alloc_in_zone_order(
1361 &mut self,
1362 order: u8,
1363 migratetype: Migratetype,
1364 zone_order: &[usize],
1365 token: &IrqDisabledToken,
1366 ) -> Option<PhysFrame> {
1367 for honor_watermarks in [true, false] {
1368 for &zi in zone_order {
1369 if let Some(frame) = Self::alloc_from_zone(
1370 &mut self.zones[zi],
1371 zi,
1372 order,
1373 migratetype,
1374 honor_watermarks,
1375 token,
1376 ) {
1377 return Some(frame);
1378 }
1379 }
1380 }
1381 None
1382 }
1383
1384 #[inline]
1391 fn preferred_zone_order(migratetype: Migratetype) -> &'static [usize; ZoneType::COUNT] {
1392 match migratetype {
1393 Migratetype::Unmovable => &UNMOVABLE_ZONE_ORDER,
1394 Migratetype::Movable => &MOVABLE_ZONE_ORDER,
1395 }
1396 }
1397
1398 #[inline]
1399 fn zone_pressure_for_free_pages(zone: &Zone, free_pages: usize) -> ZonePressure {
1400 let reserve_floor = zone.watermark_min.saturating_add(zone.lowmem_reserve_pages);
1401 let low_floor = zone.watermark_low.saturating_add(zone.lowmem_reserve_pages);
1402 let high_floor = zone
1403 .watermark_high
1404 .saturating_add(zone.lowmem_reserve_pages);
1405
1406 if free_pages <= reserve_floor {
1407 ZonePressure::Min
1408 } else if free_pages <= low_floor {
1409 ZonePressure::Low
1410 } else if free_pages <= high_floor {
1411 ZonePressure::High
1412 } else {
1413 ZonePressure::Healthy
1414 }
1415 }
1416
1417 fn compaction_candidate(
1418 &self,
1419 order: u8,
1420 migratetype: Migratetype,
1421 zone_order: &[usize],
1422 ) -> Option<CompactionCandidate> {
1423 if order == 0 {
1424 return None;
1425 }
1426
1427 let requested_pages = 1usize << order;
1428 let mut best: Option<CompactionCandidate> = None;
1429
1430 for &zone_idx in zone_order {
1431 let zone = &self.zones[zone_idx];
1432 if zone.page_count == 0 {
1433 continue;
1434 }
1435
1436 let cached_pages = LOCAL_CACHED_ZONE_FRAMES[zone_idx].load(AtomicOrdering::Relaxed);
1437 if cached_pages == 0 {
1438 continue;
1439 }
1440
1441 let effective_free = Self::zone_effective_free_pages(zone, zone_idx);
1442 let available_pages = effective_free
1443 .saturating_sub(zone.watermark_min.saturating_add(zone.lowmem_reserve_pages));
1444 if available_pages < requested_pages {
1445 continue;
1446 }
1447
1448 let usable_pages = zone.free_pages_at_or_above_order(order);
1449 if usable_pages >= requested_pages {
1450 continue;
1451 }
1452
1453 let fragmentation_score = zone.fragmentation_score(order, cached_pages);
1454 if fragmentation_score < COMPACTION_FRAGMENTATION_THRESHOLD {
1455 continue;
1456 }
1457
1458 let pageblocks = Self::zone_pageblock_counts(zone);
1459 let candidate = CompactionCandidate {
1460 zone_idx,
1461 zone_type: zone.zone_type,
1462 order,
1463 migratetype,
1464 pressure: Self::zone_pressure_for_free_pages(zone, effective_free),
1465 fragmentation_score,
1466 requested_pages,
1467 available_pages,
1468 usable_pages,
1469 cached_pages,
1470 pageblock_count: pageblocks[Migratetype::Unmovable.index()]
1471 .saturating_add(pageblocks[Migratetype::Movable.index()]),
1472 matching_pageblocks: pageblocks[migratetype.index()],
1473 };
1474
1475 let replace = match best {
1476 None => true,
1477 Some(current) => {
1478 candidate.fragmentation_score > current.fragmentation_score
1479 || (candidate.fragmentation_score == current.fragmentation_score
1480 && candidate.cached_pages > current.cached_pages)
1481 || (candidate.fragmentation_score == current.fragmentation_score
1482 && candidate.cached_pages == current.cached_pages
1483 && candidate.matching_pageblocks > current.matching_pageblocks)
1484 }
1485 };
1486
1487 if replace {
1488 best = Some(candidate);
1489 }
1490 }
1491
1492 best
1493 }
1494
1495 #[inline]
1496 fn compaction_drain_budget(candidate: CompactionCandidate) -> usize {
1497 let pageblock_goal = if candidate.matching_pageblocks != 0 {
1498 PAGEBLOCK_PAGES
1499 } else {
1500 candidate.requested_pages
1501 };
1502 let target_pages = core::cmp::max(candidate.requested_pages, pageblock_goal)
1503 .saturating_mul(2)
1504 .max(LOCAL_CACHE_FLUSH_BATCH);
1505 core::cmp::min(target_pages, candidate.cached_pages)
1506 }
1507
1508 fn alloc_locked_with_migratetype(
1510 &mut self,
1511 order: u8,
1512 migratetype: Migratetype,
1513 token: &IrqDisabledToken,
1514 ) -> Result<PhysFrame, AllocError> {
1515 if order > MAX_ORDER as u8 {
1516 return Err(AllocError::InvalidOrder);
1517 }
1518
1519 let cpu_idx = crate::arch::x86_64::percpu::current_cpu_index();
1520 if ALLOC_IN_PROGRESS[cpu_idx].swap(true, core::sync::atomic::Ordering::Acquire) {
1521 panic!("Recursive allocation detected on CPU {}!", cpu_idx);
1522 }
1523
1524 let result = self
1525 .alloc_in_zone_order(
1526 order,
1527 migratetype,
1528 Self::preferred_zone_order(migratetype),
1529 token,
1530 )
1531 .ok_or_else(|| {
1532 crate::memory::buddy::record_buddy_alloc_fail(order);
1533 AllocError::OutOfMemory
1534 });
1535
1536 ALLOC_IN_PROGRESS[cpu_idx].store(false, core::sync::atomic::Ordering::Release);
1537 result
1538 }
1539
1540 fn alloc_zone_locked(
1542 &mut self,
1543 order: u8,
1544 zone: ZoneType,
1545 migratetype: Migratetype,
1546 token: &IrqDisabledToken,
1547 ) -> Result<PhysFrame, AllocError> {
1548 if order > MAX_ORDER as u8 {
1549 return Err(AllocError::InvalidOrder);
1550 }
1551
1552 let cpu_idx = crate::arch::x86_64::percpu::current_cpu_index();
1553 if ALLOC_IN_PROGRESS[cpu_idx].swap(true, core::sync::atomic::Ordering::Acquire) {
1554 panic!("Recursive allocation detected on CPU {}!", cpu_idx);
1555 }
1556
1557 let zone_idx = zone as usize;
1558 let zone_order = [zone_idx];
1559 let result = self
1560 .alloc_in_zone_order(order, migratetype, &zone_order, token)
1561 .ok_or_else(|| {
1562 crate::memory::buddy::record_buddy_alloc_fail(order);
1563 AllocError::OutOfMemory
1564 });
1565
1566 ALLOC_IN_PROGRESS[cpu_idx].store(false, core::sync::atomic::Ordering::Release);
1567 result
1568 }
1569
1570 fn mark_block_allocated(frame_phys: u64, order: u8, migratetype: Migratetype) {
1571 let page_count = 1usize << order;
1572 for page_idx in 0..page_count {
1573 let phys = frame_phys + page_idx as u64 * PAGE_SIZE;
1574 let meta = get_meta(PhysAddr::new(phys));
1575 debug_assert_eq!(
1578 meta.get_refcount(),
1579 crate::memory::frame::REFCOUNT_UNUSED,
1580 "buddy: mark_block_allocated on frame {:#x} with unexpected refcount (corruption?)",
1581 phys,
1582 );
1583 meta.set_flags(Self::allocated_flags_for(migratetype));
1584 meta.set_order(order);
1585 }
1588 }
1589
1590 fn mark_block_free(frame_phys: u64, order: u8, migratetype: Migratetype) {
1591 Self::set_block_meta(
1592 frame_phys,
1593 order,
1594 Self::free_flags_for(migratetype),
1595 crate::memory::frame::REFCOUNT_UNUSED,
1596 );
1597 }
1598
1599 fn set_block_meta(frame_phys: u64, order: u8, flags: u32, refcount: u32) {
1605 let page_count = 1usize << order;
1606 for page_idx in 0..page_count {
1607 let phys = frame_phys + page_idx as u64 * PAGE_SIZE;
1608 let meta = get_meta(PhysAddr::new(phys));
1609 meta.set_flags(flags);
1610 meta.set_order(order);
1611 meta.set_next(FRAME_META_LINK_NONE);
1612 meta.set_prev(FRAME_META_LINK_NONE);
1613 meta.set_refcount(refcount);
1614 meta.reset_with_free_list_meta();
1615 }
1616 }
1617}
1618
1619static BUDDY_ALLOCATOR: SpinLock<Option<BuddyAllocator>> = SpinLock::new(None);
1620
1621static BUDDY_ALLOC_FAIL_COUNTS: [core::sync::atomic::AtomicUsize;
1630 crate::memory::zone::MAX_ORDER + 1] =
1631 [const { core::sync::atomic::AtomicUsize::new(0) }; crate::memory::zone::MAX_ORDER + 1];
1632
1633static COMPACTION_ATTEMPTS: AtomicUsize = AtomicUsize::new(0);
1634static COMPACTION_SUCCESSES: AtomicUsize = AtomicUsize::new(0);
1635static COMPACTION_LAST_ORDER: AtomicUsize = AtomicUsize::new(COMPACTION_SNAPSHOT_NONE);
1636static COMPACTION_LAST_MIGRATETYPE: AtomicUsize = AtomicUsize::new(COMPACTION_SNAPSHOT_NONE);
1637static COMPACTION_LAST_ZONE: AtomicUsize = AtomicUsize::new(COMPACTION_SNAPSHOT_NONE);
1638static COMPACTION_LAST_PRESSURE: AtomicUsize = AtomicUsize::new(ZonePressure::SNAPSHOT_COUNT);
1639static COMPACTION_LAST_FRAGMENTATION: AtomicUsize = AtomicUsize::new(0);
1640static COMPACTION_LAST_REQUESTED_PAGES: AtomicUsize = AtomicUsize::new(0);
1641static COMPACTION_LAST_AVAILABLE_PAGES: AtomicUsize = AtomicUsize::new(0);
1642static COMPACTION_LAST_USABLE_PAGES: AtomicUsize = AtomicUsize::new(0);
1643static COMPACTION_LAST_CACHED_PAGES: AtomicUsize = AtomicUsize::new(0);
1644static COMPACTION_LAST_DRAINED_PAGES: AtomicUsize = AtomicUsize::new(0);
1645static COMPACTION_LAST_PAGEBLOCK_COUNT: AtomicUsize = AtomicUsize::new(0);
1646static COMPACTION_LAST_MATCHING_PAGEBLOCKS: AtomicUsize = AtomicUsize::new(0);
1647
1648pub(crate) fn record_buddy_alloc_fail(order: u8) {
1653 let idx = order as usize;
1654 if idx <= crate::memory::zone::MAX_ORDER {
1655 BUDDY_ALLOC_FAIL_COUNTS[idx].fetch_add(1, core::sync::atomic::Ordering::Relaxed);
1656 }
1657}
1658
1659pub fn buddy_alloc_fail_counts_snapshot() -> [usize; crate::memory::zone::MAX_ORDER + 1] {
1664 let mut out = [0usize; crate::memory::zone::MAX_ORDER + 1];
1665 for (i, counter) in BUDDY_ALLOC_FAIL_COUNTS.iter().enumerate() {
1666 out[i] = counter.load(core::sync::atomic::Ordering::Relaxed);
1667 }
1668 out
1669}
1670
1671fn snapshot_zone_type(value: usize) -> Option<ZoneType> {
1672 match value {
1673 x if x == ZoneType::DMA as usize => Some(ZoneType::DMA),
1674 x if x == ZoneType::Normal as usize => Some(ZoneType::Normal),
1675 x if x == ZoneType::HighMem as usize => Some(ZoneType::HighMem),
1676 _ => None,
1677 }
1678}
1679
1680fn snapshot_migratetype(value: usize) -> Option<Migratetype> {
1681 match value {
1682 x if x == Migratetype::Unmovable as usize => Some(Migratetype::Unmovable),
1683 x if x == Migratetype::Movable as usize => Some(Migratetype::Movable),
1684 _ => None,
1685 }
1686}
1687
1688fn record_compaction_attempt(candidate: CompactionCandidate, drained_pages: usize, success: bool) {
1689 COMPACTION_ATTEMPTS.fetch_add(1, AtomicOrdering::Relaxed);
1690 if success {
1691 COMPACTION_SUCCESSES.fetch_add(1, AtomicOrdering::Relaxed);
1692 }
1693
1694 COMPACTION_LAST_ORDER.store(candidate.order as usize, AtomicOrdering::Relaxed);
1695 COMPACTION_LAST_MIGRATETYPE.store(candidate.migratetype as usize, AtomicOrdering::Relaxed);
1696 COMPACTION_LAST_ZONE.store(candidate.zone_type as usize, AtomicOrdering::Relaxed);
1697 COMPACTION_LAST_PRESSURE.store(candidate.pressure.as_snapshot(), AtomicOrdering::Relaxed);
1698 COMPACTION_LAST_FRAGMENTATION.store(candidate.fragmentation_score, AtomicOrdering::Relaxed);
1699 COMPACTION_LAST_REQUESTED_PAGES.store(candidate.requested_pages, AtomicOrdering::Relaxed);
1700 COMPACTION_LAST_AVAILABLE_PAGES.store(candidate.available_pages, AtomicOrdering::Relaxed);
1701 COMPACTION_LAST_USABLE_PAGES.store(candidate.usable_pages, AtomicOrdering::Relaxed);
1702 COMPACTION_LAST_CACHED_PAGES.store(candidate.cached_pages, AtomicOrdering::Relaxed);
1703 COMPACTION_LAST_DRAINED_PAGES.store(drained_pages, AtomicOrdering::Relaxed);
1704 COMPACTION_LAST_PAGEBLOCK_COUNT.store(candidate.pageblock_count, AtomicOrdering::Relaxed);
1705 COMPACTION_LAST_MATCHING_PAGEBLOCKS
1706 .store(candidate.matching_pageblocks, AtomicOrdering::Relaxed);
1707}
1708
1709pub fn compaction_stats_snapshot() -> CompactionStats {
1711 let last_order = COMPACTION_LAST_ORDER.load(AtomicOrdering::Relaxed);
1712 let last_migratetype = COMPACTION_LAST_MIGRATETYPE.load(AtomicOrdering::Relaxed);
1713 let last_zone = COMPACTION_LAST_ZONE.load(AtomicOrdering::Relaxed);
1714 let last_pressure = COMPACTION_LAST_PRESSURE.load(AtomicOrdering::Relaxed);
1715
1716 CompactionStats {
1717 attempts: COMPACTION_ATTEMPTS.load(AtomicOrdering::Relaxed),
1718 successes: COMPACTION_SUCCESSES.load(AtomicOrdering::Relaxed),
1719 last_order: if last_order == COMPACTION_SNAPSHOT_NONE {
1720 None
1721 } else {
1722 Some(last_order as u8)
1723 },
1724 last_migratetype: snapshot_migratetype(last_migratetype),
1725 last_zone: snapshot_zone_type(last_zone),
1726 last_pressure: ZonePressure::from_snapshot(last_pressure),
1727 last_fragmentation_score: COMPACTION_LAST_FRAGMENTATION.load(AtomicOrdering::Relaxed),
1728 last_requested_pages: COMPACTION_LAST_REQUESTED_PAGES.load(AtomicOrdering::Relaxed),
1729 last_available_pages: COMPACTION_LAST_AVAILABLE_PAGES.load(AtomicOrdering::Relaxed),
1730 last_usable_pages: COMPACTION_LAST_USABLE_PAGES.load(AtomicOrdering::Relaxed),
1731 last_cached_pages: COMPACTION_LAST_CACHED_PAGES.load(AtomicOrdering::Relaxed),
1732 last_drained_pages: COMPACTION_LAST_DRAINED_PAGES.load(AtomicOrdering::Relaxed),
1733 last_pageblock_count: COMPACTION_LAST_PAGEBLOCK_COUNT.load(AtomicOrdering::Relaxed),
1734 last_matching_pageblocks: COMPACTION_LAST_MATCHING_PAGEBLOCKS.load(AtomicOrdering::Relaxed),
1735 }
1736}
1737
1738static POISON_QUARANTINE_PAGES: AtomicUsize = AtomicUsize::new(0);
1740
1741pub fn poison_quarantine_pages_snapshot() -> usize {
1743 POISON_QUARANTINE_PAGES.load(AtomicOrdering::Relaxed)
1744}
1745
1746pub fn debug_buddy_lock_addr() -> usize {
1748 &BUDDY_ALLOCATOR as *const _ as usize
1749}
1750
1751static ALLOC_IN_PROGRESS: [core::sync::atomic::AtomicBool; crate::arch::x86_64::percpu::MAX_CPUS] =
1753 [const { core::sync::atomic::AtomicBool::new(false) }; crate::arch::x86_64::percpu::MAX_CPUS];
1754
1755struct LocalFrameCache {
1756 len: usize,
1757 frames: [u64; LOCAL_CACHE_CAPACITY],
1758}
1759
1760impl LocalFrameCache {
1761 const fn new() -> Self {
1762 Self {
1763 len: 0,
1764 frames: [0; LOCAL_CACHE_CAPACITY],
1765 }
1766 }
1767
1768 fn clear(&mut self) {
1769 self.len = 0;
1770 }
1771
1772 fn pop(&mut self) -> Option<PhysFrame> {
1773 if self.len == 0 {
1774 return None;
1775 }
1776 self.len -= 1;
1777 Some(PhysFrame {
1778 start_address: PhysAddr::new(self.frames[self.len]),
1779 })
1780 }
1781
1782 fn push(&mut self, frame: PhysFrame) -> Result<(), PhysFrame> {
1783 if self.len >= LOCAL_CACHE_CAPACITY {
1784 return Err(frame);
1785 }
1786 self.frames[self.len] = frame.start_address.as_u64();
1787 self.len += 1;
1788 Ok(())
1789 }
1790
1791 fn pop_many(&mut self, out: &mut [u64]) -> usize {
1792 let count = core::cmp::min(self.len, out.len());
1793 for slot in out.iter_mut().take(count) {
1794 self.len -= 1;
1795 *slot = self.frames[self.len];
1796 }
1797 count
1798 }
1799
1800 fn pop_many_for_zone(&mut self, out: &mut [u64], zone_idx: usize) -> usize {
1801 let mut written = 0usize;
1802 let mut idx = 0usize;
1803
1804 while idx < self.len && written < out.len() {
1805 let phys = self.frames[idx];
1806 if zone_index_for_phys(phys) != zone_idx {
1807 idx += 1;
1808 continue;
1809 }
1810
1811 self.len -= 1;
1812 out[written] = phys;
1813 written += 1;
1814 self.frames[idx] = self.frames[self.len];
1815 }
1816
1817 written
1818 }
1819}
1820
1821static LOCAL_FRAME_CACHES: [SpinLock<LocalFrameCache, PreemptDisabled>; LOCAL_CACHE_SLOTS] =
1835 [const { SpinLock::new(LocalFrameCache::new()) }; LOCAL_CACHE_SLOTS];
1836static LOCAL_CACHED_FRAMES: AtomicUsize = AtomicUsize::new(0);
1837static LOCAL_CACHED_ZONE_FRAMES: [AtomicUsize; ZoneType::COUNT] =
1838 [const { AtomicUsize::new(0) }; ZoneType::COUNT];
1839static LOCAL_CACHED_ZONE_MIGRATETYPE_FRAMES: [AtomicUsize; LOCAL_CACHED_ZONE_MIGRATETYPE_SLOTS] =
1840 [const { AtomicUsize::new(0) }; LOCAL_CACHED_ZONE_MIGRATETYPE_SLOTS];
1841
1842type GlobalGuard = SpinLockGuard<'static, Option<BuddyAllocator>>;
1843
1844struct OnDemandGlobalLock {
1845 guard: Option<GlobalGuard>,
1846}
1847
1848impl OnDemandGlobalLock {
1849 fn new() -> Self {
1850 Self { guard: None }
1851 }
1852
1853 fn unlock(&mut self) {
1854 self.guard = None;
1855 }
1856
1857 fn with_allocator<R>(
1858 &mut self,
1859 f: impl FnOnce(&mut BuddyAllocator, &IrqDisabledToken) -> R,
1860 ) -> Option<R> {
1861 let guard = self.guard.get_or_insert_with(|| BUDDY_ALLOCATOR.lock());
1862 guard.with_mut_and_token(|slot, token| slot.as_mut().map(|allocator| f(allocator, token)))
1863 }
1864
1865 fn alloc_with_migratetype(
1866 &mut self,
1867 order: u8,
1868 migratetype: Migratetype,
1869 ) -> Result<PhysFrame, AllocError> {
1870 self.with_allocator(|allocator, token| {
1871 allocator.alloc_locked_with_migratetype(order, migratetype, token)
1872 })
1873 .unwrap_or(Err(AllocError::OutOfMemory))
1874 }
1875
1876 fn free(&mut self, frame: PhysFrame, order: u8) {
1877 let _ = self.with_allocator(|allocator, token| allocator.free(frame, order, token));
1878 }
1879
1880 fn free_phys_batch(&mut self, phys_batch: &[u64], count: usize) {
1881 if count == 0 {
1882 return;
1883 }
1884 let _ = self.with_allocator(|allocator, token| {
1885 for phys in phys_batch.iter().take(count).copied() {
1886 allocator.free(
1887 PhysFrame {
1888 start_address: PhysAddr::new(phys),
1889 },
1890 0,
1891 token,
1892 );
1893 }
1894 });
1895 }
1896}
1897
1898#[inline]
1899fn zone_index_for_phys(phys: u64) -> usize {
1900 if phys < DMA_MAX {
1901 ZoneType::DMA as usize
1902 } else if phys < NORMAL_MAX {
1903 ZoneType::Normal as usize
1904 } else {
1905 ZoneType::HighMem as usize
1906 }
1907}
1908
1909#[inline]
1910fn local_cache_slot(cpu_idx: usize, migratetype: Migratetype) -> usize {
1911 migratetype.index() * crate::arch::x86_64::percpu::MAX_CPUS + cpu_idx
1912}
1913
1914#[inline]
1915fn local_cached_zone_migratetype_slot(zone_idx: usize, migratetype: Migratetype) -> usize {
1916 migratetype.index() * ZoneType::COUNT + zone_idx
1917}
1918
1919#[inline]
1920fn is_cacheable_phys_for(phys: u64, migratetype: Migratetype) -> bool {
1921 match migratetype {
1922 Migratetype::Unmovable => zone_index_for_phys(phys) == ZoneType::Normal as usize,
1923 Migratetype::Movable => zone_index_for_phys(phys) != ZoneType::DMA as usize,
1924 }
1925}
1926
1927#[inline]
1928fn local_cached_zone_migratetype_count(zone_idx: usize, migratetype: Migratetype) -> usize {
1929 LOCAL_CACHED_ZONE_MIGRATETYPE_FRAMES[local_cached_zone_migratetype_slot(zone_idx, migratetype)]
1930 .load(AtomicOrdering::Relaxed)
1931}
1932
1933#[inline]
1934fn local_cached_inc_phys(phys: u64, migratetype: Migratetype) {
1935 let zone_idx = zone_index_for_phys(phys);
1936 LOCAL_CACHED_FRAMES.fetch_add(1, AtomicOrdering::Relaxed);
1937 LOCAL_CACHED_ZONE_FRAMES[zone_idx].fetch_add(1, AtomicOrdering::Relaxed);
1938 LOCAL_CACHED_ZONE_MIGRATETYPE_FRAMES[local_cached_zone_migratetype_slot(zone_idx, migratetype)]
1939 .fetch_add(1, AtomicOrdering::Relaxed);
1940}
1941
1942#[inline]
1943fn local_cached_dec_phys(phys: u64, migratetype: Migratetype) {
1944 let prev_total = LOCAL_CACHED_FRAMES.fetch_sub(1, AtomicOrdering::Relaxed);
1945 debug_assert!(prev_total > 0);
1946 let zone = zone_index_for_phys(phys);
1947 let prev_zone = LOCAL_CACHED_ZONE_FRAMES[zone].fetch_sub(1, AtomicOrdering::Relaxed);
1948 debug_assert!(prev_zone > 0);
1949 let prev_zone_type = LOCAL_CACHED_ZONE_MIGRATETYPE_FRAMES
1950 [local_cached_zone_migratetype_slot(zone, migratetype)]
1951 .fetch_sub(1, AtomicOrdering::Relaxed);
1952 debug_assert!(prev_zone_type > 0);
1953}
1954
1955fn drain_local_caches_to_global(max_pages: usize, global: &mut OnDemandGlobalLock) -> usize {
1956 if max_pages == 0 {
1957 return 0;
1958 }
1959
1960 let mut drained = 0usize;
1961 let mut batch = [0u64; LOCAL_CACHE_FLUSH_BATCH];
1962 for migratetype in Migratetype::ALL {
1963 for cpu in 0..crate::arch::x86_64::percpu::MAX_CPUS {
1964 if drained >= max_pages {
1965 break;
1966 }
1967 let target = core::cmp::min(batch.len(), max_pages.saturating_sub(drained));
1968 if target == 0 {
1969 break;
1970 }
1971
1972 let popped = {
1973 let mut cache = LOCAL_FRAME_CACHES[local_cache_slot(cpu, migratetype)].lock();
1974 cache.pop_many(&mut batch[..target])
1975 };
1976 if popped == 0 {
1977 continue;
1978 }
1979
1980 for phys in batch.iter().take(popped).copied() {
1981 local_cached_dec_phys(phys, migratetype);
1982 }
1983 global.free_phys_batch(&batch, popped);
1984
1985 global.unlock();
1987 drained += popped;
1988 }
1989 }
1990
1991 drained
1992}
1993
1994fn drain_local_caches_for_zone(
1995 max_pages: usize,
1996 zone_idx: usize,
1997 primary_migratetype: Migratetype,
1998 global: &mut OnDemandGlobalLock,
1999) -> usize {
2000 if max_pages == 0 {
2001 return 0;
2002 }
2003
2004 let mut drained = 0usize;
2005 let mut batch = [0u64; LOCAL_CACHE_FLUSH_BATCH];
2006
2007 for migratetype in primary_migratetype.fallback_order() {
2008 for cpu in 0..crate::arch::x86_64::percpu::MAX_CPUS {
2009 if drained >= max_pages {
2010 return drained;
2011 }
2012
2013 let target = core::cmp::min(batch.len(), max_pages.saturating_sub(drained));
2014 if target == 0 {
2015 break;
2016 }
2017
2018 let popped = {
2019 let mut cache = LOCAL_FRAME_CACHES[local_cache_slot(cpu, migratetype)].lock();
2020 cache.pop_many_for_zone(&mut batch[..target], zone_idx)
2021 };
2022 if popped == 0 {
2023 continue;
2024 }
2025
2026 for phys in batch.iter().take(popped).copied() {
2027 local_cached_dec_phys(phys, migratetype);
2028 }
2029 global.free_phys_batch(&batch, popped);
2030 global.unlock();
2031 drained += popped;
2032 }
2033 }
2034
2035 if drained < max_pages {
2036 drained = drained.saturating_add(drain_local_caches_to_global(
2037 max_pages.saturating_sub(drained),
2038 global,
2039 ));
2040 }
2041
2042 drained
2043}
2044
2045pub fn init_buddy_allocator(memory_regions: &[MemoryRegion]) {
2047 for cache in &LOCAL_FRAME_CACHES {
2048 cache.lock().clear();
2049 }
2050 LOCAL_CACHED_FRAMES.store(0, AtomicOrdering::Relaxed);
2051 for zone_cached in &LOCAL_CACHED_ZONE_FRAMES {
2052 zone_cached.store(0, AtomicOrdering::Relaxed);
2053 }
2054 for zone_cached in &LOCAL_CACHED_ZONE_MIGRATETYPE_FRAMES {
2055 zone_cached.store(0, AtomicOrdering::Relaxed);
2056 }
2057
2058 {
2059 let mut guard = BUDDY_ALLOCATOR.lock();
2060 *guard = Some(BuddyAllocator::new());
2061 guard.with_mut_and_token(|slot, _token| {
2062 if let Some(allocator) = slot.as_mut() {
2063 allocator.init(memory_regions);
2064 }
2065 });
2066 }
2067 crate::sync::debug_set_trace_buddy_addr(debug_buddy_lock_addr());
2069}
2070
2071pub fn get_allocator() -> &'static SpinLock<Option<BuddyAllocator>> {
2073 &BUDDY_ALLOCATOR
2074}
2075
2076fn refill_local_cache(
2077 cpu_idx: usize,
2078 global: &mut OnDemandGlobalLock,
2079 migratetype: Migratetype,
2080) -> Result<PhysFrame, AllocError> {
2081 let (base, order) = match global.alloc_with_migratetype(LOCAL_CACHE_REFILL_ORDER, migratetype) {
2083 Ok(frame) => (frame, LOCAL_CACHE_REFILL_ORDER),
2084 Err(AllocError::OutOfMemory) => (global.alloc_with_migratetype(0, migratetype)?, 0),
2085 Err(e) => return Err(e),
2086 };
2087 global.unlock();
2088
2089 let frame_count = 1usize << order;
2090 let mut overflow = [0u64; LOCAL_CACHE_REFILL_FRAMES];
2091 let mut overflow_len = 0usize;
2092 let mut ret = None;
2093
2094 {
2095 let mut cache = LOCAL_FRAME_CACHES[local_cache_slot(cpu_idx, migratetype)].lock();
2096 for idx in 0..frame_count {
2097 let phys = base.start_address.as_u64() + (idx as u64) * PAGE_SIZE;
2098 let frame = PhysFrame {
2099 start_address: PhysAddr::new(phys),
2100 };
2101 if !is_cacheable_phys_for(phys, migratetype) {
2102 overflow[overflow_len] = phys;
2103 overflow_len += 1;
2104 continue;
2105 }
2106 if ret.is_none() {
2107 BuddyAllocator::mark_block_allocated(phys, 0, migratetype);
2111 ret = Some(frame);
2112 continue;
2113 }
2114 BuddyAllocator::mark_block_free(phys, 0, migratetype);
2117 if cache.push(frame).is_ok() {
2118 local_cached_inc_phys(phys, migratetype);
2119 } else {
2120 overflow[overflow_len] = phys;
2121 overflow_len += 1;
2122 }
2123 }
2124 }
2125
2126 if overflow_len != 0 {
2127 global.free_phys_batch(&overflow, overflow_len);
2128 }
2129
2130 ret.ok_or(AllocError::OutOfMemory)
2131}
2132
2133fn steal_from_other_caches(cpu_idx: usize, migratetype: Migratetype) -> Option<PhysFrame> {
2134 let cpu_count = crate::arch::x86_64::percpu::cpu_count()
2135 .max(1)
2136 .min(crate::arch::x86_64::percpu::MAX_CPUS);
2137
2138 for step in 1..cpu_count {
2139 let peer = (cpu_idx + step) % cpu_count;
2140 let mut cache = LOCAL_FRAME_CACHES[local_cache_slot(peer, migratetype)].lock();
2141 if let Some(frame) = cache.pop() {
2142 BuddyAllocator::mark_block_allocated(frame.start_address.as_u64(), 0, migratetype);
2143 local_cached_dec_phys(frame.start_address.as_u64(), migratetype);
2144 return Some(frame);
2145 }
2146 }
2147 None
2148}
2149
2150fn alloc_order0_cached(migratetype: Migratetype) -> Result<PhysFrame, AllocError> {
2151 let cpu_idx = crate::arch::x86_64::percpu::current_cpu_index();
2152
2153 {
2154 let mut cache = LOCAL_FRAME_CACHES[local_cache_slot(cpu_idx, migratetype)].lock();
2155 if let Some(frame) = cache.pop() {
2156 BuddyAllocator::mark_block_allocated(frame.start_address.as_u64(), 0, migratetype);
2157 local_cached_dec_phys(frame.start_address.as_u64(), migratetype);
2158 return Ok(frame);
2159 }
2160 }
2161
2162 let mut global = OnDemandGlobalLock::new();
2163
2164 if let Ok(frame) = refill_local_cache(cpu_idx, &mut global, migratetype) {
2165 return Ok(frame);
2166 }
2167 global.unlock();
2169
2170 if let Some(frame) = steal_from_other_caches(cpu_idx, migratetype) {
2171 return Ok(frame);
2172 }
2173
2174 global.alloc_with_migratetype(0, migratetype)
2175}
2176
2177fn free_order0_cached(frame: PhysFrame, migratetype: Migratetype) {
2178 if crate::memory::frame::block_phys_has_poison_guard(frame.start_address.as_u64(), 0) {
2181 let mut global = OnDemandGlobalLock::new();
2182 global.free(frame, 0);
2183 return;
2184 }
2185
2186 if !is_cacheable_phys_for(frame.start_address.as_u64(), migratetype) {
2187 let mut global = OnDemandGlobalLock::new();
2188 global.free(frame, 0);
2189 return;
2190 }
2191
2192 let cpu_idx = crate::arch::x86_64::percpu::current_cpu_index();
2193 let mut spill = [0u64; LOCAL_CACHE_FLUSH_BATCH];
2194
2195 let spill_len = {
2196 let mut cache = LOCAL_FRAME_CACHES[local_cache_slot(cpu_idx, migratetype)].lock();
2197 if cache.push(frame).is_ok() {
2198 BuddyAllocator::mark_block_free(frame.start_address.as_u64(), 0, migratetype);
2201 local_cached_inc_phys(frame.start_address.as_u64(), migratetype);
2202 return;
2203 }
2204
2205 let mut spill_len = cache.pop_many(&mut spill);
2207 for phys in spill.iter().take(spill_len).copied() {
2208 local_cached_dec_phys(phys, migratetype);
2209 }
2210
2211 if cache.push(frame).is_ok() {
2212 BuddyAllocator::mark_block_free(frame.start_address.as_u64(), 0, migratetype);
2213 local_cached_inc_phys(frame.start_address.as_u64(), migratetype);
2214 } else {
2215 spill[spill_len] = frame.start_address.as_u64();
2218 spill_len += 1;
2219 }
2220 spill_len
2221 };
2222
2223 if spill_len != 0 {
2224 let mut global = OnDemandGlobalLock::new();
2225 global.free_phys_batch(&spill, spill_len);
2226 }
2227}
2228
2229pub fn alloc(_token: &IrqDisabledToken, order: u8) -> Result<PhysFrame, AllocError> {
2234 alloc_migratetype(_token, order, Migratetype::Unmovable)
2235}
2236
2237pub fn alloc_migratetype(
2242 _token: &IrqDisabledToken,
2243 order: u8,
2244 migratetype: Migratetype,
2245) -> Result<PhysFrame, AllocError> {
2246 if crate::silo::debug_boot_reg_active() {
2247 crate::serial_println!(
2248 "[trace][buddy] alloc enter order={} migratetype={:?} buddy_lock={:#x}",
2249 order,
2250 migratetype,
2251 &BUDDY_ALLOCATOR as *const _ as usize
2252 );
2253 }
2254 if order == 0 {
2255 alloc_order0_cached(migratetype)
2256 } else {
2257 let mut global = OnDemandGlobalLock::new();
2258 match global.alloc_with_migratetype(order, migratetype) {
2259 Ok(frame) => Ok(frame),
2260 Err(AllocError::OutOfMemory) => {
2261 let candidate = global
2262 .with_allocator(|allocator, _token| {
2263 allocator.compaction_candidate(
2264 order,
2265 migratetype,
2266 BuddyAllocator::preferred_zone_order(migratetype),
2267 )
2268 })
2269 .flatten();
2270
2271 if let Some(candidate) = candidate {
2272 let budget = BuddyAllocator::compaction_drain_budget(candidate);
2273 global.unlock();
2274 let drained = drain_local_caches_for_zone(
2275 budget,
2276 candidate.zone_idx,
2277 migratetype,
2278 &mut global,
2279 );
2280 let retry = global.alloc_with_migratetype(order, migratetype);
2281 record_compaction_attempt(candidate, drained, retry.is_ok());
2282 if retry.is_ok() || drained != 0 {
2283 return retry;
2284 }
2285 } else {
2286 global.unlock();
2287 }
2288
2289 let _ = drain_local_caches_to_global(usize::MAX, &mut global);
2290 global.alloc_with_migratetype(order, migratetype)
2291 }
2292 Err(e) => Err(e),
2293 }
2294 }
2295}
2296
2297pub fn free(_token: &IrqDisabledToken, frame: PhysFrame, order: u8) {
2301 let migratetype = BuddyAllocator::block_migratetype(frame.start_address.as_u64());
2302 if order == 0 {
2303 free_order0_cached(frame, migratetype);
2304 } else {
2305 let mut global = OnDemandGlobalLock::new();
2306 global.free(frame, order);
2307 }
2308}
2309
2310impl FrameAllocator for BuddyAllocator {
2311 fn alloc(&mut self, order: u8, token: &IrqDisabledToken) -> Result<PhysFrame, AllocError> {
2313 self.alloc_locked_with_migratetype(order, Migratetype::Unmovable, token)
2314 }
2315
2316 fn free(&mut self, frame: PhysFrame, order: u8, token: &IrqDisabledToken) {
2318 let cpu_idx = crate::arch::x86_64::percpu::current_cpu_index();
2319 if ALLOC_IN_PROGRESS[cpu_idx].swap(true, core::sync::atomic::Ordering::Acquire) {
2320 panic!("Recursive deallocation detected on CPU {}!", cpu_idx);
2321 }
2322
2323 let frame_phys = frame.start_address.as_u64();
2324 let mut zi = Self::zone_index_for_addr(frame_phys);
2325
2326 if Self::find_segment_index(&self.zones[zi], frame_phys, order).is_none() {
2332 let mut found = false;
2333 for candidate_zi in 0..ZoneType::COUNT {
2334 if candidate_zi == zi {
2335 continue;
2336 }
2337 if Self::find_segment_index(&self.zones[candidate_zi], frame_phys, order).is_some()
2338 {
2339 serial_println!(
2340 "[buddy] WARN: frame 0x{:x} order {} belongs to zone[{}] {:?}, not zone[{}] {:?}; forwarding.",
2341 frame_phys, order,
2342 candidate_zi, self.zones[candidate_zi].zone_type,
2343 zi, self.zones[zi].zone_type,
2344 );
2345 zi = candidate_zi;
2346 found = true;
2347 break;
2348 }
2349 }
2350 if !found {
2351 serial_println!(
2352 "[buddy] free: frame 0x{:x} order {} not in any zone segment; checking all zones...",
2353 frame_phys, order,
2354 );
2355 for zzi in 0..ZoneType::COUNT {
2356 let z = &self.zones[zzi];
2357 serial_println!(
2358 " zone[{}] {:?}: segments={} page_count={}",
2359 zzi,
2360 z.zone_type,
2361 z.segment_count,
2362 z.page_count,
2363 );
2364 for si in 0..z.segment_count {
2365 let seg = &z.segments()[si];
2366 serial_println!(
2367 " segment[{}]: base=0x{:x} pages={} end=0x{:x}",
2368 si,
2369 seg.base.as_u64(),
2370 seg.page_count,
2371 seg.end_address(),
2372 );
2373 }
2374 }
2375 serial_println!(
2376 "[buddy] CRITICAL: frame 0x{:x} order {} belongs to no zone segment!",
2377 frame_phys,
2378 order,
2379 );
2380 }
2381 }
2382
2383 let zone = &mut self.zones[zi];
2384 if crate::memory::frame::block_phys_has_poison_guard(frame_phys, order) {
2388 Self::quarantine_poisoned_block_in_zone(zone, frame, order, token);
2389 } else {
2390 Self::free_to_zone(zone, frame, order, token);
2391 }
2392
2393 ALLOC_IN_PROGRESS[cpu_idx].store(false, core::sync::atomic::Ordering::Release);
2394 }
2395}
2396
2397impl BuddyAllocator {
2398 pub fn alloc_zone(
2400 &mut self,
2401 order: u8,
2402 zone: ZoneType,
2403 token: &IrqDisabledToken,
2404 ) -> Result<PhysFrame, AllocError> {
2405 self.alloc_zone_locked(order, zone, Migratetype::Unmovable, token)
2406 }
2407
2408 pub fn alloc_zone_migratetype(
2413 &mut self,
2414 order: u8,
2415 zone: ZoneType,
2416 migratetype: Migratetype,
2417 token: &IrqDisabledToken,
2418 ) -> Result<PhysFrame, AllocError> {
2419 self.alloc_zone_locked(order, zone, migratetype, token)
2420 }
2421}
2422
2423#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2428pub enum ZonePressure {
2429 Healthy,
2431 High,
2433 Low,
2435 Min,
2437}
2438
2439impl ZonePressure {
2440 const SNAPSHOT_COUNT: usize = 4;
2441
2442 #[inline]
2443 const fn as_snapshot(self) -> usize {
2444 match self {
2445 Self::Healthy => 0,
2446 Self::High => 1,
2447 Self::Low => 2,
2448 Self::Min => 3,
2449 }
2450 }
2451
2452 #[inline]
2453 const fn from_snapshot(value: usize) -> Option<Self> {
2454 match value {
2455 0 => Some(Self::Healthy),
2456 1 => Some(Self::High),
2457 2 => Some(Self::Low),
2458 3 => Some(Self::Min),
2459 _ => None,
2460 }
2461 }
2462}
2463
2464#[derive(Debug, Clone, Copy)]
2469pub struct ZoneStats {
2470 pub zone_type: ZoneType,
2472 pub base: u64,
2474 pub managed_pages: usize,
2476 pub present_pages: usize,
2478 pub spanned_pages: usize,
2480 pub reserved_pages: usize,
2482 pub allocated_pages: usize,
2484 pub cached_pages: usize,
2486 pub cached_unmovable_pages: usize,
2488 pub cached_movable_pages: usize,
2490 pub free_pages: usize,
2492 pub movable_free_pages: usize,
2494 pub unmovable_free_pages: usize,
2496 pub segment_count: usize,
2498 pub segment_capacity: usize,
2500 pub pageblock_count: usize,
2502 pub unmovable_pageblocks: usize,
2504 pub movable_pageblocks: usize,
2506 pub watermark_min: usize,
2508 pub watermark_low: usize,
2510 pub watermark_high: usize,
2512 pub lowmem_reserve_pages: usize,
2514 pub largest_free_order: Option<u8>,
2516}
2517
2518impl ZoneStats {
2519 pub const fn empty() -> Self {
2521 Self {
2522 zone_type: ZoneType::DMA,
2523 base: 0,
2524 managed_pages: 0,
2525 present_pages: 0,
2526 spanned_pages: 0,
2527 reserved_pages: 0,
2528 allocated_pages: 0,
2529 cached_pages: 0,
2530 cached_unmovable_pages: 0,
2531 cached_movable_pages: 0,
2532 free_pages: 0,
2533 movable_free_pages: 0,
2534 unmovable_free_pages: 0,
2535 segment_count: 0,
2536 segment_capacity: 0,
2537 pageblock_count: 0,
2538 unmovable_pageblocks: 0,
2539 movable_pageblocks: 0,
2540 watermark_min: 0,
2541 watermark_low: 0,
2542 watermark_high: 0,
2543 lowmem_reserve_pages: 0,
2544 largest_free_order: None,
2545 }
2546 }
2547
2548 #[inline]
2550 pub fn hole_pages(&self) -> usize {
2551 self.spanned_pages.saturating_sub(self.managed_pages)
2552 }
2553
2554 #[inline]
2556 pub fn reserve_floor_pages(&self) -> usize {
2557 self.watermark_min.saturating_add(self.lowmem_reserve_pages)
2558 }
2559
2560 #[inline]
2562 pub fn available_after_reserve_pages(&self) -> usize {
2563 self.free_pages.saturating_sub(self.reserve_floor_pages())
2564 }
2565
2566 pub fn pressure(&self) -> ZonePressure {
2568 let reserve_floor = self.reserve_floor_pages();
2569 let low_floor = self.watermark_low.saturating_add(self.lowmem_reserve_pages);
2570 let high_floor = self
2571 .watermark_high
2572 .saturating_add(self.lowmem_reserve_pages);
2573
2574 if self.free_pages <= reserve_floor {
2575 ZonePressure::Min
2576 } else if self.free_pages <= low_floor {
2577 ZonePressure::Low
2578 } else if self.free_pages <= high_floor {
2579 ZonePressure::High
2580 } else {
2581 ZonePressure::Healthy
2582 }
2583 }
2584}
2585
2586#[derive(Debug, Clone, Copy)]
2591pub struct CompactionStats {
2592 pub attempts: usize,
2594 pub successes: usize,
2596 pub last_order: Option<u8>,
2598 pub last_migratetype: Option<Migratetype>,
2600 pub last_zone: Option<ZoneType>,
2602 pub last_pressure: Option<ZonePressure>,
2604 pub last_fragmentation_score: usize,
2606 pub last_requested_pages: usize,
2608 pub last_available_pages: usize,
2610 pub last_usable_pages: usize,
2612 pub last_cached_pages: usize,
2614 pub last_drained_pages: usize,
2616 pub last_pageblock_count: usize,
2618 pub last_matching_pageblocks: usize,
2620}
2621
2622impl CompactionStats {
2623 pub const fn empty() -> Self {
2625 Self {
2626 attempts: 0,
2627 successes: 0,
2628 last_order: None,
2629 last_migratetype: None,
2630 last_zone: None,
2631 last_pressure: None,
2632 last_fragmentation_score: 0,
2633 last_requested_pages: 0,
2634 last_available_pages: 0,
2635 last_usable_pages: 0,
2636 last_cached_pages: 0,
2637 last_drained_pages: 0,
2638 last_pageblock_count: 0,
2639 last_matching_pageblocks: 0,
2640 }
2641 }
2642}
2643
2644impl BuddyAllocator {
2645 pub fn page_totals(&self) -> (usize, usize) {
2647 let mut total_pages = 0usize;
2648 let mut allocated_pages = 0usize;
2649 for zone in &self.zones {
2650 total_pages = total_pages.saturating_add(zone.page_count);
2651 allocated_pages = allocated_pages.saturating_add(zone.allocated);
2652 }
2653 let cached_pages = LOCAL_CACHED_FRAMES.load(AtomicOrdering::Relaxed);
2654 allocated_pages = allocated_pages.saturating_sub(cached_pages);
2655 (total_pages, allocated_pages)
2656 }
2657
2658 pub fn get_zone(&self, idx: usize) -> &Zone {
2660 &self.zones[idx]
2661 }
2662
2663 pub fn zone_snapshot(&self, out: &mut [ZoneStats]) -> usize {
2666 let n = core::cmp::min(out.len(), self.zones.len());
2667 for (i, zone) in self.zones.iter().take(n).enumerate() {
2668 let cached_unmovable = local_cached_zone_migratetype_count(i, Migratetype::Unmovable);
2669 let cached_movable = local_cached_zone_migratetype_count(i, Migratetype::Movable);
2670 let cached = cached_unmovable.saturating_add(cached_movable);
2671 let pageblocks = Self::zone_pageblock_counts(zone);
2672 let mut free_by_type = zone.free_pages_by_migratetype();
2673 free_by_type[Migratetype::Unmovable.index()] =
2674 free_by_type[Migratetype::Unmovable.index()].saturating_add(cached_unmovable);
2675 free_by_type[Migratetype::Movable.index()] =
2676 free_by_type[Migratetype::Movable.index()].saturating_add(cached_movable);
2677 out[i] = ZoneStats {
2678 zone_type: zone.zone_type,
2679 base: zone.base.as_u64(),
2680 managed_pages: zone.page_count,
2681 present_pages: zone.present_pages,
2682 spanned_pages: zone.span_pages,
2683 reserved_pages: zone.reserved_pages,
2684 allocated_pages: zone.allocated.saturating_sub(cached),
2685 cached_pages: cached,
2686 cached_unmovable_pages: cached_unmovable,
2687 cached_movable_pages: cached_movable,
2688 free_pages: Self::zone_effective_free_pages(zone, i),
2689 movable_free_pages: free_by_type[Migratetype::Movable.index()],
2690 unmovable_free_pages: free_by_type[Migratetype::Unmovable.index()],
2691 segment_count: zone.segment_count,
2692 segment_capacity: zone.segment_capacity,
2693 pageblock_count: pageblocks[Migratetype::Unmovable.index()]
2694 .saturating_add(pageblocks[Migratetype::Movable.index()]),
2695 unmovable_pageblocks: pageblocks[Migratetype::Unmovable.index()],
2696 movable_pageblocks: pageblocks[Migratetype::Movable.index()],
2697 watermark_min: zone.watermark_min,
2698 watermark_low: zone.watermark_low,
2699 watermark_high: zone.watermark_high,
2700 lowmem_reserve_pages: zone.lowmem_reserve_pages,
2701 largest_free_order: zone.largest_free_order(),
2702 };
2703 }
2704 n
2705 }
2706}