mboot.c32: add VESA mode setting support syslinux-3.84-pre2
authorH. Peter Anvin <hpa@zytor.com>
Sat, 14 Nov 2009 04:16:43 +0000 (20:16 -0800)
committerH. Peter Anvin <hpa@zytor.com>
Sat, 14 Nov 2009 04:22:44 +0000 (20:22 -0800)
Add VESA mode setting support per the Multiboot specification.

Signed-off-by: H. Peter Anvin <hpa@zytor.com>
com32/mboot/Makefile
com32/mboot/initvesa.c [new file with mode: 0644]
com32/mboot/map.c
com32/mboot/mboot.c
com32/mboot/mboot.h
com32/mboot/vesa.h [new file with mode: 0644]

index a690271..c1ac76f 100644 (file)
@@ -24,7 +24,7 @@ LNXLIBS          = ../libutil/libutil_lnx.a
 MODULES          = mboot.c32
 TESTFILES =
 
-OBJS = mboot.o map.o mem.o apm.o solaris.o
+OBJS = mboot.o map.o mem.o initvesa.o apm.o solaris.o
 
 all: $(MODULES) $(TESTFILES)
 
diff --git a/com32/mboot/initvesa.c b/com32/mboot/initvesa.c
new file mode 100644 (file)
index 0000000..5452648
--- /dev/null
@@ -0,0 +1,219 @@
+/* ----------------------------------------------------------------------- *
+ *
+ *   Copyright 1999-2008 H. Peter Anvin - All Rights Reserved
+ *   Copyright 2009 Intel Corporation; author: H. Peter Anvin
+ *
+ *   Permission is hereby granted, free of charge, to any person
+ *   obtaining a copy of this software and associated documentation
+ *   files (the "Software"), to deal in the Software without
+ *   restriction, including without limitation the rights to use,
+ *   copy, modify, merge, publish, distribute, sublicense, and/or
+ *   sell copies of the Software, and to permit persons to whom
+ *   the Software is furnished to do so, subject to the following
+ *   conditions:
+ *
+ *   The above copyright notice and this permission notice shall
+ *   be included in all copies or substantial portions of the Software.
+ *
+ *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ *   OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ *   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ *   HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ *   WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ *   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ *   OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * ----------------------------------------------------------------------- */
+
+/*
+ * initvesa.c
+ *
+ * Query the VESA BIOS and select a 640x480x32 mode with local mapping
+ * support, if one exists.
+ */
+
+#include <inttypes.h>
+#include <com32.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#include "vesa.h"
+#include "mboot.h"
+
+struct vesa_info vesa_info;
+
+void set_graphics_mode(const struct multiboot_header *mbh,
+                      struct multiboot_info *mbi)
+{
+    com32sys_t rm;
+    uint16_t mode, bestmode, *mode_ptr;
+    struct vesa_general_info *gi;
+    struct vesa_mode_info *mi;
+    int pxf, bestpxf;
+    int wantx, wanty;
+    int err, besterr;
+    bool better;
+    addr_t viaddr;
+
+    /* Only do this if requested by the OS image */
+    if (!(mbh->flags & MULTIBOOT_VIDEO_MODE) || mbh->mode_type != 0)
+       return;
+
+    /* Allocate space in the bounce buffer for these structures */
+    gi = &((struct vesa_info *)__com32.cs_bounce)->gi;
+    mi = &((struct vesa_info *)__com32.cs_bounce)->mi;
+
+    memset(&rm, 0, sizeof rm);
+    memset(gi, 0, sizeof *gi);
+
+    gi->signature = VBE2_MAGIC;        /* Get VBE2 extended data */
+    rm.eax.w[0] = 0x4F00;      /* Get SVGA general information */
+    rm.edi.w[0] = OFFS(gi);
+    rm.es = SEG(gi);
+    __intcall(0x10, &rm, &rm);
+
+    if (rm.eax.w[0] != 0x004F)
+       return;                 /* Function call failed */
+    if (gi->signature != VESA_MAGIC)
+       return;                 /* No magic */
+    if (gi->version < 0x0102)
+       return;                 /* VESA 1.2+ required */
+
+    memcpy(&vesa_info.gi, gi, sizeof *gi);
+
+    /* Search for a suitable mode with a suitable color and memory model... */
+
+    mode_ptr = GET_PTR(gi->video_mode_ptr);
+    bestmode = 0;
+    bestpxf = 0;
+    wantx = mbh->width  ? mbh->width  : 0xffff;
+    wanty = mbh->height ? mbh->height : 0xffff;
+    besterr = wantx + wanty;
+
+    while ((mode = *mode_ptr++) != 0xFFFF) {
+       mode &= 0x1FF;          /* The rest are attributes of sorts */
+
+       memset(mi, 0, sizeof *mi);
+       rm.eax.w[0] = 0x4F01;   /* Get SVGA mode information */
+       rm.ecx.w[0] = mode;
+       rm.edi.w[0] = OFFS(mi);
+       rm.es = SEG(mi);
+       __intcall(0x10, &rm, &rm);
+
+       /* Must be a supported mode */
+       if (rm.eax.w[0] != 0x004f)
+           continue;
+
+       /* Must be an LFB color graphics mode supported by the hardware.
+
+          The bits tested are:
+          7 - linear frame buffer
+          4 - graphics mode
+          3 - color mode
+          1 - mode information available (mandatory in VBE 1.2+)
+          0 - mode supported by hardware
+        */
+       if ((mi->mode_attr & 0x009b) != 0x009b)
+           continue;
+
+       /* We don't support multibank (interlaced memory) modes */
+       /*
+        *  Note: The Bochs VESA BIOS (vbe.c 1.58 2006/08/19) violates the
+        * specification which states that banks == 1 for unbanked modes;
+        * fortunately it does report bank_size == 0 for those.
+        */
+       if (mi->banks > 1 && mi->bank_size)
+           continue;
+
+       /* Must either be a packed-pixel mode or a direct color mode
+          (depending on VESA version ); must be a supported pixel format */
+       pxf = 0;                /* Not usable */
+
+       if (mi->bpp == 32 &&
+           (mi->memory_layout == 4 ||
+            (mi->memory_layout == 6 && mi->rpos == 16 && mi->gpos == 8 &&
+             mi->bpos == 0)))
+           pxf = 32;
+       else if (mi->bpp == 24 &&
+                (mi->memory_layout == 4 ||
+                 (mi->memory_layout == 6 && mi->rpos == 16 && mi->gpos == 8 &&
+                  mi->bpos == 0)))
+           pxf = 24;
+       else if (mi->bpp == 16 &&
+                (mi->memory_layout == 4 ||
+                 (mi->memory_layout == 6 && mi->rpos == 11 && mi->gpos == 5 &&
+                  mi->bpos == 0)))
+           pxf = 16;
+       else if (mi->bpp == 15 &&
+                (mi->memory_layout == 4 ||
+                 (mi->memory_layout == 6 && mi->rpos == 10 && mi->gpos == 5 &&
+                  mi->bpos == 0)))
+           pxf = 15;
+
+       better = false;
+
+       err = abs(mi->h_res - wantx) + abs(mi->v_res - wanty);
+
+       if (!bestpxf)
+           better = true;
+       else if ((vesa_info.mi.h_res < wantx || vesa_info.mi.v_res < wantx) &&
+                mi->h_res >= wantx && mi->v_res >= wanty)
+           /* This matches criteria, which the previous one didn't */
+           better = true;
+       else if (err < besterr)
+           better = true;
+       else if (err == besterr && (pxf == (int)mbh->depth || pxf > bestpxf))
+           better = true;
+
+       if (better) {
+           bestmode = mode;
+           bestpxf = pxf;
+           memcpy(&vesa_info.mi, mi, sizeof *mi);
+       }
+    }
+
+    if (!bestpxf)
+       return;                 /* No mode found */
+
+    mi = &vesa_info.mi;
+    mode = bestmode;
+
+    /* Now set video mode */
+    rm.eax.w[0] = 0x4F02;      /* Set SVGA video mode */
+    mode |= 0x4000;            /* Request linear framebuffer */
+    rm.ebx.w[0] = mode;
+    __intcall(0x10, &rm, &rm);
+    if (rm.eax.w[0] != 0x004F)
+       return;                 /* Failed to set mode */
+
+    mbi->flags |= MB_INFO_VIDEO_INFO;
+    mbi->vbe_mode = mode;
+    viaddr = map_data(&vesa_info, sizeof vesa_info, 4, 0);
+    mbi->vbe_control_info = viaddr + offsetof(struct vesa_info, gi);
+    mbi->vbe_mode_info = viaddr + offsetof(struct vesa_info, mi);
+
+    /* Get the VBE 2.x PM entry point if supported */
+    rm.eax.w[0] = 0x4F0A;
+    rm.ebx.w[0] = 0;
+    __intcall(0x10, &rm, &rm);
+    if (rm.eax.w[0] == 0x004F) {
+       mbi->vbe_interface_seg = rm.es;
+       mbi->vbe_interface_off = rm.edi.w[0];
+       mbi->vbe_interface_len = rm.ecx.w[0];
+    }
+
+    /* Tell syslinux we changed video mode */
+    rm.eax.w[0] = 0x0017;      /* Report video mode change */
+    /* In theory this should be:
+
+       rm.ebx.w[0] = (mi->mode_attr & 4) ? 0x0007 : 0x000f;
+
+       However, that would assume all systems that claim to handle text
+       output in VESA modes actually do that... */
+    rm.ebx.w[0] = 0x000f;
+    rm.ecx.w[0] = vesa_info.mi.h_res;
+    rm.edx.w[0] = vesa_info.mi.v_res;
+    __intcall(0x22, &rm, NULL);
+}
index 887776f..a32f9b3 100644 (file)
@@ -36,7 +36,6 @@
 
 static struct syslinux_movelist *ml = NULL;
 static struct syslinux_memmap *mmap = NULL, *amap = NULL;
-static struct multiboot_header *mbh;
 static addr_t mboot_high_water_mark = 0x100000;
 
 /*
@@ -100,8 +99,9 @@ int init_map(void)
     return 0;
 }
 
-int map_image(void *ptr, size_t len)
+struct multiboot_header *map_image(void *ptr, size_t len)
 {
+    struct multiboot_header *mbh;
     int mbh_len;
     char *cptr = ptr;
     Elf32_Ehdr *eh = ptr;
@@ -134,10 +134,10 @@ int map_image(void *ptr, size_t len)
     }
 
     if (mbh_len) {
-       bad_flags = mbh->flags & (MULTIBOOT_UNSUPPORTED | MULTIBOOT_VIDEO_MODE);
+       bad_flags = mbh->flags & MULTIBOOT_UNSUPPORTED;
        if (bad_flags) {
            printf("Unsupported Multiboot flags set: %#x\n", bad_flags);
-           return -1;
+           return NULL;
        }
     }
 
@@ -187,13 +187,13 @@ int map_image(void *ptr, size_t len)
                    printf
                        ("Memory segment at 0x%08x (len 0x%08x) is unavailable\n",
                         addr, msize);
-                   return -1;  /* Memory region unavailable */
+                   return NULL;        /* Memory region unavailable */
                }
 
                /* Mark this region as allocated in the available map */
                if (syslinux_add_memmap(&amap, addr, msize, SMT_ALLOC)) {
                    error("Overlapping segments found in ELF header\n");
-                   return -1;
+                   return NULL;
                }
 
                if (ph->p_filesz) {
@@ -201,7 +201,7 @@ int map_image(void *ptr, size_t len)
                    if (syslinux_add_movelist
                        (&ml, addr, (addr_t) cptr + ph->p_offset, dsize)) {
                        error("Failed to map PHDR data\n");
-                       return -1;
+                       return NULL;
                    }
                }
                if (msize > dsize) {
@@ -209,7 +209,7 @@ int map_image(void *ptr, size_t len)
                    if (syslinux_add_memmap
                        (&mmap, addr + dsize, msize - dsize, SMT_ZERO)) {
                        error("Failed to map PHDR zero region\n");
-                       return -1;
+                       return NULL;
                    }
                }
                if (addr + msize > mboot_high_water_mark)
@@ -235,7 +235,7 @@ int map_image(void *ptr, size_t len)
            addr = map_data(sh, len, 4096, MAP_HIGH | MAP_NOPAD);
            if (!addr) {
                error("Failed to map symbol table\n");
-               return -1;
+               return NULL;
            }
 
            mbinfo.flags |= MB_INFO_ELF_SHDR;
@@ -257,7 +257,7 @@ int map_image(void *ptr, size_t len)
                                align, MAP_HIGH);
                if (!addr) {
                    error("Failed to map symbol section\n");
-                   return -1;
+                   return NULL;
                }
                sh[i].sh_addr = addr;
            }
@@ -279,34 +279,34 @@ int map_image(void *ptr, size_t len)
            != SMT_FREE) {
            printf("Memory segment at 0x%08x (len 0x%08x) is unavailable\n",
                   mbh->load_addr, data_len + bss_len);
-           return -1;          /* Memory region unavailable */
+           return NULL;                /* Memory region unavailable */
        }
        if (syslinux_add_memmap(&amap, mbh->load_addr,
                                data_len + bss_len, SMT_ALLOC)) {
            error("Failed to claim a.out address space!\n");
-           return -1;
+           return NULL;
        }
        if (data_len)
            if (syslinux_add_movelist(&ml, mbh->load_addr, (addr_t) data_ptr,
                                      data_len)) {
                error("Failed to map a.out data\n");
-               return -1;
+               return NULL;
            }
        if (bss_len)
            if (syslinux_add_memmap
                (&mmap, mbh->load_end_addr, bss_len, SMT_ZERO)) {
                error("Failed to map a.out bss\n");
-               return -1;
+               return NULL;
            }
        if (mbh->bss_end_addr > mboot_high_water_mark)
            mboot_high_water_mark = mbh->bss_end_addr;
     } else {
        error
            ("Invalid Multiboot image: neither ELF header nor a.out kludge found\n");
-       return -1;
+       return NULL;
     }
 
-    return 0;
+    return mbh;
 }
 
 /*
index e7bb8db..8425e06 100644 (file)
@@ -152,6 +152,7 @@ int main(int argc, char *argv[])
 {
     int nmodules;
     struct module_data *modules;
+    struct multiboot_header *mbh;
     bool keeppxe = false;
 
     openconsole(&dev_null_r, &dev_stdcon_w);
@@ -193,7 +194,8 @@ int main(int argc, char *argv[])
      * Map the primary image.  This should be done before mapping anything
      * else, since it will have fixed address requirements.
      */
-    if (map_image(modules[0].data, modules[0].len))
+    mbh = map_image(modules[0].data, modules[0].len);
+    if (!mbh)
        return 1;
 
     /* Map the mbinfo structure */
@@ -223,6 +225,9 @@ int main(int argc, char *argv[])
     if (opt.solaris)
        mboot_solaris_dhcp_hack();
 
+    /* Set the graphics mode if requested */
+    set_graphics_mode(mbh, &mbinfo);
+
     /* Run it */
     mboot_run(keeppxe ? 3 : 0);
     error("mboot.c32: boot failed\n");
index e578620..993b31a 100644 (file)
@@ -79,7 +79,7 @@ extern struct my_options {
 #define MAP_NOPAD      2
 addr_t map_data(const void *data, size_t len, size_t align, int flags);
 addr_t map_string(const char *string);
-int map_image(void *ptr, size_t len);
+struct multiboot_header *map_image(void *ptr, size_t len);
 void mboot_run(int bootflags);
 int init_map(void);
 
@@ -92,4 +92,8 @@ void mboot_apm(void);
 /* solaris.c */
 void mboot_solaris_dhcp_hack(void);
 
+/* initvesa.c */
+void set_graphics_mode(const struct multiboot_header *mbh,
+                      struct multiboot_info *mbi);
+
 #endif /* MBOOT_H */
diff --git a/com32/mboot/vesa.h b/com32/mboot/vesa.h
new file mode 100644 (file)
index 0000000..ecc084a
--- /dev/null
@@ -0,0 +1,100 @@
+/* ----------------------------------------------------------------------- *
+ *
+ *   Copyright 1999-2008 H. Peter Anvin - All Rights Reserved
+ *
+ *   Permission is hereby granted, free of charge, to any person
+ *   obtaining a copy of this software and associated documentation
+ *   files (the "Software"), to deal in the Software without
+ *   restriction, including without limitation the rights to use,
+ *   copy, modify, merge, publish, distribute, sublicense, and/or
+ *   sell copies of the Software, and to permit persons to whom
+ *   the Software is furnished to do so, subject to the following
+ *   conditions:
+ *
+ *   The above copyright notice and this permission notice shall
+ *   be included in all copies or substantial portions of the Software.
+ *
+ *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ *   OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ *   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ *   HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ *   WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ *   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ *   OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * ----------------------------------------------------------------------- */
+
+#ifndef LIB_SYS_VESA_H
+#define LIB_SYS_VESA_H
+
+#include <inttypes.h>
+#include <com32.h>
+
+/* VESA General Information table */
+struct vesa_general_info {
+    uint32_t signature;                /* Magic number = "VESA" */
+    uint16_t version;
+    far_ptr_t vendor_string;
+    uint8_t capabilities[4];
+    far_ptr_t video_mode_ptr;
+    uint16_t total_memory;
+
+    uint16_t oem_software_rev;
+    far_ptr_t oem_vendor_name_ptr;
+    far_ptr_t oem_product_name_ptr;
+    far_ptr_t oem_product_rev_ptr;
+
+    uint8_t reserved[222];
+    uint8_t oem_data[256];
+} __attribute__ ((packed));
+
+#define VESA_MAGIC ('V' + ('E' << 8) + ('S' << 16) + ('A' << 24))
+#define VBE2_MAGIC ('V' + ('B' << 8) + ('E' << 16) + ('2' << 24))
+
+struct vesa_mode_info {
+    uint16_t mode_attr;
+    uint8_t win_attr[2];
+    uint16_t win_grain;
+    uint16_t win_size;
+    uint16_t win_seg[2];
+    far_ptr_t win_scheme;
+    uint16_t logical_scan;
+
+    uint16_t h_res;
+    uint16_t v_res;
+    uint8_t char_width;
+    uint8_t char_height;
+    uint8_t memory_planes;
+    uint8_t bpp;
+    uint8_t banks;
+    uint8_t memory_layout;
+    uint8_t bank_size;
+    uint8_t image_pages;
+    uint8_t page_function;
+
+    uint8_t rmask;
+    uint8_t rpos;
+    uint8_t gmask;
+    uint8_t gpos;
+    uint8_t bmask;
+    uint8_t bpos;
+    uint8_t resv_mask;
+    uint8_t resv_pos;
+    uint8_t dcm_info;
+
+    uint8_t *lfb_ptr;          /* Linear frame buffer address */
+    uint8_t *offscreen_ptr;    /* Offscreen memory address */
+    uint16_t offscreen_size;
+
+    uint8_t reserved[206];
+} __attribute__ ((packed));
+
+struct vesa_info {
+    struct vesa_general_info gi;
+    struct vesa_mode_info mi;
+};
+
+extern struct vesa_info vesa_info;
+
+#endif /* LIB_SYS_VESA_H */