:: limine / common / drivers / gop.c 11.5 KB raw

1
#if defined (UEFI)
2
3
#include <stdint.h>
4
#include <stddef.h>
5
#include <efi.h>
6
#include <lib/misc.h>
7
#include <lib/term.h>
8
#include <drivers/gop.h>
9
#include <drivers/edid.h>
10
#include <lib/print.h>
11
#include <mm/pmm.h>
12
13
static uint16_t linear_masks_to_bpp(uint32_t red_mask, uint32_t green_mask,
14
                                    uint32_t blue_mask, uint32_t alpha_mask) {
15
    uint32_t compound_mask = red_mask | green_mask | blue_mask | alpha_mask;
16
    uint16_t ret = 32;
17
    while ((compound_mask & (1U << 31)) == 0) {
18
        ret--;
19
        compound_mask <<= 1;
20
    }
21
    return ret;
22
}
23
24
static void linear_mask_to_mask_shift(
25
                uint8_t *mask, uint8_t *shift, uint32_t linear_mask) {
26
    *shift = 0;
27
    *mask = 0;
28
    if (linear_mask == 0) {
29
        return;
30
    }
31
    while ((linear_mask & 1) == 0) {
32
        (*shift)++;
33
        linear_mask >>= 1;
34
    }
35
    while ((linear_mask & 1) == 1) {
36
        (*mask)++;
37
        linear_mask >>= 1;
38
    }
39
}
40
41
static bool validate_pitch(struct fb_info *ret, size_t mode) {
42
    uint64_t bytes_per_pixel = ret->framebuffer_bpp / 8;
43
    if (bytes_per_pixel == 0
44
     || ret->framebuffer_pitch % bytes_per_pixel != 0
45
     || ret->framebuffer_pitch < ret->framebuffer_width * bytes_per_pixel) {
46
        printv("gop: Mode %u has invalid pitch %u (width=%u, bpp=%u), skipping.\n",
47
               (uint32_t)mode, (uint32_t)ret->framebuffer_pitch,
48
               (uint32_t)ret->framebuffer_width, (uint32_t)ret->framebuffer_bpp);
49
        return false;
50
    }
51
    return true;
52
}
53
54
// Most of this code taken from https://wiki.osdev.org/GOP
55
56
static bool mode_to_fb_info(struct fb_info *ret, EFI_GRAPHICS_OUTPUT_PROTOCOL *gop, size_t mode) {
57
    EFI_STATUS status;
58
59
    EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *mode_info;
60
    UINTN mode_info_size;
61
62
    status = gop->QueryMode(gop, mode, &mode_info_size, &mode_info);
63
64
    if (status) {
65
        return false;
66
    }
67
68
    switch (mode_info->PixelFormat) {
69
        case PixelBlueGreenRedReserved8BitPerColor:
70
            ret->framebuffer_bpp = 32;
71
            ret->red_mask_size = 8;
72
            ret->red_mask_shift = 16;
73
            ret->green_mask_size = 8;
74
            ret->green_mask_shift = 8;
75
            ret->blue_mask_size = 8;
76
            ret->blue_mask_shift = 0;
77
            break;
78
        case PixelRedGreenBlueReserved8BitPerColor:
79
            ret->framebuffer_bpp = 32;
80
            ret->red_mask_size = 8;
81
            ret->red_mask_shift = 0;
82
            ret->green_mask_size = 8;
83
            ret->green_mask_shift = 8;
84
            ret->blue_mask_size = 8;
85
            ret->blue_mask_shift = 16;
86
            break;
87
        case PixelBitMask:
88
            if ((mode_info->PixelInformation.RedMask
89
               | mode_info->PixelInformation.GreenMask
90
               | mode_info->PixelInformation.BlueMask
91
               | mode_info->PixelInformation.ReservedMask) == 0) {
92
                return false;
93
            }
94
            ret->framebuffer_bpp = linear_masks_to_bpp(
95
                                      mode_info->PixelInformation.RedMask,
96
                                      mode_info->PixelInformation.GreenMask,
97
                                      mode_info->PixelInformation.BlueMask,
98
                                      mode_info->PixelInformation.ReservedMask);
99
            linear_mask_to_mask_shift(&ret->red_mask_size,
100
                                      &ret->red_mask_shift,
101
                                      mode_info->PixelInformation.RedMask);
102
            linear_mask_to_mask_shift(&ret->green_mask_size,
103
                                      &ret->green_mask_shift,
104
                                      mode_info->PixelInformation.GreenMask);
105
            linear_mask_to_mask_shift(&ret->blue_mask_size,
106
                                      &ret->blue_mask_shift,
107
                                      mode_info->PixelInformation.BlueMask);
108
            break;
109
        default:
110
            return false;
111
    }
112
113
    ret->memory_model = 0x06;
114
    ret->framebuffer_pitch = mode_info->PixelsPerScanLine * (ret->framebuffer_bpp / 8);
115
    ret->framebuffer_width = mode_info->HorizontalResolution;
116
    ret->framebuffer_height = mode_info->VerticalResolution;
117
118
    if (!validate_pitch(ret, mode)) {
119
        return false;
120
    }
121
122
    return true;
123
}
124
125
bool gop_force_16 = false;
126
127
static bool try_mode(struct fb_info *ret, EFI_GRAPHICS_OUTPUT_PROTOCOL *gop,
128
                     size_t mode, uint64_t width, uint64_t height, int bpp,
129
                     struct fb_info *fbs, size_t fbs_count) {
130
    EFI_STATUS status;
131
132
    if (!mode_to_fb_info(ret, gop, mode)) {
133
        return false;
134
    }
135
136
    if (width != 0 && height != 0 && bpp != 0) {
137
        if (ret->framebuffer_width != width
138
         || ret->framebuffer_height != height
139
         || ret->framebuffer_bpp != bpp) {
140
            return false;
141
        }
142
    }
143
144
    if (gop_force_16) {
145
        if (ret->framebuffer_width >= 65536
146
         || ret->framebuffer_height >= 65536
147
         || ret->framebuffer_pitch >= 65536) {
148
            return false;
149
        }
150
    }
151
152
    for (size_t i = 0; i < fbs_count; i++) {
153
        if (gop->Mode->FrameBufferBase == fbs[i].framebuffer_addr) {
154
            return false;
155
        }
156
    }
157
158
    printv("gop: Found matching mode %X, attempting to set...\n", (uint64_t)mode);
159
160
    if (mode == gop->Mode->Mode) {
161
        printv("gop: Mode was already set, perfect!\n");
162
    } else {
163
        status = gop->SetMode(gop, mode);
164
165
        if (status) {
166
            printv("gop: Failed to set video mode %X, moving on...\n", (uint64_t)mode);
167
            return false;
168
        }
169
    }
170
171
    // Recalculate pitch from gop->Mode->Info, as some firmware (e.g. Apple
172
    // Macs) report incorrect PixelsPerScanLine via QueryMode.
173
    ret->framebuffer_pitch = gop->Mode->Info->PixelsPerScanLine * (ret->framebuffer_bpp / 8);
174
175
    if (!validate_pitch(ret, mode)) {
176
        return false;
177
    }
178
179
    ret->framebuffer_addr = gop->Mode->FrameBufferBase;
180
181
    return true;
182
}
183
184
static struct fb_info *get_mode_list(size_t *count, EFI_GRAPHICS_OUTPUT_PROTOCOL *gop) {
185
    UINTN modes_count = gop->Mode->MaxMode;
186
187
    struct fb_info *ret = ext_mem_alloc_counted(modes_count, sizeof(struct fb_info));
188
189
    size_t actual_count = 0;
190
    for (size_t i = 0; i < modes_count; i++) {
191
        if (mode_to_fb_info(&ret[actual_count], gop, i)) {
192
            actual_count++;
193
        }
194
    }
195
196
    struct fb_info *tmp = ext_mem_alloc_counted(actual_count, sizeof(struct fb_info));
197
    memcpy(tmp, ret, actual_count * sizeof(struct fb_info));
198
199
    pmm_free(ret, modes_count * sizeof(struct fb_info));
200
    ret = tmp;
201
202
    *count = actual_count;
203
    return ret;
204
}
205
206
#define MAX_PRESET_MODES 128
207
no_unwind static int preset_modes[MAX_PRESET_MODES];
208
no_unwind static bool preset_modes_initialised = false;
209
210
void init_gop(struct fb_info **ret, size_t *_fbs_count,
211
              uint64_t target_width, uint64_t target_height, uint16_t target_bpp) {
212
    if (preset_modes_initialised == false) {
213
        for (size_t i = 0; i < MAX_PRESET_MODES; i++) {
214
            preset_modes[i] = -1;
215
        }
216
        preset_modes_initialised = true;
217
    }
218
219
    EFI_STATUS status;
220
221
    EFI_HANDLE tmp_handles[1];
222
223
    EFI_HANDLE *handles = tmp_handles;
224
    UINTN handles_size = sizeof(EFI_HANDLE);
225
    EFI_GUID gop_guid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
226
227
    status = gBS->LocateHandle(ByProtocol, &gop_guid, NULL, &handles_size, handles);
228
229
    if (status != EFI_SUCCESS && status != EFI_BUFFER_TOO_SMALL) {
230
        *_fbs_count = 0;
231
        return;
232
    }
233
234
    UINTN handles_alloc = handles_size;
235
    handles = ext_mem_alloc(handles_alloc);
236
237
    status = gBS->LocateHandle(ByProtocol, &gop_guid, NULL, &handles_size, handles);
238
    if (status != EFI_SUCCESS) {
239
        pmm_free(handles, handles_alloc);
240
        *_fbs_count = 0;
241
        return;
242
    }
243
244
    size_t handles_count = handles_size / sizeof(EFI_HANDLE);
245
246
    *ret = ext_mem_alloc_counted(handles_count, sizeof(struct fb_info));
247
248
    const struct resolution fallback_resolutions[] = {
249
        { 0,    0,   0  },   // Overridden by EDID
250
        { 0,    0,   0  },   // Overridden by preset
251
        { 1024, 768, 32 },
252
        { 800,  600, 32 },
253
        { 640,  480, 32 },
254
        { 1024, 768, 24 },
255
        { 800,  600, 24 },
256
        { 640,  480, 24 },
257
        { 1024, 768, 16 },
258
        { 800,  600, 16 },
259
        { 640,  480, 16 }
260
    };
261
262
    size_t fbs_count = 0;
263
    for (size_t i = 0; i < handles_count && i < MAX_PRESET_MODES; i++) {
264
        struct fb_info *fb = &(*ret)[fbs_count];
265
266
        uint64_t _target_width = target_width;
267
        uint64_t _target_height = target_height;
268
        uint64_t _target_bpp = target_bpp;
269
270
        EFI_GRAPHICS_OUTPUT_PROTOCOL *gop;
271
272
        status = gBS->HandleProtocol(handles[i], &gop_guid, (void **)&gop);
273
        if (status != EFI_SUCCESS) {
274
            continue;
275
        }
276
277
        EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *mode_info;
278
        UINTN mode_info_size;
279
280
        status = gop->QueryMode(gop, gop->Mode == NULL ? 0 : gop->Mode->Mode,
281
                                &mode_info_size, &mode_info);
282
283
        if (status == EFI_NOT_STARTED) {
284
            if (fbs_count > 0) {
285
                continue;
286
            }
287
            status = gop->SetMode(gop, 0);
288
            if (status) {
289
                continue;
290
            }
291
            status = gop->QueryMode(gop, gop->Mode == NULL ? 0 : gop->Mode->Mode,
292
                                    &mode_info_size, &mode_info);
293
        }
294
295
        if (status) {
296
            continue;
297
        }
298
299
        if (preset_modes[i] == -1) {
300
            preset_modes[i] = gop->Mode->Mode;
301
        }
302
303
        fb->edid = get_edid_info(handles[i]);
304
305
        UINTN modes_count = gop->Mode->MaxMode;
306
307
        size_t current_fallback = 0;
308
309
        if (!_target_width || !_target_height || !_target_bpp) {
310
            goto fallback;
311
        } else {
312
            printv("gop: Requested resolution of %ux%ux%u\n",
313
                   _target_width, _target_height, _target_bpp);
314
        }
315
316
retry:
317
        for (size_t j = 0; j < modes_count; j++) {
318
            if (try_mode(fb, gop, j, _target_width, _target_height, _target_bpp, *ret, fbs_count)) {
319
                goto success;
320
            }
321
        }
322
323
fallback:
324
        if (current_fallback == 0) {
325
            current_fallback++;
326
327
            if (fb->edid != NULL) {
328
                uint64_t edid_width = (uint64_t)fb->edid->det_timing_desc1[2];
329
                         edid_width += ((uint64_t)fb->edid->det_timing_desc1[4] & 0xf0) << 4;
330
                uint64_t edid_height = (uint64_t)fb->edid->det_timing_desc1[5];
331
                         edid_height += ((uint64_t)fb->edid->det_timing_desc1[7] & 0xf0) << 4;
332
                if (edid_width >= mode_info->HorizontalResolution
333
                 && edid_height >= mode_info->VerticalResolution) {
334
                    _target_width = edid_width;
335
                    _target_height = edid_height;
336
                    _target_bpp = 32;
337
                    goto retry;
338
                }
339
            }
340
        }
341
342
        if (current_fallback == 1) {
343
            current_fallback++;
344
345
            if (try_mode(fb, gop, preset_modes[i], 0, 0, 0, *ret, fbs_count)) {
346
                goto success;
347
            }
348
        }
349
350
        if (current_fallback < SIZEOF_ARRAY(fallback_resolutions)) {
351
            _target_width = fallback_resolutions[current_fallback].width;
352
            _target_height = fallback_resolutions[current_fallback].height;
353
            _target_bpp = fallback_resolutions[current_fallback].bpp;
354
355
            current_fallback++;
356
            goto retry;
357
        }
358
359
        continue;
360
361
success:;
362
        size_t mode_count;
363
        fb->mode_list = get_mode_list(&mode_count, gop);
364
        fb->mode_count = mode_count;
365
366
        fbs_count++;
367
    }
368
369
    pmm_free(handles, handles_alloc);
370
371
    gop_force_16 = false;
372
373
    *_fbs_count = fbs_count;
374
}
375
376
#endif
tab: 248 wrap: offon