mm/mtrr: Reintroduce logic to enable WC regions for FBs using MTRRs
diff --git a/common/lib/gterm.c b/common/lib/gterm.c
index c77d2dc6..e9027eaf 100644
--- a/common/lib/gterm.c
+++ b/common/lib/gterm.c
@@ -10,6 +10,7 @@
#include <lib/image.h>
#include <lib/rand.h>
#include <mm/pmm.h>
+#include <mm/mtrr.h>
#include <flanterm.h>
#include <flanterm_backends/fb.h>
#include <lib/term.h>
@@ -809,6 +810,19 @@ bool gterm_init(struct fb_info **_fbs, size_t *_fbs_count,
return false;
}
+#if defined (__i386__) || defined (__x86_64__)
+ for (size_t i = 0; i < fbs_count; i++) {
+ if (fbs[i].framebuffer_bpp != 32) {
+ continue;
+ }
+ uint64_t fb_size = (uint64_t)fbs[i].framebuffer_pitch * fbs[i].framebuffer_height;
+ if (fb_size == 0) {
+ continue;
+ }
+ mtrr_wc_add_fb_range(fbs[i].framebuffer_addr, fb_size);
+ }
+#endif
+
struct gterm_config cfg;
gterm_parse_config(config, &cfg);
diff --git a/common/lib/term.c b/common/lib/term.c
index 96f0e220..4b85cb33 100644
--- a/common/lib/term.c
+++ b/common/lib/term.c
@@ -6,6 +6,7 @@
#include <lib/misc.h>
#include <lib/fb.h>
#include <mm/pmm.h>
+#include <mm/mtrr.h>
#include <drivers/vga_textmode.h>
#include <flanterm_backends/fb.h>
@@ -19,6 +20,10 @@ size_t terms_i = 0;
int term_backend = _NOT_READY;
void term_notready(void) {
+#if defined (__i386__) || defined (__x86_64__)
+ mtrr_wc_clear_fb_ranges();
+#endif
+
for (size_t i = 0; i < terms_i; i++) {
struct flanterm_context *term = terms[i];
diff --git a/common/mm/mtrr.c b/common/mm/mtrr.c
index 2ab123f8..4e35da4b 100644
--- a/common/mm/mtrr.c
+++ b/common/mm/mtrr.c
@@ -143,4 +143,168 @@ void mtrr_restore(void) {
asm volatile ("mov %0, %%cr0" :: "r"(old_cr0) : "memory");
}
+#define MTRR_TYPE_WC 1
+#define WC_MAX_SLOTS 4
+
+static struct {
+ uint8_t slot;
+ uint64_t saved_base;
+ uint64_t saved_mask;
+} wc_saved[WC_MAX_SLOTS];
+static size_t wc_n_saved = 0;
+
+bool mtrr_wc_add_fb_range(uint64_t base, uint64_t size) {
+ if (size == 0 || !mtrr_supported()) {
+ return false;
+ }
+ if (wc_n_saved >= WC_MAX_SLOTS) {
+ return false;
+ }
+
+ uint64_t mtrrcap = rdmsr(0xfe);
+ if (!(mtrrcap & ((uint64_t)1 << 10))) {
+ return false;
+ }
+ uint8_t var_reg_count = mtrrcap & 0xff;
+
+ uint32_t eax, ebx, ecx, edx;
+ if (!cpuid(0x80000008, 0, &eax, &ebx, &ecx, &edx)) {
+ return false;
+ }
+ uint8_t maxphysaddr = eax & 0xff;
+ if (maxphysaddr < 32 || maxphysaddr > 52) {
+ return false;
+ }
+
+ base &= ~(uint64_t)0xfff;
+
+ uint64_t aligned_size = 0x1000;
+ while (aligned_size < size) {
+ aligned_size <<= 1;
+ if (aligned_size == 0) {
+ return false;
+ }
+ }
+ size = aligned_size;
+
+ // MTRR match is (addr & mask) == (base & mask); only correct when base is size-aligned.
+ if (base & (size - 1)) {
+ return false;
+ }
+
+ uint64_t mask = (((uint64_t)1 << maxphysaddr) - 1) & ~(size - 1);
+
+ for (uint8_t i = 0; i < var_reg_count; i++) {
+ uint64_t mb = rdmsr(0x200 + i * 2);
+ uint64_t mm = rdmsr(0x200 + i * 2 + 1);
+ if (!(mm & ((uint64_t)1 << 11))) {
+ continue;
+ }
+ uint64_t exist_mask = mm & ~(uint64_t)0xfff;
+ uint64_t exist_base = mb & ~(uint64_t)0xfff;
+ for (uint64_t a = base; a < base + size; a += 0x1000) {
+ if ((a & exist_mask) == (exist_base & exist_mask)) {
+ return false;
+ }
+ }
+ }
+
+ uint8_t slot;
+ bool found = false;
+ for (uint8_t i = 0; i < var_reg_count; i++) {
+ uint64_t mm = rdmsr(0x200 + i * 2 + 1);
+ if (!(mm & ((uint64_t)1 << 11))) {
+ slot = i;
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ return false;
+ }
+
+#if defined (UEFI)
+ asm volatile ("cli");
+#endif
+
+ uintptr_t old_cr0;
+ asm volatile ("mov %%cr0, %0" : "=r"(old_cr0) :: "memory");
+ uintptr_t new_cr0 = (old_cr0 | (1U << 30)) & ~((uintptr_t)1 << 29);
+ asm volatile ("mov %0, %%cr0" :: "r"(new_cr0) : "memory");
+ asm volatile ("wbinvd" ::: "memory");
+
+ uintptr_t cr3;
+ asm volatile ("mov %%cr3, %0" : "=r"(cr3) :: "memory");
+ asm volatile ("mov %0, %%cr3" :: "r"(cr3) : "memory");
+
+ uint64_t mtrr_def = rdmsr(0x2ff);
+ wrmsr(0x2ff, mtrr_def & ~((uint64_t)1 << 11));
+
+ wc_saved[wc_n_saved].slot = slot;
+ wc_saved[wc_n_saved].saved_base = rdmsr(0x200 + slot * 2);
+ wc_saved[wc_n_saved].saved_mask = rdmsr(0x200 + slot * 2 + 1);
+ wc_n_saved++;
+
+ wrmsr(0x200 + slot * 2, base | MTRR_TYPE_WC);
+ wrmsr(0x200 + slot * 2 + 1, mask | ((uint64_t)1 << 11));
+
+ wrmsr(0x2ff, mtrr_def | ((uint64_t)1 << 11));
+
+ asm volatile ("mov %%cr3, %0" : "=r"(cr3) :: "memory");
+ asm volatile ("mov %0, %%cr3" :: "r"(cr3) : "memory");
+ asm volatile ("wbinvd" ::: "memory");
+ asm volatile ("mov %0, %%cr0" :: "r"(old_cr0) : "memory");
+
+#if defined (UEFI)
+ asm volatile ("sti");
+#endif
+
+ return true;
+}
+
+void mtrr_wc_clear_fb_ranges(void) {
+ if (wc_n_saved == 0) {
+ return;
+ }
+ if (!mtrr_supported()) {
+ wc_n_saved = 0;
+ return;
+ }
+
+#if defined (UEFI)
+ asm volatile ("cli");
+#endif
+
+ uintptr_t old_cr0;
+ asm volatile ("mov %%cr0, %0" : "=r"(old_cr0) :: "memory");
+ uintptr_t new_cr0 = (old_cr0 | (1U << 30)) & ~((uintptr_t)1 << 29);
+ asm volatile ("mov %0, %%cr0" :: "r"(new_cr0) : "memory");
+ asm volatile ("wbinvd" ::: "memory");
+
+ uintptr_t cr3;
+ asm volatile ("mov %%cr3, %0" : "=r"(cr3) :: "memory");
+ asm volatile ("mov %0, %%cr3" :: "r"(cr3) : "memory");
+
+ uint64_t mtrr_def = rdmsr(0x2ff);
+ wrmsr(0x2ff, mtrr_def & ~((uint64_t)1 << 11));
+
+ for (size_t i = 0; i < wc_n_saved; i++) {
+ wrmsr(0x200 + wc_saved[i].slot * 2, wc_saved[i].saved_base);
+ wrmsr(0x200 + wc_saved[i].slot * 2 + 1, wc_saved[i].saved_mask);
+ }
+
+ wrmsr(0x2ff, mtrr_def);
+
+ asm volatile ("mov %%cr3, %0" : "=r"(cr3) :: "memory");
+ asm volatile ("mov %0, %%cr3" :: "r"(cr3) : "memory");
+ asm volatile ("wbinvd" ::: "memory");
+ asm volatile ("mov %0, %%cr0" :: "r"(old_cr0) : "memory");
+
+#if defined (UEFI)
+ asm volatile ("sti");
+#endif
+
+ wc_n_saved = 0;
+}
+
#endif
diff --git a/common/mm/mtrr.h b/common/mm/mtrr.h
index 9cead124..2c005d78 100644
--- a/common/mm/mtrr.h
+++ b/common/mm/mtrr.h
@@ -1,11 +1,17 @@
#ifndef MM__MTRR_H__
#define MM__MTRR_H__
+#include <stdint.h>
+#include <stdbool.h>
+
#if defined (__x86_64__) || defined (__i386__)
void mtrr_save(void);
void mtrr_restore(void);
+bool mtrr_wc_add_fb_range(uint64_t base, uint64_t size);
+void mtrr_wc_clear_fb_ranges(void);
+
#endif
#endif
