patina_dxe_core 21.0.1

A pure rust implementation of the UEFI DXE Core.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
//! DXE Core Global Coherency Domain (GCD)
//!
//! ## License
//!
//! Copyright (c) Microsoft Corporation.
//!
//! SPDX-License-Identifier: Apache-2.0
//!
mod io_block;
mod memory_block;
mod spin_locked_gcd;

pub use spin_locked_gcd::DescriptorFilter;

use goblin::pe::section_table;

use alloc::boxed::Box;
use core::{cell::Cell, ffi::c_void, ops::Range};
use patina::{
    base::{align_down, align_up},
    error::EfiError,
    pi::{
        dxe_services::{GcdIoType, GcdMemoryType, MemorySpaceDescriptor},
        hob::{self, Hob, HobList, PhaseHandoffInformationTable},
    },
};
use patina_internal_cpu::paging::{PatinaPageTable, create_cpu_paging};
use r_efi::efi;

#[cfg(feature = "compatibility_mode_allowed")]
use patina::base::{UEFI_PAGE_SIZE, align_range};

use crate::{GCD, gcd::spin_locked_gcd::PagingAllocator, pecoff};

pub use spin_locked_gcd::{AllocateType, MapChangeType, SpinLockedGcd};

/// The MemoryProtectionPolicy struct is the source of truth for Patina's memory protection rules.
/// All memory protection decisions in Patina are driven by functions in this struct to have one
/// easily auditable location.
///
/// All rules in this struct are associated functions (don't require an instantiation of the struct)
/// except for the apply_default_allocated_memory_protection_policy function, because this relies on
/// internal state. The GCD contains a MemoryProtectionPolicy instance to manage this state.
pub(crate) struct MemoryProtectionPolicy {
    /// The default attributes for memory allocations. This will be efi::MEMORY_XP unless
    /// we have entered compatibility mode, in which case it is 0, e.g. no protection
    memory_allocation_default_attributes: Cell<u64>,
}

impl MemoryProtectionPolicy {
    /// Create a new MemoryProtectionPolicy instance with default settings.
    pub(crate) const fn new() -> Self {
        Self { memory_allocation_default_attributes: Cell::new(efi::MEMORY_XP) }
    }

    /// Rule: All memory allocations will be marked as the set cache type with NX applied. If compatibility mode
    /// has been activated, no protections will be applied.
    ///
    /// Arguments
    /// * `attributes` - The cache attributes to apply to the allocated memory
    ///
    /// Use Case: This is called whenever memory is allocated via the GCD to ensure
    /// allocated memory is NX by default.
    pub(crate) const fn apply_allocated_memory_protection_policy(&self, attributes: u64) -> u64 {
        (attributes & efi::CACHE_ATTRIBUTE_MASK) | self.memory_allocation_default_attributes.get()
    }

    /// Rule: All resource descriptor HOBs are initially mapped as the supplied cache attribute
    /// (for Resource Descriptor v2 HOBs) with NX applied. All system memory is marked as RP because
    /// we default to it being unmapped until allocated. Reserved and MMIO memory we must map by default
    /// to preserve compatibility with drivers that expect to be able to access those regions automatically.
    ///
    /// Arguments
    /// * `attributes` - The memory attributes from the resource descriptor HOB (if applicable)
    /// * `memory_type` - The GCD memory type being mapped
    ///
    /// Use Case: This is called when we are processing the initial resource descriptor HOBs into the GCD.
    pub(crate) fn apply_resc_desc_hobs_protection_policy(attributes: u64, memory_type: GcdMemoryType) -> u64 {
        let mut new_attributes = (attributes & efi::CACHE_ATTRIBUTE_MASK) | efi::MEMORY_XP;

        if memory_type == GcdMemoryType::SystemMemory {
            new_attributes |= efi::MEMORY_RP;
        }

        new_attributes
    }

    /// Rule: If we have Uncached memory, we must also apply NX to it.
    /// In DXE, we should never be executing from UC memory. On AArch64, this is defined as
    /// a programming error to have executable device memory (which UC maps to).
    ///
    /// Arguments
    /// * `attributes` - The memory attributes to evaluate whether NX needs to be applied
    ///
    /// Use Case: This is called whenever memory attributes are being set in the GCD to ensure
    /// that Uncached memory is not executable.
    pub(crate) const fn apply_nx_to_uc_policy(attributes: u64) -> u64 {
        let mut new_attributes = attributes;
        if new_attributes & efi::MEMORY_UC == efi::MEMORY_UC {
            new_attributes |= efi::MEMORY_XP;
        }

        new_attributes
    }

    /// Rule: The Memory Attributes Table, per UEFI spec, may only have RO, XP, and Runtime set. Only
    /// RuntimeServicesCode and RuntimeServicesData are reported in the MAT. RuntimeServicesCode memory consists
    /// of code sections, data sections, and potentially extra unused memory for padding.
    ///   - If a Runtime Services Code region has no attributes set, mark it as RO, XP, and Runtime. This will
    ///     prevent unused memory from being executed or written to.
    ///   - If a Runtime Services Data region has no attributes set, mark it as XP and Runtime to ensure data can be
    ///     used but not executed.
    ///   - Otherwise, filter the attributes to only the allowed attributes
    ///
    /// Arguments
    /// * `attributes` - The memory attributes for this region
    /// * `memory_type` - The GCD memory type for this region
    ///
    /// Use Case: This is called when building the Memory Attributes Table prior to installing it.
    pub(crate) const fn apply_memory_attributes_table_policy(attributes: u64, memory_type: efi::MemoryType) -> u64 {
        const ALLOWED_MAT_ATTRIBUTES: u64 = efi::MEMORY_RO | efi::MEMORY_XP | efi::MEMORY_RUNTIME;

        match attributes & (efi::MEMORY_RO | efi::MEMORY_XP) {
            // if we don't have any attributes set here, we should mark code as RO and XP. These are
            // likely extra sections in the memory bins and so should not be used
            // Data we will mark as XP only, as likely the caching attributes were changed, which
            // dropped the XP attribute, so we need to set it here.
            0 if memory_type == efi::RUNTIME_SERVICES_CODE => ALLOWED_MAT_ATTRIBUTES,
            0 if memory_type == efi::RUNTIME_SERVICES_DATA => efi::MEMORY_RUNTIME | efi::MEMORY_XP,
            _ => attributes & ALLOWED_MAT_ATTRIBUTES,
        }
    }

    /// Rule: All loaded image stacks must have a guard page that is unmapped. We accomplish that by setting
    /// the RP attribute on the guard page. We also set XP to align with other unmapped pages.
    ///
    /// Arguments
    /// * `attributes` - The memory attributes for the stack guard page
    ///
    /// Use Case: This is called when loading an image to ensure the stack guard page is protected.
    pub(crate) const fn apply_image_stack_guard_policy(attributes: u64) -> u64 {
        attributes | efi::MEMORY_RP | efi::MEMORY_XP
    }

    /// Rule: All loaded image sections must have memory protections applied based on the section type. The cache
    /// attributes from the memory space descriptor are preserved.
    ///   - Code sections are marked as Read Only and Executable
    ///   - Data sections are marked as Read/Write and Non-Executable
    ///   - Sections w/o the write characteristic are marked as Read Only
    ///
    /// Arguments
    /// * `section_base_addr` - The base address of the section being loaded
    /// * `section_characteristics` - The PE/COFF section characteristics
    ///
    /// Returns a tuple of (attributes, capabilities) to be applied to the section
    ///
    /// Use Case: This is called when loading an image to ensure each section has the proper memory protections.
    pub(crate) const fn apply_image_protection_policy(
        section_characteristics: u32,
        descriptor: &MemorySpaceDescriptor,
    ) -> (u64, u64) {
        let mut attributes = efi::MEMORY_XP;
        if section_characteristics & pecoff::IMAGE_SCN_CNT_CODE == pecoff::IMAGE_SCN_CNT_CODE {
            attributes = efi::MEMORY_RO;
        }

        if section_characteristics & section_table::IMAGE_SCN_MEM_WRITE == 0
            && ((section_characteristics & section_table::IMAGE_SCN_MEM_READ) == section_table::IMAGE_SCN_MEM_READ)
        {
            attributes |= efi::MEMORY_RO;
        }

        attributes |= descriptor.attributes & efi::CACHE_ATTRIBUTE_MASK;

        let capabilities = attributes | descriptor.capabilities;

        (attributes, capabilities)
    }

    /// Rule: The EFI_MEMORY_MAP descriptor.attributes field is actually a capability field that must not have
    /// access attributes in it; some OSes treat these as actually set attributes, not capabilities. The runtime
    /// attribute is taken from the attributes, not the capabilities. Persistent memory must have EFI_MEMORY_NV set.
    /// Runtime services code and data must have the runtime attribute set.
    ///
    /// Arguments
    /// * `attributes` - The memory attributes from the EFI_MEMORY_MAP descriptor
    /// * `capabilities` - The memory capabilities from the EFI_MEMORY_MAP descriptor
    /// * `gcd_memory_type` - The GCD memory type for this region
    /// * `memory_type` - The UEFI memory type for this region
    ///
    /// Use Case: This is called when building the EFI_MEMORY_MAP to ensure the attributes are correctly set.
    pub(crate) fn apply_efi_memory_map_policy(
        attributes: u64,
        capabilities: u64,
        gcd_memory_type: GcdMemoryType,
        memory_type: efi::MemoryType,
    ) -> u64 {
        let mut final_attributes =
            capabilities & !(efi::MEMORY_ACCESS_MASK | efi::MEMORY_RUNTIME) | (attributes & efi::MEMORY_RUNTIME);

        if gcd_memory_type == GcdMemoryType::Persistent {
            final_attributes |= efi::MEMORY_NV;
        }

        if matches!(memory_type, efi::RUNTIME_SERVICES_CODE | efi::RUNTIME_SERVICES_DATA) {
            // Add the runtime attribute for runtime services code and data as
            // higher level code will expect this but it is not explicitly tracked.
            final_attributes |= efi::MEMORY_RUNTIME;
        }

        final_attributes
    }

    /// Rule: All new memory should support all access capabilities and runtime. These are generally applicable, not
    /// specific to any memory. All new memory is marked as EFI_MEMORY_RP to start with and will not be mapped until
    /// SetMemorySpaceAttributes() is called to set the attributes. EFI_MEMORY_XP is also set to allow merging with
    /// other free memory blocks.
    ///
    /// Arguments
    /// - * `capabilities` - The existing capabilities for the memory region
    ///
    /// Returns the updated capabilities and the attributes to set
    ///
    /// Use Case: This is called whenever new memory is added to the GCD
    pub(crate) const fn apply_add_memory_policy(capabilities: u64) -> (u64, u64) {
        (capabilities | efi::MEMORY_ACCESS_MASK | efi::MEMORY_RUNTIME, efi::MEMORY_RP | efi::MEMORY_XP)
    }

    /// Rule: All free memory should be marked as EFI_MEMORY_RP, EFI_MEMORY_XP, and the preserved cache attributes.
    /// EFI_MEMORY_RP will cause the memory to be unmapped in the page table, but we still set EFI_MEMORY_XP to align
    /// with the originally added memory so that free memory can be coalesced into fewer blocks.
    ///
    /// Arguments
    /// - * `attributes` - The existing attributes for the memory region
    ///
    /// Use Case: This is called whenever memory is freed in the GCD
    pub(crate) const fn apply_free_memory_policy(attributes: u64) -> u64 {
        (attributes & efi::CACHE_ATTRIBUTE_MASK) | efi::MEMORY_RP | efi::MEMORY_XP
    }

    /// Rule: Page 0 should be unmapped to catch null pointer dereferences. Cache attributes should be preserved.
    ///
    /// Arguments
    /// - * `attributes` - The existing attributes for page 0
    ///
    /// Use Case: This is called when initializing paging to ensure page 0 is unmapped.
    pub(crate) const fn apply_null_page_policy(attributes: u64) -> u64 {
        (attributes & efi::CACHE_ATTRIBUTE_MASK) | efi::MEMORY_RP | efi::MEMORY_XP
    }

    /// Rule: If the compatibility_mode_allowed feature flag is not set, we will fail to load
    /// the image that would crash the system with memory protections enabled
    ///
    /// Arguments
    /// * `image_base_page` - The base page of the image being loaded
    /// * `image_num_pages` - The number of pages in the image being loaded
    /// * `filename` - The name of the image being loaded
    ///
    /// Use Case: This is called when the platform has not allowed compatibility mode and we are attempting to load
    /// an EFI_APPLICATION that is not NX compatible.
    #[cfg(not(feature = "compatibility_mode_allowed"))]
    pub(crate) fn activate_compatibility_mode(
        _gcd: &SpinLockedGcd,
        _image_base_page: usize,
        _image_num_pages: usize,
        filename: &str,
    ) -> Result<(), EfiError> {
        log::error!(
            "Attempting to load {} that is not NX compatible. Compatibility mode is not allowed in this build, not loading image.",
            filename
        );
        Err(EfiError::LoadError)
    }

    /// Rule: If the platform allows compatibility mode, activate it when an EFI_APPLICATION without the NX_COMPAT flag
    /// is loaded.
    /// This will:
    /// - Activate compatibility mode for the GCD lower layers
    /// - Set the memory space attributes for all memory ranges in the loader code and data allocators to be RWX
    /// - Uninstall the memory attributes protocol
    ///
    /// Arguments
    /// * `image_base_page` - The base page of the image being loaded
    /// * `image_num_pages` - The number of pages in the image being loaded
    /// * `filename` - The name of the image being loaded
    ///
    /// Use Case: This is called when the platform has allowed compatibility mode and we are attempting to load
    /// an EFI_APPLICATION that is not NX compatible.
    #[cfg(feature = "compatibility_mode_allowed")]
    pub(crate) fn activate_compatibility_mode(
        gcd: &SpinLockedGcd,
        image_base_page: usize,
        image_num_pages: usize,
        filename: &str,
    ) -> Result<(), EfiError> {
        use patina::log_debug_assert;

        // remove default NX protection
        gcd.memory_protection_policy.memory_allocation_default_attributes.set(0);

        const LEGACY_BIOS_WB_ADDRESS: usize = 0xA0000;

        log::warn!("Attempting to load an application image that is not NX compatible. Activating compatibility mode.");

        // always map page 0 if it exists in this system, as grub will attempt to read it for legacy boot structures
        // map it WB by default, because 0 is being used as the null page, it may not have gotten cache attributes
        // populated
        match gcd.get_existent_memory_descriptor_for_address(0) {
            Ok(descriptor) if descriptor.memory_type == GcdMemoryType::SystemMemory => {
                // set_memory_space_attributes will set both the GCD and paging attributes
                if let Err(e) = gcd.set_memory_space_attributes(
                    0,
                    UEFI_PAGE_SIZE,
                    descriptor.attributes & efi::CACHE_ATTRIBUTE_MASK,
                ) {
                    log_debug_assert!("Failed to map page 0 for compat mode. Status: {e:#x?}");
                }
            }
            _ => {}
        }

        // map legacy region if system mem
        let mut address = UEFI_PAGE_SIZE; // start at 0x1000, as we already mapped page 0
        while address < LEGACY_BIOS_WB_ADDRESS {
            let mut size = UEFI_PAGE_SIZE;
            if let Ok(descriptor) = gcd.get_existent_memory_descriptor_for_address(address as efi::PhysicalAddress) {
                // if the legacy region is not system memory, we should not map it
                if descriptor.memory_type == GcdMemoryType::SystemMemory {
                    size = match address + descriptor.length as usize {
                        end_addr if end_addr > LEGACY_BIOS_WB_ADDRESS => LEGACY_BIOS_WB_ADDRESS - address,
                        _ => descriptor.length as usize,
                    };

                    // set_memory_space_attributes will set both the GCD and paging attributes
                    match gcd.set_memory_space_attributes(
                        address,
                        size,
                        descriptor.attributes & efi::CACHE_ATTRIBUTE_MASK,
                    ) {
                        Ok(_) => {}
                        Err(e) => {
                            log_debug_assert!(
                                "Failed to map legacy bios region at {:#x?} of length {:#x?} with attributes {:#x?}. Status: {:#x?}",
                                address,
                                size,
                                descriptor.attributes & efi::CACHE_ATTRIBUTE_MASK,
                                e
                            );
                        }
                    }
                }
            }
            address += size;
        }

        // if the allocator doesn't have any memory, then when it is used next it will allocate from the GCD
        // and the GCD will be in compatibility mode, so we don't care here
        let mut loader_mem_ranges = crate::allocator::get_memory_ranges_for_memory_type(efi::LOADER_CODE);
        loader_mem_ranges.extend(crate::allocator::get_memory_ranges_for_memory_type(efi::LOADER_DATA));
        for range in loader_mem_ranges.iter() {
            let mut addr = range.start;
            while addr < range.end {
                let mut len = UEFI_PAGE_SIZE as u64;
                match gcd.get_existent_memory_descriptor_for_address(addr) {
                    Ok(descriptor) => {
                        let attributes = descriptor.attributes & !efi::MEMORY_XP;
                        len = match descriptor.base_address + descriptor.length {
                            end if end > range.end => range.end - addr,
                            _ => descriptor.length,
                        };

                        // We need to ensure we are operating on page aligned addresses and lengths, as the image(s) that
                        // were allocated here may not be page aligned. We don't share pools across types, so this is safe.
                        (addr, len) = match align_range(addr, len, UEFI_PAGE_SIZE as u64) {
                            Ok((aligned_addr, aligned_len)) => (aligned_addr, aligned_len),
                            Err(_) => {
                                log_debug_assert!(
                                    "Failed to align address {addr:#x?} + {len:#x?} to page size, compatibility mode may fail",
                                );

                                // If we can't align the address, we can't set the attributes, so try the next range
                                addr += len;
                                continue;
                            }
                        };

                        if gcd.set_memory_space_attributes(addr as usize, len as usize, attributes).is_err() {
                            log_debug_assert!(
                                "Failed to set memory space attributes for range {addr:#x?} - {len:#x?}, compatibility mode may fail",
                            );
                        }
                    }
                    _ => {
                        log_debug_assert!(
                            "Failed to get memory space descriptor for range {:#x?} - {:#x?}, compatibility mode may fail",
                            range.start,
                            range.end,
                        );
                    }
                }
                addr += len;
            }
        }
        crate::memory_attributes_protocol::uninstall_memory_attributes_protocol();

        // for this image map all mem RWX preserving cache attributes if we find them
        let stripped_attrs = gcd
            .get_existent_memory_descriptor_for_address(image_base_page as u64)
            .map(|desc| desc.attributes & efi::CACHE_ATTRIBUTE_MASK)
            .unwrap_or(patina::base::DEFAULT_CACHE_ATTR);
        if gcd
            .set_memory_space_attributes(image_base_page, patina::uefi_pages_to_size!(image_num_pages), stripped_attrs)
            .is_err()
        {
            // if we failed to map this image RWX, we should still attempt to execute it, it may succeed

            log_debug_assert!("Failed to set GCD attributes for image {}", filename);
        }
        Ok(())
    }
}

pub fn init_gcd(physical_hob_list: *const c_void) {
    let mut free_memory_start: u64 = 0;
    let mut free_memory_size: u64 = 0;
    let mut memory_start: u64 = 0;
    let mut memory_end: u64 = 0;
    let mut free_memory_attributes: u64 = 0;
    let mut free_memory_capabilities: u64 = 0;

    // SAFETY: physical_hob_list is provided by the platform and must point to a valid HOB list.
    let hob_list = Hob::Handoff(unsafe {
        (physical_hob_list as *const PhaseHandoffInformationTable)
            .as_ref::<'static>()
            .expect("Physical hob list pointer is null, but it must exist and be valid.")
    });
    for hob in &hob_list {
        match hob {
            Hob::Handoff(handoff) => {
                free_memory_start = align_up(handoff.free_memory_bottom, 0x1000).expect("Unaligned free memory bottom");
                free_memory_size =
                    align_down(handoff.free_memory_top, 0x1000).expect("Unaligned free memory top") - free_memory_start;
                memory_start = handoff.memory_bottom;
                memory_end = handoff.memory_top;
            }
            Hob::Cpu(cpu) => {
                GCD.init(cpu.size_of_memory_space as u32, cpu.size_of_io_space as u32);
            }
            Hob::ResourceDescriptorV2(_) | Hob::ResourceDescriptor(_) => {
                debug_assert!(
                    free_memory_start != 0,
                    "The handoff HOB should come before any resource descriptor HOBs."
                );

                // Check if this is the resource descriptor for the free memory region, so that it can be used to initialize
                // the GCD. The handoff HOB should always come first, so the free memory should always be found before the
                // resource descriptor HOB.
                if free_memory_start != 0
                    && free_memory_attributes == 0
                    && let Some((res_desc, cache_attributes)) = parse_resource_descriptor_hob(&hob)
                    && res_desc.resource_type == hob::EFI_RESOURCE_SYSTEM_MEMORY
                    && res_desc.physical_start <= free_memory_start
                    && res_desc.physical_start.saturating_add(res_desc.resource_length)
                        >= free_memory_start.saturating_add(free_memory_size)
                {
                    free_memory_attributes = cache_attributes.unwrap_or(0);
                    free_memory_capabilities = spin_locked_gcd::get_capabilities(
                        GcdMemoryType::SystemMemory,
                        res_desc.resource_attribute as u64,
                    );
                }
            }
            _ => (),
        }
    }

    log::info!("memory_start: {memory_start:#x?}");
    log::info!("memory_size: {:#x?}", memory_end - memory_start);
    log::info!("free_memory_start: {free_memory_start:#x?}");
    log::info!("free_memory_size: {free_memory_size:#x?}");
    log::info!("physical_hob_list: {:#x?}", physical_hob_list as u64);
    log::info!("free_memory_attributes: {free_memory_attributes:#x?}");
    log::info!("free_memory_capabilities: {free_memory_capabilities:#x?}");

    // make sure the PHIT is present and it was reasonable.
    if free_memory_size == 0 {
        panic!("PHIT HOB indicates no free memory available for DXE core to start. Free memory size = 0.");
    }
    if memory_end <= memory_start {
        panic!("PHIT HOB indicates no memory available for DXE core to start. Memory end <= memory start.");
    }

    // initialize the GCD with an initial memory space. Note: this will fail if GCD.init() above didn't happen.
    // SAFETY: We are directly using the free memory space from the PHIT HOB, which must be valid and reserved for use
    // per spec.
    unsafe {
        GCD.init_memory_blocks(
            GcdMemoryType::SystemMemory,
            free_memory_start as usize,
            free_memory_size as usize,
            free_memory_attributes,
            efi::MEMORY_ACCESS_MASK | free_memory_capabilities,
        )
        .expect("Failed to add initial region to GCD.");
    }
}

#[coverage(off)]
/// Initialize the patina-paging crate
///
/// # Arguments
/// * `hob_list` - The HOB list as passed to DXE Core
///
/// This function installs the new Patina controlled page tables based
/// on the HOB list provided. Note that coverage is disabled for the
/// wrapper function because this simply wraps the actual implementation
/// in the SpinLockedGcd struct, which is covered by unit tests.
pub fn init_paging(hob_list: &HobList) {
    let page_allocator = PagingAllocator::new(&GCD);
    let page_table: Box<dyn PatinaPageTable> =
        Box::new(create_cpu_paging(page_allocator).expect("Failed to create CPU page table"));
    GCD.init_paging_with(hob_list, page_table);
}

pub fn add_hob_resource_descriptors_to_gcd(hob_list: &HobList) {
    #[cfg(feature = "v1_resource_descriptor_support")]
    {
        log::debug!("v1_resource_descriptor_support feature is active (V1 ResourceDescriptor HOBs only)");
    }

    #[cfg(not(feature = "v1_resource_descriptor_support"))]
    {
        log::debug!("v1_resource_descriptor_support feature is NOT active (V2 ResourceDescriptor HOBs only)");
    }

    let phit = hob_list
        .iter()
        .find_map(|x| match x {
            patina::pi::hob::Hob::Handoff(handoff) => Some(*handoff),
            _ => None,
        })
        .expect("Failed to find PHIT Hob");

    let free_memory_start = align_up(phit.free_memory_bottom, 0x1000).expect("Unaligned free memory bottom");
    let free_memory_size =
        align_down(phit.free_memory_top, 0x1000).expect("Unaligned free memory top") - free_memory_start;

    //Iterate over the hob list and map resource descriptor HOBs into the GCD.
    for hob in hob_list.iter() {
        let mut gcd_mem_type: GcdMemoryType = GcdMemoryType::NonExistent;

        let mut resource_attributes: u32 = 0;
        // Only process Resource Descriptor HOBs according to the selected version
        let (res_desc, cache_attributes) = match parse_resource_descriptor_hob(hob) {
            Some((desc, Some(attrs))) => (desc, attrs),
            Some((desc, None)) => (desc, 0u64),
            None => continue, // Not a resource descriptor HOB or unsupported version for this build
        };

        let mem_range = res_desc.physical_start
            ..res_desc.physical_start.checked_add(res_desc.resource_length).expect("Invalid resource descriptor hob");

        match res_desc.resource_type {
            hob::EFI_RESOURCE_SYSTEM_MEMORY => {
                resource_attributes = res_desc.resource_attribute;

                if resource_attributes & hob::MEMORY_ATTRIBUTE_MASK == hob::TESTED_MEMORY_ATTRIBUTES {
                    if resource_attributes & hob::EFI_RESOURCE_ATTRIBUTE_MORE_RELIABLE
                        == hob::EFI_RESOURCE_ATTRIBUTE_MORE_RELIABLE
                    {
                        gcd_mem_type = GcdMemoryType::MoreReliable;
                    } else {
                        gcd_mem_type = GcdMemoryType::SystemMemory;
                    }
                }

                if (resource_attributes & hob::MEMORY_ATTRIBUTE_MASK == (hob::INITIALIZED_MEMORY_ATTRIBUTES))
                    || (resource_attributes & hob::MEMORY_ATTRIBUTE_MASK == (hob::PRESENT_MEMORY_ATTRIBUTES))
                {
                    gcd_mem_type = GcdMemoryType::Reserved;
                }

                if resource_attributes & hob::EFI_RESOURCE_ATTRIBUTE_PERSISTENT
                    == hob::EFI_RESOURCE_ATTRIBUTE_PERSISTENT
                {
                    gcd_mem_type = GcdMemoryType::Persistent;
                }
            }
            hob::EFI_RESOURCE_MEMORY_MAPPED_IO | hob::EFI_RESOURCE_FIRMWARE_DEVICE => {
                resource_attributes = res_desc.resource_attribute;
                gcd_mem_type = GcdMemoryType::MemoryMappedIo;
            }
            hob::EFI_RESOURCE_MEMORY_MAPPED_IO_PORT | hob::EFI_RESOURCE_MEMORY_RESERVED => {
                resource_attributes = res_desc.resource_attribute;
                gcd_mem_type = GcdMemoryType::Reserved;
            }
            hob::EFI_RESOURCE_IO => {
                log::info!(
                    "Mapping io range {:#x?} as {:?}",
                    res_desc.physical_start..res_desc.resource_length,
                    GcdIoType::Io
                );
                GCD.add_io_space(GcdIoType::Io, res_desc.physical_start as usize, res_desc.resource_length as usize)
                    .expect("Failed to add IO space to GCD");
            }
            hob::EFI_RESOURCE_IO_RESERVED => {
                log::info!(
                    "Mapping io range {:#x?} as {:?}",
                    res_desc.physical_start..res_desc.resource_length,
                    GcdIoType::Reserved
                );
                GCD.add_io_space(
                    GcdIoType::Reserved,
                    res_desc.physical_start as usize,
                    res_desc.resource_length as usize,
                )
                .expect("Failed to add IO space to GCD");
            }
            _ => {
                debug_assert!(false, "Unknown resource type in HOB");
            }
        };

        if gcd_mem_type != GcdMemoryType::NonExistent {
            debug_assert!(res_desc.attributes_valid());
        }

        if gcd_mem_type != GcdMemoryType::NonExistent {
            let memory_attributes =
                MemoryProtectionPolicy::apply_resc_desc_hobs_protection_policy(cache_attributes, gcd_mem_type);

            for split_range in
                remove_range_overlap(&mem_range, &(free_memory_start..(free_memory_start + free_memory_size)))
                    .into_iter()
                    .take_while(|r| r.is_some())
                    .flatten()
            {
                // SAFETY: GCD is initialized and split_range is derived from valid HOB ranges.
                unsafe {
                    GCD.add_memory_space(
                        gcd_mem_type,
                        split_range.start as usize,
                        split_range.end.saturating_sub(split_range.start) as usize,
                        spin_locked_gcd::get_capabilities(gcd_mem_type, resource_attributes as u64),
                    )
                    .expect("Failed to add memory space to GCD");
                }

                log::info!(
                    "Mapping memory range {split_range:#x?} as {gcd_mem_type:?} with attributes {memory_attributes:#x?}",
                );

                match GCD.set_memory_space_attributes(
                    split_range.start as usize,
                    split_range.end.saturating_sub(split_range.start) as usize,
                    memory_attributes,
                ) {
                    // NotReady is expected result here since page table is not yet initialized. In this case GCD
                    // will be updated with the appropriate attributes which will then be sync'd to page table
                    // once it is initialized.
                    Err(EfiError::NotReady) => (),
                    _ => {
                        // In debug builds, assert to catch GCD attribute setting failures during development.
                        // In production, allow the system to continue with a potentially torn state,
                        // matching EDK2 behavior where non-critical GCD operations can fail gracefully.
                        debug_assert!(
                            false,
                            "GCD failed to set memory attributes {:#X} for base: {:#X}, length: {:#X}",
                            memory_attributes,
                            split_range.start,
                            split_range.end.saturating_sub(split_range.start),
                        );
                    }
                }
            }
        }
    }
}

fn remove_range_overlap<T: PartialOrd + Copy>(a: &Range<T>, b: &Range<T>) -> [Option<Range<T>>; 2] {
    if a.start < b.end && a.end > b.start {
        // Check if `a` has a portion before the overlap
        let first_range = if a.start < b.start { Some(a.start..b.start) } else { None };

        // Check if `a` has a portion after the overlap
        let second_range = if a.end > b.end { Some(b.end..a.end) } else { None };

        [first_range, second_range]
    } else {
        // No overlap
        [Some(a.start..a.end), None]
    }
}

/// Parse Resource Descriptor HOB v2
///
/// This function takes in a HOB and returns:
/// - Some((Resource Descriptor, Some(cache_attributes))) if cache attributes are present
/// - Some((Resource Descriptor, None)) if no cache attributes are present
/// - None if not a v2 resource descriptor HOB
#[cfg(not(feature = "v1_resource_descriptor_support"))]
fn parse_resource_descriptor_hob(hob: &Hob) -> Option<(hob::ResourceDescriptor, Option<u64>)> {
    match hob {
        Hob::ResourceDescriptorV2(v2_res_desc) => {
            let attrs = if v2_res_desc.attributes != 0 { Some(v2_res_desc.attributes) } else { None };
            Some((v2_res_desc.v1, attrs))
        }
        _ => None, // Not a resource descriptor HOB or a v1 HOB
    }
}

/// Parse Resource Descriptor HOB v1
///
/// This function takes in a HOB and returns:
/// - Some((Resource Descriptor, None))
/// - None if not a v1 resource descriptor HOB
#[cfg(feature = "v1_resource_descriptor_support")]
fn parse_resource_descriptor_hob(hob: &Hob) -> Option<(hob::ResourceDescriptor, Option<u64>)> {
    match hob {
        Hob::ResourceDescriptor(v1_res_desc) => {
            // Legacy platforms: Process v1 HOBs normally
            Some((**v1_res_desc, None)) // v1 HOBs have no cache attributes
        }
        _ => None, // Not a resource descriptor HOB or a v2 HOB
    }
}

#[cfg(test)]
#[coverage(off)]
mod tests {
    use core::ffi::c_void;

    use patina::pi::{
        dxe_services::{GcdIoType, GcdMemoryType, IoSpaceDescriptor, MemorySpaceDescriptor},
        hob::{HobList, PhaseHandoffInformationTable},
    };

    use crate::{
        GCD,
        gcd::init_gcd,
        test_support::{self, build_test_hob_list},
    };

    use super::*;

    const MEM_SIZE: u64 = 0x200000;

    fn with_locked_state<F: Fn() + std::panic::RefUnwindSafe>(f: F) {
        test_support::with_global_lock(|| {
            test_support::init_test_logger();
            // SAFETY: Test code - resetting the global GCD state for test isolation.
            // The test lock is used to prevent concurrent access.
            unsafe {
                GCD.reset();
            }
            f();
        })
        .unwrap();
    }

    fn init_gcd_should_init_gcd(physical_hob_list: *const c_void, mem_base: u64) {
        // SAFETY: Test code - the physical_hob_list pointer is pointing to a HOBs list
        // constructed in test_full_gcd_init() and directly passed to this function.
        let handoff = unsafe {
            (physical_hob_list as *const PhaseHandoffInformationTable)
                .as_ref::<'static>()
                .expect("Physical hob list pointer is null, but it must exist and be valid.")
        };

        let free_memory_start = handoff.free_memory_bottom;
        let free_memory_size = handoff.free_memory_top - handoff.free_memory_bottom;

        init_gcd(physical_hob_list);
        assert!(free_memory_start >= mem_base && free_memory_start < mem_base + MEM_SIZE);
        assert!(free_memory_size <= 0x100000);
        let mut descriptors: Vec<MemorySpaceDescriptor> = Vec::with_capacity(GCD.memory_descriptor_count() + 10);
        GCD.get_memory_descriptors(&mut descriptors, DescriptorFilter::All).expect("get_memory_descriptors failed.");
        assert!(
            descriptors
                .iter()
                .any(|x| x.base_address == free_memory_start && x.memory_type == GcdMemoryType::SystemMemory)
        )
    }

    fn add_resource_descriptors_should_add_resource_descriptors(hob_list: &HobList, mem_base: u64) {
        add_hob_resource_descriptors_to_gcd(hob_list);
        let mut descriptors: Vec<MemorySpaceDescriptor> = Vec::with_capacity(GCD.memory_descriptor_count() + 10);
        GCD.get_memory_descriptors(&mut descriptors, DescriptorFilter::All).expect("get_memory_descriptors failed.");
        descriptors
            .iter()
            .find(|x| x.base_address == mem_base + 0xE0000 && x.memory_type == GcdMemoryType::SystemMemory)
            .unwrap();
        descriptors
            .iter()
            .find(|x| x.base_address == mem_base + 0x190000 && x.memory_type == GcdMemoryType::Reserved)
            .unwrap();
        //Note: resource descriptors 3 & are merged into a single contiguous region in GCD, so no separate entry exists.
        //So verify the length of the entry encompasses both.
        let mmio_3_4 = descriptors
            .iter()
            .find(|x| x.base_address == 0x10000000 && x.memory_type == GcdMemoryType::MemoryMappedIo)
            .unwrap();
        assert_eq!(mmio_3_4.length, 0x2000000);
        descriptors.iter().find(|x| x.base_address == 0x12000000 && x.memory_type == GcdMemoryType::Reserved).unwrap();

        let mut descriptors: Vec<IoSpaceDescriptor> = Vec::with_capacity(GCD.io_descriptor_count() + 10);
        GCD.get_io_descriptors(&mut descriptors).expect("get_io_descriptors failed.");
        descriptors.iter().find(|x| x.base_address == 0x0000 && x.io_type == GcdIoType::Reserved).unwrap();
        descriptors.iter().find(|x| x.base_address == 0x1000 && x.io_type == GcdIoType::Io).unwrap();
    }

    #[test]
    fn test_full_gcd_init() {
        with_locked_state(|| {
            let physical_hob_list = build_test_hob_list(MEM_SIZE);
            init_gcd_should_init_gcd(physical_hob_list, physical_hob_list as u64);

            let mut hob_list = HobList::default();
            hob_list.discover_hobs(physical_hob_list);

            add_resource_descriptors_should_add_resource_descriptors(&hob_list, physical_hob_list as u64);
        });
    }

    #[test]
    fn test_remove_range_overlap() {
        // Test case 1: No overlap
        let a = 10..20;
        let b = 30..40;
        let result = remove_range_overlap(&a, &b);
        assert_eq!(result, [Some(10..20), None]);

        // Test case 2: Partial overlap at the front of 'b'
        let a = 5..20;
        let b = 10..30;
        let result = remove_range_overlap(&a, &b);
        assert_eq!(result, [Some(5..10), None]);

        // Test case 3: Partial overlap at the end of 'b'
        let a = 20..40;
        let b = 10..30;
        let result = remove_range_overlap(&a, &b);
        assert_eq!(result, [None, Some(30..40)]);

        // Test case 4: 'a' is completely inside 'b' (middle overlap)
        let a = 20..30;
        let b = 10..40;
        let result = remove_range_overlap(&a, &b);
        assert_eq!(result, [None, None]);
    }

    #[test]
    fn test_memory_protection_policy_apply_allocated_memory_protection_policy() {
        let policy = MemoryProtectionPolicy::new();
        let attributes = efi::MEMORY_WB;
        let result = policy.apply_allocated_memory_protection_policy(attributes);
        // Should preserve cache attributes and add default XP
        assert_eq!(result, efi::MEMORY_WB | efi::MEMORY_XP);
    }

    #[test]
    fn test_memory_protection_policy_apply_resc_desc_hobs_protection_policy() {
        // system memory, adds XP and RP
        let attributes = efi::MEMORY_WB;
        let memory_type = GcdMemoryType::SystemMemory;
        let result = MemoryProtectionPolicy::apply_resc_desc_hobs_protection_policy(attributes, memory_type);
        assert_eq!(result, efi::MEMORY_WB | efi::MEMORY_XP | efi::MEMORY_RP);

        // not system memory, only adds XP
        let attributes = efi::MEMORY_UC;
        let memory_type = GcdMemoryType::MemoryMappedIo;
        let result = MemoryProtectionPolicy::apply_resc_desc_hobs_protection_policy(attributes, memory_type);
        assert_eq!(result, efi::MEMORY_UC | efi::MEMORY_XP);
    }

    #[test]
    fn test_memory_protection_policy_apply_nx_to_uc_policy() {
        // UC set, should add XP
        let attributes = efi::MEMORY_UC;
        let result = MemoryProtectionPolicy::apply_nx_to_uc_policy(attributes);
        assert_eq!(result, efi::MEMORY_UC | efi::MEMORY_XP);

        // UC not set, should not change
        let attributes = efi::MEMORY_WB;
        let result = MemoryProtectionPolicy::apply_nx_to_uc_policy(attributes);
        assert_eq!(result, efi::MEMORY_WB);
    }

    #[test]
    fn test_memory_protection_policy_apply_memory_attributes_table_policy() {
        // Test case 1: RUNTIME_SERVICES_CODE with no MAT attributes
        let attributes = efi::MEMORY_WB;
        let memory_type = efi::RUNTIME_SERVICES_CODE;
        let result = MemoryProtectionPolicy::apply_memory_attributes_table_policy(attributes, memory_type);
        assert_eq!(result, efi::MEMORY_RO | efi::MEMORY_XP | efi::MEMORY_RUNTIME);

        // Test case 2: RUNTIME_SERVICES_DATA with no MAT attributes
        let attributes = efi::MEMORY_WB;
        let memory_type = efi::RUNTIME_SERVICES_DATA;
        let result = MemoryProtectionPolicy::apply_memory_attributes_table_policy(attributes, memory_type);
        assert_eq!(result, efi::MEMORY_RUNTIME | efi::MEMORY_XP);

        // Test case 3: Attributes already set
        let attributes = efi::MEMORY_RO | efi::MEMORY_XP | efi::MEMORY_RUNTIME | efi::MEMORY_WB;
        let memory_type = efi::RUNTIME_SERVICES_CODE;
        let result = MemoryProtectionPolicy::apply_memory_attributes_table_policy(attributes, memory_type);
        assert_eq!(result, efi::MEMORY_RO | efi::MEMORY_XP | efi::MEMORY_RUNTIME);
    }

    #[test]
    fn test_memory_protection_policy_apply_image_stack_guard_policy() {
        let attributes = efi::MEMORY_WB;
        let result = MemoryProtectionPolicy::apply_image_stack_guard_policy(attributes);
        assert_eq!(result, efi::MEMORY_RP | efi::MEMORY_XP | efi::MEMORY_WB);
    }

    #[test]
    fn test_memory_protection_policy_apply_image_protection_policy() {
        let descriptor = MemorySpaceDescriptor {
            base_address: 0,
            length: 0x1000,
            memory_type: GcdMemoryType::SystemMemory,
            attributes: efi::MEMORY_WB,
            capabilities: efi::MEMORY_WB | efi::MEMORY_ACCESS_MASK,
            image_handle: std::ptr::null_mut(),
            device_handle: std::ptr::null_mut(),
        };

        // Code section
        let section_characteristics = pecoff::IMAGE_SCN_CNT_CODE;
        let (attributes, capabilities) =
            MemoryProtectionPolicy::apply_image_protection_policy(section_characteristics, &descriptor);
        assert_eq!(attributes, efi::MEMORY_RO | efi::MEMORY_WB);
        assert_eq!(capabilities, efi::MEMORY_WB | efi::MEMORY_ACCESS_MASK);

        // Data section (no write, but read)
        let section_characteristics = section_table::IMAGE_SCN_MEM_READ;
        let (attributes, _) =
            MemoryProtectionPolicy::apply_image_protection_policy(section_characteristics, &descriptor);
        assert_eq!(attributes, efi::MEMORY_RO | efi::MEMORY_WB | efi::MEMORY_XP);

        // Data section (write allowed)
        let section_characteristics = section_table::IMAGE_SCN_MEM_WRITE;
        let (attributes, _) =
            MemoryProtectionPolicy::apply_image_protection_policy(section_characteristics, &descriptor);
        assert_eq!(attributes, efi::MEMORY_XP | efi::MEMORY_WB);
    }

    #[test]
    fn test_memory_protection_policy_apply_efi_memory_map_policy() {
        // Persistent memory type
        let attributes = efi::MEMORY_RUNTIME | efi::MEMORY_WB;
        let capabilities = efi::MEMORY_WB | efi::MEMORY_RO | efi::MEMORY_RUNTIME;
        let gcd_memory_type = GcdMemoryType::Persistent;
        let memory_type = efi::CONVENTIONAL_MEMORY;
        let result =
            MemoryProtectionPolicy::apply_efi_memory_map_policy(attributes, capabilities, gcd_memory_type, memory_type);
        assert_eq!(result, efi::MEMORY_NV | efi::MEMORY_RUNTIME | efi::MEMORY_WB);

        // Non-persistent, should not set NV, attributes don't have runtime, shouldn't be set
        let attributes = efi::MEMORY_WB;
        let gcd_memory_type = GcdMemoryType::SystemMemory;
        let result =
            MemoryProtectionPolicy::apply_efi_memory_map_policy(attributes, capabilities, gcd_memory_type, memory_type);
        assert_eq!(result, efi::MEMORY_WB);

        // Runtime services code should have MEMORY_RUNTIME set
        let attributes = efi::MEMORY_WB;
        let capabilities = efi::MEMORY_WB | efi::MEMORY_RO | efi::MEMORY_RUNTIME;
        let gcd_memory_type = GcdMemoryType::SystemMemory;
        let memory_type = efi::RUNTIME_SERVICES_CODE;
        let result =
            MemoryProtectionPolicy::apply_efi_memory_map_policy(attributes, capabilities, gcd_memory_type, memory_type);
        assert_eq!(result, efi::MEMORY_WB | efi::MEMORY_RUNTIME);

        // Runtime services data should have MEMORY_RUNTIME set
        let memory_type = efi::RUNTIME_SERVICES_DATA;
        let result =
            MemoryProtectionPolicy::apply_efi_memory_map_policy(attributes, capabilities, gcd_memory_type, memory_type);
        assert_eq!(result, efi::MEMORY_WB | efi::MEMORY_RUNTIME);
    }

    #[test]
    fn test_memory_protection_policy_apply_add_memory_policy() {
        let capabilities = efi::MEMORY_WB | efi::MEMORY_UC;
        let (new_capabilities, attributes) = MemoryProtectionPolicy::apply_add_memory_policy(capabilities);
        assert_eq!(new_capabilities, efi::MEMORY_ACCESS_MASK | efi::MEMORY_RUNTIME | efi::MEMORY_WB | efi::MEMORY_UC);
        assert_eq!(attributes, efi::MEMORY_RP | efi::MEMORY_XP);
    }

    #[test]
    fn test_memory_protection_policy_apply_free_memory_policy() {
        let attributes = efi::MEMORY_WB | efi::MEMORY_RO | efi::MEMORY_RUNTIME;
        let result = MemoryProtectionPolicy::apply_free_memory_policy(attributes);
        assert_eq!(result, efi::MEMORY_RP | efi::MEMORY_XP | efi::MEMORY_WB);
    }

    #[test]
    fn test_memory_protection_policy_apply_null_page_policy() {
        let attributes = efi::MEMORY_WB | efi::MEMORY_RO;
        let result = MemoryProtectionPolicy::apply_null_page_policy(attributes);
        assert_eq!(result, efi::MEMORY_RP | efi::MEMORY_XP | efi::MEMORY_WB);
    }

    #[test]
    #[cfg(not(feature = "compatibility_mode_allowed"))]
    fn test_activate_compatibility_mode_not_allowed() {
        let gcd: SpinLockedGcd = SpinLockedGcd::new(None);

        let result = MemoryProtectionPolicy::activate_compatibility_mode(&gcd, 0x1000, 0x10, "test_image.efi");
        assert_eq!(result, Err(EfiError::LoadError));
    }

    #[test]
    #[cfg(feature = "compatibility_mode_allowed")]
    fn test_activate_compatibility_mode_allowed() {
        use crate::{
            allocator,
            test_support::{MockPageTable, MockPageTableWrapper},
        };
        use std::{cell::RefCell, rc::Rc};
        with_locked_state(|| {
            GCD.init(48, 16);

            // Add memory and MMIO regions
            // SAFETY: get_memory returns a test-owned buffer sized for the requested block count.
            let mem = unsafe { crate::test_support::get_memory(spin_locked_gcd::MEMORY_BLOCK_SLICE_SIZE * 10) };
            let address = align_up(mem.as_ptr() as usize, 0x1000).unwrap();
            // SAFETY: address/length come from the test buffer and are valid for initializing GCD memory blocks.
            unsafe {
                GCD.init_memory_blocks(
                    patina::pi::dxe_services::GcdMemoryType::SystemMemory,
                    address,
                    spin_locked_gcd::MEMORY_BLOCK_SLICE_SIZE * 10,
                    efi::MEMORY_WB,
                    efi::CACHE_ATTRIBUTE_MASK | efi::MEMORY_ACCESS_MASK,
                )
                .unwrap();
                GCD.add_memory_space(
                    patina::pi::dxe_services::GcdMemoryType::SystemMemory,
                    0x0,
                    0xA0000,
                    efi::CACHE_ATTRIBUTE_MASK | efi::MEMORY_ACCESS_MASK,
                )
                .unwrap();
            }

            let mock_table = Rc::new(RefCell::new(MockPageTable::new()));
            let mock_page_table = Box::new(MockPageTableWrapper::new(Rc::clone(&mock_table)));
            GCD.add_test_page_table(mock_page_table);

            let policy = &GCD.memory_protection_policy;

            // 1. Default attributes should be MEMORY_XP before activation
            assert_eq!(policy.memory_allocation_default_attributes.get(), efi::MEMORY_XP);

            // allocate some loader code/data memory to test later
            let mut loader_code_mem = 0;
            let mut loader_data_mem = 0;

            allocator::core_allocate_pages(efi::ALLOCATE_ANY_PAGES, efi::LOADER_CODE, 2, &mut loader_code_mem, None)
                .expect("Failed to allocate loader code memory");
            allocator::core_allocate_pages(efi::ALLOCATE_ANY_PAGES, efi::LOADER_DATA, 2, &mut loader_data_mem, None)
                .expect("Failed to allocate loader data memory");

            // loader code/data should be XP by default
            let loader_code_ranges = allocator::get_memory_ranges_for_memory_type(efi::LOADER_CODE);
            let loader_data_ranges = allocator::get_memory_ranges_for_memory_type(efi::LOADER_DATA);

            for range in loader_code_ranges.iter().chain(loader_data_ranges.iter()) {
                let mut addr = range.start;
                while addr < range.end {
                    let mut len = 0x1000;
                    if let Ok(desc) = GCD.get_existent_memory_descriptor_for_address(addr) {
                        assert_eq!(desc.attributes & efi::MEMORY_XP, efi::MEMORY_XP);
                        len = desc.length;
                    }
                    addr += len;
                }
            }

            let mut image_base_page = 0;
            allocator::core_allocate_pages(
                efi::ALLOCATE_ANY_PAGES,
                efi::BOOT_SERVICES_DATA,
                4,
                &mut image_base_page,
                None,
            )
            .expect("Failed to allocate loader code memory");
            let image_num_pages = 4;
            let filename = "legacy_app.efi";

            let desc = GCD.get_existent_memory_descriptor_for_address(image_base_page).unwrap();
            assert_eq!(desc.attributes & efi::MEMORY_XP, efi::MEMORY_XP);

            // 2. Activate compatibility mode
            let result = MemoryProtectionPolicy::activate_compatibility_mode(
                &GCD,
                image_base_page as usize,
                image_num_pages,
                filename,
            );
            assert!(result.is_ok());

            // 3. After activation, default attributes should be 0
            assert_eq!(policy.memory_allocation_default_attributes.get(), 0);

            // 4. Page 0 should be mapped
            let desc = GCD.get_existent_memory_descriptor_for_address(0).unwrap();
            assert_eq!(desc.attributes & efi::CACHE_ATTRIBUTE_MASK, efi::MEMORY_WB);

            // 5. Legacy BIOS region (0xA0000) should be mapped if system memory
            let legacy_desc = GCD.get_existent_memory_descriptor_for_address(0xA0000);
            if let Ok(desc) = legacy_desc
                && desc.memory_type == GcdMemoryType::SystemMemory
            {
                assert_eq!(desc.base_address, 0xA0000);
            }

            // 6. All loader code/data memory should have XP cleared (RWX)
            let loader_code_ranges = allocator::get_memory_ranges_for_memory_type(efi::LOADER_CODE);
            let loader_data_ranges = allocator::get_memory_ranges_for_memory_type(efi::LOADER_DATA);

            for range in loader_code_ranges.iter().chain(loader_data_ranges.iter()) {
                let mut addr = range.start;
                while addr < range.end {
                    let mut len = 0x1000;
                    if let Ok(desc) = GCD.get_existent_memory_descriptor_for_address(addr) {
                        assert_eq!(desc.attributes & efi::MEMORY_XP, 0);
                        len = desc.length;
                    }
                    addr += len;
                }
            }

            // 7. The image region should be mapped RWX (XP cleared)
            let desc = GCD.get_existent_memory_descriptor_for_address(image_base_page).unwrap();
            assert_eq!(desc.attributes & efi::MEMORY_XP, 0);
        });
    }
}