grub-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH] Preferred resolution detection for VBE


From: Colin Watson
Subject: [PATCH] Preferred resolution detection for VBE
Date: Tue, 14 Dec 2010 16:38:36 +0000
User-agent: Mutt/1.5.18 (2008-05-17)

I'd like to use GRUB_GFXMODE=auto as a default.  It's more appropriate
on more platforms than the current default of 640x480, and generally
speaking I think we should be trying to use something as close to the
native panel resolution as we can.

I'm a bit nervous of doing this right now, though.  It seems safe on
most drivers, but on vbe it just picks the largest mode it can find.
The mode list comes from the VESA BIOS on the graphics card, and may not
bear much resemblance to what the monitor can do.  I realise most
current monitors are pretty robust, but I've heard too many horror
stories of people managing to blow up CRTs with excessive modes to be
entirely comfortable with this, and even aside from that it probably
just won't look very good if we try to do 1600x1200 on a 14" screen, if
it shows anything at all.

VBE offers a couple of better ways to do this.  They aren't supported
quite everywhere, but they aren't that unusual these days.  Firstly,
there's EDID
(http://en.wikipedia.org/wiki/Extended_display_identification_data),
which gets information from the connected monitor.  Version 1.3 and
above provides a preferred mode.  Secondly, there are the Flat Panel
extensions (VBE/FP - you can find at least a draft spec on Google,
though it seems tricky to find a final version), which has a nice simple
function to get the panel properties.  The X.org VESA driver tries the
former followed by the latter, so this should be a safe approach.  We
can then fall back to 640x480 to make sure that "auto" is a safe
default.

I've pushed this to
bzr+ssh://bzr.sv.gnu.org/grub/branches/vbe-autodetect/, currently based
on my previous parse-color branch since it was convenient to test both
at once.

2010-12-14  Colin Watson  <address@hidden>

        Preferred resolution detection for VBE.

        * grub-core/video/i386/pc/vbe.c (grub_vbe_bios_get_flat_panel_info):
        New function.
        (grub_vbe_bios_get_ddc_capabilities): Likewise.
        (grub_vbe_bios_read_edid): Likewise.
        (grub_vbe_edid_checksum): Likewise.
        (grub_vbe_get_preferred_mode): Likewise.  Try EDID followed by the
        Flat Panel extension, in line with the X.org VESA driver.
        (grub_video_vbe_setup): When the mode is "auto", try to get the
        preferred mode from VBE, and use the largest mode that is no larger
        than the preferred mode (some BIOSes expose a preferred mode that is
        not in their mode list!).  If this fails, fall back to 640x480 as a
        safe conservative choice.
        * include/grub/i386/pc/vbe.h (struct grub_vbe_flat_panel_info): New
        structure.
        (struct grub_vbe_edid_info): Likewise.
        (grub_vbe_bios_get_flat_panel_info): Add prototype.
        (grub_vbe_bios_get_ddc_capabilities): Likewise.
        (grub_vbe_bios_read_edid): Likewise.

        * util/grub.d/00_header.in (GRUB_GFXMODE): Default to "auto".  This
        is more appropriate on a wider range of platforms than 640x480.

=== modified file 'grub-core/video/i386/pc/vbe.c'
--- grub-core/video/i386/pc/vbe.c       2010-09-15 22:37:30 +0000
+++ grub-core/video/i386/pc/vbe.c       2010-12-14 16:22:19 +0000
@@ -273,6 +273,56 @@ grub_vbe_bios_get_pm_interface (grub_uin
   return regs.eax & 0xffff;
 }
 
+/* Call VESA BIOS 0x4f11 to get flat panel information, return status.  */
+grub_vbe_status_t
+grub_vbe_bios_get_flat_panel_info (struct grub_vbe_flat_panel_info 
*flat_panel_info)
+{
+  struct grub_bios_int_registers regs;
+
+  regs.eax = 0x4f11;
+  regs.ebx = 0x0001;
+  regs.es = (((grub_addr_t) flat_panel_info) & 0xffff0000) >> 4;
+  regs.edi = ((grub_addr_t) flat_panel_info) & 0xffff;
+  regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT;
+  grub_bios_interrupt (0x10, &regs);
+  return regs.eax & 0xffff;
+}
+
+/* Call VESA BIOS 0x4f15 to get DDC availability, return status.  */
+grub_vbe_status_t
+grub_vbe_bios_get_ddc_capabilities (grub_uint8_t *level)
+{
+  struct grub_bios_int_registers regs;
+
+  regs.eax = 0x4f15;
+  regs.ebx = 0x0000;
+  regs.ecx = 0x0000;
+  regs.es = 0x0000;
+  regs.edi = 0x0000;
+  regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT;
+  grub_bios_interrupt (0x10, &regs);
+
+  *level = regs.ebx & 0xff;
+  return regs.eax & 0xffff;
+}
+
+/* Call VESA BIOS 0x4f15 to read EDID information, return status.  */
+grub_vbe_status_t
+grub_vbe_bios_read_edid (struct grub_vbe_edid_info *edid_info)
+{
+  struct grub_bios_int_registers regs;
+
+  regs.eax = 0x4f15;
+  regs.ebx = 0x0001;
+  regs.ecx = 0x0000;
+  regs.edx = 0x0000;
+  regs.es = (((grub_addr_t) edid_info) & 0xffff0000) >> 4;
+  regs.edi = ((grub_addr_t) edid_info) & 0xffff;
+  regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT;
+  grub_bios_interrupt (0x10, &regs);
+  return regs.eax & 0xffff;
+}
+
 
 grub_err_t
 grub_vbe_probe (struct grub_vbe_info_block *info_block)
@@ -327,6 +377,70 @@ grub_vbe_probe (struct grub_vbe_info_blo
   return GRUB_ERR_NONE;
 }
 
+static grub_err_t
+grub_vbe_edid_checksum (struct grub_vbe_edid_info *edid_info)
+{
+  const char *edid_bytes = (const char *) edid_info;
+  int i;
+  char checksum = 0;
+
+  /* Check EDID checksum.  */
+  for (i = 0; i < 128; ++i)
+    checksum += edid_bytes[i];
+
+  if (checksum != 0)
+    return grub_error (GRUB_ERR_BAD_DEVICE,
+                      "invalid EDID checksum %d", checksum);
+
+  grub_errno = GRUB_ERR_NONE;
+  return grub_errno;
+}
+
+static grub_err_t
+grub_vbe_get_preferred_mode (unsigned int *width, unsigned int *height)
+{
+  grub_vbe_status_t status;
+  grub_uint8_t ddc_level;
+  struct grub_vbe_edid_info edid_info;
+  struct grub_vbe_flat_panel_info flat_panel_info;
+
+  if (controller_info.version >= 0x200
+      && (grub_vbe_bios_get_ddc_capabilities (&ddc_level) & 0xff)
+        == GRUB_VBE_STATUS_OK)
+    {
+      status = grub_vbe_bios_read_edid (&edid_info);
+      /* Bit 1 in the Feature Support field indicates that the first
+         Detailed Timing Description is the preferred timing mode.  */
+      if (status == GRUB_VBE_STATUS_OK
+         && grub_vbe_edid_checksum (&edid_info) == GRUB_ERR_NONE
+         && edid_info.version == 1 /* we don't understand later versions */
+         && (edid_info.feature_support
+             & GRUB_VBE_EDID_FEATURE_PREFERRED_TIMING_MODE)
+         && edid_info.detailed_timings[0].pixel_clock)
+       {
+         *width = edid_info.detailed_timings[0].horizontal_active_lo
+                  | (((unsigned int)
+                      (edid_info.detailed_timings[0].horizontal_hi & 0xf0))
+                     << 4);
+         *height = edid_info.detailed_timings[0].vertical_active_lo
+                   | (((unsigned int)
+                       (edid_info.detailed_timings[0].vertical_hi & 0xf0))
+                      << 4);
+         return GRUB_ERR_NONE;
+       }
+    }
+
+  status = grub_vbe_bios_get_flat_panel_info (&flat_panel_info);
+  if (status == GRUB_VBE_STATUS_OK)
+    {
+      *width = flat_panel_info.horizontal_size;
+      *height = flat_panel_info.vertical_size;
+      return GRUB_ERR_NONE;
+    }
+
+  return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "cannot get preferred mode");
+}
+
 grub_err_t
 grub_vbe_set_video_mode (grub_uint32_t vbe_mode,
                         struct grub_vbe_mode_info_block *vbe_mode_info)
@@ -695,11 +809,28 @@ grub_video_vbe_setup (unsigned int width
   struct grub_vbe_mode_info_block best_vbe_mode_info;
   grub_uint32_t best_vbe_mode = 0;
   int depth;
+  int preferred_mode = 0;
 
   /* Decode depth from mode_type.  If it is zero, then autodetect.  */
   depth = (mode_type & GRUB_VIDEO_MODE_TYPE_DEPTH_MASK)
           >> GRUB_VIDEO_MODE_TYPE_DEPTH_POS;
 
+  if (width == 0 && height == 0)
+    {
+      grub_vbe_get_preferred_mode (&width, &height);
+      if (grub_errno == GRUB_ERR_NONE)
+       preferred_mode = 1;
+      else
+       {
+         /* Fall back to 640x480.  This is conservative, but the largest
+            mode supported by the graphics card may not be safe for the
+            display device.  */
+         grub_errno = GRUB_ERR_NONE;
+         width = 640;
+         height = 480;
+       }
+    }
+
   /* Walk thru mode list and try to find matching mode.  */
   for (p = vbe_mode_list; *p != 0xFFFF; p++)
     {
@@ -742,10 +873,21 @@ grub_video_vbe_setup (unsigned int width
        /* Unsupported bitdepth . */
         continue;
 
-      if (((vbe_mode_info.x_resolution != width)
-          || (vbe_mode_info.y_resolution != height)) && width != 0 && height 
!= 0)
-        /* Non matching resolution.  */
-        continue;
+      if (preferred_mode)
+       {
+         if (vbe_mode_info.x_resolution > width
+             || vbe_mode_info.y_resolution > height)
+           /* Resolution exceeds that of preferred mode.  */
+           continue;
+       }
+      else
+       {
+         if (((vbe_mode_info.x_resolution != width)
+              || (vbe_mode_info.y_resolution != height))
+             && width != 0 && height != 0)
+           /* Non matching resolution.  */
+           continue;
+       }
 
       /* Check if user requested RGB or index color mode.  */
       if ((mode_mask & GRUB_VIDEO_MODE_TYPE_COLOR_MASK) != 0)

=== modified file 'include/grub/i386/pc/vbe.h'
--- include/grub/i386/pc/vbe.h  2010-09-15 22:37:30 +0000
+++ include/grub/i386/pc/vbe.h  2010-12-14 16:22:19 +0000
@@ -169,6 +169,81 @@ struct grub_vbe_palette_data
   grub_uint8_t alignment;
 } __attribute__ ((packed));
 
+struct grub_vbe_flat_panel_info
+{
+  grub_uint16_t horizontal_size;
+  grub_uint16_t vertical_size;
+  grub_uint16_t panel_type;
+  grub_uint8_t red_bpp;
+  grub_uint8_t green_bpp;
+  grub_uint8_t blue_bpp;
+  grub_uint8_t reserved_bpp;
+  grub_uint32_t reserved_offscreen_mem_size;
+  grub_vbe_farptr_t reserved_offscreen_mem_ptr;
+
+  grub_uint8_t reserved[14];
+} __attribute__ ((packed));
+
+struct grub_vbe_edid_info
+{
+  grub_uint8_t header[8];
+  grub_uint16_t manufacturer_id;
+  grub_uint16_t product_id;
+  grub_uint32_t serial_number;
+  grub_uint8_t week_of_manufacture;
+  grub_uint8_t year_of_manufacture;
+  grub_uint8_t version;
+  grub_uint8_t revision;
+
+  grub_uint8_t video_input_definition;
+  grub_uint8_t max_horizontal_image_size;
+  grub_uint8_t max_vertical_image_size;
+  grub_uint8_t display_gamma;
+  grub_uint8_t feature_support;
+#define GRUB_VBE_EDID_FEATURE_PREFERRED_TIMING_MODE    (1 << 1)
+
+  grub_uint8_t red_green_lo;
+  grub_uint8_t blue_white_lo;
+  grub_uint8_t red_x_hi;
+  grub_uint8_t red_y_hi;
+  grub_uint8_t green_x_hi;
+  grub_uint8_t green_y_hi;
+  grub_uint8_t blue_x_hi;
+  grub_uint8_t blue_y_hi;
+  grub_uint8_t white_x_hi;
+  grub_uint8_t white_y_hi;
+
+  grub_uint8_t established_timings_1;
+  grub_uint8_t established_timings_2;
+  grub_uint8_t manufacturer_reserved_timings;
+
+  grub_uint16_t standard_timings[8];
+
+  struct {
+    grub_uint16_t pixel_clock;
+    /* Only valid if the pixel clock is non-null.  */
+    grub_uint8_t horizontal_active_lo;
+    grub_uint8_t horizontal_blanking_lo;
+    grub_uint8_t horizontal_hi;
+    grub_uint8_t vertical_active_lo;
+    grub_uint8_t vertical_blanking_lo;
+    grub_uint8_t vertical_hi;
+    grub_uint8_t horizontal_sync_offset_lo;
+    grub_uint8_t horizontal_sync_pulse_width_lo;
+    grub_uint8_t vertical_sync_lo;
+    grub_uint8_t sync_hi;
+    grub_uint8_t horizontal_image_size_lo;
+    grub_uint8_t vertical_image_size_lo;
+    grub_uint8_t image_size_hi;
+    grub_uint8_t horizontal_border;
+    grub_uint8_t vertical_border;
+    grub_uint8_t flags;
+  } detailed_timings[4];
+
+  grub_uint8_t extension_flag;
+  grub_uint8_t checksum;
+} __attribute__ ((packed));
+
 /* Prototypes for helper functions.  */
 /* Call VESA BIOS 0x4f00 to get VBE Controller Information, return status.  */
 grub_vbe_status_t 
@@ -197,6 +272,15 @@ grub_vbe_bios_get_scanline_length (grub_
 grub_vbe_status_t 
 grub_vbe_bios_get_display_start (grub_uint32_t *x,
                                 grub_uint32_t *y);
+/* Call VESA BIOS 0x4f11 to get flat panel information, return status.  */
+grub_vbe_status_t
+grub_vbe_bios_get_flat_panel_info (struct grub_vbe_flat_panel_info 
*flat_panel_info);
+/* Call VESA BIOS 0x4f15 to get DDC availability, return status.  */
+grub_vbe_status_t
+grub_vbe_bios_get_ddc_capabilities (grub_uint8_t *level);
+/* Call VESA BIOS 0x4f15 to read EDID information, return status.  */
+grub_vbe_status_t
+grub_vbe_bios_read_edid (struct grub_vbe_edid_info *edid_data);
 
 grub_vbe_status_t grub_vbe_bios_getset_dac_palette_width (int set, int *width);
 

=== modified file 'util/grub.d/00_header.in'
--- util/grub.d/00_header.in    2010-12-10 11:45:08 +0000
+++ util/grub.d/00_header.in    2010-12-14 16:22:19 +0000
@@ -36,7 +36,7 @@ done
 if [ "x${GRUB_DEFAULT}" = "x" ] ; then GRUB_DEFAULT=0 ; fi
 if [ "x${GRUB_DEFAULT}" = "xsaved" ] ; then GRUB_DEFAULT='${saved_entry}' ; fi
 if [ "x${GRUB_TIMEOUT}" = "x" ] ; then GRUB_TIMEOUT=5 ; fi
-if [ "x${GRUB_GFXMODE}" = "x" ] ; then GRUB_GFXMODE=640x480 ; fi
+if [ "x${GRUB_GFXMODE}" = "x" ] ; then GRUB_GFXMODE=auto ; fi
 
 if [ "x${GRUB_DEFAULT_BUTTON}" = "x" ] ; then 
GRUB_DEFAULT_BUTTON="$GRUB_DEFAULT" ; fi
 if [ "x${GRUB_DEFAULT_BUTTON}" = "xsaved" ] ; then 
GRUB_DEFAULT_BUTTON='${saved_entry}' ; fi

-- 
Colin Watson                                       address@hidden



reply via email to

[Prev in Thread] Current Thread [Next in Thread]