]> git.ipfire.org Git - thirdparty/grub.git/commitdiff
Use write-combining MTRR to speed up video with buggy BIOSes.
authorColin Watson <cjwatson@ubuntu.com>
Mon, 27 Feb 2012 21:31:51 +0000 (22:31 +0100)
committerVladimir 'phcoder' Serbinenko <phcoder@gmail.com>
Mon, 27 Feb 2012 21:31:51 +0000 (22:31 +0100)
* 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
grub-core/video/i386/pc/vbe.c

index eda01e8ec9e476d06c97f4f2b58e465a5684a0ae..97aef946e5950c0f9ab383cfd6b16ec7dd481b75 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,23 @@
+2012-02-27  Colin Watson  <cjwatson@ubuntu.com>
+
+       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  <cjwatson@ubuntu.com>
 
        * include/grub/partition.h (grub_partition_map): Change prototype of
index 51815d9628830987926cb32ea759285347d7619a..5656388a6cbe397745ca1081d31f31c9324446c2 100644 (file)
@@ -29,6 +29,7 @@
 #include <grub/video.h>
 #include <grub/i386/pc/int.h>
 #include <grub/i18n.h>
+#include <grub/cpu/tsc.h>
 
 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, &regs);
+
+  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