:: commit 09941f905b1c60ebe22ba2185c28a43dbad73191

mintsuki <mintsuki@protonmail.com> — 2022-10-14 03:17

parents: 5e29b6574c

limine: Add support for framebuffer modes listing

diff --git a/PROTOCOL.md b/PROTOCOL.md
index 5b656cf2..97cbf6c4 100644
--- a/PROTOCOL.md
+++ b/PROTOCOL.md
@@ -580,6 +580,28 @@ struct limine_framebuffer {
     uint8_t unused[7];
     uint64_t edid_size;
     void *edid;
+
+    /* Revision 1 */
+    uint64_t mode_count;
+    struct limine_video_mode **modes;
+};
+
+`modes` is an array of `mode_count` pointers to `struct limine_video_mode` describing the
+available video modes for the given framebuffer.
+
+```
+struct limine_video_mode {
+    uint64_t pitch;
+    uint64_t width;
+    uint64_t height;
+    uint16_t bpp;
+    uint8_t memory_model;
+    uint8_t red_mask_size;
+    uint8_t red_mask_shift;
+    uint8_t green_mask_size;
+    uint8_t green_mask_shift;
+    uint8_t blue_mask_size;
+    uint8_t blue_mask_shift;
 };
 ```
 
diff --git a/common/drivers/gop.c b/common/drivers/gop.c
index 18b8734f..4e0b8833 100644
--- a/common/drivers/gop.c
+++ b/common/drivers/gop.c
@@ -37,19 +37,19 @@ static void linear_mask_to_mask_shift(
 
 // Most of this code taken from https://wiki.osdev.org/GOP
 
-bool gop_force_16 = false;
-
-static bool try_mode(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop,
-                     struct fb_info *ret, size_t mode, uint64_t width, uint64_t height, int bpp) {
+static bool mode_to_fb_info(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop, struct fb_info *ret, size_t mode) {
     EFI_STATUS status;
 
+    ret->default_res = false;
+
     EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *mode_info;
     UINTN mode_info_size;
 
     status = gop->QueryMode(gop, mode, &mode_info_size, &mode_info);
 
-    if (status)
+    if (status) {
         return false;
+    }
 
     switch (mode_info->PixelFormat) {
         case PixelBlueGreenRedReserved8BitPerColor:
@@ -87,20 +87,39 @@ static bool try_mode(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop,
                                       mode_info->PixelInformation.BlueMask);
             break;
         default:
-            panic(false, "gop: Invalid PixelFormat");
+            return false;
+    }
+
+    ret->memory_model = 0x06;
+    ret->framebuffer_pitch = mode_info->PixelsPerScanLine * (ret->framebuffer_bpp / 8);
+    ret->framebuffer_width = mode_info->HorizontalResolution;
+    ret->framebuffer_height = mode_info->VerticalResolution;
+
+    return true;
+}
+
+bool gop_force_16 = false;
+
+static bool try_mode(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop,
+                     struct fb_info *ret, size_t mode, uint64_t width, uint64_t height, int bpp) {
+    EFI_STATUS status;
+
+    if (!mode_to_fb_info(gop, ret, mode)) {
+        return false;
     }
 
     if (width != 0 && height != 0 && bpp != 0) {
-        if ((uint64_t)mode_info->HorizontalResolution != width
-         || (uint64_t)mode_info->VerticalResolution != height
-         || (int)ret->framebuffer_bpp != bpp)
+        if (ret->framebuffer_width != width
+         || ret->framebuffer_height != height
+         || ret->framebuffer_bpp != bpp) {
             return false;
+        }
     }
 
     if (gop_force_16) {
-        if (mode_info->HorizontalResolution >= 65536
-         || mode_info->VerticalResolution >= 65536
-         || mode_info->PixelsPerScanLine * (ret->framebuffer_bpp / 8) >= 65536) {
+        if (ret->framebuffer_width >= 65536
+         || ret->framebuffer_height >= 65536
+         || ret->framebuffer_pitch >= 65536) {
             return false;
         }
     }
@@ -121,17 +140,67 @@ static bool try_mode(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop,
 
     current_video_mode = mode;
 
-    ret->memory_model = 0x06;
     ret->framebuffer_addr = gop->Mode->FrameBufferBase;
-    ret->framebuffer_pitch = gop->Mode->Info->PixelsPerScanLine * (ret->framebuffer_bpp / 8);
-    ret->framebuffer_width = gop->Mode->Info->HorizontalResolution;
-    ret->framebuffer_height = gop->Mode->Info->VerticalResolution;
 
     fb_clear(ret);
 
     return true;
 }
 
+struct fb_info *gop_get_mode_list(size_t *count) {
+    EFI_STATUS status;
+
+    EFI_HANDLE tmp_handles[1];
+
+    EFI_HANDLE *handles = tmp_handles;
+    UINTN handles_size = sizeof(EFI_HANDLE);
+    EFI_GUID gop_guid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
+
+    status = gBS->LocateHandle(ByProtocol, &gop_guid, NULL, &handles_size, handles);
+
+    if (status != EFI_SUCCESS && status != EFI_BUFFER_TOO_SMALL) {
+        return false;
+    }
+
+    handles = ext_mem_alloc(handles_size);
+
+    status = gBS->LocateHandle(ByProtocol, &gop_guid, NULL, &handles_size, handles);
+    if (status != EFI_SUCCESS) {
+        pmm_free(handles, handles_size);
+        return false;
+    }
+
+    EFI_HANDLE gop_handle = handles[0];
+    pmm_free(handles, handles_size);
+
+    EFI_GRAPHICS_OUTPUT_PROTOCOL *gop;
+
+    status = gBS->HandleProtocol(gop_handle, &gop_guid, (void **)&gop);
+    if (status != EFI_SUCCESS) {
+        return false;
+    }
+
+    UINTN modes_count = gop->Mode->MaxMode;
+
+    struct fb_info *ret = ext_mem_alloc(modes_count * sizeof(struct fb_info));
+
+    size_t actual_count = 0;
+    for (size_t i = 0; i < modes_count; i++) {
+        if (mode_to_fb_info(gop, &ret[actual_count], i)) {
+            actual_count++;
+        }
+    }
+
+    struct fb_info *tmp = ext_mem_alloc(actual_count * sizeof(struct fb_info));
+    memcpy(tmp, ret, actual_count * sizeof(struct fb_info));
+
+    pmm_free(ret, modes_count * sizeof(struct fb_info));
+    ret = tmp;
+
+    *count = modes_count;
+    return ret;
+}
+
 #define INVALID_PRESET_MODE 0xffffffff
 
 static no_unwind size_t preset_mode = INVALID_PRESET_MODE;
diff --git a/common/drivers/gop.h b/common/drivers/gop.h
index bbc44207..3b8afe53 100644
--- a/common/drivers/gop.h
+++ b/common/drivers/gop.h
@@ -4,6 +4,7 @@
 #if defined (UEFI)
 
 #include <stdint.h>
+#include <stddef.h>
 #include <stdbool.h>
 #include <efi.h>
 #include <lib/fb.h>
@@ -11,6 +12,8 @@
 bool init_gop(struct fb_info *ret,
               uint64_t target_width, uint64_t target_height, uint16_t target_bpp);
 
+struct fb_info *gop_get_mode_list(size_t *count);
+
 extern bool gop_force_16;
 
 #endif
diff --git a/common/drivers/vbe.c b/common/drivers/vbe.c
index 7273f471..2ecae69d 100644
--- a/common/drivers/vbe.c
+++ b/common/drivers/vbe.c
@@ -116,6 +116,73 @@ static int set_vbe_mode(uint16_t mode) {
     return r.eax & 0xff;
 }
 
+struct fb_info *vbe_get_mode_list(size_t *count) {
+    struct vbe_info_struct vbe_info;
+    get_vbe_info(&vbe_info);
+
+    uint16_t *vid_modes = (uint16_t *)rm_desegment(vbe_info.vid_modes_seg,
+                                                   vbe_info.vid_modes_off);
+
+    size_t modes_count = 0;
+    for (size_t i = 0; vid_modes[i] != 0xffff; i++) {
+        struct vbe_mode_info_struct vbe_mode_info;
+        get_vbe_mode_info(&vbe_mode_info, vid_modes[i]);
+
+        // We only support RGB for now
+        if (vbe_mode_info.memory_model != 0x06)
+            continue;
+        // We only support linear modes
+        if (!(vbe_mode_info.mode_attributes & (1 << 7)))
+            continue;
+
+        modes_count++;
+    }
+
+    struct fb_info *ret = ext_mem_alloc(modes_count * sizeof(struct fb_info));
+
+    for (size_t i = 0, j = 0; vid_modes[i] != 0xffff; i++) {
+        struct vbe_mode_info_struct vbe_mode_info;
+        get_vbe_mode_info(&vbe_mode_info, vid_modes[i]);
+
+        // We only support RGB for now
+        if (vbe_mode_info.memory_model != 0x06)
+            continue;
+        // We only support linear modes
+        if (!(vbe_mode_info.mode_attributes & (1 << 7)))
+            continue;
+
+        ret[j].memory_model = vbe_mode_info.memory_model;
+
+        ret[j].framebuffer_width = vbe_mode_info.res_x;
+        ret[j].framebuffer_height = vbe_mode_info.res_y;
+        ret[j].framebuffer_bpp = vbe_mode_info.bpp;
+
+        if (vbe_info.version_maj < 3) {
+            ret[j].framebuffer_pitch  = vbe_mode_info.bytes_per_scanline;
+            ret[j].red_mask_size      = vbe_mode_info.red_mask_size;
+            ret[j].red_mask_shift     = vbe_mode_info.red_mask_shift;
+            ret[j].green_mask_size    = vbe_mode_info.green_mask_size;
+            ret[j].green_mask_shift   = vbe_mode_info.green_mask_shift;
+            ret[j].blue_mask_size     = vbe_mode_info.blue_mask_size;
+            ret[j].blue_mask_shift    = vbe_mode_info.blue_mask_shift;
+        } else {
+            ret[j].framebuffer_pitch  = vbe_mode_info.lin_bytes_per_scanline;
+            ret[j].red_mask_size      = vbe_mode_info.lin_red_mask_size;
+            ret[j].red_mask_shift     = vbe_mode_info.lin_red_mask_shift;
+            ret[j].green_mask_size    = vbe_mode_info.lin_green_mask_size;
+            ret[j].green_mask_shift   = vbe_mode_info.lin_green_mask_shift;
+            ret[j].blue_mask_size     = vbe_mode_info.lin_blue_mask_size;
+            ret[j].blue_mask_shift    = vbe_mode_info.lin_blue_mask_shift;
+        }
+
+        j++;
+    }
+
+    *count = modes_count;
+
+    return ret;
+}
+
 bool init_vbe(struct fb_info *ret,
               uint16_t target_width, uint16_t target_height, uint16_t target_bpp) {
     printv("vbe: Initialising...\n");
diff --git a/common/drivers/vbe.h b/common/drivers/vbe.h
index a212e486..37b5e234 100644
--- a/common/drivers/vbe.h
+++ b/common/drivers/vbe.h
@@ -2,10 +2,13 @@
 #define __DRIVERS__VBE_H__
 
 #include <stdint.h>
+#include <stddef.h>
 #include <stdbool.h>
 #include <lib/fb.h>
 
 bool init_vbe(struct fb_info *ret,
               uint16_t target_width, uint16_t target_height, uint16_t target_bpp);
 
+struct fb_info *vbe_get_mode_list(size_t *count);
+
 #endif
diff --git a/common/lib/fb.c b/common/lib/fb.c
index f291e01d..95057e1c 100644
--- a/common/lib/fb.c
+++ b/common/lib/fb.c
@@ -19,6 +19,14 @@ bool fb_init(struct fb_info *ret,
     return r;
 }
 
+struct fb_info *fb_get_mode_list(size_t *count) {
+#if defined (BIOS)
+    return vbe_get_mode_list(count);
+#elif defined (UEFI)
+    return gop_get_mode_list(count);
+#endif
+}
+
 void fb_clear(struct fb_info *fb) {
     for (size_t y = 0; y < fb->framebuffer_height; y++) {
         switch (fb->framebuffer_bpp) {
diff --git a/common/lib/fb.h b/common/lib/fb.h
index f2428b76..83c65170 100644
--- a/common/lib/fb.h
+++ b/common/lib/fb.h
@@ -2,6 +2,7 @@
 #define __LIB__FB_H__
 
 #include <stdint.h>
+#include <stddef.h>
 
 struct resolution {
     uint64_t width;
@@ -10,24 +11,27 @@ struct resolution {
 };
 
 struct fb_info {
-    bool default_res;
-    uint8_t  memory_model;
-    uint64_t framebuffer_addr;
     uint64_t framebuffer_pitch;
     uint64_t framebuffer_width;
     uint64_t framebuffer_height;
     uint16_t framebuffer_bpp;
+    uint8_t  memory_model;
     uint8_t  red_mask_size;
     uint8_t  red_mask_shift;
     uint8_t  green_mask_size;
     uint8_t  green_mask_shift;
     uint8_t  blue_mask_size;
     uint8_t  blue_mask_shift;
+
+    bool default_res;
+    uint64_t framebuffer_addr;
 };
 
 bool fb_init(struct fb_info *ret,
              uint64_t target_width, uint64_t target_height, uint16_t target_bpp);
 
+struct fb_info *fb_get_mode_list(size_t *count);
+
 void fb_clear(struct fb_info *fb);
 
 #endif
diff --git a/common/protos/limine.c b/common/protos/limine.c
index 9a40aa30..979a5bc7 100644
--- a/common/protos/limine.c
+++ b/common/protos/limine.c
@@ -522,7 +522,6 @@ FEAT_START
     smbios_request->response = reported_addr(smbios_response);
 FEAT_END
 
-
 #if defined (UEFI)
     // EFI system table feature
 FEAT_START
@@ -791,6 +790,20 @@ FEAT_START
         fbp->edid = reported_addr(edid_info);
     }
 
+    framebuffer_response->revision = 1;
+
+    size_t modes_count;
+    struct fb_info *modes = fb_get_mode_list(&modes_count);
+    if (modes != NULL) {
+        uint64_t *modes_list = ext_mem_alloc(modes_count * sizeof(uint64_t));
+        for (size_t i = 0; i < modes_count; i++) {
+            modes[i].memory_model = LIMINE_FRAMEBUFFER_RGB;
+            modes_list[i] = reported_addr(&modes[i]);
+        }
+        fbp->modes = reported_addr(modes_list);
+        fbp->mode_count = modes_count;
+    }
+
     fbp->memory_model     = LIMINE_FRAMEBUFFER_RGB;
     fbp->address          = reported_addr((void *)(uintptr_t)fb.framebuffer_addr);
     fbp->width            = fb.framebuffer_width;
diff --git a/limine.h b/limine.h
index 774fb0b5..a929f2c3 100644
--- a/limine.h
+++ b/limine.h
@@ -97,6 +97,20 @@ struct limine_hhdm_request {
 
 #define LIMINE_FRAMEBUFFER_RGB 1
 
+struct limine_video_mode {
+    uint64_t pitch;
+    uint64_t width;
+    uint64_t height;
+    uint16_t bpp;
+    uint8_t memory_model;
+    uint8_t red_mask_size;
+    uint8_t red_mask_shift;
+    uint8_t green_mask_size;
+    uint8_t green_mask_shift;
+    uint8_t blue_mask_size;
+    uint8_t blue_mask_shift;
+};
+
 struct limine_framebuffer {
     LIMINE_PTR(void *) address;
     uint64_t width;
@@ -113,6 +127,9 @@ struct limine_framebuffer {
     uint8_t unused[7];
     uint64_t edid_size;
     LIMINE_PTR(void *) edid;
+    /* Revision 1 */
+    uint64_t mode_count;
+    LIMINE_PTR(struct limine_video_mode **) modes;
 };
 
 struct limine_framebuffer_response {
diff --git a/test/limine.c b/test/limine.c
index ded2abaa..27ec7b20 100644
--- a/test/limine.c
+++ b/test/limine.c
@@ -296,6 +296,10 @@ FEAT_START
         e9_printf("Blue mask shift: %d", fb->blue_mask_shift);
         e9_printf("EDID size: %d", fb->edid_size);
         e9_printf("EDID at: %x", fb->edid);
+        e9_printf("Video modes:");
+        for (size_t j = 0; j < fb->mode_count; j++) {
+            e9_printf("  %dx%dx%d", fb->modes[j]->width, fb->modes[j]->height, fb->modes[j]->bpp);
+        }
     }
 FEAT_END
 
tab: 248 wrap: offon