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
