From cfe456dc03fc29833ba7e7c77e03d25b5aa73217 Mon Sep 17 00:00:00 2001 From: Colin Watson Date: Mon, 27 Feb 2012 22:31:51 +0100 Subject: [PATCH] Use write-combining MTRR to speed up video with buggy BIOSes. * grub-core/video/i386/pc/vbe.c (framebuffer): New member mtrr. (cpuid): New define. (rdmsr): Likewise. (wrmsr): Likewise. (mtrr_base): Likewise. (mtrr_mask): Likewise. (grub_vbe_enable_mtrr_entry): New function. (grub_vbe_enable_mtrr): Likewise. (grub_vbe_disable_mtrr): Likewise. (grub_vbe_bios_set_display_start): Disable mtrr when handing the control off to BIOS. (grub_video_vbe_init): Fill mtrr. (grub_video_vbe_fini): Disable mtrr. (grub_video_vbe_get_info_and_fini): Likewise. (grub_video_vbe_setup): Enable mtrr. --- ChangeLog | 20 ++++ grub-core/video/i386/pc/vbe.c | 196 +++++++++++++++++++++++++++++++++- 2 files changed, 214 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index eda01e8ec..97aef946e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,23 @@ +2012-02-27 Colin Watson + + Use write-combining MTRR to speed up video with buggy BIOSes. + + * grub-core/video/i386/pc/vbe.c (framebuffer): New member mtrr. + (cpuid): New define. + (rdmsr): Likewise. + (wrmsr): Likewise. + (mtrr_base): Likewise. + (mtrr_mask): Likewise. + (grub_vbe_enable_mtrr_entry): New function. + (grub_vbe_enable_mtrr): Likewise. + (grub_vbe_disable_mtrr): Likewise. + (grub_vbe_bios_set_display_start): Disable mtrr when handing the + control off to BIOS. + (grub_video_vbe_init): Fill mtrr. + (grub_video_vbe_fini): Disable mtrr. + (grub_video_vbe_get_info_and_fini): Likewise. + (grub_video_vbe_setup): Enable mtrr. + 2012-02-27 Colin Watson * include/grub/partition.h (grub_partition_map): Change prototype of diff --git a/grub-core/video/i386/pc/vbe.c b/grub-core/video/i386/pc/vbe.c index 51815d962..5656388a6 100644 --- a/grub-core/video/i386/pc/vbe.c +++ b/grub-core/video/i386/pc/vbe.c @@ -29,6 +29,7 @@ #include #include #include +#include GRUB_MOD_LICENSE ("GPLv3+"); @@ -44,6 +45,7 @@ static struct struct grub_video_mode_info mode_info; grub_uint8_t *ptr; + int mtrr; } framebuffer; static grub_uint32_t initial_vbe_mode; @@ -56,6 +58,171 @@ real2pm (grub_vbe_farptr_t ptr) + ((unsigned long) ptr & 0x0000FFFF)); } +#define cpuid(num,a,b,c,d) \ + asm volatile ("xchgl %%ebx, %1; cpuid; xchgl %%ebx, %1" \ + : "=a" (a), "=r" (b), "=c" (c), "=d" (d) \ + : "0" (num)) + +#define rdmsr(num,a,d) \ + asm volatile ("rdmsr" : "=a" (a), "=d" (d) : "c" (num)) + +#define wrmsr(num,lo,hi) \ + asm volatile ("wrmsr" : : "c" (num), "a" (lo), "d" (hi) : "memory") + +#define mtrr_base(reg) (0x200 + (reg) * 2) +#define mtrr_mask(reg) (0x200 + (reg) * 2 + 1) + +/* Try to set up a variable-range write-combining MTRR for a memory region. + This is best-effort; if it seems too hard, we just accept the performance + degradation rather than risking undefined behaviour. It is intended + exclusively to work around BIOS bugs, as the BIOS should already be + setting up a suitable MTRR. */ +static void +grub_vbe_enable_mtrr_entry (int mtrr) +{ + grub_uint32_t eax, edx; + grub_uint32_t mask_lo, mask_hi; + + rdmsr (mtrr_mask (mtrr), eax, edx); + mask_lo = eax; + mask_hi = edx; + + mask_lo |= 0x800 /* valid */; + wrmsr (mtrr_mask (mtrr), mask_lo, mask_hi); +} + +static void +grub_vbe_enable_mtrr (grub_uint8_t *base, grub_size_t size) +{ + grub_uint32_t eax, ebx, ecx, edx; + grub_uint32_t features; + grub_uint32_t mtrrcap; + int var_mtrrs; + grub_uint32_t max_extended_cpuid; + grub_uint32_t maxphyaddr; + grub_uint64_t fb_base, fb_size; + grub_uint64_t size_bits, fb_mask; + grub_uint32_t bits_lo, bits_hi; + grub_uint64_t bits; + int i, first_unused = -1; + grub_uint32_t base_lo, base_hi, mask_lo, mask_hi; + + fb_base = (grub_uint64_t) (grub_size_t) base; + fb_size = (grub_uint64_t) size; + + /* Check that fb_base and fb_size can be represented using a single + MTRR. */ + + if (fb_base < (1 << 20)) + return; /* under 1MB, so covered by fixed-range MTRRs */ + if (fb_base >= (1LL << 36)) + return; /* over 36 bits, so out of range */ + if (fb_size < (1 << 12)) + return; /* variable-range MTRRs must cover at least 4KB */ + + size_bits = fb_size; + while (size_bits > 1) + size_bits >>= 1; + if (size_bits != 1) + return; /* not a power of two */ + + if (fb_base & (fb_size - 1)) + return; /* not aligned on size boundary */ + + fb_mask = ~(fb_size - 1); + + /* Check CPU capabilities. */ + + if (! grub_cpu_is_cpuid_supported ()) + return; + + cpuid (1, eax, ebx, ecx, edx); + features = edx; + if (! (features & 0x00001000)) /* MTRR */ + return; + + rdmsr (0xFE, eax, edx); + mtrrcap = eax; + if (! (mtrrcap & 0x00000400)) /* write-combining */ + return; + var_mtrrs = (mtrrcap & 0xFF); + + cpuid (0x80000000, eax, ebx, ecx, edx); + max_extended_cpuid = eax; + if (max_extended_cpuid >= 0x80000008) + { + cpuid (0x80000008, eax, ebx, ecx, edx); + maxphyaddr = (eax & 0xFF); + } + else + maxphyaddr = 36; + bits_lo = 0xFFFFF000; /* assume maxphyaddr >= 36 */ + bits_hi = (1 << (maxphyaddr - 32)) - 1; + bits = bits_lo | ((grub_uint64_t) bits_hi << 32); + + /* Check whether an MTRR already covers this region. If not, take an + unused one if possible. */ + for (i = 0; i < var_mtrrs; i++) + { + rdmsr (mtrr_mask (i), eax, edx); + mask_lo = eax; + mask_hi = edx; + if (mask_lo & 0x800) /* valid */ + { + grub_uint64_t real_base, real_mask; + + rdmsr (mtrr_base (i), eax, edx); + base_lo = eax; + base_hi = edx; + + real_base = ((grub_uint64_t) (base_hi & bits_hi) << 32) | + (base_lo & bits_lo); + real_mask = ((grub_uint64_t) (mask_hi & bits_hi) << 32) | + (mask_lo & bits_lo); + if (real_base < (fb_base + fb_size) && + real_base + (~real_mask & bits) >= fb_base) + return; /* existing MTRR overlaps this region */ + } + else if (first_unused < 0) + first_unused = i; + } + + if (first_unused < 0) + return; /* all MTRRs in use */ + + /* Set up the first unused MTRR we found. */ + rdmsr (mtrr_base (first_unused), eax, edx); + base_lo = eax; + base_hi = edx; + rdmsr (mtrr_mask (first_unused), eax, edx); + mask_lo = eax; + mask_hi = edx; + + base_lo = (base_lo & ~bits_lo & ~0xFF) | + (fb_base & bits_lo) | 0x01 /* WC */; + base_hi = (base_hi & ~bits_hi) | ((fb_base >> 32) & bits_hi); + wrmsr (mtrr_base (first_unused), base_lo, base_hi); + mask_lo = (mask_lo & ~bits_lo) | (fb_mask & bits_lo) | 0x800 /* valid */; + mask_hi = (mask_hi & ~bits_hi) | ((fb_mask >> 32) & bits_hi); + wrmsr (mtrr_mask (first_unused), mask_lo, mask_hi); + + framebuffer.mtrr = first_unused; +} + +static void +grub_vbe_disable_mtrr (int mtrr) +{ + grub_uint32_t eax, edx; + grub_uint32_t mask_lo, mask_hi; + + rdmsr (mtrr_mask (mtrr), eax, edx); + mask_lo = eax; + mask_hi = edx; + + mask_lo &= ~0x800 /* valid */; + wrmsr (mtrr_mask (mtrr), mask_lo, mask_hi); +} + /* Call VESA BIOS 0x4f09 to set palette data, return status. */ static grub_vbe_status_t grub_vbe_bios_set_palette_data (grub_uint32_t color_count, @@ -221,6 +388,9 @@ grub_vbe_bios_set_display_start (grub_uint32_t x, grub_uint32_t y) { struct grub_bios_int_registers regs; + if (framebuffer.mtrr >= 0) + grub_vbe_disable_mtrr (framebuffer.mtrr); + /* Store x in %ecx. */ regs.ecx = x; regs.edx = y; @@ -229,6 +399,10 @@ grub_vbe_bios_set_display_start (grub_uint32_t x, grub_uint32_t y) regs.ebx = 0x0080; regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT; grub_bios_interrupt (0x10, ®s); + + if (framebuffer.mtrr >= 0) + grub_vbe_enable_mtrr_entry (framebuffer.mtrr); + return regs.eax & 0xffff; } @@ -325,7 +499,6 @@ grub_vbe_bios_read_edid (struct grub_video_edid_info *edid_info) return regs.eax & 0xffff; } - grub_err_t grub_vbe_probe (struct grub_vbe_info_block *info_block) { @@ -630,6 +803,7 @@ grub_video_vbe_init (void) /* Reset frame buffer. */ grub_memset (&framebuffer, 0, sizeof(framebuffer)); + framebuffer.mtrr = -1; return grub_video_fb_init (); } @@ -655,6 +829,11 @@ grub_video_vbe_fini (void) vbe_mode_list = NULL; err = grub_video_fb_fini (); + if (framebuffer.mtrr >= 0) + { + grub_vbe_disable_mtrr (framebuffer.mtrr); + framebuffer.mtrr = -1; + } return err; } @@ -956,6 +1135,10 @@ grub_video_vbe_setup (unsigned int width, unsigned int height, /* Copy default palette to initialize emulated palette. */ err = grub_video_fb_set_palette (0, GRUB_VIDEO_FBSTD_NUMCOLORS, grub_video_fbstd_colors); + + grub_vbe_enable_mtrr (framebuffer.ptr, + controller_info.total_memory << 16); + return err; } @@ -986,9 +1169,18 @@ static grub_err_t grub_video_vbe_get_info_and_fini (struct grub_video_mode_info *mode_info, void **framebuf) { + grub_err_t err; grub_free (vbe_mode_list); vbe_mode_list = NULL; - return grub_video_fb_get_info_and_fini (mode_info, framebuf); + err = grub_video_fb_get_info_and_fini (mode_info, framebuf); + if (err) + return err; + if (framebuffer.mtrr >= 0) + { + grub_vbe_disable_mtrr (framebuffer.mtrr); + framebuffer.mtrr = -1; + } + return GRUB_ERR_NONE; } static void