The Pedigree Project 0.1
framebuffer.h
1/* Copyright (c) 2026, Pedigree Developers. SPDX-License-Identifier: ISC */
2#ifndef PEDIGREE_UEFI_FRAMEBUFFER_H
3#define PEDIGREE_UEFI_FRAMEBUFFER_H
4
5#include <stdint.h>
6
7typedef struct efi_graphics_mode_info {
8 uint32_t version;
9 uint32_t width;
10 uint32_t height;
11 uint32_t pixel_format;
12 uint32_t red_mask;
13 uint32_t green_mask;
14 uint32_t blue_mask;
15 uint32_t reserved_mask;
16 uint32_t pixels_per_scanline;
18
19typedef struct efi_graphics_mode {
20 uint32_t max_mode;
21 uint32_t mode;
23 uint64_t info_size;
24 uint64_t framebuffer;
25 uint64_t framebuffer_size;
27
28typedef struct efi_graphics_output {
29 uint64_t (*query_mode)(struct efi_graphics_output*, uint32_t, uint64_t*,
31 uint64_t (*set_mode)(struct efi_graphics_output*, uint32_t);
32 void* blt;
35
36typedef struct boot_framebuffer {
37 uint64_t address;
38 uint32_t width, height, pitch, bpp, format;
40
41typedef uint64_t (*efi_free_pool_t)(void*);
42
43typedef struct efi_edid_active {
44 uint32_t size;
45 uint8_t* bytes;
47
48static int framebuffer_format(const efi_graphics_mode_info_t* info, uint32_t* output) {
49 if (!info || !info->width || !info->height || info->pixels_per_scanline < info->width ||
50 info->pixels_per_scanline > UINT32_MAX / 4)
51 return 0;
52
53 uint32_t format = info->pixel_format;
54 if (format == 2) {
55 // Only layouts that can use the same 32-bit renderer as the standard modes.
56 if (info->green_mask != 0x0000ff00 ||
57 (info->reserved_mask && info->reserved_mask != 0xff000000))
58 return 0;
59 if (info->red_mask == 0x000000ff && info->blue_mask == 0x00ff0000)
60 format = 0;
61 else if (info->red_mask == 0x00ff0000 && info->blue_mask == 0x000000ff)
62 format = 1;
63 else
64 return 0;
65 }
66 if (format > 1)
67 return 0;
68 *output = format;
69 return 1;
70}
71
72static int decode_framebuffer(const efi_graphics_output_t* graphics, boot_framebuffer_t* output) {
73 if (!graphics || !graphics->mode)
74 return 0;
75 const efi_graphics_mode_t* mode = graphics->mode;
76 const efi_graphics_mode_info_t* info = mode->info;
77 uint32_t format;
78 if (!info || mode->info_size < sizeof(*info) || !mode->max_mode || mode->mode >= mode->max_mode ||
79 !mode->framebuffer || !framebuffer_format(info, &format))
80 return 0;
81
82 const uint32_t pitch = info->pixels_per_scanline * 4;
83 const uint64_t span = (uint64_t)pitch * info->height;
84 if (span > mode->framebuffer_size || mode->framebuffer_size > UINT64_MAX - mode->framebuffer)
85 return 0;
86 *output = (boot_framebuffer_t){mode->framebuffer, info->width, info->height, pitch, 32, format};
87 return 1;
88}
89
90static int preferred_framebuffer_size(const efi_edid_active_t* edid, uint32_t* width,
91 uint32_t* height) {
92 static const uint8_t header[] = {0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0};
93 if (!edid || !edid->bytes || edid->size < 128)
94 return 0;
95 const uint8_t* bytes = edid->bytes;
96 uint8_t checksum = 0;
97 for (unsigned i = 0; i < sizeof(header); ++i)
98 if (bytes[i] != header[i])
99 return 0;
100 for (unsigned i = 0; i < 128; ++i)
101 checksum += bytes[i];
102 if (checksum || bytes[18] != 1 || !(bytes[24] & 2))
103 return 0;
104 const uint8_t* timing = bytes + 54;
105 if (!(timing[0] | timing[1]) || (timing[17] & 0x80))
106 return 0;
107 const uint32_t x = timing[2] | ((uint32_t)(timing[4] & 0xf0) << 4);
108 const uint32_t y = timing[5] | ((uint32_t)(timing[7] & 0xf0) << 4);
109 if (!x || !y)
110 return 0;
111 *width = x;
112 *height = y;
113 return 1;
114}
115
116static int select_framebuffer_mode(efi_graphics_output_t* graphics, efi_free_pool_t free_pool,
117 const efi_edid_active_t* edid, boot_framebuffer_t* output) {
118 // A boot manager may have deliberately selected this mode. Only negotiate
119 // another mode when the current GOP cannot provide a usable framebuffer.
120 if (decode_framebuffer(graphics, output))
121 return 1;
122 if (!graphics || !graphics->mode || !graphics->query_mode || !graphics->set_mode || !free_pool)
123 return 0;
124 uint32_t preferred_width = 0, preferred_height = 0;
125 preferred_framebuffer_size(edid, &preferred_width, &preferred_height);
126 const uint32_t count = graphics->mode->max_mode;
127 int ceiling_preferred = 2;
128 uint64_t ceiling_area = UINT64_MAX;
129 uint32_t ceiling_mode = UINT32_MAX;
130 for (uint32_t attempt = 0; attempt < count; ++attempt) {
131 int best_preferred = -1;
132 uint64_t best_area = 0;
133 uint32_t best_mode = 0, best_width = 0, best_height = 0;
134 for (uint32_t candidate = 0; candidate < count; ++candidate) {
135 efi_graphics_mode_info_t* info = 0;
136 uint64_t size = 0;
137 uint32_t format;
138 const uint64_t status = graphics->query_mode(graphics, candidate, &size, &info);
139 if (!status && info && size >= sizeof(*info) && framebuffer_format(info, &format)) {
140 const int preferred = info->width == preferred_width && info->height == preferred_height;
141 const uint64_t area = (uint64_t)info->width * info->height;
142 const int below_ceiling =
143 preferred < ceiling_preferred ||
144 (preferred == ceiling_preferred &&
145 (area < ceiling_area || (area == ceiling_area && candidate < ceiling_mode)));
146 if (below_ceiling &&
147 (preferred > best_preferred ||
148 (preferred == best_preferred &&
149 (area > best_area || (area == best_area && candidate > best_mode))))) {
150 best_preferred = preferred;
151 best_area = area;
152 best_mode = candidate;
153 best_width = info->width;
154 best_height = info->height;
155 }
156 }
157 if (info)
158 free_pool(info);
159 }
160 if (best_preferred < 0)
161 return 0;
162 if (!graphics->set_mode(graphics, best_mode) && graphics->mode &&
163 graphics->mode->mode == best_mode && decode_framebuffer(graphics, output) &&
164 output->width == best_width && output->height == best_height)
165 return 1;
166 // Retry lower-ranked candidates without retaining firmware-owned QueryMode buffers.
167 ceiling_preferred = best_preferred;
168 ceiling_area = best_area;
169 ceiling_mode = best_mode;
170 }
171 return 0;
172}
173
174#endif