Skip to main content

strat9_kernel/memory/
buddy.rs

1// Buddy allocator implementation
2//
3// Refcount sentinel invariant (OSTD-style, fully enforced):
4//
5//   free-list frame => refcount == REFCOUNT_UNUSED  (u32::MAX)
6//   live frame      => refcount >= 1
7//
8// `mark_block_free()` stamps REFCOUNT_UNUSED on every free path.
9// `mark_block_allocated()` leaves refcount untouched (still REFCOUNT_UNUSED)
10// so that `FrameAllocOptions::allocate()` can perform a fail-fast
11// CAS(REFCOUNT_UNUSED → 1) that catches double-free / free-list corruption
12// immediately rather than silently aliasing memory.
13
14use 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    /// Per-zone bitmap pool reserved from free memory: [start, end).
74    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    /// Creates a new instance.
95    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    /// Performs the init operation.
107    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        // Dump memory regions for diagnostic (compare QEMU vs VMware maps)
120        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        // Pass 1: compute per-zone address span (base + span_pages)
149        self.pass_count(memory_regions);
150
151        // Diagnostic: log span info for each zone (helps diagnose VMware memory map issues)
152        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        // Pass 2: reserve per-zone bitmap pools using an upper bound derived
163        // from the boot allocator's current free extents.
164        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        ///////////
173        // Pass 3: reserve exact segment storage from the remaining accessible
174        // boot memory and then build the final segmented buddy layout from the boot
175        // allocator's remaining free ranges after bitmap and segment-storage
176        // reservations.
177        ///////////
178        // CRITICAL HERE : re-snapshot after pass_reserve_segment_storage
179        // because it consumes pages from the boot allocator.
180        // Using the stale snapshot would cause the buddy to build segments spanning pages that
181        // actually hold the ZoneSegment metadata, leading to silent corruption
182        // when those pages are later allocated and written by a live frame owner.
183        ///////////
184        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        // Re-snapshot: segment-storage pages are now consumed from the boot
194        // allocator and must not appear in any buddy segment.
195        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        // Seal the boot allocator: all its remaining free regions are now managed
203        // by buddy.  Any later boot_alloc::alloc_stack() call would otherwise
204        // double-allocate pages that buddy already tracks in its free lists.
205        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    /// Performs the pass count operation.
242    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    /// Reserve per-zone segment tables sized to the actual fragmented layout.
286    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    /// Reserve per-zone bitmap pools using a segmentation-safe upper bound.
325    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            // Zero stolen pages to initialize all bitmaps to 0.
359            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    /// Finalise zone accounting once the managed segment set is known.
370    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    /// Compute a bounded watermark target for a zone.
390    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    /// Compute a bounded low-memory reserve target.
400    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    /// Bound a policy target to something meaningful for the current zone size.
409    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    /// Build the final segmented physical layout from remaining boot allocator ranges.
427    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    /// Assign bitmap slices to each populated segment.
472    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    /// Seed each contiguous segment with greedy block insertion.
541    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    /// Seeds a contiguous physical range `[start, end)` as free using greedy block insertion.
558    ///
559    /// Unlike the previous min/max span design, `segment` is guaranteed to be a
560    /// genuinely contiguous free extent. Greedy seeding therefore improves boot
561    /// time without ever making holes visible to the buddy topology.
562    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    /// Allocates from zone.
631    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    /// Allocate from one contiguous segment.
657    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    /// Releases to zone.
731    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            ////////////// REMOVE HERE DIAGNOSTIC ///////////////////////////////////
738            //
739            // diagnostic: dump zone/segment state to help identify the root cause.
740            //
741            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            //
766            /////////////////// REMOVE HERE /////////////////////////
767
768            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    /// Drops allocator accounting for a poisoned block without returning it to the free list.
814    ///
815    /// The block is **not** placed on any free list and its debug-bitmap entries
816    /// remain marked as "allocated" : because they genuinely are: the pages are
817    /// quarantined and inaccessible.  Clearing them would defeat the double-free
818    /// detector for any later attempt to free the same block.
819    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        // Intentionally NO mark_allocated(false) here : pages stay "allocated"
851        // in the debug bitmap because they are quarantined, not freed.
852
853        zone.allocated = zone.allocated.saturating_sub(1usize << order);
854        POISON_QUARANTINE_PAGES.fetch_add(1usize << order, AtomicOrdering::Relaxed);
855    }
856
857    /// Linux-style parity-map coalescing insertion.
858    /// Returns after inserting the (potentially coalesced) block into the appropriate free list, without recursing further.
859    /// If the buddy bit is already set or we reach MAX_ORDER, the block is inserted as-is.
860    /// Otherwise, the buddy block is removed from its free list and coalesced with the current block, and the process repeats at the next order.
861    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    /// Performs the page index operation.
904    #[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    /// Performs the pair index operation.
914    #[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    /// Performs the toggle pair operation.
920    #[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    /// Performs the buddy phys operation.
932    #[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    /// Performs the mark allocated operation.
949    #[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    /// Releases list push.
973    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    /// Releases list pop.
988    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    /// Releases list remove.
1008    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    /// Reads free next.
1036    #[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    /// Writes free next.
1047    #[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    /// Reads free prev.
1057    #[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    /// Writes free prev.
1068    #[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    /// Performs the zone index for addr operation.
1078    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    /// Performs the zone bounds operation.
1089    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    /// Performs the zone intersection aligned operation.
1098    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        // Reserve physical address 0 as sentinel/not-usable.
1114        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    /// Performs the protected overlap end operation.
1124    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    /// Performs the protected module ranges operation.
1140    fn protected_module_ranges() -> [Option<(u64, u64)>; boot_alloc::MAX_PROTECTED_RANGES] {
1141        boot_alloc::protected_ranges_snapshot()
1142    }
1143
1144    /// Performs the pairs for order operation.
1145    #[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    /// Performs the bits to bytes operation.
1152    #[inline]
1153    fn bits_to_bytes(bits: usize) -> usize {
1154        bits.div_ceil(8)
1155    }
1156
1157    /// Performs the bitmap bytes for span operation.
1158    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    /// Upper bound for bitmap storage over any segmentation of `page_count` pages.
1172    ///
1173    /// For one page, every order contributes at most one parity bit. Summing that
1174    /// pessimistic bound across all pages yields a simple safe allocation bound,
1175    /// even if bitmap-pool reservations split ranges further.
1176    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    /// Exact byte count required for pageblock migratetype tags over one contiguous span.
1187    #[inline]
1188    fn pageblock_tag_bytes_for_span(span_pages: usize) -> usize {
1189        span_pages.div_ceil(PAGEBLOCK_PAGES)
1190    }
1191
1192    /// Safe upper bound for pageblock-tag storage across any segmentation of `page_count` pages.
1193    #[inline]
1194    fn pageblock_tag_bytes_upper_bound_for_pages(page_count: usize) -> usize {
1195        page_count
1196    }
1197
1198    /// Performs the align up operation.
1199    #[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    /// Performs the align down operation.
1206    #[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    /// Default pageblock migratetype assigned at bootstrap for one zone.
1213    #[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    /// Returns the pageblock index covering `phys` inside `segment`.
1222    #[inline]
1223    fn pageblock_index(segment: &ZoneSegment, phys: u64) -> usize {
1224        Self::page_index(segment, phys) / PAGEBLOCK_PAGES
1225    }
1226
1227    /// Decode one pageblock tag byte into a migratetype.
1228    #[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    /// Returns the current pageblock migratetype for a block start.
1237    #[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    /// Retag every pageblock overlapped by the buddy block `[phys, phys + 2^order * PAGE_SIZE)`.
1252    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    /// Count pageblocks by migratetype for one zone.
1276    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    /// Returns whether the zone should be considered for the current request.
1297    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    /// Returns whether a buddy block is free and coalescible with `migratetype`.
1317    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    /// Decode the block migratetype stored in frame metadata flags.
1327    fn block_migratetype(frame_phys: u64) -> Migratetype {
1328        Self::migratetype_from_flags(get_meta(PhysAddr::new(frame_phys)).get_flags())
1329    }
1330
1331    /// Decode a migratetype from frame flags.
1332    #[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    /// Encode the metadata flags for a free block of the given migratetype.
1342    #[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    /// Encode the metadata flags for an allocated block of the given migratetype.
1351    #[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    /// Try to allocate from the supplied zone order, first honoring reserves and then bypassing them.
1360    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    /// Returns the preferred zone scan order for one migratetype.
1385    ///
1386    /// Unmovable allocations still prefer `Normal` first because the current
1387    /// kernel hot-touches those pages directly. Movable allocations instead
1388    /// prefer `HighMem` first to preserve scarce low memory for pinned kernel
1389    /// structures and emergency paths.
1390    #[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    /// Allocate while the caller already owns the global allocator lock.
1509    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    /// Allocate from one explicit zone while the caller already owns the global allocator lock.
1541    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            // Sentinel must still be intact at this point : if not, the frame
1576            // was never on the free list (double-alloc or metadata corruption).
1577            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            // Leave refcount as REFCOUNT_UNUSED; FrameAllocOptions::allocate()
1586            // will perform CAS(REFCOUNT_UNUSED → 1) as the fail-fast handoff.
1587        }
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    /// Stamp every 4 KiB [`MetaSlot`] in the buddy block (flags, order, free-list links, refcount).
1600    ///
1601    /// [`MetaSlot::reset_with_free_list_meta`] runs on **each** page, including non-head pages
1602    /// of a multi-page block: the whole block returns to the buddy as one unit, so vtable and
1603    /// guard bits are cleared (except poison preserved per-slot) on every constituent frame.
1604    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
1621/// Per-order allocation failure counters.
1622///
1623/// `BUDDY_ALLOC_FAIL_COUNTS[order]` counts how many times a request for
1624/// `order` failed to find a free block at `order` or any higher order.
1625/// These are incremented in `alloc_from_zone` when the loop exhausts all
1626/// orders without finding a free block.
1627///
1628/// Read via `buddy_alloc_fail_counts_snapshot()` for diagnostics.
1629static 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
1648/// Records a buddy allocation failure for the given order.
1649///
1650/// Called from `alloc_from_zone` when no free block is available at any
1651/// order >= `order`. Increments the per-order counter for diagnostics.
1652pub(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
1659/// Returns the buddy allocation failure counts by order.
1660///
1661/// Use this for diagnostics : e.g., to determine whether a heap panic is
1662/// caused by genuine memory pressure or by high-order fragmentation.
1663pub 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
1709/// Snapshot compaction-assist telemetry without locking the allocator.
1710pub 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
1738/// Pages permanently withheld from the buddy free lists due to [`meta_guard::POISONED`].
1739static POISON_QUARANTINE_PAGES: AtomicUsize = AtomicUsize::new(0);
1740
1741/// Snapshot of pages quarantined (not recycled) because frame metadata reported poison.
1742pub fn poison_quarantine_pages_snapshot() -> usize {
1743    POISON_QUARANTINE_PAGES.load(AtomicOrdering::Relaxed)
1744}
1745
1746/// Returns the global buddy lock address for deadlock tracing.
1747pub fn debug_buddy_lock_addr() -> usize {
1748    &BUDDY_ALLOCATOR as *const _ as usize
1749}
1750
1751/// Per-CPU flag to detect recursive allocations (deadlocks from logs/interrupts)
1752static 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
1821/// Per-CPU frame caches protected by a `PreemptDisabled` guardian.
1822///
1823/// These caches are only accessed from `alloc_order0_cached` / `free_order0_cached`,
1824/// which are always called with IRQs already disabled by the caller (via
1825/// `IrqDisabledToken`).  Using `PreemptDisabled` instead of the default
1826/// `IrqDisabled` avoids redundant RFLAGS save/restore on every lock
1827/// acquisition while still preventing preemption-driven data races.
1828///
1829/// # Safety invariant
1830///
1831/// If any future code path acquires a `LOCAL_FRAME_CACHES` lock from an
1832/// interrupt handler or without IRQs disabled, this must be reverted to
1833/// `SpinLock<LocalFrameCache>` (default `IrqDisabled` guardian).
1834static 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            // Keep lock acquisition on-demand during cross-CPU draining.
1986            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
2045/// Initializes buddy allocator.
2046pub 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    // Race/corruption diagnostic: register buddy lock for E9 LOCK-A/LOCK-R traces.
2068    crate::sync::debug_set_trace_buddy_addr(debug_buddy_lock_addr());
2069}
2070
2071/// Returns allocator.
2072pub 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    // Critical path: refill in batches from the global allocator to amortize lock contention.
2082    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                // Re-publish the returned page as an allocated order-0 block.
2108                // The refcount must stay REFCOUNT_UNUSED so FrameAllocOptions
2109                // can still claim it via CAS(UNUSED -> 1).
2110                BuddyAllocator::mark_block_allocated(phys, 0, migratetype);
2111                ret = Some(frame);
2112                continue;
2113            }
2114            // Pages parked in the local cache are logically free and must
2115            // therefore carry the free-list sentinel invariant.
2116            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    // Critical lock-order rule: never hold global while probing local caches.
2168    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    // NOTE: O(2^order) MetaSlot scan : acceptable here because order is always 0
2179    // (single-page check) on this hot path.
2180    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            // Mark free only on the success path: the incoming frame transitions
2199            // from "caller-allocated" to "cache sentinel" (REFCOUNT_UNUSED).
2200            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        // Cache full: pop existing frames to spill to buddy, then retry the push.
2206        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            // Still full after spilling : the incoming frame joins the spill batch.
2216            // It will be marked free by free_phys_batch → free_to_zone.
2217            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
2229/// Allocate frames with per-CPU caching on order-0 requests.
2230///
2231/// `_token` is a compile-time proof that interrupts are disabled on the calling CPU,
2232/// preventing re-entrant allocation through an interrupt handler on the same lock.
2233pub fn alloc(_token: &IrqDisabledToken, order: u8) -> Result<PhysFrame, AllocError> {
2234    alloc_migratetype(_token, order, Migratetype::Unmovable)
2235}
2236
2237/// Allocate frames with an explicit migratetype preference.
2238///
2239/// Order-0 allocations use a per-CPU cache partitioned by migratetype so the
2240/// fast path preserves the caller's mobility class.
2241pub 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
2297/// Free frames with per-CPU caching on order-0 requests.
2298///
2299/// `_token` is a compile-time proof that interrupts are disabled on the calling CPU.
2300pub 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    /// Performs the alloc operation.
2312    fn alloc(&mut self, order: u8, token: &IrqDisabledToken) -> Result<PhysFrame, AllocError> {
2313        self.alloc_locked_with_migratetype(order, Migratetype::Unmovable, token)
2314    }
2315
2316    /// Performs the free operation.
2317    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        // Verify the address-selected zone actually contains this frame in its
2327        // segment geometry. If not, search all zones to find the correct one.
2328        // This handles edge cases where boot-allocator consumption of DMA-region
2329        // pages (frame metadata, bitmap pools) leaves physical addresses below
2330        // DMA_MAX that are outside any DMA segment.
2331        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        // NOTE: O(2^order) MetaSlot scan. Acceptable for large-order frees
2385        // (kernel stacks, vmalloc) which are rare; order-0 path is handled
2386        // separately in free_order0_cached with a single-page check.
2387        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    /// Allocate explicitly from one zone (e.g. DMA-only callers).
2399    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    /// Allocate explicitly from one zone with a migratetype hint.
2409    ///
2410    /// This keeps the target zone fixed but still selects the preferred
2411    /// free-list class and fallback donor order from `migratetype`.
2412    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/// Derived pressure state for a zone snapshot.
2424///
2425/// Thresholds are evaluated against the zone's effective free pages, including
2426/// pages parked in order-0 per-CPU caches.
2427#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2428pub enum ZonePressure {
2429    /// Free pages are above the high watermark.
2430    Healthy,
2431    /// Free pages dropped below the high watermark.
2432    High,
2433    /// Free pages dropped below the low watermark.
2434    Low,
2435    /// Free pages reached the minimum watermark plus reserve floor.
2436    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/// Snapshot statistics for a single memory zone.
2465///
2466/// The struct is plain data on purpose so low-level diagnostics and crash paths
2467/// can snapshot it onto the stack without heap allocation.
2468#[derive(Debug, Clone, Copy)]
2469pub struct ZoneStats {
2470    /// Zone classification.
2471    pub zone_type: ZoneType,
2472    /// Lowest physical address covered by the zone span.
2473    pub base: u64,
2474    /// Pages currently managed by buddy in this zone.
2475    pub managed_pages: usize,
2476    /// Pages reported as usable by the firmware map before reservations.
2477    pub present_pages: usize,
2478    /// Outer span in pages, including holes.
2479    pub spanned_pages: usize,
2480    /// Pages removed from management during bootstrap.
2481    pub reserved_pages: usize,
2482    /// Pages allocated to live callers.
2483    pub allocated_pages: usize,
2484    /// Order-0 pages currently parked in per-CPU caches.
2485    pub cached_pages: usize,
2486    /// Cached pages parked in unmovable per-CPU caches.
2487    pub cached_unmovable_pages: usize,
2488    /// Cached pages parked in movable per-CPU caches.
2489    pub cached_movable_pages: usize,
2490    /// Effective free pages, including cached pages.
2491    pub free_pages: usize,
2492    /// Free pages tracked in movable free lists.
2493    pub movable_free_pages: usize,
2494    /// Free pages tracked in unmovable free lists.
2495    pub unmovable_free_pages: usize,
2496    /// Number of populated contiguous segments.
2497    pub segment_count: usize,
2498    /// Reserved segment-table capacity.
2499    pub segment_capacity: usize,
2500    /// Total number of pageblocks tracked across all segments.
2501    pub pageblock_count: usize,
2502    /// Pageblocks currently tagged unmovable.
2503    pub unmovable_pageblocks: usize,
2504    /// Pageblocks currently tagged movable.
2505    pub movable_pageblocks: usize,
2506    /// Minimum watermark.
2507    pub watermark_min: usize,
2508    /// Low watermark.
2509    pub watermark_low: usize,
2510    /// High watermark.
2511    pub watermark_high: usize,
2512    /// Low-memory reserve kept for lower-priority paths.
2513    pub lowmem_reserve_pages: usize,
2514    /// Largest currently available free order.
2515    pub largest_free_order: Option<u8>,
2516}
2517
2518impl ZoneStats {
2519    /// Empty snapshot entry for stack-allocated arrays.
2520    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    /// Returns the number of hole pages inside the zone span.
2549    #[inline]
2550    pub fn hole_pages(&self) -> usize {
2551        self.spanned_pages.saturating_sub(self.managed_pages)
2552    }
2553
2554    /// Returns the effective reserve floor enforced by policy.
2555    #[inline]
2556    pub fn reserve_floor_pages(&self) -> usize {
2557        self.watermark_min.saturating_add(self.lowmem_reserve_pages)
2558    }
2559
2560    /// Returns the free pages remaining after the reserve floor is discounted.
2561    #[inline]
2562    pub fn available_after_reserve_pages(&self) -> usize {
2563        self.free_pages.saturating_sub(self.reserve_floor_pages())
2564    }
2565
2566    /// Returns the derived pressure state from the current zone watermarks.
2567    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/// Snapshot of the last fragmentation-driven compaction assist attempt.
2587///
2588/// The fields are intentionally plain data so crash dumps and shell commands
2589/// can read them without locking or heap allocation.
2590#[derive(Debug, Clone, Copy)]
2591pub struct CompactionStats {
2592    /// Number of targeted compaction assists attempted after an allocation miss.
2593    pub attempts: usize,
2594    /// Number of attempts that yielded a successful retry.
2595    pub successes: usize,
2596    /// Last requested buddy order that triggered a targeted drain.
2597    pub last_order: Option<u8>,
2598    /// Mobility class of the last assisted allocation.
2599    pub last_migratetype: Option<Migratetype>,
2600    /// Zone selected as the preferred compaction target.
2601    pub last_zone: Option<ZoneType>,
2602    /// Pressure state observed on that zone before draining caches.
2603    pub last_pressure: Option<ZonePressure>,
2604    /// Fragmentation score that justified the assist path.
2605    pub last_fragmentation_score: usize,
2606    /// Pages requested by the original allocation.
2607    pub last_requested_pages: usize,
2608    /// Effective free pages left above reserves in the chosen zone.
2609    pub last_available_pages: usize,
2610    /// Free pages already available at or above the requested order.
2611    pub last_usable_pages: usize,
2612    /// Order-0 pages parked in local caches for the chosen zone.
2613    pub last_cached_pages: usize,
2614    /// Pages actually drained from local caches during the last attempt.
2615    pub last_drained_pages: usize,
2616    /// Total pageblocks tracked in the selected zone.
2617    pub last_pageblock_count: usize,
2618    /// Pageblocks already tagged with the requested migratetype.
2619    pub last_matching_pageblocks: usize,
2620}
2621
2622impl CompactionStats {
2623    /// Empty snapshot used before any assisted drain happened.
2624    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    /// Fast totals without heap allocation (safe in low-level paths).
2646    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    /// Get a reference to a zone by index.
2659    pub fn get_zone(&self, idx: usize) -> &Zone {
2660        &self.zones[idx]
2661    }
2662
2663    /// Snapshot zones without heap allocation.
2664    /// Returns the number of entries written to `out`.
2665    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}