From 32a3bddfdf01aad30acf8bce4a39eb4bc9ec5b3e Mon Sep 17 00:00:00 2001 From: Michael Lotz Date: Mon, 16 Sep 2024 23:54:47 +0200 Subject: [PATCH] x86: Implement PAT support for memory type configuration. Using the page attribute table to set the memory type on a per page mapping basis is the more modern and flexible approach to physical memory type handling compared to using MTRRs. Most of the needed infrastructure was already in place, as setting the page table entry attributes was already done for uncachable and write-back memory types. Using the PAT now also allows to set the last remaining memory type of write-combining through the PTE flags. The PAT is configured to have entry 4 mean write-combining and the PAT bit in the PTE is set to point to that. When PAT is supported and not disabled, MTRRs are completely ignored and left as set up by the system firmware, where the basic uncachable and RAM ranges are supposed to be set up. These configurations are then overridden by the PTE flags as needed. Change-Id: I0a74b3fc7d3ba9fa384251290ce41621b69d3a02 Reviewed-on: https://review.haiku-os.org/c/haiku/+/8340 Reviewed-by: waddlesplash --- headers/private/kernel/arch/x86/arch_cpu.h | 12 ++++ headers/private/system/safemode_defs.h | 1 + src/system/boot/platform/bios_ia32/smp.cpp | 10 ++++ .../boot/platform/efi/arch/x86/arch_smp.cpp | 10 ++++ src/system/kernel/arch/x86/arch_cpu.cpp | 55 +++++++++++++++++++ src/system/kernel/arch/x86/arch_vm.cpp | 2 - .../x86/paging/32bit/X86PagingMethod32Bit.h | 10 +--- .../x86/paging/64bit/X86PagingMethod64Bit.h | 11 +--- .../arch/x86/paging/pae/X86PagingMethodPAE.h | 10 +--- 9 files changed, 95 insertions(+), 26 deletions(-) diff --git a/headers/private/kernel/arch/x86/arch_cpu.h b/headers/private/kernel/arch/x86/arch_cpu.h index 160b5b76d9..41781e7e4c 100644 --- a/headers/private/kernel/arch/x86/arch_cpu.h +++ b/headers/private/kernel/arch/x86/arch_cpu.h @@ -57,6 +57,7 @@ #define IA32_MSR_MTRR_DEFAULT_TYPE 0x2ff #define IA32_MSR_MTRR_PHYSICAL_BASE_0 0x200 #define IA32_MSR_MTRR_PHYSICAL_MASK_0 0x201 +#define IA32_MSR_PAT 0x277 // MSR SPEC CTRL bits #define IA32_MSR_SPEC_CTRL_IBRS (1 << 0) @@ -177,6 +178,16 @@ #define IA32_HWP_REQUEST_MAXIMUM_VALID (1ULL << 62) #define IA32_HWP_REQUEST_MINIMUM_VALID (1ULL << 63) +// IA32_MSR_PAT bits +#define IA32_MSR_PAT_ENTRY_MASK 0x7ULL +#define IA32_MSR_PAT_ENTRY_SHIFT(x) (x * 8) +#define IA32_MSR_PAT_TYPE_UNCACHEABLE 0x0ULL +#define IA32_MSR_PAT_TYPE_WRITE_COMBINING 0x1ULL +#define IA32_MSR_PAT_TYPE_WRITE_THROUGH 0x4ULL +#define IA32_MSR_PAT_TYPE_WRITE_PROTECTED 0x5ULL +#define IA32_MSR_PAT_TYPE_WRITE_BACK 0x6ULL +#define IA32_MSR_PAT_TYPE_UNCACHED 0x7ULL + // x86 features from cpuid eax 1, edx register // reference http://www.intel.com/Assets/en_US/PDF/appnote/241618.pdf (Table 5-5) #define IA32_FEATURE_FPU (1 << 0) // x87 fpu @@ -668,6 +679,7 @@ void x86_set_mtrrs(uint8 defaultType, const x86_mtrr_info* infos, uint32 count); void x86_init_fpu(); bool x86_check_feature(uint32 feature, enum x86_feature_type type); +bool x86_use_pat(); void* x86_get_double_fault_stack(int32 cpu, size_t* _size); int32 x86_double_fault_get_cpu(void); diff --git a/headers/private/system/safemode_defs.h b/headers/private/system/safemode_defs.h index 086156e36a..d575f2827a 100644 --- a/headers/private/system/safemode_defs.h +++ b/headers/private/system/safemode_defs.h @@ -15,6 +15,7 @@ #define B_SAFEMODE_DISABLE_SMEP_SMAP "disable_smep_smap" #define B_SAFEMODE_DISABLE_APM "disable_apm" #define B_SAFEMODE_DISABLE_SMP "disable_smp" +#define B_SAFEMODE_DISABLE_PAT "disable_pat" #define B_SAFEMODE_DISABLE_HYPER_THREADING "disable_hyperthreading" #define B_SAFEMODE_FAIL_SAFE_VIDEO_MODE "fail_safe_video_mode" #define B_SAFEMODE_4_GB_MEMORY_LIMIT "4gb_memory_limit" diff --git a/src/system/boot/platform/bios_ia32/smp.cpp b/src/system/boot/platform/bios_ia32/smp.cpp index 9956a2c95a..1f594006ff 100644 --- a/src/system/boot/platform/bios_ia32/smp.cpp +++ b/src/system/boot/platform/bios_ia32/smp.cpp @@ -614,6 +614,16 @@ smp_add_safemode_menus(Menu *menu) } } + cpuid_info info; + if (get_current_cpuid(&info, 1, 0) == B_OK + && (info.regs.edx & IA32_FEATURE_PAT) != 0) { + menu->AddItem(item = new(nothrow) MenuItem("Disable PAT")); + item->SetType(MENU_ITEM_MARKABLE); + item->SetData(B_SAFEMODE_DISABLE_PAT); + item->SetHelpText("Disables using page attribute tables for memory " + "type setting, falling back to MTRRs."); + } + if (gKernelArgs.num_cpus < 2) return; diff --git a/src/system/boot/platform/efi/arch/x86/arch_smp.cpp b/src/system/boot/platform/efi/arch/x86/arch_smp.cpp index fad379d67f..fd0f283c6b 100644 --- a/src/system/boot/platform/efi/arch/x86/arch_smp.cpp +++ b/src/system/boot/platform/efi/arch/x86/arch_smp.cpp @@ -362,6 +362,16 @@ arch_smp_add_safemode_menus(Menu *menu) } } + cpuid_info info; + if (get_current_cpuid(&info, 1, 0) == B_OK + && (info.regs.edx & IA32_FEATURE_PAT) != 0) { + menu->AddItem(item = new(nothrow) MenuItem("Disable PAT")); + item->SetType(MENU_ITEM_MARKABLE); + item->SetData(B_SAFEMODE_DISABLE_PAT); + item->SetHelpText("Disables using page attribute tables for memory " + "type setting, falling back to MTRRs."); + } + if (gKernelArgs.num_cpus < 2) return; diff --git a/src/system/kernel/arch/x86/arch_cpu.cpp b/src/system/kernel/arch/x86/arch_cpu.cpp index 064eaa1556..455dcb8fa0 100644 --- a/src/system/kernel/arch/x86/arch_cpu.cpp +++ b/src/system/kernel/arch/x86/arch_cpu.cpp @@ -128,6 +128,8 @@ static size_t sUcodeDataSize = 0; static void* sLoadedUcodeUpdate; static spinlock sUcodeUpdateLock = B_SPINLOCK_INITIALIZER; +static bool sUsePAT = false; + static status_t acpi_shutdown(bool rebootSystem) @@ -263,6 +265,17 @@ init_mtrrs(void* _unused, int cpu) uint32 x86_count_mtrrs(void) { + if (sUsePAT) { + // When PAT is supported, we completely ignore MTRRs and leave them as + // initialized by firmware. This follows the suggestion in Intel SDM + // that these don't usually need to be touched by anything after system + // init. Using page attributes is the more flexible and modern approach + // to memory type handling and they can override MTRRs in the critical + // case of write-combining, usually used for framebuffers. + dprintf("ignoring MTRRs due to PAT support\n"); + return 0; + } + if (sCpuModule == NULL) return 0; @@ -309,6 +322,25 @@ x86_set_mtrrs(uint8 defaultType, const x86_mtrr_info* infos, uint32 count) } +static void +init_pat(int cpu) +{ + disable_caches(); + + uint64 value = x86_read_msr(IA32_MSR_PAT); + dprintf("PAT MSR on CPU %d before init: %#" B_PRIx64 "\n", cpu, value); + + // Use PAT entry 4 for write-combining, leave the rest as is + value &= ~(IA32_MSR_PAT_ENTRY_MASK << IA32_MSR_PAT_ENTRY_SHIFT(4)); + value |= IA32_MSR_PAT_TYPE_WRITE_COMBINING << IA32_MSR_PAT_ENTRY_SHIFT(4); + + dprintf("PAT MSR on CPU %d after init: %#" B_PRIx64 "\n", cpu, value); + x86_write_msr(IA32_MSR_PAT, value); + + enable_caches(); +} + + void x86_init_fpu(void) { @@ -1476,6 +1508,13 @@ x86_check_feature(uint32 feature, enum x86_feature_type type) } +bool +x86_use_pat() +{ + return sUsePAT; +} + + void* x86_get_double_fault_stack(int32 cpu, size_t* _size) { @@ -1700,6 +1739,22 @@ arch_cpu_init_percpu(kernel_args* args, int cpu) if (x86_check_feature(IA32_FEATURE_MCE, FEATURE_COMMON)) x86_write_cr4(x86_read_cr4() | IA32_CR4_MCE); + if (cpu == 0) { + bool supportsPAT = x86_check_feature(IA32_FEATURE_PAT, FEATURE_COMMON); + sUsePAT = supportsPAT + && !get_safemode_boolean_early(args, B_SAFEMODE_DISABLE_PAT, false); + + if (sUsePAT) { + dprintf("using PAT for memory type configuration\n"); + } else { + dprintf("not using PAT for memory type configuration (%s)\n", + supportsPAT ? "disabled" : "unsupported"); + } + } + + if (sUsePAT) + init_pat(cpu); + #ifdef __x86_64__ // if RDTSCP or RDPID are available write cpu number in TSC_AUX if (x86_check_feature(IA32_FEATURE_AMD_EXT_RDTSCP, FEATURE_EXT_AMD) diff --git a/src/system/kernel/arch/x86/arch_vm.cpp b/src/system/kernel/arch/x86/arch_vm.cpp index 902fa7bbe4..9a6e611172 100644 --- a/src/system/kernel/arch/x86/arch_vm.cpp +++ b/src/system/kernel/arch/x86/arch_vm.cpp @@ -717,8 +717,6 @@ arch_vm_init_post_modules(kernel_args *args) // the x86 CPU modules are now accessible sMemoryTypeRegisterCount = x86_count_mtrrs(); - if (sMemoryTypeRegisterCount == 0) - return B_OK; // not very likely, but play safe here if (sMemoryTypeRegisterCount > kMaxMemoryTypeRegisters) diff --git a/src/system/kernel/arch/x86/paging/32bit/X86PagingMethod32Bit.h b/src/system/kernel/arch/x86/paging/32bit/X86PagingMethod32Bit.h index 7af0f1d3e8..04b43a2185 100644 --- a/src/system/kernel/arch/x86/paging/32bit/X86PagingMethod32Bit.h +++ b/src/system/kernel/arch/x86/paging/32bit/X86PagingMethod32Bit.h @@ -147,18 +147,14 @@ X86PagingMethod32Bit::ClearPageTableEntryFlags(page_table_entry* entry, uint32 f /*static*/ inline uint32 X86PagingMethod32Bit::MemoryTypeToPageTableEntryFlags(uint32 memoryType) { - // ATM we only handle the uncacheable and write-through type explicitly. For - // all other types we rely on the MTRRs to be set up correctly. Since we set - // the default memory type to write-back and since the uncacheable type in - // the PTE overrides any MTRR attribute (though, as per the specs, that is - // not recommended for performance reasons), this reduces the work we - // actually *have* to do with the MTRRs to setting the remaining types - // (usually only write-combining for the frame buffer). switch (memoryType) { case B_MTR_UC: return X86_PTE_CACHING_DISABLED | X86_PTE_WRITE_THROUGH; case B_MTR_WC: + if (x86_use_pat()) + return X86_PTE_PAT; + // X86_PTE_WRITE_THROUGH would be closer, but the combination with // MTRR WC is "implementation defined" for Pentium Pro/II. return 0; diff --git a/src/system/kernel/arch/x86/paging/64bit/X86PagingMethod64Bit.h b/src/system/kernel/arch/x86/paging/64bit/X86PagingMethod64Bit.h index e1e4a9c6d2..58c70510fd 100644 --- a/src/system/kernel/arch/x86/paging/64bit/X86PagingMethod64Bit.h +++ b/src/system/kernel/arch/x86/paging/64bit/X86PagingMethod64Bit.h @@ -167,21 +167,12 @@ X86PagingMethod64Bit::ClearTableEntryFlags(uint64_t* entryPointer, /*static*/ inline uint64 X86PagingMethod64Bit::MemoryTypeToPageTableEntryFlags(uint32 memoryType) { - // ATM we only handle the uncacheable and write-through type explicitly. For - // all other types we rely on the MTRRs to be set up correctly. Since we set - // the default memory type to write-back and since the uncacheable type in - // the PTE overrides any MTRR attribute (though, as per the specs, that is - // not recommended for performance reasons), this reduces the work we - // actually *have* to do with the MTRRs to setting the remaining types - // (usually only write-combining for the frame buffer). switch (memoryType) { case B_MTR_UC: return X86_64_PTE_CACHING_DISABLED | X86_64_PTE_WRITE_THROUGH; case B_MTR_WC: - // X86_PTE_WRITE_THROUGH would be closer, but the combination with - // MTRR WC is "implementation defined" for Pentium Pro/II. - return 0; + return x86_use_pat() ? X86_64_PTE_PAT : 0; case B_MTR_WT: return X86_64_PTE_WRITE_THROUGH; diff --git a/src/system/kernel/arch/x86/paging/pae/X86PagingMethodPAE.h b/src/system/kernel/arch/x86/paging/pae/X86PagingMethodPAE.h index bcc25f6696..7fd288a803 100644 --- a/src/system/kernel/arch/x86/paging/pae/X86PagingMethodPAE.h +++ b/src/system/kernel/arch/x86/paging/pae/X86PagingMethodPAE.h @@ -192,18 +192,14 @@ X86PagingMethodPAE::ClearTableEntryFlags(uint64_t* entry, uint64_t flags) /*static*/ inline uint64 X86PagingMethodPAE::MemoryTypeToPageTableEntryFlags(uint32 memoryType) { - // ATM we only handle the uncacheable and write-through type explicitly. For - // all other types we rely on the MTRRs to be set up correctly. Since we set - // the default memory type to write-back and since the uncacheable type in - // the PTE overrides any MTRR attribute (though, as per the specs, that is - // not recommended for performance reasons), this reduces the work we - // actually *have* to do with the MTRRs to setting the remaining types - // (usually only write-combining for the frame buffer). switch (memoryType) { case B_MTR_UC: return X86_PAE_PTE_CACHING_DISABLED | X86_PAE_PTE_WRITE_THROUGH; case B_MTR_WC: + if (x86_use_pat()) + return X86_PAE_PTE_PAT; + // X86_PTE_WRITE_THROUGH would be closer, but the combination with // MTRR WC is "implementation defined" for Pentium Pro/II. return 0;